zebra-report/test
Russell Ballestrini 5e8cdadb29
zebra-spaces: send 'bye' on pagehide for listeners even on bfcache (kill closed-firefox roster zombies)
Fox 2026-06-06: "closed firefox on both phones [they] are both still
in the list" — listener-roster entries persisting indefinitely after
mobile Firefox close.

Two facts collided:

1. SERVER (proxy.unturf.com main.go aliveJanitorTick ~line 1872):
   listeners are fully exempt from the heartbeat-stall reaper.
   Justified fox 2026-06-04 because the page's {type:"alive"}
   timer throttles hard on backgrounded mobile tabs (1Hz on
   Android, paused on iOS power-save) — without the exemption,
   mobile listeners lost their seat every time they tab-switched.

2. CLIENT (this file, sendByeIfRealClose): pagehide with
   event.persisted=true means the page is going into bfcache
   (mobile app-switch / tab-close-to-bfcache / lock screen),
   so we SKIPPED the 'bye' message to keep PCs warm for resume.
   Justified fox 2026-06-03 because "the phone leaving and
   coming back cannot hear the music" — bye-driven SFU eviction
   killed the speaker's publish + subscribe PCs.

Net: a mobile Firefox close fires pagehide(persisted=true) →
no 'bye' → server has only readTimeout (120s) + hiccup grace
(8s) to detect the dead socket → ~128s of phantom listener
seat in every other client's roster.

The bfcache justification on the CLIENT side was always
specific to speakers (they have PCs to protect). LISTENERS:

  - have no publisher PC
  - finalizeLeave at main.go:1089 explicitly exempts them from
    evictFromSFU (their subscriber PC stays alive through the
    bye)
  - re-handshake fresh on pageshow via POST /subscribe (same
    path as a cold join)

So for listeners, sending 'bye' on persisted=true is
roster-only cleanup: peer-left broadcast, members.delete(uuid)
on every other client, brief disappearance from the room. On
pageshow they re-handshake and reappear — same UX as a cold
rejoin, which already works.

Fix: split the bfcache rule by role. Send 'bye' on pagehide
even when persisted=true if myRole === 'listener'. Speakers
keep the original bfcache skip exactly.

Server-side backstop (60s listener-specific TTL replacing the
full exemption) ships as a separate commit in proxy.unturf.com
so 'bye' losses (carrier NAT eating the TCP shutdown, abrupt
process kill, custom Firefox close paths) still get reaped
within the minute.

Pinned by test/sendbye-fsm.test.js — extracts
sendByeIfRealClose from this page and drives 9 scenarios
covering each (role, persisted) combination plus defensive
edges (no event, ws not open, post-demote listener state).
Wired into test-all via test-sendbye target.
2026-06-06 14:45:39 -04:00
..
functional.c phase 1: unfirehose reconstruction from session JSONL ingest 2026-05-27 13:51:14 -04:00
integration.c phase 1: unfirehose reconstruction from session JSONL ingest 2026-05-27 13:51:14 -04:00
listener-audio-attach.test.js zebra-spaces: prefix-match in flushSfuStreams (kill silent fedora-chrome listener) 2026-06-06 14:37:18 -04:00
mod-action-serializer.test.js test: pin the mod-action serializer race fix 2026-06-04 10:46:55 -04:00
multi-peer-mesh.test.js zebra-spaces: rebuild <video> on MSID supplant — autoplay grant resets 2026-06-03 16:08:01 -04:00
self-listener-fsm.test.js zebra-spaces: formalize self-listener as FSM — pure spec + observer-driven side effects + 12 unit tests 2026-06-04 13:10:29 -04:00
sendbye-fsm.test.js zebra-spaces: send 'bye' on pagehide for listeners even on bfcache (kill closed-firefox roster zombies) 2026-06-06 14:45:39 -04:00
test.h phase 1: unfirehose reconstruction from session JSONL ingest 2026-05-27 13:51:14 -04:00
unit.c phase 1: unfirehose reconstruction from session JSONL ingest 2026-05-27 13:51:14 -04:00
video-track-removal.test.js zebra-spaces: 120s mute window for screen + game (static content), 15s stays for camera 2026-06-03 10:33:28 -04:00
web-protocol.test.js test: node web protocol suite (no devices) + exponential retransmit backoff 2026-05-28 12:44:20 -04:00
zebra-fsm.test.js test/zebra-fsm: MuteFSM transition tests — 14 new cases (102 total) 2026-06-04 14:14:52 -04:00
zebra-spaces.test.js zebra-spaces: JS↔Go protocol parity + crypto tests 2026-05-31 11:06:59 -04:00