2026-07-14 03:45:39 +08:00
// Production renderer integration test for the dynamic SessionDetail timeline.
// Run: npm run test:electron:timeline
import { app , BrowserWindow , ipcMain } from 'electron' ;
import { dirname , join } from 'node:path' ;
import { fileURLToPath } from 'node:url' ;
import { setTimeout as delay } from 'node:timers/promises' ;
2026-07-14 18:11:51 +08:00
import { createSessionPatch } from '../src/shared/session-patch.mjs' ;
import { assembleSessionMessages } from '../src/shared/session-detail-assembly.mjs' ;
2026-07-14 03:45:39 +08:00
const here = dirname ( fileURLToPath ( import . meta . url ));
const appRoot = join ( here , '..' );
const sessionId = 'test-session' ;
2026-07-14 18:11:51 +08:00
const messageCount = Number ( process . env . OBELISK_TIMELINE_MESSAGE_COUNT || 2000 );
const focusMessageIndex = Math . floor ( messageCount * 0.75 );
const focusMessageUuid = `message- ${ focusMessageIndex } ` ;
const stationaryAppendRuns = 3 ;
const firstStationaryAppendIndex = messageCount ;
const scrollingAppendIndex = messageCount + stationaryAppendRuns ;
2026-07-15 01:35:05 +08:00
const nearTailEscapeAppendIndex = scrollingAppendIndex + 1 ;
const tailAppendIndex = nearTailEscapeAppendIndex + 1 ;
2026-07-14 03:45:39 +08:00
const channels = [
'db:getSessions' ,
'db:getSessionMessages' ,
'db:getSessionToolCalls' ,
'db:getSessionToolResults' ,
2026-07-14 18:11:51 +08:00
'db:getSessionPatch' ,
2026-07-14 03:45:39 +08:00
'db:getSessionSubagents' ,
'db:getSessionWorkflows' ,
'db:getSessionSummaries' ,
2026-07-14 18:11:51 +08:00
'db:getMessageFullText' ,
2026-07-14 03:45:39 +08:00
'db:getMemories' ,
'db:getProjects' ,
'db:getStats' ,
'settings:get' ,
];
let failures = 0 ;
let firstSessionListRead = true ;
2026-07-14 18:11:51 +08:00
const ipcReads = {
messages : 0 ,
toolCalls : 0 ,
toolResults : 0 ,
subagents : 0 ,
workflows : 0 ,
summaries : 0 ,
patches : 0 ,
patchMessageRows : [],
};
const messages = Array . from ({ length : messageCount }, ( _ , index ) => ({
2026-07-14 03:45:39 +08:00
uuid : `message- ${ index } ` ,
type : index % 2 === 0 ? 'user' : 'assistant' ,
timestamp : new Date ( Date . UTC ( 2026 , 6 , 14 , 0 , 0 , index )). toISOString (),
text : index === 1
? ''
: `Message ${ index } ${ 'dynamic-height content ' . repeat (( index % 7 ) + 1 ) } ` ,
content_type : index === 1 ? 'tool_use' : 'text' ,
is_meta : 0 ,
}));
2026-07-14 18:11:51 +08:00
messages [ focusMessageIndex ]. type = 'assistant' ;
messages [ focusMessageIndex ]. text = `Truncated preview ${ 'indexed content ' . repeat ( 700 ) } ` ;
const fullTextSentinel = `FULL TEXT SENTINEL ${ 'complete content ' . repeat ( 80 ) } ` ;
const codexExecSource = 'const result = { ok: true };\nreturn result;' ;
let codexExecOutput = JSON . stringify ([{
type : 'input_text' ,
text : 'Script completed\nWall time 0.1 seconds\nOutput:\n{"ok":true}' ,
}]);
2026-07-14 03:45:39 +08:00
const toolCalls = [{
id : 'call-1' ,
message_uuid : 'message-1' ,
name : 'Bash' ,
input_json : JSON . stringify ({ command : 'printf virtualized' }),
2026-07-14 18:11:51 +08:00
}, {
id : 'call-codex-exec' ,
message_uuid : focusMessageUuid ,
name : 'exec' ,
input_json : JSON . stringify ( codexExecSource ),
2026-07-14 03:45:39 +08:00
}];
const toolResults = [{
tool_use_id : 'call-1' ,
content : ` ${ 'virtualized output\n' . repeat ( 80 ) } ` ,
is_error : 0 ,
2026-07-14 18:11:51 +08:00
}, {
tool_use_id : 'call-codex-exec' ,
content : codexExecOutput ,
is_error : 0 ,
2026-07-14 03:45:39 +08:00
}];
function sessionSummary () {
return {
id : sessionId ,
title : 'Virtualized timeline integration' ,
project : 'quiet-zero' ,
project_path : '/tmp/quiet-zero' ,
source : 'claude' ,
started_at : '2026-07-14T00:00:00.000Z' ,
ended_at : '2026-07-14T01:00:00.000Z' ,
message_count : messages . length ,
git_branch : 'main' ,
};
}
function assert ( condition , message ) {
if ( condition ) console . log ( `PASS: ${ message } ` );
else {
failures ++ ;
console . error ( `FAIL: ${ message } ` );
}
}
async function waitFor ( webContents , expression , message , timeoutMs = 8000 ) {
const deadline = Date . now () + timeoutMs ;
while ( Date . now () < deadline ) {
if ( await webContents . executeJavaScript ( `Boolean( ${ expression } )` , true )) return ;
await delay ( 40 );
}
throw new Error ( `Timed out waiting for ${ message } ` );
}
2026-07-14 18:11:51 +08:00
async function startRendererTrace ( win ) {
const traceEvents = [];
let completeTrace ;
const traceComplete = new Promise ( resolve => { completeTrace = resolve ; });
const onMessage = ( _event , method , params = {}) => {
if ( method === 'Tracing.dataCollected' ) traceEvents . push (...( params . value || []));
if ( method === 'Tracing.tracingComplete' ) completeTrace ();
};
win . webContents . debugger . attach ( '1.3' );
win . webContents . debugger . on ( 'message' , onMessage );
await win . webContents . debugger . sendCommand ( 'Tracing.start' , {
categories : 'devtools.timeline,disabled-by-default-devtools.timeline,blink.user_timing,toplevel' ,
options : 'record-as-much-as-possible' ,
transferMode : 'ReportEvents' ,
});
return async () => {
await win . webContents . debugger . sendCommand ( 'Tracing.end' );
await traceComplete ;
win . webContents . debugger . removeListener ( 'message' , onMessage );
win . webContents . debugger . detach ();
return traceEvents ;
};
}
function rendererTaskMetrics ( traceEvents , startMark , endMark ) {
const start = traceEvents . find ( event => event . name === startMark );
const end = [... traceEvents ]. reverse (). find ( event => event . name === endMark );
if ( ! start || ! end ) throw new Error ( `Missing renderer trace marks: ${ startMark } , ${ endMark } ` );
const tasks = traceEvents
. filter ( event => (
/RunTask$/ . test ( event . name || '' )
&& event . ph === 'X'
&& event . pid === start . pid
&& event . tid === start . tid
&& event . ts >= start . ts
&& event . ts <= end . ts
));
const taskDurations = tasks . map ( event => event . dur / 1000 );
if ( taskDurations . length === 0 ) throw new Error ( 'Renderer trace contained no RunTask events' );
const slowest = tasks . reduce (( best , task ) => ! best || task . dur > best . dur ? task : best , null );
const slowestChildren = slowest
? traceEvents
. filter ( event => (
event . ph === 'X'
&& event . pid === slowest . pid
&& event . tid === slowest . tid
&& event !== slowest
&& event . ts >= slowest . ts
&& event . ts + ( event . dur || 0 ) <= slowest . ts + slowest . dur
))
. sort (( a , b ) => ( b . dur || 0 ) - ( a . dur || 0 ))
. slice ( 0 , 8 )
. map ( event => ({ name : event . name , durationMs : ( event . dur || 0 ) / 1000 }))
: [];
return {
tasks : taskDurations . length ,
maxTaskMs : Math . max ( 0 , ... taskDurations ),
slowestChildren ,
};
}
async function traceStationaryAppend ( win , index , expectedTotal , runIndex ) {
const startMark = `obelisk-live-commit- ${ runIndex } -start` ;
const endMark = `obelisk-live-commit- ${ runIndex } -end` ;
const stopRendererTrace = await startRendererTrace ( win );
await win . webContents . executeJavaScript ( `(() => {
const expected = ${ JSON . stringify ( String ( expectedTotal )) } ;
const counter = document.querySelector('.flap-number');
performance.mark( ${ JSON . stringify ( startMark ) } );
window.__obeliskLiveCommitObserved = new Promise(resolve => {
const finish = () => requestAnimationFrame(() => {
performance.mark( ${ JSON . stringify ( endMark ) } );
resolve(true);
});
if (counter?.getAttribute('aria-label') === expected) {
finish();
return;
}
const observer = new MutationObserver(() => {
if (counter?.getAttribute('aria-label') !== expected) return;
observer.disconnect();
finish();
});
observer.observe(counter, { attributes: true, attributeFilter: ['aria-label'] });
});
return true;
})()` , true );
appendMessage ( win , index );
await win . webContents . executeJavaScript ( 'window.__obeliskLiveCommitObserved' , true );
await win . webContents . executeJavaScript ( 'delete window.__obeliskLiveCommitObserved' , true );
return rendererTaskMetrics ( await stopRendererTrace (), startMark , endMark );
}
2026-07-14 03:45:39 +08:00
function registerHandlers () {
ipcMain . handle ( 'db:getSessions' , async () => {
if ( firstSessionListRead ) {
firstSessionListRead = false ;
await delay ( 120 );
}
return [ sessionSummary ()];
});
2026-07-14 18:11:51 +08:00
ipcMain . handle ( 'db:getSessionMessages' , () => { ipcReads . messages ++ ; return messages ; });
ipcMain . handle ( 'db:getSessionToolCalls' , () => { ipcReads . toolCalls ++ ; return toolCalls ; });
ipcMain . handle ( 'db:getSessionToolResults' , () => { ipcReads . toolResults ++ ; return toolResults ; });
ipcMain . handle ( 'db:getSessionPatch' , ( _event , _sessionId , cursor ) => {
ipcReads . patches ++ ;
const patch = createSessionPatch ({
messages : assembleSessionMessages ({ messages , toolCalls , toolResults , subagents : [], workflows : [] }),
workflows : [],
}, cursor );
ipcReads . patchMessageRows . push ( patch . changes . messages . length );
return patch ;
});
ipcMain . handle ( 'db:getSessionSubagents' , () => { ipcReads . subagents ++ ; return []; });
ipcMain . handle ( 'db:getSessionWorkflows' , () => { ipcReads . workflows ++ ; return []; });
ipcMain . handle ( 'db:getSessionSummaries' , () => { ipcReads . summaries ++ ; return []; });
ipcMain . handle ( 'db:getMessageFullText' , ( _event , uuid ) => uuid === focusMessageUuid ? fullTextSentinel : null );
2026-07-14 03:45:39 +08:00
ipcMain . handle ( 'db:getMemories' , () => []);
ipcMain . handle ( 'db:getProjects' , () => [{ project : 'quiet-zero' , count : 1 }]);
ipcMain . handle ( 'db:getStats' , () => ({}));
ipcMain . handle ( 'settings:get' , () => ({}));
}
function appendMessage ( win , index ) {
messages . push ({
uuid : `message- ${ index } ` ,
type : index % 2 === 0 ? 'user' : 'assistant' ,
timestamp : new Date (). toISOString (),
text : `Live message ${ index } ` ,
content_type : 'text' ,
is_meta : 0 ,
});
win . webContents . send ( 'obelisk:session-updated' , { sessionId });
}
2026-07-14 18:11:51 +08:00
function replaceMessageText ( win , uuid , text ) {
const index = messages . findIndex ( message => message . uuid === uuid );
if ( index < 0 ) throw new Error ( `Cannot update missing message ${ uuid } ` );
messages [ index ] = { ... messages [ index ], text };
win . webContents . send ( 'obelisk:session-updated' , { sessionId });
}
function replaceToolResult ( win , toolUseId , content ) {
const index = toolResults . findIndex ( result => result . tool_use_id === toolUseId );
if ( index < 0 ) throw new Error ( `Cannot update missing tool result ${ toolUseId } ` );
toolResults [ index ] = { ... toolResults [ index ], content };
win . webContents . send ( 'obelisk:session-updated' , { sessionId });
}
2026-07-14 03:45:39 +08:00
async function run () {
registerHandlers ();
const win = new BrowserWindow ({
show : false ,
width : 1200 ,
height : 800 ,
webPreferences : {
preload : join ( appRoot , 'out' , 'preload' , 'index.js' ),
contextIsolation : true ,
nodeIntegration : false ,
},
});
await win . loadFile ( join ( appRoot , 'out' , 'renderer' , 'index.html' ), {
hash : `/sessions/ ${ sessionId } ` ,
});
await waitFor (
win . webContents ,
2026-07-14 18:11:51 +08:00
`document.querySelector('.flap-number')?.getAttribute('aria-label') === ' ${ messageCount } '` ,
2026-07-14 03:45:39 +08:00
'the cold-start session snapshot' ,
);
const initial = await win . webContents . executeJavaScript ( `(() => ({
current: Number(document.querySelector('.msg-nav-current')?.textContent),
total: Number(document.querySelector('.flap-number')?.getAttribute('aria-label')),
rows: document.querySelectorAll('.virtual-timeline-row').length,
roots: document.querySelectorAll('.msg[data-uuid], .wf-card[data-uuid], .skill-card[data-uuid]').length,
scrollTop: document.querySelector('.detail-wrap')?.scrollTop,
scrollHeight: document.querySelector('.detail-wrap')?.scrollHeight,
}))()` , true );
assert ( initial . scrollTop < 2 && initial . current < 100 , `cold start stays at the beginning (scrollTop ${ initial . scrollTop } , item ${ initial . current } )` );
2026-07-14 18:11:51 +08:00
assert ( initial . total === messageCount , `timeline exposes all ${ messageCount } items (got ${ initial . total } )` );
2026-07-14 03:45:39 +08:00
assert ( initial . rows < 60 && initial . roots === initial . rows , `only ${ initial . rows } virtual rows are mounted` );
const disclosure = await win . webContents . executeJavaScript ( `(async () => {
const toggle = document.querySelector('.toolcall-toggle');
const row = toggle?.closest('.virtual-timeline-row');
const before = row?.getBoundingClientRect().height || 0;
toggle?.click();
await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
const after = row?.getBoundingClientRect().height || 0;
const wrap = document.querySelector('.detail-wrap');
wrap.scrollTop = wrap.scrollHeight * 0.55;
await new Promise(resolve => setTimeout(resolve, 250));
const unmounted = !document.querySelector('[data-view-key="tool:call-1"]');
document.querySelector('button[title="First"]')?.click();
await new Promise(resolve => setTimeout(resolve, 350));
return {
before,
after,
unmounted,
restored: Boolean(document.querySelector('[data-view-key="tool:call-1"].open')),
};
})()` , true );
assert ( disclosure . after > disclosure . before , `expanded tool row remeasures from ${ disclosure . before } px to ${ disclosure . after } px` );
assert ( disclosure . unmounted , 'the expanded tool row unmounts outside overscan' );
assert ( disclosure . restored , 'disclosure state survives unmount and remount' );
await win . webContents . executeJavaScript ( `window.location.hash = '#/sessions'` , true );
await waitFor ( win . webContents , `!document.querySelector('.virtual-timeline')` , 'session detail deactivation' );
2026-07-14 18:11:51 +08:00
await win . webContents . executeJavaScript ( `(async () => {
const search = document.querySelector('#search');
search.value = 'SENTINEL';
search.dispatchEvent(new Event('input', { bubbles: true }));
await new Promise(resolve => setTimeout(resolve, 300));
})()` , true );
2026-07-14 03:45:39 +08:00
await win . webContents . executeJavaScript (
2026-07-14 18:11:51 +08:00
`window.location.hash = '#/sessions/ ${ sessionId } ?focus= ${ focusMessageUuid } '` ,
2026-07-14 03:45:39 +08:00
true ,
);
await waitFor (
win . webContents ,
2026-07-14 18:11:51 +08:00
`document.querySelector('[data-uuid=" ${ focusMessageUuid } "].is-focused')` ,
2026-07-14 03:45:39 +08:00
'offscreen UUID focus' ,
);
const focusState = await win . webContents . executeJavaScript ( `(() => {
2026-07-14 18:11:51 +08:00
const target = document.querySelector('[data-uuid=" ${ focusMessageUuid } "].is-focused');
2026-07-14 03:45:39 +08:00
const wrap = document.querySelector('.detail-wrap');
const targetRect = target.getBoundingClientRect();
const wrapRect = wrap.getBoundingClientRect();
return {
current: Number(document.querySelector('.msg-nav-current')?.textContent),
visible: targetRect.bottom > wrapRect.top && targetRect.top < wrapRect.bottom,
};
})()` , true );
2026-07-14 18:11:51 +08:00
assert ( focusState . visible , `UUID navigation mounts and reveals ${ focusMessageUuid } (viewport ends at item ${ focusState . current } )` );
const codexDisplayState = await win . webContents . executeJavaScript ( `(async () => {
const tool = document.querySelector('[data-view-key="tool:call-codex-exec"]');
tool?.querySelector('.toolcall-toggle')?.click();
tool?.querySelector('.raw-toggle')?.click();
await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
return {
open: tool?.classList.contains('open'),
raw: tool?.querySelector('.raw-toggle')?.classList.contains('active'),
};
})()` , true );
assert ( codexDisplayState . open && codexDisplayState . raw , 'Codex exec disclosure and Raw state update without rebuilding its presentation' );
codexExecOutput = JSON . stringify ([{
type : 'input_text' ,
text : 'Script completed\nWall time 0.1 seconds\nOutput:\n{"ok":true,"revision":2}' ,
}]);
replaceToolResult ( win , 'call-codex-exec' , codexExecOutput );
await waitFor (
win . webContents ,
`document.querySelector('[data-view-key="tool:call-codex-exec"] .toolcall-raw')?.textContent.includes('revision')` ,
'updated Codex exec result' ,
);
const updatedCodexDisplayState = await win . webContents . executeJavaScript ( `(() => {
const tool = document.querySelector('[data-view-key="tool:call-codex-exec"]');
return {
open: tool?.classList.contains('open'),
raw: tool?.querySelector('.raw-toggle')?.classList.contains('active'),
};
})()` , true );
assert ( updatedCodexDisplayState . open && updatedCodexDisplayState . raw , 'Codex exec disclosure and Raw state survive a result update' );
2026-07-14 03:45:39 +08:00
2026-07-14 18:11:51 +08:00
await win . webContents . executeJavaScript (
`document.querySelector('[data-uuid=" ${ focusMessageUuid } "] .truncated-btn')?.click()` ,
true ,
);
await waitFor (
win . webContents ,
`document.querySelector('[data-uuid=" ${ focusMessageUuid } "]')?.textContent.includes('FULL TEXT SENTINEL')` ,
'expanded full message text' ,
);
const fullTextSearchState = await win . webContents . executeJavaScript ( `(() => {
const target = document.querySelector('[data-uuid=" ${ focusMessageUuid } "]');
return {
highlighted: [...target.querySelectorAll('mark')].some(mark => mark.textContent === 'SENTINEL'),
truncatedButtonRemoved: !target.querySelector('.truncated-btn'),
};
})()` , true );
assert (
fullTextSearchState . highlighted && fullTextSearchState . truncatedButtonRemoved ,
'full-text expansion re-renders the row and preserves search highlighting' ,
);
await delay ( 250 );
await win . webContents . executeJavaScript ( `(() => {
const original = window.marked.parse;
const originalJsonParse = JSON.parse;
const codexExecOutput = ${ JSON . stringify ( codexExecOutput ) } ;
const trackedPrefixes = [...document.querySelectorAll('.virtual-timeline-row [data-uuid]')]
.map(element => element.getAttribute('data-uuid'))
.filter(uuid => /^message-\d+$/.test(uuid))
.map(uuid => 'Message ' + uuid.slice('message-'.length) + ' ');
let calls = 0;
let codexExecCalls = 0;
window.marked.parse = function timelineMarkdownProbe(...args) {
const text = String(args[0] || '');
if (trackedPrefixes.some(prefix => text.startsWith(prefix))) calls++;
return original.apply(this, args);
};
JSON.parse = function timelineJsonProbe(value, ...args) {
if (value === codexExecOutput) codexExecCalls++;
return originalJsonParse.call(this, value, ...args);
};
window.__timelineMarkdownProbe = {
calls: () => calls,
codexExecCalls: () => codexExecCalls,
restore: () => {
window.marked.parse = original;
JSON.parse = originalJsonParse;
},
};
})()` , true );
const stationaryAnchorBefore = await win . webContents . executeJavaScript ( `(() => {
const wrap = document.querySelector('.detail-wrap');
const wrapRect = wrap.getBoundingClientRect();
const anchorRow = [...document.querySelectorAll('.virtual-timeline-row')]
.find(row => {
const rect = row.getBoundingClientRect();
return rect.bottom > wrapRect.top && rect.top < wrapRect.bottom;
});
const anchorElement = anchorRow?.querySelector('[data-uuid]');
return anchorElement && {
uuid: anchorElement.getAttribute('data-uuid'),
offset: anchorRow.getBoundingClientRect().top - wrapRect.top,
};
})()` , true );
const stationaryTraces = [];
for ( let runIndex = 0 ; runIndex < stationaryAppendRuns ; runIndex ++ ) {
stationaryTraces . push ( await traceStationaryAppend (
win ,
firstStationaryAppendIndex + runIndex ,
messageCount + runIndex + 1 ,
runIndex ,
));
await delay ( 250 );
}
const stationaryAnchorSelector = `[data-uuid=" ${ stationaryAnchorBefore ? . uuid } "]` ;
const stationaryAnchorAfter = await win . webContents . executeJavaScript ( `(() => {
const wrap = document.querySelector('.detail-wrap');
const target = document.querySelector( ${ JSON . stringify ( stationaryAnchorSelector ) } );
const row = target?.closest('.virtual-timeline-row');
return target && {
uuid: target.getAttribute('data-uuid'),
offset: row.getBoundingClientRect().top - wrap.getBoundingClientRect().top,
};
})()` , true );
const unchangedRowRenderCalls = await win . webContents . executeJavaScript ( `(() => {
const calls = {
markdown: window.__timelineMarkdownProbe.calls(),
codexExec: window.__timelineMarkdownProbe.codexExecCalls(),
};
window.__timelineMarkdownProbe.restore();
delete window.__timelineMarkdownProbe;
return calls;
})()` , true );
assert ( unchangedRowRenderCalls . markdown === 0 , `three tail appends perform zero Markdown formatting calls for unchanged mounted rows (got ${ unchangedRowRenderCalls . markdown } )` );
assert ( unchangedRowRenderCalls . codexExec === 0 , `three tail appends perform zero Codex exec JSON decodes for an unchanged mounted row (got ${ unchangedRowRenderCalls . codexExec } )` );
assert (
stationaryAnchorBefore ? . uuid === stationaryAnchorAfter ? . uuid
&& Math . abs ( stationaryAnchorBefore . offset - stationaryAnchorAfter . offset ) < 2 ,
`stationary live commits preserve reader anchor ${ stationaryAnchorBefore ? . uuid } ` ,
);
for ( const [ runIndex , trace ] of stationaryTraces . entries ()) {
if ( trace . maxTaskMs >= 8.33 ) console . log ( `SLOWEST RENDERER TASK ${ runIndex + 1 } : ${ JSON . stringify ( trace . slowestChildren ) } ` );
assert ( trace . maxTaskMs < 8.33 , `stationary live commit ${ runIndex + 1 } stays inside a 120Hz renderer task budget ( ${ trace . maxTaskMs . toFixed ( 2 ) } ms across ${ trace . tasks } tasks)` );
}
setTimeout (() => appendMessage ( win , scrollingAppendIndex ), 250 );
2026-07-14 03:45:39 +08:00
const scrollProbe = await win . webContents . executeJavaScript ( `new Promise(resolve => {
const wrap = document.querySelector('.detail-wrap');
2026-07-15 01:35:05 +08:00
const totalBeforeGesture = Number(document.querySelector('.flap-number')?.getAttribute('aria-label'));
const originalScrollTo = wrap.scrollTo.bind(wrap);
let programmaticScrolls = 0;
const blockAutomaticScrollEnd = event => event.stopImmediatePropagation();
wrap.addEventListener('scrollend', blockAutomaticScrollEnd, true);
wrap.scrollTo = (...args) => {
programmaticScrolls++;
return originalScrollTo(...args);
};
2026-07-14 03:45:39 +08:00
const gaps = [];
const startedAt = performance.now();
let previous = startedAt;
2026-07-15 01:35:05 +08:00
wrap.dispatchEvent(new WheelEvent('wheel', { deltaY: 70, bubbles: true }));
2026-07-14 03:45:39 +08:00
function frame(now) {
gaps.push(now - previous);
previous = now;
2026-07-15 01:35:05 +08:00
if (now - startedAt >= 400) wrap.scrollTop += 70;
2026-07-14 03:45:39 +08:00
if (now - startedAt < 1200) requestAnimationFrame(frame);
else {
const wrapRect = wrap.getBoundingClientRect();
const anchorRow = [...document.querySelectorAll('.virtual-timeline-row')]
.find(row => {
const rect = row.getBoundingClientRect();
return rect.bottom > wrapRect.top && rect.top < wrapRect.bottom;
2026-07-15 01:35:05 +08:00
});
2026-07-14 03:45:39 +08:00
const anchorElement = anchorRow?.querySelector('[data-uuid]');
2026-07-15 01:35:05 +08:00
const totalBeforeScrollEnd = Number(document.querySelector('.flap-number')?.getAttribute('aria-label'));
const flapBeforeScrollEnd = Boolean(document.querySelector('.flap-slot.flipping'));
wrap.scrollTo = originalScrollTo;
wrap.removeEventListener('scrollend', blockAutomaticScrollEnd, true);
wrap.dispatchEvent(new Event('scrollend'));
2026-07-14 03:45:39 +08:00
resolve({
2026-07-15 01:35:05 +08:00
totalBeforeGesture,
totalBeforeScrollEnd,
flapBeforeScrollEnd,
programmaticScrolls,
2026-07-14 03:45:39 +08:00
maxFrameGap: Math.max(...gaps),
frames: gaps.length,
rows: document.querySelectorAll('.virtual-timeline-row').length,
distanceFromTail: wrap.scrollHeight - wrap.clientHeight - wrap.scrollTop,
anchor: anchorElement && {
uuid: anchorElement.getAttribute('data-uuid'),
offset: anchorRow.getBoundingClientRect().top - wrapRect.top,
},
});
}
}
requestAnimationFrame(frame);
})` , true );
await waitFor (
win . webContents ,
2026-07-14 18:11:51 +08:00
`document.querySelector('.flap-number')?.getAttribute('aria-label') === ' ${ messageCount + stationaryAppendRuns + 1 } '` ,
2026-07-14 03:45:39 +08:00
'reader-position live update' ,
);
2026-07-15 01:35:05 +08:00
await waitFor (
win . webContents ,
`document.querySelector('.flap-slot.flipping')` ,
'post-scrollend flap animation' ,
);
2026-07-14 03:45:39 +08:00
const readerState = await win . webContents . executeJavaScript ( `(() => {
const wrap = document.querySelector('.detail-wrap');
const anchorElement = document.querySelector(
${ JSON . stringify ( `[data-uuid=" ${ scrollProbe . anchor ? . uuid } "]` ) } ,
);
const anchorRow = anchorElement?.closest('.virtual-timeline-row');
return {
current: Number(document.querySelector('.msg-nav-current')?.textContent),
anchor: anchorElement && {
uuid: anchorElement.getAttribute('data-uuid'),
offset: anchorRow.getBoundingClientRect().top - wrap.getBoundingClientRect().top,
},
};
})()` , true );
assert ( scrollProbe . rows < 60 , `live scrolling keeps mounted rows bounded ( ${ scrollProbe . rows } )` );
2026-07-15 01:35:05 +08:00
assert (
scrollProbe . totalBeforeScrollEnd === scrollProbe . totalBeforeGesture ,
`wheel-to-scrollend freezes the visible timeline ( ${ scrollProbe . totalBeforeGesture } -> ${ scrollProbe . totalBeforeScrollEnd } )` ,
);
assert (
scrollProbe . programmaticScrolls === 0 ,
`wheel-to-scrollend performs zero programmatic scrollTo calls (got ${ scrollProbe . programmaticScrolls } )` ,
);
assert ( ! scrollProbe . flapBeforeScrollEnd , 'wheel-to-scrollend does not start the flap animation' );
2026-07-14 03:45:39 +08:00
assert ( scrollProbe . anchor , 'reader anchor is captured before the deferred live commit' );
assert (
scrollProbe . distanceFromTail > 1000
2026-07-14 18:11:51 +08:00
&& readerState . current < messageCount + stationaryAppendRuns + 1
2026-07-14 03:45:39 +08:00
&& readerState . anchor ? . uuid === scrollProbe . anchor ? . uuid
&& Math . abs ( readerState . anchor . offset - scrollProbe . anchor . offset ) < 2 ,
`live append preserves reader anchor ${ scrollProbe . anchor ? . uuid } ( ${ scrollProbe . anchor ? . offset } px -> ${ readerState . anchor ? . offset } px)` ,
);
assert ( scrollProbe . maxFrameGap < 250 , `live scroll has no catastrophic long frame ( ${ scrollProbe . maxFrameGap . toFixed ( 1 ) } ms)` );
2026-07-14 18:11:51 +08:00
const updatedReaderText = `Updated ${ scrollProbe . anchor . uuid } ${ 'content identity ' . repeat ( 20 ) } ` ;
await win . webContents . executeJavaScript ( `(() => {
const original = window.marked.parse;
const targetUuid = ${ JSON . stringify ( scrollProbe . anchor . uuid ) } ;
const targetText = ${ JSON . stringify ( updatedReaderText ) } ;
const unchangedPrefixes = [...document.querySelectorAll('.virtual-timeline-row [data-uuid]')]
.map(element => element.getAttribute('data-uuid'))
.filter(uuid => uuid !== targetUuid && /^message-\d+$/.test(uuid))
.map(uuid => 'Message ' + uuid.slice('message-'.length) + ' ');
let targetCalls = 0;
let unchangedCalls = 0;
window.marked.parse = function timelineContentIdentityProbe(value, ...args) {
const text = String(value || '');
if (text === targetText) targetCalls++;
if (unchangedPrefixes.some(prefix => text.startsWith(prefix))) unchangedCalls++;
return original.call(this, value, ...args);
};
window.__timelineContentIdentityProbe = {
calls: () => ({ target: targetCalls, unchanged: unchangedCalls }),
restore: () => { window.marked.parse = original; },
};
})()` , true );
replaceMessageText ( win , scrollProbe . anchor . uuid , updatedReaderText );
await waitFor (
win . webContents ,
`document.querySelector('[data-uuid= ${ JSON . stringify ( scrollProbe . anchor . uuid ) } ]')?.textContent.includes( ${ JSON . stringify ( updatedReaderText . slice ( 0 , 40 )) } )` ,
'visible message content update' ,
);
const contentIdentityCalls = await win . webContents . executeJavaScript ( `(() => {
const calls = window.__timelineContentIdentityProbe.calls();
window.__timelineContentIdentityProbe.restore();
delete window.__timelineContentIdentityProbe;
return calls;
})()` , true );
assert ( contentIdentityCalls . target === 1 , `updated mounted row recomputes its Markdown once (got ${ contentIdentityCalls . target } )` );
assert ( contentIdentityCalls . unchanged === 0 , `updated mounted row leaves other mounted Markdown cached (got ${ contentIdentityCalls . unchanged } )` );
assert (
ipcReads . messages === 1
&& ipcReads . toolCalls === 1
&& ipcReads . toolResults === 1
&& ipcReads . subagents === 1
&& ipcReads . workflows === 1
&& ipcReads . summaries === 1
&& ipcReads . patches === 6
&& ipcReads . patchMessageRows . every ( count => count === 1 ),
`live updates use six single-message patches after one full snapshot ( ${ JSON . stringify ( ipcReads ) } )` ,
);
2026-07-14 03:45:39 +08:00
await win . webContents . executeJavaScript ( `document.querySelector('button[title="Last"]')?.click()` , true );
await waitFor (
win . webContents ,
2026-07-14 18:11:51 +08:00
`document.querySelector('.msg-nav-current')?.textContent === ' ${ messageCount + stationaryAppendRuns + 1 } '` ,
2026-07-14 03:45:39 +08:00
'last-item navigation' ,
);
await waitFor (
win . webContents ,
`(() => { const wrap = document.querySelector('.detail-wrap'); return wrap.scrollHeight - wrap.clientHeight - wrap.scrollTop < 2; })()` ,
'last-item scroll settlement' ,
);
2026-07-15 01:35:05 +08:00
await win . webContents . executeJavaScript ( `new Promise(resolve => {
const wrap = document.querySelector('.detail-wrap');
wrap.scrollTop = Math.max(0, wrap.scrollHeight - wrap.clientHeight - 20);
requestAnimationFrame(() => requestAnimationFrame(resolve));
})` , true );
await delay ( 200 );
await win . webContents . executeJavaScript ( `(() => {
const wrap = document.querySelector('.detail-wrap');
wrap.dispatchEvent(new WheelEvent('wheel', { deltaY: -24, bubbles: true }));
})()` , true );
await delay ( 200 );
appendMessage ( win , nearTailEscapeAppendIndex );
await delay ( 150 );
const nearTailPending = await win . webContents . executeJavaScript ( `(() => {
const wrap = document.querySelector('.detail-wrap');
return {
total: Number(document.querySelector('.flap-number')?.getAttribute('aria-label')),
distanceFromTail: wrap.scrollHeight - wrap.clientHeight - wrap.scrollTop,
};
})()` , true );
assert (
nearTailPending . total === messageCount + stationaryAppendRuns + 1 ,
'near-tail upward intent keeps the append pending until scrollend' ,
);
await win . webContents . executeJavaScript (
`document.querySelector('.detail-wrap')?.dispatchEvent(new Event('scrollend'))` ,
true ,
);
2026-07-14 03:45:39 +08:00
await waitFor (
win . webContents ,
2026-07-14 18:11:51 +08:00
`document.querySelector('.flap-number')?.getAttribute('aria-label') === ' ${ messageCount + stationaryAppendRuns + 2 } '` ,
2026-07-15 01:35:05 +08:00
'near-tail upward append settlement' ,
);
await delay ( 500 );
const nearTailSettled = await win . webContents . executeJavaScript ( `(() => {
const wrap = document.querySelector('.detail-wrap');
return {
current: Number(document.querySelector('.msg-nav-current')?.textContent),
total: Number(document.querySelector('.flap-number')?.getAttribute('aria-label')),
distanceFromTail: wrap.scrollHeight - wrap.clientHeight - wrap.scrollTop,
};
})()` , true );
assert (
nearTailSettled . current < nearTailSettled . total && nearTailSettled . distanceFromTail > 20 ,
`near-tail upward intent is not pulled back to the tail ( ${ JSON . stringify ( nearTailSettled ) } )` ,
);
await win . webContents . executeJavaScript ( `document.querySelector('button[title="Last"]')?.click()` , true );
await waitFor (
win . webContents ,
`document.querySelector('.msg-nav-current')?.textContent === ' ${ messageCount + stationaryAppendRuns + 2 } '` ,
'last-item navigation after upward escape' ,
);
await waitFor (
win . webContents ,
`(() => { const wrap = document.querySelector('.detail-wrap'); return wrap.scrollHeight - wrap.clientHeight - wrap.scrollTop < 2; })()` ,
'tail re-entry settlement' ,
);
appendMessage ( win , tailAppendIndex );
await waitFor (
win . webContents ,
`document.querySelector('.flap-number')?.getAttribute('aria-label') === ' ${ messageCount + stationaryAppendRuns + 3 } '` ,
2026-07-14 03:45:39 +08:00
'tail-follow total update' ,
);
await delay ( 1000 );
const tailState = await win . webContents . executeJavaScript ( `(() => {
const wrap = document.querySelector('.detail-wrap');
return {
current: Number(document.querySelector('.msg-nav-current')?.textContent),
total: Number(document.querySelector('.flap-number')?.getAttribute('aria-label')),
scrollTop: wrap.scrollTop,
maxScrollTop: wrap.scrollHeight - wrap.clientHeight,
distanceFromTail: wrap.scrollHeight - wrap.clientHeight - wrap.scrollTop,
};
})()` , true );
assert (
2026-07-15 01:35:05 +08:00
tailState . current === messageCount + stationaryAppendRuns + 3 && tailState . distanceFromTail < 2 ,
`tail follow reaches item ${ messageCount + stationaryAppendRuns + 3 } ( ${ JSON . stringify ( tailState ) } )` ,
2026-07-14 03:45:39 +08:00
);
const reduction = ( 1 - initial . rows / initial . total ) * 100 ;
console . log ( `PERF: ${ initial . total } timeline items -> ${ initial . rows } mounted rows ( ${ reduction . toFixed ( 2 ) } % fewer roots)` );
console . log ( `PERF: ${ scrollProbe . frames } frames, max frame gap ${ scrollProbe . maxFrameGap . toFixed ( 1 ) } ms during live scroll` );
win . destroy ();
}
app . whenReady ()
. then ( run )
. catch ( error => {
failures ++ ;
console . error ( error . stack || error );
})
. finally (() => {
for ( const channel of channels ) ipcMain . removeHandler ( channel );
app . exit ( failures ? 1 : 0 );
});