Ilia Denisov 2e8fa83814
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 15s
CI / ui (pull_request) Successful in 57s
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m17s
feat(telemetry): local move-preview adoption metrics (Phase 4)
Measure uptake of the client-side local move-preview accelerator (§5) so
adoption can be watched before defaulting it on: app cold starts, dictionary
loads by result (fetched / cache_hit / miss) and move previews by path
(local / network — the backend load shed).

A small best-effort client beacon (POST /metrics/local-eval, session-gated)
batches counter deltas and posts them on a 60s timer and when the app is
backgrounded — never on the gameplay path: the in-app counters are plain
in-memory increments, only the periodic flush touches the network and it is
fire-and-forget. The gateway folds each batch into three OTel counters
(local_eval_cold_start_total, local_eval_dict_load_total,
local_eval_preview_total), clamped against a spoofed inflation.

- gateway: counters + recordLocalEval + session-gated /metrics/local-eval handler
- ui: localeval-metrics accumulator/beacon; hooks in the dict loader, in
  Game.recompute and in bootstrap (skipped under the mock harness)
- caddy: route /metrics/* to the gateway
- docs: ARCHITECTURE §11; Grafana "Scrabble — Users" dashboard panels
2026-07-01 23:31:49 +02:00

scrabble-game

Multiplatform Scrabble game. Players arrive from a platform (Telegram first; later VK/MAX/iOS/Android) or from standalone web (email / guest). The game supports English Scrabble, Russian Scrabble and Эрудит.

Components

  • gateway — the only public ingress: anti-abuse, platform authentication (resolves the player and injects X-User-ID), routing to backend, and an admin surface behind Basic Auth.
  • backend — internal-only service that owns every domain concern and embeds the scrabble-solver engine library in-process.
  • ui — pure-HTML5 client (plain Svelte 5 + TypeScript + Vite) over Connect-RPC
    • FlatBuffers, embeddable in platform webviews and packageable to native via Capacitor. See ui/README.md.
  • platform/* — per-platform side-services (e.g. the Telegram bot).

Documentation (sources of truth)

Build & test

go build ./backend/... ./pkg/... ./gateway/...  # per module (the workspace spans several)
go vet ./backend/... ./pkg/... ./gateway/...
gofmt -l .                                       # must print nothing
go test -count=1 ./backend/... ./pkg/... ./gateway/...   # unit tests
go test -tags=integration -count=1 -p=1 ./backend/...    # + Postgres (needs Docker)

The integration-tagged tests start a throwaway postgres:17-alpine container via testcontainers-go and require a reachable Docker daemon; they live in the backend module. The wire contracts in pkg and the Connect edge in gateway have committed generated code (regenerate dev-time with make -C pkg gen / make -C gateway gen).

Run the backend locally

The backend now owns persistence, so it needs Postgres and applies its embedded migrations at startup:

docker run -d --name scrabble-pg -e POSTGRES_PASSWORD=dev -p 5432:5432 postgres:17-alpine
BACKEND_POSTGRES_DSN='postgres://postgres:dev@localhost:5432/postgres?search_path=backend&sslmode=disable' \
  go run ./backend/cmd/backend     # HTTP API + probes on :8080, push gRPC on :9090

Run the gateway locally

The gateway is the public edge; point it at a running backend:

GATEWAY_BACKEND_HTTP_URL=http://localhost:8080 \
GATEWAY_BACKEND_GRPC_ADDR=localhost:9090 \
  go run ./gateway/cmd/gateway     # Connect/h2c edge on :8081

Key environment: BACKEND_HTTP_ADDR (default :8080), BACKEND_LOG_LEVEL (debug|info|warn|error, default info), BACKEND_POSTGRES_DSN (required). The full configuration surface and the go-jet regeneration step live in backend/README.md.

Run the UI locally

cd ui && pnpm install
pnpm start     # mock mode: lobby -> game with no backend, on http://localhost:5173
pnpm dev       # against a running gateway (Vite proxies the RPC path to :8081)

pnpm check (type-check), pnpm test:unit (Vitest), pnpm test:e2e (Playwright smoke vs the mock), pnpm build (static bundle). Details — including the committed edge codegen (pnpm codegen) — are in ui/README.md.

Deploy (deploy/)

The full contour is deploy/docker-compose.yml: backend + gateway (with the UI embedded via go:embed, baked in by its node build stage) + Postgres + the Telegram connector (with a VPN sidecar) + an observability stack (OTel Collector → Prometheus + Tempo → Grafana) + a front caddy that owns a single /_gm Basic-Auth (admin console + Grafana). The Go services build from multi-stage distroless */Dockerfile.

docker build --build-arg DICT_VERSION=v1.3.0 -f backend/Dockerfile -t scrabble-backend .  # DICT_VERSION required; pulls that DAWG release artifact
docker build -f gateway/Dockerfile -t scrabble-gateway .   # node stage builds + embeds the UI
docker compose -f deploy/docker-compose.yml config         # validate (needs the TEST_/PROD_ env)

CI auto-deploys the test contour on a PR into — or push to — development (.gitea/workflows/ci.yaml); the prod contour is a manual deploy after development → master. Env reference: deploy/.env.example; the topology and the two-contour model are in docs/ARCHITECTURE.md §13.

S
Description
No description provided
Readme 26 MiB
v1.1.0 Latest
2026-06-22 07:27:41 +00:00
Languages
Go 57.8%
TypeScript 29.2%
Svelte 8.7%
JavaScript 1.3%
Go Template 1.2%
Other 1.7%