import { parsePatchFiles } from "@pierre/diffs"; import { FileDiff, type FileDiffMetadata, Virtualizer, WorkerPoolContextProvider, } from "@pierre/diffs/react"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { Columns2Icon, Rows3Icon } from "lucide-react"; import { type ReactNode, useEffect, useMemo, useRef, useState } from "react"; import { checkpointDiffQueryOptions, providerQueryKeys } from "~/lib/providerReactQuery"; import { cn } from "~/lib/utils"; import { isElectron } from "../env"; import { useNativeApi } from "../hooks/useNativeApi"; import { useTheme } from "../hooks/useTheme"; import { buildPatchCacheKey } from "../lib/diffRendering"; import { useTurnDiffSummaries } from "../hooks/useTurnDiffSummaries"; import { formatTimestamp } from "../session-logic"; import { useStore } from "../store"; import { ToggleGroup, Toggle } from "./ui/toggle-group"; type DiffRenderMode = "stacked" | "split"; type DiffThemeType = "light" | "dark" | "system"; type RenderablePatch = | { kind: "files"; files: FileDiffMetadata[]; } | { kind: "raw"; text: string; reason: string; }; function getRenderablePatch(patch: string | undefined): RenderablePatch | null { if (!patch) return null; const normalizedPatch = patch.trim(); if (normalizedPatch.length === 0) return null; try { const parsedPatches = parsePatchFiles(normalizedPatch, buildPatchCacheKey(normalizedPatch)); const files = parsedPatches.flatMap((parsedPatch) => parsedPatch.files); if (files.length > 0) { return { kind: "files", files }; } return { kind: "raw", text: normalizedPatch, reason: "Unsupported diff format. Showing raw patch.", }; } catch { return { kind: "raw", text: normalizedPatch, reason: "Failed to parse patch. Showing raw patch.", }; } } function resolveFileDiffPath(fileDiff: FileDiffMetadata): string { const raw = fileDiff.name ?? fileDiff.prevName ?? ""; if (raw.startsWith("a/") || raw.startsWith("b/")) { return raw.slice(2); } return raw; } function buildFileDiffRenderKey(fileDiff: FileDiffMetadata): string { return fileDiff.cacheKey ?? `${fileDiff.prevName ?? "none"}:${fileDiff.name}`; } interface DiffPanelProps { mode?: "inline" | "sheet"; } export function DiffWorkerPoolProvider({ children }: { children?: ReactNode }) { const workerPoolSize = useMemo(() => { const cores = typeof navigator === "undefined" ? 4 : Math.max(1, navigator.hardwareConcurrency || 4); return Math.max(2, Math.min(6, Math.floor(cores / 2))); }, []); return ( new Worker(new URL("../workers/diffs.worker.ts", import.meta.url), { type: "module" }), poolSize: workerPoolSize, totalASTLRUCacheSize: 240, }} highlighterOptions={{ tokenizeMaxLineLength: 1_000, }} > {children} ); } export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { const api = useNativeApi(); const { resolvedTheme } = useTheme(); const { state, dispatch } = useStore(); const queryClient = useQueryClient(); const [diffRenderMode, setDiffRenderMode] = useState("stacked"); const patchViewportRef = useRef(null); const activeThread = state.threads.find((thread) => thread.id === state.activeThreadId); const activeThreadRuntimeId = activeThread?.codexThreadId ?? activeThread?.session?.threadId ?? null; const activeSessionId = activeThread?.session?.sessionId ?? null; const { turnDiffSummaries, inferredCheckpointTurnCountByTurnId } = useTurnDiffSummaries(activeThread); const canApplyStoredTarget = Boolean(activeThread && state.diffThreadId === activeThread.id); const selectedTurnId = canApplyStoredTarget ? state.diffTurnId : null; const selectedFilePath = canApplyStoredTarget && selectedTurnId !== null ? state.diffFilePath : null; const selectedTurn = selectedTurnId === null ? undefined : (turnDiffSummaries.find((summary) => summary.turnId === selectedTurnId) ?? turnDiffSummaries[0]); const selectedCheckpointTurnCount = selectedTurn && (selectedTurn.checkpointTurnCount ?? inferredCheckpointTurnCountByTurnId[selectedTurn.turnId]); const selectedCheckpointRange = useMemo( () => typeof selectedCheckpointTurnCount === "number" ? { fromTurnCount: Math.max(0, selectedCheckpointTurnCount - 1), toTurnCount: selectedCheckpointTurnCount, } : null, [selectedCheckpointTurnCount], ); const conversationCheckpointTurnCount = useMemo(() => { const turnCounts = turnDiffSummaries .map( (summary) => summary.checkpointTurnCount ?? inferredCheckpointTurnCountByTurnId[summary.turnId], ) .filter((value): value is number => typeof value === "number"); if (turnCounts.length === 0) { return undefined; } const latest = Math.max(...turnCounts); return latest > 0 ? latest : undefined; }, [inferredCheckpointTurnCountByTurnId, turnDiffSummaries]); const conversationCheckpointRange = useMemo( () => !selectedTurn && typeof conversationCheckpointTurnCount === "number" ? { fromTurnCount: 0, toTurnCount: conversationCheckpointTurnCount, } : null, [conversationCheckpointTurnCount, selectedTurn], ); const activeCheckpointRange = selectedTurn ? selectedCheckpointRange : conversationCheckpointRange; const conversationCacheScope = useMemo(() => { if (selectedTurn || turnDiffSummaries.length === 0) { return null; } return `conversation:${turnDiffSummaries.map((summary) => summary.turnId).join(",")}`; }, [selectedTurn, turnDiffSummaries]); const activeCheckpointDiffQuery = useQuery( checkpointDiffQueryOptions(api, { sessionId: activeSessionId, threadRuntimeId: activeThreadRuntimeId, fromTurnCount: activeCheckpointRange?.fromTurnCount ?? null, toTurnCount: activeCheckpointRange?.toTurnCount ?? null, cacheScope: selectedTurn ? `turn:${selectedTurn.turnId}` : conversationCacheScope, }), ); const selectedTurnCheckpointDiff = selectedTurn ? activeCheckpointDiffQuery.data?.diff : undefined; const conversationCheckpointDiff = selectedTurn ? undefined : activeCheckpointDiffQuery.data?.diff; const isLoadingCheckpointDiff = activeCheckpointDiffQuery.isLoading; const checkpointDiffError = activeCheckpointDiffQuery.error instanceof Error ? activeCheckpointDiffQuery.error.message : activeCheckpointDiffQuery.error ? "Failed to load checkpoint diff." : null; useEffect(() => { if (!activeThread?.id || !selectedTurn || !selectedTurnCheckpointDiff) { return; } dispatch({ type: "SET_THREAD_TURN_CHECKPOINT_DIFFS", threadId: activeThread.id, checkpointDiffByTurnId: { [selectedTurn.turnId]: selectedTurnCheckpointDiff, }, }); }, [activeThread?.id, dispatch, selectedTurn, selectedTurnCheckpointDiff]); const selectedPatch = useMemo(() => { const patchForSummary = (summary: (typeof turnDiffSummaries)[number]): string | undefined => { const checkpointTurnCount = summary.checkpointTurnCount ?? inferredCheckpointTurnCountByTurnId[summary.turnId]; if (activeSessionId && typeof checkpointTurnCount === "number") { const checkpointPatch = queryClient.getQueryData<{ diff: string }>( providerQueryKeys.checkpointDiff({ sessionId: activeSessionId, threadRuntimeId: activeThreadRuntimeId, fromTurnCount: Math.max(0, checkpointTurnCount - 1), toTurnCount: checkpointTurnCount, cacheScope: `turn:${summary.turnId}`, }), )?.diff; if (checkpointPatch) { return checkpointPatch; } } if (summary.unifiedDiff) { return summary.unifiedDiff; } const filePatches = summary.files .map((file) => file.diff?.trim()) .filter((patch): patch is string => Boolean(patch)); if (filePatches.length === 0) { return undefined; } return filePatches.join("\n\n"); }; if (selectedTurn) { return selectedTurnCheckpointDiff ?? patchForSummary(selectedTurn); } if (conversationCheckpointDiff) { return conversationCheckpointDiff; } // Fallback when a conversation checkpoint diff isn't available yet: // keep one patch per file path (latest change wins) so files aren't duplicated. const latestPatchByPath = new Map(); for (const summary of turnDiffSummaries) { for (const file of summary.files) { if (latestPatchByPath.has(file.path)) { continue; } const patch = file.diff?.trim(); if (!patch) { continue; } latestPatchByPath.set(file.path, patch); } } if (latestPatchByPath.size > 0) { return Array.from(latestPatchByPath.entries()) .toSorted(([leftPath], [rightPath]) => leftPath.localeCompare(rightPath, undefined, { numeric: true, sensitivity: "base" }), ) .map(([, patch]) => patch) .join("\n\n"); } const patches = turnDiffSummaries .toReversed() .map((summary) => patchForSummary(summary)?.trim()) .filter((patch): patch is string => Boolean(patch)); if (patches.length === 0) { return undefined; } return patches.join("\n\n"); }, [ activeSessionId, activeThreadRuntimeId, conversationCheckpointDiff, inferredCheckpointTurnCountByTurnId, queryClient, selectedTurn, selectedTurnCheckpointDiff, turnDiffSummaries, ]); const renderablePatch = useMemo(() => getRenderablePatch(selectedPatch), [selectedPatch]); const renderableFiles = useMemo(() => { if (!renderablePatch || renderablePatch.kind !== "files") { return []; } return renderablePatch.files.toSorted((left, right) => resolveFileDiffPath(left).localeCompare(resolveFileDiffPath(right), undefined, { numeric: true, sensitivity: "base", }), ); }, [renderablePatch]); useEffect(() => { if (!selectedFilePath || !patchViewportRef.current) { return; } const target = Array.from( patchViewportRef.current.querySelectorAll("[data-diff-file-path]"), ).find((element) => element.dataset.diffFilePath === selectedFilePath); target?.scrollIntoView({ block: "nearest" }); }, [selectedFilePath, renderableFiles]); const selectTurn = (turnId: string) => { if (!activeThread) return; dispatch({ type: "SET_DIFF_TARGET", threadId: activeThread.id, turnId, }); }; const selectWholeConversation = () => { if (!activeThread) return; dispatch({ type: "SET_DIFF_TARGET", threadId: activeThread.id, }); }; const shouldUseDragRegion = isElectron && mode === "inline"; return ( ); }