feat(offline): advertise dict versions + background dictionary preload #196

Merged
developer merged 2 commits from feature/offline-dict-preload into development 2026-07-06 08:45:41 +00:00
Owner

Part of the PWA offline mode feature (Phase C). Two commits, into development.

What

  1. Profile advertises dict_versions — the current dictionary version per game variant, piggybacked on the existing cold-start profile request (no new round-trip, per the agreed design). Additive, backward-compatible FBS field (DictVersion table + trailing Profile.dict_versions). Wired backend → gateway → client (decoded into a per-variant map on model.Profile) + mock.
  2. Background dictionary preload — an eligible installed PWA (standalone web + confirmed email) warms the enabled variants' dictionaries while online, on lobby entry and on a variant-preference change, using the advertised versions, so a later switch to offline mode already has the data. Retries with linear backoff, honours the session miss-breaker. A first-lobby failure shows a poor-connection notice in the ad-banner slot.

Notes / decisions

  • The move generator, the dict loader and the preload orchestration stay in lazy chunks; only the thin trigger and the offline reactive state live in the app entry.
  • ⚠️ App-entry bundle budget bumped 112 → 113 KB for the irreducible main-side wiring (documented in bundle-size.mjs, matching the existing per-feature bump history: 110 → 112 → 113). Now 112.0/113. Flagging for review — the alternative was runtime-wasteful lazy-loading gymnastics to reclaim ~19 bytes.
  • Deferred to follow-ups (kept this PR focused): C4 toggle-readiness (grey / ≤5 s wait / revert on a flip-to-offline without dicts — pairs naturally with C6); the offline lobby that creates/lists local games (C6, the visible payoff); the FUNCTIONAL offline user-story (lands with C6, matching #195's approach).

Tests

  • Go: build / vet / gofmt; unit (server + transcode); integration (full -tags=integration suite — reproduced and fixed a nil-registry 500 on the profile endpoint for registry-less test servers).
  • UI: check (0 errors); unit (preloadDicts ×5, offlinePreloadEligible, codec round-trip — bite-tested by neutering the decode); e2e (196, Chromium + WebKit); build + bundle gate (112.0 / 113).
Part of the PWA **offline mode** feature (Phase C). Two commits, into `development`. ## What 1. **Profile advertises `dict_versions`** — the current dictionary version per game variant, piggybacked on the existing cold-start profile request (no new round-trip, per the agreed design). Additive, backward-compatible FBS field (`DictVersion` table + trailing `Profile.dict_versions`). Wired backend → gateway → client (decoded into a per-variant map on `model.Profile`) + mock. 2. **Background dictionary preload** — an eligible installed PWA (standalone web + confirmed email) warms the enabled variants' dictionaries while online, on lobby entry and on a variant-preference change, using the advertised versions, so a later switch to offline mode already has the data. Retries with linear backoff, honours the session miss-breaker. A first-lobby failure shows a *poor-connection* notice in the ad-banner slot. ## Notes / decisions - The move generator, the dict loader and the preload orchestration stay in **lazy chunks**; only the thin trigger and the offline reactive state live in the app entry. - ⚠️ **App-entry bundle budget bumped 112 → 113 KB** for the irreducible main-side wiring (documented in `bundle-size.mjs`, matching the existing per-feature bump history: 110 → 112 → 113). Now 112.0/113. Flagging for review — the alternative was runtime-wasteful lazy-loading gymnastics to reclaim ~19 bytes. - **Deferred to follow-ups** (kept this PR focused): C4 toggle-readiness (grey / ≤5 s wait / revert on a flip-to-offline without dicts — pairs naturally with C6); the offline **lobby** that creates/lists local games (C6, the visible payoff); the FUNCTIONAL offline user-story (lands with C6, matching #195's approach). ## Tests - **Go**: build / vet / gofmt; unit (server + transcode); **integration** (full `-tags=integration` suite — reproduced and fixed a nil-registry 500 on the profile endpoint for registry-less test servers). - **UI**: `check` (0 errors); unit (`preloadDicts` ×5, `offlinePreloadEligible`, codec round-trip — bite-tested by neutering the decode); e2e (196, Chromium + WebKit); `build` + bundle gate (112.0 / 113).
developer added 2 commits 2026-07-06 08:41:02 +00:00
The Profile now carries dict_versions (game variant -> current dictionary
version), populated from the dictionary registry at the profileResponse
choke point, so an installed PWA can preload the matching dawg per enabled
variant off the existing cold-start profile request instead of adding a
round-trip for a rare feature.

Wire path: FBS DictVersion table + Profile.dict_versions (additive,
backward-compatible trailing field) -> backend dto/registry -> gateway
ProfileResp + FBS encoder -> client codec decode into a per-variant map on
model.Profile. Empty in a degenerate no-dictionary deployment; the mock
serves v1.3.0 for all three variants. Codec decode covered by a
bite-tested round-trip unit test.
feat(offline): background-preload dictionaries for offline readiness
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 15s
CI / ui (pull_request) Successful in 1m10s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m41s
2f867b8e6c
An installed PWA (standalone web + confirmed email) warms the dictionaries
for the player's enabled variants while online — on lobby entry and on a
variant-preference change — using the per-variant version the profile now
advertises, so a later switch to offline mode already has the data.

- dict/preload.ts: pure preloadDicts (retry + linear backoff, honours the
  session dictionary miss-breaker); node-tested.
- dict/preloadrun.ts: lazy browser orchestration (real getDawg), imported
  dynamically so the loader and move generator stay out of the main bundle.
- offline.svelte.ts: kickDictPreload, gated by the pure, tested
  offlinePreloadEligible, plus the reactive first-lobby preload warning.
- Header shows a poor-connection notice in the ad-banner slot on a
  first-lobby preload failure (offline.preloadWarning, en/ru).
- App-entry bundle budget 112->113 for the irreducible main-side wiring
  (documented in bundle-size.mjs); the heavy parts remain lazy chunks.

Docs: ARCHITECTURE offline-mode + dict-preload mechanism.
owner approved these changes 2026-07-06 08:44:10 +00:00
developer merged commit 8abd8b2ae6 into development 2026-07-06 08:45:41 +00:00
developer deleted branch feature/offline-dict-preload 2026-07-06 08:45:41 +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#196