v1 Launch Checklist¶
Owner: Morgan (Solution Architect) Date: 2026-04-12 Purpose: Working checklist for the PO to ship v1. Walk through every section in order.
1. Feature Inventory — SCR Requirements vs. Implementation¶
| Req | Screen | Status | Notes |
|---|---|---|---|
| SCR-001 | Schedule Screen (signage) | DONE | /s/[slug] — court columns, ON_COURT/PLANNED/COMPLETED sections, meta-refresh 30s. Server Component, no client JS. |
| SCR-002 | Draw / Bracket View (signage) | DONE | /d/[id] — SE bracket grid with champion overlay, RR standings table + cross-table grid. Loser draws filtered (is_loser_draw suppressed in draw list query). Server Component, meta-refresh 30s. |
| SCR-003 | Contact Sheet | PARTIAL | /[tournamentId]/contacts — table with phone/email/draws, print button, Realtime subscription. Missing: name search/filter (AC-003), Copy Emails button (AC-004/AC-005). See Section 4. |
| SCR-004 | Tournament Setup | PARTIAL | /tournaments/new — create with name, slug, start/end dates. Missing: tournament edit screen, court management during setup (courts are managed inline on the schedule page instead), draw creation during setup (done via separate /draws/new page). No edit/delete for existing tournaments. See Section 4. |
| SCR-005 | Court Assignment / Match Scheduling | DONE | /[tournamentId]/schedule — @dnd-kit drag-and-drop, unassigned panel, court columns, inline court management, result entry dialog, optimistic updates, PointerSensor + TouchSensor. Realtime via useRealtimeInvalidation. |
Additional screens not in original SCR spec (shipped)¶
| Screen | Route | Notes |
|---|---|---|
| Login | /login |
Magic link auth via Supabase |
| Dashboard | /dashboard |
Tournament list with links to overview |
| Tournament Overview | /[tournamentId] |
Draw card grid with registration/match counts |
| Draw List | /[tournamentId]/draws |
Table with create button |
| Create Draw | /[tournamentId]/draws/new |
Name, type (SE/RR), format dropdown (F1-F8) |
| Draw Detail | /[tournamentId]/draws/[drawId] |
Registrations tab, SE bracket visualization, RR grid + standings, result entry dialog |
| Seeding & Generation | /[tournamentId]/draws/[drawId]/seed |
Drag-and-drop seeded/unseeded zones, generate bracket |
| Auth Callback | /auth/callback |
Handles magic link fragment |
| Dev Page | /dev |
Development-only utilities |
2. API Procedure Inventory¶
All routers mounted in packages/trpc/src/root.ts:
| Router | Procedures | Status |
|---|---|---|
health |
health check | DONE |
tournament |
list, byId, create | DONE (no update/delete) |
court |
list, create, update, delete, reorder | DONE |
draw |
list, byId, create, update, delete, generateBracket, clearBracket, standings | DONE |
player |
search, create, update, listForTournament | DONE |
registration |
add, remove, bulkAdd, updateSeeding | DONE |
match |
listByDraw, listByTournament, assignCourt, setOnCourt, submitResult, editResult, clearResult, reorderOnCourt | DONE |
signage |
schedule, draw, draws | DONE |
3. Infrastructure Checklist¶
Apply these in order. Do test first, then prod.
3.1 Database (both Supabase projects)¶
- [ ] Initial schema applied to
: run all 6 SQL files (see packages/db/drizzle/and the keystone Postgres runbook atgitlab.com/wagen-public/keystone-public/-/blob/main/docs/runbooks/04-platform-postgres.mdfor the apply procedure) - [ ] Verify test: 7 tables exist, RLS enabled, 8 DrawFormat rows, Realtime publication includes
draws,matches,registrations - [ ] Initial schema applied to
: same 6 files - [ ] Verify prod: same verification queries as test
The 6 files in order:
1. packages/db/drizzle/0000_initial_schema.sql
2. packages/db/src/policies/triggers.sql
3. packages/db/src/policies/constraints.sql
4. packages/db/src/policies/seed_draw_formats.sql
5. packages/db/src/policies/rls.sql
6. packages/db/src/policies/realtime.sql
3.2 Supabase Auth (both projects)¶
- [ ] Site URL configured in Supabase Auth settings:
- Test:
https://csd-test.wagen.io - Prod:
https://csd.wagen.io - [ ] Redirect URLs configured (Auth > URL Configuration > Redirect URLs):
- Test:
https://csd-test.wagen.io/auth/callback - Prod:
https://csd.wagen.io/auth/callback - [ ] SMTP configured on both projects (Supabase Auth > Email Templates > SMTP Settings). Without this, magic link emails go through Supabase's built-in SMTP which has a 4/hour rate limit.
- [ ] Email template reviewed — the magic link email should say "CourtsideDesk" not "Supabase"
3.3 Supabase Anti-Pause Cron¶
- [ ] Anti-pause concern is moot once CSD is on keystone — the per-tenant Postgres on
kst1.wagen.iodoes not auto-pause. Until cutover, the legacy VPS cron remains the source of truth on the legacy host.
3.4 CI/CD Pipeline¶
- [ ] All GitLab CI variables set (see
docs/gitlab-ci-variables-casey.md): VPS_HOST,VPS_USER,VPS_SSH_PRIVATE_KEY(file type),VPS_SSH_KNOWN_HOSTSSUPABASE_URL_TEST,SUPABASE_ANON_KEY_TEST,SUPABASE_SERVICE_ROLE_KEY_TESTSUPABASE_URL_PROD,SUPABASE_ANON_KEY_PROD,SUPABASE_SERVICE_ROLE_KEY_PROD- [ ] CI pipeline green on commit
7bcf3d1(latestmain) - [ ] deploy-test job has run successfully —
csd-test.wagen.ioserves the app
3.5 Deploy target¶
- [ ] CSD is onboarded on keystone (
kst1.wagen.io) per the keystone runbooks atgitlab.com/wagen-public/keystone-public/-/blob/main/docs/runbooks/10-app-onboarding.mdandgitlab.com/wagen-public/keystone-public/-/blob/main/docs/runbooks/12-platform-deploy-target.md. Bothcsd-testandcsdDocker compose stacks are running on kst1. - [ ] Caddy on kst1 routes
csd-test.wagen.ioandcsd.wagen.ioto the per-env containers (TLS via Let's Encrypt, automatic). - [ ] Per-env runtime env vars set via the keystone deploy contract —
DATABASE_URLpoints at the per-tenant Postgres role on kst1 (PgBouncer); Supabase service-role key is no longer used (gotrue replaces Supabase Auth — see ADR-0007 in keystone).
3.6 DNS¶
- [ ]
csd-test.wagen.ioA record points to Hetzner VPS IP - [ ]
csd.wagen.ioA record points to Hetzner VPS IP
3.7 Test User¶
- [ ] Organizer email invited in Supabase Auth (test project) — or sign up via magic link on
csd-test.wagen.io/login - [ ] Verify magic link arrives and completes auth flow
- [ ] Same for prod project when ready
4. Manual Testing — Golden Path¶
Walk through this on csd-test.wagen.io. Do it in one sitting. Every step must succeed before proceeding.
4.1 Authentication¶
- [ ] Navigate to
csd-test.wagen.io— redirected to login - [ ] Enter organizer email, click "Send Magic Link"
- [ ] Check email, click magic link
- [ ] Redirected to
/dashboard— session active
4.2 Tournament Creation¶
- [ ] Click "Create Tournament"
- [ ] Fill: name "Test Tournament 2026", slug "test-2026", dates today to +3 days
- [ ] Submit — lands on dashboard with new tournament listed
- [ ] Click tournament name — lands on overview page
4.3 Draw Management¶
- [ ] Navigate to Draws tab or click "Manage Draws"
- [ ] Create Draw 1: name "HS R4/R6", type Single Elimination, format "F2 — Standard, Tiebreak All Sets"
- [ ] Create Draw 2: name "HS R7/R9", type Round Robin, format "F3 — Match Tiebreak"
- [ ] Both draws appear in draw list with correct type/format labels
4.4 Player Registration¶
- [ ] Open Draw 1 detail page
- [ ] Open player search dialog
- [ ] Create 8 new players (first/last name minimum): Player A through Player H
- [ ] All 8 appear in registrations tab
- [ ] Open Draw 2 detail page, add 4 of the same players (search by name, verify search works)
4.5 Seeding & Bracket Generation¶
- [ ] On Draw 1: navigate to seeding page
- [ ] Drag Player A and Player B to seeded positions (seed 1 and seed 2)
- [ ] Click "Generate Bracket"
- [ ] Bracket appears: 8 players, 4 first-round matches, seed 1 at top, seed 2 at bottom
-
[ ] Byes auto-advanced if player count is not a power of 2
-
[ ] On Draw 2: navigate to seeding page
- [ ] Generate bracket (no seeding needed for RR)
- [ ] RR grid appears: 4 players, 6 matches (4 choose 2)
4.6 Match Results — Single Elimination¶
- [ ] On Draw 1 bracket: click a first-round match
- [ ] Result Entry Dialog opens
- [ ] Enter completed result: 6-4 6-3 — submit
- [ ] Winner advances to next round (player name appears in QF slot)
- [ ] Click another match, enter walkover (WO) — winner advances
- [ ] Click another match, enter retirement: 6-4 3-2 ret. — winner advances
- [ ] Verify bracket visually shows advancement chains correctly
4.7 Match Results — Round Robin¶
- [ ] On Draw 2 grid: click a cell
- [ ] Enter result: 6-4 3-6 [10-7] (match tiebreak format F3)
- [ ] Standings table updates: MW, ML, SW, SL, GW, GL columns reflect the result
- [ ] Enter 2 more results, verify standings recompute correctly
4.8 Court Assignment (SCR-005)¶
- [ ] Navigate to Schedule page
- [ ] Add 3 courts: "Court 1", "Court 2", "Court 3"
- [ ] Drag an unassigned match to Court 1 — match disappears from unassigned panel, appears in Court 1 column
- [ ] Drag a match from Court 1 to Court 2 — moves correctly
- [ ] Drag a match from Court 2 back to unassigned panel — returns
- [ ] Click "Mark On Court" on an assigned match — status changes to ON_COURT, visual highlight
- [ ] Enter a result via the match card — status changes to COMPLETED, moves to completed section
4.9 Contact Sheet (SCR-003)¶
- [ ] Navigate to
/[tournamentId]/contacts - [ ] All registered players appear in table sorted by last name
- [ ] Phone and email columns populated (or dash for missing)
- [ ] Draws column shows which draws each player is in
- [ ] Click Print — print dialog opens with clean layout (no nav, compact table)
4.10 Signage Screens¶
- [ ] Open
csd-test.wagen.io/s/test-2026in a new browser (no auth required) - [ ] Court schedule shows courts with assigned matches in correct columns
- [ ] ON_COURT matches highlighted, COMPLETED matches below with results
- [ ] Draw navigation bar visible — click a draw name
- [ ] SE draw: bracket rendered with results, winner highlighted, advancing lines
- [ ] RR draw: standings table + cross-table grid with scores in cells
4.11 Realtime Verification¶
- [ ] Open two browser tabs, both on the Court Assignment page
- [ ] In Tab 1: drag a match to a court
- [ ] In Tab 2: verify the match appears on that court within 2 seconds
- [ ] In Tab 1: enter a match result
-
[ ] In Tab 2: verify the result appears and match moves to completed
-
[ ] Open the Draw Detail page in one tab and the signage draw page (
/d/[id]) in another - [ ] Enter a result on the organizer tab
- [ ] Signage tab auto-refreshes within 30 seconds (meta-refresh, not WebSocket — by design for unattended displays)
5. Known Limitations and Gaps¶
5.1 SCR-003 — Contact Sheet (Partial)¶
- Missing: Name search/filter (AC-003). The contacts page renders a static table with no filter input. Requirement says "Real-time name search/filter narrows list." Not implemented.
- Missing: Copy Emails button (AC-004, AC-005). Requirement specifies a button that copies semicolon-delimited emails of visible/filtered players. Not implemented.
5.2 SCR-004 — Tournament Setup (Partial)¶
- Missing: Tournament edit. No
tournament.updateprocedure exists. Once created, tournament name/dates cannot be changed. Court and draw management exist but are on separate pages, not inline on the tournament setup screen. - Missing: Tournament delete. No way to remove a tournament.
5.3 is_loser_draw Rendering¶
- Loser draws are stored in the schema (
is_loser_drawboolean on draws table) but no frontend code referencesisLoserDrawfor display filtering. Thedraw.listquery filters them out server-side, which is the correct behavior per spec. However, there is no UI to create or flag a draw as a loser draw. This is acceptable for v1 since loser draws are "stored but never rendered."
5.4 Mobile Drag-and-Drop¶
- Riley (QA) flagged that @dnd-kit
TouchSensoris wired up but has not been tested on real mobile hardware. Court Assignment and Seeding pages use drag-and-drop as primary interactions. The Seeding page uses native HTML5 drag-and-drop (not @dnd-kit), which does not work on mobile at all. Must be tested on iPhone Safari and Android Chrome before declaring mobile-ready.
5.5 Excel Import (Stage 2 Ingestion)¶
- Not implemented. The requirements spec
PlayerList.xlsimport with SheetJS as a future stage. Thesourcefield on Player and Match entities is present in the schema, defaulting tomanual. No import UI or parser exists.
5.6 Per-User Tournament Scoping¶
- All authenticated users see all tournaments. There is no
owner_idor RBAC filtering. Any logged-in organizer can edit any tournament. This is acceptable for a single-organizer deployment but must be addressed before multi-tenant use.
5.7 Scoring Edge Cases¶
packages/scoringhas unit tests (bracket.test.ts,standings.test.ts,validate.test.ts,schemas.test.ts,formats.test.ts) but these have not been verified against real SwissTennis tournament data.- The tiebreak cascade (H2H recursive mini-table, Steps 1-5 from the requirements) is implemented in
computeStandingsbut the manual-draw-required (Losentscheid) case has only been tested synthetically. - Super-tiebreak counting as 1 set in SW/SL but NOT counting points in GW/GL — needs verification with a real F3/F8 result set.
5.8 Signage Realtime Strategy¶
- Signage screens (
/s/[slug],/d/[id]) use<meta http-equiv="refresh" content="30">(full page reload every 30 seconds) instead of Supabase Realtime WebSocket. This is a deliberate choice for unattended displays (no stale WebSocket connections). The trade-off is up to 30 seconds latency on signage updates.
5.9 Test Suite¶
- CI runs
pnpm testbut the test stage description says "Vitest scripts are stubs today (Riley's suite is a placeholder)." The scoring package has real tests. The trpc and web packages likely have stub or no test files. Confirm test coverage before relying on CI as a quality gate.
5.10 Result Entry — match.editResult and match.clearResult¶
- Both procedures exist in the backend. Verify the frontend exposes both actions (edit a completed result, clear a result) via the Result Entry Dialog. The Phase 3 commit mentions edit mode with pre-populated form, so this is likely wired up — confirm during manual testing.
6. Post-Launch Priorities¶
Ordered by value to the organizer during a live tournament.
P1 — Ship before first real tournament¶
- SCR-003 name filter + Copy Emails. The contact sheet is used constantly during a tournament to reach players. The filter and copy-emails are the highest-value missing features. Small scope — one text input + one clipboard button.
- Tournament edit/delete. Organizer will make typos in tournament name/dates. This is a simple CRUD gap.
- Mobile drag-and-drop testing + fix. If the Seeding page does not work on mobile, the organizer cannot seed players on a tablet at the venue. Replace HTML5 drag-and-drop with @dnd-kit on the Seeding page if needed.
P2 — Before second tournament¶
- Excel import (PlayerList.xls). Manual entry of 50+ players is painful. SheetJS parser for the SwissTennis export format eliminates the biggest data-entry bottleneck.
- Per-user tournament scoping. If a second organizer ever uses the app, they will see each other's tournaments.
P3 — Quality of life¶
- Real test suite. Wire up integration tests for the tRPC procedures (at minimum:
submitResult-> advancement,computeStandingswith real data,generateBracketwith byes). - Loser draw rendering. The schema supports it. Add a UI toggle on draw creation and render loser brackets on the signage view.
- Error monitoring. No Sentry or equivalent. Errors go to container stdout /
docker logs(keystone-managed Uptime Kuma is the platform's only synthetic monitor). Add structured error reporting before the app handles real tournament traffic. - Database backups. Supabase free tier has daily backups, but verify they are enabled and test a restore before production data exists.
7. Ship Sequence¶
Once manual testing (Section 4) passes on csd-test.wagen.io:
- [ ] Apply schema to
Supabase project (Section 3.1) - [ ] Configure Auth URLs for prod (Section 3.2)
- [ ] Verify CI pipeline is green
- [ ] Trigger
deploy-prodmanually in GitLab CI (it requires manual approval) - [ ] Verify
csd.wagen.ioserves the app - [ ] Log in with organizer email on prod
- [ ] Create the real tournament
- [ ] Announce to players: signage URL is
csd.wagen.io/s/<slug>