fix(host-control-mode): audit findings — host dedup demotion, episode-lobby freeze, +cleanups

Findings recovered from the multi-agent audit (verification phase was cut off by a
usage limit; verified inline against the code):

HIGH:
- Host was demoted on peerId dedup (fast reconnect / second tab): the dedup path
  removes the old socket and the kicked socket's 'disconnect' both ran the
  host-leave fallback before the same peerId re-joined → room silently unlocked on
  every host network blip. Now skip the fallback while a join for that peerId is in
  flight (peerJoinLocks). Regression test added. (A long real disconnect still
  falls back — that's the deferred host-grace EC-10.)
- Episode auto-advance froze a gated guest: in host-only the guest's EPISODE_LOBBY
  is dropped server-side, but the guest still self-paused (PAUSE_FOR_LOBBY) waiting
  for readies that never came → 60s freeze. A host-only guest now skips creating a
  lobby (the host drives episode sync).

LOW / cleanup:
- GET_HCM_STRINGS could return a raw key name ("HCM_DIALOG_TITLE") if the locale
  dictionary failed to load (getMessage returns the key on miss) → omit it so
  content keeps its English fallback.
- hcmReset now also clears the snap-back cooldown + buffering grace, so a stale
  cooldown can't swallow the first snap-back after a room/host change.
- Popup play/pause labels reset on lock too (not just unlock), so a button can't
  freeze on "Playing…" when host-only activates mid-click.
- Fix a stale comment (heartbeat now carries 'desynced').

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
KoalaDev
2026-06-28 02:25:11 +02:00
parent 02c41776b8
commit dfa0a3d03e
5 changed files with 59 additions and 19 deletions
+7 -9
View File
@@ -355,15 +355,13 @@ function setRemoteControlsLocked(locked) {
btn.style.cursor = locked ? 'not-allowed' : '';
btn.title = locked ? (getMessage('NOTICE_HOST_CONTROLS') || 'The host controls playback for everyone.') : '';
});
// When unlocking, also restore the default labels. The action handlers leave
// the text in a transitional state ("Playing..." / "Pausing...") and the 2.5s
// safety reset skips the refresh while we were guest-locked, so without this
// the button can be re-enabled with stale text after the host disables
// host-only (L-1).
if (!locked) {
if (elements.playBtn) elements.playBtn.textContent = getMessage('BTN_PLAY') || 'Play';
if (elements.pauseBtn) elements.pauseBtn.textContent = getMessage('BTN_PAUSE') || 'Pause';
}
// Always restore the default labels. The action handlers leave the text in a
// transitional state ("Playing..." / "Pausing...") and the 2.5s safety reset
// skips the refresh while guest-locked so reset on BOTH transitions: on lock
// (host enabled host-only just as the guest clicked → don't freeze "Playing...")
// and on unlock (L-1).
if (elements.playBtn) elements.playBtn.textContent = getMessage('BTN_PLAY') || 'Play';
if (elements.pauseBtn) elements.pauseBtn.textContent = getMessage('BTN_PAUSE') || 'Pause';
}
if (elements.hostControlToggle) {