Wave 0.0 — Core data & engine
6/6 shipped| Platform: core relational data model | Shipped |
| Setup: season management (create/edit + game length) | Shipped |
| Setup: team management (CRUD, colors, plan caps) | Shipped |
| Setup: fields/venues admin with GPS coordinates | Shipped |
| Stats: stat-math, standings & box-score engine | Shipped |
| Stats: derived game-stage model (live/awaiting-review) | Shipped |
Wave 0.1 — Offline scorebook & sync
7/7 shipped| Scorebook: toggle Stats vs Basic scoring view | Shipped |
| Scorebook: offline-first sync engine (idempotent at-bats) | Shipped |
| Scorebook: installable PWA + offline service worker | Shipped |
| Scorebook: tap-who-scored runs & RBI (premium) | Shipped |
| Scorebook: live multi-coach scoring (premium) | Shipped |
| Scorebook: advisory never-block compliance warnings | Shipped |
| Games: opponent / manual line-score tracking | Shipped |
Wave 0.2 — Auth, session & roles
7/7 shipped| Auth: sign-in email shows location & device | Shipped |
| Auth: passwordless OTP email login | Shipped |
| Auth: Turnstile bot check (fail-open, no lockout) | Shipped |
| Auth: signed-JWT session & cross-subdomain SSO | Shipped |
| Auth: additive role model & authorization | Shipped |
| People: coach-access request workflow | Shipped |
| Auth: cross-league data isolation (IDOR guards) | Shipped |
Wave 0.3 — Multi-tenancy
4/4 shipped| Platform: soft-delete with a 1-year recovery window | Shipped |
| Platform: one-free-league-per-email spam cap | Shipped |
| Platform: subdomain-per-league multi-tenancy & host resolution | Shipped |
| Setup: self-serve league creation & onboarding | Shipped |
Wave 0.4 — Design system
5/5 shipped| Design: semantic design-token system (light/dark) | Shipped |
| Design: hand-built UI primitive library | Shipped |
| Design: dark mode (no-FOUC) | Shipped |
| Design: editorial typography (Geist + tabular-nums) | Shipped |
| Design: per-team color theming | Shipped |
Wave 0.5 — Platform & ops
5/5 shipped| Platform: self-hosted deploy stack + migrations | Shipped |
| Platform: version-skew protection | Shipped |
| Platform: seed / demo data tooling | Shipped |
| Platform: read-only reporting layer (PowerBI) | Shipped |
| Governance: Vitest suite + Issue/Wave gate | Shipped |
Wave 0.6 — Public hub & CMS
6/6 shipped| Hub: public league pages + live scoreboard + read API | Shipped |
| Hub: versioned rules CMS | Shipped |
| Hub: news / announcements public feed | Shipped |
| Hub: game-time weather (forecast + history) | Shipped |
| Hub: per-game notes (auto-save) | Shipped |
| Platform: superadmin platform dashboard | Shipped |
Wave 1 — Solidify Crossroads
5/5 shipped| Design: role selector dismisses on outside-click | Shipped |
| Games: collapse past games on the schedule | Shipped |
| Platform: content moderation on names & slugs | Shipped |
| Games: statuses & labels (forfeit, cancelled, scrimmage) | Shipped |
| Import: backfill 2026 Summer scores & standings from league schedule sheet | Shipped |
Wave 2 — Accountability
2/2 shipped| Platform: per-action audit log of staff changes | Shipped |
| Comms: email send-log viewer (admin + commissioner) | Shipped |
Wave 3 — Premium epic
4/4 shipped| Scorebook: unlock full Stats scoring (premium) | Shipped |
| Comms: email announcements to your league (premium) | Shipped |
| Print: export/print a game lineup card (premium) | Shipped |
| Platform: premium entitlement & pricing model | Shipped |
Wave 4 — Apex / growth
6/6 shipped| Growth: modernize the apex marketing storefront | Shipped |
| Print: blank backup scoresheet for offline use | Shipped |
| Print: polish the lineup printout | Shipped |
| Growth: league finder by ZIP + broadcast openings | Shipped |
| Import: roster CSV import | Shipped |
| Comms: role-introduction / onboarding emails | Shipped |
Wave 5 — Person entity
3/3 shipped| People: unify coach/player into one person (additive roles) | Shipped |
| People: search across the people directory | Shipped |
| People: substitute & sub-pool management | Shipped |
Wave 6 — Alerts + cron
3/3 shipped| Comms: automated coach lineup-reminder emails (premium) | Shipped |
| Print: pre-game reminder to print lineup/backup | Shipped |
| Games: game review & per-side score reconciliation | Shipped |
Wave 7 — Waivers + fielding
4/4 shipped| Fielding: post-game no-show / did-not-play check | Shipped |
| Fielding: grid analytics — bench fairness & per-position stats | Shipped |
| Waivers: author, send & e-sign waivers (premium) | Shipped |
| Comms: per-kind email notification controls | Shipped |
Wave 8 — Crossroads polish
6/6 shipped| Print: fix nav chrome leaking into printouts | Shipped |
| Games: filter & search the games list | Shipped |
| Import: downloadable blank/template CSV | Shipped |
| Hub: preview the rules page while editing | Shipped |
| People: fix commissioner People view (collapsible teams) | Shipped |
| Hub: live roadmap of what we've built & what's coming | Shipped |
Wave 9 — Waiver arc
4/5 shipped| Waivers: harden the public sign endpoint (Turnstile/rate-limit/expiry) | Shipped |
| Waivers: substitute-player waiver template | Shipped |
| Waivers: host commissioner-uploaded PDF waivers (premium) | Planned |
| Waivers: tidy the commissioner waiver workspace (editor-first, tabbed) | Shipped |
| Waivers: players view & download their signed copies from their account | Shipped |
Wave 10 — Data & identity
3/4 shipped| Import: detect likely-duplicate players | Shipped |
| People: track player gender | Planned |
| Import: support a Gender column | Shipped |
| People: promote a canonical Person foundation | Shipped |
Wave 11 — Team-stats lockdown
2/2 shipped| Stats: control who sees player stats and how players are shown | Shipped |
| Growth: define the free-to-view vs. premium-depth line | Shipped |
Wave 12 — Perf & scale
2/3 shipped| Platform: parallelize independent data fetches (perf) | Shipped |
| Platform: ensure all data flows scale | Planned |
| Platform: Ensure all resources have UI links | Shipped |
Wave 13 — Governance & CI
5/7 shipped| Governance: Dependabot dependency-update PRs | Shipped |
| Governance: weekly backlog-hygiene report | In progress |
| Governance: secret-scanning push protection | Shipped |
| Governance: Project #4 board auto-sync workflows | Planned |
| Governance: keep the type-check green (fix OptOutRow drift in leaguePrefs tests) | Shipped |
| Platform: keep dependencies current & secure | Shipped |
| Governance: weekly backlog-hygiene report log | Shipped |
Wave 14 — Loose-ends quality sweep
20/20 shipped| Loose-ends: scope archivePerson coach soft-delete to the current league (cross-league IDOR) | Shipped |
| Loose-ends: single-source the game-stage label/badge; fix raw-stage string leaks | Shipped |
| Loose-ends: drive the email-log kind filter from the EmailKind source of truth | Shipped |
| Loose-ends: shared constants + validation for games.result_type and games.game_type | Shipped |
| Loose-ends: surface three silently-swallowed failures (pg_notify, getViewer, CSV parse) | Shipped |
| Loose-ends: remove the broken backfill-league-opt-outs script (queries dropped column) | Shipped |
| Loose-ends: accessibility pass — skip link, Button focus ring, scorebook aria-pressed | Shipped |
| Loose-ends: surface waiver paper-signature metadata; standardize EmptyState on commissioner game detail | Shipped |
| Import: merge two player records into one (audited) | Shipped |
| Import: offer to merge from the duplicate-detected warning | Shipped |
| Print: fit the lineup & backup cards on one page with team names and game time | Shipped |
| Print: Add option for both teams' W/L stat record (and score of each game) to display on print-out | Shipped |
| Lineup: Counter/Alert if position is not counted for in each inning | Shipped |
| Bug: Lineup print alert during game confirmations | Shipped |
| Commissioner: Show game time/game number when approving game results | Shipped |
| Commissioner: Show alert if game notes != NULL when reviewing a game | Shipped |
| Platform: Persistent filters when navigating back to a page | Shipped |
| Scorekeeping: Adjust play/RBI without clearing entire inning. | Shipped |
| Error: FC didn't log/ask who got out. | Shipped |
| Design: make the doubleheader game number prominent in the review header | Shipped |
Deferred
2/7 shipped| Stats: per-league configurable stat options | Planned |
| Stats: advanced analytics package (premium) | Shipped |
| Hub: premium historical-weather add-on | Planned |
| Platform: detect duplicate accounts bypassing premium | Planned |
| Auth: confirm & fix mobile sign-outs on real devices (PWA / in-app browser) | Planned |
| Auth: re-confirm sensitive actions with a one-time code (step-up) | Planned |
| Comms: make coach lineup-reminder emails toggleable from Email Settings | Shipped |
On hold
0/2 shipped| Waivers: legal review of the e-signature flow | Planned |
| Comms: SMS announcement delivery | Planned |
Wave 15 — League of Record
7/13 shipped| Growth: make oursoftball the "league of record" for adult rec | Planned |
| Comms: a weekly league pulse that pulls players back in all season | Planned |
| Games: "who's in this week" — game-day availability | Shipped |
| Growth: position the platform as the free, trustworthy home of your league | Shipped |
| Growth: turn every public hub into a no-sales growth loop | Shipped |
| Games: When more than one game per day/close timing in one day, label "Game #1" etc | Shipped |
| Games: Commissioner "fairness" stat track/display | Shipped |
| Hub: every shared league link shows that league's scoreboard | Planned |
| Growth: every hub recruits the next league | Shipped |
| Growth: leagues show up in search results | Planned |
| Growth: the openings list actually recruits | Shipped |
| Stats: the privacy card tells the truth | Planned |
| Hub: one-tap share on scores, brackets and standings | Planned |
Wave 16 — Mobile Friendly
3/3 shipped| Mobile: Forcing login attempt on each load | Shipped |
| Mobile: Extra attention on mobile rendering | Shipped |
| Mobile: Scoring a game, not always obvious how many R or RBI without clicking into each cell | Shipped |
Wave 17 — Team & Delivery
12/12 shipped| Platform: one-click gated production deploy from GitHub | Shipped |
| Governance: contributor onboarding — forced PRs, review routing, templates | Shipped |
| Platform: run the /ship autonomous flow from any local clone | Shipped |
| Platform: automated guardrail tests proving cross-league data stays isolated | Shipped |
| Platform: clear debug logging and stray TODOs for a clean contributor handoff | Shipped |
| Platform: split oversized modules (Scorebook, queries) into focused units | Shipped |
| Platform: split the oversized game-admin actions module into focused units | Shipped |
| Platform: apply pending dependency updates (Dependabot #165/#166) | Shipped |
| Platform: adopt major toolchain bumps — TypeScript 6, ESLint 10, @types/node 26 | Shipped |
| Platform: deploy ships the requested ref, not a stale local main | Shipped |
| Platform: /ship accepts an explicit issue list, vets list cohesion, and auto-cleans merged worktrees | Shipped |
| Platform: deploy self-heals a drifted checkout instead of aborting the sync step | Shipped |
Wave 18 — Coherence & Hardening
8/10 shipped| Auth: gate the /start landing in the proxy (or confirm it's public by design) | Shipped |
| Comms: make sure a league's email on/off settings can't be bypassed by a missing league id | Shipped |
| Platform: keep public game, player & team pages from leaking across leagues | Shipped |
| People: fix soft-delete gaps that lose coach access and signed waivers | Shipped |
| Scorebook: deleting an at-bat offline after it synced actually removes it | Shipped |
| Governance: fix docs that still describe retired Cloudflare Access and the wrong proxy matcher | Shipped |
| Print: lineup & backup cards print blank in dark mode | Shipped |
| Comms: Resend email from Email Log | Planned |
| Governance: /ship PRs carry their own docs & GH close-out — nothing left after deploy | Shipped |
| Hub: a league's logo on its share card | Planned |
Wave 19 — Playoffs
2/2 shipped| Games: playoff bracket & enhancements | Shipped |
| Playoffs: Visual edits to public page | Shipped |