feat(offline): auto-detect no network at cold start + 'go offline?' dialog
CI / changes (pull_request) Successful in 1s
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 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m44s

A sticky-online cold start with no network hung the splash on adoptSession's
retrying profile fetch. Now, for an offline-capable web install with a cached
profile:
- No network interface (navigator.onLine === false) -> enter offline mode for
  the session (no dialog; the next launch re-evaluates).
- Interface up but the gateway is unreachable within 3s (a single-attempt
  reachability probe, not the 6-retry loop) -> a 'No connection. Enable offline
  mode?' dialog: Enable -> sticky offline; Keep trying -> the normal online
  adopt (retries, 'Connecting...').

- connection.svelte: checkReachable(timeout) - a bounded single probe.
- offline.svelte: setOfflineMode(on, persist) - auto-offline is session-only, a
  deliberate choice (dialog/toggle) is sticky.
- app.svelte.ts: the cold-start auto-detect in bootstrap + the dialog resolver;
  App.svelte renders the boot dialog. i18n en/ru.
- App-entry bundle budget 113->114 (the boot path cannot be lazy-loaded).

Online cold-start unaffected (auto-detect gated to isStandalone, off in the mock
e2e): e2e 196. The offline paths are contour-verified.

Next: PR2 - mid-session flight-mode reactivity (online/offline events).
This commit is contained in:
Ilia Denisov
2026-07-06 17:49:53 +02:00
parent 2a4b0fb25e
commit 5bc2ad3b6d
7 changed files with 155 additions and 12 deletions
+6 -6
View File
@@ -20,12 +20,12 @@ import { join } from 'node:path';
const DIST = 'dist';
// Per-chunk gzip budgets in KB. The app entry was raised to 110 for the local move-preview
// wiring, to 112 for the PWA install feature, then to 113 for the offline-mode wiring: the
// dictionary-preload trigger and the offline-state reactives live in the app entry (the Header
// reads them, the lobby/profile screens fire the trigger), while the heavy parts — the dict
// loader, the move generator and the preload orchestration — stay in lazy chunks. Scoped CSS
// lands in the CSS chunk, not this JS budget.
const BUDGET = { app: 113, shared: 30, landing: 5 };
// wiring, to 112 for the PWA install feature, to 113 for the offline-mode wiring, then to 114 for
// the offline auto-detect: the cold-start reachability check and the "no connection" dialog live in
// the boot path (app.svelte.ts / the App shell), which cannot be lazy-loaded. The heavy parts — the
// dict loader, the move generator and the preload orchestration — still stay in lazy chunks. Scoped
// CSS lands in the CSS chunk, not this JS budget.
const BUDGET = { app: 114, shared: 30, landing: 5 };
// gzipped returns the gzipped byte size of a built asset, or 0 when the reference is not a
// local file (e.g. the Telegram SDK loaded from a CDN) or is missing.