An honest boundary between what Sportz has, what’s wired but inactive, and what doesn’t exist yet.
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.
WebSocket server: connect, subscribe/unsubscribe to a match, broadcast match-created, live score updates, and commentary events, 30s heartbeat
Live scores: PATCH /matches/:id/score → score_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.
Area
Reality
Why it’s absent / what it would take
Authentication
None
Sportz is a read-heavy public broadcast demo. No user accounts exist. Adding it = an auth provider (e.g. Clerk/Auth.js) + a users table + middleware.
Authorization
None
No roles, no protected resources. Follows from no auth.
Redis / caching
None
React Query caches on the client; the DB is fast enough at current scale. A cache layer is a scale-stage concern, not a today concern.
Message queue
None
Broadcasts are synchronous in-process function calls. A queue (e.g. Redis Streams, Kafka) only matters when you have multiple API instances (see below).
Microservices
Monolith
One Express process serves REST + WS. Correct for this scale; splitting would add ops cost for no benefit yet.
Zustand / Redux
Not used
State lives in React Query cache + local component state. A global store wasn’t needed for one page.
React Hook Form
Not used
The UI has no complex forms; it consumes data, it doesn’t capture much.
Server Components / streaming
Not used
The app is interactive and WebSocket-driven, so components are client components ('use client'). SSR/streaming would fight the real-time model.
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.
{"docsConfig":{"theme":"mint","$schema":"https://mintlify.com/docs.json","name":"Sportz Docs","colors":{"primary":"#EAB308","light":"#F4C542","dark":"#EAB308"},"logo":{"light":"https://mintcdn.com/sportzdocs/ZqapXRecIQ7JtK1M/logo/light.svg?fit=max\u0026auto=format\u0026n=ZqapXRecIQ7JtK1M\u0026q=85\u0026s=654dae11c1c4160e24667a72a6fa2fe7","dark":"https://mintcdn.com/sportzdocs/ZqapXRecIQ7JtK1M/logo/dark.svg?fit=max\u0026auto=format\u0026n=ZqapXRecIQ7JtK1M\u0026q=85\u0026s=ddc4c9ade6959cf37bcb93e2c3e1f320"},"favicon":"https://mintcdn.com/sportzdocs/ZqapXRecIQ7JtK1M/favicon.svg?fit=max\u0026auto=format\u0026n=ZqapXRecIQ7JtK1M\u0026q=85\u0026s=88b93ba67882ed2c861fa42d6666d149","navbar":{"primary":{"type":"github","href":"https://github.com/Jheff4/sportz-websockets"}},"footer":{"socials":{"github":"https://github.com/Jheff4"}},"contextual":{"options":["copy","view","chatgpt","claude"],"display":"header"},"styling":{"latex":false},"navigation":{"global":{"anchors":[{"anchor":"Live Demo","icon":"rocket","href":"https://sportz-ui.vercel.app"},{"anchor":"Backend Repo","icon":"server","href":"https://github.com/Jheff4/sportz-websockets"},{"anchor":"Frontend Repo","icon":"browser","href":"https://github.com/Jheff4/sportz-ui"}]},"pages":[]}},"deployment":{"deploymentMetadataValue":{"subdomain":"sportzdocs","hasAudioNarration":false,"actualSubdomain":"sportzdocs","customDomains":[],"gitSource":{"type":"github","owner":"jheff4","repo":"sportz-docs","deployBranch":"main","contentDirectory":"","isPrivate":false,"mountPath":"","label":""},"chroma":{"latestBuiltDeploymentHistoryId":"6a91a79bb34810eb4c786144","collectionId":"1d2ebbb9-3581-4408-b4ed-1155a1c227d2","liveDeploymentHistoryId":"6a91a79bb34810eb4c786144","schemaVersion":"v3"},"entitlements":{"AI_CHAT":{"status":"DISABLED"}},"buildId":"6a91a79bb34810eb4c786144","clientVersion":"0.0.4039","searchFilterCounts":[{"language":"default","tag":"Handbook","count":14},{"language":"default","tag":"Learn \u0026 Interview","count":5},{"language":"default","tag":"Architecture","count":4},{"language":"default","tag":"Operations","count":4},{"language":"default","tag":"Overview","count":3},{"language":"default","tag":"Learning","count":3},{"language":"default","tag":"Engineering Stories","count":2},{"language":"default","tag":"Interview Prep","count":2},{"language":"default","tag":"Reference","count":1}]},"assistantConfig":{"enableAskAiButton":false,"enableStarterQuestions":false,"enableContextualStarterQuestions":false,"enableAskAiSearchBar":true},"basePath":"","hasChatPermissions":false,"hasWebMcpPermissions":false,"toggles":[{"name":"kill-isr","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-mintlify-slack","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-integrations-settings","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-pr-grouping","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"custom-prefix","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"analytics-v7","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"user-flows-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"no-publish-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-onboarding","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"llms-txt-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving-live","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"stripTemperatureMiddleware","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"support-platform-pylon-zendesk","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"instant-rollback","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications-server","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-fable","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fixed-ai-action-pricing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automation-resolve-merge-conflict","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"scoped-publishing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"mintql","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"page-level-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-version-history-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-page-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"full-page-search","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workspace-projects","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"assistant-v5","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"byok-sep-25","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fork-pr-previews","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"revalidation-jwt","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"vite-origin","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-updated-9-15","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-sso-scim","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"hobby-abuse-scan","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"dashboard-inbox","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-signup","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"turbopuffer-dual-write","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-enabled","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"approve-review-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false}],"googleFontsInlined":false},"customCss":[{"filePath":"style.css","content":"#navbar .flex-1.border-b,#navbar-transition{border-bottom:none!important}#navbar div.h-16{position:relative}#navbar div.h-16::after{content:\"\";position:absolute;bottom:0;left:50%;transform:translateX(-50%);width:100vw;border-bottom:1px solid rgb(107 114 128/.12);pointer-events:none}.dark #navbar div.h-16::after{border-bottom-color:rgb(209 213 219/.08)}#navbar{overflow-x:clip}"}],"page":{"pagePath":"project-status","slug":"/project-status","pageMetadata":{"title":"Project Status \u0026 Scope","description":"An honest boundary between what Sportz has, what's wired but inactive, and what doesn't exist yet.","href":"/project-status"},"description":{"compiledSource":"\"use strict\";\nconst {jsx: _jsx} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = {\n p: \"p\",\n ..._provideComponents(),\n ...props.components\n };\n return _jsx(_components.p, {\n children: \"An honest boundary between what Sportz has, what’s wired but inactive, and what doesn’t exist yet.\"\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = {\n ..._provideComponents(),\n ...props.components\n };\n return MDXLayout ? _jsx(MDXLayout, {\n ...props,\n children: _jsx(_createMdxContent, {\n ...props\n })\n }) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"pageType":"default","mdx":{"mdxSource":{"compiledSource":"\"use strict\";const{Fragment:_Fragment,jsx:_jsx,jsxs:_jsxs}=arguments[0];const{useMDXComponents:_provideComponents}=arguments[0];function _createMdxContent(props){const _components={a:`a`,code:`code`,em:`em`,li:`li`,p:`p`,strong:`strong`,tbody:`tbody`,td:`td`,th:`th`,thead:`thead`,tr:`tr`,ul:`ul`,..._provideComponents(),...props.components},{Heading,Table,Warning}=_components;if(!Heading)_missingMdxReference(`Heading`,true);if(!Table)_missingMdxReference(`Table`,true);if(!Warning)_missingMdxReference(`Warning`,true);return _jsxs(_Fragment,{children:[_jsx(_components.p,{children:`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.`}),`\n`,_jsx(Heading,{level:`2`,id:`-built-and-working`,children:`✅ Built and working`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`REST API`}),`: `,_jsx(_components.code,{children:`GET/POST /matches`}),`, `,_jsx(_components.code,{children:`PATCH /matches/:id/score`}),`, `,_jsx(_components.code,{children:`GET/POST /matches/:id/commentary`})]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`WebSocket server`}),`: connect, subscribe/unsubscribe to a match, broadcast match-created, `,_jsx(_components.strong,{children:`live score updates`}),`, and commentary events, 30s heartbeat`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Live scores`}),`: `,_jsx(_components.code,{children:`PATCH /matches/:id/score`}),` → `,_jsx(_components.code,{children:`score_update`}),` broadcast to all clients; the frontend replaces the match in cache and the card re-renders live. See `,_jsx(_components.a,{href:`/decisions`,children:`ADR-010`}),`.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`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).`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Demo mode`}),`: `,_jsx(_components.code,{children:`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; `,_jsx(_components.strong,{children:`pruning`}),` keeps it bounded (≈last 50 commentary per match, last 8 finished matches, old rows cascade-deleted). See `,_jsx(_components.a,{href:`/decisions`,children:`ADR-011`}),`.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Database`}),`: Neon Postgres via Drizzle ORM; `,_jsx(_components.code,{children:`matches`}),` and `,_jsx(_components.code,{children:`commentary`}),` tables with a FK cascade`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Validation`}),`: every request body/query parsed with Zod`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Security middleware`}),`: Arcjet (rate limiting, bot detection, shield) on HTTP and WS upgrade; Helmet headers; CORS`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Match status`}),`: derived from `,_jsx(_components.code,{children:`startTime`}),`/`,_jsx(_components.code,{children:`endTime`}),` (scheduled → live → finished)`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Tests`}),`: 95 Vitest tests on the backend (unit/integration/WebSocket) `,_jsx(_components.strong,{children:`plus`}),` a Playwright end-to-end suite on the frontend (mocked REST + WS, accessibility scan), run against a production build. See `,_jsx(_components.a,{href:`/operations/testing`,children:`Testing`}),`.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`CI/CD (backend)`}),`: three GitHub Actions workflows (lint+format, tests, docker build/push)`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`CI/CD (frontend)`}),`: two GitHub Actions workflows (lint+format, typecheck+Playwright e2e) + Vercel deploy; `,_jsx(_components.code,{children:`main`}),` branch-protected to require the checks, so CI gates production. See `,_jsx(_components.a,{href:`/operations/devops`,children:`DevOps`}),`.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Containerization`}),`: multi-stage backend Dockerfile (dev Neon Local / prod compose); a gated `,_jsx(_components.code,{children:`standalone`}),` Dockerfile for the frontend too`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Deploy`}),`: backend on Render (Docker runtime); frontend on `,_jsx(_components.strong,{children:`Vercel`}),` (live)`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`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 `,_jsx(_components.strong,{children:`score celebration`}),` (sport-ball burst on a score-up in the watched match), an `,_jsx(_components.strong,{children:`onboarding tour`}),` (`,_jsx(_components.a,{href:`/decisions`,children:`driver.js`}),`, run-once + replayable), and a dismissible `,_jsx(_components.strong,{children:`demo banner`}),` (simulated-live + cold-start notice). Tooling: ESLint + Prettier + typecheck.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Backend observability`}),`: New Relic APM (errors, traces, DB/route timing) via `,_jsx(_components.code,{children:`newrelic`}),`, and PostHog (`,_jsx(_components.code,{children:`match_created`}),`, `,_jsx(_components.code,{children:`score_updated`}),`) via `,_jsx(_components.code,{children:`posthog-node`}),`. Both env-gated, both live in production. See `,_jsx(_components.a,{href:`/decisions`,children:`ADR-013`}),`, `,_jsx(_components.a,{href:`/decisions`,children:`ADR-014`}),`, `,_jsx(_components.a,{href:`/operations/observability`,children:`Observability`}),`.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`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 (`,_jsx(_components.a,{href:`/decisions`,children:`ADR-013`}),`). See `,_jsx(_components.a,{href:`/operations/observability`,children:`Observability`}),`.`]}),`\n`]}),`\n`,_jsx(Heading,{level:`2`,id:`-not-built-and-this-handbook-will-not-pretend-otherwise`,children:`❌ Not built (and this handbook will not pretend otherwise)`}),`\n`,_jsxs(_components.p,{children:[`These appear in the original handbook brief but `,_jsx(_components.strong,{children:`do not exist in Sportz`}),`. They are documented here as deliberate non-goals or future work, not as fiction.`]}),`\n`,_jsxs(Table,{children:[_jsx(_components.thead,{children:_jsxs(_components.tr,{children:[_jsx(_components.th,{children:`Area`}),_jsx(_components.th,{children:`Reality`}),_jsx(_components.th,{children:`Why it’s absent / what it would take`})]})}),_jsxs(_components.tbody,{children:[_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.strong,{children:`Authentication`})}),_jsx(_components.td,{children:`None`}),_jsxs(_components.td,{children:[`Sportz is a read-heavy public broadcast demo. No user accounts exist. Adding it = an auth provider (e.g. Clerk/Auth.js) + a `,_jsx(_components.code,{children:`users`}),` table + middleware.`]})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.strong,{children:`Authorization`})}),_jsx(_components.td,{children:`None`}),_jsx(_components.td,{children:`No roles, no protected resources. Follows from no auth.`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.strong,{children:`Redis / caching`})}),_jsx(_components.td,{children:`None`}),_jsxs(_components.td,{children:[`React Query caches on the client; the DB is fast enough at current scale. A cache layer is a `,_jsx(_components.a,{href:`/project-status#when-these-become-real`,children:`scale-stage`}),` concern, not a today concern.`]})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.strong,{children:`Message queue`})}),_jsx(_components.td,{children:`None`}),_jsx(_components.td,{children:`Broadcasts are synchronous in-process function calls. A queue (e.g. Redis Streams, Kafka) only matters when you have multiple API instances (see below).`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.strong,{children:`Microservices`})}),_jsx(_components.td,{children:`Monolith`}),_jsx(_components.td,{children:`One Express process serves REST + WS. Correct for this scale; splitting would add ops cost for no benefit yet.`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.strong,{children:`Zustand / Redux`})}),_jsx(_components.td,{children:`Not used`}),_jsx(_components.td,{children:`State lives in React Query cache + local component state. A global store wasn’t needed for one page.`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.strong,{children:`React Hook Form`})}),_jsx(_components.td,{children:`Not used`}),_jsx(_components.td,{children:`The UI has no complex forms; it consumes data, it doesn’t capture much.`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.strong,{children:`Server Components / streaming`})}),_jsx(_components.td,{children:`Not used`}),_jsxs(_components.td,{children:[`The app is interactive and WebSocket-driven, so components are client components (`,_jsx(_components.code,{children:`'use client'`}),`). SSR/streaming would fight the real-time model.`]})]})]})]}),`\n`,_jsx(Heading,{level:`2`,id:`when-these-become-real`,children:`When these become real`}),`\n`,_jsxs(_components.p,{children:[`The absent items aren’t permanent gaps; they’re correctly deferred until scale demands them. The `,_jsx(_components.a,{href:`/architecture/system`,children:`Scalability`}),` reasoning:`]}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Multiple API instances`}),` → in-process broadcast breaks (a client connected to instance A won’t get an event created on instance B). `,_jsx(_components.em,{children:`That’s`}),` when a queue/pub-sub (Redis) becomes necessary, not before.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`User accounts / personalization`}),` → auth + authz become necessary.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Read-heavy hot paths`}),` → a cache layer becomes worthwhile, measured first.`]}),`\n`]}),`\n`,_jsx(Warning,{children:_jsx(_components.p,{children:`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.`})})]})}function MDXContent(props={}){const{wrapper:MDXLayout}={..._provideComponents(),...props.components};return MDXLayout?_jsx(MDXLayout,{...props,children:_jsx(_createMdxContent,{...props})}):_createMdxContent(props)}return{default:MDXContent};function _missingMdxReference(id,component){throw new Error(`Expected `+(component?`component`:`object`)+\" `\"+id+\"` to be defined: you likely forgot to import, pass, or provide it.\")}","frontmatter":{},"scope":{"config":{},"pageMetadata":{"title":"Project Status \u0026 Scope","description":"An honest boundary between what Sportz has, what's wired but inactive, and what doesn't exist yet.","href":"/project-status"}}},"mdxSourceWithNoJs":null},"mdxExtracts":{"tableOfContents":[{"title":"✅ Built and working","slug":"-built-and-working","depth":2,"children":[]},{"title":"❌ Not built (and this handbook will not pretend otherwise)","slug":"-not-built-and-this-handbook-will-not-pretend-otherwise","depth":2,"children":[]},{"title":"When these become real","slug":"when-these-become-real","depth":2,"children":[]}],"codeExamples":{}},"jsLinks":[],"subdomain":"sportzdocs","siteName":"Sportz Docs","title":"Project Status \u0026 Scope - Sportz Docs","navMaps":{"firstHrefInVersion":{},"firstHrefInLanguage":{}},"lastModified":"2026-08-28T14:51:30.765Z","originalFileLocation":"project-status.mdx"},"headHtml":"\u003cscript data-mint-head>(function(){var d=document.documentElement,p=null;if(!p){try{p=localStorage.getItem(\"isDarkMode\")}catch(e){}if(p==='true')p='dark';else if(p==='false')p='light';if(p!=='light'\u0026\u0026p!=='dark'\u0026\u0026p!=='system')p=\"system\"}var r=p==='system'?(matchMedia(\"(prefers-color-scheme: dark)\").matches?'dark':'light'):p;d.classList.remove(r==='dark'?'light':'dark');d.classList.add(r);d.setAttribute(\"data-theme-preference\",p)})();\u003c/script>\u003cmeta data-mint-head name=\"description\" content=\"An honest boundary between what Sportz has, what\u0026#x27;s wired but inactive, and what doesn\u0026#x27;t exist yet.\">\u003cmeta data-mint-head name=\"application-name\" content=\"Sportz Docs\">\u003cmeta data-mint-head name=\"generator\" content=\"Mintlify\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/sportzdocs/PXW7TdvbuE1uPR7j/_generated/favicon/android-chrome-192x192.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"apple-touch-icon\" type=\"image/png\" sizes=\"180x180\" href=\"/mintlify-assets/_mintlify/favicons/sportzdocs/PXW7TdvbuE1uPR7j/_generated/favicon/apple-touch-icon.png\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/sportzdocs/PXW7TdvbuE1uPR7j/_generated/favicon/favicon-16x16.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/sportzdocs/PXW7TdvbuE1uPR7j/_generated/favicon/favicon-32x32.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/sportzdocs/PXW7TdvbuE1uPR7j/_generated/favicon/favicon.ico\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/sportzdocs/PXW7TdvbuE1uPR7j/_generated/favicon-dark/android-chrome-192x192.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/sportzdocs/PXW7TdvbuE1uPR7j/_generated/favicon-dark/favicon-16x16.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/sportzdocs/PXW7TdvbuE1uPR7j/_generated/favicon-dark/favicon-32x32.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/sportzdocs/PXW7TdvbuE1uPR7j/_generated/favicon-dark/favicon.ico\" media=\"(prefers-color-scheme: dark)\">\u003cmeta data-mint-head property=\"og:title\" content=\"Project Status \u0026amp; Scope - Sportz Docs\">\u003cmeta data-mint-head property=\"og:description\" content=\"An honest boundary between what Sportz has, what\u0026#x27;s wired but inactive, and what doesn\u0026#x27;t exist yet.\">\u003cmeta data-mint-head property=\"og:site_name\" content=\"Sportz Docs\">\u003cmeta data-mint-head property=\"og:url\" content=\"https://sportzdocs.mintlify.app/project-status\">\u003cmeta data-mint-head property=\"og:type\" content=\"website\">\u003cmeta data-mint-head property=\"og:image\" content=\"https://sportzdocs.mintlify.app/_mintlify/api/og/v2?division=Overview\u0026amp;title=Project+Status+%26+Scope\u0026amp;description=An+honest+boundary+between+what+Sportz+has%2C+what%27s+wired+but+inactive%2C+and+what+doesn%27t+exist+yet.\u0026amp;theme=613b975bd84dd49fe79bfd9d\u0026amp;signature=a22b64d9fedfe853acab8842a54f4c7f6c159bdad6b0e7363ca3de3cefe41343\">\u003cmeta data-mint-head property=\"og:image:width\" content=\"1200\">\u003cmeta data-mint-head property=\"og:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"twitter:card\" content=\"summary_large_image\">\u003cmeta data-mint-head name=\"twitter:title\" content=\"Project Status \u0026amp; Scope - Sportz Docs\">\u003cmeta data-mint-head name=\"twitter:description\" content=\"An honest boundary between what Sportz has, what\u0026#x27;s wired but inactive, and what doesn\u0026#x27;t exist yet.\">\u003cmeta data-mint-head name=\"twitter:image\" content=\"https://sportzdocs.mintlify.app/_mintlify/api/og/v2?division=Overview\u0026amp;title=Project+Status+%26+Scope\u0026amp;description=An+honest+boundary+between+what+Sportz+has%2C+what%27s+wired+but+inactive%2C+and+what+doesn%27t+exist+yet.\u0026amp;theme=613b975bd84dd49fe79bfd9d\u0026amp;signature=a22b64d9fedfe853acab8842a54f4c7f6c159bdad6b0e7363ca3de3cefe41343\">\u003cmeta data-mint-head name=\"twitter:image:width\" content=\"1200\">\u003cmeta data-mint-head name=\"twitter:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"msapplication-config\" content=\"/mintlify-assets/_mintlify/favicons/sportzdocs/PXW7TdvbuE1uPR7j/_generated/favicon/browserconfig.xml\">\u003cmeta data-mint-head name=\"apple-mobile-web-app-title\" content=\"Sportz Docs\">\u003cmeta data-mint-head name=\"msapplication-TileColor\" content=\"#EAB308\">\u003clink data-mint-head rel=\"canonical\" href=\"https://sportzdocs.mintlify.app/project-status\">\u003clink data-mint-head rel=\"alternate\" type=\"application/xml\" href=\"/sitemap.xml\">\u003clink data-mint-head rel=\"alternate\" type=\"text/markdown\" href=\"/project-status.md\">\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@graph\":[{\"@type\":\"Organization\",\"@id\":\"https://sportzdocs.mintlify.app/#organization\",\"name\":\"Sportz Docs\",\"url\":\"https://sportzdocs.mintlify.app\",\"logo\":{\"@type\":\"ImageObject\",\"url\":\"https://mintcdn.com/sportzdocs/ZqapXRecIQ7JtK1M/logo/light.svg?fit=max\u0026auto=format\u0026n=ZqapXRecIQ7JtK1M\u0026q=85\u0026s=654dae11c1c4160e24667a72a6fa2fe7\"}},{\"@type\":\"WebSite\",\"@id\":\"https://sportzdocs.mintlify.app#website\",\"name\":\"Sportz Docs\",\"url\":\"https://sportzdocs.mintlify.app\",\"publisher\":{\"@id\":\"https://sportzdocs.mintlify.app/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https://sportzdocs.mintlify.app/project-status#webpage\",\"url\":\"https://sportzdocs.mintlify.app/project-status\",\"name\":\"Project Status \u0026 Scope\",\"description\":\"An honest boundary between what Sportz has, what's wired but inactive, and what doesn't exist yet.\",\"dateModified\":\"2026-08-28T14:51:30.765Z\",\"isPartOf\":{\"@id\":\"https://sportzdocs.mintlify.app#website\"},\"breadcrumb\":{\"@id\":\"https://sportzdocs.mintlify.app/project-status#breadcrumb\"}},{\"@type\":\"BreadcrumbList\",\"@id\":\"https://sportzdocs.mintlify.app/project-status#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Overview\",\"item\":\"https://sportzdocs.mintlify.app/index\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Project Status \u0026 Scope\",\"item\":\"https://sportzdocs.mintlify.app/project-status\"}]},{\"@type\":[\"Article\",\"TechArticle\"],\"@id\":\"https://sportzdocs.mintlify.app/project-status#article\",\"headline\":\"Project Status \u0026 Scope\",\"name\":\"Project Status \u0026 Scope\",\"description\":\"An honest boundary between what Sportz has, what's wired but inactive, and what doesn't exist yet.\",\"url\":\"https://sportzdocs.mintlify.app/project-status\",\"mainEntityOfPage\":{\"@id\":\"https://sportzdocs.mintlify.app/project-status#webpage\"},\"dateModified\":\"2026-08-28T14:51:30.765Z\",\"publisher\":{\"@id\":\"https://sportzdocs.mintlify.app/#organization\"},\"isPartOf\":{\"@id\":\"https://sportzdocs.mintlify.app#website\"}}]}\u003c/script>\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@type\":\"WebSite\",\"name\":\"Sportz Docs\",\"creator\":{\"@type\":\"Organization\",\"name\":\"Mintlify\",\"url\":\"https://mintlify.com\"}}\u003c/script>","siteName":"Sportz Docs","nav":{"global":{"anchors":[{"anchor":"Live Demo","icon":"rocket","href":"https://sportz-ui.vercel.app"},{"anchor":"Backend Repo","icon":"server","href":"https://github.com/Jheff4/sportz-websockets"},{"anchor":"Frontend Repo","icon":"browser","href":"https://github.com/Jheff4/sportz-ui"}]},"tabs":[{"tab":"Handbook","groups":[{"group":"Overview","pages":[{"title":"Sportz Engineering Handbook","description":"Architecture, decisions, and operations for the Sportz real-time match platform.","href":""},{"title":"Project Status \u0026 Scope","href":"/project-status"},{"title":"User Stories","description":"Real people using Sportz, what they experience moment to moment, and why it behaves that way because of what we built.","href":"/stories"}]},{"group":"Architecture","pages":[{"title":"System Architecture (C4)","href":"/architecture/system"},{"title":"Frontend Architecture","href":"/architecture/frontend"},{"title":"Backend Architecture","href":"/architecture/backend"},{"title":"Real-Time System Deep Dive","href":"/architecture/realtime"}]},{"group":"Engineering Stories","pages":[{"title":"Architectural Decision Records","href":"/decisions"},{"title":"Engineering Log: Issues \u0026 Post-Mortems","href":"/issues"}]},{"group":"Operations","pages":[{"title":"Testing Strategy","href":"/operations/testing"},{"title":"DevOps \u0026 CI/CD","href":"/operations/devops"},{"title":"Observability","href":"/operations/observability"},{"title":"Security","href":"/operations/security"}]},{"group":"Reference","pages":[{"title":"API Reference","href":"/reference/api"}]}]},{"tab":"Learn \u0026 Interview","pages":[{"title":"Learn \u0026 Interview","href":"/learning/curriculum"}]}]},"basePath":"","title":"Project Status \u0026 Scope - Sportz Docs","buildId":"dpl_77Hw7ZuAABM8E3WMbYAf2ohQCr3v","deploymentId":"dpl_77Hw7ZuAABM8E3WMbYAf2ohQCr3v","dynamics":[],"chunks":[]}