Skip to content

Scrabbler

Source catalog · Knowledge graph

Facts

FactValue
Statusactive
Hosting platformcloudflare
Repositoryhttps://github.com/meta4m/scrabbler
Tech stacklanguage: typescript, ui: react, build_tool: vite, styling: hand-written-css, testing: vitest, hosting: cloudflare-pages, database: cloudflare-d1, auth: google-oauth-pkce
Tagsscrabble, training, pwa, tournament, word-games
Last audited2026-09-15
Upstream dependenciesNone declared
Downstream dependenciesNone declared

Secrets manifest

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

KeyDescriptionEnvironmentsRequired
CLOUDFLARE_API_TOKENDurable Cloudflare API token (Cloudflare Pages: Edit) used by the GitHub Actions workflow to deploy the scrabbler Pages projectproductionYes
GOOGLE_CLIENT_SECRETGoogle OAuth 2.0 client secret backing the optional sign-in that syncs training attempts to D1 across devicesproduction, developmentNo
SESSION_SECRETHigh-entropy string signing the HttpOnly session cookies for Google-signed-in users (Pages Functions)production, developmentNo

Overview

Project Overview: Scrabbler

1. Executive Summary

  • Mission: A mobile-first PWA for competitive Scrabble players — vocabulary recall, anagram retrieval, power-letter knowledge, dump-word recognition, and full 15×15 tournament board simulation in one fast web app.
  • Target Audience: The operator (solo developer) as a personal study system; usable by any competitive Scrabble player.
  • Primary Maintainer: The operator (meta4m) — personal project, all rights reserved.

2. Current Status & Roadmap

  • Lifecycle State: active — v1.0.0 released; latest commit (2026-09-09) added ranked, paginated word lookup running in a Web Worker, so the README’s 200-result-cap description is now slightly behind main.
  • Ongoing work: Phase 4 close-out (mobile tournament layout), Phase 6 meaning enrichment, Phase 7 multiplayer rooms/broadcast, plus proposed lookup wildcards, stronger tournament AI, and offline-first PWA service worker (see ROADMAP.md).

3. Core Capabilities

  • Word Lookup over the curated 2,580-word study source and the full 280,887-word Collins CSW24 lexicon (lazy-loaded chunks; ranked, paginated results as of the latest commit).
  • Six active-recall drills (2-letter sprint, power letters, all-letter sprint, adaptive mix, bingo families, dump words) with mastery model and spaced repetition.
  • Rack Lab — rack plays, hook/extension analysis, anagrams, dump options, bingo-family matching.
  • Tournament Lab — full 15×15 board engine: center-opening rule, cross-word validation, premium squares, blanks, bingo bonus, opponent AI, timers, learning vs competition mode.
  • My Progress dashboard with accuracy, latency, mastery ratings, and an adaptive queue.
  • Optional Google sign-in (OAuth 2.0 + PKCE, HttpOnly cookies, server-side sessions) syncing attempts to Cloudflare D1.
  • Part of a scrabble suite with the operator’s other projects: scrabble-scoresheet (offline per-player scoresheet) and scorepad (multi-device tournament manager). No shared code — the relationship is domain and maintainer only.

4. Key References

Architecture

Architecture & System Design: Scrabbler

1. System Topology

