fix(#4006): explicit-settle flag + pre-wipe snapshot capture (Codex r3)
1. _settleMessageScrollToBottom(force, explicit): when explicit (End-button / scrollToBottom), late-layout settling runs even with Auto-follow OFF — the setting only suppresses AUTOMATIC follow, not a deliberate jump. The RO + 2s fallback guards now bail on (!_autoScrollFollow && !explicit). scrollToBottom() passes explicit=true; the automatic scrollIfPinned() path stays guarded. 2. renderMessages now captures the pre-wipe scrollSnapshot when (Auto-follow off && user unpinned) too, so the non-preserve restore lands the reader where they were instead of receiving a null snapshot (no-op).
This commit is contained in:
20
static/ui.js
20
static/ui.js
@@ -3112,8 +3112,11 @@ function _followMessagesAfterDomReplace(){
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function _settleMessageScrollToBottom(force){
|
||||
// Markdown post-processing (Prism, tables, Mermaid/KaTeX/PDF placeholders)
|
||||
function _settleMessageScrollToBottom(force, explicit){
|
||||
// `explicit` = a user-invoked scroll-to-bottom (End button / scrollToBottom()).
|
||||
// When explicit, late-layout settling runs even if Auto-follow is OFF — the
|
||||
// setting only suppresses AUTOMATIC streaming follow, not a deliberate jump
|
||||
// to the bottom. (Codex #4006 r3.)
|
||||
// can grow the transcript after the first scroll write. Re-apply the bottom
|
||||
// position when content settles so late layout does not leave the viewport
|
||||
// above the real end. User scroll increments _bottomSettleToken and cancels.
|
||||
@@ -3150,7 +3153,7 @@ function _settleMessageScrollToBottom(force){
|
||||
// active one that may now be in the global _settleRO. (Codex review #3.)
|
||||
const ro=new ResizeObserver(()=>{
|
||||
if(token!==_bottomSettleToken){ ro.disconnect(); if(_settleRO===ro) _settleRO=null; return; }
|
||||
if(!_autoScrollFollow||!_scrollPinned||_messageUserUnpinned||_recentNonMessageScrollIntent()){
|
||||
if((!_autoScrollFollow&&!explicit)||!_scrollPinned||_messageUserUnpinned||_recentNonMessageScrollIntent()){
|
||||
ro.disconnect(); if(_settleRO===ro) _settleRO=null;
|
||||
_programmaticScroll=false;
|
||||
return;
|
||||
@@ -3183,7 +3186,7 @@ function _settleMessageScrollToBottom(force){
|
||||
_settleFinalTimer=setTimeout(()=>{
|
||||
if(token!==_bottomSettleToken) return;
|
||||
ro.disconnect(); if(_settleRO===ro) _settleRO=null;
|
||||
if(!_autoScrollFollow||!_scrollPinned||_messageUserUnpinned||_recentNonMessageScrollIntent()){ _programmaticScroll=false; return; }
|
||||
if((!_autoScrollFollow&&!explicit)||!_scrollPinned||_messageUserUnpinned||_recentNonMessageScrollIntent()){ _programmaticScroll=false; return; }
|
||||
_settleFinalScroll(token);
|
||||
},2000);
|
||||
}
|
||||
@@ -3219,7 +3222,7 @@ function scrollToBottom(){
|
||||
// was skipping the observer and causing Firefox paint jumps when
|
||||
// renderMessages({preserveScroll:true}) + scrollToBottom() fired back-to-back.
|
||||
_setMessageScrollToBottom();
|
||||
_settleMessageScrollToBottom(false);
|
||||
_settleMessageScrollToBottom(false, true);
|
||||
_syncScrollToBottomCue(false,{newMessage:false});
|
||||
if(typeof _updateSessionStartJumpButton==='function') _updateSessionStartJumpButton();
|
||||
}
|
||||
@@ -8655,6 +8658,8 @@ function _scrollAfterMessageRender(preserveScroll, scrollSnapshot){
|
||||
// renderMessages() runs before S.activeStreamId is set, so a stream-start
|
||||
// wouldn't otherwise be caught by the scrollIfPinned() branch above. A fresh
|
||||
// session load (not unpinned) still lands at the bottom as expected. (Codex #4006.)
|
||||
// renderMessages() captures the pre-wipe snapshot for this case too (see its
|
||||
// scrollSnapshot init), so restoring here lands the reader where they were.
|
||||
if(!_autoScrollFollow && _messageUserUnpinned){
|
||||
_restoreMessageScrollSnapshot(scrollSnapshot);
|
||||
return;
|
||||
@@ -8664,7 +8669,10 @@ function _scrollAfterMessageRender(preserveScroll, scrollSnapshot){
|
||||
|
||||
function renderMessages(options){
|
||||
const preserveScroll=!!(options&&options.preserveScroll);
|
||||
const scrollSnapshot=preserveScroll?_captureMessageScrollSnapshot():null;
|
||||
// Capture the pre-wipe scroll position when preserving OR when Auto-follow is
|
||||
// off and the user has scrolled up — both need to restore the reader's position
|
||||
// after the DOM rebuild rather than snap to the bottom. (Codex #4006 r3.)
|
||||
const scrollSnapshot=(preserveScroll||(!_autoScrollFollow&&_messageUserUnpinned))?_captureMessageScrollSnapshot():null;
|
||||
const inner=$('msgInner');
|
||||
const sid=S.session?S.session.session_id:null;
|
||||
const msgCount=S.messages.length;
|
||||
|
||||
Reference in New Issue
Block a user