# plastron — guide for LLMs & agents

This is the one canonical guide. It is served verbatim as plain text at
**https://plastron.ca/llms.txt**, mirrored in the repo `README.md`, and shown
(condensed) in the app's own readme. Paste it into a system prompt to author
plastron formulas without access to the source.

plastron is a **reactive spreadsheet made of cels**, shipped as one self-contained
`index.html`. A cell doesn't just compute — its formula can render DOM, spawn
grids, call an LLM, query SQLite, or build a whole app. **A formula IS the app,
and a formula fits in a URL**, so a whole running plastron is shareable as a link.

────────────────────────────────────────────────────────────────────────────
FIRST — RESEARCH, DON'T GUESS (plastron describes itself)
────────────────────────────────────────────────────────────────────────────
This guide lists the high-frequency verbs, but it is NOT the full set. Plastron
ships MANY more (charts, graphs, music/MIDI, peer/net, files, …). If you need a
capability that isn't spelled out below, DISCOVER it with a formula instead of
guessing a verb that may not exist:
  =vocab()            every verb + its one-line description, grouped by segment
  =vocab("charts")    just one segment's verbs
  =inspect("name")    one verb's full doc — signature, about, source
  =members("seg")     the cels in a segment
  =segments()         every loaded segment
Put one of these in a cell, read the result, THEN write the real formula. A wrong
guess surfaces as `#NAME?`; `=inspect("<verb>")` tells you the true signature.
(Full plain-text guide + complete catalog: https://plastron.ca/llms.txt)

────────────────────────────────────────────────────────────────────────────
THE TWO FORMULA LANGUAGES (pick one per cell; the leading char selects it)
────────────────────────────────────────────────────────────────────────────
- **infix** — Excel/function-call style, starts with `=`:
    `=1+1`   `=cels(8,5,"todo")`   `=dom("h1","hello")`   `=g!A1*2`   `=SUM(A1:A10)`
- **S-expression** — Lisp prefix, starts with `(`:
    `(+ 1 1)`   `(cels 8 5 "todo")`   `(dom "h1" "hello")`

They are equivalent in power. Infix is the Excel-compatible surface; S-expr is the
homoiconic one. Don't mix them inside one formula. Cell references are `A1`,
ranges are `A1:B3` or `seg!A1:B3`. Cel keys in formulas are ASCII `[\w.-]+`.

FORMAT BIG FORMULAS MULTI-LINE: newlines and tabs are IGNORED by the parser, so indent a
large nested formula and put one argument per line — a single long line is the #1 cause of
`expected ")"` (a miscounted paren). Indentation makes every `(` line up with its `)`.

STRINGS: use `"…"` or `'…'`. A second delimiter means a nested formula needs **no
escaping**, one level deep:
    `at("A1", '=dom("h1", "Tasks")')`   ✅   (not  `at("A1","=dom(\"h1\",\"Tasks\")")`)
  A cell whose content is itself a `=formula` MUST be quoted with the alternate
  delimiter — `at("B2", '=SCAN(0,A2:A6,LAMBDA(a,v,a+v))')` ✅; a BARE `at("B2", =SCAN(…))`
  is a parse error ("unexpected token ="). Only TWO string levels exist (`"` outside,
  `'` inside) — do NOT go a third level deep and NEVER use backticks. If you need depth
  (e.g. a chart inside a menu-spawned sheet), build that sheet as its OWN top-level
  `=cels(…)` and reference it, rather than inlining a third nested formula.

────────────────────────────────────────────────────────────────────────────
CORE FORMULAS (the high-frequency ones — full catalog at the bottom)
────────────────────────────────────────────────────────────────────────────
GRIDS / SHEETS
  =cels(rows, cols)                       one worksheet of editable cels
  =cels(rows, cols, "name")              a named worksheet
  =cels("in",4,3, "out",4,3)            a workbook of named sheets
  =cels(8,5,"todo", at("A1","Task"), at("B1","Status"))   seed cell contents
  at(addr, content)                       one cell's initial content (value or =formula)

VIEWS (DOM + canvas)
  =dom("h1", "hello")                     an element rendered in the cell
  =dom("div", style("color","red"), "hi")  style() = key/value attribute pairs
  =dom("button", on("click","origin.tone", 440), "♪")   on(event, verb, arg)
  =canvas(420,260, barchart(t!A2:A8, t!B2:B8))           a drawn chart

DATA + CHART TOGETHER (the right way)
  `cels()` is a GENESIS — it builds a sheet. It is **top-level**, NOT a child of
  `dom()`. Never write `dom("div", cels(...), ...)` — the grid won't materialize
  (it stringifies to "[object Object]") and any chart that reads it shows "no data".
  Instead, put the chart in a cell OF the same sheet so its range read is local:
    =cels("sales", 5, 2,
      at("A1","Item"),  at("B1","Value"),
      at("A2","Apples"),at("B2",10),
      at("A3","Pears"), at("B3",20),
      at("A4","Plums"), at("B4",15),
      at("A5", '=canvas(360,200, barchart(sales!A2:A4, sales!B2:B4))'))
  (Note the `'…'` delimiter on the nested chart formula — no escaping needed.)

LOGIC / DATA
  =1+1   =A1*2   =SUM(A1:A10)   =IF(A1>0,"yes","no")
  =LET(x, A1*2, x + x*x)                  name a sub-expression, reuse it (readability)
  =SUM(MAP(A1:A9, LAMBDA(v, v*v)))        LAMBDA + MAP/REDUCE/SCAN/BYROW over a range
  NO ARRAY SPILL INTO CELLS: MAP/FILTER/SCAN/BYROW return ONE array VALUE. Put it in a single
  cell and it shows as a JSON dump ([100,250,…]) — it does NOT fill the cells below/beside it.
  (EXCEPTION — a DOM container flattens an array child, so MAP→dom IS how you render a list;
  see RENDER A LIST. The rule below is only about a raw array sitting in a sheet cell.) So:
    • collapse it to one value with an aggregate:  =SUM(MAP(A2:A9, LAMBDA(v, v*v)))  ✅
    • to DISPLAY a per-row column (running totals, row totals) write an EXPLICIT formula
      in EACH target cell with its own range — there is no fill-down and no `$`:
        running total:  at("B2",'=SUM(A2:A2)'), at("B3",'=SUM(A2:A3)'), at("B4",'=SUM(A2:A4)')
        row totals:     at("D2",'=SUM(A2:C2)'), at("D3",'=SUM(A3:C3)'), at("D4",'=SUM(A4:C4)')
    • if a task REQUIRES SCAN/BYROW for that column, each cell must STILL resolve to ONE
      number — extract it with INDEX(array, position); never leave the raw array in a cell:
        at("C2",'=INDEX(SCAN(0,B2:B2,LAMBDA(a,v,a+v)),1)'), at("C3",'=INDEX(SCAN(0,B2:B3,LAMBDA(a,v,a+v)),2)') …
        at("D2",'=INDEX(BYROW(A2:C4,LAMBDA(r,SUM(r))),1)'), at("D3",'=INDEX(BYROW(A2:C4,LAMBDA(r,SUM(r))),2)') …
  A lone =SCAN(…)/=BYROW(…) sitting in one cell is the #1 way these tasks render wrong — don't.
  SEED YOUR INPUTS + LABEL THE OUTPUT: a formula over empty cells shows 0/blank, and a
  bare scalar answer is nothing to look at. If a task says "price in A1, qty in A2",
  build a LABELED sheet that seeds the inputs so the result is visible:
    =cels("inv",4,2, at("A1","Price"), at("B1",12), at("A2","Qty"), at("B2",3),
      at("A3","Subtotal"), at("B3",'=B1*B2'),
      at("A4","Grand total"), at("B4",'=LET(s, B1*B2, s + s*0.13)'))

RENDER A LIST (a collection → repeated DOM — this is how data becomes UI)
  dom() FLATTENS an array child, so turn a range into repeated elements by MAPping it:
    =dom("ul", MAP(todo!A1:A5, LAMBDA(t, dom("li", t))))            one <li> per row
  FILTER(range, LAMBDA(x, pred)) keeps only matching values — render a SUBSET:
    =dom("ul", MAP(FILTER(nums!A1:A9, LAMBDA(x, x>0)), LAMBDA(x, dom("li", x))))
  null/""/false items in the array are dropped (so a MAP that yields "" omits that child).
  To carry per-row IDENTITY (so a click/drag knows WHICH row it is), MAP over an ID column
  (seed col A = 1..N) and use the id to BUILD the cel key + READ the fields with INDEX:
    =dom("div",
      MAP(todo!A1:A5, LAMBDA(id,
        dom("div.row",
          on("click", "some.handler", CONCAT("todo.B", id)),     ← payload NAMES this row's cel
          INDEX(todo!B1:B5, id)))))                               ← INDEX reads this row's field
  MAP is for iterating a DATA RANGE. For a FIXED, small set of sections (e.g. a few columns
  or tabs), there is NO inline list literal — do NOT invent ARRAY(…)/[…]. Instead define the
  section once as a LAMBDA (via LET) and CALL it per section:
    =LET(section, LAMBDA(name, dom("div.col", dom("h3", name), …use name…)),
      dom("div", section("Left"), section("Middle"), section("Right")))
  A LET-bound LAMBDA can call EARLIER LET names and see the calling LAMBDA's params, so a
  section can reuse a shared "card"/"row" LAMBDA and the section's own name together.

DRAG & DROP — "a drop writes a cel" (reusable: boards, buckets, file moves, assignment)
  Make an item DRAGGABLE and have it NAME a cel; make a drop ZONE carry a VALUE:
    item: =dom("div", attr("draggable","true"), on("dragstart","drag.grab","todo.B2"), "a task")
    zone: =dom("div", on("dragover","drag.over"), on("drop","drag.drop","Done"), "Done")
  Dropping the item runs drag.drop → sets todo.B2 := "Done". (on("dragover","drag.over") is
  REQUIRED on the zone — without it the browser fires no drop.) Movement is REACTIVE: if a
  view FILTERs/reads that cel, the item RE-RENDERS in its new place automatically — you never
  move it by hand. drag.active remembers the grabbed cel between grab and drop.

DATABASE (browser SQLite — persistent, runs in a Worker; the db NAME is the handle)
  =sqlitedemo()                           one formula: makes a table, seeds it, opens the client
  =sqlclient("mydb")                      a SQL client WINDOW — query editor, tables sidebar, results grid
  =sql("mydb", "create table t(a,b)")     run SQL on a db; writes persist. The first arg is the db name
  =sql("mydb", "select * from t")         …or a handle from =db(); SELECT returns rows
  =dbseed("mydb", rows, "t")              bulk-load a JSON array of row-objects (or a range) into table t
  =schema("mydb")   =tables("mydb")       introspect tables/columns/PK·FK   ·   list table names
  Results from =sqlclient land in real `sqlres.*` cels you can chart or reference.

FILES (OPFS, in-browser)
  =write("/a.txt","hi")  =cat("/a.txt")  =ls("/")  =mkdir("/d")  =upload("/")

WINDOWS / LAYOUT
  =win(key,"title","body")     =jail(seed)     =doom()  (Doom in a wasm window, consent-gated)
  =cels("app",5,2, geom(40,40,520,360))   geom(x,y,w,h[,minW,minH]) sizes a sheet's own window

APP = DATA SHEET + WINDOW (one pasteable, shareable formula)
  =winapp(id, "Title", '=<formula>')   a draggable WINDOW whose body is a REACTIVE formula
  =segment(part, part, …)              compose cels()/winapp()/def() parts into ONE formula
  The idiomatic app is a data sheet + a window that reads it — one formula in, one link out:
    =segment(
      cels("todo", 4, 2, at("A1","Task"), at("B1","Status"),
        at("A2","Email Bob"), at("B2","To Do"), at("A3","Ship"), at("B3","Done")),
      winapp("todo", "To Do",
        "=dom('div', MAP(todo!A2:A3, LAMBDA(t, dom('div.card', t))))"))
  The window body reads the sheet by GLOBAL ref (todo!A2:A3) even though it is a different
  segment. Two string levels: top-level args are "…", the nested window body is '…'.

SEGMENTS — every workbook / window / app you make is a SEGMENT (a named layer)
  =segments()                  list the loaded segments
  =members("seg")              the cels in a segment
  =nav(viewport.mobile, item(…))  a navbar that switches between your segments/windows (see NAVBAR below)
  Each =cels/=winapp/=doom/=jail MINTS a document segment (kind workbook/winapp/
  wasm/jail); the substrate (net/dom/origin/…) is reserved and not exported.

NAVBAR / MENUS (a pasteable menu; one formula, mobile + desktop)
  item(label, action, ...children)   one menu node. label = emoji+text; action =
        a window KEY (click focuses it) or a '=formula' (click spawns a window);
        omit action for a submenu parent. children = nested item()s (WordPress-style).
  =nav(viewport.mobile, item(…), …)  renders the menu — pass viewport.mobile FIRST so
        it auto-switches: a collapsible ☰ left sidebar on mobile, desktop launcher
        icons otherwise. Example:
    =nav(viewport.mobile,
      item("📁 Files", "files"),
      item("📊 Charts",
        item("🥧 Pie", '=cels("pie",5,2, at("A1","Item"), at("B1","Value"), at("A2","Apples"), at("B2",10), at("A3","Pears"), at("B3",20), at("A4","Plums"), at("B4",15), at("A5", "=canvas(360,200, piechart(pie!A2:A4, pie!B2:B4))"))'),
        item("📈 Bar", '=cels("bar",5,2, at("A1","Item"), at("B1","Value"), at("A2","Q1"), at("B2",30), at("A3","Q2"), at("B3",45), at("A4","Q3"), at("B4",25), at("A5", "=canvas(360,200, barchart(bar!A2:A4, bar!B2:B4))"))')),
      item("元 Origin", "元"))
  A chart INSIDE a submenu sheet works WITHOUT a third quote level: a chart formula has
  no inner string quotes, so wrap the sheet action in '…' and the chart in "…" —
  `at("A5", "=canvas(360,200, barchart(bar!A2:A4, bar!B2:B4))")`. NEVER reach for
  backticks to get depth (`infix: unexpected character` — a hard parse error).

RESPONSIVE LAYOUT (the page is the product — use the whole viewport, don't cram)
  The result is a full plastron.ca PAGE, not just a cell. An app-like formula
  should claim space: open a sized window, or size DOM to the viewport.
  - viewport.w / viewport.h / viewport.mobile / viewport.orient — reactive cels;
    a formula that references them RE-RUNS on resize. =viewport() = a one-shot {…}.
  - =win("app", "App", body, viewport.w, viewport.h - 40)         fill the screen
  - =IF(viewport.mobile, dom("h1","phone view"), bigDesktopUi)    branch on device
  Prefer a window or a sized canvas/dom over a widget squeezed into one tiny cell.

────────────────────────────────────────────────────────────────────────────
SHARE LINKS — how to make one (IMPORTANT: don't hand-encode)
────────────────────────────────────────────────────────────────────────────
A formula fits in a URL. To make a shareable link, **write the formula and let the
app encode it** — never hand-roll the compressed form.

- `=link()`                 → encode THIS whole sheet → https://plastron.ca/#f=<payload>
- `=link("元")`             → encode one cell's source
- `#raw=<url-encoded-formula>` → the simplest link YOU can build by hand:
      https://plastron.ca/#raw=%3Dcels(8%2C5%2C%22todo%22)   (just URL-encode a valid `=formula`)
SELF-SERVICE URLs (open one → it answers in PLAIN TEXT; no app runs):
- `https://plastron.ca/#check=<url-encoded-formula>`  → "✅ VALID" or "❌ INVALID + error".
- `https://plastron.ca/#encode=<url-encoded-formula>` → the compressed `#f=` link for it.
      Example: to share `=cels(3,3)` → open `https://plastron.ca/#encode=%3Dcels(3%2C3)`.

THE #f= CODEC — build a link by hand, no app needed. `#f=<payload>` where payload is:
      tag "0" + base64url(utf8(formula))              ← plain
      tag "1" + base64url(deflateRaw(utf8(formula)))  ← compressed (raw DEFLATE)
  Emit whichever is SHORTER; the leading tag char tells the decoder which. base64url =
  base64 with "+"→"-", "/"→"_", trailing "=" stripped. **No JSON wrapper.** Worked (plain):
      =vocab()  →  utf8 3D 76 6F 63 61 62 28 29  →  base64url PXZvY2FiKCk  →  #f=0PXZvY2FiKCk

ENCRYPTED links — the URL param NAMES the method (a self-describing booter):
- `=encrypt(pass)`  → `#aes256gcm=<payload>`  (passphrase prompt on open)
      payload = base64url( salt[16] ‖ iv[12] ‖ AES-256-GCM(deflateRaw(formula)) )
      key = PBKDF2-HMAC-SHA256(pass, salt, 600k) → AES-256. Compress-THEN-encrypt.
- `=otpEncrypt()`   → `#otp=<padId>.<payload>`  (one-time **pad** = Vernam, NOT a password)
      payload = base64url( (formula XOR pad) ‖ oneTimeMAC[16] ). NO compression (would leak length).
      A one-time Carter–Wegman MAC over GF(2^127-1) makes integrity unconditional too. ONE pad
      = ONE message; never reuse a pad. `=otpDecrypt(url)` decodes it (file picker for the pad).

Opening any `#f= / #raw= / #aes256gcm= / #otp=` link is safe: it boots locked — every
dangerous fn (net/storage/db/code/secrets) is blocked until a human Allows it.

SEGMENT ARCHIVES — the lossless complement to formula-share (=link/=seed):
- `=export("seg")`            → a 甲骨 archive json string of one document segment
- `=export("seg","formula")`  → its re-minting =cels(…)/=def(…) formula
- `=export("seg","encrypt",p) → an `aes256gcm:<blob>` (encrypted archive)
- `=export()`                 → the whole document stack (substrate excluded)
- `=import('{…}')` / `=import(blob, pass)` → load it back: ADD or wholesale-REPLACE
  a same-named segment (refuses a reserved substrate name). Mix in, either form out.

────────────────────────────────────────────────────────────────────────────
WORKED ONE-TIME-PAD DEMO (the pad is PUBLIC → a codec demo, not a secret message)
────────────────────────────────────────────────────────────────────────────
  formula  =dom("h1", "🐢 turtles all the way down")
  pad      https://plastron.ca/card.png  (237084 bytes — download it, load it in the picker)
  link     https://plastron.ca/#otp=card.png.tDQhKiUocjsiLCAvudfU8CB0ccJ0bGcFKGNsbCC0Sokrd2FpIC0rNjpatQy315CfU1qkRszr88szruI

────────────────────────────────────────────────────────────────────────────
FULL VERB CATALOG (baked from =vocab() at build time — current as of this deploy)
────────────────────────────────────────────────────────────────────────────
functions (call as (name …) or =name(…)), grouped by segment:

[builtins]
  *  — Variadic product. Coerces args via Number(); 0 args → 1.
  +  — Variadic sum. Coerces args via Number(); 0 args → 0.
  -  — Variadic subtract. 0 args → 0; 1 arg → negation; ≥2 args → left fold.
  /  — Variadic divide. 0 args → NaN; 1 arg → reciprocal; ≥2 args → left fold.
  json  — (value) — turn any value into a pretty-printed JSON string (JSON.stringify(value, null, 2)). Use it to read an object/array cel as text instead of "[object Object]": =json(clients.C1) renders the chat message list as readable JSON.
  parseRange  — Parse range notation ("Seg!A1:B3", "1,1:2,2") → Range { at, shape }. Passes a Range struct through; null when unparseable.
  rangeToKeys  — Enumerate a Range (struct or notation string) into row-major member cel keys — the Key[] shape inputMap accepts.

[charts]
  barchart  — (labels, values [, x] [, y] [, w] [, h]) - bar-chart ops for canvas(): one bar per row, value above, label below. labels/values are column ranges: =canvas(420, 260, barchart(turtles!A2:A8, turtles!B2:B8)). Default box 0,0,420,260; pass x/y/w/h to place several charts on one canvas. Edits to the range re-render the chart.
  linechart  — (labels, values [, x] [, y] [, w] [, h]) - line-chart ops for canvas(): one series as a polyline with point dots, gridlines with value ticks, labels along the x axis. =canvas(420, 260, linechart(turtles!A2:A8, turtles!C2:C8)).
  piechart  — (labels, values [, x] [, y] [, w] [, h]) - pie-chart ops for canvas(): wedges from 12 o'clock + a legend with each row's value and share. Non-positive values drop out. =canvas(420, 260, piechart(turtles!A2:A8, turtles!D2:D8)).

[checkpoint]
  checkpoint  — (name) - vocabulary head: value is a checkpoint request; the channel drain takes the snapshot.

[cli-segment-export]
  exportToDir  — CLI-only. Async. (state, targetDir, opts?) -> { exportedSegments, targetDir }. Copies the plastron/ store (mirroring its layout) to targetDir/plastron/. opts.onlySegments?: Key[] (default: everything except the kernel closure); opts.includeTransitiveDeps?: boolean (default true when onlySegments is set); opts.includeKernel?: boolean (default false); opts.overwrite?: boolean (default false — refuses if targetDir/plastron exists). Rebuilds targetDir/plastron/index.json from the copied set. Pure file op; does not touch state.cels. Installed only when file-store.backend === 'node-fs'.
  importFromDir  — CLI-only. Async. (state, sourceDir, opts?) -> { importedSegments }. Reads sourceDir/plastron/segments/<name>/<version>/{manifest,segment}.json and writes each into the local segment-store. Refuses role:kernel pairs (kernel comes from the local bundle). Throws on name@version collision unless opts.overwrite is true. Installed only when file-store.backend === 'node-fs'.

[docgraph]
  wiki  — (name?) - a button that opens the wiki window on `name` (a cel key, function, segment, or win.<id> layer). =wiki("runCycle"). The article shows summary/doc metadata, the formula source, links to the functions it calls and its input cels (from inputMap), backlinks, a force-directed neighborhood graph, and an editable note saved to metadata.note. Navigation: click any link or graph node.
  wikidoc  — (article) - passthrough for the wiki window's content cel: renders the article vnode wiki.open assembled, or a hint when none is open.
  wikisrc  — (doc) - the source window's content: a node's LIVE source (formula f, or the bound native's toString - always the running code) + its github link. GitHub cannot be iframed (X-Frame-Options deny); the live source is strictly better anyway.

[dom]
  attr  — (name value …) - HTML attributes (href, target, type, id, …); pass as a child of dom().
  dom  — (tag, ...children) - a presentation vnode value. tag.class is emmet sugar; string children → text, nested dom(…) → elements, (style …)/(attr …)/(on …) → that element's style/attributes/events. An ARRAY child is FLATTENED in place, so a collection from MAP/FILTER renders as sibling children: (dom 'div' (MAP tasks!A1:A6 (LAMBDA t (dom 'div.card' t)))); null/''/false items are dropped. Children must be vnodes/strings/arrays/style/attr/on — NOT a genesis: cels()/segment() build sheets and don't nest here (they'd stringify to "[object Object]"); make the grid top-level instead.
  img  — (src... , style()/attr() children) - an image element. String args form a src fallback chain (first non-empty wins). A "/path" src references a file in OPFS: the painter hydrates it to an objectURL via file-store after paint, so formulas reference images by filesystem path - (img desktop.A2 windows.wallpaper (style ...)). data:/http(s)/blob srcs pass straight through.
  on  — (event handlerKey [payload]) - bind a dom event to a handler cel; pass as a child of dom(). =dom("button", on("click", "vault.lock"), "lock"). The handler runs (state, payload, event).
  style  — (prop value …) - inline styles for a dom element; pass as a child of dom().

[doom]
  doom  — () - open a DOOM window: a wasm-window (canvas) running Doom, lazy-fetching doom.wasm + the WAD from plastron.ca (consent-gated). Arrow keys / Ctrl (fire) / Space (use) drive the player when the window is focused. =doom()

[file-explorer]
  download  — (path) - a button that downloads an OPFS file to your disk.
  explorer  — (cwd, preview, listing) - a PURE OPFS file-explorer vnode: renders the folders (📁, click→explorer.nav to descend) and files (📄, click→explorer.open to preview) from `listing` ({entries,previewText}) at cwd, plus a '..' row and an upload input. The async OPFS read lives in the nav/open handlers (which write explorer.listing); this fn is pure so the window content formula (explorer explorer.cwd explorer.preview explorer.listing) re-fires reactively.
  upload  — (dir?) - a file picker; the chosen file is written into OPFS (default /).

[file-store]
  file-binary_isChanged  — isChanged protocol for file-binary. (oldV, newV) → boolean. Byte-by-byte compare; short-circuits on length mismatch.
  file-binary_mime  — mime protocol for file-binary. (v) → string. Returns 'application/octet-stream' — specialized schemas (file-image, file-doc-*) in Phase B override per type.
  file-binary_size  — size protocol for file-binary. (v) → number. Returns v.length when v is Uint8Array, 0 otherwise.

[forcegraph]
  fgview  — (id, spec, pos, zoom) - render a force-directed graph: canvas edges under draggable node chips (drag to move, scroll to zoom, click to act). Use IN A FORMULA with the instance cels as args so interactions re-render reactively: (fgview "g1" fg.g1.spec fg.g1.pos fg.g1.zoom). spec = { nodes: [{key,label?,size?}], edges: [[a,b]...], pin?, onNode?: {dispatch} }. Set up an instance with fg.set.

[formula-compiler]
  formula  — kind "formula" — a defn whose body is a parameterized plastron formula, not JS. Header form (p1, p2, …) => body; the body is compiled via the kernel's compileFormula and called positionally with params bound to the call args, non-param symbols (verbs, cel refs) resolved from the registry live. Lets you define reusable verbs (layout, note, glue macros) from the formula surface; the definition lives on cel.f so it is editable and wiki-visible. A formula verb is a function of its parameters — body cel refs are read live but do NOT wire reactivity into callers (pass reactive data as a parameter). Interpreted per call: cheap for UI/glue, not for hot inner loops.

[html-template-parser]
  html-template  — Inline HTML-template parser. A FormulaCel whose `f` is the template source and `parser` is "html-template" compiles to a render-spec ({vnode, mount, listeners}). Interpolation deps auto-wire into inputMap.
  html-template-ref  — Live-editable HTML-template parser. The template source is read from the reserved input name "template" at render time and reparsed on string change. Deps are author-declared in inputMap.
  render-spec_isChanged
  string-list_isChanged
  vnode_isChanged

[io-touch]
  touchcapable  — () - true on a touch device (maxTouchPoints>0 / ontouchstart). Mount the overlay only when capable.
  touchpad  — (keys) - an on-screen overlay of hold-buttons; keys=[{label,key}] write keys.pressed via touch.press/release. Doom cannot tell a thumb from a keyboard.

[js-common-schema]
  array_dehydrate
  array_hydrate
  array_isChanged
  bigint_dehydrate
  bigint_hydrate
  bigint_isChanged
  boolean_dehydrate
  boolean_hydrate
  boolean_isChanged
  date_dehydrate
  date_hydrate
  date_isChanged
  map_dehydrate
  map_hydrate
  map_isChanged
  null_dehydrate
  null_hydrate
  null_isChanged
  number_dehydrate
  number_hydrate
  number_isChanged
  object_dehydrate
  object_hydrate
  object_isChanged
  regexp_dehydrate
  regexp_hydrate
  regexp_isChanged
  set_dehydrate
  set_hydrate
  set_isChanged
  string_dehydrate
  string_hydrate
  string_isChanged
  uint8array_dehydrate
  uint8array_hydrate
  uint8array_isChanged

[js-compiler]
  js  — kind "js" — JS source (last expression must be a callable) → runtime Fn via QuickJS-emscripten (a JS-in-wasm sandbox). new Function removed (wasm-only-functions): no DOM, no eval, no host APIs unless granted via the host segment. Lazy-loads the ~1MB runtime on first compile.

[kernel]
  cel-error_dehydrate
  cel-error_hydrate
  cel-error_isChanged
  clearErrors  — Reset the kernel errors log.
  consume  — Drain one channel's queue into the caller.
  dehydrate  — Serialize non-kernel cels and manifests to JSON-shaped 甲骨 + 冊 records.
  dehydrate函  — Warm boot: emit the application as ONE 函 artifact — awake segments deflated inline, dormant payloads passed through, bundled/native as codeSeed, boot.wake = the currently-awake root cover.
  drain  — Drain a channel to empty, invoking its drain fn.
  ensureSegments  — Load every pending segment in the 冊.dependencies closure of the given names, concurrently, with a single precompute for the batch.
  f  — Default formula parser: S-expression source → CompiledLambda. Replace to swap formula languages (provide matching extractDeps).
  findDependents  — Walk 冊.dependencies to find every manifest that transitively depends on a given segment.
  flush  — Tear down a segment and its dependents; fires _dispose, removes cels, precomputes.
  forget  — Remove a segment from state: dormant ⇒ optional persist-to-sink, delete, precompute; awake ⇒ sleep-then-forget (delegates to flush). Kernel-closure guard stays.
  getCel  — The one read — return the live Cel at key (take .v from it). Undefined when missing.
  getSegmentManifest  — Read a 冊 manifest from state.segments by name.
  hydrate  — Inflate segments + manifests + fn maps into state.cels; lock-aware.
  hydrate函  — Boot an application from ONE 函 artifact (version + per-segment payload sources + boot record): install entries dormant/codeSeed, wake the boot roots, apply boot.set. codeSeed missing from the host bundle fails here, before anything runs.
  ilk  — Short form of interlinked, for formulas: (ilk). Cels of an ilk, interlinked.
  isSegmentPending  — True when a segment's manifest is registered but its cels await loadSegment/ensureSegments.
  listSegments  — Return every 冊 currently loaded into state.segments.
  loadScript  — (url) - load an external script from a URL (a CDN) at runtime; resolves once it loads. Browser-only (injects a <script>); off-browser it resolves immediately. Idempotent per URL. The one place external libraries (Pyodide, wabt) enter the page.
  loadSegment  — Install one pending segment's cels (live code-seed cels — no compile), then run schema/memo passes and precompute. No-op when the segment isn't pending.
  registerSegmentLoader  — Park a loader (sync or async () => Cel[]) for a not-yet-installed segment in the reserved segment.loaders registry; optionally seeds its 冊 manifest.
  runCycle  — Fire the full cascade once across every fireable cel.
  setCel  — Cel-plane write (recompile tier): with celType, create/replace the whole cel (definition replacements bump defGeneration so consumers recompile at next fire); without celType, metadata-only edit — v/f refused.
  setCelBatch  — Atomic batch of setCel specs (Record<key, CelSpec>); one precompute, one cascade.
  setValue  — Data-plane write (recalc tier): a ValueCel's v or a FormulaCel's f. Refuses definition cels — those are replaced via setCel.
  setValueBatch  — Atomic batch of setValue writes ([key, value][]); one precompute when any formula source changed, one cascade.
  sleep  — Dehydrate a segment in place: _dispose each cel, deflate to a 甲骨 payload on the SegmentCel's _dormant, drop the live cels. Refuses when awake dependents exist unless { cascade: true }; user-space SCCs sleep as a unit; kernel never sleeps.
  wake  — Inflate + compile a dormant segment plus its dormant dependency closure (compile.cache softens recompile), run the hydrate post-install passes, then one settle cascade over the woken cels. Idempotent; async.

[music]
  miditrack  — (range [, bpm]) - a play-button vnode for a MIDI note range. The range is rows of [track, channel, pitch, notename, start, dur, velocity] (pitch falls back to notename), or note() specs. Clicking dispatches music.play with the resolved notes; bpm defaults to 120. Pure - builds a vnode; the sound happens in the handler.
  note  — (pitch, start, dur, velocity [, channel]) - a pure NOTE SPEC. pitch is a MIDI number or notename string; start/dur in BEATS; velocity 0-127; channel selects the waveform (sine/triangle/square/saw). Returns { note, pitch, start, dur, vel, chan } for miditrack to play - it does NOT make sound itself (effects live behind the player + music.play handler).
  notename  — (name) - convert a notename to a MIDI number. "C4"=60, "A4"=69 (440Hz); sharps/flats like "F#3"/"Gb3" supported. A bare number passes through. MIDI = (octave+1)*12 + semitone.

[net]
  fetch  — (url [, method] [, body]) - the network verb. Async; returns the response body text. Goes through the net gate (logged + allowlist-checked). The single sanctioned way a formula reaches the network. =fetch("https://example.com")
  netallow  — ([host, ...]) - the whitelist, surfaced + edited. No args shows the current policy; with args DECLARES the allowlist as exactly those hosts (off-list fetches are blocked + logged). Default is allow-all. =netallow("api.anthropic.com")
  netlog  — () - the traffic wiretap, surfaced. Snapshot of every network request through the gate (method, host, status / BLOCKED / ERR). Re-evaluate to refresh. =netlog()

[opfs-seeding]
  seedStore  — Async. (state) -> { seeded, skipped }. Populate the segment-store under plastron/ from the in-memory boot segments (the kernel closure). Idempotent: a name@version already in the index is skipped. Host-called after createInitialState; NOT auto-fired at boot (would make every transient State touch disk). Writes via segment-store.putRaw, so it can seed role:kernel segments. node-fs in CLI, OPFS in browser, via file-store.

[origin]
  at  — at(addr, content) - one cell's initial content for a cels() grid, e.g. cels("in", 4, 3, at("a1", "apple")).
  cat  — (path) - read a text file from OPFS into the cell.
  cdn  — (url) - load an external script/library from a URL via the kernel loadScript primitive. =cdn("https://cdn.jsdelivr.net/npm/canvas-confetti") then call what it exposes.
  cels  — genesis: worksheets of editable cels, each like 元. TOP-LEVEL ONLY — a genesis builds the sheet; it is never a child of dom()/canvas() (nested it stringifies to "[object Object]" and never materializes). cels(rows, cols) makes one sheet (auto-named g<r>x<c>); cels(rows, cols, "name") names it; cels("in", 4, 3, "out", 4, 3) makes a WORKBOOK; cels("in", 4, 3, at("a1", "apple"), at("b2", "cel(\"monkey\")")) fills cells with initial values/formulas. To chart a grid, put the chart in a cell OF that grid: at("A5", '=canvas(360,200, barchart(sales!A2:A4, sales!B2:B4))'). delete the formula -> swept.
  db  — (name?) - open/create a sqlite database in OPFS (default main). returns a handle for sql()/tables().
  dbexport  — (db, path) - serialize the database to a portable .db blob and write it to a file-store path (browsable / downloadable). e.g. =dbexport(mydb, "/exports/mydb.db")
  dbimport  — (db, path) - load a .db file from a file-store path INTO the database, replacing its contents. e.g. =dbimport(mydb, "/exports/mydb.db")
  dbseed  — (db, rows, table) - bulk-load a JSON array of row objects into a table. e.g. =dbseed(mydb, A1:C9, "turtles")
  decrypt  — (url, passphrase?) - decode a =encrypt() URL (or bare #aes256gcm= payload) back to its formula source, as text. The inverse of encrypt. Omit the passphrase to be PROMPTED. Does NOT run it — paste the source into a cell yourself. A wrong passphrase or tampered link yields an error (GCM authenticates).
  def  — (name, kind, source) - define a callable function from source in a compiler kind ("js" works out of the box). =def("double", "js", "x => x * 2") then call it: =double(21) -> 42.
  delSeg  — (name) - delete a saved sheet from OPFS.
  doc  — DEPRECATED legacy alias of seg/甲. genesis: compose a whole document from cels()/def()/cel() parts in one formula, e.g. doc(cels("in",2,2,at("a1","x")), def("f","js","x=>x")). What seed() emits — paste it into 元 to recreate an app.
  downloadSeg  — (name) - a button that downloads a saved sheet (.json) from OPFS.
  dragdrop  — (w?, h?) - two drop zones (A, B) + a rectangle you drag between them; it snaps to the nearest zone on release.
  encrypt  — (passphrase?, target?, base?) - like link, but AES-256-GCM the source behind a passphrase. The URL parameter names the method: https://plastron.ca/#aes256gcm=<ciphertext>. =encrypt() encrypts the WHOLE sheet; =encrypt("", "元") one cell. Omit the passphrase to be PROMPTED (so it never lands in the sheet). Pipeline: deflate → AES-256-GCM (PBKDF2-SHA256, 600k) → base64url; AES-256 is quantum-resistant. The URL is opaque; share the passphrase out-of-band. Caveat: the decrypted formula lives in browser memory — run a local index.html offline for high stakes.
  ex  — (formula, target) - a try-it payload pairing an example formula with the cell key it should run in. Used inside the readme's yellow-lightning buttons: (on "click" "tryexample" (ex "=cels(8, 5)" "readme.B2")). Returns a marker the painter hands to tryexample verbatim as the click payload.
  export  — (segment?, form?) - serialize a document segment (or, with no arg, the WHOLE document stack); the boot substrate is never included. form "archive" (default) = a lossless 甲骨 json string for =import(); form "formula" = the re-minting formula (=cels(...)/=def(...)) for a workbook/function segment. Whole-doc formula is also =seed()/=link().
  geom  — geom(x, y, w, h [, minW, minH]) - a cels() grid's WINDOW geometry, CSS-style: x/y = left/top, w/h = the (proportional) width/height, minW/minH = min-width/min-height FLOORS the window never renders or resizes below (like CSS width:50%; min-width:340px). A value in (0,1] is a PROPORTION of the viewport (→ viewport.w/.h); >1 is absolute pixels. e.g. cels("app", 4, 4, geom(0.1, 0.1, 0.5, 0.4, 340, 200), at("a1", …)). Written into win.geom[name] the first time the window materializes; a later user drag/resize is preserved.
  import  — (src) - load an archive json string (or a =formula) into the document stack: ADD a new segment, or wholesale-REPLACE a same-named one. Refuses a boot-set name (net/dom/origin/…). =import("{...}")
  inspect  — (key) - the cel's full definition (celType, value, formula, metadata) as readable JSON.
  installBakedApps  — (state, manifest?) - first-run twin of seedStarter for APPLICATION segments: read the inert #plastron-apps manifest the bundle baked into the page (a { name: archive } map) and app.install each entry (OPFS only, never state). Pass `manifest` to bypass the DOM read (tests/headless). No-op off-DOM or without a filesystem. A broken baked app is logged, not thrown.
  interlinked  — (seg) - force-directed graph of a grid segment's cels (nodes) + their inputMap deps (edges), drawn on canvas.
  item  — (label, action, ...children) - one navigation menu node for nav(). label is text/emoji; action is a window KEY (clicking focuses that window) or a '=formula' (clicking spawns a new window running it), or omit action for a pure submenu parent. children are more item()s (WordPress-style nesting). e.g. item("📊 Charts", item("🥧 Pie", '=cels(3,2, ...piechart...)')).
  jail  — (seed) - render a SANDBOXED iframe running the seed formula as its OWN kernel. allow-scripts but no allow-same-origin → opaque origin: it cannot touch localStorage, the parent page, or other tabs. The browser's real Layer-A jail for untrusted plastrons.
  jailask  — (payload) - JAIL ONLY: round-trip a request to the PARENT over the postMessage bridge and return its reply. The jail has no keys/network of its own; the parent mediates (e.g. an LLM call). Outside a jail it is a no-op.
  kernel  — (seed, preset?) - spawn a QUARANTINED child plastron from a seed formula in a fresh segment (app1, app2, …). preset = locked (default) | compute | net | trusted, capped by THIS kernel's trust. The child's cells live under <seg>.*; gated capabilities (code/net/storage) come back #DENIED unless the preset grants them. A #f= share URL is just a locked seed.
  link  — (target?, base?) - a shareable URL that rebuilds a plastron from the page address (#f= hash, deflate+base64url). =link() encodes the WHOLE sheet; =link("元") encodes one cell's source; =link("", "") makes a relative #f= that works from a file. Paste the URL anywhere — it survives a tweet (t.co shortens it).
  ls  — (path?) - list a directory in OPFS (default /). dirs show a trailing /.
  members  — (segment) - readable listing of a segment's cels (skill first; locked marked).
  mkdir  — (path) - make a directory in OPFS (recursive).
  mount  — (target content) - pin a dom under another element of the sheet instead of in the cell that holds it. target is a selector matched against the view: ".sheet" pins under the cells, "div.cell" under the first cell, "#id" by id; a bare word ("top"/"bottom") is a region anchor laid out around the sheet. Deleting the formula removes it.
  mv  — (from, to) - move/rename a file or directory in OPFS.
  nav  — ([mobile], ...items) - a navigation menu from item() nodes. Pass viewport.mobile as the first arg so it AUTO-SWITCHES: a collapsible left sidebar (☰) on mobile, desktop icon-launchers otherwise (it re-renders on rotate/resize). Nesting collapses via native <details>. e.g. =nav(viewport.mobile, item("📁 Files","files"), item("📊 Charts", item("🥧 Pie", '=cels(3,2,at("A1","x"))'))).
  open  — (name?) - restore a saved sheet from localStorage. =open() loads the default slot; =open("v2") a named one.
  openSeg  — (name) - restore a sheet previously saved with saveSeg from OPFS.
  otpDecrypt  — (url) - decode a =otpEncrypt() #otp= link. Renders a file picker for the matching pad (the URL names which pad). Returns the formula source as text in the formula bar; does NOT run it. A wrong pad or tampered message fails the MAC check. Delete the pad after use.
  otpEncrypt  — (target?) - ONE-TIME-PAD encrypt with UNCONDITIONAL secrecy (perfect, quantum-immune). Renders a file picker: choose a pad file of random bytes (≥ message length + 32) shared with your peer. Produces https://plastron.ca/#otp=<padName>.<ciphertext> — ciphertext = formula XOR pad, plus a one-time Carter-Wegman MAC for unconditional integrity. NO compression (would leak length info). One pad file = ONE message: DELETE it from both stacks after use; never reuse a pad. target "" = whole sheet; a cell key = its source.
  otpLoader  — (padId, payload) - the boot view a #otp= URL renders as 元: a file picker to load the named pad and decrypt the shared plastron (kernel stays LOCKED). Used by bootFromHash; not usually called by hand.
  rm  — (path) - remove a file or directory from OPFS (auto file-vs-dir).
  save  — (name?) - persist your sheet (cell sources + def'd functions) to this browser's localStorage. =save() then reload and it's back; =save("v2") for a named slot.
  saveSeg  — (name) - save the whole current sheet to an OPFS file under /plastron/sheets.
  schema  — (db) - introspect the database: tables, columns, primary keys and foreign keys (the basis for the visual query builder).
  seed  — () - serialize the WHOLE document to a single recreating formula (a doc(...) / cels(...) you can paste into 元). Callable from any cel; its value becomes the seed source.
  segment  — genesis: compose a SEGMENT (document) from cels()/winapp()/chatapp()/def()/cel() parts in one formula, e.g. =segment(...). (was doc.)
  segments  — () - every loaded segment with role, version, dependencies.
  segs  — () - list sheets saved to OPFS (=saveSeg/openSeg/delSeg manage them).
  sheetcells  — (keys, vals) - zip a key list + value list into sheetgrid entries {key,col,row,value}, deriving col/row from each key's A1 suffix. The host formula passes the keys (strings) + the cel values (a list referencing each cel, so the grid is reactive).
  sheetdoc  — (state, seg, title?) - open a worksheet window over the cels of document segment `seg`, rendered via the sheets segment's sheetgrid (reactive: the content formula references each grid cel). The window (program) is win.<seg>; the data stays in segment <seg>, so dumpSegments([seg]) exports just the document. Idempotent.
  simulate  — (fnName, n?) - def-driven animation: run a def'd frame fn (i -> [x, y]) n times and play the trajectory on an animated canvas.
  sql  — (db, query) - run SQL against a db handle; SELECT returns rows, writes persist to OPFS.
  stat  — (path) - size / isDir / mtime of an OPFS path.
  tables  — (db) - list the tables in a db.
  taskbarBar  — (active, ...states) - render the desktop bottom taskbar: one chip per non-closed, non-docked window, the active one bordered, a minimized one dimmed. Receives each window's state VALUE; click → desktop.taskClick. Built reactively by desktop.taskbarGenesis.
  touch  — (path) - create an empty file in OPFS if it does not exist.
  tree  — (path?) - recursive directory tree in OPFS (default /).
  tryexample  — (state, exPayload) - the readme 'try it' handler. Clicking a yellow ⚡ next to an example copies that example's formula into a scratch cell beside the readme (its B-column target) and evaluates it, so the result appears. Implemented by seeding 元.draft with the formula then running origin.run on the target cell, which sniffs the source into a FormulaCel, writes it, re-evaluates, and repaints.
  unlink  — (url) - decode a =link() URL (or bare #f= payload) back to its formula source, as text. The inverse of link. Does NOT run it — paste the source into a cell yourself.
  viewport  — () - current page metrics {w, h, mobile, orient} as a one-shot snapshot. For REACTIVE layout reference the cels instead: viewport.w / viewport.h / viewport.mobile / viewport.orient update on window resize, so a formula re-runs to relayout — =IF(viewport.mobile, dom("h1","phone"), win("app","App",body)) or =win("app","App",body, viewport.w, viewport.h - 40).
  vocab  — (segment?) - the values + functions usable in formulas (callable lambdas/compilers + value cels) with descriptions; no arg = across all loaded segments.
  write  — (path, text) - write text to a file in OPFS (create/overwrite).

[peer]
  peerAccept  — (answer) - WebRTC: finalize the connection on the offerer side; the data channel opens. Browser-only.
  peerAnswer  — (offer) - WebRTC: accept an offer, return the answer (copy back to the offerer). Browser-only.
  peerOffer  — () - WebRTC: mint an SDP offer (copy it to the other browser). Browser-only.
  peerallow  — ([prefix, …]) - the shared-namespace allowlist (default ["shared."]); remote writes outside it are dropped. =peerallow("*") denies all. No args shows the policy.
  peerjoin  — (room, relayUrl) - automatic signaling via a relay: join a room, the existing member offers + the newcomer answers, all brokered over WebSocket (no SDP copy/paste). Browser-only, 2-peer rooms.
  peerlog  — () - the peer wiretap: inbound/outbound messages + the gate's decision for each.
  peersend  — (key, value) - explicit share: setValue locally + broadcast to the peer. Demo shim until automatic sync rides the post-cascade seam.

[php-compiler]
  php  — kind "php" — PHP source defining named functions; the trailing line names the callable (use the := php.<fn>{ … } selector). Compiled via php-wasm: each compile defines its source in a fresh PHP namespace so a recompile never "Cannot redeclare". The runtime call is async (returns a Promise — fireCel awaits it), so a php verb works as a whole-formula value. Dynamic-imported on first compile.

[plastron-canvas]
  canvas  — (width, height, ...ops) - a <canvas> drawn from rect/text/line/circle/wedge ops; the painter replays them onto the 2d context. use as a cell value or inside mount/dom. op LISTS flatten in, so chart fns compose: =canvas(420, 260, barchart(t!A2:A8, t!B2:B8)).
  circle  — (x, y, r [, fill] [, stroke] [, lineWidth]) - a canvas circle op.
  line  — (x1, y1, x2, y2 [, stroke] [, lineWidth]) - a canvas line segment op.
  orbit  — (cx, cy, orbitR, planetR, period [, color] [, phase]) - an ANIMATED canvas op: a planet circling (cx,cy) once every `period` seconds. A canvas with an orbit op runs a rAF loop; stack a few around a circle() for a heliocentric system.
  rect  — (x, y, w, h [, fill] [, stroke] [, lineWidth]) - a canvas rectangle op. pass to canvas(): =canvas(200, 80, rect(0, 0, 200, 80, "#1a1a2e")).
  text  — (x, y, text [, fill] [, font]) - a canvas text op (baseline at x,y). =text(12, 24, "hi", "#fff", "16px system-ui").
  wedge  — (cx, cy, r, a0, a1 [, fill] [, stroke] [, lineWidth]) - a canvas pie-slice op from angle a0 to a1 (radians, clockwise from 3 o'clock). The piechart fn stacks these.

[py-compiler]
  js-to-py  — Bridge: convert a JS-domain value to a py-domain value. Calls pyodide.toPy(). For scalars this is essentially identity (Python ints, floats, strs interoperate freely). For composites it builds a Python dict/list/etc. Authors invoke via (js-to-py someCel).
  py  — Python compiler. Compiles Python source — typically a `def` followed by the function name — into a runtime Fn via Pyodide. Pyodide is dynamic-imported on first compile and the runtime instance is shared across all py-kind cels. v1 runs main-thread; worker isolation is v2.
  py-to-js  — Bridge: convert a py-domain value (PyProxy or already-JS scalar) to a JS-domain value. Calls pyodide.toJs() on PyProxies; identity for already-converted scalars. Authors invoke via (py-to-js someCel).

[sheet]
  infix  — Infix spreadsheet formula parser. A FormulaCel with parser "infix" and f like "=A1*2" compiles to a CompiledEnvelope; A1-style references resolve to sibling cell keys (A1 → sheet.A1) and auto-wire into inputMap. Supports + - * / & comparisons, unary -, parens, ranges, and SUM/MIN/MAX/AVG/IF.

[sheet-host]
  sheetView  — (cfg editing draft mount error keys vals srcs geom selected tabdrag) - spreadsheet RenderSpec producer: each worksheet table wrapped in a draggable window frame. Single-click SELECTS a cell (origin.select sets 元.selected) and the cell KEEPS rendering its value; the window's formula bar shows the SELECTED cell's source (bound to 元.draft) and commits on Enter (origin.key) to 元.selected. Double-click opens the inline editor. Formula bar order is left-to-right [W wiki][lightning-bolt fire][textarea]: the fire button is an inline SVG lightning bolt (origin.fire — re-evaluate the selected cell), the W opens the wiki. The bar textarea defaults to a tall multi-line box (min-height ~7.5rem) and wraps (pre-wrap, no horizontal scrollbar); resize:vertical lets the user grow it further. Tabs render by win.geom[seg].order; dragging a tab chip reorders it among siblings (winsheet.tabMove) or, released off the window, tears it into its own window (winsheet.tabDrop -> tearoff). While a tab is being dragged (winsheet.tabdrag), its chip highlights in the SAME blue as a window's drop-target glow. Dragging a window/tab over another window glows the target (win.geom[seg].glow) to show where it will land. The formula bar's W wiki + fire buttons are sized for a comfortable hit target (glyphBtn ~1.1rem, bolt svg ~22px). Cells render clean (no per-cell glyphs). The titlebar shows exactly one of ◱ (mid/restore, when maximized) and ⛶ (maximize, when not), plus – minimize and ✕ close.

[sheets]
  sheetcell  — (value) - render ONE spreadsheet cell value to a vnode: a number/string as text, a vnode (a formula that built dom/canvas/a chart) in place, a cel error as its code. The leaf of the grid.
  sheetgrid  — (label, cells, opts?) - render a worksheet's cells as an editable Excel grid CONTENT vnode (corner label + column letters + row numbers + cells). `cells` is the host-aggregated list [{key,col,row,value,src}]; `opts` = {active, selected, draft, edit, select, fire, commit} (handler keys default to origin.*). The active cell shows an inline editor; others dispatch select/edit. SHEET-UNIQUE native renderer (the perf fast path); a window tab hosts the result so a sheet tabs next to any app.

[sqlite-client]
  sqlclient  — (db [, title]) - open a SQLite client WINDOW on db: a query editor, a tables sidebar, and the result rows below (rendered as a grid + materialized into real sqlres.* cels). e.g. =sqlclient("demo")
  sqlclientui  — (db, query, tablesData, results, error) - the client panel vnode: query textarea + Run, tables sidebar, results grid. Pure render; state lives in sqlc.<db>.* cels.

[sqlite-demo]
  sqlitedemo  — ([db]) - lay down a worked SQLite example: a db handle, a CREATE-TABLE schema, a JSON seed, get/aggregate queries, and the client window opened on it. e.g. =sqlitedemo()

[user-space-ops]
  closeUserSpace  — Async. (state, name) -> void. Tear down a user-space + its private deps (dependents-first / root-first flush order so each flush's dependent-check passes), WITHOUT saving (close != save; the host composes save-before-close UX) and WITHOUT evicting shared library deps (they stay loaded for the next user-space in the same application session). Throws if name isn't a loaded role:'user-space' segment.
  hydrate-closure  — Async. (state, rootName, version?) -> Key[] (newly-loaded segment names). The shared read-walk-topo-hydrate pipeline: BFS the segment-store from rootName collecting its transitive dependency closure, filter out segments already loaded, topo-order the remainder (deps first), and hydrate them in one call. Idempotent (whole closure already loaded -> []). Reused by loadUserSpace, application auto-start, and (future) optional-segment-loading's loadSegment.
  loadUserSpace  — Async. (state, name, version?) -> manifest. Open an existing user-space from segment-store, auto-starting its parent application (applications[0]) via hydrateClosure if not already loaded, then hydrating the user-space's own closure. Idempotent: reopening a loaded user-space is a cheap no-op. version defaults to latest. Throws if name isn't found or isn't role:'user-space'.
  newUserSpace  — Async. (state, name, applicationName, options?) -> manifest. Create a fresh empty user-space (role:'user-space', applications:[applicationName], dependencies:[applicationName, ...extraDeps]) and hydrate it into state. Throws if applicationName isn't a loaded role:'application' segment, or if name collides with a loaded segment / a stored segment (unless options.overwrite). autoSave defaults true (persists to segment-store immediately); pass { autoSave: false } for a preview-without-commit flow.
  saveUserSpace  — Async. (state, name) -> Key[] (persisted segment names). Dehydrate the user-space + its PRIVATE closure (role:'user-space' deps whose applications array is a subset of this user-space's) and write each via segment-store.put. Library/application/kernel deps are excluded — they ship with the distribution, not the user's saved data. Throws if name isn't a loaded role:'user-space' segment.

[wasm-bytes]
  js-to-wasm  — Bridge: convert a JS-domain value to a wasm-domain value. Scalars are identity (the wasm ABI accepts JS numbers via WebAssembly's coercion). Authors invoke via formula syntax: (js-to-wasm someCel).
  wasm  — Precompiled-wasm loader. Takes a precompiled .wasm module as base64 bytes (inline source) or a 'file-store:<path>' reference, instantiates it via WebAssembly.instantiate, and exposes one export as a callable Fn. The sibling to wat/py/js that takes bytes instead of source. Export chosen by metadata.wasmExport (else prefer 'main', else the single export). Imports default to { host }; metadata.imports names a provider cel for WASI/env shims. Gated on csp.wasm-available.
  wasm-to-js  — Bridge: convert a wasm-domain value to a JS-domain value. Scalars (i32/u32/f32/f64, i64/u64 as BigInt) are JS-equivalent on the wire, so identity. A composite WasmHandle { kind:'wasm', ref } is dereferenced from the module-side value table and released. Authors invoke via formula syntax: (wasm-to-js someCel).

[wasm-types]
  wasm-bigint_dehydrate
  wasm-bigint_hydrate
  wasm-scalar_dehydrate
  wasm-scalar_hydrate
  wasm-scalar_isChanged

[wasm-window]
  wasmcanvas  — (id, engine?, active?) - the body of a wasm window: a focusable <canvas id="wasm-<id>"> the engine grabs by id. Focus/blur toggle wasm.<id>.active; keydown/keyup route to the engine (active-gated).

[wat-compiler]
  js-to-wat  — Bridge: convert a JS-domain value to a wat-domain value. v1 scalars are identity (the wat ABI accepts JS numbers directly via WebAssembly's coercion). Becomes a real marshalling call when composites and workers land. Authors invoke via formula syntax: (js-to-wat someCel).
  wasm-to-wat  — Show-WAT diagnostic: takes a wasm binary (Uint8Array) and returns its WAT text via wabt.readWasm + toText. Use on cel._wasm to inspect any wasm module — most useful for kinds whose source isn't WAT (Rust / other wasm langs). Wat lambdas can pass their own bytes for canonical round-trip.
  wat  — WAT compiler. Compiles WebAssembly text-format source to a runtime Fn via wabt.js + WebAssembly.instantiate. Wraps the module's main export (or its single function export) as a callable Fn. Gated on csp.wasm-available.
  wat-to-js  — Bridge: convert a wat-domain value to a JS-domain value. v1 scalars (i32/u32/f32/f64) are JS-equivalent on the wire, so this is identity. When composites (string, list, record, variant) and workers arrive, this becomes a real marshalling call into the wat worker's toJs protocol. Authors invoke via formula syntax: (wat-to-js someCel).

[winapps]
  toolbar  — (...children) - a horizontal toolbar row an app places above its content. Arrays flatten and non-vnodes drop, so MAP(...) of toolbtn()s works. App-agnostic chrome; sheet-specific UI lives in the sheets segment.
  toolbtn  — (glyph, title, handler, payload?) - a generic toolbar/titlebar button for any windowed app: shows `glyph`, stops pointerdown (so it doesn't start a window drag), and dispatches handler(state, payload, event) on click. Compose several inside toolbar().

[winapps-wasm]
  wasmapp  — (id, title, engineCel, opts?) - genesis a wasm app as a SELF-MOUNTING window on the new tabbable frame: the graph<->engine bridge cels (wasm.<id>.in/.out/.active), a <canvas> content cel (wasmcanvas id), a window state cel (one tab = the canvas), and a (mount '.origin' (wframe …)) frame. So a wasm window (DOOM) tabs next to a sheet. opts = geom {x,y,w,h,icon}. The engine instance is grabbed by canvas id at boot; pair with winapps app.install (assets → OPFS) + a harness provider to keep the app mostly formula-cells.

[window]
  wframe  — (state, active, ...contents) - render ONE window's chrome around the ACTIVE tab's content (contents[active]): titlebar (icon + name + minimize/fullscreen/close), a tab strip when the window has >=2 tabs, the body, and a resize grip. `state` is the window state object { x,y,w,h,z,min,max,closed,title,icon,tabs:[{ref,title,icon}],active,dockedIn }; `active` is win.active; `contents` is one value per tab (the frame formula passes each tab's content cel). A window whose state has dockedIn set self-hides (its host renders it). Content-AGNOSTIC: a tab's content may be a sheet grid, a wasmcanvas, or any winapp body, so heterogeneous windows tab together.
  wopen  — (id, title, body, where?) - genesis a SELF-MOUNTING window: a content cel (= body, the app render), a state cel (one tab = its own content), and a frame cel `(mount '.origin' (wframe <state> win.active <content>))` that mounts itself into the desktop. `where` = geom(x,y,w,h) sizes it. There is NO separate desktop renderer - each top-level window draws itself; dock one into another with window.dock to tab them together.

[windows]
  chatapp  — (channel, title) — a chat WINDOW: seeds log/input + a winframe of (chat …). =chatapp("claude","Claude")
  chatui  — (channel, log, input) — generic chat UI; the other party (claude/grok/peer) is just a user
  desktop  — mount a fixed full-screen wallpaper behind the windows (ships inline). =desktop()
  explorerwin  — genesis: a standalone OPFS file-explorer WINDOW. Seeds explorer.cwd / explorer.preview and a (explorer explorer.cwd explorer.preview) content formula that references them, so click-to-descend / click-to-preview re-fire reactively. =explorerwin()
  readme  — the plastron readme as a vnode, for a readme window
  readmewin  — genesis: the readme WINDOW with the full plastron README
  win  — (key, title, content [, x] [, y]) - make a draggable/resizable window in ONE step. Compose two with doc: =doc(win("w1","A","hi",60,60), win("w2","B","yo",440,180))
  winapp  — (id, title, contentSource) — create a window backed by a state cel whose content is a FORMULA (win.<id>.content). Mounts a real app: =winapp("secrets","Secrets","(secrets (locked secretsNote) (apiKeys))")
  winclose  — (key) - remove a window from win.list
  window  — (key, x, y, w, h, title, content) - a draggable/resizable window FRAME around content. Geometry (x/y/w/h) comes from the host's key.x/.y/.w/.h cels, so it's reactive + persists. Titlebar drags; corner resizes; body scrolls.
  winframe  — (state, active, content) - render the STANDARD window frame DOWNSTREAM of a state cel; its titlebar buttons (W wiki · – minimize · ⛶/◱ maximize · ✕ close) write the state cel by ref. Every window uses this same frame; the wiki window (segment win.wiki) is the lone exception that omits the W button (it IS the wiki - a W there would just re-open the wiki on itself).
  winmake  — (id, title, content) - the formula: create a window backed by a STATE cel (win.<id>) + its frame. Modify the state cel to open/close/move. =winmake("d1","Window 1","hi")
  winopen  — (key, title) - register a window: add to win.list + seed its geometry cels (x/y/w/h/z/min/title) + raise
  wintoolbar  — (keys, titles) - a taskbar of open windows; click a chip to restore+raise. Pass win.list + the titles.

[xlsx]
  xlsxexport  — (segment?) - serialize a grid segment's value cels (default 元) to a .xlsx file, returned base64. Round-trips with xlsximport.
  xlsximport  — (base64) - parse .xlsx bytes (base64) into a genesis worksheet of cels (layer 'xlsx'). One sheet, number/string values.
  xlsxload  — (base64) - parse .xlsx bytes (base64) and MATERIALIZE them into the live sheet (the xlsx worksheet of cels)
  xlsxopen  — () - browser file picker: choose a .xlsx and import it into the sheet
  xlsxsave  — (segment?) - export a grid segment to .xlsx and download it (default 元)

[excel]  (infix builtins — call as =NAME(…); inline, not cels)
  ABS  — (x) absolute value
  AND  — (…) all true
  AVERAGE  — (range…) mean (AVG)
  AVERAGEIF  — (range, crit, [avgRange]) mean matching
  BYROW  — (range, fn) apply fn to each ROW (an array) → array: =BYROW(A1:C3, LAMBDA(row, SUM(row)))
  CONCAT  — (…) join (CONCATENATE)
  COUNT  — (range) count numbers
  COUNTA  — (range) count non-empty
  COUNTIF  — (range, crit) count matching
  DATE  — (y, m, d) ISO date string
  FILTER  — (range, predicate) keep values where predicate is true → array: =FILTER(A1:A9, LAMBDA(x, x > 0))
  HLOOKUP  — (key, range, row, [exact]) horizontal lookup
  IF  — (cond, then, else) branch
  IFERROR  — (value, fallback) catch errors
  IFS  — (cond, val, …) first true
  INDEX  — (range, row, [col]) cell at position (1-based)
  INT  — (x) floor
  LAMBDA  — (param, …, body) an inline function value — pass to MAP/REDUCE/etc: =LAMBDA(x, x*x)
  LEFT  — (s, n) first n chars
  LEN  — (s) length
  LET  — (name, value, …, calc) bind names for THIS formula, then compute calc — reuse a sub-expression without repeating it: =LET(r, A1*2, r + r*r)
  LOWER  — (s) lowercase
  MAP  — (range, fn) apply fn to each value → array: =SUM(MAP(A1:A9, LAMBDA(x, x*x)))
  MATCH  — (key, range, [type]) 1-based position
  MAX  — (range…) maximum
  MID  — (s, start, len) substring
  MIN  — (range…) minimum
  MOD  — (a, b) remainder
  NOT  — (x) negate
  OR  — (…) any true
  REDUCE  — (init, range, fn) fold: =REDUCE(0, A1:A9, LAMBDA(acc, x, acc + x))
  RIGHT  — (s, n) last n chars
  ROUND  — (x, n) round to n places
  ROUNDDOWN  — (x, n) round toward 0
  ROUNDUP  — (x, n) round away from 0
  SCAN  — (init, range, fn) running fold → array of each step
  SUM  — (range…) sum
  SUMIF  — (range, crit, [sumRange]) sum matching
  TEXTJOIN  — (delim, ignoreEmpty, …) join with delimiter
  TRIM  — (s) collapse spaces
  UPPER  — (s) uppercase
  VLOOKUP  — (key, range, col, [exact]) vertical lookup

values (reference by name):
  errors = []
  precomputedStates = {"waveCascade":{},"sortedWaves":[0],"wav