Queue concurrent prompts instead of dropping or conflicting

Chat store: when sendMessage() is called while streaming, push to a
per-conversation queue and return. When the stream ends the next queued
message fires automatically via setTimeout(0) to keep the call stack
clean. clearQueue() and queuedCount exposed for UI consumption.
Queue is cleaned up on conversation delete.

ChatView/WorkspaceView: remove the streaming guard from the local
sendMessage() functions and the :disabled on the textarea so users can
type and submit freely while streaming. Input placeholder changes to
"Type to queue next message…" during streaming. A small " N queued ×"
chip appears below the streaming bubble showing queue depth with a
cancel button.

DashboardChatInput: disable input, attach button, and send button
during streaming. The dashboard creates a fresh conversation per
message so in-conversation queuing doesn't apply — locking the input
is the correct UX here. Placeholder updates to "Generating response…"
during streaming.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-09 22:05:49 -04:00
parent e34f6d6e54
commit 059a2e06d5
4 changed files with 130 additions and 13 deletions
@@ -126,7 +126,7 @@ defineExpose({ focus });
<button
class="btn-attach"
@click="toggleNotePicker"
:disabled="!store.chatReady"
:disabled="!store.chatReady || store.streaming"
title="Attach a note"
>
<svg
@@ -180,18 +180,18 @@ defineExpose({ focus });
@keydown="onInputKeydown"
@input="autoResize"
:placeholder="
store.chatReady
? 'Start a new chat... (Enter to send)'
: 'Chat unavailable'
!store.chatReady ? 'Chat unavailable'
: store.streaming ? 'Generating response…'
: 'Start a new chat… (Enter to send)'
"
:disabled="!store.chatReady"
:disabled="!store.chatReady || store.streaming"
rows="1"
></textarea>
<button
class="btn-send"
@click="onSubmit"
:disabled="!messageInput.trim() || !store.chatReady"
:disabled="!messageInput.trim() || !store.chatReady || store.streaming"
>
&uarr;
</button>