zebra-report/web
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
..
blog phase 1: unfirehose reconstruction from session JSONL ingest 2026-05-27 13:51:14 -04:00
fonts phase 2: gap analysis — cat>> append + binary asset recovery 2026-05-27 13:54:19 -04:00
chat.html zebra-spaces: prefix-match in flushSfuStreams (kill silent fedora-chrome listener) 2026-06-06 14:37:18 -04:00
host-your-own.html zebra-spaces: prefix-match in flushSfuStreams (kill silent fedora-chrome listener) 2026-06-06 14:37:18 -04:00
how-it-works.html zebra-spaces: prefix-match in flushSfuStreams (kill silent fedora-chrome listener) 2026-06-06 14:37:18 -04:00
index.html web: bump mobile body padding from 1rem to 1.25rem so the right edge has visible breathing room 2026-05-31 14:13:25 -04:00
kernel.html web: bump mobile body padding from 1rem to 1.25rem so the right edge has visible breathing room 2026-05-31 14:13:25 -04:00
stamp.js web: stamp each zebra-report page with build date + md5/sha256 2026-05-28 17:50:12 -04:00
zebra-audio.html zebra-spaces: prefix-match in flushSfuStreams (kill silent fedora-chrome listener) 2026-06-06 14:37:18 -04:00
zebra-spaces.html zebra-spaces: send 'bye' on pagehide for listeners even on bfcache (kill closed-firefox roster zombies) 2026-06-06 14:45:39 -04:00