feat(variants): default a registered account to Erudit + Russian Scrabble
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Successful in 11s
CI / integration (pull_request) Successful in 22s
CI / ui (pull_request) Successful in 1m16s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m53s
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Successful in 11s
CI / integration (pull_request) Successful in 22s
CI / ui (pull_request) Successful in 1m16s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m53s
New Game opened on a single variant for everyone, which reads poorly on VK where Russian Scrabble is the familiar game. A registered account now starts with both Russian-alphabet games, so the picker offers a real choice with no pre-selection. A guest stays on Erudit alone and is invited to register for the rest: the device-local guest has no profile at all, so the client-side fallback has to keep matching the server. Registering promotes the set, but only while the guest still carries the untouched guest default. Existing accounts are not backfilled — the set they carry may be a deliberate choice. The Telegram promo start-param becomes additive rather than a replacement, with English Scrabble withheld from a Russian-speaking arrival; otherwise the English campaign link would have taken Russian Scrabble away from every account it onboarded.
This commit is contained in:
@@ -83,6 +83,11 @@ 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',
|
||||
'new.guestVariantHint': ' to play the Russian or English Scrabble.',
|
||||
|
||||
// First-run coachmark hints (components/Coachmark.svelte). The leading emoji in each comment
|
||||
// names the UI element the bubble's tail points at.
|
||||
|
||||
@@ -84,6 +84,8 @@ export const ru: Record<MessageKey, string> = {
|
||||
'new.moveLimit': 'Время на ход: {n} ч. 00 мин.',
|
||||
'new.searchHint':
|
||||
'Иногда поиск соперника может занять некоторое время. Если не захотите ждать после начала игры, можете вернуться в приложение через несколько минут.',
|
||||
'new.guestVariantHintLink': 'Зарегистрируйтесь',
|
||||
'new.guestVariantHint': ', чтобы играть в русский или английский вариант Скрэббл.',
|
||||
|
||||
// Подсказки первого запуска (components/Coachmark.svelte).
|
||||
'onboarding.lobbySettings': 'Друзья, настройки игры и профиля, обратная связь', // ⚙️
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
ALL_VARIANTS,
|
||||
DEFAULT_VARIANTS,
|
||||
availableVariants,
|
||||
showRegisterHint,
|
||||
supportsMultipleWordsToggle,
|
||||
multipleWordsForRequest,
|
||||
usesStarBlank,
|
||||
@@ -38,6 +39,18 @@ describe('availableVariants', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('showRegisterHint', () => {
|
||||
it('invites only an online guest holding a single variant', () => {
|
||||
expect(showRegisterHint(true, 1, false)).toBe(true);
|
||||
});
|
||||
|
||||
it('stays hidden for a registered player, offline, or when several variants are offered', () => {
|
||||
expect(showRegisterHint(false, 1, false)).toBe(false);
|
||||
expect(showRegisterHint(true, 1, true)).toBe(false);
|
||||
expect(showRegisterHint(true, 2, false)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('supportsMultipleWordsToggle', () => {
|
||||
it('is true for Russian variants only', () => {
|
||||
expect(supportsMultipleWordsToggle('scrabble_ru')).toBe(true);
|
||||
|
||||
+15
-6
@@ -73,23 +73,32 @@ export function usesStarBlank(v: Variant): boolean {
|
||||
|
||||
// DEFAULT_VARIANTS is the variant set a player sees before any preference is stored — a fresh
|
||||
// or offline native client whose profile has not been synced yet (the device-local guest boots
|
||||
// with no profile at all). It mirrors the backend's new-account default (the account service
|
||||
// seeds Erudit only), so the local guest and a later synced account agree on what is enabled.
|
||||
// Every dictionary is still bundled; the other variants are simply off until the player turns
|
||||
// them on in Settings.
|
||||
// with no profile at all). Such a client is a guest, so it mirrors the backend's *guest* set
|
||||
// (account.GuestVariantPreferences, Erudit alone) rather than the wider set a registered account
|
||||
// is created with. Every dictionary is still bundled; the other variants are simply off until
|
||||
// the player registers and turns them on in Settings.
|
||||
export const DEFAULT_VARIANTS: Variant[] = ['erudit_ru'];
|
||||
|
||||
// availableVariants gates ALL_VARIANTS by the player's variant preferences (the set they
|
||||
// enabled in Settings). An empty or absent set falls back to DEFAULT_VARIANTS rather than every
|
||||
// variant: a real profile always carries at least one preference, and a profileless client (a
|
||||
// fresh offline native launch) must match the server's Erudit-only default instead of exposing
|
||||
// the English game before the player opts in.
|
||||
// fresh offline native launch) must match the server's guest default instead of exposing
|
||||
// the other games before the player opts in.
|
||||
export function availableVariants(preferences: Variant[] | undefined): VariantOption[] {
|
||||
const prefs = preferences ?? [];
|
||||
const effective = prefs.length === 0 ? DEFAULT_VARIANTS : prefs;
|
||||
return ALL_VARIANTS.filter((v) => effective.includes(v.id));
|
||||
}
|
||||
|
||||
// showRegisterHint reports whether New Game invites the player to register 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.
|
||||
export function showRegisterHint(isGuest: boolean, offered: number, offline: boolean): boolean {
|
||||
return isGuest && !offline && offered === 1;
|
||||
}
|
||||
|
||||
// supportsMultipleWordsToggle reports whether the New Game "multiple words per turn" toggle
|
||||
// applies to a variant. Only Russian games choose the rule; English is always standard, so
|
||||
// its toggle is not shown.
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
import type { AccountRef, GameView, Variant } from '../lib/model';
|
||||
import {
|
||||
availableVariants,
|
||||
showRegisterHint,
|
||||
VARIANT_FLAG,
|
||||
VARIANT_RULES,
|
||||
supportsMultipleWordsToggle,
|
||||
@@ -395,6 +396,13 @@
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
<!-- A guest is offered Erudit alone; registering widens the account to both Russian games
|
||||
(and Settings then offers English), so point them at the profile screen. -->
|
||||
{#if showRegisterHint(guest, variants.length, offlineMode.active)}
|
||||
<p class="guesthint">
|
||||
<button class="hintlink" onclick={() => navigate('/profile')}>{t('new.guestVariantHintLink')}</button>{t('new.guestVariantHint')}
|
||||
</p>
|
||||
{/if}
|
||||
{#if variants.some((v) => supportsMultipleWordsToggle(v.id))}
|
||||
<label class="toggle">
|
||||
<span>{t('new.multipleWordsPerTurn')}</span>
|
||||
@@ -771,6 +779,24 @@
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
/* The guest's register invitation: a muted line opening with an inline link-styled button
|
||||
that routes to the profile screen (mirrors Profile's .linklike). */
|
||||
.guesthint {
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.hintlink {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
color: var(--accent);
|
||||
font: inherit;
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
/* The offline-blocker reason (a missing dictionary, or the online segment needing a connection). */
|
||||
.dictnote {
|
||||
margin: 0;
|
||||
|
||||
Reference in New Issue
Block a user