zebra-report/web
Russell Ballestrini c493626b69
zebra-spaces: fix lip-sync flap (median + stale-source filter) + transcript autoscroll
Fox 2026-06-05: "the lips are not synced with video for listener" and
"when i am at the bottom and new transcriptions flow in, keep
scrolling as they arrive unless ive scrolled up."

Lip-sync flap diagnosis (per fxhp-phone telemetry):
  lip-sync pub=25cc delay=2.87s
  lip-sync pub=25cc delay=6.87s
  lip-sync pub=25cc delay=2.87s   (every ~683ms)
  lip-sync pub=25cc delay=6.87s
Two values exactly 4s apart (4s = worklet target). One source is
LIVE (worklet buffer near target → 6.87s = 2.87 native + 4 worklet),
the other is a STALE worklet whose source died (buffer drained to
0 → 2.87s = 2.87 native + 0 worklet). Both post bufferedSeconds
every 683ms. With LIP_SYNC_HISTORY = 5 odd-length, alternating
samples produce alternating medians: [A,B,A,B,A] → A;
[B,A,B,A,B] → B. Video target whipsaws ±4s per tick.

Three fixes:

1. LIP_SYNC_HISTORY = 10 (was 5). Even-length window.

2. medianOf returns the AVERAGE of the two middle values for
   even-length arrays. Alternating samples now produce a stable
   median = (A+B)/2 — at least the value doesn't whip.

3. Stale-source filter in refreshLipSyncForUuid: if
   node.bufferedSeconds < 0.5 AND nativeJbufSec > 1.0, skip this
   refresh entirely. A worklet whose buffer is drained while the
   native audio receiver still reports a healthy jitter buffer is
   from an OLD subscription that's no longer carrying audio. Its
   "0" reading would pull the median toward the dead value.
   Only the LIVE worklet's refreshes update the history; the median
   stabilizes on the actual audio total delay.

Autoscroll fix in appendTranscriptLine:

The previous logic computed "near bottom" AFTER appending the line.
scrollHeight grew the instant the line was in the DOM, so
(scrollTop + clientHeight) became < (scrollHeight - 40) for every
append — autoscroll never fired. Now we check BEFORE appending and
widen the tolerance to 120px (covers touch-scroll inertia residue).
2026-06-05 14:04:25 -04:00
..
blog phase 1: unfirehose reconstruction from session JSONL ingest 2026-05-27 13:51:14 -04:00
fonts phase 2: gap analysis — cat>> append + binary asset recovery 2026-05-27 13:54:19 -04:00
chat.html zebra-spaces: listener mute button doubles as audio-unlock gesture 2026-06-03 20:57:09 -04:00
host-your-own.html zebra-spaces: listener mute button doubles as audio-unlock gesture 2026-06-03 20:57:09 -04:00
how-it-works.html zebra-spaces: listener mute button doubles as audio-unlock gesture 2026-06-03 20:57:09 -04:00
index.html web: bump mobile body padding from 1rem to 1.25rem so the right edge has visible breathing room 2026-05-31 14:13:25 -04:00
kernel.html web: bump mobile body padding from 1rem to 1.25rem so the right edge has visible breathing room 2026-05-31 14:13:25 -04:00
stamp.js web: stamp each zebra-report page with build date + md5/sha256 2026-05-28 17:50:12 -04:00
zebra-audio.html zebra-spaces: listener mute button doubles as audio-unlock gesture 2026-06-03 20:57:09 -04:00
zebra-spaces.html zebra-spaces: fix lip-sync flap (median + stale-source filter) + transcript autoscroll 2026-06-05 14:04:25 -04:00