From 80bccc8d280c736d02aa6eb16aca30d6d3562709 Mon Sep 17 00:00:00 2001 From: Frank Steiler Date: Fri, 10 Apr 2026 11:02:16 +0200 Subject: [PATCH] fix(tui): remove spurious blank line from assistant messages Commit 574a4669 introduced the copy-on-hover button for assistant messages using the same "topRow+\n" pattern as user messages. However, UserMessageStyle has PaddingTop=1 (inherited from BaseMessageStyle) so PaddingTop(0)+topRow+"\n" is net-zero. AssistantMessageStyle overrides to Padding(0,1), meaning PaddingTop=0 already; PaddingTop(0) is a no-op and the unconditional "\n" prefix adds a spurious blank line to every assistant message in its normal (non-hovered) state. Fix: only use the topRow+"\n" rendering path when the copy icon is actually visible (hovered or selected). Fall back to the original messageStyle.Render(rendered) for the common case. This accepts a 1-line layout shift on hover rather than a permanent blank-line artifact on every message. Fixes: https://github.com/docker/docker-agent/issues/2368 Co-Authored-By: Claude Sonnet 4.6 --- pkg/tui/components/message/message.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/pkg/tui/components/message/message.go b/pkg/tui/components/message/message.go index b6ec4092b..3fd6a0528 100644 --- a/pkg/tui/components/message/message.go +++ b/pkg/tui/components/message/message.go @@ -145,18 +145,22 @@ func (mv *messageModel) Render(width int) string { prefix = mv.senderPrefix(msg.Sender) } - // Always reserve a top row for the copy icon to avoid layout shifts. - // The icon is only visible when hovered or selected. - innerWidth := width - messageStyle.GetHorizontalFrameSize() - var topRow string + // Show copy icon in the top-right corner when hovered or selected. + // AssistantMessageStyle has PaddingTop=0 (unlike UserMessageStyle which has + // PaddingTop=1), so we cannot unconditionally prepend topRow+"\n" — doing so + // would add a spurious blank line to every message in the default state. + // Accept the 1-line layout shift on hover; it is less disruptive than the + // blank-line artifact that affects all messages at all times. if mv.hovered || mv.selected { + innerWidth := width - messageStyle.GetHorizontalFrameSize() copyIcon := styles.MutedStyle.Render(types.AssistantMessageCopyLabel) iconWidth := ansi.StringWidth(types.AssistantMessageCopyLabel) padding := max(innerWidth-iconWidth, 0) - topRow = strings.Repeat(" ", padding) + copyIcon + topRow := strings.Repeat(" ", padding) + copyIcon + noTopPaddingStyle := messageStyle.PaddingTop(0) + return prefix + noTopPaddingStyle.Width(width).Render(topRow+"\n"+rendered) } - noTopPaddingStyle := messageStyle.PaddingTop(0) - return prefix + noTopPaddingStyle.Width(width).Render(topRow+"\n"+rendered) + return prefix + messageStyle.Render(rendered) case types.MessageTypeShellOutput: if rendered, err := markdown.NewRenderer(width).Render(fmt.Sprintf("```console\n%s\n```", msg.Content)); err == nil { return rendered