Skip to content

feat: sync activity timer across a player's open sessions - #770

Merged
gaidheal1 merged 1 commit into
developmentfrom
feat/multi-session-timer-sync
Aug 13, 2026
Merged

feat: sync activity timer across a player's open sessions#770
gaidheal1 merged 1 commit into
developmentfrom
feat/multi-session-timer-sync

Conversation

@gaidheal1

Copy link
Copy Markdown
Member

Summary

Fixes #759. If a player has more than one session open (two browser tabs, or a phone + a laptop), starting, labelling, or submitting an activity in one session had no effect on the others — each session's useActivityTimer state was purely local, seeded once from the server and never invalidated by another session's writes.

Changes

Backend

  • Add gameplay.utils.broadcast_activity_timer(timer), reusing the existing per-player player_{id} channel group and the send_group_message/{"type": "action"} pattern already used by gameplay/signals.py and utils.control_timers.
  • Call it after every timer mutation: start/pause/reset/complete in BaseTimerViewSet, and set_activity/label_activity/complete in ActivityTimerViewSet.
  • Also call it from both auto-complete paths in gameplay/tasks.py (the 30s disconnect grace period, and the stale-connection sweep) — these complete a timer server-side without any originating request, so a tab left open elsewhere needs telling too.
  • No TimerConsumer changes needed: its existing generic action handler already relays any {"type": "action", ...} group message straight to the socket.

Frontend

  • Extend WebSocketActionMessage with the new "activity_timer_update" action and its data.activity_timer payload.
  • Wire the previously-stubbed handleGlobalWebSocketEvent dispatcher with an onActivityTimerUpdate option (the load-game stub is left as-is for future use).
  • WebSocketContext calls activityTimer.loadFromServer on receipt, mirroring GameContext's existing bootstrap load (same limitSeconds/is_premium resolution). loadFromServer just overwrites local state, so a session echoing its own update back to itself is a harmless no-op.

Testing

  • Added/updated backend tests asserting broadcast_activity_timer is called from start/complete/set_activity/label_activity and from both Celery auto-complete tasks.
  • User confirmed the full test suite passes locally.

🤖 Generated with Claude Code

Fixes multi-session desync (#759): if a player has more than one
session open (two tabs, phone + laptop), starting, labelling, or
submitting an activity in one had no effect on the others, since
each session's timer state was purely local React state.

Backend:
- Add gameplay.utils.broadcast_activity_timer(timer), reusing the
  existing player_{id} channel group and send_group_message/"action"
  message pattern from gameplay/signals.py and utils.control_timers.
- Call it after every timer mutation: start/pause/reset/complete
  (BaseTimerViewSet), and set_activity/label_activity/complete
  (ActivityTimerViewSet), plus both auto-complete paths in tasks.py
  (disconnect grace period and the stale-connection sweep) so a tab
  left open elsewhere is told the timer already closed out.
- No consumer changes needed: TimerConsumer's existing generic
  "action" handler already relays any {"type": "action", ...} group
  message straight to the socket.

Frontend:
- Extend WebSocketActionMessage with the new "activity_timer_update"
  action and its data payload.
- Wire the previously-stubbed handleGlobalWebSocketEvent dispatcher
  with an onActivityTimerUpdate option.
- WebSocketContext calls activityTimer.loadFromServer on receipt,
  mirroring GameContext's existing bootstrap load (same
  limitSeconds/is_premium resolution). loadFromServer just
  overwrites local state, so a session echoing its own update is a
  harmless no-op.

Tests: broadcast_activity_timer call verified for start/complete/
set_activity/label_activity and both auto-complete Celery tasks.
@gaidheal1
gaidheal1 merged commit 3a170ed into development Aug 13, 2026
2 checks passed
@gaidheal1
gaidheal1 deleted the feat/multi-session-timer-sync branch August 13, 2026 16:55
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.

1 participant