The dashboard tracked active calls purely by START/END events, so any START
without a matching END accumulated forever. Under heavy same-timeslot contention
this is expected: HBlink3's per-slot state turns over rapidly, and its stream
trimmer only emits an END for whichever stream currently owns the slot -- a
stream that was superseded never gets a matching END. Real RF calls also drop
mid-stream (loss of sync, hotspot reboot), with no terminator.
Add a stream reaper on both sides: the backend drops active streams >30s old
(STREAM_STALE), and the browser ages out ST.active entries on the same threshold
in its 1s tick. Normal calls still clear instantly on END; only phantoms are
swept. Found via the repeater simulator, which left 57 phantom "active" streams
after a 172-call run.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>