1- import { useState } from "react" ;
21import { toast } from "@heroui/react" ;
32import type { GitBranchInfo , GitStatusResult , Project , ProjectLocation } from "@/shared/contracts" ;
43import { getProjectAgentStatuses } from "@/shared/agentStatus" ;
@@ -7,6 +6,10 @@ import { msg, friendlyError, friendlyErrorWithDetail } from "@/shared/messages";
76import { readBridge } from "@/renderer/bridge" ;
87import { captureProductEvent } from "@/renderer/analytics/posthog" ;
98import { useAgentStatusesStore } from "@/renderer/state/agentStatusesStore" ;
9+ import {
10+ useGitReviewActionState ,
11+ useGitReviewActionStore ,
12+ } from "@/renderer/state/gitReviewActionStore" ;
1013import { useGitStore } from "@/renderer/state/gitStore" ;
1114import { startPostPushPrStatusRefresh } from "@/renderer/state/gitRefresh" ;
1215import { usePullFromSourceDialogStore } from "@/renderer/state/pullFromSourceDialogStore" ;
@@ -99,20 +102,44 @@ export function useGitReviewActions(args: UseGitReviewActionsArgs) {
99102 wslAgentStatuses ,
100103 ) ;
101104
102- const [ commitMessage , setCommitMessage ] = useState ( "" ) ;
103- const [ isCommitting , setIsCommitting ] = useState ( false ) ;
104- const [ isGenerating , setIsGenerating ] = useState ( false ) ;
105- const [ isSyncing , setIsSyncing ] = useState ( false ) ;
106- const [ isMerging , setIsMerging ] = useState ( false ) ;
107- const [ isPullingFromSource , setIsPullingFromSource ] = useState ( false ) ;
108- const [ isAbortingMerge , setIsAbortingMerge ] = useState ( false ) ;
109- const [ isFinishingMerge , setIsFinishingMerge ] = useState ( false ) ;
110-
111- const [ prTitle , setPrTitle ] = useState ( "" ) ;
112- const [ prBody , setPrBody ] = useState ( "" ) ;
113- const [ prTargetBranch , setPrTargetBranch ] = useState < string | null > ( null ) ;
114- const [ isCreatingPr , setIsCreatingPr ] = useState ( false ) ;
115- const [ isGeneratingPr , setIsGeneratingPr ] = useState ( false ) ;
105+ // The git review panel is keyed by `${projectId}:${worktreePath}` and fully
106+ // remounts when the user switches projects, so any useState here would reset
107+ // on switch — dropping draft text, the generation spinner, and any in-flight
108+ // operation's pending state while the work keeps running in the supervisor.
109+ // Holding it all in a store keyed by the same `storeKey` makes it survive the
110+ // remount: spinners reappear, async results land against the right panel even
111+ // if it unmounted mid-flight, and drafts are preserved. See
112+ // gitReviewActionStore.
113+ const {
114+ commitMessage,
115+ prTitle,
116+ prBody,
117+ prTargetBranch,
118+ isGenerating,
119+ isGeneratingPr,
120+ isCommitting,
121+ isSyncing,
122+ isMerging,
123+ isPullingFromSource,
124+ isAbortingMerge,
125+ isFinishingMerge,
126+ isCreatingPr,
127+ } = useGitReviewActionState ( storeKey ) ;
128+ const patch = useGitReviewActionStore ( ( s ) => s . patch ) ;
129+ const setCommitMessage = ( value : string ) => patch ( storeKey , { commitMessage : value } ) ;
130+ const setPrTitle = ( value : string ) => patch ( storeKey , { prTitle : value } ) ;
131+ const setPrBody = ( value : string ) => patch ( storeKey , { prBody : value } ) ;
132+ const setPrTargetBranch = ( value : string | null ) => patch ( storeKey , { prTargetBranch : value } ) ;
133+ const setIsGenerating = ( value : boolean ) => patch ( storeKey , { isGenerating : value } ) ;
134+ const setIsGeneratingPr = ( value : boolean ) => patch ( storeKey , { isGeneratingPr : value } ) ;
135+ const setIsCommitting = ( value : boolean ) => patch ( storeKey , { isCommitting : value } ) ;
136+ const setIsSyncing = ( value : boolean ) => patch ( storeKey , { isSyncing : value } ) ;
137+ const setIsMerging = ( value : boolean ) => patch ( storeKey , { isMerging : value } ) ;
138+ const setIsPullingFromSource = ( value : boolean ) =>
139+ patch ( storeKey , { isPullingFromSource : value } ) ;
140+ const setIsAbortingMerge = ( value : boolean ) => patch ( storeKey , { isAbortingMerge : value } ) ;
141+ const setIsFinishingMerge = ( value : boolean ) => patch ( storeKey , { isFinishingMerge : value } ) ;
142+ const setIsCreatingPr = ( value : boolean ) => patch ( storeKey , { isCreatingPr : value } ) ;
116143
117144 const writeActions = usePrWriteActions ( {
118145 projectLocation : project . location ,
@@ -240,6 +267,9 @@ export function useGitReviewActions(args: UseGitReviewActionsArgs) {
240267 }
241268
242269 async function handleGenerateMessage ( ) : Promise < void > {
270+ // A generation may still be running from before a panel remount — don't
271+ // start a second one; the first one's result will land in the store.
272+ if ( isGenerating ) return ;
243273 setIsGenerating ( true ) ;
244274 try {
245275 const message = await generateMessage ( ) ;
@@ -467,6 +497,9 @@ export function useGitReviewActions(args: UseGitReviewActionsArgs) {
467497 return ;
468498 }
469499
500+ // Don't start a second PR-summary generation if one is still in flight
501+ // from before a panel remount — its result will land in the store.
502+ if ( isGeneratingPr ) return ;
470503 setIsGeneratingPr ( true ) ;
471504 for ( const candidate of candidates ) {
472505 const resolved = resolveCommitGenConfig ( candidate , commitGenModel , commitGenEffort ) ;
0 commit comments