14918cc94f
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).
77 lines
3.7 KiB
TypeScript
77 lines
3.7 KiB
TypeScript
// The client-version gate's two client signals (docs/ARCHITECTURE.md §2).
|
|
//
|
|
// HARD tier ("client too old"): the gateway refuses a foreground call with the update_required
|
|
// sentinel (the Execute result_code, the Subscribe FailedPrecondition). The transport reports it here,
|
|
// which drives the net-state machine into offlineVersionLocked; the notice (UpdateOverlay.svelte) reads
|
|
// netState.versionLocked and offers "Update" (store / reload) or "Play offline" (dismiss → stay
|
|
// offline). Offline play never trips it — the kill switch refuses the call before it leaves the device,
|
|
// and the background guest reconcile swallows it (stays a local guest, no notice).
|
|
//
|
|
// SOFT tier ("update available"): a served response carries the X-Update-Recommended header, which the
|
|
// transport reports here. It drives the machine (versionRecommended), whose setNudge effect — fired
|
|
// only from online — latches the dismissible nudge (UpdateNudge.svelte). The hard notice supersedes the
|
|
// nudge naturally: the nudge shows only while online, and version-locked is an offline state.
|
|
|
|
import { emit } from './netstate.svelte';
|
|
import { clientChannel } from './channel';
|
|
|
|
/** UPDATE_REQUIRED is the stable sentinel the gateway returns (the Execute result_code, and the
|
|
* GatewayError code the Subscribe FailedPrecondition maps to) for a client too old to be served. */
|
|
export const UPDATE_REQUIRED = 'update_required';
|
|
|
|
/** openUpdate sends the user to the fix, shared by the hard-tier notice and the soft-tier nudge: the
|
|
* store listing on a native build (VITE_RUSTORE_URL, handed to the OS via '_system'), or a plain
|
|
* reload on the web (which fetches the current client). */
|
|
export function openUpdate(): void {
|
|
const ch = clientChannel();
|
|
if (ch === 'android' || ch === 'ios') {
|
|
const url = import.meta.env.VITE_RUSTORE_URL;
|
|
if (url) window.open(url, '_system');
|
|
} else {
|
|
location.reload();
|
|
}
|
|
}
|
|
|
|
/** reportUpdateRequired drives the net-state machine into offlineVersionLocked (the hard tier). The
|
|
* transport calls it only for a foreground update_required — the background reconcile swallows it and
|
|
* stays offline. The notice reads netState.versionLocked; there is no separate latch. */
|
|
export function reportUpdateRequired(): void {
|
|
emit('versionRejected');
|
|
}
|
|
|
|
// The soft "update available" nudge. `active` latches when the machine accepts a versionRecommended
|
|
// from online (via the setNudge effect → latchUpdateNudge); `dismissed` hides the banner for the
|
|
// session once the user closes it. A fresh launch re-evaluates the version, so both reset on reload.
|
|
let recommended = $state(false);
|
|
let nudgeDismissed = $state(false);
|
|
|
|
export const updateRecommended = {
|
|
/** active is true once the gateway has signalled an available (non-blocking) update. */
|
|
get active(): boolean {
|
|
return recommended;
|
|
},
|
|
/** dismissed is true once the user has closed the nudge for this session. */
|
|
get dismissed(): boolean {
|
|
return nudgeDismissed;
|
|
},
|
|
};
|
|
|
|
/** latchUpdateNudge raises the soft-nudge latch. It is wired to the machine's setNudge effect
|
|
* (registerNetNudge), which fires only from online, so the nudge is never raised while offline or
|
|
* version-locked. */
|
|
export function latchUpdateNudge(): void {
|
|
recommended = true;
|
|
}
|
|
|
|
/** dismissUpdateNudge hides the nudge for the session (a fresh launch re-evaluates the version). */
|
|
export function dismissUpdateNudge(): void {
|
|
nudgeDismissed = true;
|
|
}
|
|
|
|
/** reportUpdateRecommended signals an available update (the soft tier). The transport calls it when a
|
|
* served response carries the X-Update-Recommended header; it drives the machine (versionRecommended),
|
|
* which latches the nudge from online. */
|
|
export function reportUpdateRecommended(): void {
|
|
emit('versionRecommended');
|
|
}
|