Skip to main content
This page exists so the handbook never lies to you. Documentation that describes systems which don’t exist is worse than no documentation: it sends maintainers chasing ghosts. Here is the exact line between reality and aspiration.

✅ Built and working

  • REST API: GET/POST /matches, PATCH /matches/:id/score, GET/POST /matches/:id/commentary
  • WebSocket server: connect, subscribe/unsubscribe to a match, broadcast match-created, live score updates, and commentary events, 30s heartbeat
  • Live scores: PATCH /matches/:id/scorescore_update broadcast to all clients; the frontend replaces the match in cache and the card re-renders live. See ADR-010.
  • Live-feed dedup: WS cache writes dedup by id, so a live event that’s also in the initial REST batch (or delivered twice) won’t duplicate; score updates replace the match by id (idempotent).
  • Demo mode: DEMO_MODE=true runs an in-process multi-sport simulator (real clubs/players, rule-correct scoring, match lifecycle) so the deployed app is always live for visitors. It writes real rows to the DB continuously; pruning keeps it bounded (≈last 50 commentary per match, last 8 finished matches, old rows cascade-deleted). See ADR-011.
  • Database: Neon Postgres via Drizzle ORM; matches and commentary tables with a FK cascade
  • Validation: every request body/query parsed with Zod
  • Security middleware: Arcjet (rate limiting, bot detection, shield) on HTTP and WS upgrade; Helmet headers; CORS
  • Match status: derived from startTime/endTime (scheduled → live → finished)
  • Tests: 95 Vitest tests on the backend (unit/integration/WebSocket) plus a Playwright end-to-end suite on the frontend (mocked REST + WS, accessibility scan), run against a production build. See Testing.
  • CI/CD (backend): three GitHub Actions workflows (lint+format, tests, docker build/push)
  • CI/CD (frontend): two GitHub Actions workflows (lint+format, typecheck+Playwright e2e) + Vercel deploy; main branch-protected to require the checks, so CI gates production. See DevOps.
  • Containerization: multi-stage backend Dockerfile (dev Neon Local / prod compose); a gated standalone Dockerfile for the frontend too
  • Deploy: backend on Render (Docker runtime); frontend on Vercel (live)
  • Frontend: Next.js App Router app: match grid, commentary timeline, dark mode, animations, responsive bottom sheet (with a live scoreboard), modals, skeletons. Plus: per-sport tags + semantic event-type colors, a score celebration (sport-ball burst on a score-up in the watched match), an onboarding tour (driver.js, run-once + replayable), and a dismissible demo banner (simulated-live + cold-start notice). Tooling: ESLint + Prettier + typecheck.
  • Backend observability: New Relic APM (errors, traces, DB/route timing) via newrelic, and PostHog (match_created, score_updated) via posthog-node. Both env-gated, both live in production. See ADR-013, ADR-014, Observability.
  • Frontend observability: New Relic Browser (RUM, Core Web Vitals, distributed tracing linked to the backend agent) and PostHog (autocapture + custom events), both env-gated, both confirmed live in the deployed Vercel bundle. Sentry was removed entirely (ADR-013). See Observability.

❌ Not built (and this handbook will not pretend otherwise)

These appear in the original handbook brief but do not exist in Sportz. They are documented here as deliberate non-goals or future work, not as fiction.

When these become real

The absent items aren’t permanent gaps; they’re correctly deferred until scale demands them. The Scalability reasoning:
  • Multiple API instances → in-process broadcast breaks (a client connected to instance A won’t get an event created on instance B). That’s when a queue/pub-sub (Redis) becomes necessary, not before.
  • User accounts / personalization → auth + authz become necessary.
  • Read-heavy hot paths → a cache layer becomes worthwhile, measured first.
If you are a future maintainer and you find one of the “Not built” items has since been added, update this table. The value of this page is entirely in it staying honest.