Scrabbler
Source catalog · Knowledge graph
Facts
| Fact | Value |
|---|---|
| Status | active |
| Hosting platform | cloudflare |
| Repository | https://github.com/meta4m/scrabbler |
| Tech stack | language: typescript, ui: react, build_tool: vite, styling: hand-written-css, testing: vitest, hosting: cloudflare-pages, database: cloudflare-d1, auth: google-oauth-pkce |
| Tags | scrabble, training, pwa, tournament, word-games |
| Last audited | 2026-09-15 |
| Upstream dependencies | None declared |
| Downstream dependencies | None declared |
Secrets manifest
Key names and metadata only. Encrypted records are available in this project’s vault below.
| Key | Description | Environments | Required |
|---|---|---|---|
CLOUDFLARE_API_TOKEN | Durable Cloudflare API token (Cloudflare Pages: Edit) used by the GitHub Actions workflow to deploy the scrabbler Pages project | production | Yes |
GOOGLE_CLIENT_SECRET | Google OAuth 2.0 client secret backing the optional sign-in that syncs training attempts to D1 across devices | production, development | No |
SESSION_SECRET | High-entropy string signing the HttpOnly session cookies for Google-signed-in users (Pages Functions) | production, development | No |
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 behindmain. - 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
- Repository: meta4m/scrabbler
- Production URL: scrabbler.pages.dev
- Architecture Docs:
architecture.md· Deployment Guide:deployment.md
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+useEffectonly. - 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 oneWordshape — bundledsource-words.json(2,580 words) and lazy-loadedcsw24.json(280,887 words, ~770 KB gz) + definitions (~3.5 MB gz);words.tsnormalizes and filters. - Pages Functions (
functions/api/):auth/google.ts(OAuth start/callback),auth/logout.ts,me.ts,sync/attempts.ts;functions/lib/auth.tsholds server-side session helpers. - D1 binding (
SCRABBLER_DB→scrabbler-profile-db): schema inmigrations/0001_profile_sync.sql(users, sessions, attempts).
3. Data Flow & Storage Contracts
- Anonymous storage:
localStoragekeyscrabbler.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-localuseStateanduseMemofor 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.
ADR-03: Lazy-loaded dictionary chunks with in-browser search
- 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.cssis 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.cssmust be empty before commit).
Deployment
Deployment & Operational Runbook: Scrabbler
1. Hosting & Environments
| Environment | Platform | URL / Endpoint | Auto-Deploy Branch |
|---|---|---|---|
| Production | Cloudflare Pages | https://scrabbler.pages.dev | main (GitHub Actions) |
| PR preview | Cloudflare Pages | https://pr-<n>.scrabbler.pages.dev | PRs from this repo |
| Local dev | Vite dev server | http://localhost:5173 | N/A |
2. Prerequisites & Toolchain
- Node.js 22+ and npm; Wrangler 4.x for Pages deploys and D1 access.
- A Cloudflare account with the
scrabblerPages project and thescrabbler-profile-dbD1 database (wrangler.jsoncbindsSCRABBLER_DB; thedatabase_idthere is the live D1 — do not change it).
3. Local Development Setup
git clone https://github.com/meta4m/scrabblercd scrabblernpm installnpm 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_SECRETnpx 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.varsfor Pages Functions dev). - Runtime (Cloudflare Pages project):
GOOGLE_CLIENT_SECRET,SESSION_SECRETviawrangler pages secret put ... --project-name scrabbler;GOOGLE_CLIENT_IDandAPP_ORIGINare non-secret config (OAuth client ID / origin URL). - CI (GitHub Actions secrets):
CLOUDFLARE_API_TOKEN;CLOUDFLARE_ACCOUNT_IDis non-secret account config. Fork PRs get no deploy credentials by design.
5. Deployment Commands
npm test # unit tests must be greennpx tsc -b # strict type check must be cleannpm run build # tsc -b && vite build → dist/npm run deploy:production # wrangler pages deploy dist --project-name scrabblernpm run deploy:preview # same, to the "preview" branchPrimary 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 →
Syncedpill appears; attempts visible from another device. npm testandnpx tsc -bmust pass before any deployment (enforced by CI).git diff -- src/styles.cssmust 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.devURL 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 URIhttps://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.