Web player: survive the suspended-account card destroying the status element

The suspended branch replaces the whole status overlay with its own markup, and that markup does not
contain #statusText. showStatus then did:

    document.getElementById('statusText').textContent = msg;

so every later call threw a TypeError for the life of the page. The consequences got worse the
further down they went:

- Each refresh beat re-emits device:paired, whose handler calls showStatus('Waiting for content...')
  — so the player raised an uncaught error and sent itself a "crashed" exit beacon every few minutes
  while suspended. This is very likely the "Cannot set properties of null (setting 'textContent')"
  the comment near the exit-signal contract says could never be traced.
- showNothingScheduled() calls showStatus BEFORE arming its 30-second re-check. So once the account
  was restored, a playlist whose dayparts had all closed left the screen on the stale orange
  "Account Suspended / Please upgrade your plan" card with no retry timer at all — it never
  re-checked the schedule and never recovered without a reload.

showStatus now rebuilds the element if it is missing rather than bailing, so the message the caller
asked for is actually displayed and the recovery path continues.

Verified in headless Chrome against the real player: destroy the overlay exactly as the suspended
branch does, then call showStatus — no throw, no uncaught page error, and "Waiting for content..."
on screen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uaeo9MvzKoyXuN6ZsbhtkL
This commit is contained in:
Claude 2026-07-30 21:08:06 -05:00
parent 64a6bfd860
commit fb8cafc444

View file

@ -3282,8 +3282,24 @@
// ==================== UI Helpers ==================== // ==================== UI Helpers ====================
function showStatus(msg) { function showStatus(msg) {
document.getElementById('statusOverlay').style.display = 'flex'; const overlay = document.getElementById('statusOverlay');
document.getElementById('statusText').textContent = msg; if (!overlay) return;
overlay.style.display = 'flex';
// #statusText can be GONE: the suspended-account branch replaces the whole overlay with its
// own markup, which does not contain it. Reading .textContent off null then threw a TypeError
// out of every later showStatus call — the player reported itself "crashed" on each refresh
// beat, and worse, showNothingScheduled() throws BEFORE arming its 30s re-check, so a screen
// whose dayparts had all closed was stranded on the stale suspended card with no retry.
// Rebuild the element rather than bail, so the message the caller wanted is actually shown.
let text = document.getElementById('statusText');
if (!text) {
overlay.innerHTML = '';
text = document.createElement('p');
text.id = 'statusText';
text.style.cssText = 'color:#94a3b8;font-size:20px;font-family:sans-serif;text-align:center';
overlay.appendChild(text);
}
text.textContent = msg;
} }
function hideStatus() { function hideStatus() {