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.
This commit is contained in:
+84
-1
@@ -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<min` → notice → "Play offline" plays local; `min≤v<recommended` → dismissible banner, online
|
||||
continues; `v≥recommended`/both empty → nothing; fail-open on a garbled header.
|
||||
- Targeted tests: Go `config_test`/`server_test` (three bands + ordering + fail-open); `e2e/update.spec.ts`
|
||||
(notice → play offline; nudge banner).
|
||||
|
||||
- **O5 — Unified lobby.**
|
||||
- Files — Modify: `ui/src/screens/Lobby.svelte` (merge `localSource.list()` + `gateway.gamesList()`; offline ⇒
|
||||
local active + cached server greyed; drop the mode-exclusive `loadSeq` branch), `ui/src/lib/lobbysort.ts`
|
||||
(sort the merged set), a self-identity helper (recognise `session.userId` **and** `localGuestId()`).
|
||||
- Acceptance: online ⇒ local + server both listed; offline ⇒ local active + server greyed-from-cache
|
||||
(un-openable); a reconciled guest's local games stay visible online (**closes G-step-0**); TG/VK server-only.
|
||||
- Targeted tests: `e2e` unified lobby (greyed offline; both online); `lobbysort.test.ts` extended.
|
||||
|
||||
- **O6 — Create flows.**
|
||||
- Files — Modify: `ui/src/screens/NewGame.svelte` (with-friends online/offline segmented control, online
|
||||
disabled when `netState.offline`; vs_ai already branches on the flag — verify; the dict-availability guard).
|
||||
- Acceptance: with-friends online = invite / offline = hotseat; no network ⇒ online disabled, offline
|
||||
preselected, create works; vs_ai offline→local / online→server unchanged; create disabled with a reason when
|
||||
the variant's dawg is unavailable offline.
|
||||
- Targeted tests: `e2e` with-friends toggle (online disabled offline); vs_ai local-vs-server by state.
|
||||
|
||||
- **O7 — Docs + native e2e.**
|
||||
- Files — Modify: `docs/ARCHITECTURE.md` (§2 gate → two tiers + graceful degrade; §3 the net-state model),
|
||||
`docs/FUNCTIONAL.md` (+`_ru`), `docs/TESTING.md`, `deploy/README.md` (the two version vars), `ui/e2e/native.spec.ts`.
|
||||
- Acceptance: the F-baked docs describe both tiers + the implicit model + the unified lobby; `deploy/README`
|
||||
lists `GATEWAY_RECOMMENDED_CLIENT_VERSION`.
|
||||
- Targeted tests: n/a (docs); the native e2e reflects the new boot/offline.
|
||||
|
||||
---
|
||||
|
||||
## Google Play variant (later — design now so we don't repaint)
|
||||
|
||||
Reference in New Issue
Block a user