┏━━━┓ ━┳━ ┏━━━┓
┗━┳━┛ ━┻━ ┣━━━┛
QIP Components are Quick to run/make/maintain, Isolated from network/disk/dependencies, and Portable across browser/server/native.
QIP Components are fast for you to create with coding agents and fast for users to run. Your users get small .wasm modules that load fast. You get small amounts of code that are easy to review.
We believe small functions should not need a massive application environment to run. QIP is for small pieces of software. Write or vibe Zig/C then compile to WebAssembly, and you get a deterministic puzzle piece that runs the same everywhere.
Use it for text, images, documents, archives, interactive UI, or any format. Components pass content in and content out, with an optional MIME type for each side. You can pipe component into another step-by-step like a recipe.
Make a recipe you like? You can be confident it will work identically on mobile, in a browser, in your CI pipeline, on Windows, or whatever comes next. If it works here, it works there.
Modern software never stops moving. QIP components are self-contained, so you can worry less about supply-chain attacks, outdated libraries, remote-code execution, and environment drift.
QIP is built around a strict contract: same component, same input, same output. It does not read the clock, locale, filesystem, package graph, environment variables, OS, device, chipset, or network — unless you deliberately pass it in. Every input is explicit. This means if it works today, it’ll work tomorrow.
Components, AI coding, security: you can pick all three.
npm install --global @qip.dev/qipxqipx requires Node.js 22 or newer.
Use the same rgb-to-hex.wasm component to render, benchmark, test, and debug.
Start by running it. qipx downloads the component from qip.dev and saves it at
text/rgb-to-hex.wasm:
printf 'rgb(101, 79, 240)' \
| npx @qip.dev/qipx qip.dev run text/rgb-to-hex.wasmIt prints #654ff0.
Benchmark that saved component on the same input:
printf 'rgb(101, 79, 240)' \
| npx @qip.dev/qipx qip.dev bench \
-i - --runs 100 text/rgb-to-hex.wasmTest its declared behavior with a reusable Compliance oracle:
npx @qip.dev/qipx qip.dev comply \
text/rgb-to-hex.wasm \
--with oracles/rgb-to-hex.comply.wasmThen open the same component in the interactive debugger:
npx @qip.dev/qipx qip.dev tui \
-F component=@text/rgb-to-hex.wasm \
-F 'input=rgb(101, 79, 240)' \
interactive/wasm-debugger.wasmPress s or ↓ to step into the next instruction, Space to continue, and
Ctrl-C to leave the debugger.
Multiple components run left to right. Hosts apply to every missing component in the pipeline:
printf 'qip + wasm\n' \
| npx @qip.dev/qipx qip.dev run \
bytes/zlib-compress.wasm \
bytes/base64-encode.wasmQIP does not use WASI or WIT, standards that have ballooned in complexity from scope creep. We want to get stuff done in today’s browsers so we pick a much smaller contract between hosts and modules:
input_ptr()/input_bytes_cap(): where the host writes input.input_content_type_ptr()/input_content_type_size(): MIME type of the inputoutput_bytes_cap(): the maximum output size.output_content_type_ptr()/output_content_type_size(): MIME type of the outputrender(input_size) -> i64: transform input and return its output pointer and size, or reject the input.- Optional
uniform_set_<key>(value): primitive integer or float parameters applied before rendering.
You can read more about the Content component contract in our docs.
You can pipe the results of other CLI tools to stdin, pass one raw file with
-i, or construct multipart input with repeatable -F options. You can also
chain multiple QIP components together.
With hosts, -F name=@path.wasm downloads and saves a missing safe relative
Wasm file using the same local-first rules as pipeline components.
Put one or more HTTPS hosts before run, dry run, bench, or comply to
load a missing component by its relative path:
printf '# Hello\n' \
| qipx qip.dev run text/markdown/gfm-commonmark.0.31.2.wasmqipx uses local components when available. Otherwise, it downloads them over HTTPS and saves them at the same local file path for later runs.
npm install --global @qip.dev/qipx
# Convert purple from rgb to hex
echo "rgb(101, 79, 240)" | qipx run components/text/rgb-to-hex.wasm
# #654ff0
# Normalize phone number
echo "+1 (212) 555-0100" | qipx run components/text/e164.wasm
# +12125550100
# Expand emoji shortcodes
echo "Run :rocket: WebAssembly components identically on any computer :sparkles:" | qipx run components/text/shortcode-to-emoji.wasm
# Run 🚀 WebAssembly components identically on any computer ✨
# Create zlib bytes (dynamic Huffman, shown as base64)
echo "qip + wasm" | qipx run components/bytes/zlib-compress-dynamic-huffman.wasm components/bytes/base64-encode.wasm
# eAEFwKENAAAMArBX8LtqcmIJBMH7VEcMsv4CEnkDbg==
# Round-trip zlib back to original text
echo "qip + wasm" | qipx run components/bytes/zlib-compress-dynamic-huffman.wasm components/bytes/zlib-decompress.wasm
# qip + wasm
# Load Hacker News, extract all links
curl -s https://news.ycombinator.com | qipx run components/text/html/html-link-extractor.wasm
# Render QIP logo to ICO
qipx run -i qip-logo.svg \
components/image/svg+xml/svg-rasterize-to-ktx2-r8g8b8a8-srgb.wasm \
components/image/ktx2/ktx2-r8g8b8a8-srgb-double.wasm \
components/image/ktx2/ktx2-r8g8b8a8-srgb-to-favicon.wasm \
> qip-logo.ico
# Render Switzerland flag SVG to ICO
echo '<svg width="32" height="32"><rect width="32" height="32" fill="#d52b1e" /><rect x="13" y="6" width="6" height="20" fill="#ffffff" /><rect x="6" y="13" width="20" height="6" fill="#ffffff" /></svg>' \
| qipx run \
components/image/svg+xml/svg-rasterize-to-ktx2-r8g8b8a8-srgb.wasm \
components/image/ktx2/ktx2-r8g8b8a8-srgb-to-favicon.wasm \
> switzerland-flag.ico
There are a few recommended languages for writing QIP components: Zig, C, or raw WebAssembly text format.
Here we’ll write an infallible QIP component for an E.164 canonicalizer. It
takes a phone number and converts it into a canonical international form. It
does not export failure_modes_per_input_offset, and every successful call
sets failed to zero.
+1 (212) 555-0100->+121255501001212-555-0100->+12125550100
// The input is maximum 64KiB
const INPUT_CAP: usize = 64 * 1024;
// The output is maximum 64KiB
const OUTPUT_CAP: usize = 64 * 1024;
var input_buf: [INPUT_CAP]u8 = undefined;
var output_buf: [OUTPUT_CAP]u8 = undefined;
// Export functions so the QIP host can read these static values.
export fn input_ptr() u32 {
return @as(u32, @intCast(@intFromPtr(&input_buf)));
}
export fn input_utf8_cap() u32 {
return @as(u32, @intCast(INPUT_CAP));
}
export fn output_utf8_cap() u32 {
return @as(u32, @intCast(OUTPUT_CAP));
}
fn isDigit(c: u8) bool {
return c >= '0' and c <= '9';
}
export fn render(input_size_in: u32) packed struct(u64) {
output_size: u32,
output_ptr: u31,
failed: u1,
} {
if (input_size_in > INPUT_CAP) @trap();
const input_size: usize = @intCast(input_size_in);
// Emit '+' then append only digits.
output_buf[0] = '+';
var output_size: usize = 1;
var i: usize = 0;
while (i < input_size) : (i += 1) {
const c = input_buf[i];
if (!isDigit(c)) continue;
if (output_size >= OUTPUT_CAP) @trap();
output_buf[output_size] = c;
output_size += 1;
}
// If just '+' then return empty string.
if (output_size == 1) output_size = 0;
return .{
.output_size = @intCast(output_size),
.output_ptr = @intCast(@intFromPtr(&output_buf)),
.failed = 0,
};
}zig build-exe e164.zig \
-target wasm32-freestanding \
-O ReleaseSmall \
-fno-entry \
--export=render \
--export=input_ptr \
--export=input_utf8_cap \
--export=output_utf8_cap \
-femit-bin=e164.wasmecho "+1 (212) 555-0100" | qipx run e164.wasm
# +12125550100
echo " 1212-555-0100 " | qipx run e164.wasm
# +12125550100When your JavaScript runtime or bundler supports direct WebAssembly ES module imports, import the QIP exports and wrap the memory exchange in a normal JavaScript function:
import {
memory,
input_ptr,
input_utf8_cap,
render,
} from "./e164.wasm";
const encoder = new TextEncoder();
const decoder = new TextDecoder();
export function normalizeE164(phoneNumber) {
const input = new Uint8Array(
memory.buffer,
input_ptr(),
input_utf8_cap(),
);
const { read, written } = encoder.encodeInto(phoneNumber, input);
if (read !== phoneNumber.length) {
throw new RangeError("phone number exceeds input capacity");
}
const result = BigInt.asUintN(64, render(written));
if ((result >> 63n) !== 0n) throw new Error("component rejected input");
const outputSize = Number(result & 0xffff_ffffn);
const outputPtr = Number((result >> 32n) & 0x7fff_ffffn);
return decoder.decode(
new Uint8Array(memory.buffer, outputPtr, outputSize),
);
}
console.log(normalizeE164("+1 (212) 555-0100"));
// +12125550100This uses the component directly: encode the input, write it at input_ptr(),
call render(), then decode the pointer and size in its result. There is no QIP
JavaScript runtime or reusable wrapper involved. It assumes e164.wasm is the
known-valid component built above; hosts accepting arbitrary Wasm need a
separate validation boundary described by the Content Component
Contract. Direct
.wasm imports depend on runtime or bundler support; Running In
JavaScript covers the equivalent
explicit-instantiation fallback.
We’ll write some C to trim leading and trailing whitespace.
#include <stdint.h>
#define INPUT_CAP (4u * 1024u * 1024u)
static char input_buffer[INPUT_CAP];
__attribute__((export_name("input_ptr")))
uint32_t input_ptr() {
return (uint32_t)(uintptr_t)input_buffer;
}
__attribute__((export_name("input_utf8_cap")))
uint32_t input_utf8_cap() {
return sizeof(input_buffer);
}
__attribute__((export_name("output_utf8_cap")))
uint32_t output_utf8_cap() {
return sizeof(input_buffer);
}
static int is_space(char c) {
return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v';
}
__attribute__((export_name("render")))
uint64_t render(uint32_t input_size) {
if (input_size > INPUT_CAP) __builtin_trap();
uint32_t start = 0;
while (start < input_size && is_space(input_buffer[start])) {
start++;
}
uint32_t end = input_size;
while (end > start && is_space(input_buffer[end - 1])) {
end--;
}
uint32_t output_size = end - start;
uint32_t output_ptr = (uint32_t)(uintptr_t)(input_buffer + start);
return ((uint64_t)output_ptr << 32) | output_size;
}zig cc trim.c \
-target wasm32-freestanding \
-nostdlib \
-Wl,--no-entry \
-Wl,--export=render \
-Wl,--export-memory \
-Wl,--export=input_ptr \
-Wl,--export=input_utf8_cap \
-Wl,--export=output_utf8_cap \
-Oz \
-o trim.wasmecho " hello world " | qipx run trim.wasm
# hello world
printf "\t line one \n" | qipx run trim.wasm
# line oneYou can also write raw WebAssembly text format which compiles directly to .wasm. Here is a hello world example:
(module $YourTextModule
;; Memory must be exported with name "memory"
;; First page empty, input at 0x10000, output at 0x20000
(memory (export "memory") 3)
;; Internal buffer constants with function exports for qip integration
(global $input_ptr i32 (i32.const 0x10000))
(global $input_utf8_cap i32 (i32.const 0x10000))
(global $output_ptr i32 (i32.const 0x20000))
(global $output_utf8_cap i32 (i32.const 0x10000))
(func (export "input_ptr") (result i32)
(global.get $input_ptr))
(func (export "input_utf8_cap") (result i32)
(global.get $input_utf8_cap))
(func (export "output_utf8_cap") (result i32)
(global.get $output_utf8_cap))
;; Required export: render(input_size) -> packed output pointer and size
(func (export "render") (param i32 $input_size) (result i64)
;; Write "Hello, World" as i64 + i32
;; "Hello, W" as i64 (little-endian: 0x57202c6f6c6c6548)
(i64.store (global.get $output_ptr) (i64.const 0x57202c6f6c6c6548))
;; "orld" as i32 (little-endian: 0x646c726f)
(i32.store (i32.add (global.get $output_ptr) (i32.const 8)) (i32.const 0x646c726f))
;; Put the pointer in bits 32..62 and the size in bits 0..31.
(i64.or
(i64.shl
(i64.extend_i32_u (global.get $output_ptr))
(i64.const 32))
(i64.const 12))
)
)The Node.js qip-router CLI makes static websites from files and QIP recipes:
- Put website source content in a directory (Markdown, HTML, images, CSS, etc.).
- Add recipe QIP components to transform source files by MIME type. For example you could create a
_recipes/text/markdown/10-markdown-to-html.wasmto render Markdown to HTML. - Preview locally with
npx qip-router dev. - Export as static files with
npx qip-router warc.
Example content:
docs/
index.md
about.md
images/logo.png
docs/_recipes/
text/markdown/10-markdown-to-html.wasm
text/markdown/20-html-page-wrap.wasm
Preview in dev mode:
npx qip-router dev ./docs -p 4000
open http://localhost:4000Resolve a single path through the same router pipeline:
# GET /about
npx qip-router get ./docs /about
# HEAD /about
npx qip-router head ./docs /about
# List all routes
npx qip-router list ./docsBuild static tar from the site:
npx qip-router warc ./docs \
| qipx run components/application/warc/warc-to-static-tar-no-trailing-slash.wasm \
> site.tar
tar -tf site.tar
ls ./site- QIP Component Contracts
- Content Component Contract
- Interactive Component Contract
- Uniforms
- QIP Component Patterns
- Writing QIP Components in Zig
- Building C Libraries as QIP Components
- Translating QIP Components To C
- Translating QIP Components To Zig
- Translating QIP Components To Swift
- Hard Limits
- Provable Loops
- Running In JavaScript
- qipx CLI
- Running Interactive Components In A Terminal
- QIP Component Compliance
If you are contributing components or running the full Makefile in this repo, install these tools:
makeand standard POSIX command-line tools such asfind,diff,tar,perl, andxxd- Go (required for
qipCLI): https://go.dev/doc/install - Zig (used for
.zigand.c->.wasmbuilds): https://ziglang.org/download/ wat2wasmfrom WABT (used for.wat->.wasmbuilds): https://github.com/WebAssembly/wabt- Node.js 20+ (used by
make test-nodeand the defaultmake testpath): https://nodejs.org/ - Deno (optional, used by
make test-deno): https://deno.com/
Quick installs:
# macOS (Homebrew)
brew install go zig wabt node
# Ubuntu/Debian (install Node.js 20+ from your preferred Node distribution)
sudo apt-get update
sudo apt-get install -y build-essential golang-go wabt perl vim-commonAfter installing dependencies, build in parallel:
make -j components recipesRun the test targets through the Makefile:
make -j test-node
make -j test-deno
make -j test-go
make -j testtest/trace-with.mjs is included in both make -j test-node and make -j test-deno. To run it directly with Node:
make -j qip components/application/wasm/wasm-trace-instrument.wasm
node --test test/trace-with.mjsTo run it directly with Deno, pass the same permissions used by make test-deno:
deno test --allow-read --allow-write --allow-run --allow-sys --allow-env test/trace-with.mjsYou can clone this repo to use the components provided in ./components.
The component layout groups components by content domain and media type:
components/
bytes/
image/svg+xml/
text/
css/
html/
javascript/
markdown/
x-c/
rgba/
Benchmark the performance of one or more QIP components. If you compare multiple components then it’ll check each output is exactly the same. This is useful for porting from C to Zig, or asking your AI agent to implement optimizations and verifying that the result still behaves the same.
# Benchmark one component for two seconds
echo "World" | qipx bench -i - --benchtime=2s components/text/hello.wasm
# Benchmark: outputs match
# Benchmark two components against each other and verify identical output
echo "World" | qipx bench -i - --benchtime=2s components/text/hello.wasm components/text/hello-c.wasm
# Benchmark: outputs match
# Benchmark three components against each other and verify identical output
echo "World" | qipx bench -i - --benchtime=2s components/text/hello.wasm components/text/hello-c.wasm components/text/hello-zig.wasm
# Benchmark: outputs match
# Ask Node.js to collect garbage between component measurements
echo "World" | NODE_OPTIONS=--expose-gc qipx bench -i - --benchtime=2s components/text/hello.wasm- Add WebAssembly table support to the component debugger. Decide whether to show table entries alongside locals and globals.
- Allow compiling TUIs into native code via
components/application/wasm/qip-component-to-c.wasm. So you get the benefit of a sandbox but you get the fast performance of native. - Explore a consistent route hierarchy for interactive image tools, such as moving
/image-resizeto/image/resize. Consider all image tools together, preserve redirects for existing URLs, and decide how tool routes coexist with the/imagecomponent namespace. - Add
--view-sourcetonpx qip-router warc, including recipe source and view-source records. - Align
npx qip-routerCLI output with./qip router. Rendering and WARC output match byte-for-byte, andlisthas the same routes after whitespace normalization. Remaining differences:listuses tabs instead of Go's padded columns;headprints an HTTP-style block to stdout while Go logs headers to stderr; Node does not currently emitETagfor some static/rawHEADresponses that Go reports. - Add
github:owner/repo/subdircontent roots toqip-router, pinned to one repository snapshot per load. - Add AVIF encoding with Display P3 HDR with either PQ (Perceptual Quantizer) or HLG.
- Allow ordered HTTPS host fallback in qipx:
qipx qip.dev run text/markdown/gfm-commonmark.0.31.2.wasm - Decide whether qipx redirects may target any configured HTTPS host, not only the source origin. Keep the two-redirect limit and reject unconfigured origins; define whether a failed redirected request resumes the original fallback sequence and may request the target host twice.
- Investigate if qip-component-to-c is affected by https://trustsig.eu/blog/wasm2c-tableflip-unchecked-calloc/
- Remove
@memcpy(ktx_buf[ktx.HEADER_SIZE..], output_buf[0..]);— just render directly to output_buf instead of ktx_buf. - For interactive components should we inline the ktx2 header write function into components?
- For interactive components should we allow uniforms to be optional?
- Return the dynamic output pointer, size, and rejection state from
render. - Should uniforms return their previous value? This means we can bring a component back to its original state.
- Ensure we always
new TextDecoder("utf-8", { fatal: true }) - Add
/textand/imagepages with list of wasm modules. I think image should include any with input or output image. - Should we drop
/componentsprefix from web path? - Recipes page: interactive upload and text entry
- Recipes page: copy recipe as source code (e.g. as JavaScript)
- Extend
content-recipe-to-browser-javascriptwith per-stage uniforms: add an optional recipe CSV query column, applyuniform_set_<key>functions in sorted order before each render, parsei32,i64,f32, andf64values, reject duplicate keys, missing setters, and host-managed multi-parameter hooks, and support optional runtime overrides. Consider stable stage IDs before exposing overrides for multi-stage recipes. - Recipes page: prune useless recipes
- Complete the common image conversion paths through
image/bmp:- Add
image/avif/avif-to-ktx2-r8g8b8a8-srgb.wasmso AVIF can feed the canonical KTX2 image pipeline without a BMP intermediate. - Add
image/bmp/bmp-b8g8r8a8-srgb-to-jpeg-lossy.wasm, with an explicit quality uniform, so every format that decodes to BMP can produce JPEG. - Add
image/gif/gif-first-frame-to-bmp-b8g8r8a8-srgb.wasm. Keep the first-frame policy in the name rather than implying that one BMP preserves an animation; add a separate GIF-frames-to-TAR component if all frames are needed. Or we could pass the frame as a uniform? - Add
image/x-icon/ico-to-bmp-b8g8r8a8-srgb.wasm, with a deterministic default image choice and uniforms for selecting an embedded size when needed.
- Add
- Add
php-to-cthat converts a subset of PHP to a standalone C program.text/x-php- Support
SQLite3: https://www.php.net/manual/en/sqlite3.prepare.php
- Support
- Add SQLite prepared statement example that takes SQLite database and a query, and produces a new optimized component that accepts query parameters as its input.
- Curried component: input content type
application/vnd.sqlite3output content typeapplication/sql -> application/x-www-form-urlencoded -> text/csv
- Curried component: input content type
- Work through the QIP component-to-C code-generation performance experiments, measuring each lowering change independently against the current translator and WABT
wasm2c. - How does our
qip-component-to-c.wasmcompare with RLBox? https://rlbox.dev/ - Add
warc-latency-estimator.wasmthat takes a WARC and then for each route calculates largest-contentful-paint and time-to-interaction and so forth. - Investigate lighter router
HEADhandling. TodayHEADfollows the fullGETpath so WARC recipes can add derived routes, change headers, and set the final content length correctly. Many WARC recipes need the full site to understand links, but usually do not change status or headers other thancontent-length. Find a safe way forHEADto avoid unnecessary body work when recipes can declare that behavior. - Update to latest Zig
- Add TAR fixture archives for straight-line Content Compliance cases. Accept them through
qip comply --with fixtures.tarandqipx comply --with fixtures.tar; supportmust_render_exactly,must_trap, explicit per-case uniforms in the case directory name, deterministic path sorting, duplicate-key rejection, and unknown-entry failures. - Add Compliance sidecar recording for Wasm oracles. Start with
--record-failures failures.tarfor fuzz/debug repro cases, then consider--record-cases cases.tarfor exporting all observedmust_render_exactlyandmust_traporacle calls into the TAR fixture archive format. Include a record limit so fuzzers cannot produce unbounded archives. - Retire
qip comply --straight-line-oraclesafter TAR fixture archives cover the audit use case. Procedural Wasm Compliance oracles can keep normal control flow; fixture archives provide the no-branching format. - Design
<qip-step>,<qip-connect-search-params>,<qip-render>, and pre-rendered<qip-view>together as the finite-rendering markup.- Wrap
<source>with<qip-step>in<qip-play>as multiple<source>elements are meant to be alternatives to each other. Extend the same markup to the finite elements when their design is implemented.- Add conditional sources with a step, such as to support bmp or png or jpeg upload with
<input type="file">. - Add WARC-time validation that the steps are compatible with each other.
- Add conditional sources with a step, such as to support bmp or png or jpeg upload with
- Add
<qip-connect-search-params>for explicitly allowing URL search parameters into a render without exposing every parameter, like Rails strong parameters. Each direct<input type="hidden" name="language" value="en">child declares one permitted key and uses its initial value as the fallback. Rendering replaces current values from matching search parameters, and the successful controls combine with ordinary user inputs as one form-encoded input for the steps of an enclosing<qip-view>or<qip-render>. - Add support for nested
<qip-render component="/bytes/base64-encode.wasm">that can be substituted at build time or serving time. This would allow something akin to React, Astro, or PHP rendering while keeping the executable components fixed by the page. - Add a signal for
<qip-view>marking pre-rendered output as authoritative so activation can skip the initial render (perhaps arenderedattribute). It must be an explicit marker, never inferred from non-empty output, since empty output is a valid result.
- Wrap
- Retire the web-shaped
Formcomponent contract.- Add
submit(input_size)export- Input is either
application/x-www-form-urlencodedormultipart/form-data - Have demo with
FormDatathat works in client and on server - Will be like an uncontrolled form in React, where every keypress does NOT need a re-render. Only do this on submit.
- See how it could work with the Interactive component contract, and HTML-in-canvas
- Input is either
- In favor of a future cross-host
Promptcontract: sequential prompts with recoverable failure,submit(input_size, now_ms)for state changes, andrender(0)for the current semantic projection/output.
- Add
- Add Command Palette example, combining
<input>and<canvas> - Add CSV to chart SVG example
- Add TCP simulator example, showing segments and allowing you to test failure.
- Add more shader examples from https://github.com/paper-design/shaders/tree/main/packages/shaders/src/shaders:
- God rays
- Metaballs, dithering, grain gradient, mesh gradient, heatmap, liquid metal, and halftone
- See https://shaders.paper.design
- Add
application/edifactexample - Add Wuffs example
- See: https://github.com/google/wuffs/blob/main/doc/getting-started.md
- See roadmap of examples: https://github.com/google/wuffs/blob/main/doc/roadmap.md
- Allow
qip complyto be run against arbitrary shell command. This would internally call out to the underlying command by copying the input when the comply task callsimpl.render. - Add Email render example (do we use an existing layout system?)
- Add MathML https://www.w3.org/TR/mathml-core/
<math>renderer- See https://github.com/KaTeX/KaTeX
- See https://www.intmath.com/cg5/katex-mathjax-comparison.php?processor=MathJax3
- See https://andrewlock.net/rendering-math-in-html-mathml-mathml-core-and-asciimath/
- See https://developer.mozilla.org/en-US/docs/Web/MathML/Reference/Element/semantics
- See https://asciimath.org/#syntax
- Have
/math-to-htmldemo like https://katex.org/#demo
- Increase recipe order prefix from
nntonnn. - Add dev/CI QIP Vitals for pipelines: harness metrics that zoom in on time to first render bytes (read/fetch, compile, instantiate, first
render), per-stage render time, full pipeline render time, interactive event-to-frame-bytes latency, frame-budget miss rate, output hash, wasm/compressed size, input/output byte sizes, memory pages/max memory, trap/timeout rate, and host/runtime/device metadata. Report p50/p95/p99 so timings stay measurable and comparable when output bytes still match. - Add optional field telemetry for deployed QIP components: user-experience metrics such as component hash, host/runtime/device class, time to first QIP paint p75/p95/p99, render-to-paint delay p75/p95/p99, interactive event-to-painted-frame p75/p95/p99, sampled pipeline render p95/p99, frame-budget miss rate, trap/timeout rate, and slowest device classes so production performance can be compared without changing the component contract.
- Add TypeScript-to-JavaScript type stripper.
- Document uniforms properly
qip image -i fixtures/SAAM-2015.54.2_1.jpg -o tmp/halftone.png components/rgba/color-halftone.wasm -u max_radius=2.0 components/rgba/brightness.wasm -u brightness=0.2 - Add CDN example to allow this to run server-side:
qip image -i fixtures/SAAM-2015.54.2_1.jpg -o tmp/halftone.png components/rgba/color-halftone.wasm -u max_radius=2.0 components/rgba/brightness.wasm -u brightness=0.2 - Add DOOM example.
- Add monochrome rendering
output_monochrome_bytes() -> i32 - Add application/wasm verifier for fixed memory, fixed-bound loop evidence, and no recursion. This is a conservative subset of https://en.wikipedia.org/wiki/The_Power_of_10:_Rules_for_Developing_Safety-Critical_Code: loops must compile to a visible counter, monotonic update, and exit bound.
- Add a
wasm-fuel-instrumentcomponent that injects deterministic fuel metering as a wasm-to-wasm transform: a decrementing fuel global checked at each loop backedge and call, trapping at zero. Prior art: wasmtime fuel/epochs and Parity's wasm-metering gas injection. Fuel is deterministic (same input, same fuel spend on every host), unlike wall-clock--timeout-ms, and browsers cannot preempt a wasm call on the main thread. This gives modules whose loops the static checker cannot prove (for examplecommonmark) a metered tier, while statically proven modules keep the strict tier. - Add tracing by modifying modules:
- Trace unreachable by adding calls to memory reads of input so we can check last read which will likely be source reason for the trap.
- Trace any loops by adding calls to an imported function in each iteration. e.g.
trace_loop($func_n, $loop_n) - Trace any internal calls by adding calls to an imported function. e.g.
trace_will_call($func_n, $call_n) - Trace any branches by adding calls to an imported function. e.g.
trace_if($func_n, $if_n)andtrace_block($func_n, $block_n) - Trace scalar Wasm32 memory loads/stores with before/after callbacks.
- Extend memory tracing to SIMD, atomic, and bulk-memory operations.
- Extend
paintexample withanimatethat is like Flash or After Effects with a simple keyframe and tween editor of graphics. We could have a limited number of layers (8?) and text input. - Add spreadsheet example.
- Add bar charts.
- Add scatter plot charts.
- Add pixel editor.
- Add Cover Flow example.
- Add Figma vector networks example.
- Add localized example in multiple languages.
- Add a split interactive pipeline mode with two collaborative modules: a state/update module writes an internal scene buffer, and a renderer consumes it via
render(input_size)with a simplememcpyhandoff between module instances. This would enable presentation variants such as language or locale, dark or light mode, font size, DPI scaling, and accessibility-focused rendering. - Have separate
pointermoveevent handler so we can skip expensivepointermovelisteners and rendering if not needed?? - Separate update scheduling from presentation:
finish_update()returnsnext_wake_at_ms, events report accepted or ignored, and the host decides whether to callrender(0). Specify pointer leave coordinates and button state consistently across hosts. - Add digest pinning for remote modules (for example
https://...#sha256=<hex>), and fail fast when fetched bytes do not match the pinned digest. - Update docs to encourage hard failure with traps instead of returning empty output which could lead to data loss.
- Convert soft-failure validators to trap on invalid input, then add invalid-then-valid same-instance recovery tests:
-
components/text/css/css-class-validator.wasm -
components/text/html/html-id-validator.wasm -
components/text/html/html-input-name-validator.wasm -
components/text/html/html-tag-validator.wasm -
components/text/tld-validator.wasm -
components/text/luhn.wasm
-
- Add
qip dry run ...pipeline.wasmthat validates pipeline compatibility and outputs memory usage (summing all input/output buffers). - Add
qip servecommand that runs the server inprodmode by default, and includes a module upload endpoint. - Add
random_ptrandrandom_sizeto modules that the host can detect and fill in with random data. It can choose to seed with determinism or use a cryptographic source of randomness — it’s up to the host. - Add
--postconditionor--outmustflag toqip runthat verifies the final output conforms to a particular module e.g.--postcondition valid-xml-1.0.wasm. - Add first-stage content-type guards: either lightweight ingress sniffing (check initial bytes against expected type) or validator modules (for example
validate-html.wasm) that accept untrusted input and re-emit it with asserted MIME type on success. - Add
qip photocopycommand that observes an existing tool’s input/output behavior and generates a behaviorally similar QIP module implementation in wasm, then validates it with duel/fuzz tests and reports divergences. - Finish Compliance oracles as a first-class component type alongside
Content,Interactive,Tile, andForm. The host bridge streams one case at a time, supports procedurally generated oracles and mutation loops, and keeps oracle memory separate from implementation memory.- Case identity = bridge-call ordinal. Oracles are deterministic, so "case 987 of the CommonMark suite" is reproducible by re-running the oracle and skipping the first 986 interactions (host returns pass without executing the impl until the target ordinal).
qip comply --case 987prints that case's name/input/expected without needing any failure ABI;--continuecounts all divergences instead of stopping. - Corpus extraction for non-QIP implementations: run the oracle against a null impl (host records every declared case instead of executing anything) and export the ordered corpus as a tar archive (simple, streaming, ordered, arbitrary binary entries; the repo already speaks tar). Any external harness — a Go test dueling x/text, a Node script dueling
toLocaleLowerCase, a CLI duel over stdin/stdout — consumes the tar. This makes the emit-a-corpus design a host feature of the bridge design rather than a separate module shape. - Migrate all repository oracles, including Luhn, E.164,
preserve-*, andtrap-*, to the bridge. - Move the Compliance oracle meta-contract checks into
qip complybase validation (the way static contract checks already run for render components): deterministic declarations across a double null-impl run, sequential u64 ordinals, must_render_into open/emit/finish discipline, and seed-varies-fuzz-only. Per-component harnesses must not each re-verify these;test/lib/compliance-harness.mjsis the interim JS reference implementation of both the bridge and the generic checks.
- Case identity = bridge-call ordinal. Oracles are deterministic, so "case 987 of the CommonMark suite" is reproducible by re-running the oracle and skipping the first 986 interactions (host returns pass without executing the impl until the target ordinal).
- Permit accepted output to be an immutable slice of the input region.
- Revisit numeric outputs as SIMD-aware tensors instead of restoring the old
output_i32_capdirectly. Useful proof cases are batched CRC, histograms, offset arrays, masks, and matrices. Keep element type, logical shape, and physical layout separate; Mojo's scalar-as-one-lane-SIMD and explicit layout model is pertinent prior art.