Skip to content

Fix iOS connection loss: track WebSocket state and show reconnecting …#906

Open
marcodejongh wants to merge 5 commits intomainfrom
claude/fix-ios-connection-loss-1QjLN
Open

Fix iOS connection loss: track WebSocket state and show reconnecting …#906
marcodejongh wants to merge 5 commits intomainfrom
claude/fix-ios-connection-loss-1QjLN

Conversation

@marcodejongh
Copy link
Owner

…overlay

When iOS Safari backgrounds the app, the OS silently kills the WebSocket connection. Previously the UI stayed fully interactive while changes silently failed to sync. This adds real-time WebSocket connection state tracking via graphql-ws on.connected/on.closed callbacks, a visibilitychange handler to proactively detect dead sockets on iOS foreground return, and a "Reconnecting..." overlay on the queue control bar that blocks interaction until the connection recovers.

https://claude.ai/code/session_016bdrJ2mAmjzSmZ2z6vWGN9

…overlay

When iOS Safari backgrounds the app, the OS silently kills the WebSocket
connection. Previously the UI stayed fully interactive while changes silently
failed to sync. This adds real-time WebSocket connection state tracking via
graphql-ws on.connected/on.closed callbacks, a visibilitychange handler to
proactively detect dead sockets on iOS foreground return, and a
"Reconnecting..." overlay on the queue control bar that blocks interaction
until the connection recovers.

https://claude.ai/code/session_016bdrJ2mAmjzSmZ2z6vWGN9
@claude
Copy link

claude bot commented Mar 3, 2026

Claude Review

Ready to merge - Minor issues noted below, but nothing blocking.

Issues

  1. Missing documentation update - This PR adds iOS-specific connection handling (visibilitychange detection, isReconnecting state, reconnecting overlay) that should be documented in docs/websocket-implementation.md under "Failure States and Recovery". The existing docs describe client disconnection recovery but don't mention iOS Safari killing background WebSockets or the new UI feedback mechanism.

  2. No tests added - The PR changes critical connection state handling without test coverage for:

    • isReconnecting state transitions (hasConnected && !isWebSocketConnected)
    • Visibility change handler behavior
    • Overlay rendering conditions

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 27d953fc2b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

on: {
connected: () => {
if (DEBUG) console.log(`[GraphQL] Client #${clientId} connected (first: ${!hasConnectedOnce})`);
onConnectionStateChange?.(true);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep reconnect lock until state resync finishes

onConnectionStateChange(true) is emitted as soon as the WebSocket reconnects, before onReconnectCallback() completes its async joinSession/replay sync path. Because reconnect UI gating is derived from this connection flag (hasConnected && !isWebSocketConnected), the app exits reconnect mode and re-enables queue mutations while missed events may still be replaying. After a disconnect with sequence drift, users can act on stale queue state and have those actions race with or be overwritten by the delayed resync.

Useful? React with 👍 / 👎.

The on.connected callback was setting isWebSocketConnected=true immediately,
before handleReconnect could finish its async joinSession/delta-sync path.
This caused isReconnecting to become false prematurely, re-enabling queue
mutations while stale state was still being replayed.

Fix: onConnectionStateChange now receives an isReconnect flag. On
reconnections, the persistent session defers setting isWebSocketConnected=true
until handleReconnect's finally block, keeping the reconnecting overlay
and viewOnlyMode active throughout the entire resync.

Also adds iOS background connection loss documentation to
docs/websocket-implementation.md (section 7 under Failure States).

https://claude.ai/code/session_016bdrJ2mAmjzSmZ2z6vWGN9
@claude
Copy link

claude bot commented Mar 3, 2026

Claude Review

Ready to merge - Minor issues noted below, but nothing blocking.

Issues

  1. No tests for new state machine - The isReconnecting state derivation and the reconnection flow have several edge cases (initial connection vs reconnection, deferred state update after resync). Consider adding unit tests for:
    • Initial connection: isReconnecting should remain false
    • Reconnection: isReconnecting should be true until handleReconnect completes
    • Visibility change debouncing behavior

Notes

  • Documentation properly updated in docs/websocket-implementation.md
  • The reconnection logic with deferred setIsWebSocketConnected(true) in the finally block is correctly implemented
  • Theme-aware overlay using design tokens is consistent with project patterns

claude added 3 commits March 3, 2026 20:00
- graphql-client.test.ts: Tests onConnectionStateChange callback fires
  with correct (connected, isReconnect) flags across initial connect,
  disconnect, and reconnection sequences. Verifies callback ordering
  (stateChange fires before onReconnect).

- connection-state.test.ts: Tests isReconnecting derivation truth table,
  reconnect lock timing (isReconnecting stays true during resync), and
  visibilitychange debounce behavior (300ms delay, rapid-fire cancellation,
  hidden state ignored).

https://claude.ai/code/session_016bdrJ2mAmjzSmZ2z6vWGN9
The full-page overlay blocked all user interaction. Replace it with a
bottom-center Snackbar/Alert that shows "Reconnecting..." with a spinner
and automatically disappears when the connection is re-established.
Queue mutations are still prevented by viewOnlyMode during reconnection.

https://claude.ai/code/session_016bdrJ2mAmjzSmZ2z6vWGN9
@claude
Copy link

claude bot commented Mar 3, 2026

Claude Review

Ready to merge - Well-implemented iOS reconnection handling with proper state tracking, documentation, and tests.

Minor issues:

  1. Test description mismatch - packages/web/app/components/persistent-session/__tests__/connection-state.test.ts:399: Comment says "neither connected" but test passes isWebSocketConnected = true. The assertion is correct but the description is misleading.

  2. Unrelated package-lock changes - The dev to devOptional changes for playwright packages appear unrelated to this PR.

Notes:

  • Documentation properly updated in docs/websocket-implementation.md
  • Test coverage is good for the core logic (graphql-client callbacks, derivation, visibility debounce)

@claude
Copy link

claude bot commented Mar 3, 2026

Claude Review

Ready to merge - Well-implemented iOS connection loss handling with comprehensive test coverage and updated documentation.

No significant issues found. The implementation correctly:

  • Tracks WebSocket state via onConnectionStateChange callbacks
  • Defers isWebSocketConnected=true during reconnection until resync completes (prevents mutations on stale state)
  • Uses debounced visibilitychange handler for proactive dead socket detection
  • Threads isReconnecting through the context chain to enable viewOnlyMode
  • Updates docs/websocket-implementation.md with the new failure state

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants