Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { create } from "zustand";
import { persist } from "zustand/middleware";

export type ViewMode = "split" | "unified";
export type DiffSource = "local" | "branch";

interface DiffViewerStoreState {
viewMode: ViewMode;
Expand All @@ -12,6 +13,7 @@ interface DiffViewerStoreState {
wordDiffs: boolean;
hideWhitespaceChanges: boolean;
showReviewComments: boolean;
diffSource: Record<string, DiffSource>;
}

interface DiffViewerStoreActions {
Expand All @@ -22,6 +24,7 @@ interface DiffViewerStoreActions {
toggleWordDiffs: () => void;
toggleHideWhitespaceChanges: () => void;
toggleShowReviewComments: () => void;
setDiffSource: (taskId: string, source: DiffSource) => void;
}

type DiffViewerStore = DiffViewerStoreState & DiffViewerStoreActions;
Expand All @@ -35,6 +38,7 @@ export const useDiffViewerStore = create<DiffViewerStore>()(
wordDiffs: true,
hideWhitespaceChanges: false,
showReviewComments: true,
diffSource: {},
setViewMode: (mode) =>
set((state) => {
if (state.viewMode === mode) {
Expand Down Expand Up @@ -69,6 +73,10 @@ export const useDiffViewerStore = create<DiffViewerStore>()(
set((s) => ({ hideWhitespaceChanges: !s.hideWhitespaceChanges })),
toggleShowReviewComments: () =>
set((s) => ({ showReviewComments: !s.showReviewComments })),
setDiffSource: (taskId, source) =>
set((s) => ({
diffSource: { ...s.diffSource, [taskId]: source },
})),
}),
{
name: "diff-viewer-storage",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,14 @@ import { useDiffViewerStore } from "@features/code-editor/stores/diffViewerStore
import { usePrDetails } from "@features/git-interaction/hooks/usePrDetails";
import { useCloudChangedFiles } from "@features/task-detail/hooks/useCloudChangedFiles";
import { extractCloudFileDiff } from "@features/task-detail/utils/cloudToolChanges";
import type { FileDiffMetadata } from "@pierre/diffs";
import { processFile } from "@pierre/diffs";
import { Flex, Spinner, Text } from "@radix-ui/themes";
import { useReviewNavigationStore } from "@renderer/features/code-review/stores/reviewNavigationStore";
import type { ChangedFile, Task } from "@shared/types";
import type { Task } from "@shared/types";
import { useMemo } from "react";
import type { DiffOptions } from "../types";
import type { PrCommentThread } from "../utils/prCommentAnnotations";
import { InteractiveFileDiff } from "./InteractiveFileDiff";
import { LazyDiff } from "./LazyDiff";
import { PatchedFileDiff } from "./PatchedFileDiff";
import {
DeferredDiffPlaceholder,
DiffFileHeader,
ReviewShell,
useReviewState,
} from "./ReviewShell";
Expand Down Expand Up @@ -122,18 +117,23 @@ export function CloudReviewPage({ task }: CloudReviewPageProps) {
);
}

const githubFileUrl = prUrl
? `${prUrl}/files#diff-${file.path.replaceAll("/", "-")}`
: undefined;

return (
<div key={file.path} data-file-path={file.path}>
<LazyDiff>
<CloudFileDiff
<PatchedFileDiff
file={file}
taskId={taskId}
prUrl={prUrl}
options={diffOptions}
collapsed={isCollapsed}
onToggle={() => toggleFile(file.path)}
commentThreads={showReviewComments ? commentThreads : undefined}
toolCallDiff={toolCallDiffs?.get(file.path) ?? null}
fallback={toolCallDiffs?.get(file.path) ?? null}
externalUrl={githubFileUrl}
/>
</LazyDiff>
</div>
Expand All @@ -142,76 +142,3 @@ export function CloudReviewPage({ task }: CloudReviewPageProps) {
</ReviewShell>
);
}

function CloudFileDiff({
file,
taskId,
prUrl,
options,
collapsed,
onToggle,
commentThreads,
toolCallDiff,
}: {
file: ChangedFile;
taskId: string;
prUrl: string | null;
options: DiffOptions;
collapsed: boolean;
onToggle: () => void;
commentThreads?: Map<number, PrCommentThread>;
toolCallDiff: { oldText: string | null; newText: string | null } | null;
}) {
const fileDiff = useMemo((): FileDiffMetadata | undefined => {
if (!file.patch) return undefined;
return processFile(file.patch, { isGitDiff: true });
}, [file.patch]);

const diffSourceProps = useMemo(() => {
if (fileDiff) return { fileDiff };
if (toolCallDiff) {
const name = file.path.split("/").pop() || file.path;
return {
oldFile: { name, contents: toolCallDiff.oldText ?? "" },
newFile: { name, contents: toolCallDiff.newText ?? "" },
};
}
return null;
}, [fileDiff, toolCallDiff, file.path]);

if (!diffSourceProps) {
const hasChanges = (file.linesAdded ?? 0) + (file.linesRemoved ?? 0) > 0;
const reason = hasChanges ? "large" : "unavailable";
const githubFileUrl = prUrl
? `${prUrl}/files#diff-${file.path.replaceAll("/", "-")}`
: undefined;
return (
<DeferredDiffPlaceholder
filePath={file.path}
linesAdded={file.linesAdded ?? 0}
linesRemoved={file.linesRemoved ?? 0}
reason={reason}
collapsed={collapsed}
onToggle={onToggle}
externalUrl={githubFileUrl}
/>
);
}

return (
<InteractiveFileDiff
{...diffSourceProps}
options={{ ...options, collapsed }}
taskId={taskId}
prUrl={prUrl}
commentThreads={commentThreads}
renderCustomHeader={(fd) => (
<DiffFileHeader
fileDiff={fd}
collapsed={collapsed}
onToggle={onToggle}
/>
)}
/>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { CaretDown, GitBranch, HardDrives } from "@phosphor-icons/react";
import {
Button,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@posthog/quill";
import { useDiffViewerStore } from "@renderer/features/code-editor/stores/diffViewerStore";
import type { ResolvedDiffSource } from "../utils/resolveDiffSource";

interface DiffSourceSelectorProps {
taskId: string;
effectiveSource: ResolvedDiffSource;
branchAvailable: boolean;
}

const LABELS: Record<ResolvedDiffSource, string> = {
local: "Local",
branch: "Branch",
};

export function DiffSourceSelector({
taskId,
effectiveSource,
branchAvailable,
}: DiffSourceSelectorProps) {
const setDiffSource = useDiffViewerStore((s) => s.setDiffSource);

if (!branchAvailable) return null;

const Icon = effectiveSource === "branch" ? GitBranch : HardDrives;

return (
<DropdownMenu>
<DropdownMenuTrigger
render={
<Button
size="sm"
variant="default"
className="rounded-xs"
aria-label="Diff source"
>
<Icon size={12} />
<span className="whitespace-nowrap">{LABELS[effectiveSource]}</span>
<CaretDown size={10} weight="bold" />
</Button>
}
/>
<DropdownMenuContent
align="end"
side="bottom"
sideOffset={6}
className="min-w-[160px]"
>
<DropdownMenuItem onClick={() => setDiffSource(taskId, "local")}>
<HardDrives size={12} />
Local changes
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setDiffSource(taskId, "branch")}>
<GitBranch size={12} />
Branch vs. default
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { type FileDiffMetadata, processFile } from "@pierre/diffs";
import type { ChangedFile } from "@shared/types";
import { useMemo } from "react";
import type { DiffOptions } from "../types";
import type { PrCommentThread } from "../utils/prCommentAnnotations";
import { InteractiveFileDiff } from "./InteractiveFileDiff";
import { DeferredDiffPlaceholder, DiffFileHeader } from "./ReviewShell";

interface PatchedFileDiffProps {
file: ChangedFile;
taskId: string;
options: DiffOptions;
collapsed: boolean;
onToggle: () => void;
fallback?: { oldText: string | null; newText: string | null } | null;
externalUrl?: string;
prUrl?: string | null;
commentThreads?: Map<number, PrCommentThread>;
}

export function PatchedFileDiff({
file,
taskId,
options,
collapsed,
onToggle,
fallback,
externalUrl,
prUrl,
commentThreads,
}: PatchedFileDiffProps) {
const fileDiff = useMemo((): FileDiffMetadata | undefined => {
if (!file.patch) return undefined;
return processFile(file.patch, { isGitDiff: true });
}, [file.patch]);

const diffSourceProps = useMemo(() => {
if (fileDiff) return { fileDiff };
if (fallback) {
const name = file.path.split("/").pop() || file.path;
return {
oldFile: { name, contents: fallback.oldText ?? "" },
newFile: { name, contents: fallback.newText ?? "" },
};
}
return null;
}, [fileDiff, fallback, file.path]);

if (!diffSourceProps) {
const hasChanges = (file.linesAdded ?? 0) + (file.linesRemoved ?? 0) > 0;
return (
<DeferredDiffPlaceholder
filePath={file.path}
linesAdded={file.linesAdded ?? 0}
linesRemoved={file.linesRemoved ?? 0}
reason={hasChanges ? "large" : "unavailable"}
collapsed={collapsed}
onToggle={onToggle}
externalUrl={externalUrl}
/>
);
}

return (
<InteractiveFileDiff
{...diffSourceProps}
options={{ ...options, collapsed }}
taskId={taskId}
prUrl={prUrl}
commentThreads={commentThreads}
renderCustomHeader={(fd) => (
<DiffFileHeader
fileDiff={fd}
collapsed={collapsed}
onToggle={onToggle}
/>
)}
/>
);
}
Loading
Loading