feat: poll countdown timer, game-selection sync, source tracking, and multi-admin fixes
Work spanning May 7-10 across multiple sessions:
Poll winner detection + source column (May 7):
- Fix race condition in handleEndPolling where WS voting.ended cleared
leadingGame before the setTimeout could capture the winner
- Add pollActiveRef guard to prevent late poll.leading messages from
re-activating an ended poll
- Add 'source' column to session_games (dice/manual/poll) with backward-
compatible fallback from manually_added flag
- Show indigo "Poll" badge in game lists (Picker, Home, SessionDetail)
- Include source in session export (JSON and text formats)
Multi-admin poll state sync (May 9):
- Enrich poll.start broadcast with pollStartedAt timestamp so all admin
clients can start their timers from the correct time
- Enrich voting.ended broadcast with winnerGameId/Label/Votes so all
admins see the winner prompt, not just the one who clicked End Poll
- Add poll.start WS handler in SessionInfo so Admin B sees polls started
by Admin A without refreshing
- Make handleStartPolling optimistic with rollback on failure
WebSocket keepalive + auto-reconnect (May 9):
- Add 30s ping interval to SessionInfo WS connection (matching server's
60s timeout) to prevent silent disconnects
- Add auto-reconnect on close with 3s delay
- Proper cleanup of ping interval, reconnect timeout, and onclose handler
Sync selected game across admin clients (May 10):
- New POST/DELETE /sessions/:id/game-selection endpoints with DB
persistence (pending_game_id, pending_game_source columns)
- Broadcast game.picked/game.dismissed WS events to session subscribers
- handleDismissGame replaces inline setSelectedGame(null) calls
- Restore pending game selection on page load for late-joining admins
- Clear pending selection when game is formally added to session
Poll ending countdown timer (May 10):
- POST /:id/voting/end now accepts optional { delay } (0-300 seconds)
- New POST /:id/voting/cancel-end to abort a scheduled end
- New poll.ending and poll.ending.cancelled WS events
- poll_ending_at column on sessions table for crash recovery
- rescheduleEndingPolls() called on server startup to resume countdowns
- End Poll button opens popover with End Now / 5s / 10s / 30s / custom
- Red "Poll Ending" card with countdown display and Cancel button
- Document new WS events in docs/api/websocket.md
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -143,6 +143,8 @@ Must be authenticated.
|
||||
| `voting.ended` | Host ended the voting/polling period (broadcast to subscribers) |
|
||||
| `poll.start` | Host started a new poll (broadcast to subscribers) |
|
||||
| `poll.leading` | Current poll leader updated (broadcast to subscribers) |
|
||||
| `poll.ending` | Poll is ending after a countdown (broadcast to subscribers) |
|
||||
| `poll.ending.cancelled`| Scheduled poll end was cancelled (broadcast to subscribers) |
|
||||
|
||||
---
|
||||
|
||||
@@ -391,12 +393,15 @@ Possible `reason` values: `room_closed`, `room_not_found`, `connection_failed`,
|
||||
### voting.ended
|
||||
|
||||
- **Broadcast to:** Clients subscribed to the session
|
||||
- **Triggered by:** `POST /api/sessions/:id/voting/end` (host ends the voting/polling period)
|
||||
- **Triggered by:** `POST /api/sessions/:id/voting/end` (host ends the voting/polling period, either immediately or when a countdown reaches zero)
|
||||
|
||||
**Data:**
|
||||
```json
|
||||
{
|
||||
"sessionId": 3
|
||||
"sessionId": 3,
|
||||
"winnerGameId": 42,
|
||||
"winnerLabel": "Quiplash 3",
|
||||
"winnerVotes": 7
|
||||
}
|
||||
```
|
||||
|
||||
@@ -427,6 +432,42 @@ Possible `reason` values: `room_closed`, `room_not_found`, `connection_failed`,
|
||||
}
|
||||
```
|
||||
|
||||
### poll.ending
|
||||
|
||||
- **Broadcast to:** Clients subscribed to the session
|
||||
- **Triggered by:** `POST /api/sessions/:id/voting/end` with `{ "delay": N }` where N > 0
|
||||
|
||||
Signals that a countdown has started and the poll will automatically end when the timer reaches zero. All connected admin clients should display the countdown. The `endsAt` timestamp is authoritative; derive the remaining time on each client by comparing against the local clock.
|
||||
|
||||
**Data:**
|
||||
```json
|
||||
{
|
||||
"sessionId": 3,
|
||||
"endsAt": "2026-05-10T20:15:30.000Z",
|
||||
"delaySeconds": 30
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|----------------|---------|--------------------------------------------------|
|
||||
| `sessionId` | number | The session the poll belongs to |
|
||||
| `endsAt` | string | ISO 8601 timestamp when the poll will auto-end |
|
||||
| `delaySeconds` | number | Original delay requested (seconds, 1-300) |
|
||||
|
||||
### poll.ending.cancelled
|
||||
|
||||
- **Broadcast to:** Clients subscribed to the session
|
||||
- **Triggered by:** `POST /api/sessions/:id/voting/cancel-end`
|
||||
|
||||
The scheduled poll end was cancelled by an admin. Clients should stop displaying the countdown and revert to the normal "Voting In Progress" state.
|
||||
|
||||
**Data:**
|
||||
```json
|
||||
{
|
||||
"sessionId": 3
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Error Handling
|
||||
|
||||
Reference in New Issue
Block a user