Skip to content

Metadata (fd 3)

The metadata channel is JSONL on fd 3. One JSON object per line — each defines a panel, mutates an existing one, or clears it.

TypeRenderer
image<img> from a blob URL (PNG, JPEG, WebP, GIF). Requires byteLength + bytes on fd 4.
svgSVG string as innerHTML. The SVG can come either inline in data (UTF-8 string) or via byteLength on fd 4.
htmlHTML string as innerHTML. Same data delivery as svg.
canvas2dA <canvas> rendered via drawImage. Requires byteLength + bytes on fd 4 (raster image).
updateMutate fields on an existing panel id.
clearRemove a panel by id.

Custom content types register through registerRenderer() in src/views/terminal/content-renderers.ts.

FieldTypeDescription
idstringUnique panel identifier (per surface).
typestringAny content type or update / clear.
positionenumfloat (viewport-fixed, default), inline (scrolls with terminal), fixed (no chrome).
x, ynumberPosition in pixels (origin: top-left of pane).
width, heightnumber | "auto"Dimensions.
draggablebooleanAllow drag (default: true for float, false otherwise).
resizablebooleanAllow resize (default: true for float, false otherwise).
interactivebooleanForward mouse events to fd 5.
byteLengthnumberSize of binary payload on the data channel.
dataChannelstringNamed data channel (default: "data" = fd 4).
datastringInline UTF-8 payload (alternative to byteLength, for text content).
formatstringFor image: png / jpeg / webp / gif.
opacitynumber0.0–1.0.
zIndexnumberStacking order.
{"id":"photo","type":"image","format":"png","x":100,"y":50,"width":400,"height":300,"byteLength":24576}

Followed by 24 576 raw PNG bytes on fd 4.

{"id":"chart","type":"svg","x":50,"y":50,"width":400,"height":300,"data":"<svg viewBox='0 0 100 100'><circle cx='50' cy='50' r='40' fill='blue'/></svg>"}
{"id":"btn","type":"html","x":20,"y":20,"width":200,"height":80,"interactive":true,"data":"<button onclick='alert(1)'>Click</button>"}
{"id":"photo","type":"update","x":200,"y":150}

Only the fields you pass are changed. Cannot change type — clear and recreate instead.

{"id":"photo","type":"clear"}

Removes the panel and frees its DOM element. Its events are no longer delivered.

  • Use ids you can map back to script-side state. They’re echoed on every event.
  • Prefer inline over float for one-shot output — they scroll naturally with the terminal text.
  • Set byteLength-vs-data consciously. data is fine for tiny payloads; for anything > 64 KiB use byteLength to skip JSON-string escaping.
  • Don’t stream raw frames at 60 fps — for canvas-style animation, use update to send small mutations rather than re-emitting the full payload.
  • src/views/terminal/panel-manager.ts — fd 3 dispatch.
  • src/bun/sideband-parser.ts — JSONL + binary reader.
  • src/shared/types.ts — panel option types.