fix(host-control-mode): hold desync invariant + stop Solo-badge flicker

Two issues surfaced by a holistic audit of the combined desync-persist +
reconcile-on-init changes:

1. Stale desync outside gated-guest state. hcmDesynced is persisted, but neither
   ROOM_DATA nor CONTROL_MODE cleared it when the local peer became host or the
   room switched to 'everyone'. Combined with reconcile-on-init, a desynced guest
   promoted to host (host-leave fallback) while content was reloading could
   re-adopt desynced=true — self-labelling "Solo" to all peers and ignoring host
   commands in 'everyone' mode. Enforce the invariant centrally
   (hcmEnforceDesyncInvariant: desynced ⟹ host-only && !host) on every role/mode
   change, and guard the content reconcile to only adopt when actually a gated guest.

2. Solo-badge flicker. The host-side PEER_STATUS handler set peer.desynced
   unconditionally, but the background-driven keepAlive heartbeat omits the field —
   so every ~30s it clobbered desynced to false, flickering the badge. Only update
   when present (matching the sibling fields), and include desynced in the
   background heartbeat so both heartbeat sources are consistent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
KoalaDev
2026-06-26 16:06:04 +02:00
parent 96e2207cf3
commit 95ff460856
2 changed files with 23 additions and 3 deletions
+3 -1
View File
@@ -1440,7 +1440,9 @@
hcmHostPeerId = res.hostPeerId || null;
// Re-adopt persisted desync after a page reload so we don't start synced
// while background still relays us as "Solo" to the host (split-brain).
if (res.desynced && !hcmDesynced) {
// Only when we're actually a gated guest — never adopt a stale flag as the
// host or in 'everyone' mode (would self-label "Solo" / ignore commands).
if (res.desynced && res.controlMode === 'host-only' && !res.amHost && !hcmDesynced) {
hcmDesynced = true;
hcmShowBadge();
}