fix(offline): return online after flight-mode off; gate online-game actions offline
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 1m11s
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m38s
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 1m11s
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m38s
Two mid-session issues found on the contour: - Auto-offline did not return to online after the network came back: a single reachability check right after the 'online' event failed (the interface is up before the gateway is actually reachable again). Retry a few times with backoff (tryReturnOnline) — that is what actually gets the app back online. - An online game viewed while offline (flight mode on) still enabled its network actions, so they hit the kill switch and raised 'something went wrong' toasts. Gate them: netReady = isLocalGame || (connection.online && !offlineMode.active) — a local game stays fully usable; an online game's make/exchange/hint/resign disable while offline and re-enable when back. Also suppress the 'offline' code in handleError (a blocked call in offline mode is expected, not a toast).
This commit is contained in:
+12
-6
@@ -13,6 +13,7 @@
|
||||
import { navigate } from '../lib/router.svelte';
|
||||
import { app, handleError, showToast, markChatRead, seedChatUnread } from '../lib/app.svelte';
|
||||
import { connection } from '../lib/connection.svelte';
|
||||
import { offlineMode } from '../lib/offline.svelte';
|
||||
import { GatewayError } from '../lib/client';
|
||||
import { t, type MessageKey } from '../lib/i18n/index.svelte';
|
||||
import type { EvalResult, MoveRecord, MoveResult, StateView, Tile } from '../lib/model';
|
||||
@@ -52,6 +53,11 @@
|
||||
// screen calls the game-loop methods (state/history/submit/pass/exchange/resign/hint/evaluate/
|
||||
// draft) through it, so the same UI drives a local vs_ai game and an online one alike.
|
||||
const source = $derived(gameSource(id));
|
||||
// A local (offline) game plays entirely on-device, so it is always ready; an online game needs a
|
||||
// live connection — and offline mode's kill switch refuses its calls — so its network actions are
|
||||
// disabled while disconnected or in offline mode (which also stops the "something went wrong"
|
||||
// toasts a blocked call would otherwise raise).
|
||||
const netReady = $derived(isLocalGameId(id) || (connection.online && !offlineMode.active));
|
||||
// Unsubscribes from a local game's robot-reply events (offline only; null for a network game).
|
||||
let localUnsub: (() => void) | null = null;
|
||||
|
||||
@@ -1481,19 +1487,19 @@
|
||||
/>
|
||||
</div>
|
||||
{#if !gameOver && placement.pending.length > 0 && !recallOverRack}
|
||||
<button class="make" onclick={commit} disabled={busy || !isMyTurn || !connection.online || !preview?.legal} aria-label={t('game.makeMove')}>✅</button>
|
||||
<button class="make" onclick={commit} disabled={busy || !isMyTurn || !netReady || !preview?.legal} aria-label={t('game.makeMove')}>✅</button>
|
||||
{/if}
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
{#snippet controlButtons()}
|
||||
<button class="tab" disabled={busy || !isMyTurn || !connection.online} onclick={openExchange}>
|
||||
<button class="tab" disabled={busy || !isMyTurn || !netReady} onclick={openExchange}>
|
||||
<span class="sq" data-coach="game-turn">🔄</span><span class="lbl">{t('game.draw')}</span>
|
||||
</button>
|
||||
<TapConfirm
|
||||
triggerClass="tab"
|
||||
label={t('game.hint')}
|
||||
disabled={busy || !isMyTurn || !connection.online || hintCount <= 0}
|
||||
disabled={busy || !isMyTurn || !netReady || hintCount <= 0}
|
||||
onconfirm={doHint}
|
||||
>
|
||||
<span class="sq" data-coach="game-hints">🛟{#if hintCount > 0}<span class="badge">{hintCount}</span>{/if}</span>
|
||||
@@ -1551,7 +1557,7 @@
|
||||
<Modal title={t('game.confirmResign')} onclose={() => (resignOpen = false)}>
|
||||
<div class="confirm-row">
|
||||
<button class="cancel" onclick={() => (resignOpen = false)}>{t('common.cancel')}</button>
|
||||
<button class="danger" onclick={doResign} disabled={!connection.online}>{t('game.dropGame')}</button>
|
||||
<button class="danger" onclick={doResign} disabled={!netReady}>{t('game.dropGame')}</button>
|
||||
</div>
|
||||
</Modal>
|
||||
{/if}
|
||||
@@ -1563,7 +1569,7 @@
|
||||
<button
|
||||
class="confirm"
|
||||
onclick={() => { exportOpen = false; void exportArtifact('png'); }}
|
||||
disabled={!connection.online}
|
||||
disabled={!netReady}
|
||||
>
|
||||
{t('game.exportImageOpt')}
|
||||
</button>
|
||||
@@ -1571,7 +1577,7 @@
|
||||
<button
|
||||
class={exportImageAvailable ? 'export-alt' : 'confirm'}
|
||||
onclick={() => { exportOpen = false; void exportArtifact('gcg'); }}
|
||||
disabled={!connection.online}
|
||||
disabled={!netReady}
|
||||
>
|
||||
{t('game.exportGcgOpt')}
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user