diff --git a/ui-tui/src/__tests__/useQueue.test.ts b/ui-tui/src/__tests__/useQueue.test.ts index 78cd358585..ada53589da 100644 --- a/ui-tui/src/__tests__/useQueue.test.ts +++ b/ui-tui/src/__tests__/useQueue.test.ts @@ -1,26 +1,26 @@ import { describe, expect, it } from 'vitest' -import { removeAt } from '../hooks/useQueue.js' +import { removeAtInPlace } from '../hooks/useQueue.js' -describe('removeAt', () => { +describe('removeAtInPlace', () => { it('removes the item at the given index in place', () => { const arr = ['a', 'b', 'c'] - removeAt(arr, 1) + removeAtInPlace(arr, 1) expect(arr).toEqual(['a', 'c']) }) it('is a no-op when the index is out of bounds', () => { const arr = ['a', 'b'] - removeAt(arr, -1) - removeAt(arr, 5) + removeAtInPlace(arr, -1) + removeAtInPlace(arr, 5) expect(arr).toEqual(['a', 'b']) }) it('returns the same reference (mutates in place)', () => { const arr = ['x'] - const same = removeAt(arr, 0) + const same = removeAtInPlace(arr, 0) expect(same).toBe(arr) expect(arr).toEqual([]) diff --git a/ui-tui/src/app/useInputHandlers.ts b/ui-tui/src/app/useInputHandlers.ts index 538b5561b7..84978b98c4 100644 --- a/ui-tui/src/app/useInputHandlers.ts +++ b/ui-tui/src/app/useInputHandlers.ts @@ -307,14 +307,17 @@ export function useInputHandlers(ctx: InputHandlerContext): InputHandlerResult { return scrollTranscript(key.pageUp ? -step : step) } - if (key.escape && terminal.hasSelection) { - return clearSelection() - } - + // Queue-edit cancel beats selection-clear: the queue header explicitly + // promises "Esc cancel", so honoring it takes priority over the implicit + // selection-dismissal convention. Without an active edit, fall through. if (key.escape && cState.queueEditIdx !== null) { return cActions.clearIn() } + if (key.escape && terminal.hasSelection) { + return clearSelection() + } + if (key.upArrow && !cState.inputBuf.length) { const inputSel = getInputSelection() const cursor = inputSel && inputSel.start === inputSel.end ? inputSel.start : null diff --git a/ui-tui/src/components/queuedMessages.tsx b/ui-tui/src/components/queuedMessages.tsx index 6e954c8345..f66b6fd314 100644 --- a/ui-tui/src/components/queuedMessages.tsx +++ b/ui-tui/src/components/queuedMessages.tsx @@ -24,8 +24,9 @@ export function QueuedMessages({ cols, queueEditIdx, queued, t }: QueuedMessages return ( - queued ({queued.length}) - {queueEditIdx !== null ? ` · editing ${queueEditIdx + 1} · ⌃X delete · esc cancel` : ''} + {`queued (${queued.length})${ + queueEditIdx !== null ? ` · editing ${queueEditIdx + 1} · Ctrl+X delete · Esc cancel` : '' + }`} {q.showLead && ( diff --git a/ui-tui/src/content/hotkeys.ts b/ui-tui/src/content/hotkeys.ts index c5e32cd2a2..b79d08061b 100644 --- a/ui-tui/src/content/hotkeys.ts +++ b/ui-tui/src/content/hotkeys.ts @@ -23,7 +23,7 @@ export const HOTKEYS: [string, string][] = [ [paste + '+V / /paste', 'paste text; /paste attaches clipboard image'], ['Tab', 'apply completion'], ['↑/↓', 'completions / queue edit / history'], - ['Ctrl+X', 'delete the queued message you’re editing (esc cancels edit)'], + ['Ctrl+X', 'delete the queued message you’re editing (Esc cancels edit)'], [action + '+A/E', 'home / end of line'], [action + '+Z / ' + action + '+Y', 'undo / redo input edits'], [action + '+W', 'delete word'], diff --git a/ui-tui/src/hooks/useQueue.ts b/ui-tui/src/hooks/useQueue.ts index 18e9a6c55d..0c79ab4eb4 100644 --- a/ui-tui/src/hooks/useQueue.ts +++ b/ui-tui/src/hooks/useQueue.ts @@ -1,6 +1,8 @@ import { useCallback, useRef, useState } from 'react' -export function removeAt(arr: T[], i: number): T[] { +// Mutates `arr` in place; returned reference is the same input array, kept +// so callers can chain. Use `Array.prototype.toSpliced` if you need a copy. +export function removeAtInPlace(arr: T[], i: number): T[] { if (i < 0 || i >= arr.length) { return arr } @@ -50,7 +52,7 @@ export function useQueue() { (i: number) => { const before = queueRef.current.length - removeAt(queueRef.current, i) + removeAtInPlace(queueRef.current, i) if (queueRef.current.length !== before) { syncQueue()