# Scrabble Game — implementation plan Living plan and **stage tracker**. Each stage is implemented in its own session; the rules for starting and finishing a stage are in [`CLAUDE.md`](CLAUDE.md). The architecture/decision record is [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md); behaviour is [`docs/FUNCTIONAL.md`](docs/FUNCTIONAL.md). When a stage produces a decision, bake it back here **and** into the affected docs/code in the same PR. ## Context Greenfield multiplatform Scrabble. Players arrive from a platform (Telegram first; later VK/MAX/iOS/Android) or standalone web (email / guest). Three executables — `gateway`, `backend`, `ui` — plus per-platform side-services. Deliberately simpler than the sibling `../galaxy-game` (idea donor, not a template). The `../scrabble-solver` engine is embedded in-process as a library. ## Locked decisions (recap — full record in docs/ARCHITECTURE.md) Stack: `go.work` monorepo, modules `scrabble/`, Go 1.26.x, backend gin+pgx+Postgres(schema `backend`)+goose+zap+OTel (deps added when first used). Wire: Connect-RPC + FlatBuffers (client↔gateway), REST/JSON + `X-User-ID` (gateway↔backend), gRPC server-stream for live events. Auth: platform-native, thin opaque session token, no Ed25519/signing, likely no Redis. UI: pure HTML5/CSS, plain Svelte + Vite, Capacitor for native. MVP surfaces: Telegram + web (email + ephemeral guest) + link/merge. Variants: ru/en/Эрудит. Legality: validate-at-submit. End: empty bag+rack / 6 scoreless / 24h timeout. Hint: top-1. Word-check: unlimited + complaint. Robot: P(win)≈0.40, margin targeting, [2,90]min skewed timing, sleep 00:00–07:00 opp-tz, nudge logic. Dictionary: pin per game. History: structured + GCG export, dictionary- independent (see ARCHITECTURE §9.1). ## Stage tracker | # | Stage | Status | |---|-------|--------| | 0 | Scaffolding (go.work, backend skeleton, docs, CI) | **done** | | 1 | Backend foundation (config, server, Postgres+goose, sessions, accounts) | **done** | | 2 | Engine package over scrabble-solver | todo | | 3 | Game domain (lifecycle, rules, hint, word-check, history+GCG, stats) | todo | | 4 | Lobby & social (matchmaking, friends, block, chat, profile, nudge) | todo | | 5 | Robot opponent | todo | | 6 | Gateway edge (Connect/FB, platform auth, sessions, push bridge, admin) | todo | | 7 | UI (plain Svelte + Vite, board, lobby, chat, i18n) | todo | | 8 | Telegram integration (bot side-service, deep-link, push) | todo | | 9 | Admin & dictionary ops (complaint review, version reload) | todo | | 10 | Account linking & merge | todo | | 11 | Polish (observability, perf with evidence, deploy) | todo | Scaffolding is incremental: `go.work` lists only existing modules; each stage adds the modules it needs. ## Stages Each stage: read this plan + relevant docs, **interview the owner on the open details below**, implement within scope, then update plan/docs/code and get CI green before marking done. ### Stage 0 — Scaffolding *(done)* Scope: `go.work` (Go 1.26.3, `use ./backend`); minimal runnable `backend` (gin, zap, `/healthz`, `/readyz`, env config); docs skeleton; `PLAN.md`; `CLAUDE.md`; `.gitea/workflows/go-unit.yaml`; README; `.gitignore`. Acceptance: `go build ./backend/...` + `go vet` + gofmt clean + `go test ./backend/...` green; CI green on push. ### Stage 1 — Backend foundation Scope: config/server route groups (`/api/v1/{public,user,internal,admin}`, probes), Postgres (pgx) + embedded goose migrations + schema `backend`, telemetry (OTel) wiring, in-memory cache scaffolding, thin sessions + accounts + platform identities. Open details: Postgres version + DSN/`search_path` convention; jet vs sqlc/sqlx (default jet); migration naming; exact session-token shape (opaque random length, TTL, revocation); account/identity table shape; whether the admin bootstrap lands here or in Stage 9. ### Stage 2 — Engine package Scope: `backend/internal/engine` over scrabble-solver — versioned DAWG load/registry, GenerateMoves/ValidatePlay/ScorePlay wrappers, bag/rack, the **dictionary-independent** game-state model + decode helpers. Add `replace scrabble-solver => ../scrabble-solver` to `go.work` here and solve the CI sibling-checkout (clone `gitea.iliadenisov.ru/.../scrabble-solver`). Open details: how CI obtains the solver (clone sibling vs publish/tag the solver module); in-memory game-state representation; how blanks and exchanges are modelled; Эрудит specifics to verify against the solver. ### Stage 3 — Game domain Scope: create/join, turn order, submit play/pass/exchange/resign, validate-at-submit, scoring, end-conditions, 24h timeout/auto-resign, hint, word-check + complaint capture, structured history + GCG writer, stats on finish. Open details: GCG dialect details (blanks, exchanges, notation); exact stats edge cases; turn-timeout scheduler mechanism (cron vs per-game timer); complaint payload shape. ### Stage 4 — Lobby & social Scope: matchmaking pool, friends, block, per-game chat, profile + email confirm-code, nudge. Open details: pool fairness/keying confirmation; deep-link format per platform; chat length limit + retention; friend-request lifecycle; email-code provider (SMTP relay choice). ### Stage 5 — Robot opponent Scope: human-like player — balance ~0.40, margin targeting, skewed [2,90]min timing + sleep + nudge logic, friend/DM blocking, name pool. Open details: exact delay distribution + parameters; margin band; name pool source; how the scheduler drives robot moves; metrics for tuning balance. ### Stage 6 — Gateway edge Scope: Connect/gRPC-Web (h2c), Telegram initData validation → session → `X-User-ID`, in-memory rate-limit, admin Basic-Auth passthrough, FlatBuffers transcoding, in-app push stream bridging backend `push` gRPC stream, email + ephemeral-guest paths. Open details: FlatBuffers schema layout + message_type catalog; rate-limit classes/limits; admin surface routing; session cache shape at the gateway. ### Stage 7 — UI Scope: plain Svelte + Vite static; Connect-web + FlatBuffers client; lobby (my games, profile tabs); board (HTML5/CSS grid, drag-n-drop, no assets); chat; hint/word-check; in-app stream; i18n en/ru; in-memory session (+IndexedDB if available); Capacitor-ready structure. Open details: detailed game-board UX (deferred by the owner to this stage); client routing; offline/refresh behaviour; design system / theming. ### Stage 8 — Telegram integration Scope: bot side-service, deep-link invites, platform push (your-turn / nudge), Mini App launch/auth; backend↔platform internal API. Open details: bot framework/library; deep-link scheme; push message templates; internal API contract; Mini App hosting/origin. ### Stage 9 — Admin & dictionary ops Scope: admin endpoints (users, games, complaint review queue, dictionary versions + reload), complaint→dictionary update pipeline. Open details: whether a server-rendered console is wanted or JSON-only; the dictionary rebuild/deploy pipeline; complaint resolution workflow. ### Stage 10 — Account linking & merge Scope: link-via-confirm; merge-into-A (stats sum, transfer games/friends, dedupe). High blast-radius — focused regression tests. Open details: conflict resolution (active games on both, duplicate friends, display-name collisions); irreversibility/audit; confirm-flow per platform. ### Stage 11 — Polish Scope: observability dashboards, evidence-based performance work, prod build/deploy. Open details: deployment target/host; dashboards; load expectations. ## Refinements logged during implementation - **Stage 0**: solver `replace` deferred to Stage 2 (nothing imports it yet; adding the path now would break CI, which checks out only this repo). Docker / compose deferred to a stage that has something to deploy. Trunk is `master` (owner preference); `feature/*` + PR from Stage 1; the genesis commit lands on `master` by necessity. - **Stage 1** (interview + implementation): - Query layer: **go-jet** over `database/sql` (pgx stdlib) + otelsql; a `cmd/jetgen` tool regenerates the **committed** code from a throwaway container. Postgres **17** pinned for jetgen, tests and prod. - Sessions: opaque token stored only as a **SHA-256 hash** (kept as hex `text`, not `bytea` — avoids jet bytea-literal friction), **revoke-only** (no TTL); revocation-audit table deferred. Backend keeps a warmed write-through session cache that gates `/readyz`. - Data model: **UUIDv7** PKs; one unified `identities` table (`kind ∈ telegram|email`, widen to `vk`/`max` later); no soft-delete / actor-audit columns yet. - HTTP surface: **service/store/cache layer only**. `/api/v1/{public,user, internal,admin}` groups + `X-User-ID` middleware are scaffolding (exposed via `Server` group accessors); the session/account REST handlers land with the gateway in **Stage 6**. Admin bootstrap deferred to **Stage 9**. - Telemetry: providers + request-timing middleware + otelsql; exporters `none` (default) / `stdout`; OTLP + dashboards deferred to **Stage 11**. - Tests/CI: integration tests behind the `integration` build tag in `backend/internal/inttest` + new `integration.yaml` (testcontainers, Ryuk off, serial), firing on push and PR. Backend now **hard-depends on Postgres at boot** (migrations at startup) — a deliberate contract change from Stage 0, documented in both READMEs. All code stays in the existing `backend` module under `internal/` (+ `cmd/jetgen`); `go.work` untouched.