fix(webui): clear the user-row intrinsic-height cache on session switch (Greptile #5672)
The module-level _userRowIntrinsicHeightBySessionIdx cache is keyed by session-relative index (_messageSessionIndexForRawIdx = _messageSessionIndexBase() + rawIdx). The base is 0 for the common non-offset session, so keys collide across sessions: after switching from a session with tall user rows to one with short rows, the new session's off-screen rows at colliding keys inherited the previous session's remembered heights and inflated scrollHeight until re-measured. Add _clearUserRowIntrinsicHeightCache() and call it from _clearMessageVirtualHeightCache — the existing per-session height-cache clear that _resetMessageRenderWindow invokes on session switch — so the new cache shares the exact same lifetime as _messageVirtualHeightCache. Keys are deleted in place to keep the const binding stable. Guard the call with typeof so the node harnesses that extract _clearMessageVirtualHeightCache without this helper keep running. Adds a mutation-checked regression (test_cache_cleared_on_session_switch_prevents_ stale_height_bleed): a rebuilt row at a colliding key after the clear must fall back to the content estimate, not the stale remembered height; making the clear a no-op fails the test.
This commit is contained in:
1 parent
12a45c655f
commit
a7515d67e8
2 files changed
+61
No files matched your search
@@ -598,6 +598,7 @@ function _clearMessageVirtualHeightCache(){
|
||||
clearTimeout(_messageVirtualScrollSettleTimer);
|
||||
_messageVirtualScrollSettleTimer=0;
|
||||
_messageVirtualDeferredMeasurement=null;
|
||||
if(typeof _clearUserRowIntrinsicHeightCache==='function') _clearUserRowIntrinsicHeightCache();
|
||||
}
|
||||
function _resetMessageRenderWindow(sid){
|
||||
_messageRenderWindowSid=sid||null;
|
||||
@@ -1175,6 +1176,16 @@ function _messageViewportIntersectsRenderedRow(){
|
||||
// either. Refreshed every measure pass, so edits self-heal. Desktop rests at
|
||||
// content-visibility:visible (intrinsic-size ignored) → inert there, zero behavior change.
|
||||
const _userRowIntrinsicHeightBySessionIdx=Object.create(null);
|
||||
// Cleared on session switch alongside _messageVirtualHeightCache (both are
|
||||
// per-session measured-height caches keyed by session-relative index). Without this,
|
||||
// keys collide across sessions — _messageSessionIndexForRawIdx = _messageSessionIndexBase()
|
||||
// + rawIdx and the base is 0 for the common non-offset session — so a new session's
|
||||
// off-screen user rows would inherit the previous session's remembered heights and
|
||||
// inflate scrollHeight until each is re-measured. Delete keys in place to keep the
|
||||
// const binding stable for any closure that captured it.
|
||||
function _clearUserRowIntrinsicHeightCache(){
|
||||
for(const k in _userRowIntrinsicHeightBySessionIdx) delete _userRowIntrinsicHeightBySessionIdx[k];
|
||||
}
|
||||
function _rememberUserRowIntrinsicHeight(sessionMsgIdx, height){
|
||||
const key=Number(sessionMsgIdx);
|
||||
if(!Number.isFinite(key)||!(height>0)) return;
|
||||
|
||||
@@ -270,3 +270,53 @@ console.log(JSON.stringify({ inline: assistantRow.style.containIntrinsicSize }))
|
||||
"assistant rows must NOT get an inline intrinsic-size writeback; "
|
||||
f"got {m['inline']!r}"
|
||||
)
|
||||
|
||||
|
||||
def test_cache_cleared_on_session_switch_prevents_stale_height_bleed():
|
||||
"""Greptile #5672 review: the module-level height cache is keyed by
|
||||
session-relative index (_messageSessionIndexBase()+rawIdx), and the base is 0
|
||||
for the common non-offset session, so keys collide across sessions. Without a
|
||||
clear on session switch, a new session's off-screen user row at the same key
|
||||
inherits the previous session's remembered height and inflates scrollHeight.
|
||||
|
||||
_clearUserRowIntrinsicHeightCache() (wired into _clearMessageVirtualHeightCache,
|
||||
which _resetMessageRenderWindow calls on session switch) must empty the cache so
|
||||
a rebuilt row at a colliding key falls back to the content estimate, NOT the
|
||||
stale remembered height.
|
||||
|
||||
Mutation: make _clearUserRowIntrinsicHeightCache a no-op and this fails (the
|
||||
rebuilt row reserves the stale 5000px instead of the ~short estimate)."""
|
||||
js = UI_JS_PATH.read_text(encoding="utf-8")
|
||||
source = _extract_func_script(js) + _fake_row_prelude() + r"""
|
||||
eval(extractFunc('_rememberUserRowIntrinsicHeight'));
|
||||
eval(extractFunc('_estimateUserRowIntrinsicHeight'));
|
||||
eval(extractFunc('_applyUserRowIntrinsicHeight'));
|
||||
eval(extractFunc('_clearUserRowIntrinsicHeightCache'));
|
||||
// Session A: a tall user row at session-relative index 3 measured 5000px.
|
||||
_rememberUserRowIntrinsicHeight(3, 5000);
|
||||
const beforeClear = makeRow('user', 3, 0);
|
||||
_applyUserRowIntrinsicHeight(beforeClear, 'x'); // would reserve the remembered 5000
|
||||
// Session switch clears the cache.
|
||||
_clearUserRowIntrinsicHeightCache();
|
||||
// Session B: a SHORT user row at the SAME colliding key 3, never measured here.
|
||||
const afterClear = makeRow('user', 3, 0);
|
||||
_applyUserRowIntrinsicHeight(afterClear, 'hi'); // must fall back to the estimate
|
||||
const estimate = _estimateUserRowIntrinsicHeight('hi');
|
||||
console.log(JSON.stringify({
|
||||
beforeClear: beforeClear.style.containIntrinsicSize,
|
||||
afterClear: afterClear.style.containIntrinsicSize,
|
||||
estimate: 'auto ' + estimate + 'px',
|
||||
}));
|
||||
"""
|
||||
m = json.loads(_run_node(source))
|
||||
assert m["beforeClear"] == "auto 5000px", (
|
||||
"sanity: before the clear, the remembered 5000px must be reserved; "
|
||||
f"got {m['beforeClear']!r}"
|
||||
)
|
||||
assert m["afterClear"] == m["estimate"], (
|
||||
"after a session switch clear, a rebuilt row at the colliding key must fall "
|
||||
f"back to the content estimate ({m['estimate']}), NOT the stale remembered "
|
||||
f"5000px; got {m['afterClear']!r} (buggy: cache not cleared → stale bleed)"
|
||||
)
|
||||
assert m["afterClear"] != "auto 5000px", "stale height must not survive the clear"
|
||||
|
||||
Reference in new issue
Block a user