Skip to content

Scorepad

Source catalog · Knowledge graph

Facts

FactValue
Statusactive
Hosting platformcloudflare
Repositoryhttps://github.com/meta4m/scorepad
Tech stacklanguage: typescript, ui: react, routing: react-router, api: hono, validation: zod, styling: tailwindcss, build_tool: vite, testing: vitest, hosting: cloudflare-pages, runtime: cloudflare-pages-functions, database: cloudflare-d1
Tagsscrabble, tournament, scoresheet, round-robin, standings
Last audited2026-09-15
Upstream dependenciesscrabble-scoresheet
Downstream dependenciesNone declared

Secrets manifest

Key names and metadata only. Encrypted records are available in this project’s vault below.

KeyDescriptionEnvironmentsRequired
SESSION_SECRETHMAC secret signing stateless per-sheet seat tokens (p1/p2/scorer claims on /game/:id); production falls back to a weak default if unset, so it must be setproductionYes
DIRECTOR_PASSWORDBootstrap password that creates the first director account on empty-database login; optional alternative to the /api/auth/setup first-run flowproductionNo

Overview

Project Overview: Scorepad

1. Executive Summary

  • Mission: A multi-device Scrabble tournament manager — a director console for tournaments, divisions, rosters and pairings; per-player scoresheets on each player’s own phone; automatic reconciliation of independent sheets into official results.
  • Target Audience: Tournament directors and scorekeepers running local Scrabble tournaments; players following live standings.
  • Primary Maintainer: The operator (meta4m) — private project.

2. Current Status & Roadmap

  • Lifecycle State: active — phase-driven development (visible history: Phase 9 player registry → Phase 10 round-robin simplification/kickoff → Phase 10.1 absent players → paper-grid scoresheet rework, all on 2026-08-24; 8 D1 migrations). No wrap-up or deprecation signal in the repo.
  • Ongoing work: The newest commits reworked the scoresheet UI to mirror the original paper grid (docs/scrabble_scoresheet.pdf) with score-only entry and a stable layout — UX iteration is in flight.

3. Core Capabilities

  • Director console (/admin): create tournaments/divisions, player registry with contacts (phone/email/club), Swiss or Berger round-robin pairing, kickoff times, editable tournaments, archive lock, absent-player handling without deleting results.
  • Scorekeeper access codes: tournament-scoped sessions via access code (12 h TTL); directors get account login (PBKDF2-hashed, 7-day sessions).
  • Per-player scoresheets (/game/:gid): seat tokens for p1/p2/optional scorer; turn grid; 100-tile tracker with undo; WhatsApp/email/copyable match-code handoff (PK phone normalization); first 30-second start countdown gate; first-mover seat swap until first turn.
  • Reconciliation: independent per-owner sheets compared; agreement confirms the official result, mismatch flags needs_review for director adjudication.
  • Standings: points/spread/rating-sorted tables with bye = win +50 spread; public read-only tournament view (/t/:id) and printable blank scoresheet (/print).
  • Successor to scrabble-scoresheet: it implements that project’s documented roadmap (multi-device sync via D1, match pairing, two-sheet reconciliation, tournament management) and ships the identical source paper scoresheet in docs/. Training-side needs live in the sibling app scrabbler.

4. Key References

  • Repository: meta4m/scorepad
  • Production URL: https://scorepad.pages.dev — referenced in tests/phase9.test.ts share links and the scorepad Pages project name; live deployment is not independently confirmed in-repo (no CI workflow file exists; deploys are manual).
  • Architecture Docs: architecture.md · Deployment Guide: deployment.md

Architecture

Architecture & System Design: Scorepad

1. System Topology

