feat(offline): gate the offline toggle on dictionary readiness
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 1m10s
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Successful in 1s
CI / deploy (pull_request) Successful in 1m44s

Flipping the Settings toggle to offline now checks that every enabled variant's
dictionary is on the device before entering offline mode: it fetches missing ones
cache-first and waits up to ~5 s (raceOfflineReady + the lazy dict/offlineready),
greying the toggle meanwhile. If they cannot be readied in time it stays online and
shows a 'needs internet' note, while the fetch keeps warming the cache in the
background so a later flip is instant. Leaving offline is never gated.

Prevents entering a half-baked offline mode (no dawg -> cannot create/play a local
game) when the background preload has not finished (poor connection, or an immediate
flip right after install).

- offline.ts: raceOfflineReady (pure, injected sleep; unit-tested red->green)
- dict/offlineready.ts: ensureOfflineDicts (cache-first preloadDicts, lazy chunk)
- offline.svelte.ts: requestOffline + TOGGLE_READY_BUDGET_MS
- Settings.svelte: checking/needsData state, disabled toggle, inline note
- i18n: settings.offlineChecking / settings.offlineNeedsData (en+ru)
- docs: FUNCTIONAL(+_ru) offline story + ARCHITECTURE offline paragraph
This commit is contained in:
Ilia Denisov
2026-07-06 20:03:12 +02:00
parent 05c445e4da
commit 30770a759b
10 changed files with 153 additions and 9 deletions
+5 -1
View File
@@ -1259,7 +1259,11 @@ eligible installed PWA (standalone web + confirmed email) **background-preloads*
— on lobby entry and on a variant-preference change — through the same three-tier loader, retried
with backoff and honouring the session miss-breaker; the move generator, the loader and the preload
orchestration stay in lazy chunks. A first-lobby preload failure shows a *poor-connection* notice in
the ad-banner slot. A **cold launch already in offline mode** boots from the persisted session and
the ad-banner slot. Flipping the Settings toggle **to** offline runs that same cache-first fetch
bounded by a ~5 s UI wait (`raceOfflineReady` + the lazy `dict/offlineready`): it enters offline only
once every enabled variant is ready, otherwise it stays online with a *needs internet* note while the
fetch finishes in the background (the next flip is then instant). A **cold launch already in offline
mode** boots from the persisted session and
profile (the profile is saved on every online adopt/refresh) — `bootstrap` skips the session
adoption and profile fetch that would otherwise hang with no network, and lands straight in the
offline lobby; without a cached profile (an install that was never online) it drops the sticky flag
+5 -2
View File
@@ -265,8 +265,11 @@ Only **vs_ai** games are playable — New Game creates a device-local game again
plays entirely in the browser with no backend. Local games are kept on the device, visible only in
offline mode, and never sync to the account. So a game can be created and played with no connection,
the app quietly preloads the dictionaries for the player's enabled variants while still online, and
(once installed) launches from a precached shell even with no network. The mode is device-scoped and
sticky across launches.
(once installed) launches from a precached shell even with no network. Switching **to** offline first
checks that the enabled variants' dictionaries are on the device — if any are missing it fetches them
and waits briefly, and if they cannot be readied it stays online with a short *needs internet* note
(the download keeps running in the background, so a later switch is instant). The mode is
device-scoped and sticky across launches.
The app also **enters offline mode on its own** when it cannot reach the network. On a cold launch
with no connection it switches to offline for that session; when the device is online but the gateway
+5 -2
View File
@@ -270,8 +270,11 @@ e-mail) либо ввод фразы. Активные игры форфейтя
целиком в браузере без бэкенда. Локальные игры хранятся на устройстве, видны только в офлайн-режиме и
никогда не синхронизируются с аккаунтом. Чтобы игру можно было создать и сыграть без связи, приложение
заранее, пока ещё онлайн, подгружает словари включённых игроком вариантов и (после установки)
запускается из прекешированного шелла даже без сети. Режим привязан к устройству и сохраняется между
запусками.
запускается из прекешированного шелла даже без сети. Переключение **в** офлайн сначала проверяет, что
словари включённых вариантов есть на устройстве — если каких-то не хватает, оно их подгружает и недолго
ждёт, а если подготовить их не удаётся, остаётся онлайн с короткой заметкой *нужен интернет* (загрузка
продолжается в фоне, так что следующее переключение мгновенно). Режим привязан к устройству и
сохраняется между запусками.
Приложение также **само включает офлайн-режим**, когда не может достучаться до сети. При холодном
запуске без связи оно переходит в офлайн на текущую сессию; когда устройство онлайн, но шлюз молчит