Files
joshuaandClaude Opus 5.5 825eb14f2e Phase 13: questions, sounds and videos on the scoreboard
A game can now carry a list of items the host puts on the scoreboard one at
a time: a question (shown above the standings, with an optional picture and
an answer to reveal), a sound (played through the scoreboard's speakers) or
a video such as a karaoke track (takes over the screen).

- Media is a YouTube link, an https link to a file, or an upload to the
  private game-media bucket. The scoreboard API signs a URL only for the
  item that is on screen.
- What is on screen is state on the event, like the timer, so the ping
  still carries no data. An unrevealed answer and a sound's or video's name
  never reach the scoreboard payload.
- Control room: click an item to show it, or "Random" to have one picked
  (prefers items not played yet). Show/hide answer, pause, take off screen;
  shortcuts Q / A / X.
- Duplicating a game or event copies its items and files; deleting removes
  the files.

Needs supabase/migrations/0006_screen_items.sql applied before deploying.
database.types.ts was edited by hand to match; re-run gen:types after.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 10:54:08 +02:00

7.0 KiB
Raw Permalink Blame History

Build plan

Work one phase at a time. At the end of each phase: run lint + tests, commit, and give the human a short "how to test this manually" checklist. Don't start the next phase until told.

Phase 0 — Scaffold

  • Next.js + TS strict + Tailwind + ESLint + Vitest. src/ layout per ARCHITECTURE.md.
  • Fonts: Big Shoulders (Google's new name for Big Shoulders Display) + Atkinson Hyperlegible via next/font/google. Tokens from UI.md as CSS variables + Tailwind theme.
  • Supabase clients (browser, server, admin with server-only).
  • .env.local loading; fail fast with a clear error if vars are missing. Done when: npm run dev shows a placeholder home; npm run test runs a dummy test.

Phase 1 — Database + types

  • Apply supabase/migrations/0001_init.sql. Generate TS types into src/lib/database.types.ts.
  • src/lib/tokens.ts zod schemas (32-char hex). Done when: tables visible in Supabase; anon key cannot select from any table (write a quick script/test proving it).

Phase 2 — Scoring engine (TDD)

  • Implement SCORING.md in src/lib/scoring/ as pure functions.
  • All listed test cases + edge cases. Done when: tests green, 100% branch coverage on the scoring module.

Phase 3 — Admin auth + setup screens

  • /login magic link; proxy.ts guards /admin/* with ADMIN_EMAILS.
  • Events list + create/duplicate/delete.
  • Event setup: teams CRUD (colour picker, reorder), games builder with criteria + presets, notes. Done when: I can build a "Worship Game Night" with 4 teams and a Karaoke game with 5 criteria (1–5) and a Guess the Lyric game with 1 number criterion.

Phase 4 — Scoreboard + realtime

  • src/lib/realtime.ts notify() (server broadcast) + useRefreshChannel(channel, refetch) hook (debounce, reconnect, 10s poll, refetch on focus).
  • GET /api/public/scoreboard/[token] building the payload via the scoring engine.
  • /s/[token] page per UI.md: FLIP rank animation, count-up totals, reduced motion, fullscreen, cursor hide, connection dot. Done when: changing anything in the DB via admin (or SQL + a manual notify) updates an open scoreboard tab in < 1s.

Phase 5 — Control room (direct scoring)

  • /admin/events/[id]/live: game list, prev/next, set current (→ live), mark done.
  • Scoring grid with block + number inputs, autosave, optimistic UI.
  • Bonus/penalty panel with quick buttons, custom, reason, undo.
  • Keyboard shortcuts. Done when: I run a full direct-scored game and the scoreboard tracks every change.

Phase 6 — Visibility + reveal

  • Visible / Frozen / Hidden toggle. Freeze stores snapshot server-side. Hidden strips scores in the API.
  • Reveal mode: start, reveal next (bottom-up), finish → visible. Done when: in Hidden mode, the network tab on the scoreboard contains NO totals. Reveal works end-to-end.

Phase 7 — Judges

  • Nominate judges, copy link, QR codes (use a small QR lib), revoke.
  • Open link toggle + /jo/[token] name flow + localStorage.
  • /j/[token] mobile page: team cards, spotlight pinned, block rows, autosave status, read-only states.
  • PUT /api/judge/[token]/scores with all checks from ARCHITECTURE.md + rate limit. Clearing = DELETE or value: null.
  • Control room in judged mode: averages, judge progress matrix, per-judge breakdown, override cells, spotlight select, lock toggle. Done when: 2 phones judging Karaoke at once, admin sees progress live, scoreboard updates with averages, override works, lock makes judges read-only.

Phase 8 — Polish + deploy

  • Share panel (all links + QR in one place, "copy all judge links for this game" as text for WhatsApp).
  • Empty states + error copy per UI.md.
  • Accessibility pass (focus, contrast, tap targets).
  • Deploy to Vercel; set env vars; set Supabase auth redirect URLs to the Vercel domain. Done when: full dry run on the deployed URL: laptop admin + projector scoreboard + 2 phone judges.

Phase 9 — Timer + sound effects

  • Apply supabase/migrations/0002_timer.sql, then npm run gen:types.
  • Timer rules in src/lib/timer.ts (tested); control-room panel (set, start/pause/reset, show/hide, T); scoreboard display.
  • Scoreboard sound effects: reveal drum-roll + crash/fanfare, game-change chime, last-10-seconds ticks + buzzer; "Turn sound on" button. Done when: a timer started in the control room counts down on the scoreboard with ticks and a buzzer, and a reveal plays its drum-roll.

Phase 10 — Team buzzers

  • Apply supabase/migrations/0003_buzzer.sql, then npm run gen:types and npm run check:rls.
  • Buzzer rules in src/lib/buzzer.ts (tested); /b/[token] phone page + GET/POST buzzer API.
  • "Teams buzz in" checkbox per game; buzzer links per team in the share panel.
  • Control room: buzz order + "Clear buzzers" (B). Scoreboard: "{Team} buzzed!" banner + ding. Done when: two phones on different teams buzz in a buzzer game; the control room and scoreboard show who was first within a second, and "Clear buzzers" resets both phones.

Phase 11 — Buzzer answers

  • Apply supabase/migrations/0004_buzz_result.sql, then npm run gen:types.
  • checkMark / bumpScore / answeringTeamId in src/lib/buzzer.ts (tested); markBuzz server action.
  • Control room: "Got it" (G), "Wrong" (W), "Undo", "Next question" (B).
  • Phone and scoreboard follow whose turn it is; "correct" and "wrong" sounds. Done when: team A buzzes then team B; "Wrong" hands the turn to B; "Got it" gives B one point in the scoring grid and on the scoreboard; "Undo" takes it back; "Next question" resets the phones.

Phase 12 — Co-hosts

  • Apply supabase/migrations/0005_cohosts.sql, then npm run gen:types and npm run check:rls.
  • event_cohosts table; RLS widened from owner to owner-or-co-host, with delete and sharing owner-only.
  • "Co-hosts" section on the setup page (addCohost / removeCohost server actions); "Shared with you" in the events list. Done when: admin A shares an event with admin B's email; B sees it in their list, can edit it and run the control room alongside A, but has no Delete button and can't change the co-hosts; removing B makes the event disappear from B's list.

Phase 13 — Questions, sounds and videos on the scoreboard

  • Apply supabase/migrations/0006_screen_items.sql (table, event columns, the game-media bucket and its policies), then npm run gen:types and npm run check:rls.
  • Rules in src/lib/screen.ts (tested); setup editor per game with links and uploads; control-room panel (show, random, answer, pause, take off screen; Q / A / X).
  • Scoreboard: question above the standings, sound banner, video takeover. Done when: a quiz game shows its questions one by one with Q and reveals an answer with A (the answer is not in the scoreboard's network tab before that); an uploaded mp3 plays on the scoreboard; a karaoke game with several YouTube links plays the one I click full screen, "Random video" picks one that hasn't been played, and "Take off screen" brings the standings back.

Stretch (only if asked)

Participants within teams · CSV export of results.