native polish: guest-locale banner, seamless guest sign-in (+local-game fix), 'Opponent's turn' label, deploy-authoritative dictionary #273

Merged
developer merged 5 commits from feature/native-polish-and-dict-deploy into development 2026-07-14 23:07:05 +00:00
Owner

Four fixes from the Android on-device test round (owner-approved directions).

A — banner locale (fix(account))

A fresh guest was provisioned with only its time zone; PreferredLanguage fell to the en column default, so a Russian user's brand-new native guest saw English language-dependent server content (the ad banner, bot messages) until the client's later language reconcile. The client already sends its locale in the guest login (GuestLoginRequest.locale) but the gateway dropped it — now read it, thread it through GuestAuthProvisionGuest, and seed the language (validated). No wire change.

B — seamless guest sign-in + the merged-away local game (feat(link))

A guest linking an identity that already has a durable account was shown an irreversible 'merge two accounts?' prompt — nonsense from the user's side (they are just signing into their account). The confirm step now merges inline for a guest initiator and returns the switched session (a durable initiator still confirms — consolidating two real accounts is consequential). The active-game guard still refuses, surfaced as a clear error.merge_active_game_conflict message.

Also fixes bug 3: the guest's device-local games (vs_ai / hotseat) were seated under the retired account id, so after the switch the lobby/game-header could not identify "me" and showed every seat as an opponent (the game still played — turn logic is seat-index based). applyLinkResult now re-points local game seats to the surviving account.

C — 'Opponent's turn' label (feat(game))

The under-board turn strip now reads the generic 'Opponent's turn' for online PvP and vs_ai instead of the opponent's name / robot mark (hotseat still names the seat to move; the seat row still names players).

D — deploy-authoritative dictionary (feat(dict))

Root-caused the native dict-preload splash: the dictionary lives on a persistent volume seeded from the image once and never re-seeded, so a redeploy that bumped DICT_VERSION left the new DAWGs unreachable (the mount shadows the image copy) and new games stuck on the last console-installed version (prod = v1.3.0, bundle = v1.3.1) — the native app then fetched the server's older pinned dict over the network. On boot the backend now delivers the build version onto the volume add-only (from an unshadowed BACKEND_DICT_SEED_DIR) and activates it — old games keep theirs; a console version newer than the build is not downgraded. So a bump+redeploy goes live for new games with no admin step.

After merge + promote + prod deploy: prod moves to v1.3.1 (matching the bundle) → new games no longer splash; the current v1.3.0 game keeps its pinned version (a one-time fetch, by design).

Verified locally: Go unit + integration (incl. the auto-merge, the active-game conflict, the deploy-delivers path, the guest language), UI svelte-check + vitest, e2e 252/252 (chromium+webkit), UI build.

Four fixes from the Android on-device test round (owner-approved directions). ## A — banner locale (`fix(account)`) A fresh guest was provisioned with only its time zone; `PreferredLanguage` fell to the `en` column default, so a Russian user's brand-new native guest saw **English** language-dependent server content (the ad banner, bot messages) until the client's later language reconcile. The client already sends its locale in the guest login (`GuestLoginRequest.locale`) but the gateway dropped it — now read it, thread it through `GuestAuth` → `ProvisionGuest`, and seed the language (validated). No wire change. ## B — seamless guest sign-in + the merged-away local game (`feat(link)`) A guest linking an identity that already has a durable account was shown an **irreversible 'merge two accounts?'** prompt — nonsense from the user's side (they are just signing into their account). The confirm step now **merges inline for a guest initiator** and returns the switched session (a **durable** initiator still confirms — consolidating two real accounts is consequential). The active-game guard still refuses, surfaced as a clear `error.merge_active_game_conflict` message. Also fixes **bug 3**: the guest's device-local games (vs_ai / hotseat) were seated under the retired account id, so after the switch the lobby/game-header could not identify "me" and showed **every seat as an opponent** (the game still played — turn logic is seat-index based). `applyLinkResult` now re-points local game seats to the surviving account. ## C — 'Opponent's turn' label (`feat(game)`) The under-board turn strip now reads the generic **'Opponent's turn'** for online PvP and vs_ai instead of the opponent's name / robot mark (hotseat still names the seat to move; the seat row still names players). ## D — deploy-authoritative dictionary (`feat(dict)`) Root-caused the native dict-preload splash: the dictionary lives on a persistent volume seeded from the image **once and never re-seeded**, so a redeploy that bumped `DICT_VERSION` left the new DAWGs unreachable (the mount shadows the image copy) and new games stuck on the last **console-installed** version (prod = v1.3.0, bundle = v1.3.1) — the native app then fetched the server's older pinned dict over the network. On boot the backend now **delivers** the build version onto the volume add-only (from an unshadowed `BACKEND_DICT_SEED_DIR`) and **activates** it — old games keep theirs; a console version newer than the build is not downgraded. So a bump+redeploy goes live for new games with no admin step. **After merge + promote + prod deploy:** prod moves to v1.3.1 (matching the bundle) → new games no longer splash; the current v1.3.0 game keeps its pinned version (a one-time fetch, by design). Verified locally: Go unit + integration (incl. the auto-merge, the active-game conflict, the deploy-delivers path, the guest language), UI svelte-check + vitest, e2e 252/252 (chromium+webkit), UI build.
developer added 4 commits 2026-07-14 22:37:22 +00:00
A guest was provisioned with only its detected time zone; its PreferredLanguage
fell to the 'en' column default. The client already sends its locale in the guest
login (GuestLoginRequest.locale), but the gateway dropped it and ProvisionGuest
took no language — so a Russian user's brand-new native guest got English
language-dependent server content (the ad banner, bot messages) until the client's
later language reconcile. Read the locale in the gateway guest handler, thread it
through GuestAuth -> ProvisionGuest, and seed PreferredLanguage from it (validated;
an unsupported/absent value keeps the 'en' default). Wire field already present —
no schema change.
The dictionary lives on a persistent volume seeded from the image once and never
re-seeded, so a redeploy that bumped BACKEND_DICT_VERSION left the new DAWGs
unreachable (the volume mount shadows the image copy) and the active version stuck
at whatever the admin console last installed — a native offline-first client then
fetched the server's older pinned version over the network instead of using its
bundled dict.

