Stage 8: UI social/account/history surfaces
Tests · Go / test (push) Successful in 7s
Tests · Integration / integration (push) Successful in 13s
Tests · UI / test (push) Successful in 16s

Wire the deferred Stage 7 surfaces end-to-end (UI -> gateway transcode ->
backend REST -> existing domain services): friends (incl. one-time friend
codes), per-user blocks, friend-game invitations, profile editing + email
binding, the statistics screen, and the in-game history + GCG export.

Friends gain two add paths (interview decision, a deliberate plan change):
one-time 6-digit codes (friend_codes table, 12h TTL, single-use, rate-limited
redeem); and play-gated requests (shared game required) where an explicit
decline is permanent, an ignored request lapses after 30 days, and a code
bypasses a decline. Migration 00006 widens friendships_status_chk and adds
friend_codes.

Lobby notification badge is poll + push: a new generic `notify` event drives
it live; the client polls on open/focus. Language stays a single Settings
control that writes through to the durable account's preferred_language. GCG
export is finished-only (game.ErrGameActive) and shares/downloads the .gcg file.

Tests: backend unit + inttest (friend gate/decline/code, ListInvitations,
GetStats, GCG gate), gateway transcode round-trips + notify constructor, UI
vitest (codecs, win-rate, share choice) + Playwright social specs. Docs: PLAN
(Stage 8 done + refinements + TODO-5), ARCHITECTURE, FUNCTIONAL(+ru), UI_DESIGN,
TESTING, module READMEs.
This commit is contained in:
Ilia Denisov
2026-06-03 19:47:40 +02:00
parent 539e24fba1
commit d733ce3119
114 changed files with 8210 additions and 149 deletions
+52 -1
View File
@@ -41,7 +41,7 @@ independent (see ARCHITECTURE §9.1).
| 5 | Robot opponent | **done** |
| 6 | Gateway edge (Connect/FB, platform auth, sessions, push bridge, admin) | **done** |
| 7 | UI — playable slice + UX polish (Svelte+Vite, board, lobby, chat, hint/word-check, i18n) | **done** |
| 8 | UI — social/account/history (friends, blocks, invitations, profile edit, stats, history/GCG) | todo |
| 8 | UI — social/account/history (friends, blocks, invitations, profile edit, stats, history/GCG) | **done** |
| 9 | Telegram integration (bot side-service, deep-link, push) | todo |
| 10 | Admin & dictionary ops (complaint review, version reload) | todo |
| 11 | Account linking & merge | todo |
@@ -538,6 +538,52 @@ Open details: deployment target/host; dashboards; load expectations.
(not a modal); word-check is alphabet/length-limited, cached and throttled. Design
details live in the new [`docs/UI_DESIGN.md`](docs/UI_DESIGN.md).
- **Stage 8** (interview + implementation):
- **Scope = vertical slice continued**: the social/account/history operations were
opened end-to-end (UI → gateway transcode → backend REST → existing domain
services). The only new backend logic is `lobby.ListInvitations`,
`account.Store.GetStats`, a `game.SharedGame` seam (self-join on `game_players`),
the friend-code mechanism, and the friendships `declined`-status change.
- **Friends — two add paths** (interview, a deliberate plan change): **one-time
friend codes** (the player to be added issues a **6-digit numeric** code, 12 h TTL,
SHA-256-hashed like email codes, single active per issuer, single-use, redeem
rate-limited) and a **play-gated request** (`SendFriendRequest` now requires a
shared game — active or finished). An explicit **decline is permanent** (blocks
re-send), an **ignored request lazily expires after 30 days** and may be re-sent,
and a **code from the same person bypasses a prior decline**. This **supersedes
Stage 4's** "declining/cancelling deletes the row" (cancel by the requester still
deletes; decline now sets `status='declined'`). Migration **00006** widens
`friendships_status_chk` and adds **`friend_codes`** (jetgen regen). No public ID
or name search — discovery is codes + befriend-an-opponent.
- **Badges = poll + push** (interview): a new generic **`notify`** push event
(`notify.KindNotification`, sub-kinds friend_request/friend_added/invitation/
game_started) drives the lobby hamburger + "Friends" badge; emitted on friend-
request and invitation create and on the invitation's game start. The client polls
incoming requests + open invitations on lobby open and on focus (a missed push
while hidden), and re-polls on the `notify` event. Cursor-resume stays deferred
(single-instance MVP, §10).
- **Language single-control** (interview): the Settings language control writes
through to the durable account's `preferred_language` (`profile.update`); guests
keep only the client preference. Seeding the language from the platform/client on
first provider login is a **Stage 9** forward-note.
- **Guests = durable-only** (interview): friends/blocks/invitations/statistics and
history management are durable-account-only; a guest sees a sign-in prompt.
Binding an email to an existing guest (account linking) stays **Stage 11**.
- **GCG = finished-only + share** (interview): `game.ExportGCG` refuses an active
game (`game.ErrGameActive`) to avoid leaking the live journal mid-play; the client
exports via the **Web Share API** where available, else a **Blob download**
(`game-<id>.gcg`). Capacitor-native file save lands with the native wrapper.
- **IA = as the mockup** (interview): Friends (friends + blocks) is its own screen
from the lobby menu; Invitations is a lobby section + a "play with friends" mode in
New game; Stats is a lobby tab-bar button; profile editing is on Profile; history +
GCG stay in the game.
- **Wire/codegen**: new fbs tables (friends/blocks/invitations/profile-update/email-
bind/stats/gcg + `NotificationEvent`; `Profile` gained trailing away fields) in
`pkg/fbs`, regenerated to committed Go + TS; ~21 new gateway transcode ops; new
REST handlers under `/api/v1/user/{friends,blocks,invitations,profile,email,stats}`
and `…/games/:id/gcg`. UI grows to ~82 KB gzip JS (budget 100 KB). No CI workflow
change (the Go and UI workflows already cover the new code).
## Deferred TODOs (cross-stage)
- **TODO-1 — publish & version the solver.** Once `scrabble-solver` is stable,
@@ -570,3 +616,8 @@ Open details: deployment target/host; dashboards; load expectations.
value)` table so the UI stops duplicating it, and optionally moving tile exchange to
letter **indices** end-to-end. Caveat (as for the dictionaries, TODO-2): the wire table
must stay pinned to the same `rules.Alphabet` the engine uses, or indices drift.
- **TODO-5 — QR / deep-link friend codes (owner's idea, Stage 8).** The one-time
friend code is entered by hand today. Once the Telegram/native deep-link scheme
exists (Stage 9), wrap a code in a deep link and render it as a QR so a friend can
add you by scanning rather than typing. The code semantics (12 h TTL, single use,
one active per issuer) stay as-is; only the delivery changes.