* perf(context): cut context-tab render, hover and DNA costs P0 — interaction/caching: - isolate pointer hovers: chart-local hover stays immediate, cross-card previews trail (140ms; 220ms in DNA mode) so sweeps cannot stack full-view renders; - cache pure projections (assemble, DNA/file derivations) across hover/remount; - remove forced layout from the flow/timing read paths, gate the hidden sidebar copy, memoize picker options and tool tallies. P1 — windowing/lazy rendering: - window trend bars, file-activity rows, timing-strip segments (dense gradient) and the browser DNA feed; off-screen rows use content-visibility; - lazy-mount the browser card and the below-fold event/file row behind height-matched skeletons, mounted on an idle slice / near the viewport; - window DNA bands on long total-mode logs, tighten the DNA window, sample per-bar gradient stops, and keep DNA+delta eager so its churn axis stays exact; - force-mount + reveal for stats file-pill jumps into the lazy row. Rendering-only by construction: axes, tooltips, ordering, DNA hit-testing and every readout still come from the full model; scrolling only changes which rows are in the DOM. Measured on a 987-step session: mount 986-1251ms -> ~510ms, first paint 480-592ms -> ~80ms, zero long tasks; tab DOM 15,929 -> 3,226 (below-fold mounted ~4.7k); hover 30 -> 0 long tasks; DNA toggle 875 -> ~230ms. Tests: new window/lazy/DNA/cache specs; lint, typecheck and 138-file 100% per-file coverage green. * fix(context): second-review follow-ups - keep DNA bands fresh per header epoch, and force eager DNA+delta on long logs so per-key churn axes stay exact; - correct windowed spacer heights for the container flex gap: the file list and the DNA feed now reproduce the full scroll height exactly; - re-translate memoized picker/tooltip labels when the locale changes; - re-observe lazy mounts across detach/reattach (a collapsed sidebar copy) via a callback ref, keep forced mounts for stats jumps, and clear the chart's local hover/DNA state on mouse leave; - shrink the window for a mount that already has DNA on, and add the async subagent count to the flow-measurement deps; - repair the skeleton background rule and restore the accidentally globalized .lc-heat-ctl .lc-card-sub selector.
39 lines
1.5 KiB
TypeScript
39 lines
1.5 KiB
TypeScript
import assert from 'node:assert/strict'
|
|
import { describe, test } from 'vitest'
|
|
import { assembleCached, cachedByData } from '../../../src/client/components/derivedCache'
|
|
import type { ContextHeaders } from '../../../src/shared/types'
|
|
import { timeline } from './contextViewHarness'
|
|
|
|
const headers: ContextHeaders = {
|
|
headers: [{ seq: 1, time: 1, systemTokens: 0, tools: [] }],
|
|
}
|
|
|
|
describe('derived cache', () => {
|
|
test('cachedByData reuses a value per projection and key', () => {
|
|
const data = timeline()
|
|
let calls = 0
|
|
const compute = () => { calls++; return { calls } }
|
|
const first = cachedByData(data, 'k', compute)
|
|
const again = cachedByData(data, 'k', compute)
|
|
assert.equal(first, again)
|
|
assert.equal(calls, 1)
|
|
const other = cachedByData(data, 'other', compute)
|
|
assert.notEqual(first, other)
|
|
assert.equal(calls, 2)
|
|
const otherData = timeline()
|
|
assert.notEqual(cachedByData(otherData, 'k', compute), first)
|
|
assert.equal(calls, 3)
|
|
})
|
|
|
|
test('assembleCached reuses one assembled view per projection, headers and step', () => {
|
|
const data = timeline()
|
|
const live = assembleCached(data, null, null)
|
|
assert.equal(assembleCached(data, null, null), live)
|
|
const withHeaders = assembleCached(data, headers, null)
|
|
assert.notEqual(withHeaders, live)
|
|
assert.equal(assembleCached(data, headers, null), withHeaders)
|
|
const otherHeaders: ContextHeaders = { headers: [] }
|
|
assert.notEqual(assembleCached(data, otherHeaders, null), withHeaders)
|
|
})
|
|
})
|