summaryrefslogtreecommitdiffhomepage
path: root/src/adapters
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-27 19:04:26 +0900
committerAdam Malczewski <[email protected]>2026-06-27 19:04:26 +0900
commit19b6b29b1a82b11c64c8b05c97cf8f687fd495f6 (patch)
tree753c584e651eb0cfed46a949266ab5d9b80fef98 /src/adapters
parent93b6ac11a83b8f4ec38bb957a5be5cfefa2b1240 (diff)
downloaddispatch-web-19b6b29b1a82b11c64c8b05c97cf8f687fd495f6.tar.gz
dispatch-web-19b6b29b1a82b11c64c8b05c97cf8f687fd495f6.zip
feat(concurrency): loading-ring for queued chats (CR-13 consumed)
Backend shipped "queued" ConversationStatus (additive to [email protected]): 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.
Diffstat (limited to 'src/adapters')
-rw-r--r--src/adapters/ws/logic.test.ts15
-rw-r--r--src/adapters/ws/logic.ts7
2 files changed, 21 insertions, 1 deletions
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;