From 034a4b83157acc7a7cea7a4b442bbc80d86f2719 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 2 Jul 2026 08:09:47 +0000 Subject: [PATCH 1/2] Clarify instance tab footer shortcuts with "logs" suffix Rename the Scroll and Follow shortcut labels to "Scroll logs" and "Follow logs" so users know exactly what these keys act on. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011E2Yxdfk91KwCxqmeRdsTJ --- internal/devtui/model_view.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/devtui/model_view.go b/internal/devtui/model_view.go index 1166355b..f193de44 100644 --- a/internal/devtui/model_view.go +++ b/internal/devtui/model_view.go @@ -84,8 +84,8 @@ func (m Model) renderDashboardFooter() string { shortcuts := []tui.Shortcut{ {Key: "↑/↓", Label: "Navigate"}, {Key: "enter", Label: "Select source"}, - {Key: "pgup/pgdn", Label: "Scroll"}, - {Key: "f", Label: "Follow"}, + {Key: "pgup/pgdn", Label: "Scroll logs"}, + {Key: "f", Label: "Follow logs"}, {Key: "tab", Label: "Next tab"}, {Key: "ctrl+c", Label: "Exit"}, } From 7e72c64301e5bbe641f8bb4c9ca783d5aca84068 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 2 Jul 2026 08:31:18 +0000 Subject: [PATCH 2/2] Keep dashboard footer on one row at narrow widths The "Scroll logs"/"Follow logs" labels pushed the instance tab footer to 125 visible cells, wider than a 120-column terminal. Add tui.ShortcutBarFit, which falls back to narrower separators when the bar exceeds the available width and truncates with an ellipsis as a last resort, and use it for the dashboard footer so it always stays a single row. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011E2Yxdfk91KwCxqmeRdsTJ --- go.mod | 2 +- internal/devtui/model_view.go | 8 +++--- internal/tui/shortcuts.go | 25 ++++++++++++++++- internal/tui/shortcuts_test.go | 50 ++++++++++++++++++++++++++++++++++ 4 files changed, 79 insertions(+), 6 deletions(-) create mode 100644 internal/tui/shortcuts_test.go diff --git a/go.mod b/go.mod index cc5746b1..ece6ae89 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/NYTimes/gziphandler v1.1.1 github.com/bep/godartsass/v2 v2.5.0 github.com/cespare/xxhash/v2 v2.3.0 + github.com/charmbracelet/x/ansi v0.11.7 github.com/charmbracelet/x/term v0.2.2 github.com/evanw/esbuild v0.28.1 github.com/go-sql-driver/mysql v1.10.0 @@ -50,7 +51,6 @@ require ( github.com/charmbracelet/colorprofile v0.4.3 // indirect github.com/charmbracelet/harmonica v0.2.0 // indirect github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 // indirect - github.com/charmbracelet/x/ansi v0.11.7 // indirect github.com/charmbracelet/x/exp/ordered v0.1.0 // indirect github.com/charmbracelet/x/exp/strings v0.1.0 // indirect github.com/charmbracelet/x/termios v0.1.1 // indirect diff --git a/internal/devtui/model_view.go b/internal/devtui/model_view.go index f193de44..0559f359 100644 --- a/internal/devtui/model_view.go +++ b/internal/devtui/model_view.go @@ -89,7 +89,7 @@ func (m Model) renderDashboardFooter() string { {Key: "tab", Label: "Next tab"}, {Key: "ctrl+c", Label: "Exit"}, } - return tui.ShortcutBar(shortcuts...) + return tui.ShortcutBarFit(m.width, shortcuts...) } if m.activeTab == tabConfig { @@ -99,7 +99,7 @@ func (m Model) renderDashboardFooter() string { {Key: "tab", Label: "Next tab"}, {Key: "ctrl+c", Label: "Exit"}, } - return tui.ShortcutBar(shortcuts...) + return tui.ShortcutBarFit(m.width, shortcuts...) } if m.activeTab == tabOverview { @@ -110,10 +110,10 @@ func (m Model) renderDashboardFooter() string { {Key: "tab", Label: "Next tab"}, {Key: "ctrl+c", Label: "Exit"}, } - return tui.ShortcutBar(shortcuts...) + return tui.ShortcutBarFit(m.width, shortcuts...) } - return tui.ShortcutBar( + return tui.ShortcutBarFit(m.width, tui.Shortcut{Key: "ctrl+p", Label: "Commands"}, tui.Shortcut{Key: "tab", Label: "Next tab"}, tui.Shortcut{Key: "ctrl+c", Label: "Exit"}, diff --git a/internal/tui/shortcuts.go b/internal/tui/shortcuts.go index 28637ad3..1a119519 100644 --- a/internal/tui/shortcuts.go +++ b/internal/tui/shortcuts.go @@ -2,6 +2,7 @@ package tui import ( "charm.land/lipgloss/v2" + "github.com/charmbracelet/x/ansi" ) // Shortcut describes a single keyboard shortcut to display in a footer bar. @@ -27,11 +28,33 @@ func ShortcutBadge(key, label string) string { // ShortcutBar joins multiple shortcuts into a horizontal bar separated by dividers. func ShortcutBar(shortcuts ...Shortcut) string { + return shortcutBar(" │ ", shortcuts) +} + +// ShortcutBarFit renders a shortcut bar that stays on a single row within +// maxWidth: it falls back to narrower separators when the default bar is too +// wide and truncates with an ellipsis as a last resort. A maxWidth <= 0 means +// unconstrained. +func ShortcutBarFit(maxWidth int, shortcuts ...Shortcut) string { + bar := shortcutBar(" │ ", shortcuts) + if maxWidth <= 0 || lipgloss.Width(bar) <= maxWidth { + return bar + } + + bar = shortcutBar(" │ ", shortcuts) + if lipgloss.Width(bar) <= maxWidth { + return bar + } + + return ansi.Truncate(bar, maxWidth, "…") +} + +func shortcutBar(separator string, shortcuts []Shortcut) string { if len(shortcuts) == 0 { return "" } - sep := lipgloss.NewStyle().Foreground(BorderColor).Render(" │ ") + sep := lipgloss.NewStyle().Foreground(BorderColor).Render(separator) result := ShortcutBadge(shortcuts[0].Key, shortcuts[0].Label) for _, s := range shortcuts[1:] { result += sep + ShortcutBadge(s.Key, s.Label) diff --git a/internal/tui/shortcuts_test.go b/internal/tui/shortcuts_test.go new file mode 100644 index 00000000..0c270cdf --- /dev/null +++ b/internal/tui/shortcuts_test.go @@ -0,0 +1,50 @@ +package tui + +import ( + "strings" + "testing" + + "charm.land/lipgloss/v2" + "github.com/stretchr/testify/assert" +) + +func instanceTabShortcuts() []Shortcut { + return []Shortcut{ + {Key: "↑/↓", Label: "Navigate"}, + {Key: "enter", Label: "Select source"}, + {Key: "pgup/pgdn", Label: "Scroll logs"}, + {Key: "f", Label: "Follow logs"}, + {Key: "tab", Label: "Next tab"}, + {Key: "ctrl+c", Label: "Exit"}, + } +} + +func TestShortcutBarFit_UnconstrainedMatchesShortcutBar(t *testing.T) { + shortcuts := instanceTabShortcuts() + + assert.Equal(t, ShortcutBar(shortcuts...), ShortcutBarFit(0, shortcuts...)) + assert.Equal(t, ShortcutBar(shortcuts...), ShortcutBarFit(500, shortcuts...)) +} + +func TestShortcutBarFit_NarrowSeparatorsOn120Columns(t *testing.T) { + shortcuts := instanceTabShortcuts() + assert.Greater(t, lipgloss.Width(ShortcutBar(shortcuts...)), 120) + + bar := ShortcutBarFit(120, shortcuts...) + + assert.LessOrEqual(t, lipgloss.Width(bar), 120) + assert.Equal(t, 1, lipgloss.Height(bar)) + // All shortcuts survive; only the separators shrink. + assert.Contains(t, bar, "Follow logs") + assert.Contains(t, bar, "Exit") +} + +func TestShortcutBarFit_TruncatesWhenNothingFits(t *testing.T) { + shortcuts := instanceTabShortcuts() + + bar := ShortcutBarFit(40, shortcuts...) + + assert.LessOrEqual(t, lipgloss.Width(bar), 40) + assert.Equal(t, 1, lipgloss.Height(bar)) + assert.True(t, strings.Contains(bar, "…")) +}