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

8.1 KiB
Raw Blame History

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)

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

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).