@@ -245,6 +245,30 @@ const AIViewInner: React.FC<AIViewProps> = ({
245245 }
246246 } , [ workspaceState , editingMessage ] ) ;
247247
248+ // Force re-evaluation after 2s when last message is a recent user message
249+ // This ensures retry barrier appears even if stream-start never arrives
250+ // Must be before early return to satisfy React Hooks rules
251+ useEffect ( ( ) => {
252+ if ( ! workspaceState ) return ;
253+ const { messages, canInterrupt } = workspaceState ;
254+
255+ if ( messages . length === 0 ) return ;
256+ const lastMessage = messages [ messages . length - 1 ] ;
257+
258+ if ( lastMessage . type === "user" && ! canInterrupt ) {
259+ const messageAge = Date . now ( ) - ( lastMessage . timestamp ?? 0 ) ;
260+ const timeUntilCheck = Math . max ( 0 , 2100 - messageAge ) ; // 2.1s to ensure we're past threshold
261+
262+ if ( timeUntilCheck > 0 ) {
263+ const timer = setTimeout ( ( ) => {
264+ // Force re-render to re-evaluate showRetryBarrier
265+ setForceRecheck ( ( prev ) => prev + 1 ) ;
266+ } , timeUntilCheck ) ;
267+ return ( ) => clearTimeout ( timer ) ;
268+ }
269+ }
270+ } , [ workspaceState ] ) ;
271+
248272 // Return early if workspace state not loaded yet
249273 if ( ! workspaceState ) {
250274 return (
@@ -272,26 +296,6 @@ const AIViewInner: React.FC<AIViewProps> = ({
272296 // Uses same logic as useResumeManager for DRY
273297 const showRetryBarrier = ! canInterrupt && hasInterruptedStream ( messages ) ;
274298
275- // Force re-evaluation after 2s when last message is a recent user message
276- // This ensures retry barrier appears even if stream-start never arrives
277- useEffect ( ( ) => {
278- if ( messages . length === 0 ) return ;
279- const lastMessage = messages [ messages . length - 1 ] ;
280-
281- if ( lastMessage . type === "user" && ! canInterrupt ) {
282- const messageAge = Date . now ( ) - ( lastMessage . timestamp ?? 0 ) ;
283- const timeUntilCheck = Math . max ( 0 , 2100 - messageAge ) ; // 2.1s to ensure we're past threshold
284-
285- if ( timeUntilCheck > 0 ) {
286- const timer = setTimeout ( ( ) => {
287- // Force re-render to re-evaluate showRetryBarrier
288- setForceRecheck ( ( prev ) => prev + 1 ) ;
289- } , timeUntilCheck ) ;
290- return ( ) => clearTimeout ( timer ) ;
291- }
292- }
293- } , [ messages , canInterrupt ] ) ;
294-
295299 // Note: We intentionally do NOT reset autoRetry when streams start.
296300 // If user pressed Ctrl+C, autoRetry stays false until they manually retry.
297301 // This makes state transitions explicit and predictable.
0 commit comments