From 2cb84dec29ad753eee8fcb40111c2638e675a5dc Mon Sep 17 00:00:00 2001 From: Hammad Majid Date: Thu, 17 Sep 2026 04:35:22 +0500 Subject: [PATCH 1/2] fix(picker): keep the frame at the terminal height so the filter line stays visible The preview column was one line taller than the list, so every frame was one line taller than the terminal. Bubble Tea's inline renderer dropped the top line, the filter line, and repainted every tick. Co-Authored-By: Claude Fable 5.1 --- internal/picker/view.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/internal/picker/view.go b/internal/picker/view.go index 15ca2fe..7d86e46 100644 --- a/internal/picker/view.go +++ b/internal/picker/view.go @@ -57,11 +57,11 @@ func (m Model) View() tea.View { list := m.listView(t, groups, rows, lay, now) if lay.ShowPreview { - // The preview column is joined under a matching blank line so its - // box's top border lands on the list's first row, not on the - // filter line above. - right := "\n" + m.previewView(t, rows, lay, now) - b.WriteString(lipgloss.JoinHorizontal(lipgloss.Top, list, " ", right)) + // The preview box is exactly ListHeight lines tall (lipgloss v2 + // counts the border in Height), so joining it at the top keeps + // the frame at the terminal height: one taller and the renderer + // drops the filter line and repaints every tick. + b.WriteString(lipgloss.JoinHorizontal(lipgloss.Top, list, " ", m.previewView(t, rows, lay, now))) } else { b.WriteString(list) } @@ -204,7 +204,8 @@ func (m Model) previewView(t theme, rows []match, lay Layout, now time.Time) str BorderForeground(t.rule). Padding(0, 1). Width(lay.PreviewWidth). - Height(lay.ListHeight) + Height(lay.ListHeight). + MaxHeight(lay.ListHeight) // Height is a minimum; a tall body must not grow the box return box.Render(body.String()) } From 1053cc1874188dd45cf108fdea90fe993352bd8e Mon Sep 17 00:00:00 2001 From: Hammad Majid Date: Thu, 17 Sep 2026 04:35:22 +0500 Subject: [PATCH 2/2] test(picker): pin the frame height to the terminal height with and without the preview Co-Authored-By: Claude Fable 5.1 --- internal/picker/view_test.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/internal/picker/view_test.go b/internal/picker/view_test.go index 617344a..3eca377 100644 --- a/internal/picker/view_test.go +++ b/internal/picker/view_test.go @@ -130,3 +130,25 @@ func TestModel_View_RowsShareEqualWidth(t *testing.T) { } } } + +// TestModel_View_FrameMatchesTerminalHeight pins the frame to exactly the +// terminal height, with and without the preview pane. One line taller and +// Bubble Tea's inline renderer drops the filter line off the top. +func TestModel_View_FrameMatchesTerminalHeight(t *testing.T) { + rows := manyRows(11) + for _, width := range []int{110, 45} { + for _, height := range []int{40, 30, 24, 14, 9} { + m := picker.NewModel(rows, noopStatus, picker.Options{}) + next, _ := m.Update(tea.WindowSizeMsg{Width: width, Height: height}) + m = next.(picker.Model) + + lines := strings.Split(m.View().Content, "\n") + if len(lines) != height { + t.Errorf("View() at %dx%d produced %d lines, want exactly %d", width, height, len(lines), height) + } + if !strings.Contains(lines[0], "type to filter") { + t.Errorf("View() at %dx%d: first line %q is not the filter line", width, height, lines[0]) + } + } + } +}