Skip to content

Commit b5eabe1

Browse files
author
nesquena-hermes
committed
fix(nesquena#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 nesquena#1360 (d21c972) 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.
1 parent f9055c5 commit b5eabe1

2 files changed

Lines changed: 17 additions & 8 deletions

File tree

static/style.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1588,7 +1588,7 @@
15881588
.workspace-toggle-btn:disabled{opacity:.38;cursor:not-allowed;}
15891589
.chip.model{color:var(--accent-text);border-color:var(--accent-bg-strong);background:var(--accent-bg);}
15901590
.messages-shell{flex:1;min-height:0;position:relative;display:flex;flex-direction:column;}
1591-
.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;}
1591+
.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;}
15921592
/* Overlay scroll controls so they do not affect the transcript's native scroll geometry. */
15931593
.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;}
15941594
.scroll-to-bottom-btn:hover{color:var(--text);border-color:var(--border);background:var(--hover-bg);}

static/ui.js

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3139,11 +3139,19 @@ function _settleMessageScrollToBottom(force){
31393139

31403140
const el=document.getElementById('messages');
31413141
if(!el) return;
3142-
3143-
_settleRO=new ResizeObserver(()=>{
3144-
if(token!==_bottomSettleToken){ if(_settleRO){ _settleRO.disconnect(); _settleRO=null; } return; }
3142+
// Observe the GROWING content node, not the scroll container. #messages is the
3143+
// scroller but its box is fixed by the flex layout, so it never resizes — the
3144+
// transcript grows inside #msgInner (.messages-inner). Observing #messages
3145+
// would mean the callback never fires. (Codex review #2.)
3146+
const observed=document.getElementById('msgInner')||el;
3147+
3148+
// Instance-owned cleanup: close over THIS observer so a stale callback (from a
3149+
// superseded settle) only ever disconnects its own observer, never the newer
3150+
// active one that may now be in the global _settleRO. (Codex review #3.)
3151+
const ro=new ResizeObserver(()=>{
3152+
if(token!==_bottomSettleToken){ ro.disconnect(); if(_settleRO===ro) _settleRO=null; return; }
31453153
if(!_scrollPinned||_messageUserUnpinned||_recentNonMessageScrollIntent()){
3146-
if(_settleRO){ _settleRO.disconnect(); _settleRO=null; }
3154+
ro.disconnect(); if(_settleRO===ro) _settleRO=null;
31473155
_programmaticScroll=false;
31483156
return;
31493157
}
@@ -3158,11 +3166,12 @@ function _settleMessageScrollToBottom(force){
31583166
clearTimeout(_settleTimer);
31593167
_settleTimer=setTimeout(()=>{
31603168
if(token!==_bottomSettleToken) return;
3161-
if(_settleRO){ _settleRO.disconnect(); _settleRO=null; }
3169+
ro.disconnect(); if(_settleRO===ro) _settleRO=null;
31623170
_setMessageScrollToBottom();
31633171
},300);
31643172
});
3165-
_settleRO.observe(el);
3173+
_settleRO=ro;
3174+
ro.observe(observed);
31663175

31673176
// Static-content safety net: a fully-static response (no Prism/KaTeX/Mermaid/
31683177
// late images) never resizes after the initial sync write, so the
@@ -3173,7 +3182,7 @@ function _settleMessageScrollToBottom(force){
31733182
clearTimeout(_settleFinalTimer);
31743183
_settleFinalTimer=setTimeout(()=>{
31753184
if(token!==_bottomSettleToken) return;
3176-
if(_settleRO){ _settleRO.disconnect(); _settleRO=null; }
3185+
ro.disconnect(); if(_settleRO===ro) _settleRO=null;
31773186
if(!_scrollPinned||_messageUserUnpinned||_recentNonMessageScrollIntent()){ _programmaticScroll=false; return; }
31783187
_settleFinalScroll(token);
31793188
},2000);

0 commit comments

Comments
 (0)