Release XR (v0.51.662): ignore DOM-wipe scroll clamp during render (#4934)
Release XR (v0.51.662): ignore DOM-wipe scroll clamp during render (#4934)
This commit is contained in:
commit
a569d6ba3b
3 changed files with 35 additions and 0 deletions
|
|
@ -3,6 +3,12 @@
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
## [v0.51.662] — 2026-06-25 — Release XR (live replies keep following during transcript rebuilds)
|
||||
|
||||
### Fixed
|
||||
|
||||
- **A streaming reply no longer stops following (or appears to jump backward) when the transcript rebuilds mid-stream.** `renderMessages` rebuilds the message pane by clearing it (`innerHTML=''`), and during that brief gap the scroll container can collapse and the browser clamps `scrollTop` to 0, emitting a scroll event. The scroll listener was interpreting that render artifact as a deliberate user scroll-up and unpinning the reader, so the live reply stopped auto-following. The DOM-wipe scroll is now marked programmatic and ignored; genuine user scroll-up (wheel/touch) during a render is still detected and still unpins. Thanks @allenliang2022. (#4934)
|
||||
|
||||
## [v0.51.661] — 2026-06-25 — Release XQ (no more fabricated multi-hour processing time)
|
||||
|
||||
### Fixed
|
||||
|
|
|
|||
|
|
@ -11316,6 +11316,13 @@ function renderMessages(options){
|
|||
// Mobile scroll-jank fix: temporarily disable overflow-anchor so Chromium
|
||||
// cannot re-anchor to the topmost row during the DOM wipe-and-rebuild gap.
|
||||
if(window._fixMobileScrollJank) window._fixMobileScrollJank();
|
||||
// The DOM wipe can briefly collapse #msgInner to zero height, causing the
|
||||
// browser to clamp #messages.scrollTop to 0 and emit a scroll event. That
|
||||
// event is a render artifact, not user intent; if the scroll listener sees it
|
||||
// with _programmaticScroll=false, it marks the reader manually unpinned and
|
||||
// the live reply stops following / appears to jump backward.
|
||||
_programmaticScroll=true;
|
||||
_programmaticScrollSetAt=performance.now();
|
||||
inner.innerHTML='';
|
||||
const compressionNode=compressionState?_compressionCardsNode(compressionState):null;
|
||||
const {message:referenceMessage, rawIdx:referenceMessageRawIdx}=_latestCompressionReferenceMessage(
|
||||
|
|
@ -12524,6 +12531,7 @@ function renderMessages(options){
|
|||
}
|
||||
_updateMessageVirtualMeasurements(renderVisWithIdx, renderVisibleIdxs, virtualWindow);
|
||||
_recycleStash.clear();
|
||||
if(typeof _deferClearProgrammaticScroll==='function') _deferClearProgrammaticScroll(160);
|
||||
}
|
||||
|
||||
function _toolDisplayName(tc){
|
||||
|
|
|
|||
|
|
@ -83,6 +83,27 @@ def test_rebuild_path_calls_fix_before_wipe():
|
|||
)
|
||||
|
||||
|
||||
def test_rebuild_path_marks_dom_wipe_scroll_as_programmatic():
|
||||
# During innerHTML='' the scroller can transiently collapse to clientHeight
|
||||
# and clamp scrollTop to 0. That browser event must be suppressed as
|
||||
# programmatic; otherwise the scroll listener treats it as user upward
|
||||
# intent and disables live auto-follow.
|
||||
fix_idx = UI_JS.find("window._fixMobileScrollJank()")
|
||||
assert fix_idx != -1, "renderMessages() guard call not found"
|
||||
wipe_idx = UI_JS.find("innerHTML=''", fix_idx)
|
||||
assert wipe_idx != -1, "innerHTML='' not found after _fixMobileScrollJank()"
|
||||
window = UI_JS[fix_idx:wipe_idx]
|
||||
assert "_programmaticScroll=true" in window, (
|
||||
"renderMessages() must mark the DOM wipe/rebuild scroll event as "
|
||||
"programmatic before innerHTML='' can clamp scrollTop."
|
||||
)
|
||||
assert "_programmaticScrollSetAt=performance.now()" in window
|
||||
assert UI_JS.find("_deferClearProgrammaticScroll(160)", wipe_idx) != -1, (
|
||||
"renderMessages() must clear the programmatic-scroll suppression after "
|
||||
"the rebuild/post-render paint window."
|
||||
)
|
||||
|
||||
|
||||
def test_streaming_tick_calls_fix_before_dom_writes():
|
||||
# The streaming render tick in messages.js must call _fixMobileScrollJank()
|
||||
# before _lastRenderMs=performance.now() so anchor suppression covers every
|
||||
|
|
|
|||
Loading…
Reference in a new issue