detached 37eb752

tui: drop redundant leading blanks in live tool overlay

renderToolCall used to prepend a blank line at the top of each of
its three branches (streaming-with-body, finished-no-result,
finished-with-result) so the live box wouldn't sit flush against
the prose / previous tool block above it. That made sense before
each tool result owned its own complete box \u2014 back then the
assistant message also injected a blank before its embedded top
edge, and the two paths matched.

After the box-ownership refactor the transcript path no longer
emits a leading blank (Build()'s natural inter-message separator
provides one), but renderToolCall still did, so a streaming tool
sat two blank rows below the previous content while the same
call, once finalised, sat only one. The visible "snap tighter"
when streaming ended was the doubled gap collapsing.

Drop the three leading blanks. Build()'s inter-message blank is
the single source of vertical spacing now, so streaming and
finalised forms have identical gaps.
commit
37eb752cb29f784f57e95f0e1afa20f10dc16ab9
author
patriceckhart · · 2026-04-27 15:24 UTC
parent
48d22c4
signature
unsigned
refs
tag: v0.1.30
1 file · +6 -6
M internal/tui/view.go +6 -6
@@ -598,9 +598,12 @@ func (v *View) renderToolCall(tc ToolCallView, width int) []string {
598598 }
599599 label := tc.Name + " " + arg
600600
601- // Every box is preceded by a blank line so it doesn't sit
602- // flush against the prose / previous tool block above it.
603- // Matches the transcript-side framing in renderMessage.
601+ // No leading blank: Build()'s inter-message separator already
602+ // places one blank row between the previous transcript content
603+ // and the live overlay, matching the spacing the same call has
604+ // once it finalises into a transcript box. Adding another blank
605+ // here would double the gap during streaming and visibly tighten
606+ // when the overlay disappears.
604607
605608 // Streaming body (write/edit): top edge with the label, body
606609 // rows wrapped with vertical edges, bottom edge to close the
@@ -608,7 +611,6 @@ func (v *View) renderToolCall(tc ToolCallView, width int) []string {
608611 // the same frame the transcript renders the closed box, so
609612 // there's no visible hop.
610613 if tc.Streaming && tc.Result == "" {
611- lines = append(lines, "")
612614 lines = append(lines, toolBoxTop(v.Theme, label, width))
613615 lines = append(lines, toolBoxSide(v.Theme, "", width))
614616 if body := v.renderLiveToolBody(tc, width); len(body) > 0 {
@@ -623,7 +625,6 @@ func (v *View) renderToolCall(tc ToolCallView, width int) []string {
623625 // directly closed by the bottom. Avoids a blank interior row
624626 // for no-output tools.
625627 if tc.Result == "" {
626- lines = append(lines, "")
627628 lines = append(lines, toolBoxTop(v.Theme, label, width))
628629 lines = append(lines, toolBoxBottom(v.Theme, width))
629630 return lines
@@ -634,7 +635,6 @@ func (v *View) renderToolCall(tc ToolCallView, width int) []string {
634635 // vertical edges; bottom edge closes the box. Blank interior
635636 // rows after the top and before the bottom give the body a bit
636637 // of breathing room from the corners.
637- lines = append(lines, "")
638638 lines = append(lines, toolBoxTop(v.Theme, label, width))
639639 lines = append(lines, toolBoxSide(v.Theme, "", width))
640640 color := v.Theme.ToolOut
@@ -598,9 +598,12 @@ func (v *View) renderToolCall(tc ToolCallView, width int) []string {
598 }598 }
599 label := tc.Name + " " + arg599 label := tc.Name + " " + arg
600 600
601- // Every box is preceded by a blank line so it doesn't sit601+ // No leading blank: Build()'s inter-message separator already
602- // flush against the prose / previous tool block above it.602+ // places one blank row between the previous transcript content
603- // Matches the transcript-side framing in renderMessage.603+ // and the live overlay, matching the spacing the same call has
604+ // once it finalises into a transcript box. Adding another blank
605+ // here would double the gap during streaming and visibly tighten
606+ // when the overlay disappears.
604 607
605 // Streaming body (write/edit): top edge with the label, body608 // Streaming body (write/edit): top edge with the label, body
606 // rows wrapped with vertical edges, bottom edge to close the609 // rows wrapped with vertical edges, bottom edge to close the
@@ -608,7 +611,6 @@ func (v *View) renderToolCall(tc ToolCallView, width int) []string {
608 // the same frame the transcript renders the closed box, so611 // the same frame the transcript renders the closed box, so
609 // there's no visible hop.612 // there's no visible hop.
610 if tc.Streaming && tc.Result == "" {613 if tc.Streaming && tc.Result == "" {
611- lines = append(lines, "")
612 lines = append(lines, toolBoxTop(v.Theme, label, width))614 lines = append(lines, toolBoxTop(v.Theme, label, width))
613 lines = append(lines, toolBoxSide(v.Theme, "", width))615 lines = append(lines, toolBoxSide(v.Theme, "", width))
614 if body := v.renderLiveToolBody(tc, width); len(body) > 0 {616 if body := v.renderLiveToolBody(tc, width); len(body) > 0 {
@@ -623,7 +625,6 @@ func (v *View) renderToolCall(tc ToolCallView, width int) []string {
623 // directly closed by the bottom. Avoids a blank interior row625 // directly closed by the bottom. Avoids a blank interior row
624 // for no-output tools.626 // for no-output tools.
625 if tc.Result == "" {627 if tc.Result == "" {
626- lines = append(lines, "")
627 lines = append(lines, toolBoxTop(v.Theme, label, width))628 lines = append(lines, toolBoxTop(v.Theme, label, width))
628 lines = append(lines, toolBoxBottom(v.Theme, width))629 lines = append(lines, toolBoxBottom(v.Theme, width))
629 return lines630 return lines
@@ -634,7 +635,6 @@ func (v *View) renderToolCall(tc ToolCallView, width int) []string {
634 // vertical edges; bottom edge closes the box. Blank interior635 // vertical edges; bottom edge closes the box. Blank interior
635 // rows after the top and before the bottom give the body a bit636 // rows after the top and before the bottom give the body a bit
636 // of breathing room from the corners.637 // of breathing room from the corners.
637- lines = append(lines, "")
638 lines = append(lines, toolBoxTop(v.Theme, label, width))638 lines = append(lines, toolBoxTop(v.Theme, label, width))
639 lines = append(lines, toolBoxSide(v.Theme, "", width))639 lines = append(lines, toolBoxSide(v.Theme, "", width))
640 color := v.Theme.ToolOut640 color := v.Theme.ToolOut

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