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.
each step boundary is a server-parked state — the native front hands the iframe a slug, nothing else
2 · The four steps
step
scope (native after it ships)
APIs it consumes
handoff 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
No dual paths. A ported step renders native only; the iframe serves only the tail.
The frontier moves, it never forks.
House objects only. The v797–v800 dress is the spec: group-title labels, 12/10 radii,
the 600 column, one red (--rec), yours = gold, CTA text per the owner's row-3 call
(glyphs stay).
The server is untouched. Every endpoint exists; the port is a client move. Anything
that tempts a server change is out of scope and gets its own ask.
The fake pair is the gate. Every step ships only after「a fake success」and
「a fake failure」run end-to-end through the NATIVE surfaces on :8016 — full spine, zero spend,
3 s a step. Plus both gates (smoketest · i18n audit) and the style-guide vlog, as ever.
i18n rides along. The dict is already shared; ported strings keep their existing keys,
new ones land with zh entries in the same change.
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
step
status
N1 — Name + Identity
SHIPPED 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 — Confirm
SHIPPED 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 — Build
SHIPPED 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 sweep
SHIPPED 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.