The Studio goes native — the port plan

COMPLETE 2026-08-13 — all four steps SHIPPED (v801–v804), one day, zero forks. Originally: The build flow (lib/builder-ui.html, an ~1,800-line SPA inside the New-persona sub-page's iframe) is ported into room-ui.html in four steps, each shippable and verifiable alone. Precondition already met: the landing (v795), the sub-page shell (v796) and the house-language pass (v797–v800) fixed the target look; the server is untouched throughout — every endpoint the port needs already exists.

1 · The seam that makes it incremental

The server is the flow's only state. Every build is a slug whose _job.json records exactly where it stands (probed · locked · confirmed · running · published/failed), and ?resume=<slug> already lands the page on the parked step — that is how the landing's In-progress rows work today. So the port can move the native/iframe frontier one step at a time: completed steps render native, the iframe serves only the not-yet-ported tail, and the handoff between them is the same ?resume the app already trusts. No dual implementations of any one step, ever — a copy is consistent only until the original moves.

the flow 1 · Name form · material · probe 2 · Identity pick · lean · concepts · lock 3 · Confirm card · made-from · ack · start 4 · Build spine · SSE · finish the frontier N1 — steps 1+2 native iframe tail, entered via ?resume=slug N2 — step 3 joins N3 takes step 4 → N4 sweeps
each step boundary is a server-parked state — the native front hands the iframe a slug, nothing else

2 · The four steps

stepscope (native after it ships)APIs it consumeshandoff to the iframe≈ size
N1 Name + Identity. The form (Who · Notes · Your material staging, +Files/+Links), the probe, the identity cards (candidates · the two dashed escapes · None-of-these + hint re-check), the nobody/invented paths (lean · concepts), the lock. The stepper becomes a house object and the header becomes the flow's one back and one title — the parked rows 1–2 of the inconsistency table get their answer here (owner input at design time). config · probe · lock · supplied GET/POST/DELETE after the lock → ?resume=slug lands on Confirm ~400 lines
N2 Confirm. The locked card, Your material (post-lock add/remove), the full-size made-from bar + legend, the acknowledge row, the estimate line, the meter and the one-build-at-a-time mutex, Confirm & build. start · supplied · config (estimates) after start → ?resume=slug lands on the running spine ~200 lines
N3 Build (the heaviest). The spine — stages, facets, shimmer, the live event line — over the /events SSE; the cost ticker; abort · resume · re-audit; the finish states (published → the greenroom notice already carries the meet; failed → reason + Restart). The same view read-only over /events+/ledger replaces the ?report iframe behind the landing's Build detail. events (SSE) · ledger · resume · reaudit · abort none — the frontier reaches the end ~350 lines
N4 The sweep. npFrame dies: loadFrame/liveness/theme-postMessage and the builder-layer back-relay contract all go (native layers register directly); the /builder HTTP-cache trap dies with the iframe. builder-ui.html survives only as the console's standalone report reader (?report=), out of the app. Docs + memory updated; one real cheap invented build end-to-end as the closing validation (~$0.09, the nemo precedent). negative

3 · The rules the port holds to

Verification prerequisite. The fake-build fixtures are admin-gated (fake_kind checks the role at probe and start). Dev verification needs an admin login — promote a QA account (e.g. qa-amy) to admin in app-dev.db; the owner's own logins stay untouched per the standing rule.
What deliberately stays. The Studio landing (v795), the sub-page shell (v796), the long-press menu (v799) — the port fills the shell those built. The build pipeline (lib/builder.py) and its endpoints are not part of this plan. The console keeps /builder?report= as its own tool.

4 · Status

stepstatus
N1 — Name + IdentitySHIPPED v801 — fake pair green end-to-end; two findings folded into the design: ① a probe has NO server dir until start (a fake never gets one), so the handoff carries CLIENT STATE (an np-lock message, pending-flush on builder-ready) and the lock POST stays a fire-and-forget park, exactly as the flow’s own; ② the /builder HTTP-cache trap is CLOSED — the frame URL rides the shell’s BUILD, so parent and frame can’t version-skew.
N2 — ConfirmSHIPPED v802 — the locked card, material add/remove, the made-from bar + band, the ack, the estimate and the mutex all native; START is the frontier now, and it is the honest one: the build exists server-side and is running, so ?resume carries the iframe straight onto the spine. N1’s np-lock bridge and the carry-links plumbing were swept the same day they became dead — the frontier moves, it never forks.
N3 — BuildSHIPPED v803 — the spine over the /events SSE (snapshot-then-tail, docs/facets/seat rows, model chips as coloured text), the $/time tick folded into the state line (the old fixed ticker’s stage label duplicated the spine’s active stage and died), the terminal (made-from actual · coverage · bill tables · audit/sources folds · test checklist), Resume-on-failed and the dead-seat re-audit.「Build detail」IS the same view read-only. The iframe is never loaded by the app any more — the whole resume router is native; N4 sweeps the corpse.
N4 — the sweepSHIPPED v804 — THE PORT IS COMPLETE. npFrame and every scrap of its plumbing died: frameAlive/loadFrame/frameNeedsLoad, the theme relay, the cross-frame message contract(np-* · builder-*), the G3 builder-layer back relay, the npBuilderUrl cache-buster(the HTTP-cache trap died with the frame). Standalone /builder self-gates to the console’s ?report= reader and redirects everything else into the app. Closing validation: a REAL invented build(Tamsin Veil → authored as its own persona)ran end-to-end through the native flow — probe → web-silent lean → live concepts → confirm → the native spine(Author · checks · patch · audit)→ ✓ built · 3:31 · $0.027 — then deleted and purged.
canon: the Studio page · the shell it fills: v795–v800 (see the style guide changelog) · plan authored 2026-08-12; status updated in the same change as each ship, per the house plan rule.