* Fix draft state not updating when returning from backgrounded mobile app
Mobile browsers suspend JavaScript and silently drop WebSocket connections
when the user switches to another app, without firing "offline"/"online"
events. Add a visibilitychange handler so that when the user returns:
- If the socket is disconnected, reconnect immediately (skipping backoff)
- If the socket appears connected but JS was suspended, rejoin the draft
room and trigger a loader revalidation to catch any missed picks/state
https://claude.ai/code/session_016tCZVFjSeHdQsdKktbDHEt
* Simplify reconnect handlers and fix connect_error overlay flicker
- Merge handleOnline and the shared branch of handleVisibilityChange into a
single handleReturn function. visibilitychange is now a thin guard that calls
it only on show. Both events share the same logic: reconnect if socket dropped,
or restore UI state + rejoin room + revalidate if the socket survived.
- Remove setIsReconnecting(false) from connect_error: reconnect_attempt fires
immediately after and resets it to true anyway, causing the "Reconnecting"
overlay to flicker off and back on during every retry cycle.
https://claude.ai/code/session_016tCZVFjSeHdQsdKktbDHEt
* Fix four issues from useDraftSocket code review
- Manager listener leak: add socket.io.off() for reconnect_attempt and
reconnect_failed in cleanup — socket.disconnect() only tears down the
socket, not the Manager listeners, causing them to accumulate on re-mounts.
- reconnect_failed dead code: add reconnectionAttempts: 10 to io() config
so the handler is actually reachable after exhausting retries.
- connectionError flicker: remove setConnectionError from connect_error —
reconnect_attempt fires immediately after and clears it anyway, causing the
error overlay to flash on every retry cycle. Error now only appears via
reconnect_failed once all attempts are exhausted. connect_error instead
ensures setIsReconnecting(true) so the reconnecting overlay shows instead
of the initial "Connecting to Draft" spinner.
- Add comment to on/off/emit noting they are no-ops if called before the
effect runs (socketRef.current === null).
https://claude.ai/code/session_016tCZVFjSeHdQsdKktbDHEt
---------
Co-authored-by: Claude <noreply@anthropic.com>