ui/phase-25: backend turn-cutoff guard + auto-pause + UI sync protocol
Backend now owns the turn-cutoff and pause guards the order tab relies on: the scheduler flips runtime_status between generation_in_progress and running around every engine tick, a failed tick auto-pauses the game through OnRuntimeSnapshot, and a new game.paused notification kind fans out alongside game.turn.ready. The user-games handlers reject submits with HTTP 409 turn_already_closed or game_paused depending on the runtime state. UI delegates auto-sync to a new OrderQueue: offline detection, single retry on reconnect, conflict / paused classification. OrderDraftStore surfaces conflictBanner / pausedBanner runes, clears them on local mutation or on a game.turn.ready push via resetForNewTurn. The order tab renders the matching banners and the new conflict per-row badge; i18n bundles cover en + ru. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -291,6 +291,36 @@ describe("EventStream", () => {
|
||||
eventStream.stop();
|
||||
});
|
||||
|
||||
test("game.paused events dispatch to the matching handler (Phase 25)", async () => {
|
||||
const handler = vi.fn();
|
||||
eventStream.on("game.paused", handler);
|
||||
const payload = new TextEncoder().encode(
|
||||
JSON.stringify({
|
||||
game_id: "11111111-2222-3333-4444-555555555555",
|
||||
turn: 7,
|
||||
reason: "generation_failed",
|
||||
}),
|
||||
);
|
||||
const event = buildEvent("game.paused", payload);
|
||||
const client = makeRouter(async function* () {
|
||||
yield event;
|
||||
});
|
||||
eventStream.start({
|
||||
core: mockCore(),
|
||||
keypair: mockKeypair(),
|
||||
deviceSessionId: "device-1",
|
||||
gatewayResponsePublicKey: new Uint8Array(32),
|
||||
client,
|
||||
sleep: async () => {},
|
||||
random: () => 0,
|
||||
});
|
||||
await vi.waitFor(() => {
|
||||
expect(handler).toHaveBeenCalled();
|
||||
});
|
||||
expect(handler.mock.calls[0]?.[0].eventType).toBe("game.paused");
|
||||
eventStream.stop();
|
||||
});
|
||||
|
||||
test("connectionStatus transitions through connecting → connected → idle", async () => {
|
||||
expect(eventStream.connectionStatus).toBe("idle");
|
||||
const event = buildEvent(
|
||||
|
||||
Reference in New Issue
Block a user