Skip to content

Allow users to queue messages#2120

Merged
wwwillchen merged 14 commits intodyad-sh:mainfrom
azizmejri1:feat/chat-message-queueing
Feb 14, 2026
Merged

Allow users to queue messages#2120
wwwillchen merged 14 commits intodyad-sh:mainfrom
azizmejri1:feat/chat-message-queueing

Conversation

@azizmejri1
Copy link
Collaborator

@azizmejri1 azizmejri1 commented Jan 8, 2026


Summary by cubic

Allow users to queue multiple messages while a response is streaming. Queued items are visible, editable, reorderable, and auto-send one-by-one only after the current stream ends successfully.

  • New Features

    • Per-chat queue state via queuedMessagesByIdAtom and streamCompletedSuccessfullyByIdAtom.
    • QueuedMessagesList to view, edit, reorder, and delete items; shows status and attachment indicator.
    • ChatInput queues while streaming, supports editing queued items, clears input/attachments only on successful queue/send, and clears all queued on cancel.
    • useStreamChat exposes queuedMessages and queue/update/remove/reorder/clear APIs; processes the next item only after a confirmed successful end.
  • Bug Fixes

    • Eliminated race conditions by gating processing on successful onEnd (wasCancelled-aware), fixing a stale firstMessage closure, and clearing the queue before cancel IPC to avoid rapid-response races.
    • Preserved input and attachments when queueMessage fails; prevented dropping newly queued messages; cleared editing state when the edited queued item is deleted.

Written for commit fb431f5. Summary will update on new commits.


Note

Introduces queued message support so users can submit another prompt while a response is streaming; the queued prompt auto-sends only after the current stream ends successfully.

  • Adds QueuedMessage, queuedMessageByIdAtom, and streamCompletedSuccessfullyByIdAtom to track one queued message and confirmed stream completion per chat
  • Extends useStreamChat with queue APIs (queuedMessage, queueMessage, clearQueuedMessage) and processing gated by onEnd success; resets/sets completion flags; warns on multiple queue attempts
  • Updates ChatInput to queue while streaming, clear on successful queue, and clear queued message on cancel; uses shouldProcessQueue
  • Updates MessagesList to display queued prompt with status and a Clear action
  • Expands IPC ChatResponseEnd schema with optional wasCancelled; send this flag on cancel in chat_stream_handlers
  • Adds e2e test queued_message.spec.ts verifying queuing and auto-send behavior

Written by Cursor Bugbot for commit 62ed213. This will update automatically on new commits. Configure here.


Open with Devin

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @azizmejri1, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the chat user experience by introducing a message queuing mechanism. Previously, users were blocked from sending new messages while an AI response was actively streaming. With this change, users can now type and submit messages even during an ongoing stream; these messages will be held in a queue and automatically dispatched as soon as the current AI response concludes. This ensures a smoother, more interactive conversation flow and provides visual confirmation of pending messages.

Highlights

  • Message Queuing System: Implemented a new system that allows users to queue messages while a chat stream is active, which are then automatically sent once the current stream finishes.
  • User Interface Feedback: Introduced a visual indicator in the chat interface to display messages that are currently queued, providing clear feedback to the user.
  • Stream Chat Hook Enhancements: The useStreamChat hook was extended with new functionalities to manage the queuing, processing, and clearing of messages.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces the ability for users to queue messages while a response is streaming, enhancing the chat experience by allowing continuous interaction. The queued message is displayed in the UI and automatically sent once the current stream concludes. The changes involve adding a QueuedMessage interface and queuedMessageByIdAtom for state management, modifying ChatInput to handle message queuing and clearing, and updating useStreamChat to manage the queue and auto-send messages. MessagesList has also been updated to display the queued message.

Overall, the feature is well-implemented, but there are a few areas for improvement regarding type safety and minor code redundancy.

@wwwillchen
Copy link
Collaborator

@BugBot run

cursor[bot]

This comment was marked as outdated.

@wwwillchen
Copy link
Collaborator

@BugBot run

@wwwillchen
Copy link
Collaborator

@BugBot run

@wwwillchen
Copy link
Collaborator

@BugBot run

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

@wwwillchen
Copy link
Collaborator

@BugBot run

@github-actions
Copy link
Contributor

🔍 Multi-Agent Code Review

Found 3 issue(s) flagged by 3 independent reviewers:

  • 🔴 1 HIGH severity
  • 🟡 2 MEDIUM severity

