Skip to content

Commit b27cd3e

Browse files
jmelahmanclaude
andcommitted
fix(cli): align the picker title border with the panel width
Measure the rendered panel instead of assuming panelWidth+2 — the replacement top border was one column wider than the panel. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 12499d4 commit b27cd3e

3 files changed

Lines changed: 33 additions & 4 deletions

File tree

cli/20260821_18h11m09s_grim.png

74.4 KB
Loading

cli/internal/tui/commands_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package tui
22

33
import (
4+
"strings"
45
"testing"
56

7+
"charm.land/lipgloss/v2"
68
"github.com/onyx-dot-app/onyx/cli/internal/config"
79
"github.com/onyx-dot-app/onyx/cli/internal/models"
810
)
@@ -141,6 +143,31 @@ func TestSelectModelSetsOverrideAndStatus(t *testing.T) {
141143
}
142144
}
143145

146+
func TestPickerBorderLinesShareOneWidth(t *testing.T) {
147+
v := newViewport(120, false)
148+
v.showPicker(pickerModel, []pickerItem{
149+
{id: "0", label: "Gemma 4 E2B - Ollama *"},
150+
{id: "1", label: "Qwen 3 8B - Ollama"},
151+
})
152+
153+
var widths []int
154+
for _, line := range strings.Split(v.renderPicker(120, 30), "\n") {
155+
trimmed := strings.TrimRight(line, " ")
156+
if strings.TrimSpace(stripANSI(trimmed)) == "" {
157+
continue
158+
}
159+
widths = append(widths, lipgloss.Width(trimmed))
160+
}
161+
if len(widths) == 0 {
162+
t.Fatal("expected rendered panel lines")
163+
}
164+
for i, w := range widths {
165+
if w != widths[0] {
166+
t.Errorf("panel line %d width = %d, want %d (title border must match the panel)", i, w, widths[0])
167+
}
168+
}
169+
}
170+
144171
func TestSelectModelInvalidIndex(t *testing.T) {
145172
m := NewModel(config.DefaultConfig(), nil)
146173
m, _ = cmdSelectModel(m, "5")

cli/internal/tui/viewport.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -353,18 +353,20 @@ func (v *viewport) renderPicker(width, height int) string {
353353
Bold(true).
354354
Render(" " + title + " ")
355355

356-
// Build top border manually to avoid ANSI-corrupted rune slicing.
357-
// panelWidth+2 accounts for the left and right border characters.
356+
// Build top border manually to avoid ANSI-corrupted rune slicing. Measure
357+
// the rendered panel instead of assuming its width — lipgloss box sizing
358+
// would put the replacement line off by one.
359+
panelLines := strings.Split(panel, "\n")
360+
panelTotalWidth := lipgloss.Width(panelLines[len(panelLines)-1])
358361
borderColor := lipgloss.NewStyle().Foreground(accentColor)
359362
titleWidth := lipgloss.Width(titleRendered)
360-
rightDashes := panelWidth + 2 - 3 - titleWidth // total - "╭─" - "╮" - title
363+
rightDashes := panelTotalWidth - 3 - titleWidth // total - "╭─" - "╮" - title
361364
if rightDashes < 0 {
362365
rightDashes = 0
363366
}
364367
topBorder := borderColor.Render("╭─") + titleRendered +
365368
borderColor.Render(strings.Repeat("─", rightDashes)+"╮")
366369

367-
panelLines := strings.Split(panel, "\n")
368370
if len(panelLines) > 0 {
369371
panelLines[0] = topBorder
370372
}

0 commit comments

Comments
 (0)