Root cause of the 'late listener can't see screens' symptom: my recent
'rebuild sub PC on failure' commit (6a72e77) triggered on connection
state 'closed' as well as 'failed', AND the guard `if (sfuSubPC === pc)`
only worked because we ASSUMED Chrome would fire the state change
asynchronously. Chrome fires it SYNCHRONOUSLY during pc.close(), at
which point sfuSubPC still points at the closing pc — guard passes,
rebuild fires. Then sfuUnsubscribe closes the new PC, which triggers
another rebuild. SFU log showed listeners cycling
subscribe → 40s → close → subscribe forever.
Fix:
1. onconnectionstatechange now only rebuilds on 'failed' (the actually-
terminal state). 'closed' = sfuUnsubscribe(), 'disconnected' =
transient and WebRTC may recover on its own.
2. sfuUnsubscribe nulls sfuSubPC BEFORE pc.close(), so even if the
handler fired synchronously the === guard would correctly fail.
3. visibilitychange handler also tightened to only fire on 'failed' —
same reasoning.
The rebuild path for actual ICE failures still works (state goes
'connected' → 'disconnected' → 'failed' → rebuild).
83 FSM tests still green.