From c3d36dda2d64cfc6b777d99eab426e74b596cd19 Mon Sep 17 00:00:00 2001 From: Ilia Denisov Date: Mon, 27 Jul 2026 20:45:10 +0200 Subject: [PATCH] fix(ui): use the guest stubs' "sign in" wording, and link it in Stats The New Game hint said "Register" where the Stats and Profile guest stubs say "Sign in". Move the shared word into common.signInLink so the two screens cannot drift, and turn the Stats stub's opening word into the same link to the profile screen. --- backend/internal/account/link.go | 2 +- docs/ARCHITECTURE.md | 2 +- docs/FUNCTIONAL.md | 3 ++- docs/FUNCTIONAL_ru.md | 4 ++-- ui/src/lib/i18n/en.ts | 13 ++++++++----- ui/src/lib/i18n/ru.ts | 4 ++-- ui/src/lib/variants.ts | 6 +++--- ui/src/screens/NewGame.svelte | 7 ++++--- ui/src/screens/Stats.svelte | 17 ++++++++++++++++- 9 files changed, 39 insertions(+), 19 deletions(-) diff --git a/backend/internal/account/link.go b/backend/internal/account/link.go index f711aad..7de9f55 100644 --- a/backend/internal/account/link.go +++ b/backend/internal/account/link.go @@ -285,7 +285,7 @@ func (s *Store) AttachIdentity(ctx context.Context, accountID uuid.UUID, kind, e // for an already-durable account. // // The promotion also widens the guest's narrow variant set (GuestVariantPreferences, -// Эрудит alone) to the registered default, so a player who registers from the New Game +// Эрудит alone) to the registered default, so a player who signs in from the New Game // hint really does gain Russian Scrabble. A guest who changed the set themselves is left // alone: only the untouched guest default is replaced. func (s *Store) ClearGuest(ctx context.Context, accountID uuid.UUID) error { diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 72be347..282de0a 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -249,7 +249,7 @@ arrive from a platform rather than completing a mandatory registration). **registered** account defaults to the two Russian-alphabet games — **Erudit + Russian Scrabble** (a DB column default); a **guest** is deliberately narrower and starts on **Erudit only** (`account.ProvisionGuest` writes the set explicitly), with New Game inviting - the guest to register for the rest. Registering promotes the set to the registered default + the guest to sign in for the rest. Signing in promotes the set to the registered default (`account.ClearGuest`), but only while the guest still carries the untouched guest set — a player who chose their own set is never overruled, and **existing accounts are not backfilled** when the default moves. At least one variant must stay selected. The picker is diff --git a/docs/FUNCTIONAL.md b/docs/FUNCTIONAL.md index 896a171..87a83f1 100644 --- a/docs/FUNCTIONAL.md +++ b/docs/FUNCTIONAL.md @@ -455,7 +455,8 @@ variants — Erudite, Russian Scrabble and English Scrabble, shown **Erudite-fir yourself to be matched into; a **newly registered account starts with Erudite + Russian Scrabble**, and arriving through the **promo bot's deep link** also enables **English Scrabble** when your Telegram language is not Russian. A **guest** starts with **Erudite only** and New Game -invites them to register for the rest; registering widens the account to the registered default. +invites them to **sign in** for the rest (the same wording and link as the Stats guest stub, both +routing to Settings → Profile); signing in widens the account to the registered default. Changing the defaults never rewrites accounts that already exist. You must keep **at least one** selected. This list is exactly what **New Game** offers when you start a game (auto-match, an AI game, or a friend invitation you create) — a variant you have not enabled is diff --git a/docs/FUNCTIONAL_ru.md b/docs/FUNCTIONAL_ru.md index 15aae53..1113e0a 100644 --- a/docs/FUNCTIONAL_ru.md +++ b/docs/FUNCTIONAL_ru.md @@ -462,8 +462,8 @@ Telegram. Внутри VK Mini App те же настройки — и язык которые ты разрешаешь себя подбирать; **новый зарегистрированный аккаунт стартует с Эрудитом и русским Scrabble**, а вход по **диплинку промо-бота** дополнительно включает **английский Scrabble**, если язык Telegram не русский. **Гость** стартует **только с Эрудитом**, и «Новая игра» -предлагает ему зарегистрироваться ради остальных; регистрация расширяет аккаунт до набора -зарегистрированного. Смена набора по умолчанию никогда не переписывает уже существующие аккаунты. +предлагает ему **войти** ради остальных (та же формулировка и ссылка, что и у гостевой заглушки в +статистике, обе ведут в Settings → Profile); вход расширяет аккаунт до набора зарегистрированного. Смена набора по умолчанию никогда не переписывает уже существующие аккаунты. Нужно оставить выбранным **хотя бы один**. Именно этот список предлагает **Новая игра**, когда ты запускаешь партию (авто-подбор, игра с ИИ или приглашение друга, которое ты создаёшь), — не включённый вариант не предлагается, и сервер его отклоняет. На партии, в которые тебя diff --git a/ui/src/lib/i18n/en.ts b/ui/src/lib/i18n/en.ts index 7f5125c..2b37159 100644 --- a/ui/src/lib/i18n/en.ts +++ b/ui/src/lib/i18n/en.ts @@ -41,6 +41,10 @@ export const en = { 'common.you': 'You', 'common.guest': 'Guest', 'common.save': 'Save', + // The opening word of a guest's invitation to sign in, rendered as a link to the profile + // screen (New Game, Stats). Shared so the two screens cannot drift apart; each screen keeps + // its own tail, which carries its leading space — Svelte trims literal edge whitespace. + 'common.signInLink': 'Sign in', 'login.title': 'Sign in', 'login.guest': 'Play as guest', @@ -83,10 +87,8 @@ export const en = { 'new.moveLimit': 'Move time: {n} h 00 min', 'new.searchHint': 'Finding an opponent can sometimes take a while. If you do not want to wait, close the app after starting the game and come back in a couple of minutes.', - // The guest's invitation to register, shown under the single variant a guest is offered. It is - // two keys because the first word is a link to the profile screen; the tail carries its own - // leading space, since Svelte trims literal whitespace at the edges of markup. - 'new.guestVariantHintLink': 'Register', + // The tail of the guest's invitation to sign in (common.signInLink opens it), shown under the + // single variant a guest is offered. 'new.guestVariantHint': ' to play the Russian or English Scrabble.', // First-run coachmark hints (components/Coachmark.svelte). The leading emoji in each comment @@ -420,7 +422,8 @@ export const en = { 'stats.winRate': 'Win rate', 'stats.maxGame': 'Best game', 'stats.maxWord': 'Best move', - 'stats.guestHint': 'Sign in to track your statistics.', + // The tail of the guest's invitation to sign in (common.signInLink opens it). + 'stats.guestHint': ' to track your statistics.', 'game.exportGcg': 'Export game', 'game.exportChoice': 'Export the finished game as:', diff --git a/ui/src/lib/i18n/ru.ts b/ui/src/lib/i18n/ru.ts index d528c4d..c698eee 100644 --- a/ui/src/lib/i18n/ru.ts +++ b/ui/src/lib/i18n/ru.ts @@ -42,6 +42,7 @@ export const ru: Record = { 'common.you': 'Вы', 'common.guest': 'Гость', 'common.save': 'Сохранить', + 'common.signInLink': 'Войдите', 'login.title': 'Вход', 'login.guest': 'Играть как гость', @@ -84,7 +85,6 @@ export const ru: Record = { 'new.moveLimit': 'Время на ход: {n} ч. 00 мин.', 'new.searchHint': 'Иногда поиск соперника может занять некоторое время. Если не захотите ждать после начала игры, можете вернуться в приложение через несколько минут.', - 'new.guestVariantHintLink': 'Зарегистрируйтесь', 'new.guestVariantHint': ', чтобы играть в русский или английский вариант Скрэббл.', // Подсказки первого запуска (components/Coachmark.svelte). @@ -417,7 +417,7 @@ export const ru: Record = { 'stats.winRate': 'Доля побед', 'stats.maxGame': 'Лучшая игра', 'stats.maxWord': 'Лучший ход', - 'stats.guestHint': 'Войдите, чтобы вести статистику.', + 'stats.guestHint': ', чтобы вести статистику.', 'game.exportGcg': 'Экспорт партии', 'game.exportChoice': 'Экспортировать завершённую партию как:', diff --git a/ui/src/lib/variants.ts b/ui/src/lib/variants.ts index 475d37f..84246e0 100644 --- a/ui/src/lib/variants.ts +++ b/ui/src/lib/variants.ts @@ -90,11 +90,11 @@ export function availableVariants(preferences: Variant[] | undefined): VariantOp return ALL_VARIANTS.filter((v) => effective.includes(v.id)); } -// showRegisterHint reports whether New Game invites the player to register in order to unlock the +// showRegisterHint reports whether New Game invites the player to sign in in order to unlock the // other games, given whether they are a guest (isGuest), how many variants they are offered // (offered) and whether the app is in offline mode (offline). It is the guest's case alone: a -// guest is created with Erudit only, while registering widens the account to both Russian-alphabet -// games. Offline it is suppressed — registration needs the network, so the link would be dead. +// guest is created with Erudit only, while signing in widens the account to both Russian-alphabet +// games. Offline it is suppressed — signing in needs the network, so the link would be dead. export function showRegisterHint(isGuest: boolean, offered: number, offline: boolean): boolean { return isGuest && !offline && offered === 1; } diff --git a/ui/src/screens/NewGame.svelte b/ui/src/screens/NewGame.svelte index e5c856f..624307a 100644 --- a/ui/src/screens/NewGame.svelte +++ b/ui/src/screens/NewGame.svelte @@ -396,11 +396,12 @@ {/each} - + {#if showRegisterHint(guest, variants.length, offlineMode.active)}

- {t('new.guestVariantHint')} + {t('new.guestVariantHint')}

{/if} {#if variants.some((v) => supportsMultipleWordsToggle(v.id))} diff --git a/ui/src/screens/Stats.svelte b/ui/src/screens/Stats.svelte index 56f9672..e8bb123 100644 --- a/ui/src/screens/Stats.svelte +++ b/ui/src/screens/Stats.svelte @@ -4,6 +4,7 @@ import WordTiles from '../components/WordTiles.svelte'; import { app, handleError } from '../lib/app.svelte'; import { gateway } from '../lib/gateway'; + import { navigate } from '../lib/router.svelte'; import { t, i18n, type MessageKey } from '../lib/i18n/index.svelte'; import { gamesPlayed, hintSharePercent, winRate } from '../lib/stats'; import { ALL_VARIANTS, variantNameKey } from '../lib/variants'; @@ -56,7 +57,11 @@
{#if app.profile?.isGuest} -

{t('stats.guestHint')}

+ +

+ {t('stats.guestHint')} +

{:else if stats}
{#each cards as c (c.key)} @@ -89,6 +94,16 @@ .muted { color: var(--text-muted); } + /* The inline link-styled button opening the guest stub (mirrors New Game's .hintlink). */ + .hintlink { + background: none; + border: none; + padding: 0; + color: var(--accent); + font: inherit; + text-decoration: underline; + cursor: pointer; + } .grid { display: grid; grid-template-columns: repeat(2, 1fr);