From 85090422337af5e3f9fe5ca5ef0b884f987d163e Mon Sep 17 00:00:00 2001 From: Lacy Morrow Date: Wed, 4 Mar 2026 13:22:49 -0500 Subject: [PATCH 1/5] Show shuffle (z) and repeat (r) keys in bottom help bar They were only visible in the Ctrl+K keymap overlay but not in the always-visible bottom controls line. --- ui/view.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/view.go b/ui/view.go index b6c8da89..dd5623be 100644 --- a/ui/view.go +++ b/ui/view.go @@ -848,7 +848,7 @@ func (m Model) renderHelp() string { parts += helpKey("←→", "Seek ") } - parts += helpKey("+-", "Vol ") + helpKey("/", "Search ") + helpKey("a", "Queue ") + helpKey("Tab", "Focus ") + helpKey("Ctrl+K", "Keys ") + helpKey("Q", "Quit") + parts += helpKey("+-", "Vol ") + helpKey("z", "Shfl ") + helpKey("r", "Rpt ") + helpKey("/", "Search ") + helpKey("a", "Queue ") + helpKey("Tab", "Focus ") + helpKey("Ctrl+K", "Keys ") + helpKey("Q", "Quit") return parts } From 3a9636d95b6c2331afa73e1c15e2bfbf265e2c6b Mon Sep 17 00:00:00 2001 From: Lacy Morrow Date: Wed, 4 Mar 2026 13:24:40 -0500 Subject: [PATCH 2/5] Persist shuffle/repeat state to config on toggle Saves shuffle and repeat preferences to config.toml when toggled via z/r keys, so they survive restarts across all providers. --- ui/keys.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ui/keys.go b/ui/keys.go index 24bdbdb6..bab3b81f 100644 --- a/ui/keys.go +++ b/ui/keys.go @@ -199,11 +199,13 @@ func (m *Model) handleKey(msg tea.KeyMsg) tea.Cmd { case "r": m.playlist.CycleRepeat() + _ = config.Save("repeat", fmt.Sprintf("%q", m.playlist.Repeat().String())) m.player.ClearPreload() return m.preloadNext() case "z": m.playlist.ToggleShuffle() + _ = config.Save("shuffle", fmt.Sprintf("%v", m.playlist.Shuffled())) m.player.ClearPreload() return m.preloadNext() From 08930cb3fc2eadd15901cd12630ccc2dbbfd6053 Mon Sep 17 00:00:00 2001 From: Lacy Morrow Date: Wed, 4 Mar 2026 13:29:07 -0500 Subject: [PATCH 3/5] Move shuffle/repeat to top of Ctrl+K keymap overlay They were at positions 23-24, below the 12-line visible window. Moved them right after volume controls so they're visible without scrolling. --- ui/keys.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/keys.go b/ui/keys.go index bab3b81f..b20dffff 100644 --- a/ui/keys.go +++ b/ui/keys.go @@ -735,6 +735,8 @@ var keymapEntries = []keymapEntry{ {"< ,", "Previous track"}, {"← →", "Seek ±5s"}, {"+ -", "Volume up/down"}, + {"z", "Toggle shuffle"}, + {"r", "Cycle repeat"}, {"m", "Toggle mono"}, {"e", "Cycle EQ preset"}, {"t", "Choose theme"}, @@ -750,8 +752,6 @@ var keymapEntries = []keymapEntry{ {"p", "Playlist manager"}, {"i", "Track info / metadata"}, {"S", "Save track to ~/Music"}, - {"r", "Cycle repeat"}, - {"z", "Toggle shuffle"}, {"x", "Expand/collapse playlist"}, {"/", "Search playlist"}, {"Tab", "Toggle focus"}, From 4cc21416066faaa36871f6e424e0bb8d171f2fe5 Mon Sep 17 00:00:00 2001 From: Lacy Morrow Date: Wed, 4 Mar 2026 13:36:47 -0500 Subject: [PATCH 4/5] Responsive help bar: drop low-priority hints when terminal is narrow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each help hint has a priority. When the combined width exceeds the terminal width, the lowest-priority hints are dropped first: 100 Spc(⏯) 95 Q(Quit) 90 <>(Trk) 80 +-(Vol) 70 ←→(Seek) 60 Ctrl+K(Keys) 50 Tab(Focus) 40 /(Search) 30 a(Queue) 20 z(Shfl) 20 r(Rpt) On a narrow terminal you still see play/pause, track nav, vol, and quit. On wide terminals everything shows. --- ui/view.go | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 66 insertions(+), 4 deletions(-) diff --git a/ui/view.go b/ui/view.go index dd5623be..59dd9073 100644 --- a/ui/view.go +++ b/ui/view.go @@ -841,16 +841,78 @@ func (m Model) renderHelp() string { return helpKey("↑↓", "Navigate ") + helpKey("Enter", "Load ") + helpKey("Tab", "Focus ") + helpKey("Q", "Quit") } - parts := helpKey("Spc", "⏯ ") + helpKey("<>", "Trk ") + // Build help hints with priority (lower = dropped first when too wide). + var hints []helpHint + + hints = append(hints, helpHint{helpKey("Spc", "⏯ "), 100}) + hints = append(hints, helpHint{helpKey("<>", "Trk "), 90}) track, _ := m.playlist.Current() if !track.Stream || m.player.Seekable() { - parts += helpKey("←→", "Seek ") + hints = append(hints, helpHint{helpKey("←→", "Seek "), 70}) } - parts += helpKey("+-", "Vol ") + helpKey("z", "Shfl ") + helpKey("r", "Rpt ") + helpKey("/", "Search ") + helpKey("a", "Queue ") + helpKey("Tab", "Focus ") + helpKey("Ctrl+K", "Keys ") + helpKey("Q", "Quit") + hints = append(hints, + helpHint{helpKey("+-", "Vol "), 80}, + helpHint{helpKey("z", "Shfl "), 20}, + helpHint{helpKey("r", "Rpt "), 20}, + helpHint{helpKey("/", "Search "), 40}, + helpHint{helpKey("a", "Queue "), 30}, + helpHint{helpKey("Tab", "Focus "), 50}, + helpHint{helpKey("Ctrl+K", "Keys "), 60}, + helpHint{helpKey("Q", "Quit"), 95}, + ) + + return fitHints(hints, m.width) +} + +// helpHint is a rendered help key with an associated display priority. +type helpHint struct { + text string + priority int +} + +// fitHints drops lowest-priority hints until they fit within maxWidth. +func fitHints(hints []helpHint, maxWidth int) string { + // Start with all hints; drop lowest priority until it fits. + active := make([]bool, len(hints)) + for i := range active { + active[i] = true + } + + for { + var total int + for i, h := range hints { + if active[i] { + total += lipgloss.Width(h.text) + } + } + if total <= maxWidth { + break + } + + // Find lowest-priority active hint and drop it. + minPri := 1<<31 - 1 + minIdx := -1 + for i, h := range hints { + if active[i] && h.priority < minPri { + minPri = h.priority + minIdx = i + } + } + if minIdx < 0 { + break // nothing left to drop + } + active[minIdx] = false + } - return parts + var result string + for i, h := range hints { + if active[i] { + result += h.text + } + } + return result } // renderStreamStatus shows a network stats line for HTTP streams: From 871864d005c63d7a112f30394813546430f075df Mon Sep 17 00:00:00 2001 From: Lacy Morrow Date: Wed, 4 Mar 2026 13:37:52 -0500 Subject: [PATCH 5/5] Handle config.Save errors for shuffle/repeat Show a status message via saveMsg when persisting fails instead of silently ignoring the error. Addresses Gemini Code Assist review feedback. --- ui/keys.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/ui/keys.go b/ui/keys.go index b20dffff..7784c2fb 100644 --- a/ui/keys.go +++ b/ui/keys.go @@ -199,13 +199,19 @@ func (m *Model) handleKey(msg tea.KeyMsg) tea.Cmd { case "r": m.playlist.CycleRepeat() - _ = config.Save("repeat", fmt.Sprintf("%q", m.playlist.Repeat().String())) + if err := config.Save("repeat", fmt.Sprintf("%q", m.playlist.Repeat().String())); err != nil { + m.saveMsg = fmt.Sprintf("Config save failed: %s", err) + m.saveMsgTTL = 60 + } m.player.ClearPreload() return m.preloadNext() case "z": m.playlist.ToggleShuffle() - _ = config.Save("shuffle", fmt.Sprintf("%v", m.playlist.Shuffled())) + if err := config.Save("shuffle", fmt.Sprintf("%v", m.playlist.Shuffled())); err != nil { + m.saveMsg = fmt.Sprintf("Config save failed: %s", err) + m.saveMsgTTL = 60 + } m.player.ClearPreload() return m.preloadNext()