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
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:
@@ -1,33 +1,29 @@
|
||||
<script lang="ts">
|
||||
// Non-dismissable "update required" cover. Shown when the gateway has refused a foreground call
|
||||
// as too old (update.svelte.ts — the client-version gate). Unlike the maintenance overlay this is
|
||||
// terminal: an installed build cannot become compatible without an actual update, so its one
|
||||
// action takes the user to the fix — the store listing on a native build (VITE_RUSTORE_URL, opened
|
||||
// in the system browser / store app) or a plain reload on the web (which fetches the current
|
||||
// client). Mirrors MaintenanceOverlay.svelte's look.
|
||||
import { updateRequired } from '../lib/update.svelte';
|
||||
import { clientChannel } from '../lib/channel';
|
||||
// The "update required" notice (the hard version tier). Shown when the gateway has refused a
|
||||
// foreground call as too old, driving the net-state machine into offlineVersionLocked. Unlike the
|
||||
// old terminal cover it is dismissable: "Update" takes the user to the fix (the store on native,
|
||||
// a reload on the web), while "Play offline" dismisses the notice and leaves the app in the offline
|
||||
// lobby (the version lock is an offline state, sticky until an actual update on the next launch).
|
||||
// Mirrors MaintenanceOverlay.svelte's look.
|
||||
import { netState } from '../lib/netstate.svelte';
|
||||
import { openUpdate } from '../lib/update.svelte';
|
||||
import { t } from '../lib/i18n/index.svelte';
|
||||
|
||||
function onAction(): void {
|
||||
const ch = clientChannel();
|
||||
if (ch === 'android' || ch === 'ios') {
|
||||
// '_system' hands the URL to the OS (the store app / external browser) rather than the WebView.
|
||||
const url = import.meta.env.VITE_RUSTORE_URL;
|
||||
if (url) window.open(url, '_system');
|
||||
} else {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
// Dismissed for the session once the user chooses "Play offline". The lock is sticky until a fresh
|
||||
// launch, so the notice does not reappear this session; the app stays in the offline lobby.
|
||||
let dismissed = $state(false);
|
||||
</script>
|
||||
|
||||
{#if updateRequired.active}
|
||||
{#if netState.versionLocked && !dismissed}
|
||||
<div class="scrim" role="alertdialog" aria-modal="true" aria-labelledby="update-title" aria-describedby="update-body">
|
||||
<div class="card">
|
||||
<div class="tile" aria-hidden="true">Э</div>
|
||||
<h1 id="update-title">{t('update.title')}</h1>
|
||||
<p id="update-body">{t('update.body')}</p>
|
||||
<button type="button" onclick={onAction}>{t('update.action')}</button>
|
||||
<div class="acts">
|
||||
<button type="button" class="primary" onclick={openUpdate}>{t('update.action')}</button>
|
||||
<button type="button" onclick={() => (dismissed = true)}>{t('update.playOffline')}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -36,8 +32,7 @@
|
||||
.scrim {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
/* Above the game (z 60) and toasts (z 50) — a terminal update block covers everything the user
|
||||
could otherwise interact with; below the dev DebugPanel (z 10000). */
|
||||
/* Above the game (z 60) and toasts (z 50); below the dev DebugPanel (z 10000). */
|
||||
z-index: 100;
|
||||
background: rgba(0, 0, 0, 0.55);
|
||||
display: grid;
|
||||
@@ -79,6 +74,11 @@
|
||||
color: var(--text-muted);
|
||||
line-height: 1.5;
|
||||
}
|
||||
.acts {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.6rem;
|
||||
}
|
||||
button {
|
||||
font: inherit;
|
||||
padding: 0.55rem 1.4rem;
|
||||
@@ -92,4 +92,13 @@
|
||||
border-color: var(--accent);
|
||||
color: var(--accent);
|
||||
}
|
||||
.primary {
|
||||
background: var(--accent);
|
||||
color: var(--accent-text);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
.primary:hover {
|
||||
color: var(--accent-text);
|
||||
opacity: 0.9;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user