fix(#3920): address review — restore sidebar overflow-anchor, wire _settleFinalScroll via 2s fallback, + CHANGELOG v0.51.377
3 maintainer-review items the contributor hadn't addressed:
1. (blocking) restored .session-list overflow-anchor:none — the PR accidentally reverted the deliberate Firefox sidebar fix (2dfe765b); only the on-topic .messages removal is kept.
2/3. _settleFinalScroll was dead code + the promised 2s static-content safety timeout was missing — wired _settleFinalScroll(token) via a single 2s top-level fallback after _settleRO.observe(el), declared _settleFinalTimer + cleared it in _cancelBottomSettle and at settle re-entry.
This commit is contained in:
@@ -3,6 +3,12 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v0.51.377] — 2026-06-13 — Release MP (Firefox post-stream scroll jitter, #3920)
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Firefox no longer jitters/steps the chat scroll after a streamed turn settles (#3920).** The post-stream bottom-settle replaced its `setTimeout` fan-out + double-`requestAnimationFrame` `scrollHeight` polling (which forced a reflow per frame on Firefox) with a single synchronous write plus a `ResizeObserver` that re-anchors once per frame as late layout (Prism/KaTeX/Mermaid/images) grows, disconnecting after 300ms of quiet. A 2s top-level fallback covers fully-static responses that never resize, and the deliberate Firefox session-sidebar `overflow-anchor:none` fix is preserved. (#3920)
|
||||
|
||||
## [v0.51.376] — 2026-06-13 — Release MO (Hide Thinking also hides Worklog reasoning, #3903)
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -992,7 +992,7 @@
|
||||
.sidebar-section{padding:14px 14px 8px;}
|
||||
.new-chat-btn{width:100%;padding:9px 12px;border-radius:9px;background:var(--accent-bg);border:1px solid var(--accent-bg-strong);color:var(--accent-text);font-size:13px;cursor:pointer;display:flex;align-items:center;gap:8px;transition:all .15s;margin-bottom:8px;font-weight:500;}
|
||||
.new-chat-btn:hover{background:var(--accent-bg-strong);border-color:var(--accent);}
|
||||
.session-list{flex:1;overflow-y:auto;padding:0 8px 8px;min-height:0;overscroll-behavior-y:contain;touch-action:pan-y;}
|
||||
.session-list{flex:1;overflow-y:auto;padding:0 8px 8px;min-height:0;overscroll-behavior-y:contain;touch-action:pan-y;overflow-anchor:none;}
|
||||
.sidebar-search{position:relative;padding:8px 12px;flex-shrink:0;}
|
||||
.session-search-field{position:relative;display:flex;align-items:center;width:100%;}
|
||||
.sidebar-search input{width:100%;background:var(--bg);border:1px solid var(--border);border-radius:8px;color:var(--text);padding:7px 10px 7px 32px;font-size:13px;outline:none;transition:border-color .15s,box-shadow .15s,background .15s;box-sizing:border-box;}
|
||||
|
||||
18
static/ui.js
18
static/ui.js
@@ -2428,10 +2428,11 @@ let _bottomSettleToken=0;
|
||||
let _settleRAF=0;
|
||||
let _settleRO=null;
|
||||
let _settleTimer=0;
|
||||
let _settleFinalTimer=0;
|
||||
const NON_MESSAGE_SCROLL_INTENT_SUPPRESS_MS=350;
|
||||
let _touchStartY=null;
|
||||
let _newMessageCueVisible=false;
|
||||
function _cancelBottomSettle(){ _bottomSettleToken++; if(_settleRO){ _settleRO.disconnect(); _settleRO=null; } clearTimeout(_settleTimer); cancelAnimationFrame(_settleRAF); }
|
||||
function _cancelBottomSettle(){ _bottomSettleToken++; if(_settleRO){ _settleRO.disconnect(); _settleRO=null; } clearTimeout(_settleTimer); clearTimeout(_settleFinalTimer); cancelAnimationFrame(_settleRAF); }
|
||||
function _recordNonMessageScrollIntent(e){
|
||||
const el=document.getElementById('messages');
|
||||
const target=e&&e.target;
|
||||
@@ -3129,6 +3130,7 @@ function _settleMessageScrollToBottom(force){
|
||||
cancelAnimationFrame(_settleRAF);
|
||||
if(_settleRO){ _settleRO.disconnect(); _settleRO=null; }
|
||||
clearTimeout(_settleTimer);
|
||||
clearTimeout(_settleFinalTimer);
|
||||
|
||||
// Sync write anchors the viewport immediately.
|
||||
_setMessageScrollToBottom();
|
||||
@@ -3161,6 +3163,20 @@ function _settleMessageScrollToBottom(force){
|
||||
},300);
|
||||
});
|
||||
_settleRO.observe(el);
|
||||
|
||||
// Static-content safety net: a fully-static response (no Prism/KaTeX/Mermaid/
|
||||
// late images) never resizes after the initial sync write, so the
|
||||
// ResizeObserver callback above never fires and its 300ms quiet-timer is never
|
||||
// armed. Arm a single 2s top-level fallback so a late settle still runs for
|
||||
// that case. The token check inside _settleFinalScroll makes this a no-op if a
|
||||
// newer settle started, and it self-skips if the user unpinned. (Review #2/#3.)
|
||||
clearTimeout(_settleFinalTimer);
|
||||
_settleFinalTimer=setTimeout(()=>{
|
||||
if(token!==_bottomSettleToken) return;
|
||||
if(_settleRO){ _settleRO.disconnect(); _settleRO=null; }
|
||||
if(!_scrollPinned||_messageUserUnpinned||_recentNonMessageScrollIntent()){ _programmaticScroll=false; return; }
|
||||
_settleFinalScroll(token);
|
||||
},2000);
|
||||
}
|
||||
|
||||
function _settleFinalScroll(token){
|
||||
|
||||
Reference in New Issue
Block a user