From fb8cafc44408e6b5b7dd2e22ff8aa35c343dac9c Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 30 Jul 2026 21:08:06 -0500 Subject: [PATCH] Web player: survive the suspended-account card destroying the status element MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) Claude-Session: https://claude.ai/code/session_01Uaeo9MvzKoyXuN6ZsbhtkL --- server/player/index.html | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/server/player/index.html b/server/player/index.html index 23cf059..896f888 100644 --- a/server/player/index.html +++ b/server/player/index.html @@ -3282,8 +3282,24 @@ // ==================== UI Helpers ==================== function showStatus(msg) { - document.getElementById('statusOverlay').style.display = 'flex'; - document.getElementById('statusText').textContent = msg; + const overlay = document.getElementById('statusOverlay'); + 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() {