Claude/websocket state machine session 01 timj g nk un4 v mv rck4k p76s#909
Claude/websocket state machine session 01 timj g nk un4 v mv rck4k p76s#909marcodejongh wants to merge 10 commits intomainfrom
Conversation
…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
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
- 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
Break the BoardSessionBridge reactivation loop that caused infinite connect/disconnect cycles after join failures. Track failed session IDs so the bridge doesn't re-activate a session that was just cleared. Also harden getSessionMembers() against partial Redis data (missing username would violate String! schema constraint) and add production-safe logging to the joinSession resolver. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The "detect failed session" effect in BoardSessionBridge was marking sessions as failed on initial mount when activeSession is null (not yet set), preventing the activation effect from ever running. Now only marks a session as failed after it was previously active and then cleared. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The EVENTS_REPLAY query was using GraphQL field aliases (addedItem: item, currentItem: item), so the response already had aliased field names. But transformToSubscriptionEvent was reading event.item (the un-aliased name), which was undefined at runtime. Remove the aliases so the response matches the QueueEvent type, and the transform function correctly remaps the fields. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nnection Extract the 400-line connection useEffect from persistent-session-context.tsx into two testable, non-React classes: - ConnectionStateMachine: explicit FSM replacing 6+ scattered boolean state variables, eliminating inconsistent state combinations - SessionConnection: encapsulates client lifecycle, session join/leave, subscription setup, reconnection with delta sync, and transient retry logic This reduces persistent-session-context.tsx from 1418 to ~990 lines and makes the connection orchestration independently testable. https://claude.ai/code/session_01TimjGNkUn4VMvRCK4kP76s
Generated patch from the WebSocket ConnectionStateMachine + SessionConnection refactor since git push is returning 403. https://claude.ai/code/session_01TimjGNkUn4VMvRCK4kP76s
Claude ReviewIssues
DocumentationThe TestsNew test files have good coverage for:
No missing test coverage identified for the changes. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 356f498195
ℹ️ 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".
| } else { | ||
| this.scheduleRetry(); | ||
| } |
There was a problem hiding this comment.
Reset state before scheduling transient reconnect attempts
When joinSession() returns null, connect() treats it as transient and calls scheduleRetry(), but the state machine is still in CONNECTING. The timer then calls connect() again, which exits immediately because of the duplicate-connection guard, so retries never actually run and the session can remain stuck in a perpetual connecting state after a single transient failure.
Useful? React with 👍 / 👎.
| } finally { | ||
| // Mark as connected now that resync is complete | ||
| if (!this.disposed) { | ||
| this.stateMachine.transition('CONNECTED'); | ||
| } |
There was a problem hiding this comment.
Gate CONNECTED transition on successful reconnect resync
handleReconnect() always transitions to CONNECTED in finally, even when joinSession() failed and the function returned early. Since joinSession() converts errors into null, transient reconnect failures will still flip flags to connected, causing the UI to leave reconnect/view-only mode without a completed rejoin or sync and potentially operate on stale state.
Useful? React with 👍 / 👎.
No description provided.