Issues

  • 🔴 Race condition: chatId captured by closure can become stale - src/hooks/useStreamChat.ts:279
  • 🟡 Input value not cleared when message is queued - src/components/chat/ChatInput.tsx:211
  • 🟡 Race condition when cancelling stream - src/components/chat/ChatInput.tsx:238

See inline comments for details.

Generated by multi-agent consensus review (3 agents)

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Copy link
Collaborator

@wwwillchen wwwillchen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

overall looking quite nice!

a couple of ux comments:

  1. let's have it expanded by default - i think people want to see what prompts sare actually queued up
  2. there should be a smooth animation when collapsing/expanding the the prompt queue list

@wwwillchen
Copy link
Collaborator

@BugBot run

Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 17 additional findings in Devin Review.

Open in Devin Review

@wwwillchen
Copy link
Collaborator

@BugBot run

@github-actions
Copy link
Contributor

🎭 Playwright Test Results

❌ Some tests failed

OS Passed Failed Flaky Skipped
🍎 macOS 356 0 2 112
🪟 Windows 356 2 2 112

Summary: 712 passed, 2 failed, 4 flaky, 224 skipped

Failed Tests

🪟 Windows

  • edit_code.spec.ts > edit code
    • Error: expect(locator).toBeVisible() failed
  • setup_flow.spec.ts > Setup Flow > node.js install flow
    • Error: expect(locator).toBeVisible() failed

⚠️ Flaky Tests

🍎 macOS

  • setup_flow.spec.ts > Setup Flow > setup banner shows correct state when node.js is installed (passed after 1 retry)
  • setup_flow.spec.ts > Setup Flow > ai provider setup flow (passed after 1 retry)

🪟 Windows

  • chat_input.spec.ts > send button disabled during pending proposal (passed after 1 retry)
  • setup_flow.spec.ts > Setup Flow > setup banner shows correct state when node.js is installed (passed after 1 retry)

📊 View full report

@wwwillchen
Copy link
Collaborator

@BugBot run

@wwwillchen
Copy link
Collaborator

@BugBot run

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

