Live (Play)¶
Public Live page for answering open questions. Open questions are delivered over WebSocket; answers stay on REST.
| URL | /play (also linked from admin nav; opens in a new tab) |
| Stream | WS /api/play/stream?api_key= |
| REST | POST /api/auth/login · GET/PATCH /api/users/me · POST/DELETE /api/matches/{id}/follow · PUT /api/question-answers · GET /api/question-answers · GET /api/matches (today’s slate for Following, optional followed=true) · GET /api/users/me/points · GET /api/question-answers/open (catch-up only) |
Mobile / client authors: Live play client.
Workflow¶
- Log in with username or email + password (accounts via admin
Users or
POST /api/auth/register). The API key is stored inlocalStorage. Profile load readsmatch_daily_follow_countand syncs browser IANAmatch_follow_timezonewhen it differs. - The page opens
WS /api/play/stream, appliessnapshot/question.opened/question.closed, and submits answers with BearerPUT /api/question-answers. Open questions are the primary UI: countdown and prompt first, then a situation / clock / period line when the issuing event had that context, then large choice buttons; a Pivotal badge when the definition was markedis_pivotal_moment; a Match question badge for pre-game Match questions; sponsors and issuance IDs sit below. With multiple open questions, unanswered questions stay on top; answering one sinks that question below the others (still changeable until the window closes, or until first pitch for Match questions). When a Match question locks (answer_locked=true),/playremoves it from the open column and refreshes Recent answers. Locked pending Match answers stay at the top of Recent answers and do not consume the usual 10-row recent limit (Pending until the match is scored). Live plays a short browser tone onquestion.openedand a different tone onquestion.closedwithreason=resolved(not on reconnectsnapshot, lock republish, retracts, or stale closes). Sounds are off by default; Turn sound on/off in the header toggles this and persists inlocalStorage. - Following: the bar lists each followed match as a label with
(questions/pivotal)issuance totals from the match list (e.g.NYY @ CHC (7/3)= 7 non-pivotal questions issued, 3 pivotal moments — mutually exclusive; plus remaining daily slots when restricted; hover shows the match shortcode after a short delay). Counts refresh with the followable slate poll and bump on livequestion.opened. The Choose-matches modal lists today’s matches only (calendar day in the user’smatch_follow_timezone; all statuses), grouped by league, with the same(questions/pivotal)counts and shortcode in each row. Follows for other days stay on the server but are hidden from the bar/modal until that day’s slate. - Restricted (non-null daily limit): must choose matches via
POST/DELETE …/follow; empty selection shows no questions. Unfollow before start refunds today's slot; unfollow after start (non-test) does not. Test matches never count and always refund. - Unrestricted (
nulllimit): must still follow matches to see live questions (empty selection shows none); no daily quota. Each league header has All / None for bulk select. On login the local list is unioned with server follows (so clone auto-follow appears), then pruned to today’s slate. Toggle / clear / league bulk also syncPOST/DELETE …/follow. Clone-and-listen also appends the new id tolocalStorageimmediately. - Closed matches cannot be newly followed (checkbox disabled; All skips them). Matches already followed before close stay selected and may be unfollowed (no quota refund).
- Following a non-test in progress match asks for confirmation: unfollowing later will not refund the daily follow slot (same as server rules). Unfollow after start asks again before dropping the follow.
- Points and recent answers refresh on a slower REST cadence (and after
closes). If the socket drops, the page reconnects and falls back to a slow
GET /openpoll until the stream is healthy again.
Layout: mobile-first — compact header/Following, questions in a single column, Recent answers collapsed by default under ~900px (expandable). From ~900px up, questions take a wider column beside a sticky Recent answers panel.
Recent answers cards (shared admin_answer_history_list) show match label,
shortcode, a short issuance id (first 8 of question_issuance_id) with a
copy button for the full UUID, and answered-at. Expand for the full issuance id
(also copyable), definition id, sponsors, and event/condition details.
Report this: on a resolved recent answer, a logged-in player can file
user feedback. Locked pending Match answers stay unsubmittable. The modal
asks for a Topic (from GET /api/user-feedback/form
for that row’s kind) and an optional description (max 250 characters).
One submission per answer. After a successful POST (or a 409 already
submitted), the control becomes a quiet Feedback sent. History
offers the same control for operators reviewing a player. Mobile contract:
Report this. Operators
review submissions on User Feedback.
Notes¶
/playis outside/admin(no admin session required).- If the stored Bearer key returns Invalid or revoked API key, Play clears the local session (same as Change) and shows the login form again.
- Admin operators sign in at
/admin/login; Manage → Users creates accounts. - Ops (cache TTL, WS connection caps, Caddy/ALB path routing): Environment configuration.