Commit graph

10 commits

Author SHA1 Message Date
Chris Parsons
6dfe56e178
fix: fully sync local draft state after socket reconnection (#41)
On reconnect, revalidate() re-fetches fresh loader data but the local
useState copies (picks, currentPick, isPaused, isDraftComplete, queue)
never synced with the new values — leaving the UI stale until a manual
refresh.

- Watch revalidatorState (idle→loading→idle) to detect when a
  revalidation completes and apply the fresh loader snapshot to all
  affected local state
- Buffer pick-made socket events received during the revalidation window
  (instead of discarding or double-applying them); merge into the DB
  snapshot on completion, deduplicated by pick ID
- Keep setCurrentPick paired with setPicks in handlePickMade so the
  "on the clock" indicator and the picks list never desync
- Sync queue state from fresh userQueue after revalidation so
  participants drafted while the user was away disappear from their
  queue even if the participant-removed-from-queues events were missed

Adds useDraftSocket reconnect test suite (9 tests) covering initial
connect, socket reconnect, visibility-change triggering, network
flapping, error exhaustion, and cleanup.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 22:50:46 -08:00
Chris Parsons
364b05cd96
Fix draft state not updating when returning from backgrounded mobile app (#39)
* 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>
2026-02-25 09:36:52 -08:00
Chris Parsons
64a60a75d8
fix: eliminate socket ghost connection and stale state on reconnect (#38)
- Reduce ping detection window from ~45s to ~15s (pingTimeout 5s, pingInterval 10s)
- Listen to window offline/online events for instant UI feedback on network drop
- Force immediate reconnect on online event instead of waiting for backoff
- Fix stuck-reconnecting bug when network returns within ping timeout window
- Wrap on/off/emit in useCallback to prevent listener re-registration every second
- Expose reconnectCount to trigger revalidator.revalidate() after each reconnect
- Reset hasConnectedOnce ref per socket instance to avoid spurious revalidations
- Remove stale socket ref from hook return value; add emit helper instead

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 12:24:04 -08:00
Chris Parsons
77e408cad8
Make draft room fully usable on mobile (#30)
- Add bottom nav bar (Lobby/Board/Roster/Controls) with 56px touch targets
- Add mobile card list view in AvailableParticipantsSection with 44px Draft/Queue buttons
- Add round labels and sticky header row to DraftGridSection grid
- Add mobile Sheet for commissioner actions in DraftGridSection (replaces right-click context menu)
- Use useMediaQuery to render a single layout tree, eliminating duplicate component
  instances (previously both desktop and mobile layouts were always in the DOM)
- Add compact header on mobile (text-lg vs text-3xl); hide commissioner buttons
  and Exit link in header on mobile — surfaced in Controls tab instead
- Add "On Clock" bar on mobile showing current team and timer during active draft
- Add "your turn" indicator dot on Lobby bottom nav tab
- Default mobile tab to "board" for commissioner-only viewers
- Fix sticky corner cell z-index in TeamsDraftedGrid (z-10 → z-20)
- Fix round column spacer in DraftGridSection header not being sticky-left
- Extract shared prop objects to eliminate copy-paste between layouts
- Extract getParticipantState() helper to deduplicate participant render logic
- Move MobileSheetData type and MOBILE_TABS array to module scope
- Remove unnecessary non-null assertions in DraftGridSection Sheet handlers

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 22:36:12 -08:00
Chris Parsons
401efcd833
Add browser push notifications toggle to draft page (#11)
* Add push notifications implementation plan

Documents the approach for adding a browser Notification API toggle
to the draft page sidebar, including files to create/modify and
edge cases to handle.

https://claude.ai/code/session_0149MvVUYDY6pFUAV1fL4K69

* Add browser push notifications toggle to draft page

Adds a Notifications toggle in the draft sidebar (below Autodraft)
that uses the Browser Notification API to alert users when a pick is
made or when it's their turn, while the tab is not focused.

- New useDraftNotifications hook for state, permission, and localStorage
- New NotificationSettings component with Switch toggle
- Fires "It's your turn to pick!" when the next pick is the user's
- Fires "{Team} picked {Player}" for all other picks
- Gracefully hides toggle when Notification API is unavailable

https://claude.ai/code/session_0149MvVUYDY6pFUAV1fL4K69

* Fix all code review issues with push notifications

- Extract snake draft order calculation to shared getTeamForPick()
  helper in lib/draft-order.ts, removing the duplicated logic
- Fix notification firing on user's own picks (guard with team id check)
- Fix notification firing after draft is complete (early return)
- Use a ref for sendNotification to prevent full socket handler
  re-registration every time the toggle is changed
- Add permission drift listener via Permissions API so the UI reacts
  if the user revokes notification permission in browser settings
- Add SSR guard for document.hidden in sendNotification
- Remove redundant isSupported prop from NotificationSettings;
  derive unsupported state from permissionState === "unsupported"
- Move NotificationSettings out of QueueSection prop-drilling path;
  render it via a new settingsSection slot on DraftSidebar instead

https://claude.ai/code/session_0149MvVUYDY6pFUAV1fL4K69

* Fix second round of notification code review issues

- Add My Turn Only / All Picks mode granularity so users can choose
  to only be notified when it's their pick, or for every pick
- Remove double top-border by stripping leftover border-t/pt-4/mt-4
  wrapper from NotificationSettings (now provided by DraftSidebar)
- Remove unused isSupported from hook return value
- Add n.onclick = () => window.focus() so clicking a notification
  brings the draft tab back into focus
- Scope localStorage keys to userId to avoid shared-browser conflicts
  (keys now: draftNotifications-{userId}-{seasonId})
- Add notificationsModeRef alongside sendNotificationRef so mode
  changes don't trigger full socket handler re-registration

https://claude.ai/code/session_0149MvVUYDY6pFUAV1fL4K69

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 19:30:53 -08:00
Chris Parsons
5b10548f13 feat: Add ConnectionOverlay component for improved socket connection handling and user feedback 2025-10-26 21:01:12 -07:00
Chris Parsons
ff97e25438 feat: add autodraft status and team connection indicators to draft grid 2025-10-21 23:22:17 -07:00
Chris Parsons
e2b06be6b6 feat: add socket test route and echo event handler for debugging 2025-10-17 12:15:07 -07:00
Chris Parsons
b9743aacc1 Revert Phase 8 and 9 implementations back to 0bef91e 2025-10-16 10:17:31 -07:00
Chris Parsons
61ef5f3fe7 refactor: migrate server.js to TypeScript and update dependencies for socket.io support 2025-10-16 00:56:31 -07:00