diff --git a/ui/frontend/src/sync/order-draft.svelte.ts b/ui/frontend/src/sync/order-draft.svelte.ts index d1b8b26..debfc01 100644 --- a/ui/frontend/src/sync/order-draft.svelte.ts +++ b/ui/frontend/src/sync/order-draft.svelte.ts @@ -131,6 +131,14 @@ export class OrderDraftStore { }): Promise { if (this.status !== "ready") return; this.client = opts.client; + // Guard against placeholder game ids the Phase 10 e2e specs + // still use — auto-sync needs a real UUID for the FBS request + // envelope, and a parser exception here would only be visible + // as a noisy `console.warn` deep in the layout boot path. + if (!isUuid(this.gameId)) { + this.syncStatus = "idle"; + return; + } this.syncStatus = "syncing"; this.syncError = null; try { @@ -232,6 +240,9 @@ export class OrderDraftStore { private scheduleSync(): void { if (this.client === null) return; + // Same UUID guard as `hydrateFromServer` — placeholder game + // ids in test fixtures must not blow up the auto-sync path. + if (!isUuid(this.gameId)) return; if (this.syncing !== null) { this.pending = true; return; @@ -379,6 +390,12 @@ export class OrderDraftStore { } } +const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; + +function isUuid(value: string): boolean { + return UUID_RE.test(value); +} + function validateCommand(cmd: OrderCommand): CommandStatus { switch (cmd.kind) { case "planetRename": diff --git a/ui/frontend/tests/e2e/game-shell.spec.ts b/ui/frontend/tests/e2e/game-shell.spec.ts index 65751d1..4aa9c12 100644 --- a/ui/frontend/tests/e2e/game-shell.spec.ts +++ b/ui/frontend/tests/e2e/game-shell.spec.ts @@ -1,7 +1,9 @@ // Phase 10 end-to-end coverage for the in-game shell. Every spec -// boots an authenticated session through `/__debug/store` (no -// gateway calls — the shell makes none in Phase 10), navigates into -// `/games/test-shell/map`, and exercises one slice of the chrome: +// boots an authenticated session through `/__debug/store` (the +// in-game shell makes a handful of gateway calls — for the lobby +// record, the report, and the order read-back; we don't mock them +// here, the shell tolerates ECONNREFUSED), navigates into +// `/games//map`, and exercises one slice of the chrome: // header navigation, sidebar tab preservation, mobile bottom-tabs, // and the breakpoint switches at 768 / 1024 px. @@ -14,7 +16,10 @@ import { expect, test, type Page } from "@playwright/test"; // `setDeviceSessionId`); the merged global declaration covers both. const SESSION_ID = "phase-10-shell-session"; -const GAME_ID = "test-shell"; +// GAME_ID has to be a real UUID — Phase 14's auto-sync calls +// `uuidToHiLo` on it for the FBS request envelope, and an +// arbitrary string would throw on every layout boot. +const GAME_ID = "10101010-1010-1010-1010-101010101010"; async function bootShell(page: Page): Promise { await page.goto("/__debug/store");