feat(offline): implicit net-state model, two-tier version gate, unified lobby
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 11s
CI / integration (pull_request) Successful in 19s
CI / ui (pull_request) Successful in 1m12s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m50s
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 11s
CI / integration (pull_request) Successful in 19s
CI / ui (pull_request) Successful in 1m12s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m50s
Land the offline-model redesign (ANDROID_PLAN.md O1-O7): replace the explicit offline toggle with a single detected net-state machine, unify the lobby, and add a two-tier client-version gate. Contour-safe: both version vars empty => dormant, the wire change is additive, so web/pwa/vk/tg behaviour is unchanged unless the gate is deliberately configured. O1 net-state reducer (test-first). O2 store + wiring (connection/offline shims; +@capacitor/network). O3 remove the offline toggle + migrate the pref. O4 two-tier gate: hard update_required degrades to an offline Update/Play-offline notice (not terminal); soft GATEWAY_RECOMMENDED_CLIENT_VERSION -> X-Update-Recommended nudge. O5 unified lobby (device-local + greyed-from-cache server games; self-set identity; closes G-step-0). O6 create flows (with-friends online/offline segment + offline dict guard). O7 docs. Deploy/CI: wire the version gate through the deploy (GATEWAY_MIN_CLIENT_VERSION + GATEWAY_RECOMMENDED_CLIENT_VERSION as plain unprefixed vars via compose + ci.yaml + prod-deploy.yaml + write-prod-env.sh + .env.example) so the gate is live when set (test-contour commit-hash version fails open; empty => dormant). Disable the manual android-build CI workflow for now (rename .disabled). Bump the app bundle budget 127->130 KB for the added always-loaded wiring. Fix the UI Docker stage (gateway/Dockerfile): install --ignore-scripts so the Alpine/musl SPA build no longer tries to native-build sharp (a local android:assets tool, unused in the image); esbuild's binary is an optional dep so Vite still builds. Tests: gateway go green (two-tier gate over a real HTTP handler); docker build of the landing + gateway targets green locally; compose --no-interpolate confirms the gateway env; svelte-check 0/0, vitest 617, e2e 122/122 (chromium + webkit).
This commit is contained in:
+22
-13
@@ -17,14 +17,17 @@ tests or touching CI.
|
||||
- **UI** — Vitest (unit) + Playwright
|
||||
(e2e), mirroring the chosen plain-Svelte + Vite toolchain. Vitest covers
|
||||
the FlatBuffers codecs (friend list, invitation, stats), the win-rate
|
||||
derivation and the GCG share/copy/download choice, plus Playwright specs against the
|
||||
derivation, the GCG share/copy/download choice and the **net-state reducer**
|
||||
(`netstate.test.ts` — every transition of the connectivity/version machine, the anti-flap
|
||||
hysteresis, the two-tier version decision and all 12 offline edge cases as pure assertions),
|
||||
plus Playwright specs against the
|
||||
mock for the friends screen (code issue/redeem, accept a request), the lobby
|
||||
invitations section, the stats screen, profile editing, and the export chooser's
|
||||
finished-only visibility + its signed-URL download flow (route-intercepted). The
|
||||
**offline mode** spec (`e2e/offline.spec.ts`) plays a full device-local `vs_ai` game
|
||||
end-to-end: it forces the installed-PWA display mode, enters offline through the
|
||||
Settings toggle (whose readiness check fetches the enabled variants' dawgs), then creates
|
||||
and plays a local game with a **pinned bag seed** (`window.__mock.setLocalSeed`, so the
|
||||
end-to-end: offline is **implicit** now (no toggle), so it drives the mock's `__net` hook to
|
||||
auto-detect offline (asserting the toast, the greyed-from-cache server games and the disabled
|
||||
Stats tab), self-heals back online, then creates and plays a local game with a **pinned bag seed** (`window.__mock.setLocalSeed`, so the
|
||||
rack is deterministic and the human can tap out a precomputed opening), asserting the
|
||||
robot's real reply and the IndexedDB replay after a reload. A local game needs a real
|
||||
dictionary, so the mock's `fetchDict` serves the per-variant dawgs from the preview
|
||||
@@ -36,15 +39,21 @@ tests or touching CI.
|
||||
`Capacitor.getPlatform` stub is clobbered by the core shim), then asserts a no-session cold boot lands
|
||||
in the **offline guest lobby** (not `/login`), plays a local vs_ai move from the **bundled** dawg tier
|
||||
(`playwright.config.ts` bundles the dicts into `dist-e2e/dict/`), starts a hotseat game, and drives
|
||||
`window.__native.reconcile()` to prove online lights up and Profile hides the Telegram/VK link buttons.
|
||||
The **update overlay** spec (`e2e/update.spec.ts`) drives the `__update` hook to prove the terminal
|
||||
update cover; `retry.test.ts` covers the `FailedPrecondition → update_required` mapping.
|
||||
- **Client-version gate** (Go) — `gateway/internal/clientver` unit-tests the `v?MAJOR.MINOR.PATCH` parse
|
||||
(with/without `v`, a `-N-gSHA` suffix, `dev`/empty ⇒ !ok) and ordering; `connectsrv`'s server tests
|
||||
assert a too-old `Execute` returns `result_code = "update_required"` (and the op handler never ran), a
|
||||
too-old `Subscribe` returns `FailedPrecondition`, and an absent header / empty min / unparseable header
|
||||
/ equal version all pass (fail-open); `config` tests reject a non-empty, unparseable
|
||||
`GATEWAY_MIN_CLIENT_VERSION`.
|
||||
`window.__native.reconcile()` to prove online lights up — the device-local game stays listed in the
|
||||
**unified lobby** (closing G-step-0) — and Profile hides the Telegram/VK link buttons.
|
||||
The **version-gate** spec (`e2e/update.spec.ts`) drives the `__update` hook to prove both tiers — the
|
||||
hard *Update / Play offline* notice (and that "Play offline" drops into the offline lobby) and the soft
|
||||
dismissable *update available* nudge in the lobby; `retry.test.ts` covers the `FailedPrecondition →
|
||||
update_required` mapping.
|
||||
- **Client-version gate** (Go, two tiers) — `gateway/internal/clientver` unit-tests the `v?MAJOR.MINOR.PATCH`
|
||||
parse (with/without `v`, a `-N-gSHA` suffix, `dev`/empty ⇒ !ok) and ordering. `connectsrv`'s server tests
|
||||
assert the **hard** tier — a too-old `Execute` returns `result_code = "update_required"` (and the op
|
||||
handler never ran), a too-old `Subscribe` returns `FailedPrecondition`, and an absent header / empty min /
|
||||
unparseable header / equal version all pass (fail-open) — and the **soft** tier (`TestExecuteUpdateRecommended`):
|
||||
a served build in `min ≤ v < recommended` carries the `X-Update-Recommended: 1` response header, while a
|
||||
too-old, up-to-date, absent/garbled, or dormant-tier request does not. `config` tests reject a non-empty
|
||||
unparseable `GATEWAY_MIN_CLIENT_VERSION` / `GATEWAY_RECOMMENDED_CLIENT_VERSION`, and a recommended below the
|
||||
minimum.
|
||||
- **Render sidecar** — `renderer/` (Node + skia-canvas executing the shared
|
||||
`ui/src/lib/gameimage.ts`) carries a `node --test` smoke: the committed fixture (a
|
||||
real self-played 35-move game) must rasterize to a plausible PNG
|
||||
|
||||
Reference in New Issue
Block a user