| ... | @@ -1,5 +1,10 @@ | ... | @@ -1,5 +1,10 @@ |
| 1 | import { parsePatchFiles } from "@pierre/diffs"; | 1 | import { parsePatchFiles } from "@pierre/diffs"; |
| 2 | import { FileDiff, type FileDiffMetadata, WorkerPoolContextProvider } from "@pierre/diffs/react"; | 2 | import { |
| | 3 | FileDiff, |
| | 4 | type FileDiffMetadata, |
| | 5 | Virtualizer, |
| | 6 | WorkerPoolContextProvider, |
| | 7 | } from "@pierre/diffs/react"; |
| 3 | import { useQuery, useQueryClient } from "@tanstack/react-query"; | 8 | import { useQuery, useQueryClient } from "@tanstack/react-query"; |
| 4 | import { Columns2Icon, Rows3Icon } from "lucide-react"; | 9 | import { Columns2Icon, Rows3Icon } from "lucide-react"; |
| 5 | import { type ReactNode, useEffect, useMemo, useRef, useState } from "react"; | 10 | import { type ReactNode, useEffect, useMemo, useRef, useState } from "react"; |
| ... | @@ -15,6 +20,7 @@ import { useStore } from "../store"; | ... | @@ -15,6 +20,7 @@ import { useStore } from "../store"; |
| 15 | import { ToggleGroup, Toggle } from "./ui/toggle-group"; | 20 | import { ToggleGroup, Toggle } from "./ui/toggle-group"; |
| 16 | | 21 | |
| 17 | type DiffRenderMode = "stacked" | "split"; | 22 | type DiffRenderMode = "stacked" | "split"; |
| | 23 | type DiffThemeType = "light" | "dark" | "system"; |
| 18 | | 24 | |
| 19 | type RenderablePatch = | 25 | type RenderablePatch = |
| 20 | | { | 26 | | { |
| ... | @@ -61,6 +67,10 @@ function resolveFileDiffPath(fileDiff: FileDiffMetadata): string { | ... | @@ -61,6 +67,10 @@ function resolveFileDiffPath(fileDiff: FileDiffMetadata): string { |
| 61 | return raw; | 67 | return raw; |
| 62 | } | 68 | } |
| 63 | | 69 | |
| | 70 | function buildFileDiffRenderKey(fileDiff: FileDiffMetadata): string { |
| | 71 | return fileDiff.cacheKey ?? `${fileDiff.prevName ?? "none"}:${fileDiff.name}`; |
| | 72 | } |
| | 73 | |
| 64 | interface DiffPanelProps { | 74 | interface DiffPanelProps { |
| 65 | mode?: "inline" | "sheet"; | 75 | mode?: "inline" | "sheet"; |
| 66 | } | 76 | } |
| ... | @@ -396,46 +406,60 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { | ... | @@ -396,46 +406,60 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { |
| 396 | </div> | 406 | </div> |
| 397 | ) : ( | 407 | ) : ( |
| 398 | <> | 408 | <> |
| 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"> |
| 400 | {!canApplyStoredTarget && state.diffThreadId && ( | 410 | {!canApplyStoredTarget && state.diffThreadId && ( |
| 401 | <p className="mb-2 text-[11px] text-muted-foreground/65"> | 411 | <div className="px-3 pt-2"> |
| 402 | Showing diffs for the active thread. | 412 | <p className="mb-2 text-[11px] text-muted-foreground/65"> |
| 403 | </p> | 413 | Showing diffs for the active thread. |
| | 414 | </p> |
| | 415 | </div> |
| 404 | )} | 416 | )} |
| 405 | {checkpointDiffError && !renderablePatch && ( | 417 | {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> |
| 407 | )} | 421 | )} |
| 408 | {!renderablePatch ? ( | 422 | {!renderablePatch ? ( |
| 409 | <div className="flex h-full items-center justify-center text-xs text-muted-foreground/70"> | 423 | <div className="flex h-full items-center justify-center px-3 py-2 text-xs text-muted-foreground/70"> |
| 410 | {isLoadingCheckpointDiff | 424 | <p> |
| 411 | ? "Loading checkpoint diff..." | 425 | {isLoadingCheckpointDiff |
| 412 | : "No patch available for this selection."} | 426 | ? "Loading checkpoint diff..." |
| | 427 | : "No patch available for this selection."} |
| | 428 | </p> |
| 413 | </div> | 429 | </div> |
| 414 | ) : renderablePatch.kind === "files" ? ( | 430 | ) : renderablePatch.kind === "files" ? ( |
| 415 | <div className="space-y-3"> | 431 | <Virtualizer |
| 416 | {renderableFiles.map((fileDiff) => ( | 432 | className="h-full min-h-0 overflow-auto px-3 py-2" |
| 417 | <div | 433 | config={{ |
| 418 | key={fileDiff.cacheKey ?? `${fileDiff.prevName ?? "none"}:${fileDiff.name}`} | 434 | overscrollSize: 600, |
| 419 | data-diff-file-path={resolveFileDiffPath(fileDiff)} | 435 | intersectionObserverMargin: 1200, |
| 420 | className="rounded-md" | 436 | }} |
| 421 | > | 437 | > |
| 422 | <FileDiff | 438 | {renderableFiles.map((fileDiff) => { |
| 423 | fileDiff={fileDiff} | 439 | const filePath = resolveFileDiffPath(fileDiff); |
| 424 | options={{ | 440 | const fileKey = buildFileDiffRenderKey(fileDiff); |
| 425 | diffStyle: diffRenderMode === "split" ? "split" : "unified", | 441 | return ( |
| 426 | lineDiffType: "none", | 442 | <div key={fileKey} data-diff-file-path={filePath} className="rounded-md"> |
| 427 | themeType: resolvedTheme, | 443 | <FileDiff |
| 428 | }} | 444 | fileDiff={fileDiff} |
| 429 | /> | 445 | options={{ |
| 430 | </div> | 446 | diffStyle: diffRenderMode === "split" ? "split" : "unified", |
| 431 | ))} | 447 | lineDiffType: "none", |
| 432 | </div> | 448 | themeType: resolvedTheme as DiffThemeType, |
| | 449 | }} |
| | 450 | /> |
| | 451 | </div> |
| | 452 | ); |
| | 453 | })} |
| | 454 | </Virtualizer> |
| 433 | ) : ( | 455 | ) : ( |
| 434 | <div className="space-y-2"> | 456 | <div className="h-full overflow-auto px-3 py-2"> |
| 435 | <p className="text-[11px] text-muted-foreground/75">{renderablePatch.reason}</p> | 457 | <div className="space-y-2"> |
| 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"> | 458 | <p className="text-[11px] text-muted-foreground/75">{renderablePatch.reason}</p> |
| 437 | {renderablePatch.text} | 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"> |
| 438 | </pre> | 460 | {renderablePatch.text} |
| | 461 | </pre> |
| | 462 | </div> |
| 439 | </div> | 463 | </div> |
| 440 | )} | 464 | )} |
| 441 | </div> | 465 | </div> |