Skip to content

Support responsive height in Lyrics and Themes views#215

Merged
bjarneo merged 4 commits into
bjarneo:mainfrom
zambetti:lyrics-themes-responsive
May 7, 2026
Merged

Support responsive height in Lyrics and Themes views#215
bjarneo merged 4 commits into
bjarneo:mainfrom
zambetti:lyrics-themes-responsive

Conversation

@zambetti
Copy link
Copy Markdown
Contributor

@zambetti zambetti commented May 6, 2026

This PR improves the Lyrics and Themes views by adding support for the expanded-view setting ctrl+x.

Additionally, the Themes view now adopts the same scrolling behavior as the Keymap and File Browser views.

There is a small refactor to rename toggleExpandPlaylist to the more accurate toggleExpandedView.

Summary by CodeRabbit

  • New Features
    • Ctrl+X keyboard shortcut now toggles expanded view in the lyrics overlay
    • Theme picker redesigned with live preview and scrollable theme selection
    • Improved lyrics display with dynamic visible sizing for better usability

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 6, 2026

📝 Walkthrough

Walkthrough

This PR refactors and expands an "expanded view" toggle feature across the UI. The toggleExpandPlaylist() function is renamed to toggleExpandedView() and integrated into the lyrics overlay and theme picker. The theme picker gains dynamic scrolling and live preview on navigation, while the lyrics overlay uses dynamic height calculation based on the expanded state.

Changes

UI Expanded View Toggle Refactor

Layer / File(s) Summary
Core Function Refactor
ui/model/keys.go
togglePlaylist() is renamed to toggleExpandedView(). The new function toggles heightExpanded, applies height mode, and adjusts scroll.
Data Shape
ui/model/state.go
Added scroll int field to themePickerState struct to track scroll position within the theme picker.
Helper Functions
ui/model/lyrics.go, ui/model/overlays.go
Added lyricsVisibleHeight() to compute dynamic lyrics display lines based on expanded state. Added themePickerHelpLine(), themePickerVisible(), and themePickerMaybeAdjustScroll() to support theme picker scrolling and visibility.
Key Handler Integration
ui/model/keys.go, ui/model/filebrowser.go, ui/model/keymap.go
Updated all Ctrl+X handlers to call toggleExpandedView(). Added Ctrl+X support to lyrics overlay. Enhanced theme picker key handlers (Up/Down/Home/End) with live preview application and scroll adjustment.
Overlay Rendering Updates
ui/model/overlays.go, ui/model/view_overlays.go
Theme picker now initializes scroll to 0 and renders dynamically with a default entry plus themed list using themePickerVisible() for max visibility. Lyrics overlay now uses dynamic lyricsVisibleHeight() for display calculations instead of fixed height.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'Support responsive height in Lyrics and Themes views' directly describes the main change: enabling Lyrics and Themes sections to respect the expanded view setting.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
ui/model/view_overlays.go (1)

498-575: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Dynamic lyrics window looks correct; the len(lines) - 2 arithmetic is the fragile bit.

The synced-mode half-window math (half := visible / 2, clamping endIdx/startIdx) and the scroll-mode min(scroll+visible, len(lines)) are both sound, and switching to padLines(lines, visible, rendered) matches the dynamic height nicely.

The one fragile spot is line 568:

rendered := len(lines) - 2 // -2 for header and spacing

This silently assumes exactly two leading header lines. Today's renderer does open with titleStyle.Render("L Y R I C S") + "", so it's correct, but if anyone adds a subtitle/dim line at the top later, padding will drift and the overlay height will jump. Computing rendered as a counter inside each branch (or capturing headerLen := len(lines) before the body block and using len(lines) - headerLen) would make the coupling explicit.

