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

This commit is contained in:
Luke Parker 2026-07-06 16:26:03 +10:00 committed by GitHub
commit 3a149ba71c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 1074 additions and 404 deletions

View file

@ -0,0 +1,13 @@
import { describe, expect, test } from "bun:test"
import { shouldVirtualizeReviewDiff } from "./session-review-file-preview-v2-virtualize"
describe("shouldVirtualizeReviewDiff", () => {
test("renders small diffs directly", () => {
expect(shouldVirtualizeReviewDiff({ additionLines: 500, deletionLines: 500 })).toBe(false)
})
test("virtualizes large diffs", () => {
expect(shouldVirtualizeReviewDiff({ additionLines: 501, deletionLines: 1 })).toBe(true)
expect(shouldVirtualizeReviewDiff({ additionLines: 1, deletionLines: 501 })).toBe(true)
})
})

View file

@ -0,0 +1,5 @@
const lineThreshold = 500
export function shouldVirtualizeReviewDiff(input: { additionLines: number; deletionLines: number }) {
return Math.max(input.additionLines, input.deletionLines) > lineThreshold
}

View file

@ -22,6 +22,7 @@ import type {
} from "../../components/session-review"
import type { SessionReviewExpandMode } from "./session-review-v2"
import { createLineCommentControllerV2 } from "./line-comment-annotations-v2"
import { shouldVirtualizeReviewDiff } from "./session-review-file-preview-v2-virtualize"
import { LineCommentV2OverflowIcon } from "@opencode-ai/ui/v2/line-comment-v2"
import { MenuV2 } from "@opencode-ai/ui/v2/menu-v2"
import "./session-review-v2.css"
@ -219,6 +220,10 @@ export function SessionReviewFilePreviewV2(props: SessionReviewFilePreviewV2Prop
preloadedDiff={view().preloaded}
diffStyle={props.diffStyle}
expandUnchanged={expandUnchanged()}
virtualize={shouldVirtualizeReviewDiff({
additionLines: view().fileDiff.additionLines.length,
deletionLines: view().fileDiff.deletionLines.length,
})}
hunkSeparators={view().fileDiff.isPartial ? "simple" : "line-info-basic"}
enableLineSelection={lineCommentsEnabled()}
enableGutterUtility={lineCommentsEnabled()}

View file

@ -11,6 +11,7 @@ import { ResizeHandle } from "@opencode-ai/ui/resize-handle"
import { ScrollView } from "@opencode-ai/ui/scroll-view"
import { makeEventListener } from "@solid-primitives/event-listener"
import { Show, createEffect, createMemo, createSignal, type JSX } from "solid-js"
import { getWorkerPool } from "../../pierre/worker"
import "./session-review-v2.css"
export const SESSION_REVIEW_V2_SIDEBAR_WIDTH_DEFAULT = 240
@ -48,6 +49,7 @@ export type SessionReviewV2SidebarProps = {
onWidthChange?: (width: number) => void
minWidth?: number
maxWidth?: number
viewportRef?: (element: HTMLDivElement) => void
children?: JSX.Element
}
@ -74,44 +76,47 @@ export function SessionReviewV2Sidebar(props: SessionReviewV2SidebarProps) {
inert={!props.open}
style={{ width: props.open ? `${width()}px` : "0px" }}
>
<Show when={props.open}>
<div data-slot="session-review-v2-sidebar-header">
<div data-slot="session-review-v2-sidebar-title">{props.title}</div>
{props.stats}
</div>
<div data-slot="session-review-v2-sidebar-filter">
<TextInputV2
type="search"
value={props.filter}
onInput={(event) => props.onFilterChange(event.currentTarget.value)}
onKeyDown={props.onFilterKeyDown}
showClearButton={props.filter.length > 0}
clearLabel={i18n.t("ui.list.clearFilter")}
onClearClick={() => props.onFilterChange("")}
placeholder={i18n.t("ui.sessionReviewV2.filterFiles")}
aria-label={i18n.t("ui.sessionReviewV2.filterFiles")}
leadingIcon={
<svg
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
d="M12.25 12.25L10.0625 10.0625M11.0833 6.41667C11.0833 8.994 8.994 11.0833 6.41667 11.0833C3.83934 11.0833 1.75 8.994 1.75 6.41667C1.75 3.83934 3.83934 1.75 6.41667 1.75C8.994 1.75 11.0833 3.83934 11.0833 6.41667Z"
stroke="currentColor"
stroke-linecap="square"
/>
</svg>
}
/>
</div>
<ScrollView data-slot="session-review-v2-sidebar-tree" class="group/file-tree-v2" thumbVisibility="scroll">
{props.children}
</ScrollView>
</Show>
<div data-slot="session-review-v2-sidebar-header">
<div data-slot="session-review-v2-sidebar-title">{props.title}</div>
{props.stats}
</div>
<div data-slot="session-review-v2-sidebar-filter">
<TextInputV2
type="search"
value={props.filter}
onInput={(event) => props.onFilterChange(event.currentTarget.value)}
onKeyDown={props.onFilterKeyDown}
showClearButton={props.filter.length > 0}
clearLabel={i18n.t("ui.list.clearFilter")}
onClearClick={() => props.onFilterChange("")}
placeholder={i18n.t("ui.sessionReviewV2.filterFiles")}
aria-label={i18n.t("ui.sessionReviewV2.filterFiles")}
leadingIcon={
<svg
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
d="M12.25 12.25L10.0625 10.0625M11.0833 6.41667C11.0833 8.994 8.994 11.0833 6.41667 11.0833C3.83934 11.0833 1.75 8.994 1.75 6.41667C1.75 3.83934 3.83934 1.75 6.41667 1.75C8.994 1.75 11.0833 3.83934 11.0833 6.41667Z"
stroke="currentColor"
stroke-linecap="square"
/>
</svg>
}
/>
</div>
<ScrollView
data-slot="session-review-v2-sidebar-tree"
class="group/file-tree-v2"
thumbVisibility="scroll"
viewportRef={props.viewportRef}
>
{props.children}
</ScrollView>
</aside>
<Show when={props.open && props.onWidthChange}>
<div data-slot="session-review-v2-sidebar-resize" onPointerDown={() => setResizing(true)}>
@ -131,6 +136,10 @@ export function SessionReviewV2Sidebar(props: SessionReviewV2SidebarProps) {
export function SessionReviewV2(props: SessionReviewV2Props) {
const i18n = useI18n()
createEffect(() => {
getWorkerPool(props.diffStyle)
})
const fileIndex = () => {
const files = props.files
if (files.length === 0) return -1