graph TD
Director[Director browser] --> Admin[React admin console /admin<br/>tournaments · rosters · pairings]
Keeper[Scorekeeper] --> Code[Access-code login /api/auth/code]
Player[Player phone] --> Sheet[Scoresheet /game/:gid<br/>seat token p1/p2/scorer]
Public[Players / spectators] --> Pub[Public standings /t/:id]
Admin --> SPA[React 19 + Tailwind SPA<br/>Cloudflare Pages]
Sheet --> SPA
Pub --> SPA
Code --> API
SPA --> API[Hono API on Pages Functions<br/>functions/api catch-all]
API --> D1[(D1 scoresheet-db<br/>8 migrations)]

2. Core Components & Subsystems

  • SPA (src/): React 19 + React Router 7 + Tailwind 4, built by Vite 6; pages for Home, Login, admin layout (Tournaments, Players, TournamentDetail, Division), Scoresheet, PublicTournament, BlankScoresheet; useAuth session hook; api.ts typed fetch client; share.ts match-code handoff.
  • API (server/): Hono app mounted at /api via the Pages Functions catch-all route in functions/api/ (hono/cloudflare-pages); routes auth, players, tournaments, entries, games, sheets; /api/health checks D1 liveness; zod-validated JSON bodies.
  • Domain logic (pure, shared): pairings.ts (Berger round-robin + Swiss with rematch avoidance), standings.ts (points/spread with bye +50), reconcile.ts (compare p1/p2/scorer sheet totals), registry.ts, joincode.ts; shared/ holds tiles.ts (100-tile distribution), startGate.ts (30 s countdown + seat-swap freeze), sheet.ts, types.ts used by server, client, and tests alike.
  • Auth subsystem (server/auth.ts): PBKDF2 password hashing (Web Crypto), D1-backed sessions with director/scorekeeper roles, HMAC-signed stateless seat tokens (gameId.owner.sig) for per-sheet claims; client stores seat claims in localStorage (sheetClient.ts).
  • Storage: D1 scoresheet-db — users, sessions, tournaments, divisions, players, entries, games (join codes, needs_review, starts_at), turns, sheets (migration 0004+).

3. Data Flow & Storage Contracts

  • Primary storage: Cloudflare D1 (DB binding, scoresheet-db, migrations 0001–0008); server is the single source of truth for tournaments/games/turns/sheets.
  • Client storage: localStorage holds only seat-token claims per game (sp_* keys) so a player re-enters their own sheet without a login.
  • Sheet contract: each game can have independent sheets for p1, p2, and an optional scorer; turns are {turn_no, word, score, total_after} rows (max 25 per side); reconciliation requires all present sheets to agree on final totals before a game confirms.
  • Tests: Vitest suites for pairings, reconcile, roster, and phase-9 flows.

4. Architectural Decision Records (ADRs)

ADR-01: Hono on Pages Functions — one deployment unit
  • Context: A React SPA plus an API, with an aversion to separate Worker services.
  • Decision: The Hono app runs as a Pages Function catch-all (the functions/api route handler); static assets and API deploy together to dist/.
  • Consequences: Single wrangler pages deploy ships everything; API scaling is bounded by Pages Functions (no separate routing, queues, or cron).
ADR-02: Pure shared domain modules
  • Context: Pairings, standings, start-gate, and tile rules must behave identically on server, client, and in tests.
  • Decision: Pure functions in server/ and shared/ with no DB or DOM access; routes only orchestrate persistence.
  • Consequences: High unit-test coverage without mocks; the trade-off is duplicated import graphs kept honest by tsconfig.functions.json.
ADR-03: Keep the two-independent-sheets principle from scrabble-scoresheet
  • Context: Tournament dispute-avoidance practice (each player keeps their own sheet).
  • Decision: Per-owner sheets with mandatory p1/p2 confirmation; disagreement sets needs_review for director adjudication instead of auto-averaging.
  • Consequences: Trust model survives digitization; reconciliation adds a manual adjudication step on mismatch.
