perf: move conversations into an external store and window the message timeline

This commit is contained in:
mesalogo
2026-10-03 02:53:51 +08:00
parent f571301764
commit fdf7d83b20
17 changed files with 2922 additions and 1065 deletions
+2 -2
View File
@@ -575,7 +575,7 @@ try {
return { latency: { count: 1, p50: openMs, p95: openMs, max: openMs }, fields: { openMs, renderedMessages } }
})
await measure('scroll-long-conversation', 'Wheel-scroll up through the long conversation for 3 s', async () => {
await measure('scroll-long-conversation', 'Wheel-scroll up through the long conversation for 3 s', () => withRendererProfile('scroll-long-conversation', async () => {
const point = await js(`(() => { const e = document.querySelector('.chat-history-pane[data-active=true] #chat-message-list') || document.querySelector('.chat-history-pane[data-active=true]'); const r = e.getBoundingClientRect(); return { x: Math.round(r.x + r.width / 2), y: Math.round(r.y + r.height / 2) }; })()`)
const scroller = `(() => { const p = document.querySelector('.chat-history-pane[data-active=true]'); const c = p && [p, ...p.querySelectorAll('*')].find(e => e.scrollHeight > e.clientHeight + 4 && /(auto|scroll)/.test(getComputedStyle(e).overflowY)); return c ? c.scrollTop : null; })()`;
const before = await js(scroller)
@@ -587,7 +587,7 @@ try {
await sleep(300)
const after = await js(scroller)
return { fields: { scrollTopBefore: before, scrollTopAfter: after, scrolledPx: before === null || after === null ? null : before - after } }
})
}))
await screenshot('long-conversation')
+22 -5
View File
@@ -120,11 +120,22 @@ app.whenReady().then(async () => {
await waitFor("!document.querySelector('.runtime-checklist__content')")
await evaluate(`document.querySelector('.chat-history-pane[data-active="true"] .chat').scrollTop = 400`)
await waitFor("document.querySelector('.chat-scroll-to-bottom') !== null")
const toggleAnchor = await evaluate(`document.querySelector('.chat-history-pane[data-active="true"] article').getBoundingClientRect().top`)
// The timeline is windowed (PERF-14): the first mounted row can change while
// the reader's place stays put, so track one visible message by id.
const anchorRow = await evaluate(`(() => {
const pane = document.querySelector('.chat-history-pane[data-active="true"]');
const top = pane.querySelector('.chat').getBoundingClientRect().top;
const row = [...pane.querySelectorAll('article')].find(a => a.getBoundingClientRect().bottom > top + 1);
const id = row.closest('[data-message-id]')?.dataset.messageId ?? row.id;
return { id, top: row.getBoundingClientRect().top };
})()`)
const anchorTop = `(() => { const pane = document.querySelector('.chat-history-pane[data-active="true"]');
const wrapper = pane.querySelector('[data-message-id=${JSON.stringify(anchorRow.id)}]');
return (wrapper?.querySelector('article') ?? document.getElementById(${JSON.stringify(anchorRow.id)})).getBoundingClientRect().top; })()`
for (const enabled of [true, false]) {
await evaluate(`for (const e of [document.documentElement, document.querySelector('.app-shell')]) e.dataset.frostedGlass = '${enabled}'`)
await evaluate('new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)))')
assert.equal(await evaluate(`document.querySelector('.chat-history-pane[data-active="true"] article').getBoundingClientRect().top`), toggleAnchor, 'toggle preserves reader position')
assert.equal(await evaluate(anchorTop), anchorRow.top, 'toggle preserves reader position')
}
for (const theme of ['light', 'dark']) {
await evaluate(`document.documentElement.dataset.theme = '${theme}'`)
@@ -169,10 +180,16 @@ app.whenReady().then(async () => {
await waitFor(`document.querySelector('.chat-history-pane[data-active="true"]').dataset.conversationId === 'a'`)
assert.equal(await evaluate(`document.querySelector('.chat-history-pane[data-active="true"] .chat').scrollTop`), 400, 'keep-alive scroll position')
await evaluate(`document.querySelector('.chat-history-pane[data-active="true"] .chat').scrollTop = 0`)
const anchor = await evaluate(`document.querySelector('.chat-history-pane[data-active="true"] article').getBoundingClientRect().top`)
const firstRowId = `document.querySelector('.chat-history-pane[data-active="true"] [data-message-id]').dataset.messageId`
const prependId = await evaluate(firstRowId)
const rowTop = id => `document.querySelector('.chat-history-pane[data-active="true"] [data-message-id=${JSON.stringify(id)}] article').getBoundingClientRect().top`
const anchor = await evaluate(rowTop(prependId))
await click('.load-earlier-messages')
await waitFor(`document.querySelectorAll('.chat-history-pane[data-active="true"] article').length === 100`)
const restoredAnchor = await evaluate(`document.querySelectorAll('.chat-history-pane[data-active="true"] article')[20].getBoundingClientRect().top`)
// Earlier rows load above; the windowed list may mount some of them, but the
// row the reader was looking at must not move.
await waitFor(`${firstRowId} !== ${JSON.stringify(prependId)}`)
await evaluate('new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)))')
const restoredAnchor = await evaluate(rowTop(prependId))
assert.ok(Math.abs(restoredAnchor - anchor) < 1, 'prepend preserves message anchor')
await click('.runtime-checklist__toggle')
await waitFor(`!!document.querySelector('.runtime-checklist__content')`)