♻️ Less fragile rendered count
 func (m Model) renderLyricsOverlay() string {
 	visible := m.lyricsVisibleHeight()
 	lines := []string{
 		titleStyle.Render("L Y R I C S"),
 		"",
 	}
+	headerLen := len(lines)
 
 	if m.lyrics.loading {
 		...
 	}
 
-	rendered := len(lines) - 2 // -2 for header and spacing
+	rendered := len(lines) - headerLen
 	lines = padLines(lines, visible, rendered)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/model/view_overlays.go` around lines 498 - 575, renderLyricsOverlay
currently computes rendered with a hardcoded "-2" which assumes exactly two
header lines; capture the header length explicitly to avoid future breakage by:
after the header lines are appended (title + spacing) record headerLen :=
len(lines), then after the various branches compute rendered := len(lines) -
headerLen and pass that to padLines; update references around the existing
rendered variable (and keep logic in renderLyricsOverlay and usage of
padLines/lyrics.scroll unchanged) so the header size is decoupled from the
padding calculation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ui/model/lyrics.go`:
- Around line 58-66: The helper lyricsVisibleHeight currently uses a hard-coded
overhead (m.height-4) and a value receiver; change it to use a named constant
(e.g. lyricsOverheadLines = 4) or compute the overhead via the same
lipgloss.Height(probeFrame) probe pattern used by renderLyricsOverlay to keep
renderer and helper in sync, and change the receiver from (m Model) to a pointer
receiver (m *Model) to avoid copying; update references to lyricsVisibleHeight
and ensure renderLyricsOverlay and related functions (lyricsArtistTitle,
lyricsSyncable) use the shared constant or probe logic.

In `@ui/model/overlays.go`:
- Around line 53-99: themePickerVisible/themePickerMaybeAdjustScroll duplicate
logic from keymapVisible/keymapMaybeAdjustScroll and
fbVisible/fbMaybeAdjustScroll; extract the shared behavior into two helpers and
call them from each overlay: add computeOverlayVisible(probeSections []string,
heightExpanded bool, cap int) to build the probe frame, compute fixedHeight and
return the visible limit, and add clampOverlayScroll(cursor *int, scroll *int,
visible, count int) to normalize cursor/scroll bounds and adjust scroll when
cursor moves; then update themePickerVisible to call computeOverlayVisible with
its probeSections and update themePickerMaybeAdjustScroll to call
clampOverlayScroll(&m.themePicker.cursor, &m.themePicker.scroll, visible,
len(m.themes)+1) (and similarly replace logic in keymap*/fb* to call these
helpers).
- Around line 53-55: The footer help line in themePickerHelpLine currently only
shows "↓↑ / Enter / Esc" but must include the newly supported keys from
handleThemeKey (PgUp/PgDn, Ctrl+U/Ctrl+D, Home/g, End/G) and at minimum Ctrl+X
for Expand; update themePickerHelpLine to add helpKey entries for these keys
(e.g., "PgUp/PgDn", "Ctrl+U/Ctrl+D", "Home/g", "End/G", "Ctrl+X") so the footer
reflects the responsive-height and paging controls and improves discoverability.

---

Outside diff comments:
In `@ui/model/view_overlays.go`:
- Around line 498-575: renderLyricsOverlay currently computes rendered with a
hardcoded "-2" which assumes exactly two header lines; capture the header length
explicitly to avoid future breakage by: after the header lines are appended
(title + spacing) record headerLen := len(lines), then after the various
branches compute rendered := len(lines) - headerLen and pass that to padLines;
update references around the existing rendered variable (and keep logic in
renderLyricsOverlay and usage of padLines/lyrics.scroll unchanged) so the header
size is decoupled from the padding calculation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3214f1d1-c1eb-45a4-8897-ee809e51138b

📥 Commits

Reviewing files that changed from the base of the PR and between 761c921 and 3d6591f.

📒 Files selected for processing (7)
  • ui/model/filebrowser.go
  • ui/model/keymap.go
  • ui/model/keys.go
  • ui/model/lyrics.go
  • ui/model/overlays.go
  • ui/model/state.go
  • ui/model/view_overlays.go

Comment thread ui/model/lyrics.go
Comment on lines +58 to +66
// lyricsVisibleHeight returns the number of lyrics lines to show.
func (m Model) lyricsVisibleHeight() int {
limit := maxPlVisible
if m.heightExpanded {
limit = m.height
}
// Fixed overhead: header (2) + spacing/footer (2) = 4.
return max(3, min(limit, m.height-4))
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | 💤 Low value

Helper looks correct; just verify the overhead constant stays in sync with the renderer.

max(3, min(limit, m.height-4)) mirrors the structure used by keymapVisible / fbVisible / themePickerVisible. The hard-coded 4 corresponds to the renderer's "title (1) + blank (1) + blank (1) + helpKey footer (1)" overhead. That's currently correct (renderLyricsOverlay in view_overlays.go opens with titleStyle… + "" and closes with "" + helpKey), but it's an implicit coupling — if anyone adds, say, a subtitle line to the header the overhead drifts silently.

Two small things to consider:

  1. Tie the constant to a named value (e.g. lyricsOverheadLines = 4) shared with the renderer, or compute it via the same lipgloss.Height(probeFrame) probe pattern the other overlays use.
  2. The receiver here is value (m Model) while lyricsArtistTitle / lyricsSyncable in the same file use pointer receivers. Minor, but (m *Model) would avoid copying the model on every render call.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/model/lyrics.go` around lines 58 - 66, The helper lyricsVisibleHeight
currently uses a hard-coded overhead (m.height-4) and a value receiver; change
it to use a named constant (e.g. lyricsOverheadLines = 4) or compute the
overhead via the same lipgloss.Height(probeFrame) probe pattern used by
renderLyricsOverlay to keep renderer and helper in sync, and change the receiver
from (m Model) to a pointer receiver (m *Model) to avoid copying; update
references to lyricsVisibleHeight and ensure renderLyricsOverlay and related
functions (lyricsArtistTitle, lyricsSyncable) use the shared constant or probe
logic.

Comment thread ui/model/overlays.go
Comment on lines +53 to +99
func (m *Model) themePickerHelpLine() string {
return helpKey("↓↑", "Scroll ") + helpKey("Enter", "Select ") + helpKey("Esc", "Close")
}

func (m *Model) themePickerVisible() int {
probeSections := []string{
titleStyle.Render("T H E M E S"),
"",
"x", // 1-line list placeholder
"",
dimStyle.Render(" 0/0 themes"),
"",
m.themePickerHelpLine(),
}

probeFrame := ui.FrameStyle.Render(strings.Join(probeSections, "\n"))
fixedHeight := lipgloss.Height(probeFrame) - 1

limit := maxPlVisible
if m.heightExpanded {
limit = m.height
}
return max(3, min(limit, m.height-fixedHeight))
}

func (m *Model) themePickerMaybeAdjustScroll(visible int) {
if visible <= 0 {
return
}
count := len(m.themes) + 1
if m.themePicker.cursor < 0 {
m.themePicker.cursor = 0
}
if m.themePicker.cursor >= count && count > 0 {
m.themePicker.cursor = count - 1
}

if m.themePicker.cursor < m.themePicker.scroll {
m.themePicker.scroll = m.themePicker.cursor
} else if m.themePicker.cursor >= m.themePicker.scroll+visible {
m.themePicker.scroll = m.themePicker.cursor - visible + 1
}

if m.themePicker.scroll+visible > count && count > 0 {
m.themePicker.scroll = max(0, count-visible)
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | ⚖️ Poor tradeoff

Theme-picker visibility/scroll helpers are correct but largely duplicate the keymap & file browser pattern.

themePickerVisible and themePickerMaybeAdjustScroll are near-line-for-line copies of keymapVisible/keymapMaybeAdjustScroll and fbVisible/fbMaybeAdjustScroll, only differing in the probe sections, the count source, and the cursor/scroll fields. With three overlays (and growing) reusing the same shape, a small generic helper — e.g. clampOverlayScroll(cursor, scroll *int, visible, count int) and a computeOverlayVisible(probeSections []string, heightExpanded bool, cap int) — would remove the drift risk if any of these handlers diverges.

Not blocking — just worth keeping in mind as the next overlay is added.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/model/overlays.go` around lines 53 - 99,
themePickerVisible/themePickerMaybeAdjustScroll duplicate logic from
keymapVisible/keymapMaybeAdjustScroll and fbVisible/fbMaybeAdjustScroll; extract
the shared behavior into two helpers and call them from each overlay: add
computeOverlayVisible(probeSections []string, heightExpanded bool, cap int) to
build the probe frame, compute fixedHeight and return the visible limit, and add
clampOverlayScroll(cursor *int, scroll *int, visible, count int) to normalize
cursor/scroll bounds and adjust scroll when cursor moves; then update
themePickerVisible to call computeOverlayVisible with its probeSections and
update themePickerMaybeAdjustScroll to call
clampOverlayScroll(&m.themePicker.cursor, &m.themePicker.scroll, visible,
len(m.themes)+1) (and similarly replace logic in keymap*/fb* to call these
helpers).

Comment thread ui/model/overlays.go
Comment on lines +53 to +55
func (m *Model) themePickerHelpLine() string {
return helpKey("↓↑", "Scroll ") + helpKey("Enter", "Select ") + helpKey("Esc", "Close")
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Help line omits the new keys you just wired up.

handleThemeKey now supports pgup/pgdn, ctrl+u/d, home/g, end/G, and ctrl+x, but the footer only advertises ↓↑ / Enter / Esc. Discoverability for the new responsive-height + paging behavior suffers. Consider extending — at minimum mention Ctrl+X (Expand) since that's the headline of this PR.

♻️ Suggested footer hint additions
 func (m *Model) themePickerHelpLine() string {
-	return helpKey("↓↑", "Scroll ") + helpKey("Enter", "Select ") + helpKey("Esc", "Close")
+	return helpKey("↓↑", "Scroll ") + helpKey("PgUp/Dn", "Page ") +
+		helpKey("Ctrl+X", "Expand ") + helpKey("Enter", "Select ") + helpKey("Esc", "Close")
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (m *Model) themePickerHelpLine() string {
return helpKey("↓↑", "Scroll ") + helpKey("Enter", "Select ") + helpKey("Esc", "Close")
}
func (m *Model) themePickerHelpLine() string {
return helpKey("↓↑", "Scroll ") + helpKey("PgUp/Dn", "Page ") +
helpKey("Ctrl+X", "Expand ") + helpKey("Enter", "Select ") + helpKey("Esc", "Close")
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/model/overlays.go` around lines 53 - 55, The footer help line in
themePickerHelpLine currently only shows "↓↑ / Enter / Esc" but must include the
newly supported keys from handleThemeKey (PgUp/PgDn, Ctrl+U/Ctrl+D, Home/g,
End/G) and at minimum Ctrl+X for Expand; update themePickerHelpLine to add
helpKey entries for these keys (e.g., "PgUp/PgDn", "Ctrl+U/Ctrl+D", "Home/g",
"End/G", "Ctrl+X") so the footer reflects the responsive-height and paging
controls and improves discoverability.

@bjarneo bjarneo merged commit a09442e into bjarneo:main May 7, 2026
1 check passed
@zambetti zambetti deleted the lyrics-themes-responsive branch May 7, 2026 22:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants