From 835844314ee977ecabdbf29656fc7bde1add51d1 Mon Sep 17 00:00:00 2001 From: Tyler Koenig Date: Tue, 30 Jun 2026 17:09:58 -0400 Subject: [PATCH 1/4] fix(tui): show context-aware Enter hint in hotbar When cursor is on a group row, hotbar now shows [Enter]Expand or [Enter]Collapse instead of [Enter]Detail, matching actual behavior. --- internal/tui/view_dashboard.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/internal/tui/view_dashboard.go b/internal/tui/view_dashboard.go index cb0345e..bf76781 100644 --- a/internal/tui/view_dashboard.go +++ b/internal/tui/view_dashboard.go @@ -295,7 +295,15 @@ func (m Model) renderFooter(_ dashboardStats) string { } else if m.detailOpen { keys = "[i]Close [h]History [s]SLA [e]Edit [m]Maint [l]Logs [S]Settings [T]Theme [q]Quit" } else { - keys = "[/]Filter [i]Info [Enter]Detail [n]New [e]Edit [d]Del [m]Maint [l]Logs [S]Settings [T]Theme [q]Quit" + enterHint := "[Enter]Detail" + if m.cursor < len(m.sites) && m.sites[m.cursor].Type == "group" { + if m.collapsed[m.sites[m.cursor].ID] { + enterHint = "[Enter]Expand" + } else { + enterHint = "[Enter]Collapse" + } + } + keys = "[/]Filter [i]Info " + enterHint + " [n]New [e]Edit [d]Del [m]Maint [l]Logs [S]Settings [T]Theme [q]Quit" } line := m.st.subtleStyle.Render(keys) -- 2.52.0 From ef54b36e0d4c981668d4258a55889a36167751ac Mon Sep 17 00:00:00 2001 From: Tyler Koenig Date: Tue, 30 Jun 2026 19:09:51 -0400 Subject: [PATCH 2/4] refactor(tui): make Enter always open detail, Space for group collapse Enter now consistently opens the detail sidebar for both monitors and groups. Group collapse/expand is Space-only. Hotbar shows [Space]Expand or [Space]Collapse contextually when cursor is on a group row. --- internal/tui/update.go | 10 ---------- internal/tui/view_dashboard.go | 8 ++++---- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/internal/tui/update.go b/internal/tui/update.go index 5b233a7..d14c622 100644 --- a/internal/tui/update.go +++ b/internal/tui/update.go @@ -651,16 +651,6 @@ func (m *Model) handleDashboardKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) { } if len(m.sites) > 0 { site := m.sites[m.cursor] - if site.Type == "group" { - m.collapsed[site.ID] = !m.collapsed[site.ID] - payload := collapsedJSON(m.collapsed) - st := m.store - ctx := m.ctx - m.refreshLive() - return m, writeCmd("Save collapsed groups", func() error { - return st.SetPreference(ctx, "collapsed_groups", payload) - }) - } if !m.detailOpen { m.detailOpen = true m.detailMode = detailDefault diff --git a/internal/tui/view_dashboard.go b/internal/tui/view_dashboard.go index bf76781..3d9b295 100644 --- a/internal/tui/view_dashboard.go +++ b/internal/tui/view_dashboard.go @@ -295,15 +295,15 @@ func (m Model) renderFooter(_ dashboardStats) string { } else if m.detailOpen { keys = "[i]Close [h]History [s]SLA [e]Edit [m]Maint [l]Logs [S]Settings [T]Theme [q]Quit" } else { - enterHint := "[Enter]Detail" + groupHint := "" if m.cursor < len(m.sites) && m.sites[m.cursor].Type == "group" { if m.collapsed[m.sites[m.cursor].ID] { - enterHint = "[Enter]Expand" + groupHint = " [Space]Expand" } else { - enterHint = "[Enter]Collapse" + groupHint = " [Space]Collapse" } } - keys = "[/]Filter [i]Info " + enterHint + " [n]New [e]Edit [d]Del [m]Maint [l]Logs [S]Settings [T]Theme [q]Quit" + keys = "[/]Filter [i]Info [Enter]Detail" + groupHint + " [n]New [e]Edit [d]Del [m]Maint [l]Logs [S]Settings [T]Theme [q]Quit" } line := m.st.subtleStyle.Render(keys) -- 2.52.0 From efa8894b18fa5cc4301c95e91e1d4dea13419710 Mon Sep 17 00:00:00 2001 From: Tyler Koenig Date: Tue, 30 Jun 2026 19:51:44 -0400 Subject: [PATCH 3/4] refactor(tui): unify detail toggle on Enter, remove i keybind Enter now toggles detail sidebar open/closed with preference persistence. Removes redundant i keybind that did the same thing. Hotbar updated: [Enter]Detail when closed, [Enter]Close when open. --- internal/tui/update.go | 49 ++++++++++++++-------------------- internal/tui/view_dashboard.go | 4 +-- 2 files changed, 22 insertions(+), 31 deletions(-) diff --git a/internal/tui/update.go b/internal/tui/update.go index d14c622..9ddcbb6 100644 --- a/internal/tui/update.go +++ b/internal/tui/update.go @@ -650,13 +650,27 @@ func (m *Model) handleDashboardKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) { return m, nil } if len(m.sites) > 0 { - site := m.sites[m.cursor] - if !m.detailOpen { - m.detailOpen = true - m.detailMode = detailDefault - m.recalcLayout() + m.detailOpen = !m.detailOpen + m.detailMode = detailDefault + m.recalcLayout() + st := m.store + ctx := m.ctx + open := m.detailOpen + var cmd tea.Cmd + if m.detailOpen { + cmd = m.loadDetailCmd(m.sites[m.cursor].ID) } - return m, m.loadDetailCmd(site.ID) + saveCmd := writeCmd("Save detail preference", func() error { + v := "false" + if open { + v = "true" + } + return st.SetPreference(ctx, "detail_open", v) + }) + if cmd != nil { + return m, tea.Batch(cmd, saveCmd) + } + return m, saveCmd } case "e": return m.handleEditItem() @@ -683,29 +697,6 @@ func (m *Model) handleDashboardKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) { return st.UpdateSitePaused(ctx, id, paused) }) } - case "i": - if len(m.sites) > 0 { - m.detailOpen = !m.detailOpen - m.recalcLayout() - st := m.store - ctx := m.ctx - open := m.detailOpen - var cmd tea.Cmd - if m.detailOpen { - cmd = m.loadDetailCmd(m.sites[m.cursor].ID) - } - saveCmd := writeCmd("Save detail preference", func() error { - v := "false" - if open { - v = "true" - } - return st.SetPreference(ctx, "detail_open", v) - }) - if cmd != nil { - return m, tea.Batch(cmd, saveCmd) - } - return m, saveCmd - } case "esc": if m.focusedPanel != panelMonitors { m.focusedPanel = panelMonitors diff --git a/internal/tui/view_dashboard.go b/internal/tui/view_dashboard.go index 3d9b295..468685d 100644 --- a/internal/tui/view_dashboard.go +++ b/internal/tui/view_dashboard.go @@ -293,7 +293,7 @@ func (m Model) renderFooter(_ dashboardStats) string { } else if m.detailOpen && m.detailMode == detailHistory { keys = "[Esc]Back [l]Logs [S]Settings [T]Theme [q]Quit" } else if m.detailOpen { - keys = "[i]Close [h]History [s]SLA [e]Edit [m]Maint [l]Logs [S]Settings [T]Theme [q]Quit" + keys = "[Enter]Close [h]History [s]SLA [e]Edit [m]Maint [l]Logs [S]Settings [T]Theme [q]Quit" } else { groupHint := "" if m.cursor < len(m.sites) && m.sites[m.cursor].Type == "group" { @@ -303,7 +303,7 @@ func (m Model) renderFooter(_ dashboardStats) string { groupHint = " [Space]Collapse" } } - keys = "[/]Filter [i]Info [Enter]Detail" + groupHint + " [n]New [e]Edit [d]Del [m]Maint [l]Logs [S]Settings [T]Theme [q]Quit" + keys = "[/]Filter [Enter]Detail" + groupHint + " [n]New [e]Edit [d]Del [m]Maint [l]Logs [S]Settings [T]Theme [q]Quit" } line := m.st.subtleStyle.Render(keys) -- 2.52.0 From be144367017176401cee8094e4a224dcea763de5 Mon Sep 17 00:00:00 2001 From: Tyler Koenig Date: Tue, 30 Jun 2026 19:56:16 -0400 Subject: [PATCH 4/4] style(tui): accent keys, muted descriptions, dot separators in hotbar Keys rendered in accent color, descriptions in muted. Dot separators between items. No brackets. Faster to scan at a glance. --- internal/tui/view_dashboard.go | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/internal/tui/view_dashboard.go b/internal/tui/view_dashboard.go index 468685d..5999fbb 100644 --- a/internal/tui/view_dashboard.go +++ b/internal/tui/view_dashboard.go @@ -277,36 +277,46 @@ func (m Model) renderStatusLine(stats dashboardStats) string { return left + strings.Repeat(" ", padW) + ver } +func (m Model) hotkey(key, desc string) string { + k := lipgloss.NewStyle().Foreground(m.theme.Accent).Render(key) + d := m.st.subtleStyle.Render(desc) + return k + " " + d +} + func (m Model) renderFooter(_ dashboardStats) string { + dot := m.st.subtleStyle.Render(" · ") + if m.filterMode { cursor := lipgloss.NewStyle().Foreground(m.theme.Accent).Render("│") - return "\n" + m.st.titleStyle.Render("/") + " " + m.filterText + cursor + " " + m.st.subtleStyle.Render("[Enter]Apply [Esc]Clear") + keys := m.hotkey("Enter", "Apply") + dot + m.hotkey("Esc", "Clear") + return "\n" + m.st.titleStyle.Render("/") + " " + m.filterText + cursor + " " + keys } - var keys string + var parts []string if m.focusedPanel == panelMaint { - keys = "[n]New [x]End [d]Del [Esc]Back [S]Settings [T]Theme [q]Quit" + parts = []string{m.hotkey("n", "New"), m.hotkey("x", "End"), m.hotkey("d", "Del"), m.hotkey("Esc", "Back")} } else if m.focusedPanel == panelLogs { - keys = "[↑/↓]Scroll [Enter]Expand [l/Esc]Back [S]Settings [T]Theme [q]Quit" + parts = []string{m.hotkey("↑/↓", "Scroll"), m.hotkey("Enter", "Expand"), m.hotkey("l/Esc", "Back")} } else if m.detailOpen && m.detailMode == detailSLA { - keys = "[1-4]Period [Esc]Back [l]Logs [S]Settings [T]Theme [q]Quit" + parts = []string{m.hotkey("1-4", "Period"), m.hotkey("Esc", "Back")} } else if m.detailOpen && m.detailMode == detailHistory { - keys = "[Esc]Back [l]Logs [S]Settings [T]Theme [q]Quit" + parts = []string{m.hotkey("Esc", "Back")} } else if m.detailOpen { - keys = "[Enter]Close [h]History [s]SLA [e]Edit [m]Maint [l]Logs [S]Settings [T]Theme [q]Quit" + parts = []string{m.hotkey("Enter", "Close"), m.hotkey("h", "History"), m.hotkey("s", "SLA"), m.hotkey("e", "Edit"), m.hotkey("m", "Maint")} } else { - groupHint := "" + parts = []string{m.hotkey("/", "Filter"), m.hotkey("Enter", "Detail")} if m.cursor < len(m.sites) && m.sites[m.cursor].Type == "group" { if m.collapsed[m.sites[m.cursor].ID] { - groupHint = " [Space]Expand" + parts = append(parts, m.hotkey("Space", "Expand")) } else { - groupHint = " [Space]Collapse" + parts = append(parts, m.hotkey("Space", "Collapse")) } } - keys = "[/]Filter [Enter]Detail" + groupHint + " [n]New [e]Edit [d]Del [m]Maint [l]Logs [S]Settings [T]Theme [q]Quit" + parts = append(parts, m.hotkey("n", "New"), m.hotkey("e", "Edit"), m.hotkey("d", "Del"), m.hotkey("m", "Maint")) } + parts = append(parts, m.hotkey("l", "Logs"), m.hotkey("S", "Settings"), m.hotkey("T", "Theme"), m.hotkey("q", "Quit")) - line := m.st.subtleStyle.Render(keys) + line := strings.Join(parts, dot) if m.filterText != "" { line = m.st.subtleStyle.Render(fmt.Sprintf("filter: %s ", m.filterText)) + line } -- 2.52.0