Asymmetric recovery between media publishers fixed. Mic + camera use
watchPublishPC for auto-rebuild on PC 'failed' — getUserMedia
constraints don't need a gesture once permission is granted, so the
client just creates a fresh PC and re-publishes. Screen share has
been the odd one out: getDisplayMedia requires a fresh user gesture,
so the previous policy was to tear everything down on PC 'failed' +
show a "tap share screen to re-share" log line.
But — the browser's "you are sharing" indicator typically stays on
across a transient network glitch; the MediaStream tracks remain in
readyState='live'. We don't need a fresh getDisplayMedia call; just
a fresh PC bound to the same stream. New sfuRebuildScreenPC() does
exactly that: tears down the dead PC (nulls sfuScreenPC first to
avoid recursive teardown via onconnectionstatechange), unpublishes
the old peer_id at the SFU, builds a fresh PC against the existing
sfuScreenStream, publishes, re-attaches the same recovery handler.
Asymmetry collapses: screen now auto-rebuilds silently IF the source
is still alive, and only falls back to the manual "share screen"
prompt when the user has actually stopped sharing (browser-native
stop, source window closed, lid closed). Mirror of the sub-PC
self-heal we shipped in 09a4516 — closes the last asymmetry between
publishers (and the last UX wart fox 2026-06-04 flagged on rejoin).