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 10s
CI / integration (pull_request) Successful in 21s
CI / ui (pull_request) Successful in 1m14s
CI / conformance (pull_request) Successful in 12s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Failing after 22s

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, and the wire change is additive, so web/pwa/vk/tg behaviour is unchanged unless the gate is deliberately configured.

O1 pure net-state reducer (test-first). O2 reactive store + event wiring (connection/offline become thin 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 (ARCHITECTURE, FUNCTIONAL +_ru, TESTING, deploy/README).

Also disable the manual android-build CI workflow for now (rename to .disabled); the Android APK release comes later.

Tests: gateway go green, svelte-check 0/0, vitest 617, e2e 122/122 (chromium + webkit).
This commit is contained in:
Ilia Denisov
2026-07-13 01:44:28 +02:00
parent 09e05eef18
commit a8f78c7880
48 changed files with 1953 additions and 973 deletions
+7 -4
View File
@@ -63,15 +63,18 @@ test.describe('native offline-first', () => {
expect(await page.locator('[data-cell].filled').count()).toBeGreaterThan(3);
}).toPass({ timeout: 15000 });
// Back to the lobby, then the network returns: reconciliation silently mints + adopts a server
// guest and clears the auto-offline, so online lights up — the offline tint drops, the seeded
// online game (vs Ann) appears and the Stats tab re-enables. (The local vs_ai game stays
// device-only, so the online lobby list no longer shows it — that is the intended split.)
// Back to the lobby (still offline): the device-local vs_ai game (🤖) is listed. Then the network
// returns: reconciliation silently mints + adopts a server guest and clears the auto-offline, so
// online lights up — the offline tint drops, the seeded online game (vs Ann) appears and Stats
// re-enables. The local game STAYS visible in the unified lobby alongside the server games — a
// reconciled guest keeps its device-local games (this closes G-step-0).
await page.getByRole('button', { name: /Back|Назад/i }).click();
await expect(page.locator('button.tab').nth(2)).toBeVisible();
await expect(page.locator('.rowwrap').filter({ hasText: '🤖' })).toBeVisible(); // the local vs_ai game, listed while offline
await page.evaluate(() => (window as unknown as { __native: { reconcile(): void } }).__native.reconcile());
await expect(page.locator('header.nav.offline')).toHaveCount(0);
await expect(page.getByText('Ann', { exact: false }).first()).toBeVisible({ timeout: 15000 });
await expect(page.locator('.rowwrap').filter({ hasText: '🤖' })).toBeVisible(); // still listed online — the reconciled guest keeps it (G-step-0)
await expect(page.locator('button.tab').nth(1)).toBeEnabled();
// The native sign-in surface is guest + email only: on Profile the Telegram + VK LINK buttons are