Stage 17 #2: Connecting indicator + auto-retry, instead of red toasts
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 36s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 55s
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 36s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 55s
Connectivity failures become state, not a toast on every attempt. A global online signal (lib/connection.svelte.ts) flips on a transport unavailable / rate_limited and on the live stream's drop, driving a pure-CSS header spinner + 'Connecting…' in place of the title and softly disabling the in-game server actions (commit / exchange / pass / hint; local board/rack/reset stay live). - transport: exec auto-retries with capped exponential backoff — every op on a rate-limit (rejected before processing, safe), reads only on unavailable (a mutation is never blindly re-sent, to avoid double-applying one whose response was lost; its button is disabled while offline so the player re-issues on reconnect). A reachability watcher (profile.get probe) and any successful traffic clear the signal. - the old red error.unavailable toast is gone (handleError suppresses connection codes; the indicator replaces it). A server-data screen still opens with the spinner and fills on reconnect (global indicator + read auto-retry), so navigation is never dead. - pure retry policy unit-tested (retry.ts); a mock-only window.__conn hook drives a Chromium+WebKit e2e (indicator shows offline, the action disables, both clear on reconnect). Full suite + build green. - docs: ARCHITECTURE transport note, FUNCTIONAL (+ _ru), PLAN tracker (incl. #1 — the bot already drains all updates, no change). Also records #1 as investigated/no-change in PLAN. Other server-action buttons (chat send, profile save, …) still degrade to a safe no-op offline; visual disable is easy to extend.
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
// Retry policy for the gateway transport (Stage 17). When a unary call fails at the transport
|
||||
// level the app retries it with capped exponential backoff while showing the "Connecting…"
|
||||
// indicator, instead of flashing a red toast each time.
|
||||
//
|
||||
// Idempotency: a rate-limit rejection (ResourceExhausted) never reached the backend, so any op is
|
||||
// safe to retry. A transport 'unavailable' is ambiguous for a mutation (its response could have
|
||||
// been lost after the backend applied it), so only **read-only** ops are auto-retried on
|
||||
// 'unavailable'; a mutation is surfaced instead (its button is disabled while offline and
|
||||
// re-enables on reconnect, so the player re-issues it deliberately).
|
||||
|
||||
/** READ_OPS is the set of side-effect-free message types (safe to auto-retry on any failure). */
|
||||
export const READ_OPS: ReadonlySet<string> = new Set([
|
||||
'profile.get',
|
||||
'games.list',
|
||||
'game.state',
|
||||
'game.history',
|
||||
'game.gcg',
|
||||
'game.evaluate',
|
||||
'game.check_word',
|
||||
'stats.get',
|
||||
'lobby.poll',
|
||||
'chat.list',
|
||||
'draft.get',
|
||||
'friends.list',
|
||||
'friends.incoming',
|
||||
'friends.outgoing',
|
||||
'blocks.list',
|
||||
'invitation.list',
|
||||
]);
|
||||
|
||||
/**
|
||||
* retryable reports whether a failed op should be auto-retried. A rate-limit rejection is always
|
||||
* safe (the gateway rejected it before processing); a transport 'unavailable' is retried only for
|
||||
* read-only ops, never a mutation; every other code (a domain rejection, not-found, …) is final.
|
||||
*/
|
||||
export function retryable(code: string, op: string): boolean {
|
||||
if (code === 'rate_limited') return true;
|
||||
if (code === 'unavailable') return READ_OPS.has(op);
|
||||
return false;
|
||||
}
|
||||
|
||||
/** isConnectionCode reports whether a code is a transport/connectivity failure the Connecting
|
||||
* indicator covers (so the UI suppresses its red toast). */
|
||||
export function isConnectionCode(code: string): boolean {
|
||||
return code === 'unavailable' || code === 'rate_limited';
|
||||
}
|
||||
|
||||
/** backoffMs is the delay before retry attempt n (1-based): capped exponential growth plus a
|
||||
* little jitter, so a fleet of clients does not retry in lockstep after an outage. */
|
||||
export function backoffMs(attempt: number): number {
|
||||
const base = Math.min(8000, 500 * 2 ** Math.max(0, attempt - 1));
|
||||
return base + Math.floor(Math.random() * 250);
|
||||
}
|
||||
Reference in New Issue
Block a user