fix(#3920): apply Codex gate findings — observe #msgInner, instance-owned RO cleanup, restore .messages overflow-anchor
Codex SHIP-ONLY-WITH-FIXES (3 real issues; Codex+Opus disagreed on #1, stricter wins):
1. CORE — .messages lost overflow-anchor:none, reopening the #1360 (d21c9720) streaming-scroll-anchor regression. Restored it (native anchoring stays OFF; the RO does the settle). Opus had judged this removal acceptable; took Codex's stricter call.
2. SILENT — the ResizeObserver observed #messages (the scroll container, fixed by flex layout, never resizes) instead of #msgInner (.messages-inner, the growing transcript node) → callback would never fire. Now observes #msgInner.
3. SILENT — stale RO callbacks mutated the global _settleRO, which could disconnect a newer active observer after settle re-entry. Now instance-owned: close over const ro, only clear the global when _settleRO===ro.
This commit is contained in:
@@ -1588,7 +1588,7 @@
|
||||
.workspace-toggle-btn:disabled{opacity:.38;cursor:not-allowed;}
|
||||
.chip.model{color:var(--accent-text);border-color:var(--accent-bg-strong);background:var(--accent-bg);}
|
||||
.messages-shell{flex:1;min-height:0;position:relative;display:flex;flex-direction:column;}
|
||||
.messages{flex:1;overflow-y:auto;display:flex;flex-direction:column;min-height:0;position:relative;z-index:0;-webkit-overflow-scrolling:touch;touch-action:pan-y;overscroll-behavior-y:contain;}
|
||||
.messages{flex:1;overflow-y:auto;display:flex;flex-direction:column;min-height:0;position:relative;z-index:0;-webkit-overflow-scrolling:touch;touch-action:pan-y;overscroll-behavior-y:contain;overflow-anchor:none;}
|
||||
/* Overlay scroll controls so they do not affect the transcript's native scroll geometry. */
|
||||
.scroll-to-bottom-btn{position:absolute;right:20px;bottom:16px;width:32px;height:32px;border-radius:50%;border:1px solid var(--border2);background:var(--code-bg);color:var(--muted);font-size:16px;cursor:pointer;display:flex;align-items:center;justify-content:center;box-shadow:0 2px 8px rgba(0,0,0,.25);z-index:10;transition:color .12s,border-color .12s,background .12s,transform .12s;}
|
||||
.scroll-to-bottom-btn:hover{color:var(--text);border-color:var(--border);background:var(--hover-bg);}
|
||||
|
||||
21
static/ui.js
21
static/ui.js
@@ -3139,11 +3139,19 @@ function _settleMessageScrollToBottom(force){
|
||||
|
||||
const el=document.getElementById('messages');
|
||||
if(!el) return;
|
||||
// Observe the GROWING content node, not the scroll container. #messages is the
|
||||
// scroller but its box is fixed by the flex layout, so it never resizes — the
|
||||
// transcript grows inside #msgInner (.messages-inner). Observing #messages
|
||||
// would mean the callback never fires. (Codex review #2.)
|
||||
const observed=document.getElementById('msgInner')||el;
|
||||
|
||||
_settleRO=new ResizeObserver(()=>{
|
||||
if(token!==_bottomSettleToken){ if(_settleRO){ _settleRO.disconnect(); _settleRO=null; } return; }
|
||||
// Instance-owned cleanup: close over THIS observer so a stale callback (from a
|
||||
// superseded settle) only ever disconnects its own observer, never the newer
|
||||
// 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(_settleRO){ _settleRO.disconnect(); _settleRO=null; }
|
||||
ro.disconnect(); if(_settleRO===ro) _settleRO=null;
|
||||
_programmaticScroll=false;
|
||||
return;
|
||||
}
|
||||
@@ -3158,11 +3166,12 @@ function _settleMessageScrollToBottom(force){
|
||||
clearTimeout(_settleTimer);
|
||||
_settleTimer=setTimeout(()=>{
|
||||
if(token!==_bottomSettleToken) return;
|
||||
if(_settleRO){ _settleRO.disconnect(); _settleRO=null; }
|
||||
ro.disconnect(); if(_settleRO===ro) _settleRO=null;
|
||||
_setMessageScrollToBottom();
|
||||
},300);
|
||||
});
|
||||
_settleRO.observe(el);
|
||||
_settleRO=ro;
|
||||
ro.observe(observed);
|
||||
|
||||
// Static-content safety net: a fully-static response (no Prism/KaTeX/Mermaid/
|
||||
// late images) never resizes after the initial sync write, so the
|
||||
@@ -3173,7 +3182,7 @@ function _settleMessageScrollToBottom(force){
|
||||
clearTimeout(_settleFinalTimer);
|
||||
_settleFinalTimer=setTimeout(()=>{
|
||||
if(token!==_bottomSettleToken) return;
|
||||
if(_settleRO){ _settleRO.disconnect(); _settleRO=null; }
|
||||
ro.disconnect(); if(_settleRO===ro) _settleRO=null;
|
||||
if(!_scrollPinned||_messageUserUnpinned||_recentNonMessageScrollIntent()){ _programmaticScroll=false; return; }
|
||||
_settleFinalScroll(token);
|
||||
},2000);
|
||||
|
||||
Reference in New Issue
Block a user