detached 0a670c0

interactive: blank line above status block

Insert one blank row between the chat area and the status bar
(spinner / model / cwd). Previously the status block sat flush
against the chat content above; the blank gives it air whether
the agent is busy (spinner first) or idle (model line first).

Cursor row math bumped to skip both the new top blank and the
existing blank between statusLines and edLines so the input
cursor still lands on the right cell.
commit
0a670c01e257b298a4c91fc70504ef852f066ed8
author
patriceckhart · · 2026-04-28 07:08 UTC
parent
cead9fd
signature
unsigned
refs
tag: v0.1.35
1 file · +14 -10
M internal/agent/modes/interactive.go +14 -10
@@ -816,15 +816,17 @@ func (i *Interactive) redraw() {
816816 queue = append(queue, "")
817817 }
818818
819- // Bottom-sticky sections (always visible, never scroll). A blank
820- // row is inserted between the status bar and the editor, and a
821- // trailing blank row is added at the very bottom, so the input
822- // has breathing room from the surrounding chrome instead of
823- // sitting flush against the status line and the terminal edge.
824- bottom := make([]string, 0, len(dialog)+len(suggest)+len(queue)+len(edLines)+3)
819+ // Bottom-sticky sections (always visible, never scroll). One
820+ // blank row above statusLines so the spinner / model / cwd
821+ // block doesn't sit flush against the chat content above it,
822+ // one blank between status bar and editor for input breathing
823+ // room, and one trailing blank at the very bottom so the
824+ // editor isn't flush against the terminal edge.
825+ bottom := make([]string, 0, len(dialog)+len(suggest)+len(queue)+len(edLines)+4)
825826 bottom = append(bottom, dialog...)
826827 bottom = append(bottom, suggest...)
827828 bottom = append(bottom, queue...)
829+ bottom = append(bottom, "")
828830 bottom = append(bottom, statusLines...)
829831 bottom = append(bottom, "")
830832 bottom = append(bottom, edLines...)
@@ -941,10 +943,12 @@ func (i *Interactive) redraw() {
941943 // the blinking cursor shows where the user is actually typing.
942944 // Dialogs without a cursor (model picker, /help, /login, etc.)
943945 // return -1 and the main editor keeps the cursor.
944- // +1 accounts for the blank row inserted between statusLines
945- // and edLines above. Without it the rendered cursor would land
946- // on the blank instead of inside the editor row.
947- cursorRow := len(visibleChat) + len(dialog) + len(suggest) + len(queue) + len(statusLines) + 1 + curR
946+ // +2 accounts for the blank row above statusLines (so the
947+ // status block has air above it) and the blank row between
948+ // statusLines and edLines (input breathing room). Without
949+ // these the rendered cursor would land on a blank instead of
950+ // inside the editor row.
951+ cursorRow := len(visibleChat) + len(dialog) + len(suggest) + len(queue) + 1 + len(statusLines) + 1 + curR
948952 cursorCol := curC
949953 if i.btwDialog.Active() {
950954 if r, c := i.btwDialog.CursorPos(cols); r >= 0 {
@@ -816,15 +816,17 @@ func (i *Interactive) redraw() {
816 queue = append(queue, "")816 queue = append(queue, "")
817 }817 }
818 818
819- // Bottom-sticky sections (always visible, never scroll). A blank819+ // Bottom-sticky sections (always visible, never scroll). One
820- // row is inserted between the status bar and the editor, and a820+ // blank row above statusLines so the spinner / model / cwd
821- // trailing blank row is added at the very bottom, so the input821+ // block doesn't sit flush against the chat content above it,
822- // has breathing room from the surrounding chrome instead of822+ // one blank between status bar and editor for input breathing
823- // sitting flush against the status line and the terminal edge.823+ // room, and one trailing blank at the very bottom so the
824- bottom := make([]string, 0, len(dialog)+len(suggest)+len(queue)+len(edLines)+3)824+ // editor isn't flush against the terminal edge.
825+ bottom := make([]string, 0, len(dialog)+len(suggest)+len(queue)+len(edLines)+4)
825 bottom = append(bottom, dialog...)826 bottom = append(bottom, dialog...)
826 bottom = append(bottom, suggest...)827 bottom = append(bottom, suggest...)
827 bottom = append(bottom, queue...)828 bottom = append(bottom, queue...)
829+ bottom = append(bottom, "")
828 bottom = append(bottom, statusLines...)830 bottom = append(bottom, statusLines...)
829 bottom = append(bottom, "")831 bottom = append(bottom, "")
830 bottom = append(bottom, edLines...)832 bottom = append(bottom, edLines...)
@@ -941,10 +943,12 @@ func (i *Interactive) redraw() {
941 // the blinking cursor shows where the user is actually typing.943 // the blinking cursor shows where the user is actually typing.
942 // Dialogs without a cursor (model picker, /help, /login, etc.)944 // Dialogs without a cursor (model picker, /help, /login, etc.)
943 // return -1 and the main editor keeps the cursor.945 // return -1 and the main editor keeps the cursor.
944- // +1 accounts for the blank row inserted between statusLines946+ // +2 accounts for the blank row above statusLines (so the
945- // and edLines above. Without it the rendered cursor would land947+ // status block has air above it) and the blank row between
946- // on the blank instead of inside the editor row.948+ // statusLines and edLines (input breathing room). Without
947- cursorRow := len(visibleChat) + len(dialog) + len(suggest) + len(queue) + len(statusLines) + 1 + curR949+ // these the rendered cursor would land on a blank instead of
950+ // inside the editor row.
951+ cursorRow := len(visibleChat) + len(dialog) + len(suggest) + len(queue) + 1 + len(statusLines) + 1 + curR
948 cursorCol := curC952 cursorCol := curC
949 if i.btwDialog.Active() {953 if i.btwDialog.Active() {
950 if r, c := i.btwDialog.CursorPos(cols); r >= 0 {954 if r, c := i.btwDialog.CursorPos(cols); r >= 0 {

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