// 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'); }