From 09e05eef18d8552cd98d5ee51a1226e14a25d139 Mon Sep 17 00:00:00 2001 From: Ilia Denisov Date: Sun, 12 Jul 2026 22:07:49 +0200 Subject: [PATCH] docs(offline): stage the offline-model redesign (O1-O7 implementation plan) Append the O1-O7 implementation stages to ANDROID_PLAN.md's "Offline-model redesign" section (exact files, produced/consumed interfaces, acceptance criteria, targeted tests; executed via stage-implementation, TDD from the pure netState reducer in O1). Flag it in Progress as the next actionable work (gated G-step-0), self-contained for a fresh session to resume from. --- ANDROID_PLAN.md | 85 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 84 insertions(+), 1 deletion(-) diff --git a/ANDROID_PLAN.md b/ANDROID_PLAN.md index f1f8798..f025fb4 100644 --- a/ANDROID_PLAN.md +++ b/ANDROID_PLAN.md @@ -173,7 +173,13 @@ Kept current as parts land so a fresh session resumes without re-deriving. Verif `GATEWAY_MIN_CLIENT_VERSION` var + the wire-break bump discipline + the Android build/release runbook — keystore, secrets, dispatch, RuStore upload); `ui/README.md` (the native `VITE_*` build vars). Every referenced test file was confirmed to exist. -- **G — pending.** +- **G — pending; gated on the offline-model redesign (was G-step-0).** The deferred native + local-game-visibility decision grew into an owner-approved cross-cutting change (web + native + a small + additive backend/wire bit): remove the explicit offline toggle → one `netState` machine, unify the lobby, + two-tier version gate. **Designed + staged (O1–O7)** in the "Offline-model redesign" section below. + **➡ Next actionable work: implement O1** (the pure `netState` reducer, test-first) via `stage-implementation`, + then O2–O7; the release chain (PR→development→contour→master→tag→dispatch `android-build`) follows. None of + O1–O7 is started. Branch `feature/android-native`, local/unpushed. Open logistics (not code): **mandatory icon rebrand (future)** — author ONE vector master and generate *every* icon from it (web `favicon.svg` / PWA `icon-*` / maskable, Android adaptive, future iOS). Today @@ -848,6 +854,83 @@ chain (PR → development → contour verify → master → tag → dispatch `an Dropping server vs_ai (kept). TG/VK offline. In-app store-update SDK. The iOS shell (the machine is written to be reusable for it; iOS is not built here). +### Implementation stages + +Executed via `stage-implementation` (interview on every fork, tests at each layer, bake docs). Ordered so each +stage leaves the app working and independently testable. **TDD:** the pure machine (O1) is written test-first. +Standing constraints (from the design): one `netState` machine; hysteresis explicit; **server vs_ai +untouched**; wire changes **additive-only**; **TG/VK inert** (always online); **contour-safe** (both version +vars empty ⇒ dormant). Minimal-diff: keep `connection`/`offline` module paths as thin **derived shims** over +`netState` so the ~14 `offlineMode.active` consumers are not mass-renamed (single source of truth underneath). + +- **O1 — Pure net-state reducer + exhaustive unit tests (test-first).** + - Files — Create: `ui/src/lib/netstate.ts`, `ui/src/lib/netstate.test.ts`. + - Interfaces — Produces: `type NetState = 'online'|'connecting'|'offlineNoNetwork'|'offlineVersionLocked'`; + `type NetEvent` (`callFailed`/`callOk`/`probeOk`/`probeFailed`/`osOffline`/`osOnline`/`versionRejected`/ + `versionRecommended`/`boot`); `reduce(prev, event, cfg) => { state: NetState; effects: Effect[] }` + (effects: `toast`, `startProbe`, `showVersionNotice`, `setNudge`); `cfg` carries `K` + `OFFLINE_DEBOUNCE_MS`. + Consumes: nothing (pure). + - Acceptance: every transition-table row + all 12 edge cases pass as pure `reduce` assertions; the blip vs + sustained hysteresis (a single fail→ok stays `connecting`; K/debounce → `offlineNoNetwork`); `versionRejected` + from **any** state → `offlineVersionLocked`; `versionRecommended` sets the nudge only from `online`. + - Targeted tests: `netstate.test.ts` (vitest, node — pure, no jsdom). + +- **O2 — Runtime store + wire the events; migrate the two modules.** + - Files — Create: `ui/src/lib/netstate.svelte.ts` (the `$state` store running `reduce`, the derived + `offline`/`connecting` getters, the probe timer). Modify: `ui/src/lib/connection.svelte.ts` + + `ui/src/lib/offline.svelte.ts` (become thin shims re-exporting the derived getters), `ui/src/lib/transport.ts` + (emit `callFailed`/`callOk`/`versionRejected`/`versionRecommended`), `ui/src/lib/native.ts` (wire + `@capacitor/network`), `ui/src/lib/app.svelte.ts` (boot event), `ui/package.json` (+`@capacitor/network`). + - Interfaces — Produces: `netState` store `.state`/`.offline`/`.connecting`, `emit(event)`. Consumes: `reduce` + (O1), the existing `registerProbe`. + - Acceptance: airplane-mode → `offlineNoNetwork` → back → `online` via the machine; native uses + `@capacitor/network`, web `navigator`; the derived `offline` matches the old `offlineMode.active` for every + consumer (no visual/behaviour regression). + - Targeted tests: `e2e/offline.spec.ts` still green; `e2e/native.spec.ts` extended (a `$state` store ⇒ e2e, like `update.svelte.ts`). + +- **O3 — Remove the explicit offline toggle + migrate the pref.** + - Files — Modify: `ui/src/screens/Settings.svelte` (delete the `offlineEligible` toggle + `goOffline`), + `ui/src/lib/offline.ts`/`offline.svelte.ts` (drop `requestOffline`/`loadOfflinePref`/`saveOfflinePref`; clear + the stale key on boot), `ui/src/screens/SettingsHub.svelte` (tab-disable from `netState`). + - Acceptance: no toggle in Settings; a seeded stale `offlinePref` boots **online** (nobody stuck); offline + chrome/tab-gating still driven from `netState`. + - Targeted tests: `e2e` — Settings has no toggle; a stale pref does not force offline. + +- **O4 — Two-tier version gate.** + - Files — Modify: `gateway/internal/config/config.go` (+`RecommendedClientVersion`, validate ≥ `MinClientVersion`), + `gateway/internal/connectsrv/server.go` (soft band ⇒ `X-Update-Recommended: 1` response header; hard band + unchanged), `gateway/cmd/gateway/main.go` (dep). Client: `ui/src/lib/transport.ts` (read the header ⇒ + `versionRecommended`), `ui/src/lib/update.svelte.ts` (nudge state), rework `ui/src/components/UpdateOverlay.svelte` + from terminal → the `offlineVersionLocked` notice ("Update"/"Play offline"), new `ui/src/components/UpdateNudge.svelte`. + - Interfaces — Produces: `GATEWAY_RECOMMENDED_CLIENT_VERSION`, the `X-Update-Recommended` header, `updateRecommended` store. + - Acceptance: `v