scoreboardtools
Go to file
Ashraf Shafiq 9169bea79f Scaffold real-time SSBU scoreboard PWA
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>
2026-06-27 00:09:27 -04:00
apps Scaffold real-time SSBU scoreboard PWA 2026-06-27 00:09:27 -04:00
deploy Scaffold real-time SSBU scoreboard PWA 2026-06-27 00:09:27 -04:00
docs Scaffold real-time SSBU scoreboard PWA 2026-06-27 00:09:27 -04:00
packages/shared Scaffold real-time SSBU scoreboard PWA 2026-06-27 00:09:27 -04:00
.dockerignore Scaffold real-time SSBU scoreboard PWA 2026-06-27 00:09:27 -04:00
.gitignore Scaffold real-time SSBU scoreboard PWA 2026-06-27 00:09:27 -04:00
CLAUDE.md Scaffold real-time SSBU scoreboard PWA 2026-06-27 00:09:27 -04:00
docker-compose.yml Scaffold real-time SSBU scoreboard PWA 2026-06-27 00:09:27 -04:00
Dockerfile Scaffold real-time SSBU scoreboard PWA 2026-06-27 00:09:27 -04:00
package.json Scaffold real-time SSBU scoreboard PWA 2026-06-27 00:09:27 -04:00
pnpm-lock.yaml Scaffold real-time SSBU scoreboard PWA 2026-06-27 00:09:27 -04:00
pnpm-workspace.yaml Scaffold real-time SSBU scoreboard PWA 2026-06-27 00:09:27 -04:00
README.md Scaffold real-time SSBU scoreboard PWA 2026-06-27 00:09:27 -04:00
tsconfig.base.json Scaffold real-time SSBU scoreboard PWA 2026-06-27 00:09:27 -04:00

Scoreboard Tools

A self-hosted, real-time Super Smash Bros Ultimate tournament scoreboard PWA. Log in, build scoreboards, edit a customizable scorebug, open it as a live OBS overlay, and hand co-scorekeeping to someone else via a no-login link.

The full design rationale is in docs/PLAN.md.

Stack

Layer Choice
Web React + Vite + TypeScript + Tailwind, installable PWA (vite-plugin-pwa)
Server Fastify (REST) + raw ws WebSocket, run via tsx
Persistence Postgres via Drizzle ORM (postgres.js driver)
Shared @sbt/shared — zod schema + types for state and the WS protocol
Deploy Docker Compose (db + server) behind your nginx/Caddy TLS reverse proxy

Monorepo via pnpm workspaces: apps/web, apps/server, packages/shared.

The real-time model (the important bit)

The WebSocket server holds each scoreboard's state in memory as the single source of truth. Editors push full-state update messages; the server applies them, broadcasts to every client in that board's room (editors + OBS overlays) for instant updates, and debounces a Postgres save (~750ms) so we never write per-keystroke.

One hook — apps/web/src/hooks/useScoreboardSync.ts — powers all three consumers; only the connect credential and role differ:

Consumer Connects with Role
Owner editor ?board=<id> (cookie) editor
Co-scorekeeper ?control=<token> editor
OBS overlay ?overlay=<token> viewer (read-only)

Local development

Requires Node 20+, pnpm, and a Postgres on localhost:5432 (or use the compose db).

pnpm install

# point the server at a database (defaults to postgres://postgres:postgres@localhost:5432/scoreboardtools)
export DATABASE_URL=postgres://postgres:postgres@localhost:5432/scoreboardtools
export JWT_SECRET=dev-secret

pnpm dev          # runs web (:5173) and server (:3000) together

Open http://localhost:5173 — sign up, create a scoreboard, open it, and copy the overlay link into a second tab to watch real-time sync. Vite proxies /api and /ws to :3000.

pnpm test         # shared schema/protocol unit tests
pnpm typecheck    # type-check all workspaces

Production (self-hosted, Docker)

cp deploy/.env.example .env      # set JWT_SECRET (openssl rand -hex 32) and a DB password
docker compose up -d --build     # brings up Postgres + the server on 127.0.0.1:3000

Then put your TLS reverse proxy in front (TLS is required — PWA install and OBS browser sources need HTTPS, and the WebSocket must be wss://):

The server container serves the built web app, the REST API, the WebSocket, and uploaded logos (persisted in the uploads volume).

Status

All six build phases from the plan are scaffolded and wired end to end. Next session picks up on the Linux server: pnpm install, bring up Docker, point the reverse proxy at it, and verify the overlay live in OBS. See docs/PLAN.md → Verification.

Known follow-ups (intentionally deferred):

  • Character art is placeholder initials — map fighter ids to real stock icons later.
  • Schema is created via ensureSchema() on startup (idempotent CREATE TABLE IF NOT EXISTS); switch to Drizzle Kit migrations once the schema starts evolving.
  • POST /api/uploads is intentionally not auth-gated so no-login co-editors can set a logo.