From 19b6b29b1a82b11c64c8b05c97cf8f687fd495f6 Mon Sep 17 00:00:00 2001 From: Adam Malczewski Date: Sat, 27 Jun 2026 19:04:26 +0900 Subject: feat(concurrency): loading-ring for queued chats (CR-13 consumed) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backend shipped "queued" ConversationStatus (additive to wire@0.12.0): when a request blocks on a concurrency slot, conversation.statusChanged broadcasts "queued" (broadcast-only, never persisted); "active" on slot grant. FE consumes it: - WS parser (adapters/ws/logic.ts): accepts "queued" in the status set. - Store handler: "queued" updates the status map + opens a tab for a new cross-device queued conversation (like "active"). - TabList: status === "queued" -> loading-ring (spinner, aria-label "Queued"); "active" -> loading-dots (unchanged). - Composer: status type widened to ComposerStatus (idle|running|queued|error), exported from features/chat. "queued" -> a loading-ring status icon + placeholder "Queued for a slot…"; behaves like "running" for the send button (steer/stop — the turn is in flight, just waiting for a slot). - App.svelte: composerStatus derived (error > queued > running > idle) — conversationStatus === "queued" wins over generating so the corner shows a ring during the wait (turn-start fires before the slot is granted, so generating is already true while status === "queued"). - Re-mirrored .dispatch/wire.reference.md (ConversationStatus widened + header). Tests: WS parser accepts queued; store handler sets status + opens a cross-device tab + transitions queued->active->idle; TabList renders a ring for queued + dots for active. typecheck 0/0, 925 tests green (x2), biome clean, build OK. backend-handoff.md CR-13 marked RESOLVED. --- src/adapters/ws/logic.test.ts | 15 +++++++++++++++ src/adapters/ws/logic.ts | 7 ++++++- 2 files changed, 21 insertions(+), 1 deletion(-) (limited to 'src/adapters') diff --git a/src/adapters/ws/logic.test.ts b/src/adapters/ws/logic.test.ts index 113a731..dd2b773 100644 --- a/src/adapters/ws/logic.test.ts +++ b/src/adapters/ws/logic.test.ts @@ -283,6 +283,21 @@ describe("parseServerMessage", () => { }); }); + it("accepts the `queued` status (CR-13 — waiting for a concurrency slot)", () => { + const data = JSON.stringify({ + type: "conversation.statusChanged", + conversationId: "c1", + status: "queued", + workspaceId: "w1", + }); + expect(parseServerMessage(data)).toEqual({ + type: "conversation.statusChanged", + conversationId: "c1", + status: "queued", + workspaceId: "w1", + }); + }); + it("returns null for conversation.statusChanged with missing workspaceId", () => { expect( parseServerMessage( diff --git a/src/adapters/ws/logic.ts b/src/adapters/ws/logic.ts index ba3e7ee..03ef763 100644 --- a/src/adapters/ws/logic.ts +++ b/src/adapters/ws/logic.ts @@ -126,7 +126,12 @@ export function parseServerMessage(data: string): WsServerMessage | null { case "conversation.statusChanged": { if (typeof parsed.conversationId !== "string") return null; if (typeof parsed.status !== "string") return null; - if (parsed.status !== "active" && parsed.status !== "idle" && parsed.status !== "closed") { + if ( + parsed.status !== "active" && + parsed.status !== "queued" && + parsed.status !== "idle" && + parsed.status !== "closed" + ) { return null; } if (typeof parsed.workspaceId !== "string") return null; -- cgit v1.2.3