fix(stream): preserve reader viewport anchor through live-to-settlement collapse
Issue #6385: when a streaming turn settles, the two-render sequence (keep-open expanded worklog → collapsed worklog) could displace the reader's viewport because the second render captured its scroll snapshot from the intermediate expanded state, not from the original live DOM. Root cause ---------- The STREAM_DONE handler in messages.js: 1. Arms keep-settled-worklog-open token → renderMessages({preserveScroll:true}) → worklog rendered EXPANDED (height-stable swap preventing shrink jump) 2. Disarms token → _renderMessagesWithScrollSnapshot() → This function called _captureMessageScrollSnapshot() which captured the scroll anchor from the expanded-worklog DOM (step 1 output), then called renderMessages with the worklog COLLAPSED (keep-open gone), then tried to restore from the expanded-state snapshot. The snapshot's semantic anchor (row key, session idx, top offset) was captured from a DOM where the worklog was expanded. After the collapse render the worklog is no longer at that position — anchor keys don't match, the semantic restore fails, and the viewport jumps to a unrelated scrollTop. Fix --- - Capture the scroll snapshot from the LIVE DOM (before any settlement renders) and pass it as to the second render. - Modify _renderMessagesWithScrollSnapshot() to accept a pre-captured snapshot via options._prescrollSnapshot, using it instead of capturing a fresh one from the current DOM state. This ensures the collapse render anchors to the content the reader was actually viewing, not to a stale intermediate expanded-worklog position. Co-authored-by: webtecnica <webtecnica@users.noreply.github.com>
This commit is contained in:
1 parent
c0d9d81512
commit
25d39da00e
2 files changed
+15
-2
No files matched your search
+8
-1
@@ -5941,10 +5941,17 @@ function attachLiveStream(activeSid, streamId, uploaded=[], options={}){
|
||||
// render also populates the cache with the correctly-collapsed DOM, and
|
||||
// the same-frame JS restore absorbs the collapse so there is no jump.
|
||||
// (#5260 gate-cert: keep-open must be transient + uncached for everyone.)
|
||||
// #6385: capture the scroll snapshot from the LIVE DOM before arming
|
||||
// keep-open, so the collapse render below anchors to the content the
|
||||
// reader was actually viewing — not to a stale intermediate state where
|
||||
// the worklog was temporarily expanded.
|
||||
const _doneLiveScrollSnapshot=typeof _captureMessageScrollSnapshot==='function'
|
||||
? _captureMessageScrollSnapshot()
|
||||
: null;
|
||||
if(typeof _armKeepSettledWorklogOpen==='function') _armKeepSettledWorklogOpen(_settledStreamId);
|
||||
syncTopbar();renderMessages({preserveScroll:true});
|
||||
if(typeof _disarmKeepSettledWorklogOpen==='function') _disarmKeepSettledWorklogOpen();
|
||||
if(typeof _renderMessagesWithScrollSnapshot==='function') _renderMessagesWithScrollSnapshot();
|
||||
if(typeof _renderMessagesWithScrollSnapshot==='function') _renderMessagesWithScrollSnapshot({_prescrollSnapshot:_doneLiveScrollSnapshot});
|
||||
else renderMessages({preserveScroll:true});
|
||||
if(shouldFollowOnDone&&typeof scrollToBottom==='function') scrollToBottom();
|
||||
if(typeof noteWorkspaceMutationsFromToolCalls==='function') noteWorkspaceMutationsFromToolCalls(S.toolCalls);
|
||||
|
||||
+7
-1
@@ -15086,7 +15086,13 @@ function _restoreMessageScrollSnapshotSameFrame(snapshot){
|
||||
}
|
||||
}
|
||||
function _renderMessagesWithScrollSnapshot(options){
|
||||
const scrollSnapshot=_captureMessageScrollSnapshot();
|
||||
// Accept an optional pre-captured scroll snapshot via _prescrollSnapshot.
|
||||
// When provided, it is used INSTEAD of capturing a fresh one from the current
|
||||
// DOM state — essential for the STREAM_DONE collapse render: the caller has
|
||||
// already captured the snapshot from the LIVE DOM (before keep-open was armed),
|
||||
// and re-capturing from the intermediate expanded-worklog state would capture
|
||||
// stale anchors that no longer exist after the worklog collapses. (#6385)
|
||||
const scrollSnapshot=(options&&options._prescrollSnapshot)||_captureMessageScrollSnapshot();
|
||||
renderMessages({...(options||{}),preserveScroll:true});
|
||||
_restoreMessageScrollSnapshotSameFrame(scrollSnapshot);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user