On boot the backend now DELIVERS the build version onto the volume add-only, from a
second unshadowed image copy at BACKEND_DICT_SEED_DIR, into DICT_DIR/<version>/ when
absent (the flat seed and prior uploads untouched, so in-flight games keep theirs),
and InitActiveVersion makes the build version active — except a console-installed
version NEWER than the build (compared numerically) is not downgraded by a restart.
The .seed_version guard still protects the flat seed's label (a bump is a new
subdirectory, never a relabel). The console stays for out-of-band updates.

Docs: ARCHITECTURE.md §5, deploy/README.md, seedmarker.go. Tests: engine.DeliverVersion
(add-only, idempotent, flat-seed skip), compareDictVersions, and the dictionary-update
integration test's deploy-delivers path.
When a guest links an identity (email/TG/VK) that already belongs to a durable account,
the guest-primary rule already made the durable account the survivor and switched the
session — but the client still showed an irreversible 'merge two accounts?' confirmation,
which is nonsense from the user's side (they are simply signing into their account). The
confirm step now merges inline for a GUEST initiator and returns the completed merge (the
switched token); a durable initiator still gets the explicit confirmation (consolidating
two real accounts is consequential). The active-game guard still refuses, surfaced as the
clear error.merge_active_game_conflict message rather than swallowed.

Also fixes the merged-away device-local games: their human/host seat was recorded under
the retired account id, so after the switch the lobby and game header could not identify
'me' and showed every seat as an opponent (the game still played — turn logic is
seat-index based). applyLinkResult now re-points local game seats from the retired id to
the survivor (repointLocalGameSeats).

Docs: FUNCTIONAL.md (+_ru).
feat(game): show 'Opponent's turn' instead of the opponent's name in the turn strip
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 12s
CI / integration (pull_request) Successful in 21s
CI / ui (pull_request) Successful in 1m15s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m57s
513b772cd4
When it is not the viewer's turn in an online PvP or vs_ai game, the under-board status
now reads the generic 'Opponent's turn' (game.opponentsTurn) rather than the opponent's
display name or the robot mark, so whose turn it is reads uniformly. The hotseat branch
still names the seat to move (whose device-turn it is matters there), and the seat row
below continues to name each player.
developer added 1 commit 2026-07-14 22:56:52 +00:00
chore(ui): change hints symbol for backward unicode compatibility
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Successful in 11s
CI / integration (pull_request) Successful in 23s
CI / ui (pull_request) Successful in 1m15s
CI / conformance (pull_request) Successful in 11s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m49s
1d306ec0fc
owner approved these changes 2026-07-14 23:06:31 +00:00
developer merged commit 8758cbd35d into development 2026-07-14 23:07:05 +00:00
developer deleted branch feature/native-polish-and-dict-deploy 2026-07-14 23:07:05 +00:00
Sign in to join this conversation.
No Reviewers
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: developer/scrabble-game#273