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
- Bump Dockerfile base image from node:18-alpine to node:22-alpine to
fix build failure (better-sqlite3@12.8.0 requires Node 20+)
- Add post-transaction verification logging in POST /api/votes/live to
detect live_votes insertion failures in production
- Add direct DB assertion to regression test for live_votes population
- Add end-to-end integration tests covering the full vote flow: POST
vote -> GET /api/sessions/:id/votes -> GET /api/votes -> direct DB
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
The waitForGameStart() function checked roomStatus.full before
roomStatus.locked, causing it to short-circuit when a room was full
but the game hadn't started yet. This meant game.started was never
broadcast and watchGameAsAudience() was never called for full games.
Now only locked=true triggers game start detection. When full=true
but locked=false, the poller continues until the game actually starts.
Co-authored-by: Cursor <cursoragent@cursor.com>
Broadcast audience.joined when the audience client receives its first
client/welcome frame. Broadcast game.started when the room lock is
detected. Reduced initial wait and poll interval from 30s to 10s for
faster feedback.
Co-authored-by: Cursor <cursoragent@cursor.com>
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>
JWT_SECRET and ADMIN_KEY no longer fall back to insecure defaults.
The app will throw at startup if these env vars are not set.
docker-compose.yml now uses :? syntax to require them.
Co-authored-by: Cursor <cursoragent@cursor.com>