@@ -16,6 +16,7 @@ import { buildPatchCacheKey } from "../lib/diffRendering";
1616import {
1717 expandDiffFile ,
1818 reconcileDiffFileReviewState ,
19+ setDiffFileContextMode ,
1920 toggleDiffFileAccepted ,
2021 toggleDiffFileCollapsed ,
2122 type DiffFileReviewStateByPath ,
@@ -166,33 +167,85 @@ function summarizeFileDiffStats(fileDiff: FileDiffMetadata): {
166167 ) ;
167168}
168169
170+ function resolveRenderableFileDiff (
171+ renderablePatch : RenderablePatch | null ,
172+ filePath : string ,
173+ ) : FileDiffMetadata | null {
174+ if ( ! renderablePatch || renderablePatch . kind !== "files" ) {
175+ return null ;
176+ }
177+ return (
178+ renderablePatch . files . find ( ( candidate ) => resolveFileDiffPath ( candidate ) === filePath ) ?? null
179+ ) ;
180+ }
181+
182+ interface FileScopedCheckpointDiffInput {
183+ threadId : ThreadId | null ;
184+ fromTurnCount : number | null ;
185+ toTurnCount : number | null ;
186+ cacheScope ?: string | null ;
187+ enabled : boolean ;
188+ }
189+
169190function DiffFileSection ( props : {
170191 fileDiff : FileDiffMetadata ;
171192 filePath : string ;
172193 fileKey : string ;
194+ checkpointDiffInput : FileScopedCheckpointDiffInput ;
173195 diffRenderMode : DiffRenderMode ;
174196 diffWordWrap : boolean ;
175197 resolvedTheme : "light" | "dark" ;
176198 collapsed : boolean ;
177199 accepted : boolean ;
200+ contextMode : "patch" | "full" ;
178201 onOpenInEditor : ( filePath : string ) => void ;
179202 onToggleCollapsed : ( filePath : string ) => void ;
180203 onToggleAccepted : ( filePath : string ) => void ;
204+ onContextModeChange : ( filePath : string , contextMode : "patch" | "full" ) => void ;
181205} ) {
182206 const {
183207 accepted,
208+ checkpointDiffInput,
184209 collapsed,
210+ contextMode,
185211 diffRenderMode,
186212 diffWordWrap,
187213 fileDiff,
188214 fileKey,
189215 filePath,
216+ onContextModeChange,
190217 onOpenInEditor,
191218 onToggleAccepted,
192219 onToggleCollapsed,
193220 resolvedTheme,
194221 } = props ;
195222 const stats = summarizeFileDiffStats ( fileDiff ) ;
223+ const fullContextDiffQuery = useQuery (
224+ checkpointDiffQueryOptions ( {
225+ ...checkpointDiffInput ,
226+ relativePath : filePath ,
227+ contextMode : "full" ,
228+ enabled : checkpointDiffInput . enabled && ! collapsed && contextMode === "full" ,
229+ } ) ,
230+ ) ;
231+ const fullContextPatch = useMemo (
232+ ( ) =>
233+ getRenderablePatch (
234+ contextMode === "full" ? fullContextDiffQuery . data ?. diff : undefined ,
235+ `diff-panel:file:${ resolvedTheme } :${ filePath } :full` ,
236+ ) ,
237+ [ contextMode , filePath , fullContextDiffQuery . data ?. diff , resolvedTheme ] ,
238+ ) ;
239+ const resolvedFileDiff =
240+ contextMode === "full"
241+ ? ( resolveRenderableFileDiff ( fullContextPatch , filePath ) ?? fileDiff )
242+ : fileDiff ;
243+ const fullContextError =
244+ contextMode === "full" && fullContextDiffQuery . error
245+ ? fullContextDiffQuery . error instanceof Error
246+ ? fullContextDiffQuery . error . message
247+ : "Failed to load full-file context."
248+ : null ;
196249
197250 return (
198251 < section
@@ -228,6 +281,25 @@ function DiffFileSection(props: {
228281 < DiffStatLabel additions = { stats . additions } deletions = { stats . deletions } />
229282 </ span >
230283 ) }
284+ < ToggleGroup
285+ className = "shrink-0"
286+ variant = "outline"
287+ size = "xs"
288+ value = { [ contextMode ] }
289+ onValueChange = { ( value ) => {
290+ const next = value [ 0 ] ;
291+ if ( next === "patch" || next === "full" ) {
292+ onContextModeChange ( filePath , next ) ;
293+ }
294+ } }
295+ >
296+ < Toggle aria-label = { `Show patch diff for ${ filePath } ` } value = "patch" >
297+ Patch
298+ </ Toggle >
299+ < Toggle aria-label = { `Show full file context for ${ filePath } ` } value = "full" >
300+ Full
301+ </ Toggle >
302+ </ ToggleGroup >
231303 < Button
232304 size = "xs"
233305 variant = { accepted ? "secondary" : "outline" }
@@ -243,8 +315,16 @@ function DiffFileSection(props: {
243315 </ div >
244316 { ! collapsed && (
245317 < div key = { fileKey } >
318+ { contextMode === "full" && fullContextDiffQuery . isLoading ? (
319+ < DiffPanelLoadingState label = "Loading full file..." />
320+ ) : null }
321+ { fullContextError ? (
322+ < div className = "border-b border-border/60 bg-destructive/8 px-3 py-2 text-[11px] text-destructive/80" >
323+ { fullContextError }
324+ </ div >
325+ ) : null }
246326 < FileDiff
247- fileDiff = { fileDiff }
327+ fileDiff = { resolvedFileDiff }
248328 options = { {
249329 diffStyle : diffRenderMode === "split" ? "split" : "unified" ,
250330 lineDiffType : "none" ,
@@ -507,6 +587,12 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) {
507587 } ,
508588 [ updateActiveReviewState ] ,
509589 ) ;
590+ const onChangeFileContextMode = useCallback (
591+ ( filePath : string , contextMode : "patch" | "full" ) => {
592+ updateActiveReviewState ( ( current ) => setDiffFileContextMode ( current , filePath , contextMode ) ) ;
593+ } ,
594+ [ updateActiveReviewState ] ,
595+ ) ;
510596
511597 const latestSelectedTurnId = orderedTurnDiffSummaries [ 0 ] ?. turnId ?? null ;
512598
@@ -675,17 +761,29 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) {
675761 const fileReviewState = activeReviewState [ filePath ] ?? {
676762 accepted : false ,
677763 collapsed : true ,
764+ contextMode : "patch" as const ,
678765 } ;
679766 return (
680767 < DiffFileSection
681768 key = { themedFileKey }
682769 accepted = { fileReviewState . accepted }
770+ checkpointDiffInput = { {
771+ threadId : activeThreadId ,
772+ fromTurnCount : activeCheckpointRange ?. fromTurnCount ?? null ,
773+ toTurnCount : activeCheckpointRange ?. toTurnCount ?? null ,
774+ cacheScope : selectedTurn
775+ ? `turn:${ selectedTurn . turnId } `
776+ : conversationCacheScope ,
777+ enabled : isGitRepo ,
778+ } }
683779 collapsed = { fileReviewState . collapsed }
780+ contextMode = { fileReviewState . contextMode }
684781 diffRenderMode = { diffRenderMode }
685782 diffWordWrap = { diffWordWrap }
686783 fileDiff = { fileDiff }
687784 fileKey = { themedFileKey }
688785 filePath = { filePath }
786+ onContextModeChange = { onChangeFileContextMode }
689787 onOpenInEditor = { openDiffFileInCodeViewer }
690788 onToggleAccepted = { onToggleFileAccepted }
691789 onToggleCollapsed = { onToggleFileCollapsed }
0 commit comments