Stage 7 (wip): docs bake + stage renumber (insert UI Stage 8, shift +1)
- PLAN.md: new Stage 8 (UI social/account/history); Telegram->9, Admin->10, Linking->11, Polish->12; tracker + Stage 7 refinements; split the Stage 6 'wired in Stage 7' note between 7 and 8 - ARCHITECTURE: promote ui to current (slice scope, board-replay, codegen, theming, mock) - FUNCTIONAL(+ru): client-app section with the Stage 7/8 split - README + ui/README + CLAUDE.md: UI build/run/test, codegen, pnpm notes - bumped Stage 8-11 refs (+1) across docs and code comments
This commit is contained in:
@@ -121,4 +121,12 @@ go vet ./backend/...
|
|||||||
gofmt -l . # must print nothing
|
gofmt -l . # must print nothing
|
||||||
go test -count=1 ./backend/...
|
go test -count=1 ./backend/...
|
||||||
go run ./backend/cmd/backend # /healthz, /readyz on :8080
|
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
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The `ui` module is a Node project (pnpm), **not** in `go.work`; its CI is
|
||||||
|
`.gitea/workflows/ui-test.yaml`. Committed edge codegen under `ui/src/gen/`
|
||||||
|
(regenerate with `pnpm codegen`); pnpm build-script approval lives in
|
||||||
|
`ui/pnpm-workspace.yaml` (`allowBuilds: esbuild: true`).
|
||||||
|
|||||||
@@ -40,11 +40,12 @@ independent (see ARCHITECTURE §9.1).
|
|||||||
| 4 | Lobby & social (matchmaking, friends, block, chat, profile, nudge) | **done** |
|
| 4 | Lobby & social (matchmaking, friends, block, chat, profile, nudge) | **done** |
|
||||||
| 5 | Robot opponent | **done** |
|
| 5 | Robot opponent | **done** |
|
||||||
| 6 | Gateway edge (Connect/FB, platform auth, sessions, push bridge, admin) | **done** |
|
| 6 | Gateway edge (Connect/FB, platform auth, sessions, push bridge, admin) | **done** |
|
||||||
| 7 | UI (plain Svelte + Vite, board, lobby, chat, i18n) | todo |
|
| 7 | UI — playable slice (Svelte+Vite, board, lobby, chat, hint/word-check, i18n) | todo |
|
||||||
| 8 | Telegram integration (bot side-service, deep-link, push) | todo |
|
| 8 | UI — social/account/history (friends, blocks, invitations, profile edit, stats, history/GCG) | todo |
|
||||||
| 9 | Admin & dictionary ops (complaint review, version reload) | todo |
|
| 9 | Telegram integration (bot side-service, deep-link, push) | todo |
|
||||||
| 10 | Account linking & merge | todo |
|
| 10 | Admin & dictionary ops (complaint review, version reload) | todo |
|
||||||
| 11 | Polish (observability, perf with evidence, deploy) | todo |
|
| 11 | Account linking & merge | todo |
|
||||||
|
| 12 | Polish (observability, perf with evidence, deploy) | todo |
|
||||||
|
|
||||||
Scaffolding is incremental: `go.work` lists only existing modules; each stage
|
Scaffolding is incremental: `go.work` lists only existing modules; each stage
|
||||||
adds the modules it needs.
|
adds the modules it needs.
|
||||||
@@ -70,7 +71,7 @@ platform identities.
|
|||||||
Open details: Postgres version + DSN/`search_path` convention; jet vs
|
Open details: Postgres version + DSN/`search_path` convention; jet vs
|
||||||
sqlc/sqlx (default jet); migration naming; exact session-token shape (opaque
|
sqlc/sqlx (default jet); migration naming; exact session-token shape (opaque
|
||||||
random length, TTL, revocation); account/identity table shape; whether the
|
random length, TTL, revocation); account/identity table shape; whether the
|
||||||
admin bootstrap lands here or in Stage 9.
|
admin bootstrap lands here or in Stage 10.
|
||||||
|
|
||||||
### Stage 2 — Engine package
|
### Stage 2 — Engine package
|
||||||
Scope: `backend/internal/engine` over scrabble-solver — versioned DAWG
|
Scope: `backend/internal/engine` over scrabble-solver — versioned DAWG
|
||||||
@@ -120,25 +121,90 @@ available); Capacitor-ready structure.
|
|||||||
Open details: detailed game-board UX (deferred by the owner to this stage);
|
Open details: detailed game-board UX (deferred by the owner to this stage);
|
||||||
client routing; offline/refresh behaviour; design system / theming.
|
client routing; offline/refresh behaviour; design system / theming.
|
||||||
|
|
||||||
### Stage 8 — Telegram integration
|
#### Suggested layouts (lobby + game screen)
|
||||||
|
|
||||||
|
User note:
|
||||||
|
> Detailed interview about UI/UX is **strongly** required.
|
||||||
|
> Too much to discuss.
|
||||||
|
|
||||||
|
```text
|
||||||
|
┌────────────────────┐
|
||||||
|
│ Display_Name =│- Profile
|
||||||
|
├────────────────────┤- Settings
|
||||||
|
│ Invitations │- About
|
||||||
|
│ - list │
|
||||||
|
├────────────────────┤
|
||||||
|
│ Active games │
|
||||||
|
│ - list │
|
||||||
|
├────────────────────┤
|
||||||
|
│ Finished games │
|
||||||
|
│ - list │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
├────────────────────┤
|
||||||
|
│ ┌───┐ ┌───┐ ┌───┐│
|
||||||
|
│ New │ Stats Tourn│
|
||||||
|
│ └───┘ └───┘ └───┘│
|
||||||
|
└────────────────────┘
|
||||||
|
┌────────────────────┐
|
||||||
|
Lobby│◄ ==│- History
|
||||||
|
├────────────────────┤- Chat
|
||||||
|
│You Ann Kaya Rick│- Check word
|
||||||
|
│136 700 179 39│- Drop game
|
||||||
|
├────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ c │
|
||||||
|
│ words │
|
||||||
|
│ o │
|
||||||
|
│ s │
|
||||||
|
│ s │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
├──┬──┬──┬──┬──┬──┬──┤ ┌──┐
|
||||||
|
│A │Q │Z │* │N │I │W │◄│ │MakeMove/Reset
|
||||||
|
├──┴──┴──┴──┴──┴──┴──┤ └──┘
|
||||||
|
│ ┌───┐ ┌───┐ ┌───┐ │
|
||||||
|
│ Draw│ Skip│ Shfl│ │
|
||||||
|
│ └───┘ └───┘ └───┘ │
|
||||||
|
└────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stage 8 — UI: social, account & history surfaces
|
||||||
|
Scope: the UI surfaces deferred from Stage 7's playable slice, wiring the matching
|
||||||
|
backend/gateway operations as each screen needs them (the Stage 6 vertical-slice
|
||||||
|
pattern): friends (request/accept/decline/list), per-user blocks, friend-game
|
||||||
|
invitations (create 2–4 player, accept/decline, invitations list), profile **editing**
|
||||||
|
(`account.UpdateProfile` + the email confirm-code binding UI), the statistics screen,
|
||||||
|
and the history viewer with GCG export/download.
|
||||||
|
Open details: friends/invitations UX; stats presentation; history/GCG viewer + download
|
||||||
|
mechanics; any new validation the profile-editing forms need.
|
||||||
|
|
||||||
|
### Stage 9 — Telegram integration
|
||||||
Scope: bot side-service, deep-link invites, platform push (your-turn / nudge),
|
Scope: bot side-service, deep-link invites, platform push (your-turn / nudge),
|
||||||
Mini App launch/auth; backend↔platform internal API.
|
Mini App launch/auth; backend↔platform internal API.
|
||||||
Open details: bot framework/library; deep-link scheme; push message templates;
|
Open details: bot framework/library; deep-link scheme; push message templates;
|
||||||
internal API contract; Mini App hosting/origin.
|
internal API contract; Mini App hosting/origin.
|
||||||
|
|
||||||
### Stage 9 — Admin & dictionary ops
|
### Stage 10 — Admin & dictionary ops
|
||||||
Scope: admin endpoints (users, games, complaint review queue, dictionary
|
Scope: admin endpoints (users, games, complaint review queue, dictionary
|
||||||
versions + reload), complaint→dictionary update pipeline.
|
versions + reload), complaint→dictionary update pipeline.
|
||||||
Open details: whether a server-rendered console is wanted or JSON-only; the
|
Open details: whether a server-rendered console is wanted or JSON-only; the
|
||||||
dictionary rebuild/deploy pipeline; complaint resolution workflow.
|
dictionary rebuild/deploy pipeline; complaint resolution workflow.
|
||||||
|
|
||||||
### Stage 10 — Account linking & merge
|
### Stage 11 — Account linking & merge
|
||||||
Scope: link-via-confirm; merge-into-A (stats sum, transfer games/friends,
|
Scope: link-via-confirm; merge-into-A (stats sum, transfer games/friends,
|
||||||
dedupe). High blast-radius — focused regression tests.
|
dedupe). High blast-radius — focused regression tests.
|
||||||
Open details: conflict resolution (active games on both, duplicate friends,
|
Open details: conflict resolution (active games on both, duplicate friends,
|
||||||
display-name collisions); irreversibility/audit; confirm-flow per platform.
|
display-name collisions); irreversibility/audit; confirm-flow per platform.
|
||||||
|
|
||||||
### Stage 11 — Polish
|
### Stage 12 — Polish
|
||||||
Scope: observability dashboards, evidence-based performance work, prod
|
Scope: observability dashboards, evidence-based performance work, prod
|
||||||
build/deploy.
|
build/deploy.
|
||||||
Open details: deployment target/host; dashboards; load expectations.
|
Open details: deployment target/host; dashboards; load expectations.
|
||||||
@@ -164,9 +230,9 @@ Open details: deployment target/host; dashboards; load expectations.
|
|||||||
- HTTP surface: **service/store/cache layer only**. `/api/v1/{public,user,
|
- HTTP surface: **service/store/cache layer only**. `/api/v1/{public,user,
|
||||||
internal,admin}` groups + `X-User-ID` middleware are scaffolding (exposed via
|
internal,admin}` groups + `X-User-ID` middleware are scaffolding (exposed via
|
||||||
`Server` group accessors); the session/account REST handlers land with the
|
`Server` group accessors); the session/account REST handlers land with the
|
||||||
gateway in **Stage 6**. Admin bootstrap deferred to **Stage 9**.
|
gateway in **Stage 6**. Admin bootstrap deferred to **Stage 10**.
|
||||||
- Telemetry: providers + request-timing middleware + otelsql; exporters
|
- Telemetry: providers + request-timing middleware + otelsql; exporters
|
||||||
`none` (default) / `stdout`; OTLP + dashboards deferred to **Stage 11**.
|
`none` (default) / `stdout`; OTLP + dashboards deferred to **Stage 12**.
|
||||||
- Tests/CI: integration tests behind the `integration` build tag in
|
- Tests/CI: integration tests behind the `integration` build tag in
|
||||||
`backend/internal/inttest` + new `integration.yaml` (testcontainers, Ryuk
|
`backend/internal/inttest` + new `integration.yaml` (testcontainers, Ryuk
|
||||||
off, serial), firing on push and PR. Backend now **hard-depends on Postgres
|
off, serial), firing on push and PR. Backend now **hard-depends on Postgres
|
||||||
@@ -247,7 +313,7 @@ Open details: deployment target/host; dashboards; load expectations.
|
|||||||
wins/losses; `max_word_points` = best single **move** score; ties draw,
|
wins/losses; `max_word_points` = best single **move** score; ties draw,
|
||||||
resign/timeout is a loss, guests get no stats.
|
resign/timeout is a loss, guests get no stats.
|
||||||
- **Complaint** (interview): full payload with `game_id`; word-check is scoped
|
- **Complaint** (interview): full payload with `game_id`; word-check is scoped
|
||||||
to the game's pinned `(variant, dict_version)`. Stage 9 owns the resolution
|
to the game's pinned `(variant, dict_version)`. Stage 10 owns the resolution
|
||||||
lifecycle, so the `status` column carries no value CHECK yet.
|
lifecycle, so the `status` column carries no value CHECK yet.
|
||||||
- **GCG** (interview): standard Poslfit dialect (UTF-8, `#player`/`#lexicon`
|
- **GCG** (interview): standard Poslfit dialect (UTF-8, `#player`/`#lexicon`
|
||||||
pragmas, `8G`/`H8` coordinates, lower-case blanks, `.` pass-throughs, `-TILES`
|
pragmas, `8G`/`H8` coordinates, lower-case blanks, `.` pass-throughs, `-TILES`
|
||||||
@@ -295,7 +361,7 @@ Open details: deployment target/host; dashboards; load expectations.
|
|||||||
stored as a **SHA-256 hash**; a `Mailer` seam with an SMTP relay
|
stored as a **SHA-256 hash**; a `Mailer` seam with an SMTP relay
|
||||||
(`BACKEND_SMTP_*`) and a default **log mailer**. It binds an email to the
|
(`BACKEND_SMTP_*`) and a default **log mailer**. It binds an email to the
|
||||||
current account; an email already confirmed by another account → `ErrEmailTaken`
|
current account; an email already confirmed by another account → `ErrEmailTaken`
|
||||||
(**merge is Stage 10**); email-as-login is Stage 6 and reuses this mechanism.
|
(**merge is Stage 11**); email-as-login is Stage 6 and reuses this mechanism.
|
||||||
- **Multi-player drop-out** (interview; discharges the Stage 3 deferral): the
|
- **Multi-player drop-out** (interview; discharges the Stage 3 deferral): the
|
||||||
engine's `Resign` now drops a seat and the rest **play on** while ≥ 2 are
|
engine's `Resign` now drops a seat and the rest **play on** while ≥ 2 are
|
||||||
active, finishing (last-survivor wins) when one remains; `winner` excludes all
|
active, finishing (last-survivor wins) when one remains; `winner` excludes all
|
||||||
@@ -362,9 +428,11 @@ Open details: deployment target/host; dashboards; load expectations.
|
|||||||
end-to-end — auth (`auth.telegram`/`auth.guest`/`auth.email.request`/
|
end-to-end — auth (`auth.telegram`/`auth.guest`/`auth.email.request`/
|
||||||
`auth.email.login`), `profile.get`, `game.submit_play`/`game.state`,
|
`auth.email.login`), `profile.get`, `game.submit_play`/`game.state`,
|
||||||
`lobby.enqueue`/`lobby.poll`, `chat.post`, all five push events, and the admin
|
`lobby.enqueue`/`lobby.poll`, `chat.post`, all five push events, and the admin
|
||||||
passthrough. The remaining domain operations (friends, blocks, invitations,
|
passthrough. The remaining domain operations reuse the identical transcode
|
||||||
hint, word-check, pass/exchange/resign, history/GCG, profile editing) reuse the
|
pattern and are wired as the UI needs them: the play-loop ops (pass/exchange/
|
||||||
identical transcode pattern and are wired in **Stage 7** as the UI needs them.
|
resign, hint, evaluate, word-check/complaint, history, my-games list, chat
|
||||||
|
list/nudge) in **Stage 7**; the social/account ops (friends, blocks,
|
||||||
|
invitations, profile editing, stats, GCG export) in **Stage 8**.
|
||||||
- **Wire contracts in a new shared `scrabble/pkg` module** (interview): the
|
- **Wire contracts in a new shared `scrabble/pkg` module** (interview): the
|
||||||
backend push proto (`pkg/proto/push/v1`) and the FlatBuffers edge payloads
|
backend push proto (`pkg/proto/push/v1`) and the FlatBuffers edge payloads
|
||||||
(`pkg/fbs`, one `scrabblefb` namespace) live here with **committed** generated
|
(`pkg/fbs`, one `scrabblefb` namespace) live here with **committed** generated
|
||||||
@@ -402,7 +470,7 @@ Open details: deployment target/host; dashboards; load expectations.
|
|||||||
(the galaxy donor's crypto stack was dropped, per §3).
|
(the galaxy donor's crypto stack was dropped, per §3).
|
||||||
- **Admin = gateway validates Basic-Auth** (interview): the gateway checks
|
- **Admin = gateway validates Basic-Auth** (interview): the gateway checks
|
||||||
`GATEWAY_ADMIN_USER`/`_PASSWORD` and reverse-proxies to backend
|
`GATEWAY_ADMIN_USER`/`_PASSWORD` and reverse-proxies to backend
|
||||||
`/api/v1/admin/*`; the backend admin surface is a single `ping` until Stage 9.
|
`/api/v1/admin/*`; the backend admin surface is a single `ping` until Stage 10.
|
||||||
- **Rate-limit = 2 dimensions, 3 classes** (interview): public per-IP (30/min,
|
- **Rate-limit = 2 dimensions, 3 classes** (interview): public per-IP (30/min,
|
||||||
burst 10), authenticated per-user (120/min, burst 40), admin per-IP (60/min,
|
burst 10), authenticated per-user (120/min, burst 40), admin per-IP (60/min,
|
||||||
burst 20), plus an email-code per-IP sub-limit (5/10 min); token bucket
|
burst 20), plus an email-code per-IP sub-limit (5/10 min); token bucket
|
||||||
@@ -416,6 +484,48 @@ Open details: deployment target/host; dashboards; load expectations.
|
|||||||
(unit) — integration stays `./backend/...` (the only module with tagged tests).
|
(unit) — integration stays `./backend/...` (the only module with tagged tests).
|
||||||
The solver clone + `BACKEND_DICT_DIR` steps are unchanged.
|
The solver clone + `BACKEND_DICT_DIR` steps are unchanged.
|
||||||
|
|
||||||
|
- **Stage 7** (interview + implementation):
|
||||||
|
- **Scope = playable slice** (interview): the *whole* UI shell plus the core play
|
||||||
|
loop end-to-end; the social/account/history surfaces were split out into a new
|
||||||
|
**Stage 8** and the later stages shifted +1 (Telegram→9, Admin→10, Linking→11,
|
||||||
|
Polish→12). Stage 7 wires only the operations the slice needs (the Stage 6
|
||||||
|
"as the UI needs them" pattern): the new gateway/transcode + backend-REST ops
|
||||||
|
`games.list`, `game.{pass,exchange,resign,hint,evaluate,check_word,complaint,
|
||||||
|
history}`, `chat.{list,nudge}`. The only new domain code is `game.ListForAccount`
|
||||||
|
(the "my games" query) and seat **`display_name`** resolution (server DTO layer);
|
||||||
|
`SeatView` gained a trailing `display_name`. Friends/blocks/invitations,
|
||||||
|
profile-editing, stats and the history/GCG viewer are Stage 8.
|
||||||
|
- **Stack** (interview): plain **Svelte 5 (runes) + TypeScript + Vite**, no
|
||||||
|
SvelteKit; `@connectrpc/connect-web` + the `flatbuffers` runtime, with the edge
|
||||||
|
TS bindings generated from the **same** `edge.proto` (`protoc-gen-es`) and
|
||||||
|
`scrabble.fbs` (`flatc --ts`) and **committed** under `ui/src/gen/` (dev-time
|
||||||
|
codegen, like `cmd/jetgen` / `pkg/Makefile`; CI builds the committed output).
|
||||||
|
- **No board on the wire** (discovered): `StateView` carries no grid, so the client
|
||||||
|
**replays the decoded move journal** (`game.history`, newly wired) onto an empty
|
||||||
|
board; premium squares + tile values are a client-side map **ported from
|
||||||
|
`scrabble-solver/rules/rules.go`** with a Vitest parity test.
|
||||||
|
- **Board UX** (interview): full-width, borderless; tiles placed by **Pointer-Events
|
||||||
|
drag or tap** (no HTML5 DnD — it has no touch support); a contextual **MakeMove**
|
||||||
|
control (short tap → make/reset popup, ~1 s press-and-hold → commit); per-tile
|
||||||
|
recall by tapping a pending tile; a **two-state zoom** (15↔9 cells) on touch only
|
||||||
|
(auto-zoom-in on placement, double-tap / pinch manual); a blank-letter chooser.
|
||||||
|
All board/tiles/effects are **pure HTML5/CSS + Unicode** — no image/font/SVG asset.
|
||||||
|
- **Theming** (interview): own **CSS custom-property tokens**, light/dark via
|
||||||
|
`prefers-color-scheme`, **Telegram-themeParams-ready** (a runtime hook can override
|
||||||
|
the tokens; the SDK is wired in the Telegram stage). **Navigation** (interview):
|
||||||
|
dependency-free **hash router**; session token in memory + **IndexedDB**, re-resolved
|
||||||
|
on reload (reopen Subscribe, refetch the open game); stream reconnect on focus.
|
||||||
|
**i18n** en/ru is a hand-rolled typed catalog (compile-time key parity + a test).
|
||||||
|
- **Mock transport** (owner request): a build-flagged in-memory fake (`VITE_MOCK`,
|
||||||
|
`pnpm start`) drives lobby → active game → board with no backend, tree-shaken out
|
||||||
|
of production; it is the same fixture the Playwright smoke uses.
|
||||||
|
- **Tests/CI** (interview): **Vitest** units (board replay, placement machine,
|
||||||
|
premium parity, i18n parity, FlatBuffers codec) + a **Playwright** smoke against
|
||||||
|
the mock; a new **`ui-test.yaml`** workflow (type-check, unit, build with a
|
||||||
|
**bundle-size budget** — prod is ~67 KB gzip JS — and a chromium e2e). The Go
|
||||||
|
workflows already cover the new backend/gateway/pkg code; a `game.ListForAccount`
|
||||||
|
integration test and gateway transcode tests for the new ops were added.
|
||||||
|
|
||||||
## Deferred TODOs (cross-stage)
|
## Deferred TODOs (cross-stage)
|
||||||
|
|
||||||
- **TODO-1 — publish & version the solver.** Once `scrabble-solver` is stable,
|
- **TODO-1 — publish & version the solver.** Once `scrabble-solver` is stable,
|
||||||
@@ -433,7 +543,7 @@ Open details: deployment target/host; dashboards; load expectations.
|
|||||||
git submodule (the ~0.5–0.7 MB DAWGs are regenerated wholesale and bloat git
|
git submodule (the ~0.5–0.7 MB DAWGs are regenerated wholesale and bloat git
|
||||||
history); pin by tag/hash for a reproducible startup set. A submodule/LFS pull
|
history); pin by tag/hash for a reproducible startup set. A submodule/LFS pull
|
||||||
is a **deploy-time** way to populate the directory, **not** the runtime
|
is a **deploy-time** way to populate the directory, **not** the runtime
|
||||||
dynamic-reload mechanism (Stage 9) — keep the `BACKEND_DICT_DIR` directory as
|
dynamic-reload mechanism (Stage 10) — keep the `BACKEND_DICT_DIR` directory as
|
||||||
the runtime contract: a new `.dawg` appears in it and is loaded with
|
the runtime contract: a new `.dawg` appears in it and is loaded with
|
||||||
`dawg.Load`.
|
`dawg.Load`.
|
||||||
- **TODO-3 — garbage-collect abandoned guest accounts.** Stage 6 makes a guest a
|
- **TODO-3 — garbage-collect abandoned guest accounts.** Stage 6 makes a guest a
|
||||||
|
|||||||
@@ -11,8 +11,9 @@ supports English Scrabble, Russian Scrabble and Эрудит.
|
|||||||
admin surface behind Basic Auth. *(added in a later stage)*
|
admin surface behind Basic Auth. *(added in a later stage)*
|
||||||
- **`backend`** — internal-only service that owns every domain concern and
|
- **`backend`** — internal-only service that owns every domain concern and
|
||||||
embeds the [`scrabble-solver`](../scrabble-solver) engine library in-process.
|
embeds the [`scrabble-solver`](../scrabble-solver) engine library in-process.
|
||||||
- **`ui`** — pure-HTML5 client (plain Svelte + Vite), embeddable in platform
|
- **`ui`** — pure-HTML5 client (plain Svelte 5 + TypeScript + Vite) over Connect-RPC
|
||||||
webviews and packageable to native via Capacitor. *(added in a later stage)*
|
+ FlatBuffers, embeddable in platform webviews and packageable to native via
|
||||||
|
Capacitor. See [`ui/README.md`](ui/README.md).
|
||||||
- **`platform/*`** — per-platform side-services (e.g. the Telegram bot).
|
- **`platform/*`** — per-platform side-services (e.g. the Telegram bot).
|
||||||
*(added in a later stage)*
|
*(added in a later stage)*
|
||||||
|
|
||||||
@@ -67,3 +68,15 @@ Key environment: `BACKEND_HTTP_ADDR` (default `:8080`), `BACKEND_LOG_LEVEL`
|
|||||||
(`debug|info|warn|error`, default `info`), `BACKEND_POSTGRES_DSN` (**required**).
|
(`debug|info|warn|error`, default `info`), `BACKEND_POSTGRES_DSN` (**required**).
|
||||||
The full configuration surface and the go-jet regeneration step live in
|
The full configuration surface and the go-jet regeneration step live in
|
||||||
[`backend/README.md`](backend/README.md).
|
[`backend/README.md`](backend/README.md).
|
||||||
|
|
||||||
|
## Run the UI locally
|
||||||
|
|
||||||
|
```sh
|
||||||
|
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`](ui/README.md).
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ var (
|
|||||||
// ErrInvalidEmail is returned for an unparseable email address.
|
// ErrInvalidEmail is returned for an unparseable email address.
|
||||||
ErrInvalidEmail = errors.New("account: invalid email address")
|
ErrInvalidEmail = errors.New("account: invalid email address")
|
||||||
// ErrEmailTaken is returned when the email is already confirmed by another
|
// ErrEmailTaken is returned when the email is already confirmed by another
|
||||||
// account; binding it would be a merge, which Stage 10 owns.
|
// account; binding it would be a merge, which Stage 11 owns.
|
||||||
ErrEmailTaken = errors.New("account: email already confirmed by another account")
|
ErrEmailTaken = errors.New("account: email already confirmed by another account")
|
||||||
// ErrAlreadyConfirmed is returned when the email is already confirmed by the
|
// ErrAlreadyConfirmed is returned when the email is already confirmed by the
|
||||||
// requesting account.
|
// requesting account.
|
||||||
@@ -52,7 +52,7 @@ var (
|
|||||||
// Mailer and verifies it, binding a confirmed email identity to the requesting
|
// Mailer and verifies it, binding a confirmed email identity to the requesting
|
||||||
// account. Only the SHA-256 hash of a code is stored (never the plaintext),
|
// account. Only the SHA-256 hash of a code is stored (never the plaintext),
|
||||||
// matching the session model. Binding an email already confirmed by a different
|
// matching the session model. Binding an email already confirmed by a different
|
||||||
// account is refused (ErrEmailTaken) — merging two accounts is Stage 10 — and
|
// account is refused (ErrEmailTaken) — merging two accounts is Stage 11 — and
|
||||||
// using an email as a login is Stage 6, which reuses this mechanism.
|
// using an email as a login is Stage 6, which reuses this mechanism.
|
||||||
type EmailService struct {
|
type EmailService struct {
|
||||||
store *Store
|
store *Store
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const (
|
|||||||
StatusFinished = "finished"
|
StatusFinished = "finished"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ComplaintStatus values; Stage 9 owns the resolution lifecycle, Stage 3 only
|
// ComplaintStatus values; Stage 10 owns the resolution lifecycle, Stage 3 only
|
||||||
// ever writes StatusComplaintOpen.
|
// ever writes StatusComplaintOpen.
|
||||||
const StatusComplaintOpen = "open"
|
const StatusComplaintOpen = "open"
|
||||||
|
|
||||||
@@ -176,7 +176,7 @@ type RobotTurn struct {
|
|||||||
Seed int64
|
Seed int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// Complaint is a word-check complaint awaiting admin review (Stage 9).
|
// Complaint is a word-check complaint awaiting admin review (Stage 10).
|
||||||
type Complaint struct {
|
type Complaint struct {
|
||||||
ID uuid.UUID
|
ID uuid.UUID
|
||||||
ComplainantID uuid.UUID
|
ComplainantID uuid.UUID
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
// The /api/v1/admin/* endpoints are reached through the gateway's Basic-Auth
|
// The /api/v1/admin/* endpoints are reached through the gateway's Basic-Auth
|
||||||
// reverse proxy (docs/ARCHITECTURE.md §12). The backend trusts the gateway to
|
// reverse proxy (docs/ARCHITECTURE.md §12). The backend trusts the gateway to
|
||||||
// have authenticated the operator; the admin surface itself (complaint review,
|
// have authenticated the operator; the admin surface itself (complaint review,
|
||||||
// dictionary versions) lands in Stage 9. handleAdminPing is the proxy target that
|
// dictionary versions) lands in Stage 10. handleAdminPing is the proxy target that
|
||||||
// proves the path end to end until then.
|
// proves the path end to end until then.
|
||||||
func (s *Server) handleAdminPing(c *gin.Context) {
|
func (s *Server) handleAdminPing(c *gin.Context) {
|
||||||
c.JSON(http.StatusOK, gin.H{"status": "ok"})
|
c.JSON(http.StatusOK, gin.H{"status": "ok"})
|
||||||
|
|||||||
+21
-10
@@ -24,9 +24,20 @@ Three executables plus per-platform side-services:
|
|||||||
administration. Embeds the **`scrabble-solver`** engine **as a library,
|
administration. Embeds the **`scrabble-solver`** engine **as a library,
|
||||||
in-process** — there is no per-game container. The only network consumer of
|
in-process** — there is no per-game container. The only network consumer of
|
||||||
`backend` is `gateway` (plus platform side-services over an internal API).
|
`backend` is `gateway` (plus platform side-services over an internal API).
|
||||||
- **`ui`** *(planned)* — pure-HTML5 client (plain Svelte + Vite, static build).
|
- **`ui`** — pure-HTML5 client (plain Svelte 5 + TypeScript + Vite, static build;
|
||||||
Talks to `backend` only through `gateway`. Embeddable in platform webviews;
|
no SvelteKit). Talks to `backend` only through `gateway` over Connect-RPC +
|
||||||
packageable to native (iOS/Android) via Capacitor.
|
FlatBuffers, with the edge TS bindings generated from the **same** `edge.proto`
|
||||||
|
and `scrabble.fbs` and committed under `ui/src/gen/`. The **playable slice**
|
||||||
|
(Stage 7) covers auth, "my games", auto-match, the board (play/pass/exchange/
|
||||||
|
resign), hint, word-check, chat/nudge, the live stream, i18n (en/ru) and a profile
|
||||||
|
view; the social/account/history surfaces follow in Stage 8. There is no board on
|
||||||
|
the wire — the client **reconstructs the 15×15 board by replaying the move
|
||||||
|
journal** (§9.1) and renders board, tiles, premium squares and effects as pure
|
||||||
|
CSS + Unicode (no image/font/SVG assets). Tiles are placed by Pointer-Events drag
|
||||||
|
or tap; a CSS-token theme is light/dark and Telegram-themeParams-ready; navigation
|
||||||
|
is a hash router and the session token is held in memory + IndexedDB. A build-flagged
|
||||||
|
in-memory mock transport (`pnpm start`) runs the whole slice with no backend.
|
||||||
|
Embeddable in platform webviews; packageable to native (iOS/Android) via Capacitor.
|
||||||
- **`platform/<name>`** *(planned)* — per-platform side-services (Telegram bot
|
- **`platform/<name>`** *(planned)* — per-platform side-services (Telegram bot
|
||||||
first): deep-link invites and platform-native push notifications. They talk
|
first): deep-link invites and platform-native push notifications. They talk
|
||||||
to `backend` over an internal API.
|
to `backend` over an internal API.
|
||||||
@@ -108,7 +119,7 @@ arrive from a platform rather than completing a mandatory registration).
|
|||||||
TTL, ≤ 5 attempts) is sent through a `Mailer` seam (an SMTP relay, or a
|
TTL, ≤ 5 attempts) is sent through a `Mailer` seam (an SMTP relay, or a
|
||||||
development log mailer when none is configured) and, once verified, attaches a
|
development log mailer when none is configured) and, once verified, attaches a
|
||||||
confirmed email identity. An email already confirmed by **another** account is
|
confirmed email identity. An email already confirmed by **another** account is
|
||||||
refused — adopting it would be a merge, which Stage 10 owns. Accounts and
|
refused — adopting it would be a merge, which Stage 11 owns. Accounts and
|
||||||
identities use application-generated **UUIDv7** primary keys.
|
identities use application-generated **UUIDv7** primary keys.
|
||||||
- **Linking** is initiated from an authenticated profile: choose a platform →
|
- **Linking** is initiated from an authenticated profile: choose a platform →
|
||||||
complete that platform's web-auth confirm → attach the identity to the
|
complete that platform's web-auth confirm → attach the identity to the
|
||||||
@@ -140,7 +151,7 @@ Key points:
|
|||||||
word-check tool through `Registry.Lookup`.
|
word-check tool through `Registry.Lookup`.
|
||||||
- **Dictionary versioning — pin per game.** A game records the `dict_version` it
|
- **Dictionary versioning — pin per game.** A game records the `dict_version` it
|
||||||
started on and finishes on that version; new games use the latest. Multiple
|
started on and finishes on that version; new games use the latest. Multiple
|
||||||
versions may be resident at once. An admin reload *(planned, Stage 9)*
|
versions may be resident at once. An admin reload *(planned, Stage 10)*
|
||||||
registers a new version through `Registry.Load`; delivery is the DAWG file in
|
registers a new version through `Registry.Load`; delivery is the DAWG file in
|
||||||
the image / a volume mounted at the dictionary directory. (A future split of
|
the image / a volume mounted at the dictionary directory. (A future split of
|
||||||
the solver into engine + dictionary generator with versioned artifacts is
|
the solver into engine + dictionary generator with versioned artifacts is
|
||||||
@@ -202,7 +213,7 @@ Key points:
|
|||||||
- **Word-check tool**: unlimited dictionary lookups against the game's pinned
|
- **Word-check tool**: unlimited dictionary lookups against the game's pinned
|
||||||
dictionary; each result offers a **complaint** (complainant, game, variant,
|
dictionary; each result offers a **complaint** (complainant, game, variant,
|
||||||
dict_version, word, the disputed result, an optional note) that lands in an
|
dict_version, word, the disputed result, an optional note) that lands in an
|
||||||
admin review queue *(admin side planned, Stage 9)*.
|
admin review queue *(admin side planned, Stage 10)*.
|
||||||
|
|
||||||
## 7. Robot opponent
|
## 7. Robot opponent
|
||||||
|
|
||||||
@@ -250,7 +261,7 @@ requires (there is no DM surface; chat is per-game).
|
|||||||
emits a **match-found** notification (§10), delivered over the live stream;
|
emits a **match-found** notification (§10), delivered over the live stream;
|
||||||
`Poll` remains as a fallback for a client that is not currently streaming.
|
`Poll` remains as a fallback for a client that is not currently streaming.
|
||||||
- **Friends**: a **request → accept** graph (one `friendships` table) — add by
|
- **Friends**: a **request → accept** graph (one `friendships` table) — add by
|
||||||
friend list or internal ID now, by platform deep-link with Stage 8. Declining or
|
friend list or internal ID now, by platform deep-link with Stage 9. Declining or
|
||||||
cancelling removes the pending request; blocking someone severs an existing
|
cancelling removes the pending request; blocking someone severs an existing
|
||||||
friendship.
|
friendship.
|
||||||
- **Block**: two independent **global** account toggles (`block_chat`,
|
- **Block**: two independent **global** account toggles (`block_chat`,
|
||||||
@@ -275,7 +286,7 @@ requires (there is no DM surface; chat is per-game).
|
|||||||
(confirm-code binding, see §4), **timezone** (drives the away window and the
|
(confirm-code binding, see §4), **timezone** (drives the away window and the
|
||||||
robot's sleep; user-editable), the daily **away window** and the block toggles —
|
robot's sleep; user-editable), the daily **away window** and the block toggles —
|
||||||
all editable through `account.UpdateProfile`. Linked platform accounts and merge
|
all editable through `account.UpdateProfile`. Linked platform accounts and merge
|
||||||
are Stage 10.
|
are Stage 11.
|
||||||
|
|
||||||
## 9. Persistence
|
## 9. Persistence
|
||||||
|
|
||||||
@@ -337,7 +348,7 @@ does not cover.
|
|||||||
## 10. Notifications
|
## 10. Notifications
|
||||||
|
|
||||||
Two channels: the **in-app live stream** (delivered from Stage 6) and
|
Two channels: the **in-app live stream** (delivered from Stage 6) and
|
||||||
**platform-native push** (out-of-app, via the platform side-service — Stage 8).
|
**platform-native push** (out-of-app, via the platform side-service — Stage 9).
|
||||||
The backend emits notification intents through an in-process hub
|
The backend emits notification intents through an in-process hub
|
||||||
(`internal/notify`, a `Publisher` seam installed on the game, social and lobby
|
(`internal/notify`, a `Publisher` seam installed on the game, social and lobby
|
||||||
services); a single backend→gateway **gRPC server-stream** (`Push.Subscribe`,
|
services); a single backend→gateway **gRPC server-stream** (`Push.Subscribe`,
|
||||||
@@ -348,7 +359,7 @@ robot-driver and timeout-sweeper moves emit too), **chat-message** and **nudge**
|
|||||||
(from the social service), and **match-found** (from the matchmaker, §8). Event
|
(from the social service), and **match-found** (from the matchmaker, §8). Event
|
||||||
payloads are FlatBuffers-encoded by the backend and forwarded verbatim. A client
|
payloads are FlatBuffers-encoded by the backend and forwarded verbatim. A client
|
||||||
that is not currently streaming falls back to the matchmaker's `Poll` for
|
that is not currently streaming falls back to the matchmaker's `Poll` for
|
||||||
match-found. Out-of-app platform push (your-turn, nudge) is wired in Stage 8;
|
match-found. Out-of-app platform push (your-turn, nudge) is wired in Stage 9;
|
||||||
session-revocation events and cursor-based stream resume are deferred
|
session-revocation events and cursor-based stream resume are deferred
|
||||||
(single-instance MVP).
|
(single-instance MVP).
|
||||||
|
|
||||||
|
|||||||
+13
-3
@@ -9,6 +9,16 @@ the detail is authored.
|
|||||||
|
|
||||||
## Domains
|
## Domains
|
||||||
|
|
||||||
|
### Client app *(Stage 7 / 8)*
|
||||||
|
The web/app client (Svelte + Vite) realizes these stories. The **playable slice**
|
||||||
|
(Stage 7) covers signing in (guest or email), the "my games" lobby, starting an
|
||||||
|
auto-match, playing the board (place tiles by drag or tap, pass, exchange, resign),
|
||||||
|
the top-1 hint, the unlimited word-check with complaint, per-game chat and nudge,
|
||||||
|
real-time in-app updates, switching interface language (en/ru) and theme, and a
|
||||||
|
read-only profile. Managing friends and blocks, creating friend games (invitations),
|
||||||
|
editing the profile, the statistics screen and the history/GCG viewer arrive in
|
||||||
|
Stage 8.
|
||||||
|
|
||||||
### Identity & sessions *(Stage 1 / 6)*
|
### Identity & sessions *(Stage 1 / 6)*
|
||||||
A player arrives from a platform (Telegram first), via email login, or as an
|
A player arrives from a platform (Telegram first), via email login, or as an
|
||||||
ephemeral guest. The gateway validates the credential once and mints a thin
|
ephemeral guest. The gateway validates the credential once and mints a thin
|
||||||
@@ -17,7 +27,7 @@ session-only with restricted features (auto-match only; no friends, stats or
|
|||||||
history). While the app is open the client keeps a live stream and receives
|
history). While the app is open the client keeps a live stream and receives
|
||||||
in-app updates in real time — the opponent's move, your turn, chat, nudges and a
|
in-app updates in real time — the opponent's move, your turn, chat, nudges and a
|
||||||
found match; out-of-app push (your turn, nudge) is delivered by the platform
|
found match; out-of-app push (your turn, nudge) is delivered by the platform
|
||||||
later (Stage 8).
|
later (Stage 9).
|
||||||
|
|
||||||
### Accounts, linking & merge *(Stage 1 / 10)*
|
### Accounts, linking & merge *(Stage 1 / 10)*
|
||||||
First platform contact auto-provisions a durable account. From the profile a
|
First platform contact auto-provisions a durable account. From the profile a
|
||||||
@@ -76,7 +86,7 @@ Edit language (en/ru), display name, timezone, the daily away window and the blo
|
|||||||
toggles, and bind an email by confirm-code: the backend emails a short code that,
|
toggles, and bind an email by confirm-code: the backend emails a short code that,
|
||||||
once entered, attaches the email to the account (an email already confirmed by
|
once entered, attaches the email to the account (an email already confirmed by
|
||||||
another account cannot be taken — that is a merge, a later stage). Linked platform
|
another account cannot be taken — that is a merge, a later stage). Linked platform
|
||||||
accounts and merge arrive in Stage 10.
|
accounts and merge arrive in Stage 11.
|
||||||
|
|
||||||
### History & statistics *(Stage 3)*
|
### History & statistics *(Stage 3)*
|
||||||
Finished games are archived in a dictionary-independent form and exportable to
|
Finished games are archived in a dictionary-independent form and exportable to
|
||||||
@@ -84,6 +94,6 @@ GCG. Statistics (durable accounts only): wins, losses, draws, max points in a
|
|||||||
game, and max points for a single move (the best play, which already includes
|
game, and max points for a single move (the best play, which already includes
|
||||||
every word it formed plus the all-tiles bonus).
|
every word it formed plus the all-tiles bonus).
|
||||||
|
|
||||||
### Administration *(Stage 9)*
|
### Administration *(Stage 10)*
|
||||||
Admin (Basic Auth at the gateway) reviews word complaints, manages dictionary
|
Admin (Basic Auth at the gateway) reviews word complaints, manages dictionary
|
||||||
versions, and inspects users/games.
|
versions, and inspects users/games.
|
||||||
|
|||||||
+13
-3
@@ -8,6 +8,16 @@
|
|||||||
|
|
||||||
## Домены
|
## Домены
|
||||||
|
|
||||||
|
### Клиентское приложение *(Stage 7 / 8)*
|
||||||
|
Веб/приложение-клиент (Svelte + Vite) воплощает эти истории. **Играбельный срез**
|
||||||
|
(Stage 7) покрывает вход (гость или email), лобби «мои игры», старт авто-подбора,
|
||||||
|
игру на доске (постановка фишек перетаскиванием или тапом, пас, обмен, сдача),
|
||||||
|
top-1 подсказку, безлимитную проверку слова с жалобой, чат и nudge в партии,
|
||||||
|
обновления в реальном времени, переключение языка интерфейса (en/ru) и темы и
|
||||||
|
профиль только для чтения. Управление друзьями и блоками, создание дружеских игр
|
||||||
|
(приглашения), редактирование профиля, экран статистики и просмотр истории/GCG
|
||||||
|
появятся в Stage 8.
|
||||||
|
|
||||||
### Личность и сессии *(Stage 1 / 6)*
|
### Личность и сессии *(Stage 1 / 6)*
|
||||||
Игрок приходит с платформы (сначала Telegram), через email-вход или как
|
Игрок приходит с платформы (сначала Telegram), через email-вход или как
|
||||||
эфемерный гость. Gateway один раз валидирует доступ и выдаёт тонкий
|
эфемерный гость. Gateway один раз валидирует доступ и выдаёт тонкий
|
||||||
@@ -16,7 +26,7 @@ session-токен; backend сопоставляет его с внутренн
|
|||||||
статистики и истории). Пока приложение открыто, клиент держит живой стрим и
|
статистики и истории). Пока приложение открыто, клиент держит живой стрим и
|
||||||
получает обновления в реальном времени — ход соперника, ваш ход, чат, nudge и
|
получает обновления в реальном времени — ход соперника, ваш ход, чат, nudge и
|
||||||
найденный матч; внеприложенческий push (ваш ход, nudge) платформа доставит
|
найденный матч; внеприложенческий push (ваш ход, nudge) платформа доставит
|
||||||
позже (Stage 8).
|
позже (Stage 9).
|
||||||
|
|
||||||
### Аккаунты, привязка и слияние *(Stage 1 / 10)*
|
### Аккаунты, привязка и слияние *(Stage 1 / 10)*
|
||||||
Первый контакт с платформы заводит постоянный аккаунт. Из профиля игрок
|
Первый контакт с платформы заводит постоянный аккаунт. Из профиля игрок
|
||||||
@@ -76,7 +86,7 @@ push доставляется через платформу.
|
|||||||
confirm-коду: backend шлёт на почту короткий код, и после ввода email
|
confirm-коду: backend шлёт на почту короткий код, и после ввода email
|
||||||
привязывается к аккаунту (email, уже подтверждённый другим аккаунтом, занять
|
привязывается к аккаунту (email, уже подтверждённый другим аккаунтом, занять
|
||||||
нельзя — это слияние, отдельный этап). Привязанные платформенные аккаунты и
|
нельзя — это слияние, отдельный этап). Привязанные платформенные аккаунты и
|
||||||
слияние появятся в Stage 10.
|
слияние появятся в Stage 11.
|
||||||
|
|
||||||
### История и статистика *(Stage 3)*
|
### История и статистика *(Stage 3)*
|
||||||
Завершённые партии архивируются в независимом от словаря виде и экспортируются
|
Завершённые партии архивируются в независимом от словаря виде и экспортируются
|
||||||
@@ -84,6 +94,6 @@ confirm-коду: backend шлёт на почту короткий код, и
|
|||||||
макс. очков за партию и макс. очков за один ход (лучший ход, уже включающий все
|
макс. очков за партию и макс. очков за один ход (лучший ход, уже включающий все
|
||||||
образованные им слова и бонус за все фишки).
|
образованные им слова и бонус за все фишки).
|
||||||
|
|
||||||
### Администрирование *(Stage 9)*
|
### Администрирование *(Stage 10)*
|
||||||
Админ (Basic Auth на gateway) разбирает жалобы на слова, управляет версиями
|
Админ (Basic Auth на gateway) разбирает жалобы на слова, управляет версиями
|
||||||
словаря, смотрит пользователей/игры.
|
словаря, смотрит пользователей/игры.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// reverse proxy to the backend admin API (docs/ARCHITECTURE.md §12). The gateway
|
// reverse proxy to the backend admin API (docs/ARCHITECTURE.md §12). The gateway
|
||||||
// validates the operator credential and forwards authenticated requests to
|
// validates the operator credential and forwards authenticated requests to
|
||||||
// backend /api/v1/admin/*; the backend trusts the gateway on this segment. The
|
// backend /api/v1/admin/*; the backend trusts the gateway on this segment. The
|
||||||
// admin API itself is filled in Stage 9.
|
// admin API itself is filled in Stage 10.
|
||||||
package admin
|
package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
# scrabble-ui
|
||||||
|
|
||||||
|
Pure-HTML5 game client — **plain Svelte 5 (runes) + TypeScript + Vite**, no
|
||||||
|
SvelteKit. Talks to the `gateway` over **Connect-RPC + FlatBuffers**; embeddable in
|
||||||
|
platform webviews and packageable to native via Capacitor.
|
||||||
|
|
||||||
|
Stage 7 ships the **playable slice**: sign in (guest / email), the "my games" lobby,
|
||||||
|
auto-match, the board (place tiles by drag or tap, pass, exchange, resign), hint,
|
||||||
|
word-check + complaint, per-game chat and nudge, the live in-app stream, i18n (en/ru),
|
||||||
|
theme, and a read-only profile. Friends/blocks, friend-game invitations, profile
|
||||||
|
editing, the stats screen and the history/GCG viewer are Stage 8.
|
||||||
|
|
||||||
|
## Scripts
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pnpm install
|
||||||
|
pnpm start # mock mode (VITE_MOCK): lobby -> game with no backend, :5173
|
||||||
|
pnpm dev # against a running gateway (Vite proxies /scrabble.edge.v1.Gateway -> :8081)
|
||||||
|
pnpm check # svelte-check / tsc
|
||||||
|
pnpm test:unit # Vitest (pure logic + FlatBuffers codec)
|
||||||
|
pnpm test:e2e # Playwright smoke against the mock
|
||||||
|
pnpm build # static bundle into dist/ (prod ~67 KB gzip JS)
|
||||||
|
pnpm codegen # regenerate src/gen from edge.proto + scrabble.fbs (dev-time)
|
||||||
|
```
|
||||||
|
|
||||||
|
`GATEWAY_URL` overrides the dev proxy target; `VITE_GATEWAY_URL` sets the runtime
|
||||||
|
gateway origin for a packaged (non-proxied) build.
|
||||||
|
|
||||||
|
## How it talks to the gateway
|
||||||
|
|
||||||
|
A single Connect `Execute(message_type, payload)` carries every unary op; the request
|
||||||
|
and response bodies are **FlatBuffers** tables (`pkg/fbs/scrabble.fbs`) in `payload`.
|
||||||
|
The session token rides in `Authorization: Bearer`; a domain failure comes back in
|
||||||
|
`result_code`. `Subscribe` is the live event stream. `lib/transport.ts` is the real
|
||||||
|
client; `lib/mock/` is an in-memory fake selected by `MODE === 'mock'` (and tree-shaken
|
||||||
|
out of production). Both speak the plain `lib/model.ts` types via `lib/codec.ts`.
|
||||||
|
|
||||||
|
**No board on the wire:** `StateView` is a summary + rack only, so the client
|
||||||
|
reconstructs the 15×15 board by replaying the decoded move journal (`game.history`).
|
||||||
|
Premium squares and tile values (`lib/premiums.ts`) are a client-side map **ported from
|
||||||
|
`scrabble-solver/rules/rules.go`** (pinned by a Vitest parity test). Board, tiles and
|
||||||
|
effects are pure CSS + Unicode — no image/font/SVG assets.
|
||||||
|
|
||||||
|
## Codegen
|
||||||
|
|
||||||
|
`src/gen/` is **committed**; CI builds it, it is not regenerated there (the same model
|
||||||
|
as the Go committed jet/fbs output). `pnpm codegen` runs `flatc --ts` on
|
||||||
|
`../pkg/fbs/scrabble.fbs` and `buf generate` (`protoc-gen-es`) on the edge proto. Needs
|
||||||
|
`flatc` 23.5.26 and `buf` on PATH.
|
||||||
|
|
||||||
|
## Theming
|
||||||
|
|
||||||
|
Design tokens are CSS custom properties (`src/app.css`); light/dark follows
|
||||||
|
`prefers-color-scheme` or an explicit choice in Settings. The token system is
|
||||||
|
**Telegram-themeParams-ready** (`lib/theme.ts`) — a Mini App can override the tokens at
|
||||||
|
runtime; the Telegram SDK itself is wired in the Telegram stage.
|
||||||
|
|
||||||
|
## Layout
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
lib/ model, client facade, transport (+ mock), codec, board replay,
|
||||||
|
placement state machine, premiums, i18n, theme, session, router, app store
|
||||||
|
components/ Header, Modal, Toast
|
||||||
|
screens/ Login, Lobby, NewGame, Profile, Settings, About
|
||||||
|
game/ Game, Board, Rack, Controls, MakeMove, Chat
|
||||||
|
gen/ committed edge codegen (FlatBuffers + Connect)
|
||||||
|
e2e/ Playwright smoke (mock)
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user