@@ -21,6 +21,34 @@ import { clearRepositoryCache } from './utils/cache'
2121import { logError } from './utils/logger'
2222import { debounce } from './utils/debounce'
2323
24+ // Prompt templates (moved outside component to avoid recreation on every render)
25+ const PROMPT_TEMPLATES = [
26+ {
27+ id : 'branch-summary' ,
28+ label : 'Summarize branch diff' ,
29+ content :
30+ 'You are an expert engineer. Summarize the changes between the selected branches and explain the impact of the modifications.' ,
31+ } ,
32+ {
33+ id : 'wd-review' ,
34+ label : 'Review working directory changes' ,
35+ content :
36+ 'You are a code reviewer. Review the current working directory diff for potential issues, bugs, or improvements.' ,
37+ } ,
38+ {
39+ id : 'test-plan' ,
40+ label : 'Suggest tests for diff' ,
41+ content :
42+ 'You are a QA engineer. Based on the provided diff, propose relevant unit or integration tests to cover the changes.' ,
43+ } ,
44+ {
45+ id : 'release-notes' ,
46+ label : 'Draft release notes' ,
47+ content :
48+ 'You are a technical writer. Craft concise release notes that describe the user-facing effects of the diff.' ,
49+ } ,
50+ ]
51+
2452function App ( ) {
2553 const [ appStatus , setAppStatus ] = useState < AppStatus > ( { state : 'IDLE' } )
2654 // note: we will temporarily set task='tokens' while counting, see effect below
@@ -51,6 +79,9 @@ function App() {
5179 compare ?: { binary : boolean ; text : string | null ; notFound ?: boolean }
5280 } | null > ( null )
5381
82+ // Track preview request IDs to prevent race conditions
83+ const previewRequestIdRef = useRef ( 0 )
84+
5485 const [ theme , setTheme ] = useState < 'light' | 'dark' | null > ( ( ) => {
5586 try {
5687 const saved = localStorage . getItem ( 'gc.theme' )
@@ -270,32 +301,6 @@ function App() {
270301 return ( ) => { cancelled = true }
271302 } , [ userInstructions ] )
272303
273- const PROMPT_TEMPLATES = [
274- {
275- id : 'branch-summary' ,
276- label : 'Summarize branch diff' ,
277- content :
278- 'You are an expert engineer. Summarize the changes between the selected branches and explain the impact of the modifications.' ,
279- } ,
280- {
281- id : 'wd-review' ,
282- label : 'Review working directory changes' ,
283- content :
284- 'You are a code reviewer. Review the current working directory diff for potential issues, bugs, or improvements.' ,
285- } ,
286- {
287- id : 'test-plan' ,
288- label : 'Suggest tests for diff' ,
289- content :
290- 'You are a QA engineer. Based on the provided diff, propose relevant unit or integration tests to cover the changes.' ,
291- } ,
292- {
293- id : 'release-notes' ,
294- label : 'Draft release notes' ,
295- content :
296- 'You are a technical writer. Craft concise release notes that describe the user-facing effects of the diff.' ,
297- } ,
298- ]
299304 const [ templateId , setTemplateId ] = useState < string > ( '' )
300305
301306 // Model selection: fetched dynamically; derive token limit from the selected model
@@ -370,6 +375,14 @@ function App() {
370375 const diffRangeRef = useRef < HTMLInputElement | null > ( null )
371376 const MAX_CONTEXT = 999
372377 const debouncedSetDiffContextLines = useMemo ( ( ) => debounce ( setDiffContextLines , 250 ) , [ ] )
378+
379+ // Cancel debounced function on unmount to avoid setState after unmount
380+ useEffect ( ( ) => {
381+ return ( ) => {
382+ debouncedSetDiffContextLines . cancel ( )
383+ }
384+ } , [ debouncedSetDiffContextLines ] )
385+
373386 // Collapsible User Instructions
374387 const [ instructionsOpen , setInstructionsOpen ] = useState < boolean > ( true )
375388
@@ -583,6 +596,10 @@ function App() {
583596 setNotif ( 'Binary file preview is not supported.' )
584597 return
585598 }
599+
600+ // Increment request ID to track this specific preview request
601+ const requestId = ++ previewRequestIdRef . current
602+
586603 try {
587604 const toFetchBase = status !== 'add'
588605 const toFetchCompare = status !== 'remove'
@@ -592,6 +609,11 @@ function App() {
592609 toFetchCompare && compareBranch ? gitClient . readFile ( compareBranch , path ) : Promise . resolve ( undefined ) ,
593610 ] )
594611
612+ // Check if this request is still the latest one (prevent race condition)
613+ if ( requestId !== previewRequestIdRef . current ) {
614+ return // A newer preview request has been made, ignore this result
615+ }
616+
595617 // If worker reports binary, bail out as well
596618 const baseBin = ( baseRes as any ) ?. binary
597619 const compareBin = ( compareRes as any ) ?. binary
@@ -607,8 +629,11 @@ function App() {
607629 } )
608630 setPreviewOpen ( true )
609631 } catch ( e ) {
610- const err = e instanceof Error ? e : new Error ( String ( e ) )
611- setNotif ( `Failed to read file content: ${ err . message } ` )
632+ // Only show error if this is still the latest request
633+ if ( requestId === previewRequestIdRef . current ) {
634+ const err = e instanceof Error ? e : new Error ( String ( e ) )
635+ setNotif ( `Failed to read file content: ${ err . message } ` )
636+ }
612637 }
613638 }
614639
@@ -1151,9 +1176,24 @@ function App() {
11511176 value = { templateId }
11521177 onChange = { ( e ) => {
11531178 const id = e . target . value
1154- setTemplateId ( id )
11551179 const tmpl = PROMPT_TEMPLATES . find ( ( t ) => t . id === id )
1156- if ( tmpl ) setUserInstructions ( tmpl . content )
1180+ if ( tmpl ) {
1181+ // If user has custom instructions, confirm before overwriting
1182+ const hasCustomText = userInstructions . trim ( ) && userInstructions . trim ( ) !== tmpl . content
1183+ if ( hasCustomText ) {
1184+ const confirmed = window . confirm (
1185+ 'This will replace your current instructions. Continue?'
1186+ )
1187+ if ( ! confirmed ) {
1188+ // Reset select to previous value
1189+ return
1190+ }
1191+ }
1192+ setTemplateId ( id )
1193+ setUserInstructions ( tmpl . content )
1194+ } else {
1195+ setTemplateId ( id )
1196+ }
11571197 } }
11581198 style = { { flexGrow : 1 } }
11591199 >
@@ -1297,7 +1337,6 @@ function App() {
12971337
12981338 < div className = "panel-section" >
12991339 < SelectedFilesPanel
1300- key = { `sel-${ selectedPaths . size } ` }
13011340 selectedPaths = { selectedPaths }
13021341 statusByPath = { statusByPath }
13031342 onUnselect = { ( path ) => toggleSelect ( path ) }
0 commit comments