# scrabble-game — project guide Multiplatform Scrabble game. Read this first every session. The owner drives the project **one stage per session** (tariff constraint), so the repository — not conversation memory — is the source of continuity. Keep it that way. ## Sources of truth (read before changing behaviour) - [`PLAN.md`](PLAN.md) — staged plan + **stage tracker** + per-stage *open details to interview*. - [`PRERELEASE.md`](PRERELEASE.md) — pre-release hardening tracker (phases R1–R7 before Stage 18); same per-phase *interview + bake-back* discipline as `PLAN.md`. - [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) — architecture, transport, security, the decision record. Always describes current state. - [`docs/FUNCTIONAL.md`](docs/FUNCTIONAL.md) (+ [`_ru`](docs/FUNCTIONAL_ru.md) mirror) — per-domain user stories. English authoritative. - [`docs/TESTING.md`](docs/TESTING.md) — test layers + the per-stage CI gate. - [`docs/UI_DESIGN.md`](docs/UI_DESIGN.md) — the `ui` visual/interaction design system. ## Mandatory per-stage workflow **Start of a stage** 1. Read `PLAN.md` (the stage's scope + *open details*) and the relevant `docs/`. 2. Analyse what the stage actually requires against the current code. 3. **Interview the owner** on every open detail and any fork not already fixed in the plan — do not silently pick borderline decisions. Offer options with brief pros/cons. 4. Only then implement, strictly within the stage's scope. **End of a stage** 1. Bake every new agreement back into `PLAN.md`, `docs/ARCHITECTURE.md`, `docs/FUNCTIONAL.md` (+ `_ru`), the affected service `README`, and Go Doc comments — in the **same** PR. Correct earlier stages' docs/code if a new decision changes them. 2. Update the stage tracker; add a line under *Refinements logged during implementation* for any plan deviation. 3. Get CI green, then mark the stage done. (The `stage-implementation` skill encodes this same loop and can be invoked.) ## Conventions - All code, comments, identifiers, commits, docs, filenames in **English**. - Chat with the owner follows the user-level `~/.claude/CLAUDE.md` (Russian, the agreed persona and translation rules). - Mirror every point edit of `docs/FUNCTIONAL.md` into `docs/FUNCTIONAL_ru.md` in the same patch (translate only the touched paragraphs). - Prefer compact code; do not add deps, seams or knobs until a stage needs them. Reuse before adding. Document added packages/types/funcs with Go Doc comments. - Update or add tests for every functional change. ## Branching & CI - **Two long-lived branches** (Stage 16 onward): **`development`** is the integration branch; **`master`** is the production trunk. Cut `feature/*` branches **from `development`** and PR them back into it. (Stages 0–15 used `master` as the trunk with `feature/* → master`; the genesis Stage 0 commit is on `master` by necessity.) - A commit to a `feature/*` branch triggers **nothing**. The single workflow `.gitea/workflows/ci.yaml` runs the full suite (`unit` + `integration` + `ui`) on a PR into `development` or `master`, and the gated **`deploy`** job auto-rolls the **test contour** on a PR into — or a push to — `development` (`docker compose up -d --build` on the runner host + a `GET /` probe). A PR into `master` is test-only. - Merge `development → master` only when CI is green; the **prod** deploy is then a **manual** workflow (Stage 18), never automatic. Secrets/variables are prefixed `TEST_` / `PROD_` per contour (Gitea 1.26 has no deployment environments). - After any push, watch the run to green before declaring a stage done — use the ready-made watcher, never an inline poll loop: `python3 ~/.claude/bin/gitea-ci-watch.py` (background). It reads `$GITEA_URL` / `$GITEA_TOKEN`; `gitea.iliadenisov.ru` is allow-listed in `.claude/settings.json`. Remote: `origin git@gitea.iliadenisov.ru:developer/scrabble-game.git`. ## Stack Go 1.26.3, `go.work` monorepo, module paths `scrabble/`. Dependencies are added **when first used** (incremental): backend uses `gin` + `zap` + `pgx`/`go-jet`/`goose`/OTel (added in Stage 1). Client↔gateway is Connect-RPC + FlatBuffers (h2c); gateway↔backend is REST/JSON + `X-User-ID` plus a gRPC server-stream for live events. UI is pure HTML5/CSS on plain Svelte + Vite, packaged to native with Capacitor. Likely no Redis. ## Reused engine: `../scrabble-solver` (module `scrabble-solver`, Go 1.26.3) Embedded **in-process as a library** — there is no per-game container. Public API to reuse (do not reimplement): - `scrabble.NewSolver(rs, finder)` → `GenerateMoves(b, r, mode)` (ranked, highest score first), `ValidatePlay(b, dir, tiles)`, `ScorePlay(...)`; `scrabble.Apply(b, m)`; types `Move/Word/Placement/Direction/Mode` (`scrabble-solver/scrabble/{solver,move,apply}.go`). - `rules.English() / RussianScrabble() / Erudit()` (`scrabble-solver/rules/rules.go`). - `board.New / Parse / Clone / Transpose`; `rack.New / Add / Remove / Clone`; `selfplay.NewBag / Draw / Len` (bag pattern). - Load committed dictionaries with `dawg.Load(path)` from `github.com/iliadenisov/dafsa`: `scrabble-solver/dawg/{en_sowpods,ru_scrabble,ru_erudit}.dawg`. Constraints: - Words/tiles are **alphabet-index bytes**, meaningful only with the matching `rules.Ruleset` (`Alphabet.Decode`); blank flag carried separately. **Decode to real characters before persisting history** (history must be dictionary-independent — see `docs/ARCHITECTURE.md` §9.1). - The solver's `internal/*` is NOT importable from this sibling module. - **GCG is test-only** in the solver (no public writer) — we ship our own. - Wiring: add `replace scrabble-solver => ../scrabble-solver` to `go.work` in **Stage 2** (when `internal/engine` first imports it), and make CI check out the solver sibling (`https://gitea.iliadenisov.ru/.../scrabble-solver.git`). It uses published `github.com/iliadenisov/{alphabet,dafsa}` (no local replace). ## Repository layout ``` go.work # use the existing modules; grows per stage backend/ # module scrabble/backend cmd/backend/ # main: telemetry -> db+migrate -> cache -> server cmd/jetgen/ # dev tool: regenerate go-jet code (throwaway container) internal/config/ # env config (composes postgres + telemetry) internal/telemetry/ # OTel providers + request-timing middleware internal/postgres/ # pgx/database-sql pool, goose migrations/, jet/ (generated) internal/account/ # durable accounts + identities (store) internal/session/ # opaque tokens, sessions store, cache, service internal/server/ # gin engine, /api/v1 groups, X-User-ID, probes internal/inttest/ # //go:build integration Postgres-backed tests docs/ .gitea/workflows/ PLAN.md CLAUDE.md README.md gateway/ ui/ pkg/ # added by their stages platform/telegram/ # Telegram connector side-service (Stage 9): bot + gRPC API loadtest/ # module scrabble/loadtest: the pre-release stress harness (R2) backend/Dockerfile gateway/Dockerfile platform/telegram/Dockerfile loadtest/Dockerfile # multi-stage distroless (Stage 16; loadtest R2) deploy/ # docker-compose + caddy + otelcol/prometheus/tempo/grafana (+ cAdvisor/postgres_exporter, R2) ``` ## Build & test ```sh go build ./backend/... # per module ('./...' from the root won't span the workspace) go vet ./backend/... gofmt -l . # must print nothing go test -count=1 ./backend/... go build ./platform/telegram/... && go test ./platform/telegram/... # Telegram connector (Stage 9) go run ./backend/cmd/backend # /healthz, /readyz on :8080 cd ui && pnpm install && pnpm check && pnpm test:unit && pnpm build # the UI (Stage 7+) pnpm start # UI mock mode: lobby -> game, no backend docker build -f backend/Dockerfile -t scrabble-backend . # images (Stage 16); gateway embeds the UI docker build -f gateway/Dockerfile -t scrabble-gateway . docker compose -f deploy/docker-compose.yml config # validate the full contour ``` The `ui` module is a Node project (pnpm), **not** in `go.work`; it is the `ui` job of the single `.gitea/workflows/ci.yaml` (Stage 16 folded the former go-unit / integration / ui-test workflows into it). Committed edge codegen under `ui/src/gen/` (regenerate with `pnpm codegen`); pnpm build-script approval lives in `ui/pnpm-workspace.yaml` (`allowBuilds: esbuild: true`).