Stateloom live-canvas β the imitation blueprint
Provenance: produced 2026-08-01 by a read-only reverse-engineering agent dispatched by
πΏ Mino-Bimaadizi-Daa (day 09, session e9a78814) at William's direction: model on stateloom's
infrastructure, do not consume its packages. Every claim carried file:line in the original run;
this file is the seat's durable copy. Serves chart_1785632224671 / chart_1785632228800.
1. Transport β socket.io (engine.io v4: HTTP long-poll handshake β WebSocket upgrade)
The live path is socket.io β not SSE, not polling, not file-watch-to-browser.
- Hub server:
bridge/src/hub.ts:98-102βnew Server(httpServer, { cors, serveClient: false }); binds athub.ts:295. - Verified live:
curl -s "http://127.0.0.1:4599/socket.io/?EIO=4&transport=polling"β engine.io v4 handshake advertising websocket upgrade. - Browser client:
bridge-client/src/client.ts:88-91. - Wire vocabulary, single source of truth:
bridge-protocol/src/events.ts:46-64(bridge:join,def:patch,def:full,def:request,def:ack,presence:*,bridge:error).
Two other mechanisms exist and are mistaken for the live path:
- SSE is the legacy fallback (only when bridge URL env unset):
web/src/components/DesignBridge.tsx:16-22,BridgeProvider.tsx:36(EventSource("/api/watch")),app/api/watch/route.ts:63, driven byfs.watchatroute.ts:24. Carries only an mtime ping β client re-GETs the whole file. Coarse reload, no animation. - chokidar file-watch lives inside the hub, not the browser:
bridge/src/watcher.ts:48. Turns an external edit into granular ops:watcher.ts:67diffDefinition(room.def, fileDef)βwatcher.ts:70-71broadcastdef:patch.
End-to-end trace: MCP add_state β pixels
| # | Where | What |
|---|---|---|
| 1 | mcp/src/server.ts:737 | readDef() β reads STATELOOM_PROJECT_FILE from disk |
| 2 | mcp/src/server.ts:751 | mutate in memory |
| 3 | mcp/src/server.ts:752 β :115-117 | writeDef() β disk write FIRST |
| 4 | mcp/src/server.ts:753-755 β :213-224 | bridgeEmitPatch([{op:"state.add",β¦}]) stamped with statSync(PROJECT_FILE).mtimeMs |
| 5 | bridge-client/src/client.ts:238-240 | socket.emit('def:patch', { docId, ops, origin: selfId, baseSeq, mtime }) |
| 6 | bridge/src/hub.ts:169-219 | dedup-ring check (:175) β apply (:209) β seq++ (:210) β ring push (:211) β broadcast to whole room incl. sender (:212-218) β def:ack (:219) |
| 7 | bridge/src/watcher.ts:63 | chokidar sees the same write; mtime/hash already in ring β returns. No double-apply. |
| 8 | bridge-client/src/client.ts:148-152 | gap check β drop if origin === selfId β fire('patch') |
| 9 | bridge-react/src/session.ts:126-139 | split runtime vs structural ops; apply; commit() builds new immutable snapshot (:90-99) |
| 10 | web/src/components/SocketBridgeProvider.tsx:70-74 | onPatch β applyRemoteOps(...) |
| 11 | web/src/store/useDesignerStore.ts:757-812 | _applyingRemote guard (:760), apply (:770), re-autoLayout (:782), preserve drill-down + selection (:787-800), zustand β SVG re-render |
| 12 | SocketBridgeProvider.tsx:86 | outbound subscription sees _applyingRemote β does not echo back |
React never polls: bridge-react/src/useSmcraftBridge.ts:56-60 β useSyncExternalStore.
2. Truth & ownership
The .smdf.json file on disk is durable truth. The hub owns ordering only, never disk.
- Invariant stated and enforced:
hub.ts:1-12,docio.ts:1-6; nowriteFile*anywhere inbridge/src. - Hub room =
{ docId, def, seq, mtime, ring, presence, watcher }(hub.ts:43-51), seeded from disk (hub.ts:109), snapshot to cold joiners (hub.ts:160). - Each writer persists through its own durable channel; the web app only writes disk on explicit πΎ (
Toolbar.tsx:62-67) β human edits live-but-not-durable, agent edits durable immediately. - docId = normalized absolute file path (
docio.ts:51-53), one room per docId, same string env-injected into every process byscripts/live-loop.sh:24,36(header documents the silent-divergence failure this prevents). - Schema: file top level
{ "stateMachine": β¦ }; inner doc exactlysettings,events,state(bridge-protocol/src/definition.ts:133-137).
3. Concurrency β last-write-wins, ordered by one event loop. No OT, no CRDT, no version check.
seqis a broadcast ordinal assigned solely by the hub, not persisted (hub restart resets to 0).baseSeqon the wire is never validated by the hub β no optimistic concurrency exists.- Apply-throw β
bridge:errorto sender + full-room snapshot resync (hub.ts:226-233). That is the whole conflict story. - Client gap detection:
client.ts:122-128(forward gaps only; a backward seq after hub restart is not detected). - The one real defense: dedup ring of last 20
{mtime, hash}per room (hub.ts:68-80), checked on both paths; correctness argued atwatcher.ts:10-17(both paths on one event loop). - Known UX regression: live mode silently clobbers unsaved local edits (
useDesignerStore.ts:806setsdirty:false), unlike the SSE fallback's"remote-changed"refusal.
4. Consuming AS-IS β footnote (per William's correction: we don't)
The protocol is state-machine-specific (ops.ts:22-44, SMDF definition.ts:133-137). The hub is ~90% document-agnostic (never reads a def field; SM coupling is three call sites: hub.ts:209, watcher.ts:67, unwrap at docio.ts:27/watcher.ts:40). AS-IS is not viable for a DPR model; the shape is the assignment.
5. Essential decisions (E1βE10) β these ARE the live quality
- E1 Durable-first, then announce. Persist to your own store, THEN emit the exact op applied, stamped with the store's returned token. Never emit-then-persist: crash between = canvas ahead of truth, unrecoverable; reverse is merely stale and self-heals.
- E2 The relay is a sequencer, not a database. In-memory mirror only for apply-without-round-trip and cold-joiner snapshots. No durability β hub can die and restart without data loss.
- E3 Granular ops on the wire; whole documents only for three cases β join, no-base recovery, post-error resync. Ops are why the canvas animates instead of re-mounting.
- E4 One bidirectional channel, self-echo killed by
origin. Broadcast includes the sender (so it learns its seq); client drops own echo by id; store marks remote-applied batches so outbound watcher doesn't re-emit. Without both guards: duplicate inserts, remove-throws on every self edit. - E5 Cold-load hydrates through the SAME callback as a live update. (
session.ts:161-169; real production bug46d5bc6: tab opened after the agent worked rendered an empty canvas.) - E6 Immutable snapshot +
useSyncExternalStore. New snapshot object only when content changes. No polling, no context churn. - E7 Reconcile the out-of-band writer. Watch the durable store; diff oldβnew; broadcast the diff as ops; guard with a revision-token dedup ring so your own writers' persists don't double-apply.
- E8 One canonical document id, computed absolutely, env-injected into every process.
- E9 Fail open. A dead bridge never fails a tool (warn once; durable write already succeeded). The agent conversation never breaks because the canvas is down.
- E10 Presence for free. A Map per room broadcast on join/leave β cheap, and most of the "this is alive" feeling.
Incidental (replace wholesale)
I1 the op vocabulary β veritas writes element.add/update/remove, comparison.set, dominance.recompute; I2 diff/apply implementations β keep the contract apply(prev, diff(prev,next)) β‘ next with a roundtrip test, key by stable id; I3 the {stateMachine:β¦} wrapper and .smdf.json extension; I4 runtime.enter/exit β steal the idea (presentational highlight channel that doesn't mutate the doc), not the names; I5 all diagram geometry; I6 truth-as-a-file β veritas's truth is a Postgres row (see counter-argument).
The recipe (15 lines)
1 Standalone hub process (socket.io or ws). One room per documentId. Never writes durable truth.
2 documentId = one canonical string (veritas: model id), computed once, env-injected into every process.
3 Own protocol pkg, zero deps: DocType, Op union, pure apply(doc,ops), pure diff(prev,next), event name table,
envelopes {docId, seq, ops|doc, origin, rev}. Ship a roundtrip test: apply(prev,diff(prev,next)) === next.
4 Hub room state: {doc, seq, revRing[20], presence Map}. join -> ack{selfId, snapshot, presence}.
5 Hub on op: revRing dedup -> apply -> seq++ -> ring.push -> broadcast to WHOLE room incl. sender -> ack sender.
On apply throw: error to sender + full-doc resync to room.
6 Every writer (MCP tool, CLI, web action): persist to Postgres FIRST, then emit that same op stamped with
the returned revision/updated_at. Failure to emit = warn once, never fail the tool.
7 Client wrapper: join; on inbound seq > lastSeq+1 -> request(sinceSeq) resync; drop frames where origin===selfId;
expose patch/full/presence/status callbacks. NOTE: also re-emit join on socket 'connect' (stateloom omits this).
8 React binding: session holds an immutable snapshot, commit() on change, subscribe -> useSyncExternalStore.
9 Fan the join-ack document out through the same onFull callback as a live update (cold-load hydration).
10 App store: applyRemoteOps(ops) behind an _applyingRemote flag; recompute derived layout; preserve selection
and drill-down across syncs so the view doesn't snap on every agent tool call.
11 Outbound: subscribe to the store, skip _applyingRemote batches, debounce ~60ms, emit diff(lastSynced, current).
12 Out-of-band writers: Postgres LISTEN/NOTIFY (or skip if all writes go through your API) -> diff -> broadcast.
13 Presence: role + name + deterministic color from clientId; broadcast the list on join/leave.
14 A launcher script that resolves and exports every shared value once (id, hub url, ports) β for hub, MCP and web.
15 Optional handshake token; CORS on the hub only when a browser connects to it.
Strongest argument AGAINST reusing stateloom's bridge (why we imitate instead)
Its correctness is built on a local file with an mtime, and veritas has neither a file nor a place to run a stateful singleton. Every defense keys on statSync(...).mtimeMs; veritas's truth is a Neon Postgres row written by serverless route handlers β no mtime, nothing for chokidar to watch, no guarantee the writing process is the emitting process. A substitute revision token must be invented and proven under concurrent serverless writers, not one Node event loop. The hub is stateful, single-instance, localhost-bound, with the socket URL baked into the client bundle at build time. And the survivable-on-localhost defect that isn't survivable hosted: no rejoin on reconnect β after any hub restart/blip the client is connected-but-in-no-room, patches silently dropped at hub.ts:171, canvas quietly stale. reconnect appears nowhere in the bridge sources except a test disabling it.
Model the shape. Don't inherit the file.