# 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 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. ## Stretch (only if asked) Participants within teams · co-host admins · countdown timer on scoreboard · sound effects on reveal · CSV export of results.