Skip to content

fix(session-ui): reuse Pierre file views, window medium files, cache parsed diffs - #57551

Merged
kitlangton merged 1 commit into
betafrom
pierre-diffs-fixes
Oct 20, 2026
Merged

kitlangton merged 1 commit into
betafrom
pierre-diffs-fixes

Conversation

@kitlangton

Copy link
Copy Markdown
Contributor

Three fixes to how session-ui drives @pierre/diffs, from the Pierre speed research (Oct 20).

1. TextViewer updates in place

Before this change, every reactive change (contents, overflow, any option) passed reset: true. That ran cleanUp(), cleared container.innerHTML and built a new PierreFile with a new shadow root. Now the instance is rebuilt only when its virtualizer changes, because a Pierre instance is bound to the virtualizer it was built with. Any other change goes through setOptions and render({ forceRender }), the same path DiffViewer already uses.

2. Medium files and diffs are windowed

VIRTUALIZE_BYTES = 500_000 (text) and > 500 lines (v2 review diffs) are replaced by one shared virtualizeLineThreshold = 200. Pierre keeps the viewport plus 1,000 px of overscan on each side mounted, which is about 100 rows. At 200 lines, windowing breaks even on main-thread render time (crossover below), and above that it wins on both DOM size and render time.

Two problems showed up once windowing covered these sizes, and both are fixed here:

  • Opening scrolled to the bottom. Until the worker pool is ready, Pierre draws nothing. When the virtualizer's first pass finds an empty file at the very top of its scroller, it anchors to the file's bottom edge. Once the rows arrive, it scrolls to the end of the file. The file view and the v2 review preview both put the file at the top of their scroller, so they hit this. Viewers that are about to window now wait for pool.initialize() before they create the instance. This only matters on a cold start.
  • Jumping to an off-screen line in a windowed diff. Comment focus in the v2 review sets the selection, then polls for the comment's anchor element. In a windowed diff that anchor never renders. Before this change, fixDiffSelection turned the off-screen range into null and cleared it. Now the range is kept and Pierre applies it when those rows render. DiffViewer also scrolls the selection into the window with getLinePosition and Virtualizer.scrollTo. Text files already work: file links jump by scroll offset, and VirtualizedFile keeps the selection.

3. The parsed-diff cache is used

fileDiffFromContent computed a cacheKey but never read or wrote the cache, so normalize() ran parseDiffFromFile again on every call. Patch diffs and content diffs now share one LRU (the former patchFileDiffCache, now fileDiffCache). Its keys carry a kind prefix, and it stays capped at 16 entries.

Evidence

The research method, with the production File and normalize bundled by Vite in headless Chromium 153. Studio, one job, before and after built from the same tree (before = origin/beta sources), median of 5 page loads. Warm mounts: the worker cache was filled first. DOM = elements in the light DOM plus the shadow root, and token-span counts vary with highlight timing, so mounted [data-line] rows are listed too. Render ms = synchronous mount plus a forced layout.

Fix 1: prop change on a mounted text file (4 overflow toggles + 4 appends per load)

before after
Pierre instances built per change 1 (8/8 rebuilt) 0 (0/8)
150 lines, overflow toggle 20.6 ms 18.4 ms
150 lines, append a line 14.0 ms 18.8 ms
2,500 lines, overflow toggle (with fix 2) 202.7 ms 11.0 ms
2,500 lines, append a line (with fix 2) 210.6 ms 9.5 ms

At 150 lines a full rebuild costs about as much as an in-place render. Keeping the instance mainly saves the shadow root, styles and selection state. The time savings show up once the file is large.

Fix 2: mount

case rows mounted DOM elements render ms ms until onRendered
text, 480 lines 481 → 150 2,028 → 705 35.8 → 12.9 43 → 117
text, 2,500 lines 2,501 → 150 10,108 → 705 220.7 → 14.7 243 → 118
v2 split diff, 480 lines, expanded 960 → 300 4,139 → 3,768¹ 85.9 → 25.9 121 → 273

¹ highlighted spans had landed when the after run counted. The windowed 2,500-line diff mounts 948 elements.

Trade-off: a windowed view first renders on the virtualizer's next frame. Below about 1,000 lines on a fast machine, the time until onRendered goes up (about 120 ms minimum) even though main-thread work goes down.

Crossover, forced virtualize true/false, Mac mini under load, median of 5 (render ms, DOM elements):

lines 100 200 250 300 400
full 134 / 947 102 / 1,787 178 / 2,207 248 / 2,627 682 / 3,467
windowed 28 / 947* 101 / 948 171 / 948 219 / 948 97 / 948

* at 100 lines the window already holds the whole diff.

Fix 3: normalize() called 10× on the same content diff (bun, Studio)

parse calls total
480 lines 10 → 1 14.8 → 1.3 ms
2,500 lines 10 → 1 141.5 → 13.4 ms

Tests

  • session-diff.test.ts: cache hit (same fileDiff identity; fails before this change), miss on changed content or name, eviction after 16 newer entries.
  • session-review-file-preview-v2-virtualize.test.ts: threshold boundary, and a 480-line diff virtualizes.
  • component-tests/file-viewer.spec.ts (fixture, Storybook Playwright): the TextViewer keeps the same diffs-container across an overflow change and an append. A 480-line file opens at the top with fewer than 200 rows mounted, keeps its selection and comment marks, and follows a line-number scroll jump. A windowed diff scrolls to an off-screen selection and marks it. 24/24 at --workers=2 --repeat-each=8. file, file-tool, session-review-comments and line-comment-v2 specs: 20/20.

The session-ui package has no bun harness for rendering Solid components, which is why the instance-identity check is a fixture component test rather than a bun test.

Size

Code +139 / −69 (file.tsx, session-diff.ts, virtualizer.ts, v2 threshold). Tests +161 / −5.

Out of scope: the MutationObserver / rAF cleanup and the highlighter swap.

bun run check on origin/beta currently fails on gui-extension-no-module-state-tsx in packages/gui-extensions/src/details/popover.tsx (from #57528), which is not touched here. bun typecheck for session-ui and bun run lint:changed origin/beta are clean.

@kitlangton
kitlangton merged commit 8afeea3 into beta Oct 20, 2026
11 of 19 checks passed
@kitlangton
kitlangton deleted the pierre-diffs-fixes branch October 20, 2026 23:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant