Skip to content

fix(app): optimize large review panes - #35375

Merged
Hona merged 7 commits into
anomalyco:devfrom
Hona:review-pane-bench
Jul 6, 2026
Merged

fix(app): optimize large review panes#35375
Hona merged 7 commits into
anomalyco:devfrom
Hona:review-pane-bench

Conversation

@Hona

@HonaHona commented Jul 5, 2026

Copy link
Copy Markdown
Member

Summary

  • replace the recursive review file tree with a normalized flat model and bounded TanStack virtualization
  • virtualize filtered review results while preserving path identity, focus, collapse state, and active-row scrolling
  • avoid expensive Kobalte tab-content observation and prewarm the selected diff worker pool
  • render small diffs directly while retaining Pierre virtualization for files above 500 lines
  • add production scaling benchmarks from 1 file / 100 changed lines through 10,000 files / 1,000,000 changed lines

Performance

10,000-file scenarioBeforeAfter
Uncapped 40.29 MB payload8.49 s0.80 s median
VCS-capped 13.0 MB payloadn/a0.50 s median
Rendered tree rows10,10230
Maximum frame gap7.03 sabout 0.17-0.20 s capped

Scaling graph

The legend is rendered in the chart title. Values are stable render time in milliseconds; file counts progress logarithmically.

---
config:
themeVariables:
xyChart:
plotColorPalette: "#e03131, #2f9e44"
---
xychart-beta
title "Key: red = dev baseline; green = optimized"
x-axis "Changed files" [1, 10, 100, 1000, 10000]
y-axis "Stable render (ms)" 0 --> 9000
line [254, 319, 353, 1153, 8487]
line [174, 187, 204, 258, 805]
Loading
FilesDev baselineOptimized
1254 ms174 ms
10319 ms187 ms
100353 ms204 ms
1,0001,153 ms258 ms
10,0008,487 ms805 ms

The VCS-capped result uses REVIEW_PANE_PATCH_BYTE_LIMIT=10000000 to mirror the server aggregate patch cap. Broad filtering across 10,000 results stabilizes in about 50-100 ms.

Test plan

  • bun typecheck in packages/app
  • bun run typecheck:e2e in packages/app
  • bun typecheck in packages/session-ui
  • focused file-tree, path-normalization, and virtualization unit tests
  • review tab-switch, image, and line-comment Playwright regressions
  • production Playwright scaling benchmark, including three-repeat capped and uncapped 10,000-file runs
  • repository pre-push typecheck hook (30 packages)

@Hona
Hona requested a review from Brendonovich as a code ownerJuly 5, 2026 00:58
CopilotAI review requested due to automatic review settings July 5, 2026 00:58

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves the performance and scalability of the v2 review pane by normalizing review file-tree data, applying bounded TanStack virtualization to the review sidebar + filtered file list, and reducing expensive UI observation work during tab switching. It also adds automated Playwright performance benchmarks to validate behavior from small reviews up through extreme 10k-file scenarios.

Changes:

  • Replace the recursive review file tree with a normalized, flattened model and TanStack virtualization for both the tree and filtered-results list.
  • Adjust review pane mounting/tab-panel behavior to avoid expensive tab-content observation, plus prewarm the diff worker pool.
  • Add/extend unit + Playwright regression tests and introduce a production scaling benchmark spec for large review panes.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 6 comments.

Show a summary per file
FileDescription
packages/session-ui/src/v2/components/session-review-v2.tsxPrewarms Pierre diff worker pool when review mounts.
packages/session-ui/src/v2/components/session-review-file-preview-v2.tsxAdds heuristic to disable diff virtualization for smaller diffs.
packages/session-ui/src/v2/components/session-review-file-preview-v2-virtualize.tsIntroduces shouldVirtualizeReviewDiff threshold logic.
packages/session-ui/src/v2/components/session-review-file-preview-v2-virtualize.test.tsUnit tests for the virtualization threshold helper.
packages/app/src/pages/session/v2/session-file-list-v2.tsxVirtualizes filtered review results list with focus retention + scroll-to-highlight.
packages/app/src/pages/session/v2/review-panel-v2.tsxUses virtualized file list while searching; updates FileTreeV2 usage.
packages/app/src/pages/session/v2/review-diff-kinds.tsCentralizes path normalization via normalizeFileTreeV2Path.
packages/app/src/pages/session/v2/review-diff-kinds.test.tsAdds test coverage for file/directory path normalization.
packages/app/src/pages/session/session-side-panel.tsxReworks review tabpanel mounting/ARIA to avoid costly observation; adds focusability hook.
packages/app/src/pages/session.tsxWires reviewHasFocusableContent into SessionSidePanel.
packages/app/src/components/file-tree-v2.tsxReplaces recursive tree rendering with normalized model + TanStack virtualization.
packages/app/src/components/file-tree-v2-model.tsAdds normalized tree model builder + flattening for virtualization.
packages/app/src/components/file-tree-v2-model.test.tsUnit tests for sorting, collapsing, normalization, and deep paths.
packages/app/e2e/regression/review-tab-switch.spec.tsExtends regression to assert tab/tabpanel ARIA wiring.
packages/app/e2e/performance/timeline/review-pane-scaling-benchmark.spec.tsAdds scaling benchmark coverage (1 → 10,000 files; capped/uncapped payloads).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 15 to 21
import FileTree from "@/components/file-tree"
import { SessionContextUsage } from "@/components/session-context-usage"

