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>
The owner shares an event by email; a co-host gets full access inside it
but can't delete it or change who it's shared with. Shares are keyed by
email so an admin can be added before their first login, and RLS (not the
UI) is what enforces the owner-only parts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The host marks the answering team "Got it" or "Wrong" (G / W). Wrong
passes the turn to the next team in the buzz order; Got it closes the
buzzers until "Next question" (B). Undo reverses either mark.
In a direct game a correct answer adds 1 to the team's score in the
game's first criterion, as an ordinary admin score row, so it flows
through the scoring engine (multiplier, placement) and the existing
range/step trigger. The point is written before the mark, so a refused
point never leaves a "correct" with nothing behind it. Judged games
only get the mark: the judges' scores stand.
Needs supabase/migrations/0004_buzz_result.sql applied before deploying.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each team gets a buzzer link (/b/[token]); a game opts in with a
"Teams buzz in" checkbox. The database's clock orders the buzzes, so
the result doesn't depend on phone clocks. The control room lists the
order and clears it per question (B); the scoreboard shows who buzzed
first with a ding.
Buzzes follow the existing rules: token-gated route handlers with the
service-role client, RLS on the new table, and data-free realtime pings
on a new buzzer:{eventId} channel.
Needs supabase/migrations/0003_buzzer.sql applied before deploying.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After verifying the link, /auth/confirm built its redirect from
request.nextUrl. Behind a proxy or tunnel a route handler sees the
address the server is bound to (localhost:4322), so the browser was
sent off the public domain and away from the session cookie it had
just been given. Build the redirect from NEXT_PUBLIC_SITE_URL instead,
the same value the magic link email already uses.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sets the app version to 1.0.0. Also includes the .gitignore entry for
/docs added in the working copy (existing docs stay tracked).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Timer: one per event, set from the control room (duration, presets,
start/pause/resume/reset, show/hide, T shortcut). Rules are pure
functions in src/lib/timer.ts; the server's clock sets the end time and
each screen counts down locally, corrected for its own clock.
Needs supabase/migrations/0002_timer.sql.
- Sound effects on the scoreboard, synthesised with Web Audio (no files):
drum-roll + crash per revealed team, fanfare for first place, a chime
on game change, ticks for the last 10 seconds and a buzzer at zero.
Triggers are pure functions comparing consecutive payloads.
- 'Turn sound on' button on the scoreboard, since browsers block audio
until a click; the choice is remembered across reloads.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Share links section: scoreboard link, every judged game's judge links
and open link with QR codes, and 'Copy all judge links' as plain text
for WhatsApp. Warns when links point at localhost.
- Error and not-found pages with plain-language copy; a real start page.
- Accessibility: darker 'alert strong' red for text and white-on-red
(Alert was 4.0:1 on white), no text below 70% ink, 44px block buttons
and link targets, scoreboard status stays readable when idle.
- vercel.json pins functions to Dublin next to the West EU database;
README documents deployment and pre-game-night checks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Setup: nominate judges, copy personal links, QR codes (generated on
demand in the browser), revoke/restore, and an open-link toggle.
- GET /api/judge/[token] returns only that judge's own scores; PUT
.../scores runs every check from ARCHITECTURE.md in checkJudgeWrite
(pure, unit-tested) after one parallel lookup, plus a rate limit.
- /j/[token]: mobile cards per team, spotlight pinned first, autosave
with per-card status, read-only when locked/done, link-off when revoked.
- /jo/[token]: name form -> personal link, remembered in localStorage so
a re-scan doesn't create a second judge.
- Control room: averages with on-request overrides, judge progress
matrix, per-judge breakdown, spotlight select and lock toggle.
- usePendingEdits shares the optimistic autosave logic between the admin
grid and the judge page.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The toggle lived in the right-hand column, which drops below the scoring
grid on windows narrower than 1280px, so it was off screen. It now sits
directly under the title at every width.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- setVisibility: visible / frozen / hidden. Freezing snapshots the scores
on the server with the same engine the scoreboard uses; freezing twice
keeps the original snapshot.
- Reveal mode: start, reveal next (last place first, one click per team),
finish (sets visible) and cancel. The host sees who is next; the
scoreboard plays a visual drum-roll for teams revealed while watching.
- H cycles visibility in the control room.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- /admin/events/[eventId]/live: game list with status, previous/next,
mark done/reopen, scoring grid, standings, and bonus/penalty with undo.
- Scores autosave per cell with optimistic UI; the validate_score trigger
and RLS do the checking so a save costs one DB round trip.
- BlockInput and NumberStepper are shared UI, ready for the judge page.
- buildLiveView shapes all data on the server from the scoring engine, so
the client components contain no score math.
- N / P switch games, ? shows shortcut help.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- buildScoreboardPayload is the single place deciding what the public may
see: visible, frozen (snapshot scores, live current game), hidden (no
scores, alphabetical) and reveal (unrevealed teams carry no score/order).
- GET /api/public/scoreboard/[token] validates the token, then loads the
event with the service client in one parallel round trip.
- notify() pings sb/admin/game channels over Realtime's REST endpoint with
no data; useRefreshChannel refetches immediately on a ping, throttles
bursts, and falls back to a 10s poll, focus and reconnect.
- /s/[token]: FLIP rank animation, count-up totals, reduced motion,
fullscreen, cursor hide and a connection indicator.
- Admin setup actions now notify, so renames and reorders show up live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- /login sends a Supabase magic link (admin emails only); /auth/confirm
completes sign-in; proxy.ts guards /admin and every server action
re-checks with requireAdmin(), since actions are plain POST endpoints.
- /admin lists, creates, duplicates and deletes events.
- /admin/events/[eventId] edits teams (colour, order) and games with
criteria, presets and notes. All input goes through zod schemas that
mirror the database constraints so errors read as plain sentences.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pure functions in src/lib/scoring implementing docs/SCORING.md: cell values
(override > judge average), weighted raw totals, raw and placement points
with tie averaging, and competition-ranked standings. Coverage thresholds
are enforced at 100% for the module. Also silences the hydration warning
caused by browser extensions modifying <html>.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Types are generated from the live database's API schema by
scripts/gen-types.mjs (npm run gen:types), in the same shape as
supabase gen types, so no Supabase CLI login is needed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tokens.ts validates link tokens (32 hex chars) before any DB query.
check:rls proves the anon key cannot read or insert on any table, and
fails loudly if the migration has not been applied.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Next.js 16 App Router with strict TypeScript, Tailwind 4 theme tokens from
docs/UI.md, Vitest, and the three Supabase clients. Env vars are validated
with zod in next.config.ts so dev and build fail fast with a readable error.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>