From 3cc7da83e0d06f5ac016079d5dabdb9c61560e82 Mon Sep 17 00:00:00 2001 From: Mark Boas Date: Tue, 26 May 2026 09:52:00 +0200 Subject: [PATCH] Strip detached words from wordArr before each library poll MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the editor deletes spans, the library's cached wordArr still references the now-detached nodes until the debounced refresh rebuilds it. If the library's setTimeout poll fires inside that window, updateTranscriptVisualState dereferences word.n.parentNode.classList on a node whose parentNode is null and the rejection kills the polling chain — the karaoke highlight freezes for the rest of the session. Monkeypatch updateTranscriptVisualState on the HyperaudioLite instance to filter wordArr to live entries before delegating to the original. The library only uses wordArr via a fresh binary search per call, so a temporarily shrunken array is consistent. The next debounced refreshHyperaudioInstance rebuilds wordArr fully from the live DOM. Fixes #294. --- index.html | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/index.html b/index.html index 1406b73..1ca7d60 100644 --- a/index.html +++ b/index.html @@ -743,6 +743,22 @@

Caption Regeneration

const hyperaudioInstance = new HyperaudioLite("hypertranscript", "hyperplayer", minimizedMode, autoScroll, doubleClick, webMonetization, playOnClick); + // Patch for #294: if the library's polling chain runs while wordArr + // still references a span deleted by an in-progress edit, the original + // throws on word.n.parentNode.classList and the chain dies silently. + // Strip detached entries before delegating — the next debounced + // refreshHyperaudioInstance will rebuild wordArr from the live DOM. + const originalUpdateVisualState = hyperaudioInstance.updateTranscriptVisualState.bind(hyperaudioInstance); + hyperaudioInstance.updateTranscriptVisualState = function (currentTime) { + if (hyperaudioInstance.wordArr) { + const live = hyperaudioInstance.wordArr.filter(w => w.n && w.n.parentNode); + if (live.length !== hyperaudioInstance.wordArr.length) { + hyperaudioInstance.wordArr = live; + } + } + return originalUpdateVisualState(currentTime); + }; + window.hyperaudioInstance = hyperaudioInstance; const sanitisationCheck = function () {