Files
hermes-agent/ui-tui/src/components/queuedMessages.tsx
Brooklyn Nicholson ea1012f59f feat(tui): delete queued message while editing with ctrl-x / cancel with esc
Today there's no way to remove a queued message — ↑ loads it for edit,
ctrl-K dispatches the head, but a draft you no longer want stays put
forever. ctrl-C just clears the composer and exits edit mode without
touching the queue.

Two new bindings, both gated on queueEditIdx !== null so they're
inert when the user isn't pointing at a queue item:

- ctrl-X — delete the queue item being edited, clear composer, exit
  edit mode.  "cut" matches the mental model and doesn't collide with
  any existing binding.
- esc — cancel the edit (composer clears, item stays in queue).
  Mirrors ctrl-C's existing behavior so muscle memory has two paths.

Header line now reads `queued (3) · editing 2 · ⌃X delete · esc cancel`
when in edit mode, so the affordance is discoverable without /help.
The /help hotkey table also gets a Ctrl+X entry.

ctrl-C is intentionally unchanged: it should never destroy queued
content.  Cancel is non-destructive (esc / ctrl-C); only ctrl-X
removes the item.
2026-04-27 15:24:14 -05:00

64 lines
1.7 KiB
TypeScript

import { Box, Text } from '@hermes/ink'
import { compactPreview } from '../lib/text.js'
import type { Theme } from '../theme.js'
export const QUEUE_WINDOW = 3
export function getQueueWindow(queueLen: number, queueEditIdx: number | null) {
const start =
queueEditIdx === null ? 0 : Math.max(0, Math.min(queueEditIdx - 1, Math.max(0, queueLen - QUEUE_WINDOW)))
const end = Math.min(queueLen, start + QUEUE_WINDOW)
return { end, showLead: start > 0, showTail: end < queueLen, start }
}
export function QueuedMessages({ cols, queueEditIdx, queued, t }: QueuedMessagesProps) {
if (!queued.length) {
return null
}
const q = getQueueWindow(queued.length, queueEditIdx)
return (
<Box flexDirection="column" marginTop={1}>
<Text color={t.color.dim} dimColor>
queued ({queued.length})
{queueEditIdx !== null ? ` · editing ${queueEditIdx + 1} · ⌃X delete · esc cancel` : ''}
</Text>
{q.showLead && (
<Text color={t.color.dim} dimColor>
{' '}
</Text>
)}
{queued.slice(q.start, q.end).map((item, i) => {
const idx = q.start + i
const active = queueEditIdx === idx
return (
<Text color={active ? t.color.amber : t.color.dim} dimColor key={`${idx}-${item.slice(0, 16)}`}>
{active ? '▸' : ' '} {idx + 1}. {compactPreview(item, Math.max(16, cols - 10))}
</Text>
)
})}
{q.showTail && (
<Text color={t.color.dim} dimColor>
{' '}and {queued.length - q.end} more
</Text>
)}
</Box>
)
}
interface QueuedMessagesProps {
cols: number
queueEditIdx: number | null
queued: string[]
t: Theme
}