Summary
(As of v2.6.2.) Two vectors, one mechanism — the binary search's behavior on NaN.
1. Click/double-click on non-word content
setPlayHead computes the time and updates visuals before its NaN guard (hyperaudio-lite.js:727-739):
const timeSecs = parseInt(target.dataset.m) / 1000;
this.updateTranscriptVisualState(timeSecs); // runs with NaN
...
if (!isNaN(timeSecs)) { // guard comes too late
When the (dbl)click lands on a <p>/gap rather than a word span (routine with doubleClick: true on a contenteditable transcript), timeSecs is NaN. In the binary search (:969-985), NaN < 0 and NaN > 0 are both false, so the first midpoint probe takes the exact-match branch (index = guessIndex + 1; break): the read/unread boundary jumps to the middle of the transcript and prevWordIndex is poisoned. While paused (no ticks running) the corruption persists on screen until the next interaction.
2. Malformed data-m in the transcript
A single bad data-m (e.g. data-m="" from an importer; parseInt("") → NaN cached in createWordArray, :635) permanently poisons the search — any probe path touching that element breaks at an arbitrary index, on every tick.
Fix
Early-return in setPlayHead when isNaN(timeSecs) before calling updateTranscriptVisualState; skip/drop entries with isNaN(m) in createWordArray (or guard the exact-match branch with difference === 0).
Found reviewing the copy vendored in hyperaudio-lite-editor / GliderMac (2026-07-11).
Summary
(As of v2.6.2.) Two vectors, one mechanism — the binary search's behavior on NaN.
1. Click/double-click on non-word content
setPlayHeadcomputes the time and updates visuals before its NaN guard (hyperaudio-lite.js:727-739):When the (dbl)click lands on a
<p>/gap rather than a word span (routine withdoubleClick: trueon a contenteditable transcript),timeSecsis NaN. In the binary search (:969-985),NaN < 0andNaN > 0are both false, so the first midpoint probe takes the exact-match branch (index = guessIndex + 1; break): the read/unread boundary jumps to the middle of the transcript andprevWordIndexis poisoned. While paused (no ticks running) the corruption persists on screen until the next interaction.2. Malformed data-m in the transcript
A single bad
data-m(e.g.data-m=""from an importer;parseInt("") → NaNcached increateWordArray,:635) permanently poisons the search — any probe path touching that element breaks at an arbitrary index, on every tick.Fix
Early-return in
setPlayHeadwhenisNaN(timeSecs)before callingupdateTranscriptVisualState; skip/drop entries withisNaN(m)increateWordArray(or guard the exact-match branch withdifference === 0).Found reviewing the copy vendored in hyperaudio-lite-editor / GliderMac (2026-07-11).