@@ -25,7 +25,7 @@ function dirPath(path: string): string {
2525 return parts . slice ( 0 , - 1 ) . join ( "/" ) + "/" ;
2626}
2727
28- export function DiffEditor ( props : { cwd : string } ) {
28+ export function DiffEditor ( props : { cwd : string ; prNumber ?: number | null } ) {
2929 const { setStore } = appStore ;
3030 const [ files , setFiles ] = createSignal < ChangedFile [ ] > ( [ ] ) ;
3131 const [ diffs , setDiffs ] = createSignal < FileDiff [ ] > ( [ ] ) ;
@@ -44,15 +44,18 @@ export function DiffEditor(props: { cwd: string }) {
4444
4545 async function loadAll ( ) {
4646 const cwd = props . cwd ;
47+ const prNum = props . prNumber ;
4748 if ( ! cwd || cwd === "." ) {
4849 setFiles ( [ ] ) ;
4950 setDiffs ( [ ] ) ;
5051 setLoading ( false ) ;
5152 return ;
5253 }
5354
55+ const cacheKey = prNum ? `pr:${ prNum } :${ cwd } ` : cwd ;
56+
5457 // Check cache first
55- const cached = diffCache . get ( cwd ) ;
58+ const cached = diffCache . get ( cacheKey ) ;
5659 if ( cached ) {
5760 setFiles ( cached . files ) ;
5861 setDiffs ( cached . diffs ) ;
@@ -63,24 +66,34 @@ export function DiffEditor(props: { cwd: string }) {
6366
6467 setLoading ( true ) ;
6568 setError ( null ) ;
66- // Clear old data immediately for clean transition
6769 setFiles ( [ ] ) ;
6870 setDiffs ( [ ] ) ;
6971 setSelectedFile ( null ) ;
7072
7173 try {
72- const [ changedFiles , sessionDiffs ] = await Promise . all ( [
73- ipc . getChangedFiles ( cwd ) ,
74- ipc . getSessionDiff ( cwd ) ,
75- ] ) ;
76- // Only update if cwd hasn't changed while we were fetching
74+ let changedFiles : any [ ] ;
75+ let sessionDiffs : any [ ] ;
76+
77+ if ( prNum ) {
78+ // PR mode: fetch PR diff instead of working directory diff
79+ const prDiffRaw = await ipc . getPrDiff ( cwd , prNum ) ;
80+ // Parse the raw diff into our FileDiff format
81+ const parsed = parsePrDiff ( prDiffRaw ) ;
82+ changedFiles = parsed . files ;
83+ sessionDiffs = parsed . diffs ;
84+ } else {
85+ [ changedFiles , sessionDiffs ] = await Promise . all ( [
86+ ipc . getChangedFiles ( cwd ) ,
87+ ipc . getSessionDiff ( cwd ) ,
88+ ] ) ;
89+ }
90+
7791 if ( props . cwd === cwd ) {
7892 setFiles ( changedFiles ) ;
7993 setDiffs ( sessionDiffs ) ;
8094 if ( changedFiles . length > 0 ) setSelectedFile ( changedFiles [ 0 ] . path ) ;
81- // Cache for 30 seconds
82- diffCache . set ( cwd , { files : changedFiles , diffs : sessionDiffs } ) ;
83- setTimeout ( ( ) => diffCache . delete ( cwd ) , 30000 ) ;
95+ diffCache . set ( cacheKey , { files : changedFiles , diffs : sessionDiffs } ) ;
96+ setTimeout ( ( ) => diffCache . delete ( cacheKey ) , 30000 ) ;
8497 }
8598 } catch ( e ) {
8699 if ( props . cwd === cwd ) setError ( String ( e ) ) ;
@@ -89,9 +102,49 @@ export function DiffEditor(props: { cwd: string }) {
89102 }
90103 }
91104
92- // Reload when cwd changes (thread switch)
105+ /** Parse raw unified diff text into ChangedFile[] and FileDiff[] */
106+ function parsePrDiff ( raw : string ) : { files : any [ ] ; diffs : any [ ] } {
107+ const files : any [ ] = [ ] ;
108+ const diffs : any [ ] = [ ] ;
109+ if ( ! raw ) return { files, diffs } ;
110+
111+ const fileSections = raw . split ( / ^ d i f f - - g i t / m) . filter ( Boolean ) ;
112+ for ( const section of fileSections ) {
113+ const lines = section . split ( "\n" ) ;
114+ // Extract file path from "a/path b/path"
115+ const headerMatch = lines [ 0 ] ?. match ( / a \/ ( .+ ?) b \/ ( .+ ) / ) ;
116+ const path = headerMatch ? headerMatch [ 2 ] : "unknown" ;
117+
118+ let insertions = 0 , deletions = 0 ;
119+ const hunkLines : any [ ] = [ ] ;
120+ let hunkHeader = "" ;
121+
122+ for ( const line of lines . slice ( 1 ) ) {
123+ if ( line . startsWith ( "@@" ) ) {
124+ hunkHeader = line ;
125+ } else if ( line . startsWith ( "+" ) && ! line . startsWith ( "+++" ) ) {
126+ insertions ++ ;
127+ hunkLines . push ( { line_type : "add" , content : line . slice ( 1 ) , old_line : null , new_line : null } ) ;
128+ } else if ( line . startsWith ( "-" ) && ! line . startsWith ( "---" ) ) {
129+ deletions ++ ;
130+ hunkLines . push ( { line_type : "delete" , content : line . slice ( 1 ) , old_line : null , new_line : null } ) ;
131+ } else if ( ! line . startsWith ( "\\" ) && ! line . startsWith ( "index" ) && ! line . startsWith ( "---" ) && ! line . startsWith ( "+++" ) && ! line . startsWith ( "new" ) && ! line . startsWith ( "old" ) && ! line . startsWith ( "deleted" ) && ! line . startsWith ( "similarity" ) ) {
132+ hunkLines . push ( { line_type : "context" , content : line . startsWith ( " " ) ? line . slice ( 1 ) : line , old_line : null , new_line : null } ) ;
133+ }
134+ }
135+
136+ const status = deletions > 0 && insertions > 0 ? "modified" : insertions > 0 ? "added" : "deleted" ;
137+ files . push ( { path, status, insertions, deletions } ) ;
138+ diffs . push ( { path, hunks : [ { header : hunkHeader , lines : hunkLines } ] } ) ;
139+ }
140+
141+ return { files, diffs } ;
142+ }
143+
144+ // Reload when cwd or prNumber changes (thread switch)
93145 createEffect ( ( ) => {
94- const _ = props . cwd ; // track reactive prop
146+ const _ = props . cwd ;
147+ const _pr = props . prNumber ;
95148 loadAll ( ) ;
96149 } ) ;
97150
@@ -130,7 +183,7 @@ export function DiffEditor(props: { cwd: string }) {
130183 < svg class = "de-header-icon" width = "16" height = "16" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" >
131184 < path d = "M12 3v18" /> < path d = "M3 12h18" />
132185 </ svg >
133- < h3 > Changes</ h3 >
186+ < h3 > { props . prNumber ? `PR # ${ props . prNumber } ` : " Changes" } </ h3 >
134187 < Show when = { ! loading ( ) && files ( ) . length > 0 } >
135188 < span class = "de-stat-summary" >
136189 < span class = "de-stat-files" > { files ( ) . length } file{ files ( ) . length !== 1 ? "s" : "" } </ span >
0 commit comments