fix(#4006): gate the #3920 RO settle + render fall-through on _autoScrollFollow (Codex cross-PR catch)

Codex found that with Auto-follow OFF the #3920 ResizeObserver settle + 2s fallback
+ the non-preserve render path still force-scrolled to bottom (the setting was
incomplete vs the scroll machinery shipped in #3920). Fixes:
1. RO settle guard (ui.js:3153): bail when !_autoScrollFollow.
2. 2s static fallback guard (ui.js:3186): bail when !_autoScrollFollow.
3. non-preserve render fall-through (ui.js:8653): when Auto-follow off AND the user
   has unpinned, restore the scroll snapshot instead of scrollToBottom() — also
   closes the send() race (renderMessages runs before S.activeStreamId is set).
   A fresh session load (not unpinned) still lands at the bottom. Explicit
   user-initiated scrollToBottom() (End button) is untouched.
This commit is contained in:
nesquena-hermes
2026-06-13 04:09:58 +00:00
parent 00ea7766db
commit 2bdef4166f

View File

@@ -3150,7 +3150,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(!_scrollPinned||_messageUserUnpinned||_recentNonMessageScrollIntent()){
if(!_autoScrollFollow||!_scrollPinned||_messageUserUnpinned||_recentNonMessageScrollIntent()){
ro.disconnect(); if(_settleRO===ro) _settleRO=null;
_programmaticScroll=false;
return;
@@ -3183,7 +3183,7 @@ function _settleMessageScrollToBottom(force){
_settleFinalTimer=setTimeout(()=>{
if(token!==_bottomSettleToken) return;
ro.disconnect(); if(_settleRO===ro) _settleRO=null;
if(!_scrollPinned||_messageUserUnpinned||_recentNonMessageScrollIntent()){ _programmaticScroll=false; return; }
if(!_autoScrollFollow||!_scrollPinned||_messageUserUnpinned||_recentNonMessageScrollIntent()){ _programmaticScroll=false; return; }
_settleFinalScroll(token);
},2000);
}
@@ -8650,6 +8650,15 @@ function _scrollAfterMessageRender(preserveScroll, scrollSnapshot){
scrollIfPinned();
return;
}
// Auto-follow OFF + the user has scrolled up: don't yank them to the bottom on
// an automatic (non-preserve) re-render. This also covers the send() race where
// 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.)
if(!_autoScrollFollow && _messageUserUnpinned){
_restoreMessageScrollSnapshot(scrollSnapshot);
return;
}
scrollToBottom();
}