@@ -48,18 +48,39 @@ export function useWorkspaceName(options: UseWorkspaceNameOptions): UseWorkspace
4848
4949 // Track the message that was used for the last successful generation
5050 const lastGeneratedForRef = useRef < string > ( "" ) ;
51- // Promise that resolves when current generation completes
52- const generationPromiseRef = useRef < {
53- promise : Promise < string > ;
54- resolve : ( name : string ) => void ;
55- } | null > ( null ) ;
5651 // Debounce timer
5752 const debounceTimerRef = useRef < ReturnType < typeof setTimeout > | null > ( null ) ;
53+ // Message pending in debounce timer (captured at schedule time)
54+ const pendingMessageRef = useRef < string > ( "" ) ;
5855 // Generation request counter for cancellation
5956 const requestIdRef = useRef ( 0 ) ;
57+ // Current in-flight generation promise and its resolver
58+ const generationPromiseRef = useRef < {
59+ promise : Promise < string > ;
60+ resolve : ( name : string ) => void ;
61+ requestId : number ;
62+ } | null > ( null ) ;
6063
6164 const name = autoGenerate ? generatedName : manualName ;
6265
66+ // Cancel any pending generation and resolve waiters with empty string
67+ const cancelPendingGeneration = useCallback ( ( ) => {
68+ if ( debounceTimerRef . current ) {
69+ clearTimeout ( debounceTimerRef . current ) ;
70+ debounceTimerRef . current = null ;
71+ pendingMessageRef . current = "" ;
72+ }
73+ // Increment request ID to invalidate any in-flight request
74+ const oldRequestId = requestIdRef . current ;
75+ requestIdRef . current ++ ;
76+ // Resolve any waiters so they don't hang forever
77+ if ( generationPromiseRef . current && generationPromiseRef . current . requestId === oldRequestId ) {
78+ generationPromiseRef . current . resolve ( "" ) ;
79+ generationPromiseRef . current = null ;
80+ setIsGenerating ( false ) ;
81+ }
82+ } , [ ] ) ;
83+
6384 const generateName = useCallback (
6485 async ( forMessage : string ) : Promise < string > => {
6586 if ( ! api || ! forMessage . trim ( ) ) {
@@ -77,24 +98,25 @@ export function useWorkspaceName(options: UseWorkspaceNameOptions): UseWorkspace
7798 } ) ;
7899 // TypeScript doesn't understand the Promise executor runs synchronously
79100 const safeResolve = resolvePromise ! ;
80- generationPromiseRef . current = { promise, resolve : safeResolve } ;
101+ generationPromiseRef . current = { promise, resolve : safeResolve , requestId } ;
81102
82103 try {
83104 const result = await api . nameGeneration . generate ( {
84105 message : forMessage ,
85106 } ) ;
86107
87- // Check if this request is still current
108+ // Check if this request is still current (wasn't cancelled)
88109 if ( requestId !== requestIdRef . current ) {
110+ // Don't resolve here - cancellation already resolved the promise
89111 return "" ;
90112 }
91113
92114 if ( result . success ) {
93- const generatedName = result . data . name ;
94- setGeneratedName ( generatedName ) ;
115+ const name = result . data . name ;
116+ setGeneratedName ( name ) ;
95117 lastGeneratedForRef . current = forMessage ;
96- safeResolve ( generatedName ) ;
97- return generatedName ;
118+ safeResolve ( name ) ;
119+ return name ;
98120 } else {
99121 const errorMsg =
100122 result . error . type === "unknown" && "raw" in result . error
@@ -124,35 +146,42 @@ export function useWorkspaceName(options: UseWorkspaceNameOptions): UseWorkspace
124146
125147 // Debounced generation effect
126148 useEffect ( ( ) => {
127- // Clear any pending debounce timer
128- if ( debounceTimerRef . current ) {
129- clearTimeout ( debounceTimerRef . current ) ;
130- debounceTimerRef . current = null ;
131- }
132-
133149 // Don't generate if:
134150 // - Auto-generation is disabled
135151 // - Message is empty
136152 // - Already generated for this message
137153 if ( ! autoGenerate || ! message . trim ( ) || lastGeneratedForRef . current === message ) {
154+ // Clear any pending timer since conditions changed
155+ if ( debounceTimerRef . current ) {
156+ clearTimeout ( debounceTimerRef . current ) ;
157+ debounceTimerRef . current = null ;
158+ pendingMessageRef . current = "" ;
159+ }
138160 return ;
139161 }
140162
141- // Cancel any in-flight request
142- requestIdRef . current ++ ;
163+ // Cancel any in-flight request since message changed
164+ cancelPendingGeneration ( ) ;
165+
166+ // Capture message for the debounced callback (avoid stale closure)
167+ pendingMessageRef . current = message ;
143168
144169 // Debounce the generation
145170 debounceTimerRef . current = setTimeout ( ( ) => {
146- void generateName ( message ) ;
171+ const msg = pendingMessageRef . current ;
172+ debounceTimerRef . current = null ;
173+ pendingMessageRef . current = "" ;
174+ void generateName ( msg ) ;
147175 } , debounceMs ) ;
148176
149177 return ( ) => {
150178 if ( debounceTimerRef . current ) {
151179 clearTimeout ( debounceTimerRef . current ) ;
152180 debounceTimerRef . current = null ;
181+ pendingMessageRef . current = "" ;
153182 }
154183 } ;
155- } , [ message , autoGenerate , debounceMs , generateName ] ) ;
184+ } , [ message , autoGenerate , debounceMs , generateName , cancelPendingGeneration ] ) ;
156185
157186 // When auto-generate is toggled, handle name preservation
158187 const handleSetAutoGenerate = useCallback (
@@ -187,16 +216,20 @@ export function useWorkspaceName(options: UseWorkspaceNameOptions): UseWorkspace
187216 return manualName ;
188217 }
189218
190- // Always wait for any pending generation to complete on the full message.
191- // This is important because with voice input, the message can go from empty
192- // to complete very quickly - we must ensure the generated name reflects the
193- // total content, not a partial intermediate state.
219+ // Always wait for generation to complete on the full message.
220+ // With voice input, the message can go from empty to complete very quickly,
221+ // so we must ensure the generated name reflects the total content.
194222
195223 // If there's a debounced generation pending, trigger it immediately
224+ // Use the captured message from pendingMessageRef to avoid stale closures
196225 if ( debounceTimerRef . current ) {
197226 clearTimeout ( debounceTimerRef . current ) ;
198227 debounceTimerRef . current = null ;
199- return generateName ( message ) ;
228+ const msg = pendingMessageRef . current ;
229+ pendingMessageRef . current = "" ;
230+ if ( msg . trim ( ) ) {
231+ return generateName ( msg ) ;
232+ }
200233 }
201234
202235 // If generation is in progress, wait for it to complete
0 commit comments