Files
scrabble-game/CLAUDE.md
T
Ilia Denisov 8700fbfae1
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 11s
CI / ui (pull_request) Successful in 19s
CI / deploy (pull_request) Failing after 1s
Stage 16: deploy infra & test contour
- backend + gateway multi-stage distroless Dockerfiles; the gateway embeds and
  serves the SPA at / and /telegram/ via go:embed (committed dist placeholder,
  real build baked in by the image's node stage)
- deploy/docker-compose.yml: backend + gateway + Postgres + Telegram connector
  (VPN sidecar) + OTel Collector + Prometheus (15d) + Tempo (72h) + Grafana,
  fronted by a caddy owning a single /_gm Basic-Auth (admin console + Grafana
  subpath); inter-service on a private network, only caddy on the edge network
- new metrics: backend accounts_created_total{kind} (robots excluded) and an
  in-memory gateway active_users{window=24h,7d} gauge
- CI: single .gitea/workflows/ci.yaml (unit/integration/ui + a gated test-contour
  deploy) on the new feature/* -> development -> master branch model; the old
  go-unit/integration/ui-test workflows are folded in; the connector-scoped
  compose is retired (superseded by deploy/)
- docs: ARCHITECTURE §11/§12/§13, root + gateway READMEs, CLAUDE.md branching,
  PLAN.md (stage 16 done + refinements + Stage 17 forward-notes)
2026-06-05 11:42:26 +02:00

154 lines
8.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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*.
- [`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 015 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 17), 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/<name>`. 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
backend/Dockerfile gateway/Dockerfile platform/telegram/Dockerfile # multi-stage distroless (Stage 16)
deploy/ # docker-compose + caddy + otelcol/prometheus/tempo/grafana (Stage 16)
```
## 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`).