diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/DiffPane/DiffPane.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/DiffPane/DiffPane.tsx index 3ccae838bb8..2fc56144335 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/DiffPane/DiffPane.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/DiffPane/DiffPane.tsx @@ -61,6 +61,10 @@ export function DiffPane({ context, workspaceId, onOpenFile }: DiffPaneProps) { () => new Set(data.collapsedFiles ?? []), [data.collapsedFiles], ); + const expandedSet = useMemo( + () => new Set(data.expandedFiles ?? []), + [data.expandedFiles], + ); // Stable callback via refs — identity does not churn as collapsedFiles // updates, so memo'd children can skip re-renders on unrelated toggles. @@ -80,6 +84,19 @@ export function DiffPane({ context, workspaceId, onOpenFile }: DiffPaneProps) { }, [updateData], ); + const setExpanded = useCallback( + (path: string, value: boolean) => { + const current = dataRef.current; + const expanded = current.expandedFiles ?? []; + const has = expanded.includes(path); + if (value === has) return; + const next = value + ? [...expanded, path] + : expanded.filter((p) => p !== path); + updateData({ ...current, expandedFiles: next } as PaneViewerData); + }, + [updateData], + ); if (!isLoading && files.length === 0) { return ( @@ -103,6 +120,8 @@ export function DiffPane({ context, workspaceId, onOpenFile }: DiffPaneProps) { diffStyle={diffStyle} collapsed={collapsedSet.has(file.path)} onSetCollapsed={setCollapsed} + expanded={expandedSet.has(file.path)} + onSetExpanded={setExpanded} viewed={viewedSet.has(file.path)} onSetViewed={setViewed} onOpenFile={onOpenFile} diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/DiffPane/components/DiffFileEntry/DiffFileEntry.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/DiffPane/components/DiffFileEntry/DiffFileEntry.tsx index 86f73da280a..26b25700c50 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/DiffPane/components/DiffFileEntry/DiffFileEntry.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/DiffPane/components/DiffFileEntry/DiffFileEntry.tsx @@ -33,6 +33,8 @@ interface DiffFileEntryProps { diffStyle: "split" | "unified"; collapsed: boolean; onSetCollapsed: (path: string, value: boolean) => void; + expanded: boolean; + onSetExpanded: (path: string, value: boolean) => void; viewed: boolean; onSetViewed: (path: string, next: boolean) => void; onOpenFile: (path: string, openInNewTab?: boolean) => void; @@ -45,6 +47,8 @@ export const DiffFileEntry = memo(function DiffFileEntry({ diffStyle, collapsed, onSetCollapsed, + expanded, + onSetExpanded, viewed, onSetViewed, onOpenFile, @@ -55,9 +59,9 @@ export const DiffFileEntry = memo(function DiffFileEntry({ const hasBeenNearRef = useRef(false); if (isNear) hasBeenNearRef.current = true; - const [showFullDiff, setShowFullDiff] = useState(false); const [expandUnchanged, setExpandUnchanged] = useState(false); const reason = deferReason(file); + const showFullDiff = expanded; const handleToggleCollapsed = useCallback( () => onSetCollapsed(file.path, !collapsed), @@ -90,7 +94,10 @@ export const DiffFileEntry = memo(function DiffFileEntry({ } onOpenInExternalEditor(file.path); }, [file.status, file.path, onOpenInExternalEditor, showDeletedFileToast]); - const handleShowFullDiff = useCallback(() => setShowFullDiff(true), []); + const handleShowFullDiff = useCallback( + () => onSetExpanded(file.path, true), + [onSetExpanded, file.path], + ); const handleToggleExpandUnchanged = useCallback( () => setExpandUnchanged((prev) => !prev), [], diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useWorkspacePaneOpeners/useWorkspacePaneOpeners.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useWorkspacePaneOpeners/useWorkspacePaneOpeners.ts index 955c2de7723..9453e4e5fa6 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useWorkspacePaneOpeners/useWorkspacePaneOpeners.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useWorkspacePaneOpeners/useWorkspacePaneOpeners.ts @@ -32,6 +32,7 @@ export function useWorkspacePaneOpeners({ data: { path: filePath, collapsedFiles: [], + expandedFiles: [filePath], } as DiffPaneData, }, ], @@ -42,11 +43,18 @@ export function useWorkspacePaneOpeners({ for (const pane of Object.values(tab.panes)) { if (pane.kind !== "diff") continue; const prev = pane.data as DiffPaneData; + const prevExpanded = prev.expandedFiles ?? []; state.setPaneData({ paneId: pane.id, data: { ...prev, path: filePath, + collapsedFiles: (prev.collapsedFiles ?? []).filter( + (p) => p !== filePath, + ), + expandedFiles: prevExpanded.includes(filePath) + ? prevExpanded + : [...prevExpanded, filePath], } as PaneViewerData, }); state.setActiveTab(tab.id); @@ -60,6 +68,7 @@ export function useWorkspacePaneOpeners({ data: { path: filePath, collapsedFiles: [], + expandedFiles: [filePath], } as DiffPaneData, }, }); diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/types.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/types.ts index cd2737cf785..c5ef6e83376 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/types.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/types.ts @@ -44,6 +44,7 @@ export interface DevtoolsPaneData { export interface DiffPaneData { path: string; collapsedFiles: string[]; + expandedFiles?: string[]; } export interface CommentPaneData {