const reviewTabID = "session-side-panel-review-tab"
const reviewTabPanelID = "session-side-panel-review-tabpanel"
import { SessionContextTab, SortableTab, FileVisual } from "@/components/session"
import { useCommand } from "@/context/command"

export function flattenFileTreeV2(model: FileTreeV2Model, expanded: (path: string) => boolean) {
const rows: FileTreeV2Row[] = []
const stack = (model.children.get("") ?? []).toReversed().map((node) => ({ node, level: 0 }))
Comment on lines +132 to 155
const model = createMemo(() => buildFileTreeV2Model(props.allowed ?? []))
const rows = createMemo(() => flattenFileTreeV2(model(), (path) => file.tree.state(path)?.expanded ?? true))
const [root, setRoot] = createSignal<HTMLDivElement>()
const [focused, setFocused] = createSignal<string>()
const virtualizer = createVirtualizer<HTMLDivElement, HTMLDivElement>({
get count() {
return rows().length
},
getScrollElement: () => root()?.closest<HTMLDivElement>(".scroll-view__viewport") ?? null,
estimateSize: () => 28,
gap: 2,
overscan: 10,
get getItemKey() {
const current = rows()
return (index: number) => current[index]?.node.path ?? index
},
rangeExtractor: (range) => {
const indexes = defaultRangeExtractor(range)
const path = focused()
const index = path ? rows().findIndex((row) => row.node.path === path) : -1
if (index < 0 || indexes.includes(index)) return indexes
return [...indexes, index].sort((a, b) => a - b)
},
})
Comment on lines +166 to +170
const rowByKey = createMemo(() => new Map(rows().map((row) => [row.node.path, row] as const)))
const virtualItemByKey = createMemo(
() => new Map(virtualizer.getVirtualItems().map((item) => [item.key, item] as const)),
)

const nodes = createMemo(() => visibleNodesForPath(props.path, file.tree.children, filter()))
const virtualRowKeys = createMemo(() => virtualizer.getVirtualItems().map((item) => item.key))
Comment on lines +64 to +68
rangeExtractor: (range) => {
const indexes = defaultRangeExtractor(range)
const path = focused()
const index = path ? props.files.indexOf(path) : -1
if (index < 0 || indexes.includes(index)) return indexes
Comment on lines +81 to +84
const virtualItemByKey = createMemo(
() => new Map(virtualizer.getVirtualItems().map((item) => [item.key, item] as const)),
)
const virtualRowKeys = createMemo(() => virtualizer.getVirtualItems().map((item) => item.key))
@HonaHona added the beta label Jul 5, 2026
@Hona
Hona merged commit 3a149ba into anomalyco:devJul 6, 2026
9 checks passed
drewr pushed a commit to drewr/opencode that referenced this pull request Jul 7, 2026
shaheislam pushed a commit to shaheislam/opencode-vim that referenced this pull request Jul 7, 2026
avion23 pushed a commit to avion23/opencode that referenced this pull request Jul 9, 2026
anduimagui pushed a commit to anduimagui/opencode that referenced this pull request Jul 9, 2026
avion23 pushed a commit to avion23/opencode that referenced this pull request Jul 11, 2026
avion23 pushed a commit to avion23/opencode that referenced this pull request Jul 20, 2026
3kaiu pushed a commit to 3kaiu/opencode-x that referenced this pull request Jul 31, 2026
MarsQiu007 referenced this pull request in MarsQiu007/openNovel Jul 31, 2026
banlanzs pushed a commit to banlanzs/opencode that referenced this pull request Aug 6, 2026
charlesverge pushed a commit to charlesverge/opencode that referenced this pull request Aug 11, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@Hona