From 5bc2ad3b6d4b1e6cf5edca2589ae8bff377d247b Mon Sep 17 00:00:00 2001 From: Ilia Denisov Date: Mon, 6 Jul 2026 17:49:53 +0200 Subject: [PATCH] feat(offline): auto-detect no network at cold start + 'go offline?' dialog 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). --- ui/scripts/bundle-size.mjs | 12 +++---- ui/src/App.svelte | 60 ++++++++++++++++++++++++++++++++- ui/src/lib/app.svelte.ts | 58 +++++++++++++++++++++++++++++-- ui/src/lib/connection.svelte.ts | 20 +++++++++++ ui/src/lib/i18n/en.ts | 3 ++ ui/src/lib/i18n/ru.ts | 3 ++ ui/src/lib/offline.svelte.ts | 11 ++++-- 7 files changed, 155 insertions(+), 12 deletions(-) diff --git a/ui/scripts/bundle-size.mjs b/ui/scripts/bundle-size.mjs index 2cf27c9..75f7f9e 100644 --- a/ui/scripts/bundle-size.mjs +++ b/ui/scripts/bundle-size.mjs @@ -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. diff --git a/ui/src/App.svelte b/ui/src/App.svelte index 39bd17e..734fd81 100644 --- a/ui/src/App.svelte +++ b/ui/src/App.svelte @@ -1,7 +1,7 @@