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()) } 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]) + } + } + } +}