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

@@ -29,6 +29,7 @@ const buildWebSocketUrl = (token: string | null) => {
const useWebSocketProviderState = (): WebSocketContextType => {
const wsRef = useRef<WebSocket | null>(null);
const unmountedRef = useRef(false); // Track if component is unmounted
const hasConnectedRef = useRef(false); // Track if we've ever connected (to detect reconnects)
const [latestMessage, setLatestMessage] = useState<any>(null);
const [isConnected, setIsConnected] = useState(false);
const reconnectTimeoutRef = useRef<NodeJS.Timeout | null>(null);
@@ -61,6 +62,11 @@ const useWebSocketProviderState = (): WebSocketContextType => {
websocket.onopen = () => {
setIsConnected(true);
wsRef.current = websocket;
if (hasConnectedRef.current) {
// This is a reconnect — signal so components can catch up on missed messages
setLatestMessage({ type: 'websocket-reconnected', timestamp: Date.now() });
}
hasConnectedRef.current = true;
};
websocket.onmessage = (event) => {