<!-- GENERATED from the guide segment (plastron/src/甲骨坑/library/guide/甲骨.json) by plastron/scripts/generate-llms.mjs — edit the guide cels, not this file. -->
# 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:
=help("", "text") every verb + its one-line description, grouped by segment
=help("charts", "text") just one segment's verbs (as this cell's VALUE — no pane)
=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")`
- **JSON** — data entry, starts with `{` or `[`:
`{"name": "kirk", "age": 7}` `[1, 2, 3]`
The cell's VALUE is the parsed object/array (archives round-trip it as the
same JSON). Unparseable `{`/`[` content stays a plain string.
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 a [position] index (0-based); never leave the raw array in a cell:
at("C2",'=SCAN(0,B2:B2,LAMBDA(a,v,a+v))[0]'), at("C3",'=SCAN(0,B2:B3,LAMBDA(a,v,a+v))[1]') …
at("D2",'=BYROW(A2:C4,LAMBDA(r,SUM(r)))[0]'), at("D3",'=BYROW(A2:C4,LAMBDA(r,SUM(r)))[1]') …
A lone =SCAN(…)/=BYROW(…) sitting in one cell is the #1 way these tasks render wrong — don't.
DICTS & LISTS (structured values in cells + how to read them back)
Literals (infix): `={name: "kirk", age: 7}` (keys bare or "quoted") and `=[1, "two", A1]`.
Values are full expressions — a cel ref inside stays a REAL dependency (reactive).
Typed-in data (no `=`): `{"a": 1}` / `[1,2,3]` — JSON content enters as the value itself.
ACCESS from another cell — dot for members, [ ] for 0-BASED positions (JS-domain;
INDEX() stays 1-based Excel-domain), negatives from the end, [from:to] slices:
=A2.age =A2.users[0].name =fish!A1.species (cross-sheet)
=B2[0] =B2[-1] =B2[1:3] =B2[:2] =A2[B1] (dynamic key)
=LET(o, A2, o.age + o.xs[2]) (bound names walk the binding)
=SUM(MAP(A2.xs, LAMBDA(x, x*10))) (a dict's list feeds MAP/REDUCE/…)
BROADCAST — dot over a RANGE of dict cells (or an array-of-dicts value) maps
per item, skipping empty cells; brackets stay positional (raw, no skip):
=D1:D9.age =SUM(D1:D9.age) =fish!A1:A9.species
=D1:D9.user.name (chains re-broadcast) =D1:D9[0] (first CELL's value)
=A2.rows.n (array value broadcasts the same way as a range)
A dotted name whose head ISN'T a cell ref (fg.g1.spec, viewport.mobile) is still an
exact CEL KEY — only an A1-shaped head means member access. Missing members read as
blank (like an empty cell), not an error. Objects/arrays display as raw JSON-ish text;
wrap in =json(A2) to inspect, or MAP → dom() to render.
TABLES & CONVERTERS (collections segment) — the hub form is the LIST OF DICTS:
=rows(A2:C9, A1:C1) header range + data range → list of dicts
=table(rows(A2:C9, A1:C1)) render it as an HTML table in a view pane
=unique(D1:D9.tag) set discipline (order-preserving, structural)
=jsonparse(s) JSON string value → object/array (inverse of json())
=dbseed('db', rows(A2:C9, A1:C1), 't') …and into SQLite
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 iterates a DATA RANGE or an inline list literal — for a FIXED, small set of sections
(a few columns or tabs) MAP over the list, or define the section once as a LAMBDA (via
LET) and CALL it per section:
=dom("div", MAP(["Left", "Middle", "Right"], LAMBDA(name, dom("div.col", dom("h3", name)))))
=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.
PYTHON CELLS (=def a py fn — Pyodide; auto-loads the packages the source imports)
=def("stats", "py", A1) compile the Python SOURCE in cell A1 into a callable =stats(x)
A py cell that `import numpy` / `import cv2` / `import mpmath` / `from PIL import …`
auto-loads that Pyodide package before running (heavy: the first use pulls the wheels
from the CDN, then caches). The 🕵️ ChaosEdgeSteg doc is a worked example — upstream
OpenCV steganography running in a py cell, the algorithm visible on the sheet and
bound with =def (edit the source and it recompiles live).
DATABASE (browser SQLite — persistent, runs in a Worker; the db NAME is the handle)
=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
FILES (OPFS, in-browser)
=write("/a.txt","hi") =cat("/a.txt") =ls("/") =mkdir("/d") =upload("/")
WINDOWS / LAYOUT
=wopen(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)
=wopen(id, "Title", '=<formula>') a draggable WINDOW whose body is a REACTIVE formula
=segment(part, part, …) compose cels()/wopen()/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")),
wopen("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/=wopen/=doom/=jail MINTS a document segment (kind workbook/window/
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 {…}.
- =wopen("app", "App", body, geom(0, 0, 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):
=1+1 → utf8 3D 31 2B 31 → base64url PTErMQ → #f=0PTErMQ
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 the =help text mode at build time — current as of this deploy)
────────────────────────────────────────────────────────────────────────────
functions (call as (name …) or =name(…)), grouped by segment:
[asset-loader]
asset.fetch — (state, name [, {base?, cache?, onProgress?}]) → Uint8Array. Fetch a named binary asset from base+name, cached in OPFS at /plastron/assets/<name> so it downloads once and reloads instantly. The one mechanism behind every heavy lazy tier (doom WAD, and rapier/maplibre/occt/three to come). cache:false skips the cache; caching is best-effort — a missing file-store/OPFS silently degrades to a plain fetch.
[buffer-schema]
buffers_dehydrate
buffers_hydrate
buffers_isChanged
[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.
checkpoint.delta — (state, from, to?) - {added, removed, changed} cel keys between two snapshots, or snapshot vs live when to omitted.
checkpoint.drain — (items) - take dehydrate snapshots for enqueued checkpoint requests; ring capped at 20.
checkpoint.list — (state) - snapshot names in ring order.
checkpoint.restore — (state, name?) - DISPATCHED ONLY: wipe non-kernel user-space, rehydrate the named (or latest) snapshot, precompute, cycle.
checkpoint.snapshot — (state, name) - imperative snapshot (gestures/drains call this directly).
[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'.
[collections]
jsonparse — (s) - parse a JSON STRING VALUE into the object/array it encodes — the runtime twin of typed-in `{…}`/`[…]` data entry, and the inverse of json(). A non-string passes through unchanged (already parsed); invalid JSON is a #VALUE!-style cel error IFERROR can catch. =jsonparse(cat('/data.json')).users[0].name.
e.g. =jsonparse(cat('/data.json')).users[0].name
Entry routes differ by TIER, not representation: typed-in
{"a":1} mints a writable ValueCel, ={a:1} mints a derived
FormulaCel — same shape, different writability. jsonparse is the
runtime route for JSON that arrives as a STRING value (cat(),
fetch, sql text): parse once at the boundary, then the one
access algebra applies.
rows — (data, headers?) - tabular data → a LIST OF DICTS (the hub form the collections doctrine converges on). data: nested row arrays whose FIRST row is the header ([["name","age"],["ada",36]] — the S-expr range shape, or BYROW output), OR a flat value list plus an explicit headers array (=rows(A2:C4, A1:C1) — the infix range shape: headers give the column count, the flat data chunks into rows), OR an already-dict list (identity). Empty rows are dropped. Feed the result to table()/dbseed()/broadcast dot access (=rows(...).age).
e.g. =rows(A2:C4, A1:C1) → [{name:"ada", age:36}, …]
The collections doctrine converges every tabular shape on this
ONE hub form so the access algebra never changes as data moves
between forms: =sql/supabase results already ARE it, dbseed
consumes it, broadcast dot access (=D1:D9.age) reads it. Convert
at the boundary with rows() instead of reshaping by hand —
nothing else does range→dicts.
table — (rowsOrData, opts?) - render a LIST OF DICTS as an HTML table vnode (class pl-table) for a view pane: columns are the union of row keys in first-seen order, header from the keys. Accepts anything rows() accepts (nested header-row arrays convert automatically). opts: {cols: ["name","age"]} selects/orders columns; {limit: n} caps rows (a '… N more' footer row shows the truncation). Values render via json-ish text; nested objects stringify. =view('people', table(rows(A1:C9))).
e.g. =view('people', table(rows(A2:C9, A1:C1), {limit: 20}))
Tables are VIEWS over plain JSON-shaped values, not a storage
shape — there is no table object to mutate. Edit the value/range
the table reads and the view re-renders reactively. And no
value→range spill: ranges are authored, never exploded into (the
no-spill doctrine).
unique — (list) - SET discipline for a list value: drop duplicates, keep first occurrence, preserve order. Primitives compare by value; dicts/lists by structural (JSON) equality. Non-array input returns a one-item list (or [] for blank). =unique(D1:D9.tag) → the distinct tags.
e.g. =unique(D1:D9.tag) → the distinct tags, first-seen order
Sets are lists with a uniqueness DISCIPLINE, not a fifth storage
form — keep set semantics in this verb rather than reaching for
a new container (no DICT()/ARRAY(): literals + JSON entry cover
construction).
[crdt]
crdt.append — (stack, record) — grow-only insert of a layer into a diff stack, returning a NEW array sorted by (ts, author, id) and deduped by id. Idempotent: re-appending the same record (same id) is a no-op. The stack is the CRDT value; hold it in a ValueCel.
crdt.apply — (text, ops) — apply an array of hunks { at, del, ins } to a string and return the new string. Total/defensive: clamps out-of-range at/del so a stale hunk never throws (it just lands as close as it can). Hunks apply high-index-first so earlier `at` stay valid.
crdt.diff — (before, after) — minimal LINE-LEVEL diff between two strings as an array of hunks { at, del, ins:[lines] }: at line `at`, delete `del` lines and insert `ins`. One result covers multiple lines. Invariant: (crdt.apply before (crdt.diff before after)) === after.
crdt.layer — (before, after, author, ts [, parent]) — one diff-stack layer: { id, author, ts, parent, ops } where ops = (diff before after) and id = `ts.author.<hash(ops)>` (deterministic; identical edits dedup). ts is caller-supplied (pass a Lamport counter or a `now` cel) so the verb stays pure & replayable. parent = id of the head this edit saw (causality).
crdt.resolve — (stack [, n]) — fold a diff stack from the empty string over its layers (sorted by ts, author, id) to the current text. With n, apply only the first n layers — n edits up from 0 (time-travel / undo). Every replica folds the same set the same way → convergent. =crdt.resolve(msg.stack) renders live text; recomputes whenever the stack cel changes.
[crypto]
crypto.datakey — () - mint a fresh random AES-256-GCM data key (base64 raw bytes) - the per-epoch symmetric key a shared segment wraps to recipients.
crypto.decrypt — (dataKeyB64, 'ivB64.cipherB64') - AES-256-GCM decrypt; returns the utf8 plaintext.
crypto.encrypt — (dataKeyB64, plaintext) - AES-256-GCM encrypt; returns 'ivB64.cipherB64'.
crypto.forget — (handle) - drop a key from the keystore (rotation / revoke); returns whether it existed.
crypto.keygen — (kind?) - Tier 1: mint a NON-EXTRACTABLE private key (Ed25519 'sign' default, or P-256 'ecdh' for wrap/unwrap) in the keystore; returns a CryptoKeyHandle (id + public). The private key is never a cel value.
crypto.keys — () - list the keyring as handles (public material only; private keys never surface).
crypto.prfAvailable — () - Tier 2 probe: is WebAuthn PRF (passkey) available in this host? (browser-only; false in Bun).
crypto.prfWrapKey — (credentialId?, saltB64?) - Tier 2 SCAFFOLD: derive a wallet-blob wrapping key from a passkey's PRF secret via KDF. Deep integration is a browser e2e follow-up; reports status when PRF is unavailable.
crypto.pubkey — (handle) - the PUBLIC key as a cel-storable base64 string (the keyring's shareable currency).
crypto.seedExport — () - Tier 3 RECOVERY ONLY (not login): export a 12-word recovery phrase for account recovery / device migration.
crypto.seedImport — (phrase) - Tier 3 RECOVERY ONLY: validate + import a 12-word recovery phrase (identity re-derivation is the device-migration follow-up).
crypto.sign — (handle, message) - Ed25519 sign; the private key is resolved at the effect site from the keystore, never surfaced. Returns base64 signature.
crypto.unwrap — (myEcdhHandle, senderPubB64, 'ivB64.cipherB64') - ECDH P-256 unwrap; returns the data key (base64).
crypto.verify — (publicKeyB64|handle, message, signatureB64) - Ed25519 verify against a public key; returns boolean.
crypto.wrap — (myEcdhHandle, recipientPubB64, dataKeyB64) - ECDH P-256 derive + AES-GCM wrap the data key to a recipient; returns 'ivB64.cipherB64'.
[defn]
defn.drain — (items) - commit binder requests: setCel EditableLambdaCels (definedBy/origin stamped), enforce ownership + overwrite flag, retire renames and orphans (binders are authoritative for a name's existence).
[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.
wiki.open — dispatch (state, key) - assemble the wiki article for `key` (a window state ref like win.x.state articles its layer), stash it in wiki.article, lazily create + open + raise the win.wiki window (the frame only when origin's mount resolves), paint. The W titlebar button and every in-article link dispatch this.
wiki.openSource — dispatch (state, key) - open the ⧉ source window on a node: live source + github link, in its own draggable window (lazy win.wikisrc, the win.wiki pattern).
wiki.saveDesc — dispatch (state) - write the wiki.descDraft buffer to the current entry's metadata.description (metadata-only setCel). The wiki is editable: every render (inspect, vocab, wiki, skill) reads the same field. Runtime-local until the document dehydrates.
wiki.saveNote — dispatch (state) - write the wiki.noteDraft buffer to the current entry's metadata.note (metadata-only setCel: merges, never touches structure). Layer-1 set-policy refusals surface in the draft box instead of throwing.
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.
dom.applyListenerDelta — Global-listener reconciliation: diff prev vs next listener-spec arrays and add/remove on the named global targets.
dom.applyPatch — DOM-mutating patch applier (browser only): (target, mounted, patch, reg, state, doc) → new mounted node. Maintains the per-element listener registry.
dom.diffVNodes — Pure vnode diff: (prev, next) → JSON Patch. Positional children by default; keyed reconciliation when both lists are fully keyed.
dom.paint.drain — Channel drain: forwards each buffered view cel's render-spec to the painter's batched enqueue.
drag.drop — drop handler: assigns the zone's VALUE to the cel named in drag.active, then clears it. A drop zone carries its value as the payload - (on 'drop' 'drag.drop' 'Done'). Movement is reactive: any formula referencing the assigned cel re-renders the item in its new place.
drag.grab — dragstart handler: records WHICH cel the dragged item names into drag.active. Make an item draggable with (attr 'draggable' 'true') and (on 'dragstart' 'drag.grab' <celKey>).
drag.over — dragover handler: preventDefault so the browser allows a drop. REQUIRED on any drop zone - (on 'dragover' 'drag.over'). No drop event fires without it.
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", "origin.tone", 440), "♪"). The handler runs (state, payload, event).
replay.canvas2d — (el, state) - the 2-D replayer: parse the element's data-ops draw-spec and replay it onto the 2d context (plastron-canvas vocabulary); animated ops (frames/draggable) get a self-cancelling rAF loop. The per-element half of the old drawCanvases walk.
style — (prop value …) - inline styles for a dom element; pass as a child of dom().
[doom]
doom — () - open a DOOM window: a wasm app (canvas) running Doom, lazy-fetching doom.wasm + the WAD from plastron.ca. Arrow keys / Ctrl (fire) / Space (use) drive the player when the window is focused. =doom()
doom.arm — (state) - fired when the DOOM window materialises; RAF-defers doom.boot so the canvas is mounted before the engine grabs it.
doom.boot — (state) - the browser boot: consent-gate the asset load, fetch doom.wasm + WAD, createDoomHarness, hydrate the kind:wasm engine cel, start the RAF tick loop.
[ecs]
automaton — (grid, w, h, rule) - one generation of a toroidal 8-neighbour cellular automaton on a flat w*h grid. The RULE lives in a cell (grid-program contract): a KERNEL callable (alive, count) -> 0|1 - author it as =LAMBDA(alive, n, ...) or a wat lambda (same ABI) - or a B/S rulestring as policy-as-data ('B3/S23' = Conway, 'B36/S23' = HighLife). The mechanism is only the neighbour count. Returns a NEW array so a skipEqual commit freezes cleanly on still lifes.
bufstats — (buffers, gen) - probe the dense interior: {gen, n, meanSpeed, maxSpeed} summary of a SoA buffers struct. Reference the generation cel as gen so the probe re-derives on every committed frame - gen is part of the output (stats as-of generation gen), which is what makes the off-grid buffer observable through the grid.
flockSeed — (n, seed, w, h, d) - a deterministic seeded flock: the SoA buffers struct {n, positions: Float32Array(3n), velocities: Float32Array(3n)} for n entities in a centered w*h*d box (mulberry32 - same seed, byte-identical flock). Meant as a pump effect's init so archives never carry the floats; bind metadata.schema:'buffers' on the cel for snapshot round-trips.
gridops — (op, grid, w, h, cell, ...args) - render the truthy cells of a flat w*h grid: op(px, py, cell-1, cell-1, ...args) per live cell. With rect that is a filled square per cell; the -1 leaves a hairline grid gap.
integrate — (positions, velocities, dt) - Euler step over pair tables: [[x,y],...] + [[vx,vy],...] -> [[x+vx*dt, y+vy*dt],...].
neighbors — (positions, radius, ...tables) - per-entity spatial query over a [[x,y],...] table: for entity i, an array of [pos_j, ...table_j] rows for every OTHER entity j within radius. Extra tables let a rule see neighbors' velocities etc. Naive O(n^2) - fine for hundreds of entities.
opmap — (op, table, ...args) - render a component table with an existing canvas-op builder: each row is spread into op, trailing args appended. =canvas(w, h, opmap(circle, positions, 3, '#4fc3f7')) draws one dot per entity (canvas flattens op arrays).
param.set — (state, celKey, event) - generic input->cel binding for on(): writes the event target's value to the cel named by the payload. Ranges/numbers write valueAsNumber, checkboxes write checked, everything else the string value. A missing GRID-ADDRESS cel is born on first write (the sparse-commit contract - a shared recipe doesn't carry empty draft cells); missing NAMED cels still throw. =dom('input', attr('type','range',...), on('input', 'param.set', 'ecs.cohesion')).
sim.run — (state, configKey) - start the tick pump named by a config cel: { commits?: [{to, from, every?, skipEqual?}], effects?: [{fn, kernel?, buffers, params?, gen, init?, initArgs?}], while?, fps? }. COMMITS route derived *.next cels back into table cels through the graph (the formula-tier loop). EFFECTS are the dense-interior loop: call fn(buffers, params, kernel) to mutate SoA typed arrays IN PLACE, seed via init(...initArgs) when the buffer cel is empty, and write gen=frame - the generation write re-fires sceneframe cels so scene.paint drains dirty instances; per-frame entity state never crosses the graph. fn / kernel / init are CALLABLES - reference the verb and lambda cels in the config formula (fn: swarmstep, kernel: E1, init: flockSeed) so the reference IS the dependency edge; never name them as strings. buffers/gen are cel-KEY strings (write targets the pump owns); params is an embedded object or a cel-key string read live. every=N commits on every Nth frame; skipEqual skips content-equal writes; while names a window state cel - the pump stops when it closes. The config cel is re-read every frame, so fps and every knob it derives from are live. Returns 'running' or 'already running'.
sim.stop — (state, configKey) - stop the tick pump started by sim.run for this config cel.
swarmstep — (buffers, params, kernel) - one simulation step over the SoA struct, IN PLACE, as pure MECHANISM: spatial-hash neighbor aggregates per entity (cell = radius, params.maxNeighbors cap, default 24), then the KERNEL decides each new velocity, then commit + integrate + toroidal wrap in the centered box. The kernel is the RULE and lives in a cell (the grid-program contract): an infix =LAMBDA for the legible tier or a wat lambda cel for the compiled tier - same ABI: kernel(px,py,pz, vx,vy,vz, n, cx,cy,cz, ax,ay,az, sx,sy,sz, cohesion, alignment, separation, maxSpeed) -> [vx,vy,vz], where n = neighbors found, c* = neighbor centroid, a* = mean neighbor velocity, s* = inverse-square separation sum (all zeros when n=0). params = {cohesion, alignment, separation, maxSpeed, radius, dt, w, h, d, maxNeighbors?} - author it as an infix object-literal FormulaCel over sheet cells and the sim re-derives on every edit. Deterministic (no RNG). Use as a sim.run effect: {fn: swarmstep, kernel: E1, ...}.
sysmap — (rule, ...args) - run a per-entity rule over parallel component tables (entity = array index). N comes from the first array arg; any array of exactly length N is sliced per entity, everything else (params, dt, colors) broadcasts; the entity index arrives as the rule's last argument. A lambda cel referenced in the formula contributes its CALLABLE, so =sysmap(D6, positions, velocities, ...) receives a real function - author the rule as a =LAMBDA cell (the grid-program contract: rules are cells, never native). Returns the length-N result table.
workbook — (id, title, geom?, ...'kind:title:ref') - a formula-friendly wbopen: genesis a self-mounting gen-2 WORKBOOK window over EXISTING content cels. Tab specs are flat strings because infix cannot author {ref,title}[] arrays: 'sheet:params:ecsv.params' fills the left celBook (worksheets), 'view:fish:ecsv.fish' the right cardBook (dom views). =workbook('ecs', 'ECS', geom(0.3, 0.06, 900, 560), 'sheet:params:ecsv.params', 'view:fish:ecsv.fish').
wrap — (positions, w, h) - toroidal wrap of a [[x,y],...] table into the box [0,w)x[0,h).
[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.
explorer.delete — (state, path) - dispatch target for the explorer's 🗑 button: fs.delete the file, clear explorer.preview if it was showing that file, then refresh explorer.listing (reactive via the content formula — no hand-rolled repaint).
explorer.download — (state, path) - dispatch target for the explorer's ⬇ buttons (per-file + binary-preview) and the standalone download() button: read OPFS bytes → browser file save.
explorer.nav — (state, path) - dispatch target: set explorer.cwd to path (descend a folder / climb via '..'); the explorer content formula references explorer.cwd so the listing re-fires.
explorer.open — (state, path) - dispatch target: set explorer.preview to a file path; the explorer content formula re-fires and the listing cats it into the preview pane.
explorer.openSheet — (state, path) - dispatch target on a file row's DOUBLE-click: a .csv/.xlsx/.甲 file is read from OPFS and opened as a NEW standalone sheet window via sheet-host's openAsSheet (format detected by extension: csv->values grid, xlsx->fromXlsx, 甲->rich archive with cels+formulas). Any other file falls back to the single-click text-preview behavior.
explorer.refresh — (state) - populate explorer.listing from the current explorer.cwd/preview (one OPFS read). Host-called once at boot so the explorer window shows its initial listing; the nav/open handlers refresh it thereafter. The listing's preview is GUARDED: a known-binary extension (.wasm/.wad/.png/.甲/.xlsx/fonts/...) OR a file over 256 KB OR bytes that aren't valid UTF-8 are NEVER read as text (that would blow out browser memory) — the preview shows a 'binary file — <ext>, <size> — download to inspect' placeholder instead; only small valid-UTF-8 text files are decoded into the preview pane.
explorer.rename — (state, path) - dispatch target for the explorer's ✎ button: prompt() for a new name in the same dir, fs.rename the file, follow explorer.preview if it moved, then refresh explorer.listing. No-op off-DOM (no prompt available).
explorer.rmdir — (state, path) - dispatch target for the 🗑 on a DIRECTORY row: confirm (browser confirm() when present), fs.rmdir the tree recursively, clear any preview under it, refresh the listing. Segment-store docs are directories under /plastron/segments — prefer the Sheets 📂 picker's 🗑 for those (it also maintains the store index).
explorer.upload — (state, dir, event) - dispatch target for the explorer's upload input and the standalone upload() picker: write the picked file's bytes into OPFS, then refresh the explorer if open.
explorerwin — () GENESIS: a gen-2 file-explorer WINDOW. Seeds explorer.cwd/.preview/.listing + the (explorer …) content formula, and a self-mounting wframe window (win.explorer.state/.content/.frame). The 📁 Files launcher calls it; origin.navOpen restoreWindow-s and explorer.refresh-es it on open.
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.
fs.command — (op, path [, to] [, text]) - the file-op vocabulary (ls/tree/mkdir/rm/mv/cat/write/touch/stat) as one fn over the OPFS backend; returns a formatted string. Host file verbs delegate here.
fs.delete — Async. (path) → void. File only — use fs.rmdir for directories. No-op if missing.
fs.exists — Async. (path) → boolean. True if the path resolves to any entry under the root.
fs.list — Async. (path) → string[]. Child names only (no slashes). Throws if path is not a directory.
fs.mkdir — Async. (path, recursive?) → void. Defaults recursive=true. No-op if the dir already exists.
fs.pickToCel — (state, celKey, changeEvent) - on('change') handler for an <input type=file>: base64-encodes the selected file's bytes and writes the string to the cel named by celKey via setValue. Mechanism for handing picked image/binary bytes onto the grid (e.g. into a py cell). No-op if no file was chosen.
fs.read — Async. (path) → Uint8Array. Throws if missing or if the path is a directory.
fs.readText — Async. (path) → string. UTF-8 decode of the file's bytes.
fs.rename — Async. (oldPath, newPath) → void. Files and directories. OPFS uses .move() with a read-write-delete fallback for older browsers.
fs.rmdir — Async. (path, recursive?) → void. Defaults recursive=true. No-op if missing.
fs.stat — Async. (path) → { size, isDir, mtime }. mtime is ms epoch; OPFS returns 0 for directories.
fs.write — Async. (path, content) → void. content: Uint8Array | string. Overwrites in place; auto-creates missing parent dirs.
fs.writeText — Async. (path, content) → void. UTF-8 encodes string before write; auto-creates parent dirs.
[forcegraph]
fg.arm — dispatch (state, id) - click into a graph to ARM it: while armed it owns the mousewheel (zoom, page does not scroll - the maps-embed convention). fg.disarm releases on pointerleave.
fg.click — dispatch (state, {id,key}, event) - a node click re-dispatches spec.onNode.dispatch with the node key, UNLESS the pointer travelled (a drag is not a click).
fg.disarm — dispatch (state, id) - release the wheel back to the page (pointer left the graph).
fg.drop — dispatch (state) - end the drag: hold the dropped node, relax the rest until no two nodes overlap, then a label anti-collision pass to spread overlapping label pills apart, then FREEZE.
fg.grab — dispatch (state, {id,key}, event) - start dragging a node (pointer capture; the windows pattern).
fg.move — dispatch (state, _, event) - the grabbed node follows the pointer; the rest relax around it live (a few iterations per move). Writes fg.<id>.pos - the formula re-renders.
fg.set — dispatch (state, {id, spec}) - create the fg.<id> layer cels (spec/pos/zoom) if missing, run the layout to OVERLAP-FREE (springs + repulsion + node-circle collision separation; spec.pin held at center), then a LABEL anti-collision pass that pushes overlapping label boxes apart along their least-overlap axis so the rendered text pills don't sit on top of each other, then freeze. Re-set keeps surviving node positions as warm seeds. There is no ticker - the sim runs at set time and during drags only.
fg.toggleKind — dispatch (state, {id,kind}) - toggle a node KIND in fg.<id>.hide (the legend chips dispatch this); hidden kinds drop their nodes and incident edges from the render. The pin never hides.
fg.wheel — dispatch (state, id, wheelEvent) - zoom the instance (0.35-3x) around the box center.
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.
The {nodes, edges} spec is the CANONICAL graph form (collections
doctrine): converters from adjacency/edge-list values should
target it rather than minting a new graph shape.
[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.
[genesis]
genesis.drain — (items) - commit generator requests via setCelBatch: ownership + overwrite, regeneration diff (structural identity; seeds at creation, user data plane preserved), layer segment stamping, orphan sweep, unsuppressed refire of stale consumers.
[grok]
addbot — (name, persona?, model?) GENESIS: mint a chat bot as a grok.roster.<handle> cel — a {name, persona, model} dict merged into the bestiary (grok.bots + roster). Formula-owned document recipe: it dehydrates with the sheet that authored it, and deleting the formula sweeps the bot away. '@handle …' in the chat routes a message to it.
e.g. =addbot("advisor", "terse formula expert", "grok-4")
chatpane — (transcript, draft, opts?) -> vnode. THE pure chat renderer (sheetbar pattern: instance cels as args) — author the whole chat from a GRID CELL by wrapping it in =view; referencing the cels IS the reactivity. Appkit-styled: opts.authed=false (pass signedIn(sb.<p>.auth)) gates to the login card (near-black header band, Email/Password, Sign in); signed in renders the thread — bot chips, slate-50 stream, Telegram bubbles (user solid #334155, bot/peer white-bordered — human:true dicts show 👤 name — receipts amber chips), rounded-full composer, Enter sends. opts: {project, authed, bots, bot, status, email, password} + instance wiring {title, draftCel, send, key, placeholder, chips:false} so a sibling surface (the room chat) reuses the renderer with its own verbs/cels. Inputs dispatch grok.field; buttons grok.signin/<send>/grok.pickbot/grok.signout.
e.g. =view("chat", chatpane(grok.transcript, grok.question, {project:"default", authed:signedIn(sb.default.auth), bots:grok.bots, bot:grok.bot, email:grok.email, password:grok.password, status:grok.status}))
grok.applytools — (state, toolCalls) -> [{role:'tool', tool_call_id, content}]. Apply a model reply's tool_calls THROUGH THE USER'S COMMIT PATH (typed-entry sniff -> setCel; read_cells/vocabulary read-only; evaluate_formula dry-runs in grok.scratch) and return the follow-up tool messages for the next round. Every application appends a receipt dict to grok.transcript; a write batch checkpoints first when grok.snap is on. No privileged mutation surface.
e.g. grok.applytools(state, [{id:'1', function:{name:'write_cell', arguments:'{"key":"sheet1.B2","content":"=SUM(sheet1.A1:sheet1.A9)"}'}}])
grok.ask — (state, seg, question, botHandle?) -> {ok, reply, bot} | {ok:false, error}. Assemble [system(primer+persona), context+question], invoke the server-side grok proxy (supabase.invoke) WITH the tool surface (read_cells / write_cell / evaluate_formula / vocabulary); tool calls in the reply are applied through the user's commit path (receipted in grok.transcript, loop capped at 4 rounds) and the final reply — a plastron FORMULA for the bar — returns. xAI key stays server-side.
grok.context — (state, seg) -> string. ONE segment's SOURCE cells as plain lines (formula TEXT or value JSON), excluding sync control cels. Capped. The 🔮 formula-bar ask sends this; the chat card sends the whole workbook (grok.workbook-context).
grok.field — (state, cellKey, event) -> void. An input's keystroke -> a cel (login/chat card buffers grok.email/password/question). Dispatch target for on('input', 'grok.field', '<cel>').
grok.key — (state, project, event) -> void. The composer textarea's keydown: Enter sends (grok.send), Shift+Enter newlines. Dispatch target for on('keydown', 'grok.key', '<project>').
e.g. on('keydown', 'grok.key', 'default')
grok.loadmsgs — (state, project?) -> log. Load the selected room's messages (member-gated select on appkit's chat table, oldest-first, cap 200) into grok.roomlog, mapped to chatpane dicts (mine -> role:'user'; others -> role:'assistant' + human:true + their profile name; task/report bodies show as a tagged snippet). Poll-on-send/switch — live arrival via supabase.realtime's rev bump is future wiring.
e.g. grok.loadmsgs(state, 'default')
grok.loadrooms — (state, project?) -> rooms. Fetch the signed-in user's chatrooms from appkit's DB (the list_conversations RPC via supabase.data, user JWT -> RLS applies) into grok.rooms, refresh grok.peers, repaint. The roomspane ↻ Refresh button dispatches it; grok.signin auto-runs it when a rooms view is open. Errors land on grok.status.
e.g. on('click', 'grok.loadrooms', 'default')
grok.pickbot — (state, handle) -> void. The chat header's bot chips: select the active bot (grok.bot := handle, if it exists in the bestiary) and repaint. Dispatched, never called from a formula.
e.g. on('click', 'grok.pickbot', 'muse')
grok.pickroom — (state, roomId) -> void. 'Select one and go into chat': grok.room := id, grok.roomtitle := its title, load the room's messages, then FOCUS the roomchat view tab (window.viewTab on the workbook hosting roomchat.view, then window.raise) and repaint. Dispatched by a roomspane row click.
e.g. on('click', 'grok.pickroom', '00000000-0000-0000-0000-000000000001')
grok.roomkey — (state, project, event) -> void. The room composer's keydown: Enter sends (grok.roomsend), Shift+Enter newlines. Pass as chatpane's opts.key in the room-chat formula.
e.g. on('keydown', 'grok.roomkey', 'default')
grok.roomsend — (state, project?) -> string. Send grok.roomq into the selected appkit room: an RLS-gated INSERT into the chat table AS YOURSELF (author_id = your auth uid), '@*handle' summons appkit's bot-responder with the new chat_id (fire-and-forget — appkit's own mention contract), reload the log, repaint. Sibling of grok.send (the standalone grok chat), never an overload.
e.g. on('click', 'grok.roomsend', 'default')
grok.send — (state, project?) -> string. The composer's Send: append the user turn to grok.transcript, send [chat primer + persona + grok.workbook-context, history] with the tool surface, run the tool loop (writes receipted), append the assistant turn. '@handle …' routes one message to that bot. Dispatched from the card, never from a formula.
e.g. on('click', 'grok.send', 'default')
grok.signin — (state, project?) -> string. The login card's Sign-in button: supabase.auth signin with grok.email/grok.password, write grok.status, repaint. State-aware (the state-arg wall) — dispatched, never called from a formula.
grok.signout — (state, project?) -> string. The chat header's Sign-out: supabase.auth signout (revoke + forget the session), grok.status := 'signed-out' (the card flips back to login), repaint. Dispatched, never called from a formula.
e.g. on('click', 'grok.signout', 'default')
grok.workbook-context — (state, wbRef?) -> string. The ACTIVE workbook (win.active, else the first win.*.state with a sheets tab list) as capped plain text: every sheet's dims + source cells (formula TEXT, value JSON) and every dom-view's formula — read from the wbframe state shape {sheets:[{ref,title}], views:[…]}. This is what chat bots see.
e.g. =grok.workbook-context() → 'workbook win.wb1.state …\n## sheet doc (segment doc, 12×7)\ndoc.A1 = 5 …'
loginpane — (opts) -> vnode. The login card as ITS OWN =view surface: signed out, the appkit login card (near-black band, Email/Password, Sign in -> grok.signin); signed in, the SESSION card — avatar initial, 'Signed in as <email>' (opts.session = sb.<p>.auth), Sign out. opts: {project, authed:signedIn(sb.<p>.auth), email, password, status, session}.
e.g. =view("login", loginpane({project:"default", authed:signedIn(sb.default.auth), email:grok.email, password:grok.password, status:grok.status, session:sb.default.auth}))
roomspane — (rooms, opts) -> vnode. The CHATROOMS view over appkit's conversations: one clickable row per room (🏛 lounge / 👥 group / 👤 dm, title, last-message snippet, unread badge) dispatching grok.pickroom; a ↻ Refresh button dispatching grok.loadrooms; the selected row (opts.room) highlighted; signed out -> the sign-in hint. opts: {project, authed, room, status}.
e.g. =view("rooms", roomspane(grok.rooms, {project:"default", authed:signedIn(sb.default.auth), room:grok.room}))
[host]
host.error — console.error proxy. Same pattern as host.log.
host.imports — Read host.{log,warn,error,now,random} cels into one Record<Key,Fn> for foreign runtimes (wasm imports member / Pyodide globals). Missing cels fall back to defaults.
host.log — console.log proxy exposed to wasm-backed kinds. WAT modules import via (import "host" "log" ...); Python lambdas call host.log(...). Sync. Apps that want quiet runs can replace _fn with a noop after install.
host.now — Date.now proxy — milliseconds since the Unix epoch. Sync. Use this rather than calling Date.now inside a lambda directly: replacing _fn with a deterministic clock (e.g., a fixed counter) lets tests run replay-deterministically without monkey-patching globals.
host.random — Math.random proxy — pseudorandom number in [0, 1). Sync. Same testability story as host.now: swap _fn with a seeded PRNG for deterministic tests.
host.warn — console.warn proxy. Same pattern as host.log.
[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.bindings-equal — (a, b) - EventBinding equality for event-bag diffing.
vnode.equals — (a, b) - deep structural VNode equality (ref-eq short-circuit at every level). Painters resolve this once per drain and thread it into their diff.
vnode_isChanged
[image]
image.decode — (png) → { w, h, rgba }. Decode PNG bytes (Uint8Array, base64 string, or data: URL) to an RGBA8 pixel buffer and its dimensions — via the browser canvas (createImageBitmap + OffscreenCanvas) when present, else a dependency-free pure-JS PNG codec (8-bit RGB/RGBA). Returns a CelError on undecodable input.
image.encode — (rgba, w, h) → base64 PNG. Encode an RGBA8 byte buffer of size 4·w·h to lossless 8-bit PNG bytes (base64), so LSBs survive the round trip. Returns a CelError if the buffer is too small.
image.fit — (png, maxDim) → base64 PNG downscaled so max(w,h) ≤ maxDim (decode → resample → encode); a byte-preserving no-op when the image is already within bounds. maxDim is an argument — no policy here.
[io-keys]
keys.blur — clear keys.pressed (the stuck-key fix on window blur). Attach via window|blur|(dispatch "keys.blur").
keys.capture — the document keydown/keyup handler (dispatch): writes keys.event + keys.pressed, then routes on keydown. Attach via document|keydown|(dispatch "keys.capture").
keys.route — match the event against keys.map.<keys.active> + dispatch the action (first match wins; honors inEditable/mods/preventDefault)
[io-touch]
touch.press — button pointerdown (dispatch): synthesize a keydown for <key> through keys.capture (so keys.pressed/routing behave as for a real key)
touch.release — button pointerup/cancel (dispatch): synthesize a keyup for <key> through keys.capture
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.
[keystore]
keystore.changePasscode — (oldPasscode, newPasscode) -> {ok}|{ok:false,error}. Verify the old passcode (against the persisted blob), re-derive a new seal key, re-seal under the new passcode. New passcode >=4 chars, still unrecoverable.
keystore.create — (passcode, name?) -> {ok, persisted, identity, phrase}. First-run: mint an EXTRACTABLE wallet keyring (ecdh P-256 + Ed25519), seal it (PBKDF2-HMAC-SHA-256 -> AES-256-GCM) under the passcode, persist to OPFS if available (else session-only), load into memory. Returns the 12-word recovery phrase. Passcode >=4 chars and UNRECOVERABLE (no backdoor).
keystore.export — () -> the sealed keystore blob as a JSON string, for DOWNLOAD (the file:// backup / device-migration artifact). Already ciphertext under the passcode; no secret leaves.
keystore.has — () -> bool. Is there a wallet (an unlocked keyring in memory OR a persisted keystore in OPFS)?
keystore.import — (blobString, passcode) -> {ok, identity, name}|{ok:false,error}. Load a keystore from a FILE (file:// / device migration): decrypt with the passcode, load into memory, persist to OPFS if available.
keystore.lock — () -> drop the in-memory keyring (sign-out). status -> 'locked' (if a blob persists) or 'none'.
keystore.reshuffle — () -> {ok, persisted, identity, history}. Mint a NEW current keypair; retire the old one into the history list (so docs sealed to it stay openable); re-seal + persist. Must be unlocked.
keystore.seedPhrase — () -> {ok, phrase}. The 12-word recovery phrase for the current wallet (display only; deterministic seed->key re-derivation is the documented follow-up - the working recovery in v1 is the exported passcode-locked file). Must be unlocked.
keystore.setName — (name) -> set the profile display name, re-seal + persist. Must be unlocked.
keystore.sign — (message) -> {ok, sig, pub}. Ed25519-sign a message with the CURRENT signing key. pub identifies the key for verification (public verify = crypto.verify). Must be unlocked.
keystore.unlock — (passcode) -> {ok, identity}|{ok:false,error}. Read the persisted OPFS keystore, decrypt with the passcode, load the keyring into memory + write status cels. file:// has no persisted blob -> use keystore.import with a file.
keystore.unwrap — (env, withPubB64?) -> {ok, dataKey}. ECDH-unwrap with the held key whose ecdh pub matches withPub (default current; tries current+history so a now-retired key still opens). Must be unlocked.
keystore.unwrapFrom — (state, env, theirEcdhPubB64) -> {ok, dataKey}. ECDH-unwrap a peer-wrapped key with MY current ecdh priv + the wrapper's ecdh pub (their fromPub). Must be unlocked.
keystore.wrap — (dataKeyB64, toPubB64?) -> {ok, env, pub}. ECDH-wrap a symmetric data key to a recipient ecdh public key (default = my own = self-encryption). Store the returned pub; unwrap needs it. Must be unlocked.
keystore.wrapTo — (state, dataKeyB64, theirEcdhPubB64) -> {ok, env, fromPub}. ECDH-wrap a symmetric data key to a PEER's ecdh public key (true peer key-exchange, unlike wrap which is self-only). Shared AES = ECDH(my priv, their pub); peer recovers via unwrapFrom(env, fromPub). Must be unlocked.
[lambda-source]
lambda-source.split
[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.
music.play — (state, {notes, bpm}) - the EFFECT handler. Schedules every note on the Web Audio clock at its start beat (sample-accurate via sound.play-tone's `when` offset). bpm -> seconds/beat; velocity -> gain; channel -> waveform. Dispatched by a miditrack play button.
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.
[nanosteg]
nanosteg.decode — (rgba, w, h, password, core) → recovered UTF-8 text from the image alone (blind — no cover). Writes the pixels + password into the captured WAT instance's memory, calls hash_password then decode, and reads the payload back; a magic miss (wrong password) returns a readable warning string instead of throwing.
nanosteg.embed — (rgba, w, h, payload, password, core) → RGBA8 buffer with payload hidden in the R/G/B LSBs. Writes the pixel/password/payload bytes into the captured WAT instance's memory at fixed protocol offsets, calls hash_password then embed, and reads the modified RGBA back. Pure byte-plumbing — the steg algorithm is the WAT source on the sheet. Returns a capacity CelError when the payload does not fit.
nanosteg.host — Imports provider for the NanoSteg WAT module: returns { imports:{}, onInstantiate } and captures the module's live instance (memory + exports) into a per-State registry when it instantiates. Named on the WAT cel's metadata.imports so the wat compiler's host-instance hook hands it the instance.
[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]
addCells — (sheet, rows, cols) - grow the sheet's rendered range TO AT LEAST rows x cols: a pure <seg>.dims write - sparse, so NO addressed space enters state; the grid re-renders reactively. At-least semantics keep the formula idempotent (rehydrate re-fires are no-ops). =addCells('data', 20, 9).
append — (view, xpath, item) | (xpath, item) | (view, item) - place THIS CELL's keyed slot at an XPATH: scoped to a view pane ('viz', '//div[@id="x"]', item), DOCUMENT-scoped when the first arg IS an xpath (searched across every appendable surface - ValueCel-owned vnodes; matches inside FormulaCel-derived dom are refused: edit that formula), or the pane root (2-arg name form; opens the pane if needed). GENERATIVE: the formula survives, re-fires REPLACE the slot (no duplicates), an xpath edit MOVES it. item = a dom() vnode (bare cell refs dep-wire) or a string cel address. xpath subset: /tag, //tag, *, [n], [@id='x'], [@class='x']. DISTINCT from =view.
at — at(addr, content) - one cell's initial content for a cels() grid, e.g. cels("in", 4, 3, at("a1", "apple")).
boot.run — (state, opts?) - the origin-application boot sequence: install every baked app archive into the store (OPFS, not state), then launch the desktop shell. Tests pass {manifest, open} to drive it headlessly; open:false installs only.
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.
chatcard — (project?) -> drops the slick login + grok-chat card (a reactive grok.ui formula) into the active workbook's cardBook (the dom-view pane); opens a workbook if none is active. Pair with =grokconfig(...). =chatcard('ccfr').
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.
desktop.bg — (src, fallback) - the desktop wallpaper image. img() hydrates a /path src to an objectURL via file-store, so src can be an OPFS path or a data-URI; fallback covers an empty wallpaper. Right-click opens display settings (desktop.bgmenu).
desktop.bgmenu — (state) - right-click the wallpaper: refresh the /wallpapers/ image list and open (or raise) the display-settings window.
desktop.graphNode — (state, seg) - click a state-graph node: open that segment in the app editor (origin.editapp) to edit its cels' sources.
desktop.graphRefresh — (state) - the state-graph window's ⟳ titlebar tool: rebuild the graph spec from the CURRENT state and fg.set it. Surviving nodes keep their positions (warm seeds), so a refresh adds/drops/resizes nodes in place; the fgview content formula re-renders off fg.stategraph.spec.
desktop.graphbtn — () - the round lower-left desktop button that opens the state force-graph (dispatches desktop.stategraph).
desktop.iconClick — (state, action) - launch the icon's app via origin.navOpen UNLESS the pointer dragged (a drag is not a click).
desktop.iconDrop — (state) - pointerup: record how far the icon moved (desktop.iconLastMoved, so a drag suppresses the click) and clear desktop.icondrag.
desktop.iconGrab — (state, label, event) - pointerdown on a desktop icon: capture the pointer + record the grab offset in desktop.icondrag.
desktop.iconMove — (state, _, event) - pointermove while dragging an icon: update desktop.iconpos[label] to the pointer position; repaint.
desktop.icons — (iconpos, mobile, availH, ...items) - draggable desktop app icons: persisted positions win; unpositioned tiles fill top-to-bottom and wrap into a new column when the next tile would pass availH; mobile falls back to the collapsible nav. A click launches via origin.navOpen.
desktop.importWallpaper — (state, _, event) - file-IO import: read the picked File, write it under /wallpapers/, then select it as the wallpaper. Browser-only (File API).
desktop.refreshWallpapers — (state) - list /wallpapers/ into the desktop.wallpapers cel (the settings grid references it, so it updates reactively). Creates the folder if missing.
desktop.setWallpaper — (state, path) - point the desktop.wallpaper cel at an OPFS path; the bg img + painter load it. Reactive repaint.
desktop.settingsView — (current, files) - the display-settings window body: a wallpaper preview, a file-IO import button, and a chip per image under /wallpapers/ (click → desktop.setWallpaper).
desktop.stategraph — (state) - open a force-graph of the whole state in a real window-segment window (drag/resize/minimize/fullscreen/close via wframe): one node per segment, sized by how much memory it uses, origin-application segments tinted distinctly. Reuses the forcegraph segment (fg.set + fgview).
desktop.taskClick — (state, ref) - click a taskbar chip: a minimized window restores (clear min + raise); the active one minimizes; any other open one raises.
desktop.taskbarGenesis — (list, active) - re-derive the taskbar from win.list. A FormulaCel (desktop.taskbarGenesis win.list win.active) re-fires when the window set or focus changes and returns a genesis whose desktop.taskbar.frame splices in each window's state cel by name, so the bar tracks min/raise/title reactively.
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.
domview — (name [, item]) - DEPRECATED pre-standardization name for =view (kept so old saved sheets still evaluate). Use =view.
downloadSeg — (name) - a button that downloads a saved sheet (.json) from OPFS.
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.
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.
grokconfig — (project, url, anonkey, grokEndpoint?) -> loads the grok/supabase/auth segments and seeds the login+chat config cels (sb.<project>.url/.anonkey, grok.project, grok.url) FROM A FORMULA. The url + PUBLISHABLE anon key + endpoint are not secrets, so they belong in the formula; the password is typed into the login input and the xAI key stays server-side. =grokconfig('ccfr','https://REF.supabase.co','sb_publishable_xxx','https://REF.supabase.co/functions/v1/grok').
hash — (seg, ...exclude) - a recent SHA-256 (hex) of worksheet `seg`s SOURCE cels (ValueCel value | FormulaCel formula-text; derived values excluded - sources only, so it is deterministic across replicas). Trailing-* exclude patterns drop ephemeral/IO ranges. =hash("budget") / =hash("budget", "budget.live*").
help — (segment?) - open the vocabulary EXPLORER in a view pane: a TREE of segments (the VS Code explorer shape - grouped by role, each expands like a folder to its functions), a SEARCH box over names + descriptions on top (typing swaps the tree for a flat hit list), and the clicked function's signature/description/source on the right. No graph, no layout cost - instant at any vocabulary size. =help('ecs') opens with that segment expanded. GENERATIVE: the formula survives; the value is '⧉ help'. TEXT MODE — =help('', 'text') or =help('charts', 'text') returns the whole catalog (or one segment's) as this cell's plain-text VALUE instead of opening a pane: the agent-facing discovery form (absorbed the retired =vocab).
help.search — (state, _, event) - the help explorer's search box: writes help.query from the input and runs the full repaint cycle (mount-headed frames suppress value-change propagation, so a plain setValue would not repaint).
help.selectNode — (state, key) - a function-row or search-hit click: lands {key, celType, segment, kind, description, f} in help.selectedInfo for the detail panel.
help.toggle — (state, segName) - the help tree's folder click: expand/collapse the segment (help.open is the expanded set) and show its description in the detail panel.
helpPane — (index, query, open, info) - pure renderer for the =help explorer tab: search bar + the segment/function TREE (or flat search hits) + the detail panel. The tab formula references help.index / help.query / help.open / help.selectedInfo so every interaction re-renders reactively.
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 install of the baked APPLICATION + document segments: read the inert #plastron-apps manifest the bundle baked into the page (a { name: archive } map) and origin.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), laid out + rendered by the forcegraph segment (draggable fg node chips).
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.
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.
kvseed — (into, from) -> GENERATIVE + idempotent: merge a shipped-defaults kv segment into a user-owned one — rows missing from `into` are copied from `from` (a user's edited value is NEVER overwritten; deleting a row = reset-to-default on the next fire), the kvsheet views rewire, and the cell keeps its formula (value: the ⚙ token). Pair with an install:'once' manifest on the user segment so refreshApps stomps only the defaults: =kvseed('usercfg', 'usercfg_defaults').
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.
mountcard — (cellKey, title?) -> mount ANOTHER cell's formula as a LIVE card in the active workbook's cardBook. The cell KEEPS its =dom(...) formula (editable) and the card re-renders whenever you change it — unlike =append, which snapshots. Put =dom(...) in a cell, then =mountcard('sheet1.C1', 'My Panel').
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.
origin.askgrok — (state, key) -> state. The 🔮 formula-bar button: send the bar's natural-language question + the sheet's source context to grok (server-side xAI proxy via supabase.invoke) and drop the returned plastron FORMULA back into the bar to review, then ⚡ to commit. xAI key stays server-side.
origin.celtopo — (state, key?) - open the runCycle dependency CONE of a cel (defaults to the selected cell) as a force-graph window: upstream deps (blue) it depends on + downstream dependents (green) that depend on it + the cel itself pinned (gold), edges = inputMap propagation arrows. The 🔗 button on the formula bar dispatches it; clicking a node re-centers. Reuses the forcegraph segment (fg.set + fgview).
origin.clockSync — (host) writes the current time into the clock (HH:MM) + clock.full cels so a formula referencing them (e.g. a taskbar) re-renders; the host calls it on an interval. Reference `clock` in a formula instead of a banned NOW().
origin.drain — (items) - execute cels (segment listing) requests; result text replaces the request as the cel's value.
origin.edit — (state, cellKeyOrForceSpec) - start INLINE editing a cell (seed draft with its source); the Excel double-click path (single-click SELECTS via origin.select). Payload is the cell key, or { key, force: true } to start editing past the form-control guard.
origin.editAppCel — (state, {app,key}, event) - commit an edited cel source from the app editor (on Enter): rewrite the cel's source, then refresh the editor.
origin.editapp — (state, app) - open another segment's cels as an editable source list (its formulas/definition): each cel as key → editable source; Enter commits. Click a node in the state force-graph to edit that segment. sheetapp-as-editor.
origin.fire — (state, cellKey?) - FIRE (re-evaluate) the selected cell (or the given key): a FormulaCel re-applies its source via setValue (recompile + re-cascade); a ValueCel re-writes its own value (re-cascades dependents). Wired to the 🔫 button on the formula bar.
origin.gridkey — global keydown over a SELECTED-but-not-editing cell: arrows/Tab move the selection, Enter moves down, a printable key starts editing it (the char shows in the cell + mirrors in the formula bar). Excel-style grid navigation.
origin.install — (state, archive) - persist an origin-application segment ARCHIVE (dumpSegments shape {segments,manifests}, object or JSON string) into the OPFS segment-store via store.putRaw WITHOUT loading it into state. Idempotent at name granularity (an already-stored app is skipped). The ONLY way the archive's cels reach state is a later origin.launch (hydrate-closure); install never hydrates. Returns a status string. (Distinct from winapps' app.assets, which fetches asset FILES.)
origin.key — (state, cellKey, event) - Enter commits the draft + moves the active cell down a row.
origin.launch — (state, app) - open an installed origin-application by name: hydrate its segment closure from the store (idempotent), then run its conventional ENTRY cel `<app>.entry` so it materializes its window(s). The navbar/icon click path. The app must already be installed (boot.run installs the baked set).
origin.navOpen — dispatch target for a clicked nav leaf: a '=formula' action spawns a new window (trusted, capped by the kernel); any other action is a window key and focuses that window.
origin.otpDecrypt — (state, url, event) - dispatch target: decrypt an #otp= payload with the picked pad → the formula source in the formula bar (does not run it).
origin.otpEncrypt — (state, target, event) - dispatch target: XOR the source with the picked pad → an #otp= URL in the formula bar; warns to delete the spent pad.
origin.otpUnlock — (state, payload, event) - boot dispatch target: load the pad → decrypt → the shared formula becomes 元 (still jailed/locked).
origin.run — (state, cellKey) - set the cell's content from the draft and re-evaluate; rebuild the cell list (new grids in, swept out); 元 un-deletable (empty restores readme).
origin.savedoc — (state, name) - record the document's cel states: saveUserSpace dehydrates the origin-user segment's private closure and store.put's it under the app's documents/ subfolder.
origin.savepage — (state) - download the running plastron as a single self-contained 龜甲.html you can re-open or redeploy anywhere. Invoke from a formula: =origin.savepage().
origin.select — (state, cellKey, event) - SELECT a cell (Excel single-click): set 元.selected + 元.selectedSrc + seed 元.draft from its source, WITHOUT swapping it to the inline editor, so the grid keeps showing the value while the formula bar shows the source. A click on a form control a formula rendered is left alone.
origin.tone — (state, freq) - play ONE Web Audio oscillator note at the given frequency (Hz). Loads the sound segment on first use. Wire it to a button: (on "click" "origin.tone" 440).
origin.updateCheck — (state) - boot hook (boot.run runs it after installBakedApps): read the page's baked #plastron-update build stamp {id, notice}, compare against the persisted last-seen id (/plastron/update-seen). Mismatch fills origin.update so the desktop 🔄 chip surfaces. A FRESH profile (no marker file) is current by definition — the marker initializes to the page's id, no banner. No-ops off-DOM, without OPFS, or when the host didn't bake the stamp.
e.g. =origin.updateCheck()
origin.updateLater — (state) - the notice window's dismiss button: persist the seen build id (the banner stays gone until the NEXT build ships — =refreshApps() remains the manual path), clear origin.update (the chip unmounts reactively), close the window.
origin.updateOpen — (state) - the update chip click: open the what's-new WINDOW — the shipped UPDATE.md notice (pre-wrap) over two buttons: [🔄 Refresh demo docs & reload] (origin.updateRefresh) and [Later] (origin.updateLater). A wopen window whose content cel is the built vnode (data, not formula), raised above open windows.
origin.updateRefresh — (state) - the notice window's refresh button: force-reinstall every baked app + demo doc (the banner flow IS the consent, so no confirm — unlike =refreshApps()), persist the seen build id, then location.reload() so boot-hydrated segments come up on the new build. User-created segments are untouched; edits to shipped demo docs are shadowed, not deleted.
origin.viewportSync — (host) writes live page metrics into the viewport.* cels so formulas that reference them re-run; the host calls it at boot and on resize.
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.
refreshApps — () - factory-refresh the BAKED apps + demo docs to the build this page shipped with: re-installs every archive from the baked manifest, flipping each baked segment's latest back to the shipped version. User-CREATED segments are never touched; a user's saved edits to a SHIPPED demo doc are shadowed (prior versions stay in the store). Boot already upgrades automatically when a baked archive's VERSION bumps — this verb forces it for same-version changes. Reload the page afterwards so boot-hydrated segments (the desktop) re-hydrate.
e.g. =refreshApps()
rename — (target, newName) - retitle a sheet or view tab in the active workbook, found by TITLE or segment name. Sheets rename by ALIAS (tab title + <seg>.alias; cel keys never re-key); views retitle in the window state. GENERATIVE + idempotent (a re-fire when the old name is gone but the new one exists is a no-op).
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).
sheet — (name?, rows?, cols?) - add a SPARSE worksheet tab to the active workbook: the only cel minted is <name>.dims (default 12x7); addresses are keyed into state only when a value/formula is committed. name defaults to the guarded sheetN; an existing name is a no-op. GENERATIVE: the formula survives (the tab's recipe - a saved sheet re-creates it); the value is the token '▦ name'.
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).
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 /).
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.
updatebanner — (update) - PURE renderer for the desktop update chip: a hidden div while `update` is {}; else a fixed top-right pill '🔄 plastron updated — what's new' whose click dispatches origin.updateOpen. The desktop app mounts it via =mount('.origin', updatebanner(origin.update)), so the chip derives reactively from the origin.update cel.
e.g. =updatebanner(origin.update)
view — (name [, item]) -> THE pane verb: =view('charts') opens a VIEW pane (a <div id=name>) in the active workbook's cardBook; =view('charts', dom('h1','Hi')) opens-if-needed AND contributes this cell's slot. GENERATIVE: the formula SURVIVES in the cell (the bar shows it when active); the value is the pane token '⧉ name' ('⧉ name +' for a contribution); each contributing cell owns ONE keyed slot - re-fires replace it (no duplicates), edits update it live, and a saved sheet re-creates its panes on rehydrate.
view.refresh — host hook called by settleStructural after out-of-band structure materializes — rebuilds 元.cells + repaints
viewfire — (vals) - the gen-2 desktop compositor behind 元.view (replaces sheetView): vals is the cell values of every whitelisted self-mounting frame (cellKeys); look each up in the mount registry and splice the windows into the positioned .origin desktop, mounted at #app. Renders nothing of its own.
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).
write — (path, text) - write text to a file in OPFS (create/overwrite).
[origin-lifecycle]
origin.close — (name) → {op:'close'} descriptor — close an origin-user segment (closeUserSpace flushes its private closure root-first).
origin.flush — (name, save?) → {op:'flush'} descriptor for an origin-user's <seg>.flush slot. The kernel flush hook enqueues this BEFORE eviction; the drain optionally saves first (close ≠ save), then the kernel evicts the cels.
origin.io.drain — (items) — interpret origin lifecycle descriptors with state: new→newUserSpace, save→saveUserSpace, open→hydrate-closure, close→closeUserSpace, load→sha/compat gate then loadUserSpace, flush→optional save (the kernel evicts). Composition over the shipped user-space ops; no new persistence.
origin.load — (name, version?) → {op:'load'} descriptor. The drain runs the sha/compat gate, then loadUserSpace (which auto-starts the parent application).
origin.new — (app, name) → {op:'new'} descriptor. Use in an origin-application's .new slot; emits to origin.io, the drain creates the origin-user segment.
origin.open — (app) → {op:'open'} descriptor — open the origin-application standalone (no user-segment). The drain hydrates the app closure.
origin.save — (name) → {op:'save'} descriptor. Use in a .save slot; the drain persists the origin-user's private closure via saveUserSpace/store.put.
[peer]
cascade.observe — kernel post-mutation hook: auto-broadcast the shared-namespace keys a host just wrote to the connected peer (makes the sheet live)
peer.apply — the security core (dispatch): validate an inbound remote message against the 4 rules (setValue-only, namespace allowlist, 函 quarantine, no secret exfil) + setValue if it passes. Returns the decision.
peer.broadcast — outbound (dispatch): ship the VALUES of changed cels in the shared namespace to the peer (never structure/executable/secret shapes).
peer.connect — install a transport ({send} — a fake channel in tests, an RTCDataChannel wrapper in the browser).
peer.route — (state, type, verbKey) -> string. Register an inbound frame type -> handler verb (verbKey='' unregisters). The default 'set' frame stays on the security-core gate (peer.apply); sheetsync registers 'op'/'key'/'hello' -> sheetsync.recv so encrypted CRDT ops ride the same data channel with their own signature + encryption + writers gate.
peer.tx — (state, msg) -> boolean. Send a RAW frame (string or JSON-able) over the live WebRTC transport, for a higher segment's own protocol (e.g. an encrypted CRDT op). Returns whether it was sent (false if no peer connected). Size-capped.
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.
[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.
dragdrop — (w?, h?) - two drop zones (A, B) + a rectangle you drag between them; it snaps to the nearest zone on release. A value vnode; the interactivity lives in the canvas renderer (the draggable/zone ops).
frames — (points, r?, period?, fill?, box?) - an ANIMATED canvas op: a dot of radius r steps through the trajectory points ([[x,y],…]) once per period seconds; box strokes a frame border. The painter's rAF loop drives it. The simulate verb computes a def-driven trajectory and emits through this.
line — (x1, y1, x2, y2 [, stroke] [, lineWidth]) - a canvas line segment op.
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.
[plastron-gpu]
box — (w, h, d) - a box geometry SPEC node ({spec:'box', w,h,d}). Pure - the Three geometry is built by the replayer, never here. =scene(..., mesh(box(1,1,1), material('#4a90d9'))).
camera — (x, y, z [, minDistance] [, maxDistance] [, zoomSpeed]) - a perspective camera SPEC node, looking at the origin, with optional mouse-wheel dolly clamp bounds and wheel speed. Omitted optionals get ratio defaults of the authored distance d0=|(x,y,z)| (minDistance 0.25*d0, maxDistance 4*d0, zoomSpeed 0.001); the retained scene handle attaches a wheel listener that dollies the camera along its view ray, clamped to [minDistance, maxDistance] (zoom state is ephemeral view-local, not a cel).
instances — (geometry, material, count, bufferKey) - the keystone SPEC node: N copies of one geometry as a THREE.InstancedMesh (one draw call). bufferKey is a CEL KEY STRING naming the buffer cel whose value is the SoA struct {positions: Float32Array(3N) [, colors: Float32Array(3N)]} - the spec carries the key, not the data, so it dehydrates cleanly; the scene.paint drain reads the cel and uploads per-instance transforms (residency-agnostic: any struct satisfying the shape works). instanceColor is allocated only when the struct has colors.
light — (x, y, z [, color] [, intensity]) - a directional light SPEC node (an ambient fill ships automatically).
material — (color) - a material SPEC node ({spec:'material', color}). Lambert-shaded by the replayer.
mesh — (geometry, material) - a single-object mesh SPEC node. For N copies use instances().
replay.scene — (el, state) - the 3-D replayer (dom.replayers entry for data-scene): on first sight of a <canvas data-scene>, lazy-import Three, build renderer + scene graph from the spec, full-upload the instance buffers, render once, and RETAIN the handle in a WeakMap keyed by the element (frame-to-frame updates flow through scene.paint, not re-parsing). A changed data-scene attr rebuilds; a disconnected element is pruned and its renderer disposed (GL context cap).
scene — (w, h, ...nodes) - a <canvas data-scene> VNODE carrying the scene-spec JSON (camera/light/mesh/instances nodes; optional (style ...) child). Arrives via the normal vnode pipeline; the dom replay registry dispatches it to replay.scene, which builds and RETAINS the Three object graph per canvas. 3-D twin of canvas().
scene.paint.drain — Channel drain: for each buffered {key, gen} frame, for each retained scene handle whose instances reference that buffer cel key - read the SoA struct, dirty-diff against the last upload, setMatrixAt the changed instances (setColorAt when colors present), needsUpdate once, render. Guarded; never breaks a flush.
sceneframe — (bufferKey, generation) - the scene.paint channel payload {key, gen}. emitsTo: a formula whose root call is sceneframe AUTO-JOINS the scene.paint channel - type =sceneframe('sheet1.B10', B11) into any cell and each generation bump re-fires it, enqueues the frame, and the drain uploads dirty instances + renders. This is how continuous sims paint WITHOUT routing per-frame state through the vnode diff (doctrine #5).
sphere — (r) - a sphere geometry SPEC node ({spec:'sphere', r}).
[profile]
profile.changepass — read p1(old)/p2(new) -> keystore.changePasscode.
profile.create — read p1/p2/nm drafts -> keystore.create (passcodes must match) -> show the recovery phrase.
profile.export — keystore.export -> trigger a browser download of plastron-identity.kc (sealed under the passcode).
profile.import — read imp + p1b/p1 -> keystore.import (load a keystore file, the file:// path).
profile.importfile — (<input type=file> change) read the chosen file's text into the import draft.
profile.lock — keystore.lock (sign-out).
profile.reshuffle — keystore.reshuffle (new keypair; old kept in history).
profile.seed — keystore.seedPhrase -> reveal the 12-word recovery phrase.
profile.setname — read nm -> keystore.setName.
profile.unlock — read p1 -> keystore.unlock.
profileui — (status, name, identity, p1, p2, nm, imp, msg, phrase) -> a PURE profile-window vnode. Renders create / unlock+import / unlocked (name, reshuffle, export, recovery-phrase, change-passcode, lock) by keystore.status.
profilewin — () GENESIS: the gen-2 profile WINDOW (self-mounting wframe over (profileui …)). Legacy standalone window — the 👤 Profile icon now opens the identity sheetapp doc (doc:identity) whose wallet =view renders profileui.
[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 — Compile Python source — typically a `def` followed by the function name — into a runtime Fn via Pyodide, first loading any Pyodide packages the source imports. Pyodide is dynamic-imported on first compile, the runtime instance is shared across all py-kind cels, and the imported-package set is derived from the source's own `import` lines (so a cell that `import numpy`/`import cv2` works). 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).
[scheduler]
clock.tick — (state, cost) - the governor STEP. Fold the measured frame cost (ms) into the cost model per clock.mode, write clock.cost_ewma/samples/interval/fps, and return the next interval (ms). Call once per frame after the sim + paint; schedule the next frame with the returned value. Decouples sim/render rate from device capability via a learnable cost model (learn/lock/lose). Replaces hand-rolled setInterval/rAF clock loops.
[seal]
seal.lock — (password?) - Layer-2 at-rest seal. With a password: derive a data key (PBKDF2-HMAC-SHA-256) and install the kernel seal hooks so SEALED segment cels encrypt (AES-256-GCM) on dehydrate and decrypt on hydrate. With no/empty arg: clear the hooks (re-lock) - sealed values stay ciphertext at rest and unreadable on load. The password and data key never become cel values and never enter an archive.
seal.locked — () - true when this state is locked (no seal cipher hook installed).
[segment-store]
store.delete — Async. (name, version?) -> void. Removes the per-version directory and updates the index: drops that version, repoints latest to the most-recent remaining, or removes the segment entry entirely when none remain. version defaults to latest.
store.get — Async. (name, version?) -> { manifest, segment } | undefined. version defaults to the index's latest. Returns undefined when the name (or requested version) is not in the index.
store.has — Async. (name) -> boolean. Index lookup only; no payload read. True when the name has at least one stored version.
store.list — Async. () -> { name, latest }[]. Reads plastron/index.json and returns one summary row per stored segment. Empty array when nothing is stored.
store.put — Async. (name, version, manifest, segment) -> void. Writes plastron/segments/<name>/<version>/{manifest,segment}.json then updates plastron/index.json atomically (tmp-file + rename). Overwrites an existing version in place (no history). Throws on invalid name/version, missing version, or manifest.role==='kernel'.
store.putRaw — (state, name, version, manifest, segment-record) - unguarded store write (no kernel-role refusal); the seeding path composes over this.
store.readIndex — (state) - read plastron/index.json (empty index when absent).
[session]
session.forget — (state, name) - drop a service's tokens from the store (sign-out) and set session.<name>.status to { status: none }. After this the handle's resolver returns undefined.
session.handle — (state, name, kind?) - mint a SecretHandle for a service's access token (kind='access', default) or refresh token (kind='refresh'). The handle carries the name + a resolver closure over the module-scope store; it dehydrates to name-only (the resolver, and the token, are dropped). Effect sites (e.g. the supabase data verb) resolve() the access handle to set the Authorization bearer; an auth provider resolves the refresh handle to rotate the access token. The token is never surfaced as a cel value.
session.peek — (state, name) - return the non-secret status snapshot for a service ({ status: active|none, exp, hasRefresh }). Mirrors the session.<name>.status cel; never returns the token.
session.persist — (state) -> persisted names[]. Re-wrap every LIVE session's refresh token under the (now-unlocked) wallet into its OPFS sidecar - covers sign-ins that happened while the wallet was locked or absent. session.put persists automatically once the wallet is unlocked; session.forget deletes the sidecar.
session.put — (state, name, tokens) - store a service's live session tokens ({ access, refresh?, exp?, meta? }; tokens may be an object or a JSON string) in the module-scope token store, and write a non-secret session.<name>.status cel ({ status: active, exp }) that formulas react to. The tokens NEVER become a cel value and NEVER enter an archive — only the status does. Called by auth providers (e.g. supabase-auth) on sign-in, not by user formulas. Once the wallet is unlocked, the REFRESH token also persists to an ECDH-wrapped OPFS sidecar (session-token-persistence): unlocking the wallet on the next boot restores it via session.restore.
session.restore — (state) -> restored names[]. After keystore.unlock: unwrap every wallet-wrapped refresh-token sidecar (/plastron/session/<name>.tok) back into the module-scope Map (refresh only - no access claim, no status write) and return the names; the caller fans out to each provider's refresh op (e.g. supabase.auth refresh) to mint fresh access tokens. Names already live in the Map are skipped; a sidecar the current wallet can't unwrap is left in place.
[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.cancel-edit — Action: discard the editing draft (set sheet.editing to { editing:false, draft:"" }).
sheet.commit-cell — Action: write the editing draft into the target cell (set for value-into-ValueCel; re-install + recompute for literal↔formula kind changes), then clear the editor.
sheet.move-selection — Action: move the selection to an absolute {row,col} or by a {dr,dc} delta, clamped to sheet.dims; mirror the cell's source into sheet.formula-bar (batched).
sheet.start-edit — Action: open the editor on the selected (or given) cell, seeding sheet.editing.draft with the cell's current source.
[sheetapp]
origin.newsheet — (state) - the Sheet app: create a fresh blank worksheet DOCUMENT (a new origin-user segment of sheetapp), seed an empty grid, and render it. Save with origin.savedoc.
origin.opendoc — (state, name) - open a sheetapp DOCUMENT: loadUserSpace the origin-user segment from the store (hydrates both its parent app + its own cels), then render it as worksheet window(s) (sheetdoc). The launcher path for a recorded document (doc:<name>).
sheetapp.addsheet — (state, {ref, name?, rows?, cols?}) -> create a fresh SPARSE sheet segment and tab it into the workbook via window.addSheet. The only minted cel is <seg>.dims ({rows, cols}, default 12x7) - addresses render from dims and a cel is born only when a value/formula is committed. name defaults to the guarded sheetN; an existing name is a no-op (idempotent - the =sheet verb re-fires).
sheetapp.applyrename — (state, _, event) -> commit the rename box (✓ button or Enter).
sheetapp.delete — (state, name) - the 📂 picker row's 🗑: confirm, then store.del every stored version of the document and refresh the picker. Baked demo docs reinstall on the next fresh boot; user docs are gone for good. Deleting an OPEN doc doesn't evict its live cels — and closing that window re-saves it.
sheetapp.golive — (state, ref) -> state. The 🕸️ toolbar button: take this workbook LIVE — make the doc collaborative (writers=[me] if unset, so edits route through the CRDT pipeline), join a room derived from the doc name via the signaling relay (peerjoin), and register the sheetsync inbound routes. Unlock your identity first.
sheetapp.grant — (state, ref) -> state. The 🤝 toolbar button: grant the connected peer(s) write access — add each present peer (sheetsync.peers) to <doc>.writers and ECDH-wrap + send them the sheet data key + writers + current op-log (sheetsync.share). After this both peers' edits flow encrypted over WebRTC.
sheetapp.open — (state) - the workbook 📂 button: open a picker window listing the OPFS segment store's DOCUMENTS (store.list entries carrying an `app` field — docs, not library/application segments). Click a row to open it.
e.g. =on("click", "sheetapp.open")
sheetapp.openSealed — () the workbook 🔓 button: pick a .sealed file, decrypt it with your identity (sheetkeys.opensheet), and open it as a workbook. Opens 👤 Profile if locked.
sheetapp.pick — (state, name) - picker row click: close the 📂 picker window and origin.opendoc the chosen stored document.
sheetapp.regrid — (state, seg) -> re-derive a sheet's content formula after a cel is born into (or removed from) the segment: swaps the sheetcells(...) ref list inside every workbook tab formula rendering seg. Called by origin.commit when a commit births a new grid cel.
sheetapp.renamesheet — (state, {ref,index,name}) -> rename a sheet by ALIAS (set the tab title + record <seg>.alias), NOT by re-keying cels. Formulas/cell keys are untouched.
sheetapp.save — (state, ref) - the workbook 💾 button: derive the document from the window ref (win.<doc>.state) and saveUserSpace it (dehydrate its private closure to the OPFS segment store). Reopen from OPFS with origin.opendoc.
sheetapp.seal — (ref) the workbook 🔒 button: envelope-encrypt this document to your identity (sheetkeys.sealsheet) and download <doc>.sealed. Opens 👤 Profile if your wallet is locked.
sheetapp.tabmenu — (state, {ref,index}, event) -> right-click / double-click a worksheet tab to open the floating rename box.
sheetdoc — (state, seg, title?, offset?) - 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>. Idempotent (re-open un-hides + raises).
[sheetkeys]
sheetkeys.hash — (state, seg, exclude?) -> hex SHA-256 of segment `seg`'s SOURCE cels (ValueCel value | FormulaCel formula-text), sorted by key, excluding `exclude` patterns (array or comma-string; trailing * = prefix). Derived FormulaCel values are inherently excluded (sources only). Deterministic across replicas. Used by =hash, sealsheet, and the change pipeline.
sheetkeys.opensheet — (state, blobString) -> {ok, seg, hash}. keystore.unwrap (tries current+history) -> crypto.decrypt -> restore the segment cels -> open as a gen-2 workbook (sheetdoc). A blob sealed to a key you do not hold fails cleanly. Must be unlocked.
sheetkeys.sealsheet — (state, seg, recipientPub?) -> {ok, blob}. Envelope-encrypt segment `seg`s SOURCE archive to a keypair (default = my ecdh key = self): datakey -> crypto.encrypt -> keystore.wrap. Returns a sealed blob string {v,seg,hash,by,pub,wrapped,cipher} - opaque ciphertext. Must be unlocked.
[sheets]
gridopts — (active, selected) - build the sheetgrid opts object from the editing/selected cell-key cels (e.g. 元.editing / 元.selected) so a worksheet is EDITABLE inside a workbook tab: the active cell shows the inline editor, a click selects. Reference the editing cels in the grid formula so it re-renders reactively. Handlers + draft default to origin.* / 元.draft inside sheetgrid.
kv.mint — (state, seg, event?) - the kvsheet composer's add (the + button, or Enter in its value input): mint `<seg>.<name>` from the kv.name/kv.value drafts by the entry rules (= formula, {/[ JSON, numeric, string), ensure + append to the `<seg>.keys` roster, and REWRITE every kvsheet view formula so the new cel is a spliced live reference (the reactive-set rule). Re-minting an existing name replaces the cel.
kv.retire — (state, {seg, name}) - a kvsheet row's retire button: remove the name from the `<seg>.keys` roster, rewrite the view formulas, then retire the cel outright (no guardrails - deletion doctrine). A surviving formula that referenced it traps undefined-symbol.
kv.seed — (state, {into, from}) - merge SHIPPED DEFAULTS into a user-owned kv segment: for each name in <from>.keys missing from <into>, copy the default cel's value (+description) and append the roster. NEVER overwrites an existing row - the user's value wins; re-seeding after a defaults refresh adds only NEW keys. Rewrites the kvsheet view formulas and settles. Deleting a user row = reset-to-default (the next seed re-adds it). Dispatched by the =kvseed generative formula.
kv.set — (state, celKey, event) - a kvsheet row value commit (change, or Enter in the row input): apply the entry rules to the input text. Same tier -> setValue; a value<->formula tier change replaces the cel and cycles so the new formula fires.
kvsheet — (seg, keys?, opts?, ...pairs) - a KEY-VALUE worksheet over NAMED cels (kv-sheet.md): one row per name in the `<seg>.keys` roster (name -> editable value -> retire), plus an add-row composer (name + value + add; { and [ values parse as JSON, = mints a formula). `keys` is the roster cel's value (0 bootstraps empty); `opts` the selected-key cel's value (row highlight; clicking a name dispatches origin.select so the formula bar shows its source); `pairs` are ('<seg>.<name>', <seg>.<name>) references spliced by kv.mint/kv.retire so every row's value is live. Start with =view("params", kvsheet('<seg>', 0, sheet.selected)) - the first add mints the roster and rewires the formula.
renameOverlay — (ui, draft) -> a floating rename box (an input bound to the draft cel; Enter / the button apply via sheetapp.applyrename) shown at ui.x,ui.y when ui.open. Opened by right-click/double-click on a worksheet tab.
sheet.addrow — (state, {seg, from, clear?}) - a form's add button: append a RECORD row to a worksheet. Copies the CURRENT values of the `from` cels into columns A.. of the first free row (1 + the max populated row across those target columns), grows <seg>.dims to cover it, then resets each `clear` cel to ''. Pure mechanism - the dispatching formula authors ALL policy: which draft cels feed which columns (`from` order), the id rule (a visible next-id formula cell fed as a `from` entry), and which drafts reset. Drafts are read at CLICK time (the kv.mint pattern).
sheet.resizeDrop — (state) - pointerup after a header-edge resize: clear sheet.resizedrag. The size already landed in <seg>.colw/<seg>.rowh during the move.
sheet.resizeGrab — (state, {seg, kind: col|row, key}, event) - pointerdown on a column header's right edge / row header's bottom edge: capture the pointer, LAZILY mint the <seg>.colw / <seg>.rowh size cel (ValueCel dict, segment = the sheet's, so it archives with the doc) + rewrite the grid formulas to reference it, and snapshot the drag into sheet.resizedrag. The window.grab of the sheet grid.
sheet.resizeMove — (state, _, event) - pointermove during a header-edge resize: clamp (min 24px col / 16px row) and write the new size into <seg>.colw/<seg>.rowh LIVE - the grid formula references the size cel, so each write re-renders the grid through the graph, then drains dom.paint. No-op unless sheet.resizedrag is set.
sheet.resizeReset — (state, {seg, kind: col|row, key}) - double-click a header resize handle: remove that one column/row override from <seg>.colw/<seg>.rowh so it returns to the default size.
sheetbar — (selected, draft, opts?) - the Excel FORMULA BAR vnode: a cell-ref label, a ⚡ fire button, and a textarea that edits the selected cell's source. `selected` is the selected cell key, `draft` the source text to show (seed an ASCII mirror like sheet.draft so the bar re-renders on selection). The textarea's input writes the live text to the draft cel (opts.draft, default 元.draft) without re-rendering; Enter (opts.commit, default origin.key) and ⚡ (opts.fire, default origin.fire) commit it. Stack over a grid with sheetpane.
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?, dims?, colw?, rowh?) - 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.*); `dims` ({rows,cols} - the <seg>.dims cel) sets the sparse rendered range; `colw`/`rowh` are the sparse size dicts (<seg>.colw {letter:px} / <seg>.rowh {row:px}) - headers grow Excel-style resize handles (drag the edge; double-click resets). 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.
sheetpane — (bar, grid) - stack a formula bar over a grid in one CONTENT vnode (bar fixed at top, grid scrolls below) so a worksheet window reads like Excel. Both args are already-rendered vnodes.
signedIn — (auth) -> boolean. PURE: is a Supabase auth status object signed-in ({status:'signed-in'})? Tolerant of a missing cel (undefined -> false). Grid formulas pass (signedIn sb.<project>.auth) to sheetbar to gate the 🔮 ask-grok button.
writableBy — (identity, writers) -> bool. PURE formula-bar gate: a segment with no writers list is OPEN; one WITH a list admits only its members; a locked/empty identity is never a writer of a restricted sheet. The grid formula calls (writableBy keystore.identity <seg>.writers) and passes it to sheetbar.
[sheetsync]
sheetsync.announceWriters — (state, seg) -> {ok, frame}. Broadcast the current <seg>.writers as a SIGNED 'writers' frame so every peer in the mesh converges on who may write. Grow-only union; a peer accepts it only if the signer is already a writer (bootstrap when it has none). Called by sheetapp.grant after adding a new writer.
sheetsync.commit — (state, seg, key, source) -> {ok, hash, cells, frame?}. Per-cell LWW-Map CRDT: mint an op {key,kind,val,ts,author,id} (val = formula TEXT for kind 'f', or a literal value AS-IS for kind 'v' — number/string/bool/null/object/list, never stringified), sign it, gate+merge into the <seg>.crdt winner-map (per-key last-writer-wins by ts,author,id), project the winning cell, runCycle. If the seg is synced (a data key is held) encrypt + broadcast the op; `frame` is the encrypted op for tests. SOURCES only; non-writers dropped.
sheetsync.connect — (state) -> {ok, frame}. Register inbound frame routes (op/key/hello -> sheetsync.recv) on the peer transport and emit a 'hello' announcing my identity + ecdh pub so a peer can wrap a data key to me. Returns the hello frame (for tests to hand-deliver).
sheetsync.haskey — (state, seg) -> boolean. Non-secret introspection: is this segment being synced (a data key is held in module scope)? The key itself is NEVER a cel.
sheetsync.hello — (state) -> {ok, frame}. (Re)announce presence: emit a hello carrying my identity + ecdh pub so a peer can wrap a data key to me. Registered as peer's 'open' handler so it fires when the data channel opens.
sheetsync.recv — (state, frame) -> decision string. Inbound dispatch. hello -> record peer ecdh; key -> unwrap data key + adopt writers + LWW-merge the map snapshot; op -> decrypt, verify signature, check author==signer, gate writableBy, LWW-merge into <seg>.crdt + project the cell. Idempotent (stale/duplicate ops lose the merge).
sheetsync.share — (state, seg, peerEcdhPub?) -> {ok, frame}. Grant a peer: mint the seg data key if absent, ECDH-wrap it to the peer's ecdh pub (keystore.wrapTo), and send a 'key' frame { wrapped key, writers, ENCRYPTED current MAP snapshot }. The map merges on the peer by the same per-key LWW rule as a single op.
[sound]
sound.is-playing — Returns true if the handle returned from play-pcm is still playing. Returns false for unknown handles and off-browser.
sound.play-pcm — Play raw PCM samples. Args: { samples:Float32Array (-1..1), rate?:Hz = 44100, channels?:1|2 = 1, gain?:0..1 = 1 }. For >1 channel, samples must be interleaved. No-op off-browser.
sound.play-tone — Play a synthesized oscillator tone. Args: { freq:Hz, duration?:ms = 200, type?:'sine'|'square'|'sawtooth'|'triangle' = 'sine', gain?:0..1 = 0.3 }. Lazily creates + resumes the AudioContext on first call. No-op off-browser.
sound.stop-all — Stop every active source (tones + PCM). No-op off-browser.
sound.stop-source — Stop the source identified by a handle returned from play-pcm. No-op for an unknown / already-ended handle, and no-op off-browser.
sound.update-source — Adjust gain/pan on a live play-pcm source. Args: (handle, { gain?, pan? }). gain is multiplied with the current master at apply time. No-op for unknown handles / off-browser.
[sqlite]
sqlite.command — (state, op, name, query) - in-browser SQLite (@sqlite.org/sqlite-wasm over the opfs-sahpool VFS, in a Worker). op=open returns a {__db} handle; op=sql runs a query (writes persist incrementally to OPFS); op=seed bulk-inserts JSON rows; op=schema introspects tables/columns/PK/FK; op=export serializes the db to a file-store path; op=import loads a .db file-store blob into the db. Host db()/sql()/tables()/dbseed()/schema()/dbexport()/dbimport() verbs delegate here.
[supabase]
supabase.config — (state, project, urlOrDict, anonkey?) -> string. Set the PUBLIC project config. Preferred: a DICT writes the ONE sb.<project> cel ({url, anonkey}) that sbConfig resolves FIRST; legacy (url, anonkey) strings write the two-cel sb.<project>.url / .anonkey fallback form. The anon/publishable key is browser-safe; the only secret (the user JWT) lives in the session store after supabase.auth signs in.
e.g. =supabase.config("default", {url:"https://xxx.supabase.co", anonkey:"sb_publishable_…"})
supabase.data — (state, op, project, payload) - Supabase PostgREST data plane over gated fetch. Reads config cels sb.<project>.url / sb.<project>.anonkey; sends the user JWT (resolved from the session store) as the Authorization bearer when signed in, else anon. op=select {table, query?} → rows; op=insert {table, rows} → inserted rows (+bumps rev); op=update {table, match, values} → updated rows (+rev); op=delete {table, match} → deleted rows (+rev); op=rpc {fn, args} → result. Writes bump sb.<project>.<table>.rev — reference that rev in a select formula and the select re-fires on change (the same cel realtime pushes bump). Returns rows on success, a parenthesised error string on failure (Array.isArray to discriminate).
supabase.invoke — (state, project, fn, body) -> json | {error}. Call a Supabase EDGE FUNCTION (/functions/v1/<fn>) with apikey + the user JWT resolved at the effect site (never a cel). The function holds any provider secret (e.g. an LLM key) SERVER-SIDE. Used by the grok segment to reach the chat-completion proxy.
supabase.realtime — (state, op, project, payload) - Supabase Realtime over a Phoenix-channel WebSocket. op=subscribe {table, schema?='public'} opens a persistent socket (authenticated with the session JWT for RLS) and, on each postgres_changes push, bumps sb.<project>.<table>.rev — so a server-side change re-fires a subscribed select formula through the SAME rev cel a local write bumps. op=unsubscribe {table} leaves the channel; op=status returns { connected, channels }. Browser + Bun (globalThis.WebSocket).
[supabase-auth]
supabase.auth — (state, op, project, payload) - Supabase GoTrue auth over gated fetch. Reads the public config cels sb.<project>.url and sb.<project>.anonkey. op=signin {email,password} → password grant; op=otp/magiclink {email} → email a magic link; op=refresh → rotate the access token via the stored refresh token; op=signout → revoke + clear. On sign-in the tokens go to the session store (session.put under 'supabase.<project>'); the JWT never becomes a cel value. The graph sees only the non-secret sb.<project>.auth status cel ({ status: signed-in|signed-out|otp-sent|error, email, userId }). The host should net.allowlist the project URL when the net gate is active.
[supabase-demo]
sbdemo.add — (state, project, title?) - dispatch handler: insert a todo via supabase.data; the write bumps sb.<p>.todos.rev so the panel's count re-renders.
sbdemo.field — (state, cellKey, event) - input dispatch handler: write the <input>'s value (event.target.value) into the cel named by cellKey. Wires email/password fields to cels.
sbdemo.install — (state, project='demo', url?, anonkey?) - lay down a worked Supabase example as sheet cels: config (sb.<p>.url/anonkey), seeded sb.<p>.todos.rev, sbdemo.<p>.status/live strings, and a reactive sbdemo.<p>.panel FormulaCel that renders auth status + live todo rev-count + subscription state via dom. The panel re-renders reactively as the handlers (sbdemo.signin/add/watch) drive the supabase verbs.
sbdemo.kanban — (state, project='demo', url?, anonkey?) - install the interactive login+kanban demo: config + email/password/status/tasks cels + a mounted sbdemo.<p>.view formula (=mount('.origin', sbdemo.ui(...))). Real <input>s + a Log in button, authored as a formula; on submit it signs in and renders the user's kanban board.
sbdemo.login — (state, project, event) - login button handler: sign in with the captured sbdemo.<p>.email/password, then query the user's kanban table (RLS-scoped) and write the rows to sbdemo.<p>.tasks so the board renders. Sets sbdemo.<p>.status.
sbdemo.signin — (state, project, payload) - dispatch handler: sign in via supabase.auth and write a display string to sbdemo.<p>.status (the panel reacts). Wire a button's click to this.
sbdemo.ui — (project, email, password, status, tasks) - PURE render of the login form + kanban board as a vnode. Inputs dispatch to sbdemo.field; the button dispatches to sbdemo.login. Call from a formula with the reactive cels as args so it re-renders on change.
sbdemo.watch — (state, project) - dispatch handler: subscribe sb.<p> todos to realtime; a server-side change then bumps the same rev cel and the panel re-renders live. Sets sbdemo.<p>.live.
[supabase-storage]
supabase.storage — (state, op, project, payload) - Supabase Storage (buckets) — the cloud twin of the file-store/OPFS library, over gated fetch with the session JWT (RLS-scoped). op=upload {bucket, path, content, contentType?='text/plain', upsert?=true} → { Key }; op=download {bucket, path} → text; op=list {bucket, prefix?, limit?, offset?} → objects; op=remove {bucket, path} → ok; op=createBucket {bucket, public?} → bucket; op=listBuckets → buckets. Reads config cels sb.<project>.url / sb.<project>.anonkey. Returns a parenthesised error string on failure.
[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.
[viz-core]
bandscale — (labels, r0, r1 [, pad]) - a categorical SCALE SPEC: one evenly spaced band per label across [r0,r1]. pad (0..1, default .2) is the gap fraction between bands. scaleapply(band, label) → a band's start; bandwidth(band) → its drawn width.
bandwidth — (scale) - the drawn width of one band of a band scale (0 for non-band scales). Pair with scaleapply for a bar's x and width.
colorapply — (scale, v) - the "#rrggbb" for v under a colorscale (RGB lerp, clamped to the domain). Drives a choropleth/heatmap fill.
colorscale — (d0, d1 [, c0, c1]) - a sequential color SCALE SPEC; colorapply(scale, v) lerps a value's "#rrggbb" in RGB between c0 (at d0) and c1 (at d1). Defaults to a dark→cyan ramp.
extent — (values) - [min, max] over the finite numbers in a (possibly nested) range; [0,1] when empty. Derive a scale's domain from a data column: linscale with min(col)/max(col), or feed extent to your own logic.
linscale — (d0, d1, r0, r1) - a linear SCALE SPEC mapping the data interval [d0,d1] onto the visual interval [r0,r1]; r0>r1 is fine (screen y grows down). A scale is a pure data value, not a closure — feed it to scaleapply/ticks. e.g. scaleapply(linscale(0, max(col), plotH, 0), v) → a pixel y.
nice — (lo, hi [, count]) - round [lo,hi] outward to a "nice" step (1/2/5 ×10^k); returns [niceLo, niceHi, step]. Used to give a linear axis round endpoints.
ordinalcolor — (key [, keys]) - a categorical palette color. With keys, the color of key's position in keys; otherwise key coerced to a 0-based index. The 10-color palette wraps.
scaleapply — (scale, v) - map a value through a scale. linear: un-clamped linear interpolation → number. band: the START position of v's band (v is a label or a 0-based index).
scaleinvert — (scale, pos) - the inverse of scaleapply. linear: pixel → data value. band: the label nearest pos. The hook for interaction (a pointer x → which datum).
ticks — (scale [, count]) - axis ticks for a scale as [{v, pos, label}]. linear: "nice" round values (1/2/5×10^k) across the domain; band: one tick per label at its band center. An axis is ticks(scale) rendered as text+line ops.
[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
[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, exposing the chosen export (metadata.wasmExport, else prefer 'main', else the single export) and handing a metadata.imports provider its live instance (memory + all exports) through an onInstantiate hook — parity with wasm-bytes, so a shared-memory WAT module authored as legible WAT text on the sheet can own a memory and drive multiple exports. 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]
app.assets — (manifest) - install an app's ASSETS to OPFS from an assets manifest { name, version, readme?, assets:[{url, opfs, sha?}] } (object or a URL to its JSON). Downloads each MISSING asset to its opfs path (idempotent), verifying SHA-256 when given. Returns a one-line status. The foundation for origin-applications defined in formula-cels: install to OPFS, then load from OPFS into state. Network + OPFS IO. (Renamed from app.install — 'install' now belongs solely to origin.install, the archive-to-store installer.)
app.assets.ready — (manifest) - true iff every asset in the manifest is already present in OPFS (a formula can branch on it: install when false, load when true).
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.assets (assets → OPFS) + a harness provider to keep the app mostly formula-cells.
wasmcanvas — (id, status?) - the body of a wasm window: a focusable <canvas id="wasm-<id>"> the engine grabs by id, plus a boot overlay driven by status (the engine's .out cel): a loading bar while fetching/starting, or a 'could not load' card on #ERROR. Focus/blur toggle wasm.<id>.active; keydown/keyup route to the engine (active-gated).
wasmwin.blur — (state, id) - lost focus → no longer the active wasm window.
wasmwin.focus — (state, id) - the wasm canvas gained focus → mark it the active wasm window (keys route here).
wasmwin.key — (state, id, event) - a keystroke on the canvas → route to the engine inbox if this window is active.
[window]
wbframe — (state, active, ...contents) - render a WORKBOOK window: two tab stacks at once - worksheets (left pane, tabs at bottom) and dom views (right pane, tabs on top) - split by a draggable divider, with per-pane fullscreen. state = { x,y,w,h,z,min,max,closed,title, sheets:[{ref,title,icon}], asheet, views:[{ref,title,icon}], aview, split:0..1, full:'L'|'R'|'' }. contents are one value per tab, sheets first then views (sliced by sheets.length).
wbopen — (id, title, sheetTabs, viewTabs, where?) - genesis a SELF-MOUNTING workbook window: a state cel holding the two tab stacks + a frame cel `(mount '.origin' (wbframe <state> win.active <sheetRefs...> <viewRefs...>))`. sheetTabs/viewTabs are [{ref,title,icon}] over existing content cels. where = geom(x,y,w,h).
wframe — (state, active, ...contents) - render ONE window's chrome around the ACTIVE tab's content (contents[active]): titlebar (icon + name + optional app-supplied tools + minimize/fullscreen/close; state.tools = [{icon,title,dispatch}], each tool dispatches its verb with the window ref as payload), a tab strip when the window has >=2 tabs, the body, and 8 resize handles (n/s/e/w + nw/ne/sw/se) so the window resizes from every edge and corner. `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.
window.addSheet — ({ref, viewRef, title?, icon?, f?}) push a NEW worksheet tab onto a workbook's sheet stack (state.sheets) and select it. If `f` is given, the content cel `viewRef` is authored as a FormulaCel first. Rebuilds the workbook frame to reference the new content cell.
window.addView — ({ref, viewRef, title?, icon?, f?}) push a NEW dom-view tab onto a workbook's view stack (state.views) and select it. If `f` is given, the content cel `viewRef` is authored as a FormulaCel first — so a formula can OPEN a fresh dom window in the stack. Rebuilds the workbook frame to reference the new content cell.
window.close — the red X: runs EACH tab's <seg>.flush contract cel (every tab is an app with its own save-then-teardown), then drops the window. A tab's flush is `<seg>.flush` derived from its content ref (or tab.flush).
window.dock — ({into, win}) COMBINE windows: pull an existing window `win` in as a tab of `into` (it is marked dockedIn → self-hides; the host renders it; tear-off restores it). Or ({into, tab}) append a raw tab { ref:<contentCel>, title, icon }. Rebuilds the host's self-mounting frame so it references the new content. Lets a sheet + DOOM share one tabbed frame.
window.drop — pointerup: clear the transient window.drag state.
window.grab — pointerdown on a titlebar: capture the pointer + record the move offset into window.drag.
window.grabResize — pointerdown on a resize handle: capture the pointer + snapshot the start rect (x/y/w/h) and pointer (px/py) into window.drag (resize mode). Payload is { ref, dir } where dir is one of n/s/e/w/nw/ne/sw/se; a bare string ref is tolerated for backward compat and treated as dir 'se'.
window.max — toggle fullscreen: when set the frame fills the viewport.
window.min — toggle the window's minimized flag (the frame hides; a launcher restores it).
window.move — pointermove while dragging: write the window state cel's x/y.
window.paneFull — ({ref, pane}) toggle fullscreen for a workbook pane ('L' sheets / 'R' views): set state.full to pane, or '' (see both) if already fullscreen.
window.raise — pointerdown anywhere in a window: focus it (win.active) and bump it to the top of the z-order (win.topz).
window.rebuildFrame — (state, ref) -> re-author a workbook frame formula from its current sheets/views state. For sheetapp.renamesheet after a re-key swaps a content-cel ref.
window.resizeMove — pointermove while resizing: resize per the grabbed direction, anchoring the opposite edge - e/w change width (w also moves x), n/s change height (n also moves y), corners combine. Writes the window state cel's x/y/w/h, clamped to min w>=160, h>=90.
window.sheetTab — ({ref, index}) select a worksheet tab in a workbook: set the state cel's asheet index.
window.splitGrab — (ref) pointerdown on a workbook divider: seed window.drag {ref, split:true} so the move resizes the split.
window.splitMove — (_, event) drag a workbook divider: set state.split to the pointer's fraction across the window (clamped 0.15..0.85).
window.stop — pointerdown on a titlebar/tab BUTTON: stopPropagation so the click does not start a window/tab drag.
window.tab — ({ref, index}) select a tab: set the window state cel's active index.
window.tabClose — ({ref, index}) close one tab: remove it from the window's tabs; closing the last tab closes the window.
window.undock — ({ref, index}) TEAR OFF a tab (inverse of window.dock): remove it from `ref` + rebuild its frame. If the tab backs a real window (docked via window.dock), RESTORE it (clear dockedIn → it self-mounts again); otherwise mint a fresh standalone window + add it to win.list.
window.viewTab — ({ref, index}) select a dom-view tab in a workbook: set the state cel's aview index.
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.
[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). NAMES MUST NOT LOOK LIKE CELL REFS: s2/g1/aa3 tokenize as A1 addresses and the binding is rejected — use sq/ga/total.
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
POWER — (x, y) x to the power y
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
SQRT — (x) square root
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