authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-02-18 17:56:30-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-02-18 21:59:17-08:00
logbd1c7e1d9ae77e1c047e19ef895c089c00d87aa5
tree9768a87765493d76c1e1299c7344e30dbf63066e
parent453fb4992e9333be607322af1c2c2870ad93135d

Virtualize DiffPanel rendering with @pierre/diffs beta

- upgrade `@pierre/diffs` to `^1.1.0-beta.16` - switch file diff list to `Virtualizer` for smoother large patch rendering - adjust diff panel container/layout handling and theme typing

4 files changed, 61 insertions(+), 37 deletions(-)

apps/web/package.json+1-1
......@@ -12,7 +12,7 @@
1212 },
1313 "dependencies": {
1414 "@base-ui/react": "^1.2.0",
15 "@pierre/diffs": "^1.0.11",
15 "@pierre/diffs": "^1.1.0-beta.16",
1616 "@t3tools/contracts": "workspace:*",
1717 "@tanstack/react-pacer": "^0.19.4",
1818 "@tanstack/react-query": "^5.90.0",
apps/web/src/App.tsx+1-1
......@@ -1,5 +1,5 @@
11import { QueryClient, QueryClientProvider, useQueryClient } from "@tanstack/react-query";
2import { Activity, Suspense, lazy, useEffect, useRef, useState } from "react";
2import { Activity, Suspense, lazy, useEffect, useRef } from "react";
33
44import ChatView from "./components/ChatView";
55import Sidebar from "./components/Sidebar";
apps/web/src/components/DiffPanel.tsx+57-33
......@@ -1,5 +1,10 @@
11import { parsePatchFiles } from "@pierre/diffs";
2import { FileDiff, type FileDiffMetadata, WorkerPoolContextProvider } from "@pierre/diffs/react";
2import {
3 FileDiff,
4 type FileDiffMetadata,
5 Virtualizer,
6 WorkerPoolContextProvider,
7} from "@pierre/diffs/react";
38import { useQuery, useQueryClient } from "@tanstack/react-query";
49import { Columns2Icon, Rows3Icon } from "lucide-react";
510import { type ReactNode, useEffect, useMemo, useRef, useState } from "react";
......@@ -15,6 +20,7 @@ import { useStore } from "../store";
1520import { ToggleGroup, Toggle } from "./ui/toggle-group";
1621
1722type DiffRenderMode = "stacked" | "split";
23type DiffThemeType = "light" | "dark" | "system";
1824
1925type RenderablePatch =
2026 | {
......@@ -61,6 +67,10 @@ function resolveFileDiffPath(fileDiff: FileDiffMetadata): string {
6167 return raw;
6268}
6369
70function buildFileDiffRenderKey(fileDiff: FileDiffMetadata): string {
71 return fileDiff.cacheKey ?? `${fileDiff.prevName ?? "none"}:${fileDiff.name}`;
72}
73
6474interface DiffPanelProps {
6575 mode?: "inline" | "sheet";
6676}
......@@ -396,46 +406,60 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) {
396406 </div>
397407 ) : (
398408 <>
399 <div ref={patchViewportRef} className="min-w-0 flex-1 overflow-auto px-3 py-2">
409 <div ref={patchViewportRef} className="min-h-0 min-w-0 flex-1 overflow-hidden">
400410 {!canApplyStoredTarget && state.diffThreadId && (
401 <p className="mb-2 text-[11px] text-muted-foreground/65">
402 Showing diffs for the active thread.
403 </p>
411 <div className="px-3 pt-2">
412 <p className="mb-2 text-[11px] text-muted-foreground/65">
413 Showing diffs for the active thread.
414 </p>
415 </div>
404416 )}
405417 {checkpointDiffError && !renderablePatch && (
406 <p className="mb-2 text-[11px] text-red-500/80">{checkpointDiffError}</p>
418 <div className="px-3">
419 <p className="mb-2 text-[11px] text-red-500/80">{checkpointDiffError}</p>
420 </div>
407421 )}
408422 {!renderablePatch ? (
409 <div className="flex h-full items-center justify-center text-xs text-muted-foreground/70">
410 {isLoadingCheckpointDiff
411 ? "Loading checkpoint diff..."
412 : "No patch available for this selection."}
423 <div className="flex h-full items-center justify-center px-3 py-2 text-xs text-muted-foreground/70">
424 <p>
425 {isLoadingCheckpointDiff
426 ? "Loading checkpoint diff..."
427 : "No patch available for this selection."}
428 </p>
413429 </div>
414430 ) : renderablePatch.kind === "files" ? (
415 <div className="space-y-3">
416 {renderableFiles.map((fileDiff) => (
417 <div
418 key={fileDiff.cacheKey ?? `${fileDiff.prevName ?? "none"}:${fileDiff.name}`}
419 data-diff-file-path={resolveFileDiffPath(fileDiff)}
420 className="rounded-md"
421 >
422 <FileDiff
423 fileDiff={fileDiff}
424 options={{
425 diffStyle: diffRenderMode === "split" ? "split" : "unified",
426 lineDiffType: "none",
427 themeType: resolvedTheme,
428 }}
429 />
430 </div>
431 ))}
432 </div>
431 <Virtualizer
432 className="h-full min-h-0 overflow-auto px-3 py-2"
433 config={{
434 overscrollSize: 600,
435 intersectionObserverMargin: 1200,
436 }}
437 >
438 {renderableFiles.map((fileDiff) => {
439 const filePath = resolveFileDiffPath(fileDiff);
440 const fileKey = buildFileDiffRenderKey(fileDiff);
441 return (
442 <div key={fileKey} data-diff-file-path={filePath} className="rounded-md">
443 <FileDiff
444 fileDiff={fileDiff}
445 options={{
446 diffStyle: diffRenderMode === "split" ? "split" : "unified",
447 lineDiffType: "none",
448 themeType: resolvedTheme as DiffThemeType,
449 }}
450 />
451 </div>
452 );
453 })}
454 </Virtualizer>
433455 ) : (
434 <div className="space-y-2">
435 <p className="text-[11px] text-muted-foreground/75">{renderablePatch.reason}</p>
436 <pre className="max-h-[72vh] overflow-auto rounded-md border border-border/70 bg-background/70 p-3 font-mono text-[11px] leading-relaxed text-muted-foreground/90">
437 {renderablePatch.text}
438 </pre>
456 <div className="h-full overflow-auto px-3 py-2">
457 <div className="space-y-2">
458 <p className="text-[11px] text-muted-foreground/75">{renderablePatch.reason}</p>
459 <pre className="max-h-[72vh] overflow-auto rounded-md border border-border/70 bg-background/70 p-3 font-mono text-[11px] leading-relaxed text-muted-foreground/90">
460 {renderablePatch.text}
461 </pre>
462 </div>
439463 </div>
440464 )}
441465 </div>
bun.lock+2-2
......@@ -52,7 +52,7 @@
5252 "version": "0.0.0",
5353 "dependencies": {
5454 "@base-ui/react": "^1.2.0",
55 "@pierre/diffs": "^1.0.11",
55 "@pierre/diffs": "^1.1.0-beta.16",
5656 "@t3tools/contracts": "workspace:*",
5757 "@tanstack/react-pacer": "^0.19.4",
5858 "@tanstack/react-query": "^5.90.0",
......@@ -305,7 +305,7 @@
305305
306306 "@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.46.0", "", { "os": "win32", "cpu": "x64" }, "sha512-DJbQsSJUr4KSi9uU0QqOgI7PX2C+fKGZX+YDprt3vM2sC0dWZsgVTLoN2vtkNyEWJSY2mnvRFUshWXT3bmo0Ug=="],
307307
308 "@pierre/diffs": ["@pierre/diffs@1.0.11", "", { "dependencies": { "@shikijs/core": "^3.0.0", "@shikijs/engine-javascript": "^3.0.0", "@shikijs/transformers": "^3.0.0", "diff": "8.0.3", "hast-util-to-html": "9.0.5", "lru_map": "0.4.1", "shiki": "^3.0.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-j6zIEoyImQy1HfcJqbrDwP0O5I7V2VNXAaw53FqQ+SykRfaNwABeZHs9uibXO4supaXPmTx6LEH9Lffr03e1Tw=="],
308 "@pierre/diffs": ["@pierre/diffs@1.1.0-beta.16", "", { "dependencies": { "@shikijs/transformers": "^3.0.0", "diff": "8.0.3", "hast-util-to-html": "9.0.5", "lru_map": "0.4.1", "shiki": "^3.0.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-McjTuEPuacSIcXdoI2O9W6VSHIOs9ApEHnEUwONKZnKqIo2GGv1vNg9Pr8tgBOL7lgBWNEHX5ROJ5z1X74sENQ=="],
309309
310310 "@quansync/fs": ["@quansync/fs@1.0.0", "", { "dependencies": { "quansync": "^1.0.0" } }, "sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ=="],
311311