perf(review): virtualize Guided Review file cards - #1158
Conversation
|
Reviewed in depth (typecheck clean, full suite 2585 pass, review-editor DOM tests 166 pass). The core is solid: reviewed-state persistence, saved-guide resume, and progress counts are all owned above the virtualized layer and are untouched, and the AllFilesCodeView / ReviewStateContext changes are inert for non-guide surfaces. Verdict: merge after small fixes. Five things to address:
Test note: both guide test files mock AllFilesCodeView, and happy-dom's IntersectionObserver never delivers entries, so the entire evict/remount band (reconcile, hysteresis, eviction, and findings 1-4) has zero coverage and the max-mounted assertions pass trivially at the eager-seed count. A small fake IntersectionObserver in packages/ui/test-setup/happy-dom.ts with a manual trigger would let you add one regression test each for reviewed-state, collapse-state, and draft survival across a forced evict/remount. Smaller items, take or leave: chapter badges count the Everything else card (01/07 vs the header's 6 sections); GuideSectionCard cardRef is dead code; collapsed shell is 49px vs a 33px placeholder header; onPointerEnter runs getBoundingClientRect per mounted card inside the pointer handler; the aria-hidden placeholders widen the find-in-page gap to whole files (worth a line in the PR body); the fileSummaries assertion in GuideSectionCard.test.tsx checks a prop that no longer exists. Also worth documenting in the PR description since they are real behavior changes: duplicate file refs now dedupe to first placement (later chapters show a jump-up chip instead of a card), stale refs lose the dashed inline row in favor of the outdated chip (this matters most for moved guides), and the unplaced bucket is now a full chapter card. Nice work overall: the manager lifecycle is clean (no observer leaks, all timers cancelled), and reusing AllFilesCodeView removes a lot of duplicated logic. |
|
@backnotprop addressed the review feedback in
The PR description now also documents duplicate-reference placement, stale-reference presentation, the full “Everything else” chapter, and the find-in-page trade-off. Validation:
Remote CI is currently running on |
|
Re-reviewed ef75cc4 finding by finding. All five items are properly fixed:
The three new paths also check out: search reveal has no re-fire loop, the no-IntersectionObserver fallback has no leak or double-registration, and scroll chaining is opt-in with every other consumer keeping overscroll-contain. Approving. Follow-ups worth a note (none blocking, happy to file issues): search reveal steals focus and can un-pin a card with an open composer; type-ahead smooth-scrolls the page on every debounce tick (the all-files panel reveals in place instead); App.tsx handleRevealSearchMatch still routes guide-mode matches to the hidden dock (pre-existing); the useAnnotationToolbar restore path when a composing card IS evicted remains untested; plus the cosmetic nits from the first review (chapter badge count, dead cardRef, placeholder height, pointer-handler rect churn, vestigial fileSummaries assertion). Nice turnaround, and the FakeIntersectionObserver harness is a real improvement to the test setup. |
backnotprop
left a comment
There was a problem hiding this comment.
Verified fix commit ef75cc4 against the full review; details in comments.
Summary
This PR fixes severe Guided Review performance degradation on large diffs while preserving the original two-column chapter layout and individual per-file diff cards.
All file cards remain in document order, but only the cards near the outer viewport mount an expensive Pierre
CodeView. Pierre then virtualizes the lines inside each mounted file.The shared viewport coordinator allows at most 8 file viewers to be mounted at once.
Performance results
Measurements were taken in Chrome using a representative large Guided Review workload.
During fast outer scrolling:
During inner scrolling of a large file:
CodeView.File-content requests are also bounded by the mounted window. The production path initially loads content only for nearby files. React StrictMode produced 8 requests for 4 initial mounts because of its expected development-only double mount, rather than requesting every guide file.
What changed
Outer file-card windowing
Guided Review now uses one shared
GuideViewportManagerfor the entire page.It:
IntersectionObserver;CodeViewinstances;This avoids creating one observer or one expensive viewer per guide reference.
Two-level virtualization
Each admitted file card renders a one-file
AllFilesCodeView.This provides two levels of virtualization:
No custom line pagination or Shadow DOM manipulation was introduced.
Persistent lightweight shells
Every resolved guide file keeps a lightweight shell in document flow, preserving:
When a viewer leaves the mount window, its shell keeps the measured card height and shows a lightweight placeholder.
State preservation
File-card state survives viewer eviction and remounting:
CodeViewscroll position, restored once per viewer mount;CodeViewon each toggle;The focused card remains pinned inside the 8-viewer budget, preserving an open annotation composer and its in-progress draft while the outer page scrolls. Navigation from file chips, search results, AI citations, and annotations force-mounts and activates the destination before invoking Pierre navigation.
Preserved review behavior
The virtualized cards continue to support:
Behavioral details
The virtualized guide intentionally keeps the branch's established placement rules explicit:
outdatedchapter chips rather than dashed inline diff rows;unplacedFilesrender in a full “Everything else” chapter card;Validation
Automated checks on
ef75cc4:bun run typecheck— passedbun run --cwd apps/review build— passedbun run build:hook— passedoverscroll-behavior: autofor guide-card scroll chaininggit diff --check— cleanManual verification:
Trade-off
During extremely fast outer scrolling, a distant file card may briefly display its height-preserving placeholder while the shared viewport window settles. The placeholder is replaced with the real virtualized viewer after scrolling becomes idle. Because evicted diff bodies are not present in the DOM, browser find-in-page covers mounted viewers plus every shell's file path, not all offscreen code at once.
This keeps DOM size and mount work bounded regardless of the total number of guide references.