feat(ui): offline-first native boot + lazy server-guest reconciliation

Native (Capacitor) cold boot with no cached session now enters as a
device-local guest in auto-offline mode and lands straight in the lobby
(never /login), so the app opens and plays local vs_ai / hotseat with the
APK's bundled dictionaries and zero network. When the gateway becomes
reachable, reconcileServerGuest silently mints + adopts a server guest and
clears the auto-offline, lighting up online features. Web / PWA / Telegram /
VK are byte-for-byte unchanged.

- transport: exec gains { silent, allowOffline } so background reconciliation
  bypasses the offline kill switch (like the reachability probe) and never
  raises the terminal update overlay (a too-old client stays a local guest);
  new authGuestSilent on the client interface, the real transport and the mock.
- app: native no-session boot branch; reconcileServerGuest fired at boot, by
  the recovery poll and the online event; the poll routes the session-less
  guest through reconciliation, since checkReachable needs a token it lacks.
- native: initNativeShell tolerates a missing Capacitor bridge.
- e2e: new native.spec.ts (inject window.androidBridge; boot -> offline lobby,
  local vs_ai move, reconcile -> online, hotseat start) + playwright.config
  bundles the dawgs into dist-e2e/dict for the loader's bundled tier.
This commit is contained in:
Ilia Denisov
2026-07-12 18:03:52 +02:00
parent a035edfb54
commit e077258567
9 changed files with 318 additions and 79 deletions
+5 -3
View File
@@ -22,10 +22,12 @@ export default defineConfig({
// /assets/ so the SPA-fallback also boots a subpath like /telegram/. Base only prefixes asset
// URLs, so the minified JS under test is identical to the contour's.
// After the mock build, copy the real per-variant dawgs into the preview output (dist-e2e) so the
// offline spec can play a real local vs_ai game; the files are e2e-only (never committed, never in
// the production build). Source: E2E_DICT_DIR (CI: the fetched release; local: the sibling).
// offline specs can play a real local vs_ai game; the files are e2e-only (never committed, never in
// the production build). Source: E2E_DICT_DIR (CI: the fetched release; local: the sibling). Two
// copies for the two loader tiers: e2edict/<variant>.dawg for the mock's session-gated fetchDict
// (network tier) and dict/<variant>@<version>.dawg for the native offline-first spec's bundled tier.
command:
'pnpm exec vite build --mode mock --base / --outDir dist-e2e --emptyOutDir && node scripts/e2e-dict.mjs && pnpm exec vite preview --outDir dist-e2e --port 4173 --strictPort',
'pnpm exec vite build --mode mock --base / --outDir dist-e2e --emptyOutDir && node scripts/e2e-dict.mjs && DICT_DIR="${E2E_DICT_DIR:-../../scrabble-solver/dawg}" OUT_DIR=dist-e2e node scripts/bundle-dicts.mjs && pnpm exec vite preview --outDir dist-e2e --port 4173 --strictPort',
url: 'http://localhost:4173',
reuseExistingServer: !process.env.CI,
timeout: 120_000,