Stage 397: PR #2688 — fix: resync virtualized sidebar after scroll clamp
Co-authored-by: ai-ag2026 <ai-ag2026@users.noreply.github.com>
This commit is contained in:
1 parent
d5dcd60981
commit
e2338f696f
2 files changed
+69
No files matched your search
@@ -2932,6 +2932,22 @@ function _markSessionListPointerUp(){
|
||||
if(_pendingSessionListPayload) _schedulePendingSessionListApply();
|
||||
}
|
||||
|
||||
let _sessionVirtualResyncRaf = 0;
|
||||
function _resyncSessionVirtualWindowAfterRender(list, expectedScrollTop, virtualWindow){
|
||||
if(!list||!virtualWindow||!virtualWindow.virtualized) return;
|
||||
expectedScrollTop=Number(expectedScrollTop)||0;
|
||||
if(expectedScrollTop<=0) return;
|
||||
if(_sessionVirtualResyncRaf) cancelAnimationFrame(_sessionVirtualResyncRaf);
|
||||
_sessionVirtualResyncRaf=requestAnimationFrame(()=>{
|
||||
_sessionVirtualResyncRaf=0;
|
||||
if(_renamingSid) return;
|
||||
const actualScrollTop=Number(list.scrollTop)||0;
|
||||
const tolerance=Math.max(2, Number(virtualWindow.itemHeight||SESSION_VIRTUAL_ROW_HEIGHT)/2);
|
||||
if(Math.abs(actualScrollTop-expectedScrollTop)<=tolerance) return;
|
||||
renderSessionListFromCache();
|
||||
});
|
||||
}
|
||||
|
||||
function renderSessionListFromCache(){
|
||||
// Don't re-render while user is actively renaming a session (would destroy the input)
|
||||
if(_renamingSid) return;
|
||||
@@ -3214,6 +3230,7 @@ function renderSessionListFromCache(){
|
||||
// scrollTop drops to 0 — producing a "scroll keeps jumping back" feel
|
||||
// when the list scrolls naturally. Fixed for #1669 follow-up.
|
||||
list.scrollTop=listScrollTopBeforeRender;
|
||||
_resyncSessionVirtualWindowAfterRender(list, listScrollTopBeforeRender, virtualWindow);
|
||||
}
|
||||
// Select mode toggle button (only when NOT in select mode)
|
||||
if(!_sessionSelectMode){
|
||||
|
||||
@@ -138,3 +138,55 @@ def test_session_list_only_moves_to_active_when_active_row_is_not_visible():
|
||||
assert before_idx < visible_idx < move_idx < final_idx < anchor_idx
|
||||
assert "activeIndex:-1" in render_body[before_idx:visible_idx]
|
||||
assert "activeIndex:shouldAnchorActive?activeIndex:-1" not in render_body
|
||||
|
||||
|
||||
def test_session_list_resyncs_when_browser_clamps_virtual_scroll_restore():
|
||||
"""If a hidden/reflowed sidebar rejects restored scrollTop, re-render the visible window."""
|
||||
js = SESSIONS_JS_PATH.read_text(encoding="utf-8")
|
||||
render_start = js.index("function renderSessionListFromCache()")
|
||||
render_end = js.index("async function _handleActiveSessionStorageEvent", render_start)
|
||||
render_body = js[render_start:render_end]
|
||||
|
||||
assert "_resyncSessionVirtualWindowAfterRender(list, listScrollTopBeforeRender, virtualWindow);" in render_body
|
||||
|
||||
source = _extract_func_script(js) + """
|
||||
let renderCount = 0;
|
||||
let rafCount = 0;
|
||||
let _renamingSid = null;
|
||||
const SESSION_VIRTUAL_ROW_HEIGHT = 52;
|
||||
function requestAnimationFrame(cb){ rafCount += 1; cb(); return rafCount; }
|
||||
function cancelAnimationFrame(_id){}
|
||||
function renderSessionListFromCache(){ renderCount += 1; }
|
||||
const makeHelper = new Function(
|
||||
'requestAnimationFrame',
|
||||
'cancelAnimationFrame',
|
||||
'renderSessionListFromCache',
|
||||
`let _sessionVirtualResyncRaf = 0;
|
||||
let _renamingSid = null;
|
||||
const SESSION_VIRTUAL_ROW_HEIGHT = 52;
|
||||
${extractFunc('_resyncSessionVirtualWindowAfterRender')}
|
||||
return _resyncSessionVirtualWindowAfterRender;`
|
||||
);
|
||||
const _resyncSessionVirtualWindowAfterRender = makeHelper(
|
||||
requestAnimationFrame,
|
||||
cancelAnimationFrame,
|
||||
renderSessionListFromCache
|
||||
);
|
||||
|
||||
_resyncSessionVirtualWindowAfterRender(
|
||||
{scrollTop: 0},
|
||||
52 * 10,
|
||||
{virtualized: true, itemHeight: 52}
|
||||
);
|
||||
const afterClamp = renderCount;
|
||||
_resyncSessionVirtualWindowAfterRender(
|
||||
{scrollTop: 52 * 10},
|
||||
52 * 10,
|
||||
{virtualized: true, itemHeight: 52}
|
||||
);
|
||||
console.log(JSON.stringify({afterClamp, final: renderCount, rafCount}));
|
||||
"""
|
||||
metrics = json.loads(_run_node(source))
|
||||
assert metrics["afterClamp"] == 1
|
||||
assert metrics["final"] == 1
|
||||
assert metrics["rafCount"] == 2
|
||||
Reference in new issue
Block a user