@@ -15,6 +15,7 @@ import { buildPatchCacheKey } from "../lib/diffRendering";
1515import {
1616 expandDiffFile ,
1717 reconcileDiffFileReviewState ,
18+ setDiffFileContextMode ,
1819 toggleDiffFileAccepted ,
1920 toggleDiffFileCollapsed ,
2021 type DiffFileReviewStateByPath ,
@@ -165,33 +166,83 @@ function summarizeFileDiffStats(fileDiff: FileDiffMetadata): {
165166 ) ;
166167}
167168
169+ function resolveRenderableFileDiff (
170+ renderablePatch : RenderablePatch | null ,
171+ filePath : string ,
172+ ) : FileDiffMetadata | null {
173+ if ( ! renderablePatch || renderablePatch . kind !== "files" ) {
174+ return null ;
175+ }
176+ return (
177+ renderablePatch . files . find ( ( candidate ) => resolveFileDiffPath ( candidate ) === filePath ) ?? null
178+ ) ;
179+ }
180+
181+ interface FileScopedCheckpointDiffInput {
182+ threadId : ThreadId | null ;
183+ fromTurnCount : number | null ;
184+ toTurnCount : number | null ;
185+ cacheScope ?: string | null ;
186+ enabled : boolean ;
187+ }
188+
168189function DiffFileSection ( props : {
169190 fileDiff : FileDiffMetadata ;
170191 filePath : string ;
171192 fileKey : string ;
193+ checkpointDiffInput : FileScopedCheckpointDiffInput ;
172194 diffRenderMode : DiffRenderMode ;
173195 diffWordWrap : boolean ;
174196 resolvedTheme : "light" | "dark" ;
175197 collapsed : boolean ;
176198 accepted : boolean ;
199+ contextMode : "patch" | "full" ;
177200 onOpenInEditor : ( filePath : string ) => void ;
178201 onToggleCollapsed : ( filePath : string ) => void ;
179202 onToggleAccepted : ( filePath : string ) => void ;
203+ onContextModeChange : ( filePath : string , contextMode : "patch" | "full" ) => void ;
180204} ) {
181205 const {
182206 accepted,
207+ checkpointDiffInput,
183208 collapsed,
209+ contextMode,
184210 diffRenderMode,
185211 diffWordWrap,
186212 fileDiff,
187213 fileKey,
188214 filePath,
215+ onContextModeChange,
189216 onOpenInEditor,
190217 onToggleAccepted,
191218 onToggleCollapsed,
192219 resolvedTheme,
193220 } = props ;
194221 const stats = summarizeFileDiffStats ( fileDiff ) ;
222+ const fullContextDiffQuery = useQuery (
223+ checkpointDiffQueryOptions ( {
224+ ...checkpointDiffInput ,
225+ relativePath : filePath ,
226+ contextMode : "full" ,
227+ enabled : checkpointDiffInput . enabled && ! collapsed && contextMode === "full" ,
228+ } ) ,
229+ ) ;
230+ const fullContextPatch = useMemo (
231+ ( ) =>
232+ getRenderablePatch (
233+ contextMode === "full" ? fullContextDiffQuery . data ?. diff : undefined ,
234+ `diff-panel:file:${ resolvedTheme } :${ filePath } :full` ,
235+ ) ,
236+ [ contextMode , filePath , fullContextDiffQuery . data ?. diff , resolvedTheme ] ,
237+ ) ;
238+ const resolvedFileDiff =
239+ contextMode === "full" ? ( resolveRenderableFileDiff ( fullContextPatch , filePath ) ?? fileDiff ) : fileDiff ;
240+ const fullContextError =
241+ contextMode === "full" && fullContextDiffQuery . error
242+ ? fullContextDiffQuery . error instanceof Error
243+ ? fullContextDiffQuery . error . message
244+ : "Failed to load full-file context."
245+ : null ;
195246
196247 return (
197248 < section
@@ -227,6 +278,25 @@ function DiffFileSection(props: {
227278 < DiffStatLabel additions = { stats . additions } deletions = { stats . deletions } />
228279 </ span >
229280 ) }
281+ < ToggleGroup
282+ className = "shrink-0"
283+ variant = "outline"
284+ size = "xs"
285+ value = { [ contextMode ] }
286+ onValueChange = { ( value ) => {
287+ const next = value [ 0 ] ;
288+ if ( next === "patch" || next === "full" ) {
289+ onContextModeChange ( filePath , next ) ;
290+ }
291+ } }
292+ >
293+ < Toggle aria-label = { `Show patch diff for ${ filePath } ` } value = "patch" >
294+ Patch
295+ </ Toggle >
296+ < Toggle aria-label = { `Show full file context for ${ filePath } ` } value = "full" >
297+ Full
298+ </ Toggle >
299+ </ ToggleGroup >
230300 < Button
231301 size = "xs"
232302 variant = { accepted ? "secondary" : "outline" }
@@ -242,8 +312,16 @@ function DiffFileSection(props: {
242312 </ div >
243313 { ! collapsed && (
244314 < div key = { fileKey } >
315+ { contextMode === "full" && fullContextDiffQuery . isLoading ? (
316+ < DiffPanelLoadingState label = "Loading full file..." />
317+ ) : null }
318+ { fullContextError ? (
319+ < div className = "border-b border-border/60 bg-destructive/8 px-3 py-2 text-[11px] text-destructive/80" >
320+ { fullContextError }
321+ </ div >
322+ ) : null }
245323 < FileDiff
246- fileDiff = { fileDiff }
324+ fileDiff = { resolvedFileDiff }
247325 options = { {
248326 diffStyle : diffRenderMode === "split" ? "split" : "unified" ,
249327 lineDiffType : "none" ,
@@ -506,6 +584,12 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) {
506584 } ,
507585 [ updateActiveReviewState ] ,
508586 ) ;
587+ const onChangeFileContextMode = useCallback (
588+ ( filePath : string , contextMode : "patch" | "full" ) => {
589+ updateActiveReviewState ( ( current ) => setDiffFileContextMode ( current , filePath , contextMode ) ) ;
590+ } ,
591+ [ updateActiveReviewState ] ,
592+ ) ;
509593
510594 const latestSelectedTurnId = orderedTurnDiffSummaries [ 0 ] ?. turnId ?? null ;
511595
@@ -674,17 +758,27 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) {
674758 const fileReviewState = activeReviewState [ filePath ] ?? {
675759 accepted : false ,
676760 collapsed : true ,
761+ contextMode : "patch" as const ,
677762 } ;
678763 return (
679764 < DiffFileSection
680765 key = { themedFileKey }
681766 accepted = { fileReviewState . accepted }
767+ checkpointDiffInput = { {
768+ threadId : activeThreadId ,
769+ fromTurnCount : activeCheckpointRange ?. fromTurnCount ?? null ,
770+ toTurnCount : activeCheckpointRange ?. toTurnCount ?? null ,
771+ cacheScope : selectedTurn ? `turn:${ selectedTurn . turnId } ` : conversationCacheScope ,
772+ enabled : isGitRepo ,
773+ } }
682774 collapsed = { fileReviewState . collapsed }
775+ contextMode = { fileReviewState . contextMode }
683776 diffRenderMode = { diffRenderMode }
684777 diffWordWrap = { diffWordWrap }
685778 fileDiff = { fileDiff }
686779 fileKey = { themedFileKey }
687780 filePath = { filePath }
781+ onContextModeChange = { onChangeFileContextMode }
688782 onOpenInEditor = { openDiffFileInCodeViewer }
689783 onToggleAccepted = { onToggleFileAccepted }
690784 onToggleCollapsed = { onToggleFileCollapsed }
0 commit comments