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');