fix: session reconnect catch-up, always-on input, frozen session recovery (#524)

- WebSocketContext: emit 'websocket-reconnected' on onopen when it's a reconnect
  (hasConnectedRef tracks first-connect vs. subsequent reconnects).
- useChatRealtimeHandlers: handle 'websocket-reconnected' via onWebSocketReconnect
  callback; added to globalMessageTypes to bypass sessionId mismatch checks.
- ChatInterface: on reconnect, re-fetch JSONL session history so messages missed
  during iOS background are shown immediately. Also resets isLoading and
  canAbortSession so a dead/restarted session no longer freezes the UI forever.
- ChatComposer: remove disabled={isLoading} from textarea — users can always
  type regardless of processing state; submit button still prevents double-send.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
patrickmwatson
2026-03-11 03:06:57 -04:00
committed by GitHub
parent a77f213dd5
commit 4d8fb6e30a
4 changed files with 34 additions and 3 deletions

View File

@@ -48,6 +48,7 @@ interface UseChatRealtimeHandlersArgs {
onSessionNotProcessing?: (sessionId?: string | null) => void;
onReplaceTemporarySession?: (sessionId?: string | null) => void;
onNavigateToSession?: (sessionId: string) => void;
onWebSocketReconnect?: () => void;
}
const appendStreamingChunk = (
@@ -113,6 +114,7 @@ export function useChatRealtimeHandlers({
onSessionNotProcessing,
onReplaceTemporarySession,
onNavigateToSession,
onWebSocketReconnect,
}: UseChatRealtimeHandlersArgs) {
const lastProcessedMessageRef = useRef<LatestChatMessage | null>(null);
@@ -136,7 +138,7 @@ export function useChatRealtimeHandlers({
: null;
const messageType = String(latestMessage.type);
const globalMessageTypes = ['projects_updated', 'taskmaster-project-updated', 'session-created'];
const globalMessageTypes = ['projects_updated', 'taskmaster-project-updated', 'session-created', 'websocket-reconnected'];
const isGlobalMessage = globalMessageTypes.includes(messageType);
const lifecycleMessageTypes = new Set([
'claude-complete',
@@ -300,6 +302,11 @@ export function useChatRealtimeHandlers({
}
break;
case 'websocket-reconnected':
// WebSocket dropped and reconnected — re-fetch session history to catch up on missed messages
onWebSocketReconnect?.();
break;
case 'token-budget':
if (latestMessage.data) {
setTokenBudget(latestMessage.data);