From 0520971ea64e490d86c5969b078c4512ded9c5dc Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Tue, 2 Jun 2026 11:05:53 -0400 Subject: [PATCH] =?UTF-8?q?zebra-report:=20RemoteTileFSM=20=E2=80=94=20mir?= =?UTF-8?q?ror?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- zebra-report/zebra-spaces.html | 70 +++++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/zebra-report/zebra-spaces.html b/zebra-report/zebra-spaces.html index f9a568b..c455da8 100644 --- a/zebra-report/zebra-spaces.html +++ b/zebra-report/zebra-spaces.html @@ -905,6 +905,72 @@ const subscribeSpec = { }, }; +/* ================================================================== + * RemoteTileFSM — one instance per incoming screen / camera track from + * a remote publisher. Formalises the lifecycle that f71e9e7 patched + * imperatively (frozen-thumb after unshare): + * + * inactive ──TRACK_ARRIVED──▶ receiving ──MUTED──▶ muted + * ▲ │ + * │ UNMUTED │ PRUNE / ENDED + * └────────────────────┤ + * ▼ + * removed + * + * {receiving, muted} + ENDED → removed + * * + LEFT → removed (peer-left wipes the tile from every state) + * + * MUTED is a debounce gate, not a deletion: if UNMUTED arrives within + * the runtime's debounce window (~1.5s) we stay receiving — the mute + * was a transient network blip. If PRUNE fires (debounce expired and + * still muted) the publisher really unshared and the tile dies. + * ENDED skips the debounce. removed is terminal — a new tile gets + * a fresh FSM. */ +const remoteTileSpec = { + initial: 'inactive', + context: { kind: '', pubHex: '', stream: null, lastError: null }, + states: { + inactive: { + on: { + TRACK_ARRIVED: { + target: 'receiving', + action: (ctx, ev) => { if (ev.payload) ctx.stream = ev.payload.stream || ctx.stream; }, + }, + LEFT: 'removed', + }, + }, + receiving: { + on: { + MUTED: 'muted', + ENDED: 'removed', + LEFT: 'removed', + /* a fresh ontrack for the same pubHex+kind — publisher re-shared + * before our prune fired; keep the new stream and stay receiving */ + TRACK_ARRIVED: { + target: 'receiving', + action: (ctx, ev) => { if (ev.payload && ev.payload.stream) ctx.stream = ev.payload.stream; }, + }, + }, + }, + muted: { + on: { + UNMUTED: 'receiving', + PRUNE: 'removed', + ENDED: 'removed', + LEFT: 'removed', + TRACK_ARRIVED: { + target: 'receiving', + action: (ctx, ev) => { if (ev.payload && ev.payload.stream) ctx.stream = ev.payload.stream; }, + }, + }, + }, + removed: { + entry: (ctx) => { ctx.stream = null; }, + /* terminal — no outbound transitions */ + }, + }, +}; + /* ================================================================== * identity — ed25519 keypair, persisted in localStorage as JWK. * @@ -2973,8 +3039,8 @@ logLine('', 'ready — pick a handle, type a rendezvous code, enter the space');