ADR-04: Passwords and seat tokens via Web Crypto, no auth service
  • Context: No third-party identity provider wanted; players must join a sheet with zero accounts.
  • Decision: PBKDF2 (100k iterations) hashes for directors; random-token D1 sessions with role + tournament scoping; HMAC seat tokens as stateless per-sheet claims; optional DIRECTOR_PASSWORD bootstrap on an empty database.
  • Consequences: No external auth dependency; SESSION_SECRET becomes production-critical (weak fallback secret would forge seat tokens).

Deployment

Deployment & Operational Runbook: Scorepad

1. Hosting & Environments

EnvironmentPlatformURL / EndpointAuto-Deploy Branch
ProductionCloudflare Pageshttps://scorepad.pages.dev (project name in wrangler.toml; referenced by test share links — live status unverified)none — manual npm run deploy
Local devVite build —watch + Wrangler Pages devhttp://localhost:8788N/A

Note: unlike scrabbler and scrabble-scoresheet, no GitHub Actions workflow exists in this repo — every deploy is manual.

2. Prerequisites & Toolchain

  • Node.js + npm; Wrangler 4.x (devDependency) with an authenticated wrangler login.
  • Cloudflare account holding the scorepad Pages project and the scoresheet-db D1 database (database_id is pinned in wrangler.toml).

3. Local Development Setup

Terminal window
git clone https://github.com/meta4m/scorepad
cd scorepad
npm install
npm run db:migrate # wrangler d1 migrations apply scoresheet-db --local
npm run dev # vite build --watch + wrangler pages dev dist --port 8788

First run locally: hit /api/auth/state — needs_setup: true opens the first-run director setup (/api/auth/setup), or set DIRECTOR_PASSWORD in .dev.vars (gitignored) and bootstrap via login.

4. Environment & Secrets Provisioning

Refer to spec.yml for declared secret keys.

  • Secret Injection: Retrieve ciphertext from 2Brain vault and decrypt locally into .env.local (.dev.vars for wrangler).
  • Runtime (Pages project): SESSION_SECRET (seat-token HMAC — required in production; a weak default fallback exists for dev only) and optional DIRECTOR_PASSWORD (director bootstrap). Set with wrangler pages secret put <KEY> --project-name scorepad. No other external credentials exist.

5. Deployment Commands

Terminal window
npm run build # typecheck both configs + vite build → dist/
npm run deploy # wrangler pages deploy dist --project-name scorepad
# D1 schema changes are never applied by deploy — run explicitly:
npx wrangler d1 migrations apply scoresheet-db --remote

6. Verification & Health Checks

  • curl -f https://scorepad.pages.dev/api/health → {"status":"ok","service":"scorepad","db":"up"} (reports D1 liveness).
  • Smoke test: director creates tournament + division + players → generate pairings → open a game’s share link → enter codes on two devices → enter turns on both sheets → confirm both → game final and standings update on /t/:id.
  • Pre-deploy gates: npm run typecheck, npm run lint, npm test all clean.

7. Incident Response & Rollback

  • Rollback: redeploy the previous git revision — git checkout <good-sha> && npm run build && npm run deploy. D1 migrations are forward-only (SQLite ALTER limits mean some changes rebuild tables), so a schema rollback requires a new corrective migration, not a redeploy.
  • Seat-token forgery concern: rotate SESSION_SECRET (wrangler pages secret put SESSION_SECRET) — old seat tokens become invalid; players rejoin via match codes.
  • Mismatched sheets mid-tournament: games flagged needs_review are adjudicated from the director console (review is a flag, not a status value — SQLite CHECK constraint workaround).

Project vault

Cloudflare Access authorization and your passphrase are required to retrieve and decrypt stored values.

Client-Side Encrypted Secret Vault

Zero-knowledge architecture. Secrets are encrypted client-side using Web Crypto AES-256-GCM before leaving this browser tab. Edge databases store only ciphertext, IV, and salt.

Encrypted Records for scorepad (0)

Encrypted records have not been loaded.

Add / Rotate Encrypted Secret