@@ -145,18 +145,22 @@ func (mv *messageModel) Render(width int) string {
145145 prefix = mv .senderPrefix (msg .Sender )
146146 }
147147
148- // Always reserve a top row for the copy icon to avoid layout shifts.
149- // The icon is only visible when hovered or selected.
150- innerWidth := width - messageStyle .GetHorizontalFrameSize ()
151- var topRow string
148+ // Show copy icon in the top-right corner when hovered or selected.
149+ // AssistantMessageStyle has PaddingTop=0 (unlike UserMessageStyle which has
150+ // PaddingTop=1), so we cannot unconditionally prepend topRow+"\n" — doing so
151+ // would add a spurious blank line to every message in the default state.
152+ // Accept the 1-line layout shift on hover; it is less disruptive than the
153+ // blank-line artifact that affects all messages at all times.
152154 if mv .hovered || mv .selected {
155+ innerWidth := width - messageStyle .GetHorizontalFrameSize ()
153156 copyIcon := styles .MutedStyle .Render (types .AssistantMessageCopyLabel )
154157 iconWidth := ansi .StringWidth (types .AssistantMessageCopyLabel )
155158 padding := max (innerWidth - iconWidth , 0 )
156- topRow = strings .Repeat (" " , padding ) + copyIcon
159+ topRow := strings .Repeat (" " , padding ) + copyIcon
160+ noTopPaddingStyle := messageStyle .PaddingTop (0 )
161+ return prefix + noTopPaddingStyle .Width (width ).Render (topRow + "\n " + rendered )
157162 }
158- noTopPaddingStyle := messageStyle .PaddingTop (0 )
159- return prefix + noTopPaddingStyle .Width (width ).Render (topRow + "\n " + rendered )
163+ return prefix + messageStyle .Render (rendered )
160164 case types .MessageTypeShellOutput :
161165 if rendered , err := markdown .NewRenderer (width ).Render (fmt .Sprintf ("```console\n %s\n ```" , msg .Content )); err == nil {
162166 return rendered
0 commit comments