3 Commits
Author SHA1 Message Date
webtecnica 6dd1fed8d3 fix(ui): guard all bare _autoScrollFollow reads (#6614) 2026-08-01 17:52:30 -03:00
nesquena-hermes f7ec3c7b39 test(#5217): scope follow-intent ordering assertion to _handleStreamError + strip EOF blank line (Codex gate nits) 2026-06-30 23:18:00 +00:00
allenliang2022 b117c5aa61 fix(webui): keep a following reader pinned through streaming growth and SSE recovery
Two related 'jump back' classes where a reader who is following the live stream
gets stranded mid-transcript, both verified live (OLD stranded the viewport
470-580px from bottom; FIX lands at 1px).

1. Content-grew-beneath-a-pinned-viewport (static/ui.js scroll listener).
   While streaming on a tall transcript (especially on mobile, where chunks land
   fast), new content increases scrollHeight under a stationary viewport. The
   reader never scrolled (top did not move up, _messageUserUnpinned is false), but
   bottomDistance crosses the nearBottom threshold, so the code fell through to
   _scrollPinned=false, killing auto-follow mid-stream. The follow writer and the
   scroll listener then fought frame-by-frame; the viewport stalled while content
   kept growing and was progressively stranded. Fix: in the !_messageUserUnpinned
   branch, when the viewport did NOT move up and auto-follow is on, keep the pin
   and re-snap to the true bottom via _setMessageScrollToBottom() instead of
   unpinning. The genuine scroll-away fallthrough is unchanged.

2. SSE-recovery follow-restore (static/messages.js). _handleStreamError (SSE
   drop), the Task-cancelled apply + fallback paths, and the reconnect-stream-dead
   cleanup all push/replace S.messages then renderMessages({preserveScroll:true}).
   preserveScroll's restore path keys on the pre-render snapshot's bottom-distance,
   which during a live stream can read large (content grew under a followed
   viewport), so it yanked a following reader up to a stale historical position on
   a process restart / SSE drop / cancel. Fix: capture follow-intent
   (_isMessagePaneNearBottom) BEFORE mutating S.messages, and after the recovery
   render, scrollToBottom() if the reader was following — so they see the
   interruption/cancellation notice in place. Readers who scrolled up to read
   history are left where they were.

Structural source-lock tests in tests/test_sse_recovery_scroll_stranding.py;
behavioral A/B verified via Playwright on a local source build.
2026-06-30 10:16:06 +08:00