From 70b4eeb6dafbc4dbca64ac9c96a62c75d0589ea2 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sun, 3 May 2026 03:27:35 -0700 Subject: [PATCH] fix(tui): keep pane titles single line --- tui/tui.go | 15 +++++++++++---- tui/tui_test.go | 10 ++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/tui/tui.go b/tui/tui.go index 4493a32..c7ce369 100644 --- a/tui/tui.go +++ b/tui/tui.go @@ -1567,7 +1567,7 @@ func (m model) renderRowsPane(rect rect) string { } return rowStyle(width, index == current, m.focus == focusRows, false) }) - content := lipgloss.JoinVertical(lipgloss.Left, paneTitle(focusRows, m.focus, m.groupPaneTitle()+" "+m.groupPositionLabel()), tableView) + content := lipgloss.JoinVertical(lipgloss.Left, paneTitleForWidth(focusRows, m.focus, m.groupPaneTitle()+" "+m.groupPositionLabel(), width), tableView) return paneStyle(focusRows, m.focus, rect.w, rect.h, rowsPaneAccent).Render(content) } @@ -1591,7 +1591,7 @@ func (m model) renderContextPane(rect rect) string { } return rowStyle(width, members[index] == selectedItem, m.focus == focusContext, itemInactive(m.items[members[index]])) }) - content := lipgloss.JoinVertical(lipgloss.Left, paneTitle(focusContext, m.focus, m.memberPaneTitle()+" "+m.memberPositionLabel()+" "+group.Title), tableView) + content := lipgloss.JoinVertical(lipgloss.Left, paneTitleForWidth(focusContext, m.focus, m.memberPaneTitle()+" "+m.memberPositionLabel()+" "+group.Title, width), tableView) return paneStyle(focusContext, m.focus, rect.w, rect.h, contextPaneAccent).Render(content) } @@ -1626,7 +1626,7 @@ func (m *model) configureDetailViewport(rect rect, lines []string) { if focus := paneFocusLabel(m.focus == focusDetail); focus != "" { title += " " + focus } - content := append([]string{paneTitle(focusDetail, m.focus, title)}, lines...) + content := append([]string{paneTitleForWidth(focusDetail, m.focus, title, paneContentWidth(rect.w))}, lines...) m.detailView.Width = paneContentWidth(rect.w) m.detailView.Height = maxInt(1, rect.h-2) m.detailView.MouseWheelEnabled = true @@ -2712,6 +2712,10 @@ func groupModeToggleLabel(layout LayoutPreset, mode groupMode) string { } func paneTitle(pane, focus paneFocus, suffix string) string { + return paneTitleForWidth(pane, focus, suffix, 0) +} + +func paneTitleForWidth(pane, focus paneFocus, suffix string, width int) string { label := map[paneFocus]string{ focusRows: "Rows", focusContext: "Context", @@ -2724,6 +2728,9 @@ func paneTitle(pane, focus paneFocus, suffix string) string { if pane == focus { prefix = "[*] " } + if width > 0 { + label = truncateCells(label, maxInt(1, width-lipgloss.Width(prefix))) + } return bold(prefix + label) } @@ -2769,7 +2776,7 @@ func paneScrolled(title, subtitle string, lines []string, rect rect, paneFocus p if strings.TrimSpace(subtitle) != "" { titleLine += " " + subtitle } - header := paneTitle(paneFocus, focus, titleLine) + header := paneTitleForWidth(paneFocus, focus, titleLine, contentW) body = append([]string(nil), body[scrollOffset:minInt(len(body), scrollOffset+contentH)]...) for len(body) < contentH { body = append(body, "") diff --git a/tui/tui_test.go b/tui/tui_test.go index c931202..49f1d8b 100644 --- a/tui/tui_test.go +++ b/tui/tui_test.go @@ -265,6 +265,16 @@ func TestWideRenderFillsTerminalAndKeepsThreePaneColumns(t *testing.T) { } } +func TestPaneTitlesStaySingleLineAtNarrowWidths(t *testing.T) { + title := stripANSI(paneTitleForWidth(focusContext, focusRows, "Messages 1/8 rows github-secure-session-4", 44)) + if len(title) > 44 { + t.Fatalf("pane title width = %d, want <= 44: %q", len(title), title) + } + if !strings.Contains(title, "...") { + t.Fatalf("pane title should truncate instead of wrapping: %q", title) + } +} + func TestCompactWidthKeepsUsefulColumns(t *testing.T) { group := itemGroup{Kind: "channel", Count: 18, Latest: "2026-05-02T12:00:00Z", Title: "github-secure-session-4"} mediumGroupHeader := groupListHeader(46, sortDefault)