tui: collapse double-blank between adjacent tool boxes
Each tool result owns its own box, but the assistant message that batched the originating tool_use blocks now renders to nothing (the box edges moved to the tool result). Build() was still emitting an inter-message blank for that empty render, which compounded with the blank from the next real message and produced two blank rows between adjacent tool boxes \u2014 visible whenever the model batched multiple tool calls without prose between them. Skip the inter-message blank when a message rendered to zero lines so an empty assistant message no longer contributes a separator row. Adjacent tool boxes now sit one blank row apart, matching the spacing between every other pair of chat blocks.
- commit
- 955a9b703b73e3076d52ddd3285bee653d61b56e
- author
- patriceckhart · · 2026-04-27 13:59 UTC
- parent
- 4b2bce3
- signature
- unsigned
- refs
- tag: v0.1.28
1 file · +9 -0
M internal/tui/view.go +9 -0
| @@ -232,6 +232,15 @@ func (v *View) BuildWithAnchors(width int) ([]string, []MessageAnchor) { | |||
| 232 | 232 | for idx := range v.Messages { | |
| 233 | 233 | anchors = append(anchors, MessageAnchor{MessageIdx: idx, Row: len(out)}) | |
| 234 | 234 | out = append(out, rendered[idx]...) | |
| 235 | + | // Skip the inter-message blank for messages that rendered | |
| 236 | + | // to nothing. An assistant message whose only content is | |
| 237 | + | // ToolCallBlock(s) has no rendered output of its own (each | |
| 238 | + | // tool result owns its box now), so emitting a blank for it | |
| 239 | + | // would compound with the blank from the next real message | |
| 240 | + | // and produce two blank rows between adjacent tool boxes. | |
| 241 | + | if len(rendered[idx]) == 0 { | |
| 242 | + | continue | |
| 243 | + | } | |
| 235 | 244 | out = append(out, "") | |
| 236 | 245 | } | |
| 237 | 246 | // Only render the streaming header/body when there's actual | |
| @@ -232,6 +232,15 @@ func (v *View) BuildWithAnchors(width int) ([]string, []MessageAnchor) { | |||||
| 232 | for idx := range v.Messages { | 232 | for idx := range v.Messages { | ||
| 233 | anchors = append(anchors, MessageAnchor{MessageIdx: idx, Row: len(out)}) | 233 | anchors = append(anchors, MessageAnchor{MessageIdx: idx, Row: len(out)}) | ||
| 234 | out = append(out, rendered[idx]...) | 234 | out = append(out, rendered[idx]...) | ||
| 235 | + | // Skip the inter-message blank for messages that rendered | |||
| 236 | + | // to nothing. An assistant message whose only content is | |||
| 237 | + | // ToolCallBlock(s) has no rendered output of its own (each | |||
| 238 | + | // tool result owns its box now), so emitting a blank for it | |||
| 239 | + | // would compound with the blank from the next real message | |||
| 240 | + | // and produce two blank rows between adjacent tool boxes. | |||
| 241 | + | if len(rendered[idx]) == 0 { | |||
| 242 | + | continue | |||
| 243 | + | } | |||
| 235 | out = append(out, "") | 244 | out = append(out, "") | ||
| 236 | } | 245 | } | ||
| 237 | // Only render the streaming header/body when there's actual | 246 | // Only render the streaming header/body when there's actual | ||