|
1 | | -import { startTransition, useCallback, useEffect, useMemo, useRef, useState } from 'react' |
| 1 | +import { |
| 2 | + startTransition, |
| 3 | + useCallback, |
| 4 | + useEffect, |
| 5 | + useMemo, |
| 6 | + useRef, |
| 7 | + useState, |
| 8 | +} from 'react' |
2 | 9 | import { useKeyboard } from '@opentui/react' |
3 | 10 | import { useShallow } from 'zustand/react/shallow' |
4 | 11 |
|
@@ -203,44 +210,52 @@ export const Chat = ({ |
203 | 210 | const { statusMessage } = useClipboard() |
204 | 211 | const [showReconnectionMessage, setShowReconnectionMessage] = useState(false) |
205 | 212 | const [connectionEstablished, setConnectionEstablished] = useState(0) // Increment to trigger retry check |
206 | | - const { setTimeout: setReconnectionTimeout, clearTimeout: clearReconnectionTimeout } = useSafeTimeout() |
| 213 | + const { |
| 214 | + setTimeout: setReconnectionTimeout, |
| 215 | + clearTimeout: clearReconnectionTimeout, |
| 216 | + } = useSafeTimeout() |
207 | 217 | const retryPendingMessagesRef = useRef<(() => Promise<void>) | null>(null) |
208 | 218 | const processFailedMessagesRef = useRef<(() => void) | null>(null) |
209 | 219 | const retryScheduledRef = useRef(false) |
210 | 220 |
|
211 | | - const handleReconnection = useCallback((isInitialConnection: boolean) => { |
212 | | - logger.debug( |
213 | | - { isInitialConnection }, |
214 | | - `[Connection] ${isInitialConnection ? 'Initial connection' : 'Reconnection'} callback triggered` |
215 | | - ) |
216 | | - |
217 | | - // Invalidate auth queries to allow network status to clear after reconnection |
218 | | - queryClient.invalidateQueries({ queryKey: authQueryKeys.all }) |
219 | | - logger.debug('[Connection] Invalidated auth queries to refresh network status') |
| 221 | + const handleReconnection = useCallback( |
| 222 | + (isInitialConnection: boolean) => { |
| 223 | + logger.debug( |
| 224 | + { isInitialConnection }, |
| 225 | + `[Connection] ${isInitialConnection ? 'Initial connection' : 'Reconnection'} callback triggered`, |
| 226 | + ) |
220 | 227 |
|
221 | | - // Process any failed messages and schedule them for retry (batched) |
222 | | - if (processFailedMessagesRef.current) { |
223 | | - processFailedMessagesRef.current() |
224 | | - } |
| 228 | + // Invalidate auth queries to allow network status to clear after reconnection |
| 229 | + queryClient.invalidateQueries({ queryKey: authQueryKeys.all }) |
| 230 | + logger.debug( |
| 231 | + '[Connection] Invalidated auth queries to refresh network status', |
| 232 | + ) |
225 | 233 |
|
226 | | - // Batch state updates using startTransition to prevent Bun crashes from cascading updates |
227 | | - startTransition(() => { |
228 | | - // Only show reconnection message if it's not the initial connection |
229 | | - if (!isInitialConnection) { |
230 | | - setShowReconnectionMessage(true) |
231 | | - |
232 | | - // Hide the message after the configured duration using safe timeout |
233 | | - setReconnectionTimeout(() => { |
234 | | - startTransition(() => { |
235 | | - setShowReconnectionMessage(false) |
236 | | - }) |
237 | | - }, RECONNECTION_MESSAGE_DURATION_MS) |
| 234 | + // Process any failed messages and schedule them for retry (batched) |
| 235 | + if (processFailedMessagesRef.current) { |
| 236 | + processFailedMessagesRef.current() |
238 | 237 | } |
239 | 238 |
|
240 | | - // Always trigger retry check (for both initial connection and reconnection) |
241 | | - setConnectionEstablished(prev => prev + 1) |
242 | | - }) |
243 | | - }, [queryClient, setReconnectionTimeout]) |
| 239 | + // Batch state updates using startTransition to prevent Bun crashes from cascading updates |
| 240 | + startTransition(() => { |
| 241 | + // Only show reconnection message if it's not the initial connection |
| 242 | + if (!isInitialConnection) { |
| 243 | + setShowReconnectionMessage(true) |
| 244 | + |
| 245 | + // Hide the message after the configured duration using safe timeout |
| 246 | + setReconnectionTimeout(() => { |
| 247 | + startTransition(() => { |
| 248 | + setShowReconnectionMessage(false) |
| 249 | + }) |
| 250 | + }, RECONNECTION_MESSAGE_DURATION_MS) |
| 251 | + } |
| 252 | + |
| 253 | + // Always trigger retry check (for both initial connection and reconnection) |
| 254 | + setConnectionEstablished((prev) => prev + 1) |
| 255 | + }) |
| 256 | + }, |
| 257 | + [queryClient, setReconnectionTimeout], |
| 258 | + ) |
244 | 259 |
|
245 | 260 | const isConnected = useConnectionStatus(handleReconnection) |
246 | 261 | const isConnectedRef = useRef(isConnected) |
@@ -350,7 +365,7 @@ export const Chat = ({ |
350 | 365 |
|
351 | 366 | return block |
352 | 367 | }) |
353 | | - |
| 368 | + |
354 | 369 | // Return original array reference if nothing changed |
355 | 370 | return foundTarget ? result : blocks |
356 | 371 | } |
@@ -529,9 +544,9 @@ export const Chat = ({ |
529 | 544 | retryPendingMessages, |
530 | 545 | processFailedMessages, |
531 | 546 | } = useSendMessage({ |
532 | | - messages, |
533 | | - allToggleIds, |
534 | | - setMessages, |
| 547 | + messages, |
| 548 | + allToggleIds, |
| 549 | + setMessages, |
535 | 550 | setFocusedAgentId, |
536 | 551 | setInputFocused, |
537 | 552 | inputRef, |
@@ -582,19 +597,20 @@ export const Chat = ({ |
582 | 597 | retryPendingMessagesRef.current = retryPendingMessages |
583 | 598 | processFailedMessagesRef.current = processFailedMessages |
584 | 599 |
|
585 | | - // Trigger retry when connection is established and we have pending messages |
| 600 | + // Trigger retry when we have pending messages (either connection restored or error occurred) |
586 | 601 | useEffect(() => { |
587 | | - // Prevent race condition: only schedule retry if one isn't already scheduled |
588 | | - if (connectionEstablished > 0 && pendingRetryCount > 0 && !retryScheduledRef.current) { |
| 602 | + const shouldTriggerRetry = |
| 603 | + pendingRetryCount > 0 && !retryScheduledRef.current |
| 604 | + |
| 605 | + if (shouldTriggerRetry) { |
589 | 606 | logger.debug( |
590 | 607 | { pendingRetryCount, connectionEstablished }, |
591 | | - `[RETRY-EFFECT] Scheduling retry for ${pendingRetryCount} pending message(s) after ${RECONNECTION_RETRY_DELAY_MS}ms delay` |
| 608 | + `[RETRY-EFFECT] Scheduling retry for ${pendingRetryCount} pending message(s) after ${RECONNECTION_RETRY_DELAY_MS}ms delay`, |
592 | 609 | ) |
593 | 610 | retryScheduledRef.current = true |
594 | 611 |
|
595 | | - // Small delay to ensure the connection is fully established |
| 612 | + // Small delay before retrying (gives connection time to stabilize) |
596 | 613 | const timer = setTimeout(() => { |
597 | | - logger.debug('[RETRY-EFFECT] Executing scheduled retry') |
598 | 614 | retryPendingMessagesRef.current?.() |
599 | 615 | retryScheduledRef.current = false |
600 | 616 | }, RECONNECTION_RETRY_DELAY_MS) |
|
0 commit comments