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

105 lines
7.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.