pnpm monorepo with three workspaces: - @sbt/shared: zod ScoreboardState + WebSocket protocol (single source of truth) - @sbt/server: Fastify REST + raw ws WebSocket + Drizzle/Postgres, run via tsx - @sbt/web: React + Vite + Tailwind installable PWA Real-time core: the WebSocket server holds authoritative per-board state in memory, broadcasts to all clients (editors + OBS overlays) instantly, and debounces Postgres saves (~750ms). One useScoreboardSync hook powers the editor, the no-login co-edit control page, and the read-only OBS overlay. Includes email+password auth (JWT cookie), scoreboard CRUD, logo upload, customizable scorebug (characters/stocks/score/subtitles/callout/side-swap/theme), Docker Compose + Caddy/nginx deploy configs, and docs/PLAN.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1.9 KiB
CLAUDE.md
Guidance for working in this repo. Read docs/PLAN.md for the full design.
What this is
Self-hosted real-time SSBU scoreboard PWA. pnpm monorepo:
packages/shared—@sbt/shared. zod schema + types forScoreboardStateand the WebSocket protocol. Single source of truth imported by both web and server — change state shape here and both sides follow. No build step (consumed as TS source).apps/server—@sbt/server. Fastify + rawws, Drizzle + Postgres. Runs viatsx(no compile step in dev or prod).apps/web—@sbt/web. React + Vite + Tailwind PWA.
Real-time architecture (don't break this)
apps/server/src/rooms.ts is the authority: in-memory state per board, broadcast to all
sockets on update, debounced Postgres save. apps/server/src/ws.ts resolves role + board
from the connect query (overlay=/control=/board=). The web side has exactly one sync
primitive — apps/web/src/hooks/useScoreboardSync.ts — reused by Editor, Control, Overlay.
When adding scorebug features: extend ScoreboardStateSchema in packages/shared/src/state.ts,
then render in apps/web/src/components/ScoreBug.tsx and add controls in PlayerPanel/
MatchControls. Sync, persistence, and overlay come for free.
Commands
pnpm install
pnpm dev # web :5173 + server :3000 (Vite proxies /api and /ws)
pnpm test # vitest (shared)
pnpm typecheck
docker compose up -d --build # production-style: Postgres + server
Server env: DATABASE_URL, JWT_SECRET, COOKIE_SECURE, UPLOAD_DIR, WEB_DIR, PORT
(see apps/server/src/env.ts).
Conventions
- Keep modules small and single-purpose; match the existing terse style.
- Validate all external input with zod (REST bodies and WS messages already do).
- Owner REST routes go through
requireAuth; public flows are token-only via WebSocket. - The overlay route must stay uncached (see
navigateFallbackDenylistinvite.config.ts).