From 1093f9d1416ce3fd5091a143d47e32f02ae1bcd2 Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Tue, 2 Jun 2026 11:13:31 -0400 Subject: [PATCH] =?UTF-8?q?zebra-report:=20shadow=20CallFSM=20in=20live=20?= =?UTF-8?q?page=20=E2=80=94=20mirror?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- zebra-report/zebra-spaces.html | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/zebra-report/zebra-spaces.html b/zebra-report/zebra-spaces.html index 043adf1..0803b67 100644 --- a/zebra-report/zebra-spaces.html +++ b/zebra-report/zebra-spaces.html @@ -2281,6 +2281,23 @@ function canSpeak(role){ return role==='host' || role==='cohost' || role==='spea * signaling websocket — JSON text frames, see protocol header in * cmd/zebra-spaces-signal/main.go * ================================================================== */ +/* ROOM MACHINES — shadow instance. Runs alongside the imperative + * logic during the FSM rollout: the existing handlers stay + * authoritative for side effects, but we ALSO send synthetic events + * here so the state machines track reality. fox can inspect via + * `roomMachines.call.state` / `roomMachines.sub.state` in DevTools + * during QA. As each imperative path is rewritten to be driven by + * the FSM, the shadowing comes out. */ +const roomMachines = wireZebraMachines(); +if (typeof window !== 'undefined'){ window.roomMachines = roomMachines; } +/* visible trace of every CallFSM transition so QA can correlate UI + * symptoms with state changes. Will tighten the log noise once the + * runtime takes over from the imperative handlers. */ +roomMachines.call.observe(({ state, prev, ev }) => { + if (prev === null || state === prev) return; + logLine('', 'call: ' + prev + ' → ' + state + (ev && ev.type ? ' [' + ev.type + ']' : '')); +}); + let ws = null, wantConnected = false, sigKey = null, sigReconnect = null; function send(obj){ if (ws && ws.readyState===1) ws.send(JSON.stringify(obj)); } @@ -2311,6 +2328,7 @@ async function joinSpace(){ wantConnected = true; $('btn-enter').disabled = true; setStatus('connecting…'); + roomMachines.call.send('ENTER', { code, handle: myHandle }); openSignal(); } @@ -2344,6 +2362,7 @@ async function handleSignal(raw){ case 'welcome': myUUID = m.your_uuid; myRole = m.role; roomEpoch = m.epoch; applyState(m.state); + roomMachines.call.send('WELCOME', { uuid: myUUID, role: myRole }); /* any tile auto-spotlit before welcome (during fast-path subscribe) * needs to be broadcast now so the room sees our viewing state */ spotlights.set(myUUID, spotlightKey()); @@ -2472,6 +2491,7 @@ async function handleSignal(raw){ if (m.role === 'host') hostUUID = m.uuid; if (m.uuid === myUUID){ myRole = m.role; + roomMachines.call.send('ROLE_CHANGE', { role: m.role }); logLine('', 'you are now '+m.role); setStatus('connected as '+myRole, 'ok'); const byMod = members.get(m.by); @@ -3019,6 +3039,7 @@ function handleBlocked(source){ if (blocked) return; blocked = true; wantConnected = false; + roomMachines.call.send('BOOTED', { by: source }); /* booted = clear auto-rejoin so a refresh doesn't immediately retry */ try { sessionStorage.removeItem(ACTIVE_CALL_KEY); } catch(_){} try { sessionStorage.removeItem(ACTIVE_CAM_KEY); } catch(_){} @@ -3160,6 +3181,7 @@ $('game-tabs').addEventListener('click', (ev) => { }); $('btn-leave').addEventListener('click', async () => { wantConnected = false; + roomMachines.call.send('LEAVE'); /* explicit leave wipes the auto-rejoin state — user said 'out' */ try { sessionStorage.removeItem(ACTIVE_CALL_KEY); } catch(_){} try { sessionStorage.removeItem(ACTIVE_CAM_KEY); } catch(_){} @@ -3179,6 +3201,7 @@ $('btn-leave').addEventListener('click', async () => { $('sec-screen-share').classList.add('hidden'); $('sec-spotlight').classList.add('hidden'); spotlight = null; + roomMachines.call.send('DONE'); hideNotice(); $('dot-call').className='dot warn'; setStatus('left', null); @@ -3195,8 +3218,8 @@ logLine('', 'ready — pick a handle, type a rendezvous code, enter the space');