feat(cli): queue input editing — pop queued messages for editing via ↑/ESC#2871
Conversation
Allow users to edit queued messages by pressing the Up arrow key when the cursor is at the top of the input. All queued messages are popped into the input field for revision before resubmission, reducing wasted turns from incorrect queued instructions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
📋 Review SummaryThis PR introduces a feature to allow users to edit queued messages by pressing the Up arrow key when the cursor is at the top of the input field. The implementation adds 🔍 General Feedback
🎯 Specific Feedback🟡 High
🟢 Medium
🔵 Low
✅ Highlights
|
Qwen Code PR #2871 implements queue input editing via Up arrow key: - popLast() on AsyncMessageQueue - popAllMessages() on useMessageQueue hook - "Press ↑ to edit" hint in QueuedMessageDisplay Updated matrix (进展 column) and P2 detail section. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add popAllQueuedMessages mock and messageQueue to UIState/UIActions mocks in InputPrompt.test.tsx to fix 25 test failures - Add !isAttachmentMode guard to prevent queue pop from conflicting with attachment navigation - Add single-message popAllMessages test case Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
#39) Qwen Code PR #2871 implements queue input editing via Up arrow key: - popLast() on AsyncMessageQueue - popAllMessages() on useMessageQueue hook - "Press ↑ to edit" hint in QueuedMessageDisplay Updated matrix (进展 column) and P2 detail section. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Thanks for the thorough review! Addressed the feedback in 9a419ca: 🟡 High
🟢 Medium
🟢 Low
|
There was a problem hiding this comment.
Pull request overview
Adds a CLI UX improvement to let users pull queued messages back into the input for editing (via Up-arrow at top of input), plus a UI hint and supporting queue APIs.
Changes:
- Add
popLast()toAsyncMessageQueue(core) with unit tests. - Add
popAllMessages()to the CLIuseMessageQueuehook and surface it throughUIActionstoInputPrompt. - Show a localized “Press ↑ to edit queued messages” hint in
QueuedMessageDisplayand add locale strings + tests.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/core/src/utils/asyncMessageQueue.ts | Adds popLast() API on the queue |
| packages/core/src/utils/asyncMessageQueue.test.ts | Tests for popLast() |
| packages/cli/src/ui/hooks/useMessageQueue.ts | Adds popAllMessages() to clear+return queued messages |
| packages/cli/src/ui/hooks/useMessageQueue.test.ts | Tests for popAllMessages() |
| packages/cli/src/ui/contexts/UIActionsContext.tsx | Extends UIActions with popAllQueuedMessages() |
| packages/cli/src/ui/components/QueuedMessageDisplay.tsx | Adds localized hint for editing queued messages |
| packages/cli/src/ui/components/QueuedMessageDisplay.test.tsx | Tests hint rendering |
| packages/cli/src/ui/components/InputPrompt.tsx | Handles Up-arrow to pop queued messages into the buffer |
| packages/cli/src/ui/components/InputPrompt.test.tsx | Updates mocks for new UI state/action fields |
| packages/cli/src/ui/AppContainer.tsx | Wires popAllMessages into UI actions/state |
| packages/cli/src/i18n/locales/en.js | Adds new translation key |
| packages/cli/src/i18n/locales/de.js | Adds new translation key |
| packages/cli/src/i18n/locales/ja.js | Adds new translation key |
| packages/cli/src/i18n/locales/pt.js | Adds new translation key |
| packages/cli/src/i18n/locales/ru.js | Adds new translation key |
| packages/cli/src/i18n/locales/zh.js | Adds new translation key |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
… docs - Only trigger queue pop on NAVIGATION_UP (arrow key), not HISTORY_UP (Ctrl+P), preserving existing Ctrl+P history navigation behavior - Update AsyncMessageQueue class docs to describe popLast() LIFO semantics - Add InputPrompt tests: Up arrow pops queue, Up arrow falls back to history when queue empty, Ctrl+P not intercepted by queue pop Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Update @fileoverview to describe FIFO+LIFO capability instead of "Simple FIFO queue" - Use queueRef to make popAllMessages atomic, preventing duplicate pops from key auto-repeat before React re-renders Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Update queueRef inside addMessage setter and clearQueue to keep ref in sync between renders, preventing stale reads after clearQueue - When popAllQueuedMessages returns null (queue already cleared), fall through to normal history navigation instead of consuming the key Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
packages/cli/src/ui/hooks/useMessageQueue.ts:86
useEffectauto-submission clears React state viasetMessageQueue([])but does not clearqueueRef.current. In the window before the next render,popAllMessages()can still read/return the stale queued items from the ref (e.g., if the user presses ↑ right as streaming becomes Idle), leading to duplicate insertion/editing of messages that were just auto-submitted. ClearqueueRef.currentat the same time you clear state in the Idle auto-submit path (before callingsubmitQuery) so the ref remains the source of truth between renders.
// Process queued messages when streaming becomes idle
useEffect(() => {
if (
isConfigInitialized &&
streamingState === StreamingState.Idle &&
messageQueue.length > 0
) {
// Combine all messages with double newlines for clarity
const combinedMessage = messageQueue.join('\n\n');
// Clear the queue and submit
setMessageQueue([]);
submitQuery(combinedMessage);
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Keep both popAllMessages (for Up-arrow queue editing) and drainQueue (for atomic mid-turn drain). Adopt main's ref-first write pattern. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove unused AsyncMessageQueue.popLast() (no production callers) - Change popAllMessages join separator from \n to \n\n for consistency with getQueuedMessagesText and auto-submit behavior Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…mption race The midTurnDrainRef previously used a separate messageQueueRef (synced from React state), while popAllMessages uses the hook's internal queueRef. If a tool completed between popAllMessages clearing queueRef and React re-rendering, midTurnDrainRef would read stale data and consume the same messages a second time. Switching to the hook's drainQueue makes both paths read from the same synchronous ref, eliminating the window for double consumption. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
# Conflicts: # packages/cli/src/ui/AppContainer.tsx
Add popAllMessages to useMessageQueue mock in AppContainer tests. Add test for prepending queued messages before existing input text.
- ESC pops queued messages before double-ESC clear logic - Cursor stays at user's editing position after pop via moveToOffset - Extract popQueueIntoInput helper to share logic between Up and ESC - QueuedMessageDisplay hint hides after 3 empty→non-empty transitions
Verify that when React state shows non-empty queue but the ref is already drained (popAllQueuedMessages returns null), Up arrow falls through to normal history navigation instead of getting stuck.
tanzhenxin
left a comment
There was a problem hiding this comment.
Review
Clean implementation — all issues from the previous review have been addressed. The atomic ref pattern, shared popQueueIntoInput helper, and null-return fallthrough design are well done.
Verdict
APPROVE
…↑/ESC (QwenLM#2871) * feat(cli): add queue input editing via Up arrow key Allow users to edit queued messages by pressing the Up arrow key when the cursor is at the top of the input. All queued messages are popped into the input field for revision before resubmission, reducing wasted turns from incorrect queued instructions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: add missing mocks for InputPrompt tests and attachment mode guard - Add popAllQueuedMessages mock and messageQueue to UIState/UIActions mocks in InputPrompt.test.tsx to fix 25 test failures - Add !isAttachmentMode guard to prevent queue pop from conflicting with attachment navigation - Add single-message popAllMessages test case Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address Copilot review - restrict to Up arrow, add tests, update docs - Only trigger queue pop on NAVIGATION_UP (arrow key), not HISTORY_UP (Ctrl+P), preserving existing Ctrl+P history navigation behavior - Update AsyncMessageQueue class docs to describe popLast() LIFO semantics - Add InputPrompt tests: Up arrow pops queue, Up arrow falls back to history when queue empty, Ctrl+P not intercepted by queue pop Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: update fileoverview docs and make popAllMessages atomic via ref - Update @fileoverview to describe FIFO+LIFO capability instead of "Simple FIFO queue" - Use queueRef to make popAllMessages atomic, preventing duplicate pops from key auto-repeat before React re-renders Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: sync queueRef in addMessage/clearQueue and fall through on null pop - Update queueRef inside addMessage setter and clearQueue to keep ref in sync between renders, preventing stale reads after clearQueue - When popAllQueuedMessages returns null (queue already cleared), fall through to normal history navigation instead of consuming the key Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: remove dead popLast() and align popAllMessages separator to \n\n - Remove unused AsyncMessageQueue.popLast() (no production callers) - Change popAllMessages join separator from \n to \n\n for consistency with getQueuedMessagesText and auto-submit behavior Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: use hook's drainQueue for mid-turn drain to prevent double-consumption race The midTurnDrainRef previously used a separate messageQueueRef (synced from React state), while popAllMessages uses the hook's internal queueRef. If a tool completed between popAllMessages clearing queueRef and React re-rendering, midTurnDrainRef would read stale data and consume the same messages a second time. Switching to the hook's drainQueue makes both paths read from the same synchronous ref, eliminating the window for double consumption. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: add missing popAllMessages mock and prepend branch test Add popAllMessages to useMessageQueue mock in AppContainer tests. Add test for prepending queued messages before existing input text. * feat: add ESC trigger, cursor preservation, and progressive hint - ESC pops queued messages before double-ESC clear logic - Cursor stays at user's editing position after pop via moveToOffset - Extract popQueueIntoInput helper to share logic between Up and ESC - QueuedMessageDisplay hint hides after 3 empty→non-empty transitions * test: add null-pop fallthrough test for queue race condition Verify that when React state shows non-empty queue but the ref is already drained (popAllQueuedMessages returns null), Up arrow falls through to normal history navigation instead of getting stuck. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Problem
When users queue messages during AI streaming, typos or wrong instructions waste a full turn — the queued text is submitted as-is with no way to review or correct it before it's sent.
Solution
Allow users to pop queued messages back into the input for editing before they're sent:
Key Behaviors
Cursor preservation
When existing text is in the input, queued messages are prepended and the cursor stays at the user's original editing position:
Progressive hint
"Press ↑ to edit queued messages" is shown below queued messages. It automatically hides after being displayed 3 times (per session), so experienced users aren't distracted.
Implementation
useMessageQueue.tspopAllMessages()— atomic pop viaqueueRefto prevent duplicate pops from key auto-repeatUIActionsContext.tsxpopAllQueuedMessagesto interfaceAppContainer.tsxpopAllMessages→ UIActions; use hook'sdrainQueuefor mid-turn drainInputPrompt.tsxpopQueueIntoInput()helper shared by ↑ and ESC handlers; cursor offset viamoveToOffsetQueuedMessageDisplay.tsx"Press ↑ to edit queued messages"15 files changed, +364 −18
Design Decisions & Claude Code Comparison
This feature aligns with Claude Code's queue editing behavior. Key differences are intentional:
string[]QueuedCommandobjectsisMetacommands\n\nbetween messages\nTest Plan
🤖 Generated with Claude Code