graph TD
Player[Player browser] --> SPA[React SPA / 5 views<br/>~93 KB gz shell]
SPA -->|lazy chunk on toggle| CSW[CSW24 dictionaries<br/>csw24.json + definitions]
SPA -->|anonymous attempts| LS[(localStorage<br/>scrabbler.attempts.v1)]
SPA -->|optional Google sign-in + sync| Fns[Pages Functions /api/*]
Fns --> D1[(D1 scrabbler-profile-db<br/>users / sessions / attempts)]
WW[lookup.worker.ts<br/>Web Worker search] -.->|zero-copy indexes| SPA
GH[GitHub Actions: push main] -->|test + build + deploy| Pages[Cloudflare Pages static CDN]

2. Core Components & Subsystems

  • App shell (src/App.tsx): single-file view state (home | lookup | drill | progress | rack | tournament) — no router, no state library; useState + useMemo + useEffect only.
  • Domain engines (src/lib/): scrabble.ts (racks, hooks, extensions, anagrams), tournament.ts (full board state machine and scoring), training.ts (attempts, mastery, adaptive selection), lookup-search.ts + lookup.worker.ts + lookup-client.ts (ranked search in a Web Worker over transferred indexes).
  • Data layer (src/data/): two dictionaries through one Word shape — bundled source-words.json (2,580 words) and lazy-loaded csw24.json (280,887 words, ~770 KB gz) + definitions (~3.5 MB gz); words.ts normalizes and filters.
  • Pages Functions (functions/api/): auth/google.ts (OAuth start/callback), auth/logout.ts, me.ts, sync/attempts.ts; functions/lib/auth.ts holds server-side session helpers.
  • D1 binding (SCRABBLER_DB → scrabbler-profile-db): schema in migrations/0001_profile_sync.sql (users, sessions, attempts).

3. Data Flow & Storage Contracts

  • Anonymous storage: localStorage key scrabbler.attempts.v1, capped at 500 attempts; zero-backend instant persistence.
  • Signed-in storage: Cloudflare D1 scrabbler-profile-db; on sign-in, local attempts merge with remote by attempt ID. No Google tokens are stored in browser or database.
  • Dictionary assets: static chunks served from the Pages CDN; full CSW24 search runs browser-side (now in a Web Worker) — there is no server-side search endpoint.
  • Tests: ~140+ Vitest unit tests (42 pre-v1.0.0 + 63 added with the ranked lookup) run in <200 ms.

4. Architectural Decision Records (ADRs)

ADR-01: No router, no state library
  • Context: Five-view SPA with modest shared state; bundle size matters on mobile.
  • Decision: Single-file view state in App.tsx; component-local useState and useMemo for the rest.
  • Consequences: ~93 KB gz app shell and a flat mental model; adding deeply nested routes later would require introducing a router.
ADR-02: localStorage-first, optional D1 sync
  • Context: Training must work instantly with zero backend; cross-device sync is a nice-to-have.
  • Decision: All attempts persist to localStorage; optional Google sign-in (PKCE, HttpOnly cookie, D1 sessions) merges by attempt ID.
  • Consequences: Anonymous users are device-bound (500-attempt cap); sync adds a D1 database and OAuth secrets only when used.
  • Context: The full CSW24 lexicon is ~770 KB gz (3.5 MB with definitions) vs a 288 KB study source.
  • Decision: Vite dynamic imports emit separate chunks loaded on first toggle/magnifier click; search is client-side, recently moved into a Web Worker with zero-copy transferred indexes.
  • Consequences: First visit stays ~93 KB; broad queries stay responsive; advanced server-side indexing (regex, prefix trees) remains future work.
ADR-04: Frozen global stylesheet
  • Context: The hand-written src/styles.css is complete and regression-prone.
  • Decision: Project policy forbids modifying it — new visuals use inline styles on JSX.
  • Consequences: No style-regression risk; verification is manual (git diff -- src/styles.css must be empty before commit).

Deployment

Deployment & Operational Runbook: Scrabbler

1. Hosting & Environments

EnvironmentPlatformURL / EndpointAuto-Deploy Branch
ProductionCloudflare Pageshttps://scrabbler.pages.devmain (GitHub Actions)
PR previewCloudflare Pageshttps://pr-<n>.scrabbler.pages.devPRs from this repo
Local devVite dev serverhttp://localhost:5173N/A

2. Prerequisites & Toolchain

  • Node.js 22+ and npm; Wrangler 4.x for Pages deploys and D1 access.
  • A Cloudflare account with the scrabbler Pages project and the scrabbler-profile-db D1 database (wrangler.jsonc binds SCRABBLER_DB; the database_id there is the live D1 — do not change it).

3. Local Development Setup

Terminal window
git clone https://github.com/meta4m/scrabbler
cd scrabbler
npm install
npm run dev # http://localhost:5173 — works offline with the focused source
# Optional Google OAuth locally:
cp .dev.vars.example .dev.vars # fill GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET / SESSION_SECRET
npx wrangler pages dev dist # run Pages Functions locally (after npm run build)

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 Pages Functions dev).
  • Runtime (Cloudflare Pages project): GOOGLE_CLIENT_SECRET, SESSION_SECRET via wrangler pages secret put ... --project-name scrabbler; GOOGLE_CLIENT_ID and APP_ORIGIN are non-secret config (OAuth client ID / origin URL).
  • CI (GitHub Actions secrets): CLOUDFLARE_API_TOKEN; CLOUDFLARE_ACCOUNT_ID is non-secret account config. Fork PRs get no deploy credentials by design.

5. Deployment Commands

Terminal window
npm test # unit tests must be green
npx tsc -b # strict type check must be clean
npm run build # tsc -b && vite build → dist/
npm run deploy:production # wrangler pages deploy dist --project-name scrabbler
npm run deploy:preview # same, to the "preview" branch

Primary path is the GitHub Actions workflow (.github/workflows/deploy-pages.yml): tests → build → functions type-check → deploy on push to main / manual dispatch. D1 migrations are NOT auto-applied: run wrangler d1 migrations apply scrabbler-profile-db --remote explicitly with a D1-edit credential.

6. Verification & Health Checks

  • After deploy, open https://scrabbler.pages.dev — app shell loads; a Lookup query returns results in both dictionary modes.
  • Sign-in smoke test: Google button → Synced pill appears; attempts visible from another device.
  • npm test and npx tsc -b must pass before any deployment (enforced by CI).
  • git diff -- src/styles.css must be empty before commit (project policy).

7. Incident Response & Rollback

  • Rollback: redeploy the previous git revision — git checkout <good-sha> && npm run deploy:production (or re-run the workflow from the last green commit). Each deployment also has an immutable *.scrabbler.pages.dev URL visible in the Pages dashboard.
  • Bad data / D1: migrations are forward-only and manual; investigate with wrangler d1 execute scrabbler-profile-db --remote --command "..." before any schema action.
  • Auth outage: Google sign-in failing leaves anonymous training fully functional — check Pages secrets (GOOGLE_CLIENT_SECRET, SESSION_SECRET) and the OAuth redirect URI https://scrabbler.pages.dev/api/auth/google/callback.

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 scrabbler (0)

Encrypted records have not been loaded.

Add / Rotate Encrypted Secret