7 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +347 to +353
if (editingQueuedMessageId) {
updateQueuedMessage(editingQueuedMessageId, {
prompt: currentInput,
});
setInputValue("");
setEditingQueuedMessageId(null);
return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only updates prompt, not attachments or selectedComponents

When editing a queued message, the current implementation only saves the modified prompt text. If the user had attached files or selected components in the original queued message, those remain unchanged even if the user modifies them during editing.

Suggested change
if (editingQueuedMessageId) {
updateQueuedMessage(editingQueuedMessageId, {
prompt: currentInput,
});
setInputValue("");
setEditingQueuedMessageId(null);
return;
if (editingQueuedMessageId) {
updateQueuedMessage(editingQueuedMessageId, {
prompt: currentInput,
attachments,
selectedComponents: componentsToSend,
});
setInputValue("");
clearAttachments();
setSelectedComponents([]);
setEditingQueuedMessageId(null);
return;
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/components/chat/ChatInput.tsx
Line: 347:353

Comment:
only updates `prompt`, not `attachments` or `selectedComponents`

When editing a queued message, the current implementation only saves the modified prompt text. If the user had attached files or selected components in the original queued message, those remain unchanged even if the user modifies them during editing.

```suggestion
if (editingQueuedMessageId) {
  updateQueuedMessage(editingQueuedMessageId, {
    prompt: currentInput,
    attachments,
    selectedComponents: componentsToSend,
  });
  setInputValue("");
  clearAttachments();
  setSelectedComponents([]);
  setEditingQueuedMessageId(null);
  return;
}
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +261 to +270
const handleEditQueuedMessage = useCallback(
(id: string) => {
const msg = queuedMessages.find((m) => m.id === id);
if (!msg) return;
// Load the message content into the input
setInputValue(msg.prompt);
// Set editing mode
setEditingQueuedMessageId(id);
},
[queuedMessages, setInputValue],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

editing doesn't restore attachments or selectedComponents to the UI

When editing a queued message, only the prompt text is loaded into the input field. The message's attachments and selected components aren't restored, so users can't see or modify them during editing. This creates an inconsistent editing experience where parts of the message are invisible.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/components/chat/ChatInput.tsx
Line: 261:270

Comment:
editing doesn't restore `attachments` or `selectedComponents` to the UI

When editing a queued message, only the prompt text is loaded into the input field. The message's attachments and selected components aren't restored, so users can't see or modify them during editing. This creates an inconsistent editing experience where parts of the message are invisible.

How can I resolve this? If you propose a fix, please make it concise.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 14, 2026

🔍 Dyadbot Code Review Summary

Reviewed by 3 independent agents (Correctness Expert, Code Health Expert, UX Wizard).
Found 5 new issue(s) after consensus voting and deduplication against existing comments.

Summary

Severity Count
🔴 HIGH 1
🟡 MEDIUM 3
🟢 LOW 1

Issues to Address

Severity File Issue
🔴 HIGH src/hooks/useStreamChat.ts:340-387 Queue-processing effect can send a message the user is currently editing
🟡 MEDIUM src/components/chat/QueuedMessagesList.tsx:55 Action buttons invisible to keyboard & touch users
🟡 MEDIUM src/components/chat/QueuedMessagesList.tsx:114 Hardcoded English strings not using i18n
🟡 MEDIUM src/components/chat/ChatInput.tsx:261 Editing a queued message silently overwrites current input text
🟢 Low Priority Issues (1 item)
  • Collapse/expand toggle missing aria-expanded attribute - src/components/chat/QueuedMessagesList.tsx:122

See inline comments for details.

Generated by Dyadbot code review

)}

{/* Action buttons - visible on hover */}
<div className="flex items-center gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 MEDIUM | accessibility | Consensus: 3/3

Action buttons invisible to keyboard & touch users

The action buttons use opacity-0 group-hover:opacity-100 which means they are only visible on mouse hover. Keyboard-only users tabbing through the list will land on invisible buttons they cannot see. Touch/mobile users have no hover state at all, making these buttons permanently inaccessible. There are also no focus-visible styles to reveal the buttons when focused via keyboard.

Additionally, the buttons use title attributes but no aria-label. Screen readers may not consistently announce title on buttons. The rest of the codebase uses aria-label for icon-only buttons.

💡 Suggestion: Add group-focus-within:opacity-100 to the container so buttons appear when any child receives keyboard focus. Add aria-label attributes matching the title values:

Suggested change
<div className="flex items-center gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity">
<div className="flex items-center gap-0.5 opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 transition-opacity">

Comment on lines +114 to +118
const statusText = hasError
? "will send after a successful response"
: isStreaming
? "will send after current response"
: "ready to send";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 MEDIUM | consistency | Consensus: 2/3

Hardcoded English strings not using i18n

All user-facing strings in this component are hardcoded English: "will send after a successful response", "will send after current response", "ready to send", "Queued", and all button titles ("Edit", "Move up", "Move down", "Delete"). The editing banner in ChatInput.tsx (line 605) also has a hardcoded "Editing queued message" string.

Every other component in src/components/chat/ uses useTranslation("chat") for user-visible text.

💡 Suggestion: Import useTranslation from react-i18next and use translation keys for all user-visible strings, consistent with the rest of the codebase. Add corresponding entries to the chat translation files.

Comment on lines +261 to +267
const handleEditQueuedMessage = useCallback(
(id: string) => {
const msg = queuedMessages.find((m) => m.id === id);
if (!msg) return;
// Load the message content into the input
setInputValue(msg.prompt);
// Set editing mode
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 MEDIUM | UX / data loss | Consensus: 2/3

Editing a queued message silently overwrites current input text

When the user clicks Edit on a queued message, handleEditQueuedMessage replaces the input value with the queued message's prompt. If the user was in the middle of typing something in the input field, that text is silently lost with no confirmation and no way to recover.

💡 Suggestion: Before overwriting the input, check if inputValue.trim() is non-empty and either: (a) queue the current input first as a new message, (b) warn the user that their current text will be replaced, or (c) store it temporarily so it can be restored when editing is cancelled via the "Cancel" button.

Comment on lines +340 to +387
// Process first queued message when streaming ends successfully
useEffect(() => {
if (!chatId || !shouldProcessQueue) return;

const queuedMessages = queuedMessagesById.get(chatId) ?? [];
const completedSuccessfully =
streamCompletedSuccessfullyById.get(chatId) ?? false;

// Only process queue if we have confirmation that the stream completed successfully
// This prevents race conditions where the queue might be processed during cancellation
if (queuedMessages.length > 0 && completedSuccessfully) {
// Clear the successful completion flag first to prevent loops
setStreamCompletedSuccessfullyById((prev) => {
const next = new Map(prev);
next.set(chatId, false);
return next;
});

// Get and remove the first message atomically by extracting it inside the setter
// This prevents race conditions where the queue might be modified between
// reading firstMessage and updating the queue
let messageToSend: QueuedMessageItem | undefined;
setQueuedMessagesById((prev) => {
const next = new Map(prev);
const current = prev.get(chatId) ?? [];
const [first, ...remainingMessages] = current;
messageToSend = first;
if (remainingMessages.length > 0) {
next.set(chatId, remainingMessages);
} else {
next.delete(chatId);
}
return next;
});

if (!messageToSend) return;

posthog.capture("chat:submit", { chatMode: settings?.selectedChatMode });

// Send the first message
streamMessage({
prompt: messageToSend.prompt,
chatId,
redo: false,
attachments: messageToSend.attachments,
selectedComponents: messageToSend.selectedComponents,
});
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 HIGH | race-condition | Consensus: 2/3

Queue-processing effect can send a message the user is currently editing

When a user clicks "Edit" on a queued message, editingQueuedMessageId is set in ChatInput state and the message content is loaded into the input. However, this useEffect operates independently with no awareness of the editing state. When the current stream completes, the effect pops the first message and sends it immediately.

If the user is editing the first queued message when the stream completes:

  1. The effect sends the original (pre-edit) version of the message
  2. The message is removed from the queue
  3. editingQueuedMessageId in ChatInput still references the now-deleted message
  4. When the user presses Enter, updateQueuedMessage silently fails (no matching ID)
  5. The user's edit is lost with no indication

💡 Suggestion: Pass editingQueuedMessageId into the hook (or lift it to an atom) so the queue-processing effect can skip processing when the first message is being edited. Alternatively, skip the first message and send the second one, or clear the editing state and notify the user when their in-progress edit is dequeued.

@github-actions
Copy link
Contributor

🎭 Playwright Test Results

❌ Some tests failed

OS Passed Failed Flaky Skipped
🍎 macOS 353 0 2 112
🪟 Windows 357 1 6 112

Summary: 710 passed, 1 failed, 8 flaky, 224 skipped

Failed Tests

🪟 Windows

  • chat_mode.spec.ts > chat mode selector - ask mode
    • TimeoutError: locator.click: Timeout 30000ms exceeded.

⚠️ Flaky Tests

🍎 macOS

  • setup_flow.spec.ts > Setup Flow > setup banner shows correct state when node.js is installed (passed after 1 retry)
  • setup_flow.spec.ts > Setup Flow > ai provider setup flow (passed after 1 retry)

🪟 Windows

  • attach_image.spec.ts > attach image - chat - upload to codebase (passed after 1 retry)
  • chat_input.spec.ts > send button disabled during pending proposal (passed after 1 retry)
  • context_manage.spec.ts > manage context - exclude paths with smart context (passed after 1 retry)
  • edit_code.spec.ts > edit code (passed after 1 retry)
  • setup_flow.spec.ts > Setup Flow > setup banner shows correct state when node.js is installed (passed after 1 retry)
  • setup_flow.spec.ts > Setup Flow > node.js install flow (passed after 1 retry)

📊 View full report

Copy link
Collaborator

@wwwillchen wwwillchen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

awesome, thanks!

@wwwillchen wwwillchen merged commit 11b33ef into dyad-sh:main Feb 14, 2026
15 of 16 checks passed
@github-actions
Copy link
Contributor

🎭 Playwright Test Results

❌ Some tests failed

OS Passed Failed Flaky Skipped
🍎 macOS 361 0 3 112
🪟 Windows 362 1 4 112

Summary: 723 passed, 1 failed, 7 flaky, 224 skipped

Failed Tests

🪟 Windows

  • setup_flow.spec.ts > Setup Flow > node.js install flow
    • Error: expect(locator).toBeVisible() failed

⚠️ Flaky Tests

🍎 macOS

  • engine.spec.ts > regular auto should send message to engine (passed after 1 retry)
  • setup_flow.spec.ts > Setup Flow > setup banner shows correct state when node.js is installed (passed after 1 retry)
  • smart_context_options.spec.ts > switching smart context mode saves the right setting (passed after 1 retry)

🪟 Windows

  • context_manage.spec.ts > manage context - exclude paths with smart context (passed after 1 retry)
  • edit_code.spec.ts > edit code (passed after 1 retry)
  • setup_flow.spec.ts > Setup Flow > setup banner shows correct state when node.js is installed (passed after 1 retry)
  • themes_management.spec.ts > themes management - create theme from chat input (passed after 1 retry)

📊 View full report

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-human:review-issue ai agent flagged an issue that requires human review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants