perf(#4346): recycle stable DOM nodes during virtual-scroll measurement re-renders

# Conflicts:
#	static/ui.js
#	tests/test_issue4346_vscroll_footer_jitter.py
This commit is contained in:
Rod Boev committed 2026-06-23 02:54:28 -04:00
1 parent fa3d612a4e
commit 9c364d8498
2 files changed
+97 -11

No files matched your search

+45 -11
View File
@@ -436,6 +436,8 @@ let _messageVirtualMeasurementRetryCount=0;
let _messageVirtualScrollActive=false;
let _messageVirtualScrollSettleTimer=0;
let _messageVirtualDeferredMeasurement=null;
let _msgNodeRecycleEnabled=false;
const _recycleStash=new Map();
function _markMessageVirtualScrollActive(){
_messageVirtualScrollActive=true;
clearTimeout(_messageVirtualScrollSettleTimer);
@@ -965,7 +967,9 @@ function _scheduleMessageVirtualizedRender(force){
const liveWindow=_currentMessageVirtualWindow(liveVisWithIdx,_messageVirtualKeepTailCount());
const liveKey=_messageVirtualWindowKeyFor(liveWindow);
if(!force&&liveKey===_messageVirtualWindowKey) return;
_compensateScrollForMeasurementDelta(()=>{ renderMessages({ preserveScroll:true }); });
_msgNodeRecycleEnabled=true;
try{ _compensateScrollForMeasurementDelta(()=>{ renderMessages({ preserveScroll:true }); }); }
finally{ _msgNodeRecycleEnabled=false; }
});
}
@@ -11114,6 +11118,14 @@ function renderMessages(options){
S.session && typeof S.session.compression_anchor_summary==='string'
) ? S.session.compression_anchor_summary.trim() : '';
const worklogDetailDisclosureState=_captureWorklogDetailDisclosureState(inner);
_recycleStash.clear();
if(_msgNodeRecycleEnabled){
for(const child of Array.from(inner.children)){
if(!child.dataset||!child.dataset.msgIdx) continue;
if(child.id==='liveAssistantTurn'||child.querySelector&&child.querySelector('#liveAssistantTurn')) continue;
_recycleStash.set(Number(child.dataset.msgIdx), child);
}
}
inner.innerHTML='';
const compressionNode=compressionState?_compressionCardsNode(compressionState):null;
const {message:referenceMessage, rawIdx:referenceMessageRawIdx}=_latestCompressionReferenceMessage(
@@ -11381,22 +11393,43 @@ function renderMessages(options){
if(isUser){
currentAssistantTurn=null;
const row=document.createElement('div');
row.className='msg-row';
row.id=_userMessageDomId(rawIdx);
row.dataset.msgIdx=rawIdx;
row.dataset.sessionMsgIdx=_messageSessionIndexForRawIdx(rawIdx);
row.dataset.messageAnchorKey=_messageViewportAnchorKeyForMessage(m);
row.dataset.role='user';
row.dataset.rawText=String(displayContent).trim();
row.innerHTML=`${filesHtml}<div class="msg-body">${bodyHtml}</div>${footHtml}`;
let row=_msgNodeRecycleEnabled?_recycleStash.get(rawIdx):null;
const newRawText=String(displayContent).trim();
if(row){
row.id=_userMessageDomId(rawIdx);
row.dataset.msgIdx=rawIdx;
row.dataset.sessionMsgIdx=_messageSessionIndexForRawIdx(rawIdx);
row.dataset.messageAnchorKey=_messageViewportAnchorKeyForMessage(m);
row.dataset.role='user';
if(row.dataset.rawText!==newRawText){
row.dataset.rawText=newRawText;
row.innerHTML=`${filesHtml}<div class="msg-body">${bodyHtml}</div>${footHtml}`;
}
}else{
row=document.createElement('div');
row.className='msg-row';
row.id=_userMessageDomId(rawIdx);
row.dataset.msgIdx=rawIdx;
row.dataset.sessionMsgIdx=_messageSessionIndexForRawIdx(rawIdx);
row.dataset.messageAnchorKey=_messageViewportAnchorKeyForMessage(m);
row.dataset.role='user';
row.dataset.rawText=newRawText;
row.innerHTML=`${filesHtml}<div class="msg-body">${bodyHtml}</div>${footHtml}`;
}
inner.appendChild(row);
userRows.set(rawIdx, row);
continue;
}
if(!currentAssistantTurn){
currentAssistantTurn=_createAssistantTurn(tsTitle, isTpsDisplayEnabled()?_formatTurnTps(m._turnTps):'');
const recycled=_msgNodeRecycleEnabled?_recycleStash.get(rawIdx):null;
if(recycled){
const blocks=_assistantTurnBlocks(recycled);
if(blocks) blocks.innerHTML='';
currentAssistantTurn=recycled;
}else{
currentAssistantTurn=_createAssistantTurn(tsTitle, isTpsDisplayEnabled()?_formatTurnTps(m._turnTps):'');
}
inner.appendChild(currentAssistantTurn);
}
const seg=document.createElement('div');
@@ -12273,6 +12306,7 @@ function renderMessages(options){
}
}
_updateMessageVirtualMeasurements(renderVisWithIdx, renderVisibleIdxs, virtualWindow);
_recycleStash.clear();
}
function _toolDisplayName(tc){
@@ -51,3 +51,55 @@ def test_js_try_finally_guards_class_removal():
assert remove_idx != -1, "missing classList.remove('vscroll-measuring')"
assert try_idx < finally_idx < remove_idx, \
"classList.remove must remain in the finally{} cleanup path"
def test_js_recycle_flag_exists():
"""ui.js declares the _msgNodeRecycleEnabled flag."""
assert '_msgNodeRecycleEnabled' in JS
def test_js_recycle_stash_exists():
"""ui.js declares the _recycleStash Map."""
assert '_recycleStash' in JS
def test_js_recycle_flag_set_in_virtual_render():
"""_scheduleMessageVirtualizedRender sets _msgNodeRecycleEnabled=true
before the compensate call and clears it in finally."""
fn_match = re.search(
r'function _scheduleMessageVirtualizedRender\(force\)\{(.+?)^(?=function )',
JS, re.DOTALL | re.MULTILINE
)
assert fn_match, "_scheduleMessageVirtualizedRender not found"
body = fn_match.group(1)
assert '_msgNodeRecycleEnabled=true' in body
finally_match = re.search(r'finally\{([^}]*)\}', body)
assert finally_match, "no finally block in _scheduleMessageVirtualizedRender"
assert '_msgNodeRecycleEnabled=false' in finally_match.group(1)
def test_js_stash_populated_before_wipe():
"""The recycleStash population loop appears before innerHTML='' in renderMessages."""
fn_match = re.search(
r'function renderMessages\(options\)\{(.+?)^(?=function )',
JS, re.DOTALL | re.MULTILINE
)
assert fn_match, "renderMessages not found"
body = fn_match.group(1)
stash_pos = body.find('_recycleStash.set(')
wipe_pos = body.find("inner.innerHTML='';")
assert stash_pos != -1, "_recycleStash.set not found in renderMessages"
assert wipe_pos != -1, "innerHTML wipe not found in renderMessages"
assert stash_pos < wipe_pos, "_recycleStash.set must appear before innerHTML=''"
def test_js_user_row_checks_stash():
"""The user-row creation block checks _recycleStash before createElement."""
fn_match = re.search(
r'function renderMessages\(options\)\{(.+?)^(?=function )',
JS, re.DOTALL | re.MULTILINE
)
assert fn_match, "renderMessages not found"
body = fn_match.group(1)
assert '_recycleStash.get(rawIdx)' in body, \
"user row block must check _recycleStash.get(rawIdx)"