detached 9035106

Recover queued messages with Option+Up

Pressing Option+Up while the agent is busy now pops the most recently
queued ('sliding in') message back into the editor so the user can
edit and resend it. Repeated presses keep peeling messages off the
tail of the queue, newest first; each press replaces the editor
contents rather than appending. When the queue is empty the keypress
falls through to the existing scroll-up behavior.

A muted hint row underneath the chips advertises the shortcut, using
the same color as the model info on the status bar so it reads as
ambient metadata.
commit
90351066b1593abc75f1eb146378dd30effe40c3
author
patriceckhart · · 2026-05-07 17:41 UTC
parent
63694af
signature
unsigned
refs
tag: v0.1.66
1 file · +25 -0
M internal/agent/modes/interactive.go +25 -0
@@ -1001,6 +1001,12 @@ func (i *Interactive) redraw() {
10011001 text := truncateLine(q, cols-17)
10021002 queue = append(queue, label+i.cfg.Theme.FG256(i.cfg.Theme.Muted, text))
10031003 }
1004+ // Hint row, rendered in the same muted tone as the model
1005+ // info on the status bar so it reads as ambient metadata
1006+ // rather than a chip. Tells the user how to recover the
1007+ // most recent queued message back into the editor.
1008+ hint := " Press Option+↑ to slide back into input"
1009+ queue = append(queue, i.cfg.Theme.FG256(i.cfg.Theme.Muted, hint))
10041010 }
10051011
10061012 // Bottom-sticky sections (always visible, never scroll). Each
@@ -1728,6 +1734,25 @@ func (i *Interactive) handleKey(ctx context.Context, k tui.Key) (done bool) {
17281734 i.scrollBy(-i.chatPage())
17291735 return false
17301736 case tui.KeyUp:
1737+ // Alt/Option+Up: pop the most recently queued ("sliding in")
1738+ // message back into the editor so the user can edit and
1739+ // resend it. Repeated presses keep peeling messages off the
1740+ // tail of the queue; each press *replaces* the editor
1741+ // contents (we don't append/push). When the queue is empty
1742+ // the keypress falls through to the normal scroll behavior.
1743+ if k.Alt {
1744+ i.mu.Lock()
1745+ if n := len(i.queued); n > 0 {
1746+ text := i.queued[n-1]
1747+ i.queued = i.queued[:n-1]
1748+ i.mu.Unlock()
1749+ i.ed.SetValue(text)
1750+ i.inputHistoryIndex = -1
1751+ i.invalidate()
1752+ return false
1753+ }
1754+ i.mu.Unlock()
1755+ }
17311756 // Always use up/down for chat scrolling, even when the editor
17321757 // contains text. This makes keyboard scrolling consistent with
17331758 // a draft present at the cost of disabling vertical cursor
@@ -1001,6 +1001,12 @@ func (i *Interactive) redraw() {
1001 text := truncateLine(q, cols-17)1001 text := truncateLine(q, cols-17)
1002 queue = append(queue, label+i.cfg.Theme.FG256(i.cfg.Theme.Muted, text))1002 queue = append(queue, label+i.cfg.Theme.FG256(i.cfg.Theme.Muted, text))
1003 }1003 }
1004+ // Hint row, rendered in the same muted tone as the model
1005+ // info on the status bar so it reads as ambient metadata
1006+ // rather than a chip. Tells the user how to recover the
1007+ // most recent queued message back into the editor.
1008+ hint := " Press Option+↑ to slide back into input"
1009+ queue = append(queue, i.cfg.Theme.FG256(i.cfg.Theme.Muted, hint))
1004 }1010 }
1005 1011
1006 // Bottom-sticky sections (always visible, never scroll). Each1012 // Bottom-sticky sections (always visible, never scroll). Each
@@ -1728,6 +1734,25 @@ func (i *Interactive) handleKey(ctx context.Context, k tui.Key) (done bool) {
1728 i.scrollBy(-i.chatPage())1734 i.scrollBy(-i.chatPage())
1729 return false1735 return false
1730 case tui.KeyUp:1736 case tui.KeyUp:
1737+ // Alt/Option+Up: pop the most recently queued ("sliding in")
1738+ // message back into the editor so the user can edit and
1739+ // resend it. Repeated presses keep peeling messages off the
1740+ // tail of the queue; each press *replaces* the editor
1741+ // contents (we don't append/push). When the queue is empty
1742+ // the keypress falls through to the normal scroll behavior.
1743+ if k.Alt {
1744+ i.mu.Lock()
1745+ if n := len(i.queued); n > 0 {
1746+ text := i.queued[n-1]
1747+ i.queued = i.queued[:n-1]
1748+ i.mu.Unlock()
1749+ i.ed.SetValue(text)
1750+ i.inputHistoryIndex = -1
1751+ i.invalidate()
1752+ return false
1753+ }
1754+ i.mu.Unlock()
1755+ }
1731 // Always use up/down for chat scrolling, even when the editor1756 // Always use up/down for chat scrolling, even when the editor
1732 // contains text. This makes keyboard scrolling consistent with1757 // contains text. This makes keyboard scrolling consistent with
1733 // a draft present at the cost of disabling vertical cursor1758 // a draft present at the cost of disabling vertical cursor

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 476

↑↓ moveenter openesc close