diff options
| author | Adam Malczewski <[email protected]> | 2026-06-27 19:04:26 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-27 19:04:26 +0900 |
| commit | 38416765b3ac9707e7a5f5df5e3fd50a82c4ea32 (patch) | |
| tree | e5052e4e03374697a68eb41e871924585ad6b8bb /src/app/App.svelte | |
| parent | c18485d829d2fb964653e3613da599df7d867877 (diff) | |
| download | dispatch-web-38416765b3ac9707e7a5f5df5e3fd50a82c4ea32.tar.gz dispatch-web-38416765b3ac9707e7a5f5df5e3fd50a82c4ea32.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/app/App.svelte')
| -rw-r--r-- | src/app/App.svelte | 20 |
1 files changed, 15 insertions, 5 deletions
diff --git a/src/app/App.svelte b/src/app/App.svelte index 5aa103d..b46885c 100644 --- a/src/app/App.svelte +++ b/src/app/App.svelte @@ -16,6 +16,7 @@ ModelSelector, ReasoningEffortSelector, type CompactNowResult, + type ComposerStatus, type ReasoningEffortSaveResult, type SaveCompactPercentResult, } from "../features/chat"; @@ -238,6 +239,19 @@ return tab?.title ?? NEW_TAB_TITLE; }); + // The composer status-bar status. Priority: error > queued > running > idle. + // `queued` (the turn is in flight but waiting for a concurrency slot — CR-13) + // wins over `running` so the corner shows a ring, not dots, during the wait. + // `turn-start` fires before the slot is granted, so `generating` is already + // true while `conversationStatus === "queued"`; the explicit queued check is + // what distinguishes the two. + const composerStatus = $derived.by<ComposerStatus>(() => { + if (store.activeChat.error) return "error"; + const id = store.activeConversationId; + if (id !== null && store.conversationStatus(id) === "queued") return "queued"; + return store.activeChat.generating ? "running" : "idle"; + }); + // Conversation/tab switch → snap to the bottom of the new transcript. $effect(() => { void store.activeConversationId; @@ -588,11 +602,7 @@ onStop={handleStop} contextSize={store.activeChat.currentContextSize} contextWindow={store.modelInfo[store.activeModel]?.contextWindow} - status={store.activeChat.error - ? "error" - : store.activeChat.generating - ? "running" - : "idle"} + status={composerStatus} /> </div> |
