diff --git a/static/ui.js b/static/ui.js
index 0d4abf6b..c8249f8b 100644
--- a/static/ui.js
+++ b/static/ui.js
@@ -265,10 +265,15 @@ function _statusCardHtml(card){
const MESSAGE_RENDER_WINDOW_DEFAULT=50;
let _messageRenderWindowSid=null;
let _messageRenderWindowSize=MESSAGE_RENDER_WINDOW_DEFAULT;
+// Cached visWithIdx array — invalidated when S.messages.length changes.
+let _visWithIdxCache=null;
+let _visWithIdxCacheLen=0;
function _resetMessageRenderWindow(sid){
_messageRenderWindowSid=sid||null;
_messageRenderWindowSize=MESSAGE_RENDER_WINDOW_DEFAULT;
_clearRenderCache();
+ _visWithIdxCache=null;
+ _visWithIdxCacheLen=0;
}
// ── renderMd / _renderUserFencedBlocks cache ──────────────────────────────
@@ -6120,15 +6125,29 @@ function renderMessages(options){
? (()=>{const row=document.createElement('div');row.innerHTML=`
${_compressionReferenceCardHtml(referenceText,false)}${_preservedCompressionTaskListCardsHtml(preservedCompressionTaskMessages)}
`;return row.firstElementChild;})()
: null;
let preservedCompressionTaskCardsAttached=!!referenceNode;
- const visWithIdx=[];
+ // Cache visWithIdx so expanding the render window (Load earlier) doesn't
+ // re-scan S.messages from scratch. Invalidate only when the message array
+ // length changes — i.e. new messages arrived or session was truncated.
+ if(!_visWithIdxCache || _visWithIdxCacheLen !== S.messages.length){
+ const rebuilt=[];
+ let ri=0;
+ for(const m of S.messages){
+ if(!m||!m.role||m.role==='tool'){ri++;continue;}
+ if(_isPreservedCompressionTaskListMessage(m)){ri++;continue;}
+ const hasTc=Array.isArray(m.tool_calls)&&m.tool_calls.length>0;
+ const hasTu=Array.isArray(m.content)&&m.content.some(p=>p&&p.type==='tool_use');
+ if(msgContent(m)||m._statusCard||m.attachments?.length||(m.role==='assistant'&&(hasTc||hasTu||_messageHasReasoningPayload(m)))) rebuilt.push({m,rawIdx:ri});
+ ri++;
+ }
+ _visWithIdxCache=rebuilt;
+ _visWithIdxCacheLen=S.messages.length;
+ }
+ const visWithIdx=_visWithIdxCache;
const preservedCompressionRawIdxs=[];
let rawIdx=0;
for(const m of S.messages){
if(!m||!m.role||m.role==='tool'){rawIdx++;continue;}
if(_isPreservedCompressionTaskListMessage(m)){preservedCompressionRawIdxs.push(rawIdx);rawIdx++;continue;}
- const hasTc=Array.isArray(m.tool_calls)&&m.tool_calls.length>0;
- const hasTu=Array.isArray(m.content)&&m.content.some(p=>p&&p.type==='tool_use');
- if(msgContent(m)||m._statusCard||m.attachments?.length||(m.role==='assistant'&&(hasTc||hasTu||_messageHasReasoningPayload(m)))) visWithIdx.push({m,rawIdx});
rawIdx++;
}
// Show a top affordance when earlier transcript content exists either in