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>
Adds POST /:id/voting/start and POST /:id/voting/end endpoints that
broadcast poll lifecycle events and persist poll state to the sessions
table. The poll.leading WebSocket message is now handled server-side
(rebroadcast + DB persist) with self-healing for polls started before
the persistence columns existed.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Add ticker column to games table with migration
- Bootstrap tickers from tickers.json config on startup
- POST /api/votes/live accepts optional ticker field for direct game
lookup (bypasses timestamp-interval matching)
- Ticker votes work for any game, not just session games
- Update API docs and add e2e tests for ticker voting
- Version bump to 0.6.5
Made-with: Cursor
Add 20-second game.status WebSocket heartbeat from active shard monitors
containing full game state, and GET /status-live REST endpoint for on-demand
polling. Fix missing token destructuring in SessionInfo causing crash.
Relax frontend polling from 3s to 60s since WebSocket events now cover
real-time updates. Bump version to 0.6.0.
Made-with: Cursor
Add Connection Roles table documenting host, player, shard, audience,
observer, and moderator roles with their capabilities and slot impact.
Add shard client/welcome capture and passive room monitoring section.
Made-with: Cursor
Replaces room-monitor.js (REST polling) and player-count-checker.js
(Puppeteer/CDP audience join) with a single EcastShardClient that
connects as a shard via direct WebSocket. Defines new event contract,
integration points, error handling, and reconnection strategy.
Made-with: Cursor
Occupied slots = effective player count since held reconnection slots
are unavailable to new players. connections - 1 = player count,
maxPlayers - (connections - 1) = available slots. Clean and simple.
Made-with: Cursor
Key corrections based on testing with fresh room SCWX:
- connections count includes ALL ever-joined players, not just active ones
(slots persist for reconnection, count never decreases)
- here field also includes disconnected players (slot reservation model)
- client/connected and client/disconnected confirmed as NOT delivered to
player connections after extensive testing
- Jackbox has no concept of "leaving" — player disconnect is invisible
to the API
- Added reconnection URL format (secret + id query params)
- Added error code 2027 (REST/WebSocket state divergence)
- Added ws-lifecycle-test.js for systematic protocol testing
Made-with: Cursor
Update README.md, docs/api/README.md, session-lifecycle guide,
webhooks-and-events guide, and archive docs (BOT_INTEGRATION,
API_QUICK_REFERENCE) with new endpoints and vote.received event.
Made-with: Cursor
Update REST endpoint docs (votes.md, sessions.md), WebSocket protocol
(websocket.md), OpenAPI spec, and voting guide with the new
GET /api/votes, GET /api/sessions/:id/votes, and vote.received event.
Made-with: Cursor
Covers WebSocket vote.received event, GET /api/sessions/:id/votes
breakdown, GET /api/votes paginated history, and two-phase TDD
testing strategy with regression tests before implementation.
Made-with: Cursor
Extracts checkRoomStatus into shared jackbox-api.js with proper
User-Agent header (bare fetch was silently rejected by Jackbox API)
and always-on error logging (previously gated behind DEBUG flag).
Splits room-start detection (room-monitor.js) from audience-based
player counting (player-count-checker.js) to eliminate circular
dependency and allow immediate game.started detection. Room monitor
now polls immediately instead of waiting 10 seconds for first check.
Made-with: Cursor
Moved 9 documentation .md files from root into docs/.
Moved 4 test scripts from root into tests/.
Updated cross-references in README.md and docs to reflect new paths.
Co-authored-by: Cursor <cursoragent@cursor.com>