Skip to content

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 at gitlab.com/wagen-public/keystone-public/-/blob/main/docs/runbooks/04-platform-postgres.md for 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.io does 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_HOSTS
  • SUPABASE_URL_TEST, SUPABASE_ANON_KEY_TEST, SUPABASE_SERVICE_ROLE_KEY_TEST
  • SUPABASE_URL_PROD, SUPABASE_ANON_KEY_PROD, SUPABASE_SERVICE_ROLE_KEY_PROD
  • [ ] CI pipeline green on commit 7bcf3d1 (latest main)
  • [ ] deploy-test job has run successfully — csd-test.wagen.io serves the app

3.5 Deploy target

  • [ ] CSD is onboarded on keystone (kst1.wagen.io) per the keystone runbooks at gitlab.com/wagen-public/keystone-public/-/blob/main/docs/runbooks/10-app-onboarding.md and gitlab.com/wagen-public/keystone-public/-/blob/main/docs/runbooks/12-platform-deploy-target.md. Both csd-test and csd Docker compose stacks are running on kst1.
  • [ ] Caddy on kst1 routes csd-test.wagen.io and csd.wagen.io to the per-env containers (TLS via Let's Encrypt, automatic).
  • [ ] Per-env runtime env vars set via the keystone deploy contract — DATABASE_URL points 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.io A record points to Hetzner VPS IP
  • [ ] csd.wagen.io A 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-2026 in 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.update procedure 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_draw boolean on draws table) but no frontend code references isLoserDraw for display filtering. The draw.list query 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 TouchSensor is 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.xls import with SheetJS as a future stage. The source field on Player and Match entities is present in the schema, defaulting to manual. No import UI or parser exists.

5.6 Per-User Tournament Scoping

  • All authenticated users see all tournaments. There is no owner_id or 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/scoring has 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 computeStandings but 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 test but 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

  1. 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.
  2. Tournament edit/delete. Organizer will make typos in tournament name/dates. This is a simple CRUD gap.
  3. 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

  1. Excel import (PlayerList.xls). Manual entry of 50+ players is painful. SheetJS parser for the SwissTennis export format eliminates the biggest data-entry bottleneck.
  2. Per-user tournament scoping. If a second organizer ever uses the app, they will see each other's tournaments.

P3 — Quality of life

  1. Real test suite. Wire up integration tests for the tRPC procedures (at minimum: submitResult -> advancement, computeStandings with real data, generateBracket with byes).
  2. Loser draw rendering. The schema supports it. Add a UI toggle on draw creation and render loser brackets on the signage view.
  3. 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.
  4. 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:

  1. [ ] Apply schema to Supabase project (Section 3.1)
  2. [ ] Configure Auth URLs for prod (Section 3.2)
  3. [ ] Verify CI pipeline is green
  4. [ ] Trigger deploy-prod manually in GitLab CI (it requires manual approval)
  5. [ ] Verify csd.wagen.io serves the app
  6. [ ] Log in with organizer email on prod
  7. [ ] Create the real tournament
  8. [ ] Announce to players: signage URL is csd.wagen.io/s/<slug>