From a64f429349c3cd3ea10cfc3e761ff6acb41e38f8 Mon Sep 17 00:00:00 2001 From: you Date: Thu, 19 Mar 2026 07:07:26 +0000 Subject: [PATCH] Fix VCR scrubber rubber-band: hold dragPct during async fetch Root cause traced: mouseup sets VCR.dragging=false and starts async fetch. Between fetch start and response (~50-200ms), the 30s interval fires updateTimelinePlayhead() which found no matching branch for REPLAY+old playhead, defaulting to x=cw (right edge snap). Fix: dragPct now takes priority over buffer-based position during REPLAY/PAUSED modes. Cleared only when fetch completes and replay actually starts with real buffer data. --- public/live.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/public/live.js b/public/live.js index 47a0936f..041a0503 100644 --- a/public/live.js +++ b/public/live.js @@ -346,14 +346,14 @@ // Redraw sparkline (cheap, avoids double-buffer complexity) // Just draw playhead line on top — clear only the line area let x; - if (VCR.mode === 'LIVE' || VCR.playhead < 0) { + if (VCR.mode === 'LIVE') { x = cw; // rightmost = now + } else if (VCR.dragPct != null && (VCR.mode === 'REPLAY' || VCR.mode === 'PAUSED')) { + // After scrub: hold at drag position until replay tick updates it + x = VCR.dragPct * cw; } else if (VCR.playhead >= 0 && VCR.playhead < VCR.buffer.length) { const playTs = VCR.buffer[VCR.playhead].ts; x = ((playTs - startTs) / scopeMs) * cw; - } else if (VCR.mode === 'PAUSED' && VCR.dragPct != null) { - // After scrub, hold at last drag position - x = VCR.dragPct * cw; } else { x = cw; } @@ -618,6 +618,7 @@ VCR.playhead = closest; // Only replay ~50 packets from scrub point, not entire buffer to end VCR.scrubEnd = Math.min(closest + 50, VCR.buffer.length); + VCR.dragPct = null; // let replay tick drive playhead now startReplay(); }) .catch(() => {});