872804e02e
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 19s
CI / ui (pull_request) Successful in 1m13s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Failing after 20s
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. Wire the version gate through the deploy: GATEWAY_MIN_CLIENT_VERSION + GATEWAY_RECOMMENDED_CLIENT_VERSION are plain (unprefixed) Gitea vars, passed via compose + ci.yaml (test) + prod-deploy.yaml + write-prod-env.sh (prod) + .env.example, so the gate is live when set (a 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. Tests: gateway go green (incl. two-tier gate over a real HTTP handler), compose --no-interpolate confirms the gateway env, svelte-check 0/0, vitest 617, e2e 122/122 (chromium + webkit).
76 lines
3.3 KiB
TypeScript
76 lines
3.3 KiB
TypeScript
// In-memory lobby snapshot, the lobby counterpart of gamecache.ts. The lobby re-fetches
|
|
// its lists on every entry, so without a cache the screen renders blank and "draws in"
|
|
// during the back-slide from a game. Caching the last lists lets the lobby render
|
|
// instantly (before/under the transition) and refresh in the background. Process-memory
|
|
// only; cleared on logout.
|
|
|
|
import type { AccountRef, GameView, Invitation } from './model';
|
|
|
|
interface LobbySnapshot {
|
|
games: GameView[];
|
|
invitations: Invitation[];
|
|
incoming: AccountRef[];
|
|
}
|
|
|
|
let snapshot: LobbySnapshot | null = null;
|
|
|
|
/** getLobby returns the last lobby lists, or null before the first load. The unified lobby caches one
|
|
* snapshot (device-local + server games merged); offline reuses its server games, greyed, and drops
|
|
* the local ones for a fresh device read. */
|
|
export function getLobby(): LobbySnapshot | null {
|
|
return snapshot;
|
|
}
|
|
|
|
/** setLobby stores the latest lobby lists. */
|
|
export function setLobby(s: LobbySnapshot): void {
|
|
snapshot = s;
|
|
}
|
|
|
|
/**
|
|
* patchLobbyGame upserts the given game into the cached lobby — replacing the matching entry, or
|
|
* prepending it when absent — so a game-state change seen while the lobby is unmounted (the player's
|
|
* own move, or any live game event the global stream handler applies from another screen) is already
|
|
* reflected the next time the lobby renders from the snapshot, instead of showing the stale status
|
|
* until the background refresh lands. Prepending a freshly started game likewise lets it appear at
|
|
* once. It is a no-op only when there is no snapshot yet (the lobby cold-loads on its first mount).
|
|
* The lobby re-groups and re-sorts on render, so the insert position carries no meaning.
|
|
*/
|
|
export function patchLobbyGame(g: GameView): void {
|
|
if (!snapshot) return;
|
|
const i = snapshot.games.findIndex((x) => x.id === g.id);
|
|
const games = snapshot.games.slice();
|
|
if (i === -1) games.unshift(g);
|
|
else games[i] = g;
|
|
snapshot = { ...snapshot, games };
|
|
}
|
|
|
|
/**
|
|
* patchLobbyInvitation applies a live invitation change to the cached lobby's invitations list, so a
|
|
* change seen while the lobby is unmounted is reflected on its next render instead of a stale card
|
|
* lingering until the background refresh. A still-pending invitation is upserted (a new one
|
|
* prepended newest-first, an updated one replaced in place); an invitation that reached any terminal
|
|
* status (started, declined, cancelled, expired) is removed — the authoritative list holds only
|
|
* pending invitations. It is a no-op when there is no snapshot yet, or a terminal invitation is not
|
|
* in the list. The lobby re-renders from the snapshot, so the in-place upsert needs no re-sort.
|
|
*/
|
|
export function patchLobbyInvitation(inv: Invitation): void {
|
|
if (!snapshot) return;
|
|
const i = snapshot.invitations.findIndex((x) => x.id === inv.id);
|
|
let invitations: Invitation[];
|
|
if (inv.status !== 'pending') {
|
|
if (i === -1) return; // a terminal invitation already absent — nothing to remove
|
|
invitations = snapshot.invitations.filter((x) => x.id !== inv.id);
|
|
} else if (i === -1) {
|
|
invitations = [inv, ...snapshot.invitations];
|
|
} else {
|
|
invitations = snapshot.invitations.slice();
|
|
invitations[i] = inv;
|
|
}
|
|
snapshot = { ...snapshot, invitations };
|
|
}
|
|
|
|
/** clearLobby drops the cached lobby (called on logout). */
|
|
export function clearLobby(): void {
|
|
snapshot = null;
|
|
}
|