Four corrections in one pass — all from fox's live-room session
2026-06-03 after the kick/ban + heartbeat ship:
1. Stream button visibility was too generous. A speaker viewing
the host's row got a stream button — they shouldn't. New rule:
SELF row always (self-monitor), OTHER rows only when myRole
=== 'host'. Cohost gets self-only too; lift the gate to
isMod(myRole) if room-wide cohost stream control is wanted.
2. .acts-removal's grid-column:1/-1 collapsed the parent auto-fit
grid down to a single column on a row with both primary +
removal actions, so EVERY button stacked vertically. Refactor:
.mod-actions is now a row-stack of sibling groups
(.acts-primary + .acts-removal), each its own horizontal auto-
fit grid. Primary still wraps inside itself when the panel is
narrow; kick + ban are forced to a fresh line by the parent
grid-auto-rows.
3. playoutDelayHint bumped 0.7 → 2.0 (RECV_PLAYOUT_DELAY_SEC) on
both mesh + SFU receive paths. Conversational latency goes up
but the stream/HTTP-pull DJ mode already commits to ~2s, so
matching the WebRTC path makes the room consistent. Persistent
chop survived every SDP-side dial-back fox tried; this is the
last knob left at the receiver.
4. pagehide-bye now skips bfcache (event.persisted=true). Without
this guard, a phone going to lock screen / app-switch /
minimise fired bye → server evicted SFU PCs → page resumed but
audio stayed silent. Aliveness of a bfcached page is already
handled by the server-side aliveTTL (heartbeat stops during
bfcache → server arms hiccup grace at 45s).