Expanded file rows now end in one right-aligned meta group: the read window,
the line delta, the failure dot and the stamp+time. The turn/step stamp is the
Context Browser's own (`stepStampOf`, reusing browser.stepAt/stepAtTip): the
first request that carried the op's result node, merged with the op time, or
the time alone when no logged request carries the op yet. Nested Code-Mode ops
scope through the shared `surfaceSeqOf` helper, and the meta group wraps onto
its own right-aligned line on phone-width cards.
Drop the scope line and put the five kind chips in its top-right slot. The
search box now leads the row below, the sort toggle sits on that row's right
edge, and the meta row keeps only the file tally and line deltas. The unused
scope prop, its computation and the files.scopeLatest keys go with the line;
the picked step/turn still reads in the request detail header.
The count-bearing chips widen as their tallies gain digits, so the title row
reserves its folded height on narrow cards — the same guarantee the old
controls band carried, applied to the row the chips now live in.
* 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.
browser.spec.ts (2360), trendChart.spec.ts (1629), contextView.spec.ts (1504), and
overview.spec.ts (1065) exceeded the 1000-line limit. Each splits along its existing
describe seams into same-directory siblings, with the fixtures that more than one
sibling needs extracted once into a shared harness.
The move is verbatim: test titles, describe blocks, and every assertion line are
preserved exactly (2061 tests before and after, no duplicate titles, no skips), and
the suite reports the same coverage.