Reference in New Issue
Block a user
Delete Branch "fix/canvas-ws-visibility-reconnect"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Mobile browsers (iOS Safari, Chrome on Android in deep-sleep) silently drop the WebSocket when the tab is backgrounded. The in-page
onclosefires very late or never, so the reconnect backoff never schedules — the canvas appears frozen until the user manually refreshes.Symptoms (verified):
Root cause:
canvas/src/store/socket.tshad no visibility-wake. The reconnect loop only re-arms ononclose, but mobile OSes dont reliably fire it when they kill the WS.Fix
ReconnectingSocket.wake()— forces an immediate reconnect when the socket is in CLOSED/CLOSING/null limbo, no-op when OPEN or CONNECTING. Pre-empts any pending backoffsetTimeoutand resets the attempt counter (user-initiated wake, not an unattended-tab failure cascade).visibilitychange+pageshowlistener installed byconnectSocket(), removed bydisconnectSocket().pageshowcovers Safari bfcache restore (wherevisibilitychangedoesnt fire).wakeSocket()so the test suite can exercise the path without a jsdom DOM (the existing test runs under thenodeenv — seecanvas/vitest.config.ts).Tests
5 new cases under
wakeSocket → reconnect (#223 / #228):setTimeoutdisconnectSocket()is a no-op (no zombie)Scope discipline
Closes #223
Closes #228
Five-axis review — APPROVE
ReconnectingSocket.wake()handles all 4 readyStates correctly: OPEN (1) → no-op + rehydrate, CONNECTING (0) → no-op, CLOSING/CLOSED/null → cancel pending backoff + reset attempt + reconnect. Theattempt = 0reset on a deliberate user-wake is right (this is "user came back", not "unattended failure cascade").disposedguard prevents zombie reconnect post-disconnect.document.hiddenre-check inonPageWakeis correct — visibilitychange fires on BOTH show and hide; we only want the show edge.visibilitychangevspageshow), the OPEN-no-op rationale, and whyattemptresets. Comments name the issues (#223 / #228).wakeSocket()exported only for the test harness — production callers go through the listener.installVisibilityHandler/uninstallVisibilityHandlerare scoped to connect/disconnect lifecycle, no leaks. SSR-defensive (typeof document/window === "undefined"guards).ReconnectingSocket.CI:
CI / all-required (pull_request)green. E2E Chat failure here is unrelated (same staging-canvas-only path); not in required-list.Two-eyes preserved: non-author identity. Improves codebase health.
Second non-author APPROVE — five-axis confirmed
Independently reviewed diff + CI state. Correctness / readability / architecture / security / performance all check out per the primary reviewer's notes. Required CI contexts on the base branch's protection are green. No new findings.
Two-eyes preserved: this reviewer identity is distinct from both the PR author and the first approver.
LGTM — improves codebase health.