Skip to content

Fix expanded-height drift#217

Merged
bjarneo merged 5 commits into
bjarneo:mainfrom
zambetti:expanded-height-fixes
May 7, 2026
Merged

Fix expanded-height drift#217
bjarneo merged 5 commits into
bjarneo:mainfrom
zambetti:expanded-height-fixes

Conversation

@zambetti
Copy link
Copy Markdown
Contributor

@zambetti zambetti commented May 7, 2026

Note

This PR should be stacked on PR #215, but unfortunately it looks like this PR also includes the commits from PR #215. If PR #215 is approved to be merged, then I think I can rebase this branch on main afterward so those commits won't be on this PR. Github might be smart enough to do this automatically if PR #215 is merged. Apologies for this confusion.

Summary

Fixes expanded-height ctrl+x drift by recalculating playlist viewport height/scroll when dynamic UI rows change (provider pill, status/log/footer, stream title, and playlist load events), and by clamping/padding empty states to the visible budget.

Changes

  • Updated playlist height measurement to account for provider pill row
  • Reapplied height mode + scroll adjustment when:
    • Status message expires
    • Log lines change
    • Stream title changes
    • Tracks are loaded/replaced (provider load, feed load, file-browser add/replace)
  • Normalized empty-state rendering in provider/playlist panes to always respect the visible budget (clamp + pad)

Summary by CodeRabbit

  • New Features

    • Theme picker overlay now includes improved keyboard navigation with dynamic scroll adjustment
    • Lyrics display now intelligently calculates visible height based on UI expansion and available space
  • UI/UX Improvements

    • Updated Ctrl+X keybinding to toggle expanded view across file browser and overlays
    • Enhanced scroll management and positioning for better overlay navigation experience

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 7, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR refactors overlay scrolling and visibility management. It adds a scroll field to track theme picker position, introduces helper methods to compute visibility dimensions and manage scroll bounds, renames toggle behavior to toggleExpandedView(), refactors theme picker navigation with a consistent pattern, and updates view rendering and lifecycle handlers to apply layout adjustments at appropriate times.

Changes

Overlay Scrolling and Height Management

Layer / File(s) Summary
State Schema
ui/model/state.go
themePickerState adds a scroll field to track vertical scrolling position within the theme picker overlay.
Visibility & Scroll Helpers
ui/model/overlays.go, ui/model/lyrics.go
New methods themePickerVisible(), themePickerMaybeAdjustScroll(), and themePickerHelpLine() compute overlay visibility dimensions and manage scroll bounds. lyricsVisibleHeight() centralizes lyric viewport height calculation. Imports updated to include lipgloss explicitly.
Key Handler Refactoring
ui/model/keys.go, ui/model/keymap.go, ui/model/filebrowser.go
toggleExpandPlaylist() renamed to toggleExpandedView() across all handlers. Lyrics and provider overlays now support ctrl+x binding. Theme picker navigation refactored from separate cursor/preview calls per key to a shared pattern: update cursor, call themePickerApply(), then call themePickerMaybeAdjustScroll().
View Rendering Integration
ui/model/view_overlays.go, ui/model/view.go
Theme picker rendering uses themePickerVisible() for dynamic sizing and themePicker.scroll directly; help text switches to themePickerHelpLine(). Lyrics overlay introduces shared visible := lyricsVisibleHeight() and uses padLines() for consistent height padding. Empty state and empty playlist rendering clamp output to budget before padding.
Update Handler Lifecycle
ui/model/update.go
Status expiration processing now precedes log-line expiration in tick handlers. applyHeightMode() and adjustScroll() are called conditionally when status clears or log lines change during ticks, and unconditionally after playlist state changes (tracks loaded, episodes resolved, file browser tracks resolved).
Layout Measurement
ui/model/scroll.go
Probe text assembly in measurePlVisible() reorders separator strings between rendered components, affecting computed playlist visible line count.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fix expanded-height drift' directly describes the main objective of the pull request: preventing the expanded playlist height from drifting out of sync when ctrl+x is used or when dynamic UI rows change.
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: 1

Caution

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

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

433-458: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

feedsLoadedMsg is missing applyHeightMode()/adjustScroll() after m.playlist.Add(), inconsistent with every other playlist-mutation handler.

tracksLoadedMsg (line 307), feedTrackResolvedMsg (line 426), and fbTracksResolvedMsg (line 500) all call applyHeightMode()/adjustScroll(). The PR description explicitly lists "feed load" among the events that should trigger recalculation. Without the call here, the playlist scroll position can drift after URL feed loading completes (especially when transitioning from feedLoading=true to showing real tracks while in expanded-height mode).

🐛 Proposed fix
 	case feedsLoadedMsg:
 		m.feedLoading = false
 		if len(msg.tracks) > 0 {
 			m.playlist.Add(msg.tracks...)
 			m.status.Showf(statusTTLDefault, "Loaded %d track(s)", len(msg.tracks))
 		} else {
 			m.status.Show("No tracks found at URL.", statusTTLDefault)
 		}
 		if len(msg.tracks) > 0 {
+			m.applyHeightMode()
+			m.adjustScroll()
 			batchCmd := m.initYTDLBatch(msg.urls)
🤖 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/update.go` around lines 433 - 458, The feedsLoadedMsg handler
updates the playlist via m.playlist.Add(...) but omits calling applyHeightMode()
and adjustScroll(), causing scroll drift; after adding tracks in the
feedsLoadedMsg case (immediately after m.playlist.Add(msg.tracks...)), call
m.applyHeightMode() and m.adjustScroll() before any play/notify/return logic
(keep the existing m.initYTDLBatch(msg.urls) and autoplay handling intact).
🤖 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 57-66: The lyricsVisibleHeight function hardcodes an overhead of 4
which breaks the probe-based pattern used elsewhere; replace the hardcoded
subtraction with a probe-frame height calculation like the other visible-height
helpers: build a representative probe of the lyrics overlay (mirroring
renderLyricsOverlay's header/top spacing and footer/help key structure), call
lipgloss.Height on that probe, and use that value instead of the literal 4 when
computing return max(3, min(limit, m.height - probeHeight)); keep references to
maxPlVisible, m.heightExpanded and m.height as in the current function.

---

Outside diff comments:
In `@ui/model/update.go`:
- Around line 433-458: The feedsLoadedMsg handler updates the playlist via
m.playlist.Add(...) but omits calling applyHeightMode() and adjustScroll(),
causing scroll drift; after adding tracks in the feedsLoadedMsg case
(immediately after m.playlist.Add(msg.tracks...)), call m.applyHeightMode() and
m.adjustScroll() before any play/notify/return logic (keep the existing
m.initYTDLBatch(msg.urls) and autoplay handling intact).
🪄 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: dc6d10b8-2b44-4272-aa42-8b02f4e9cf23

📥 Commits

Reviewing files that changed from the base of the PR and between 761c921 and 6d74b79.

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

Comment thread ui/model/lyrics.go
Comment on lines +57 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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Locate renderLyricsOverlay and inspect its structure to count fixed sections.
ast-grep --pattern 'func ($_ Model) renderLyricsOverlay() string {
  $$$
}'

Repository: bjarneo/cliamp

Length of output: 4845


Hardcoded overhead - 4 is correct but deviates from the probe-based pattern used elsewhere.

The fixed overhead of 4 lines is accurate: renderLyricsOverlay has header (1) + spacing (1) at the top and footer spacing (1) + help key (1) at the bottom. However, this implementation diverges from the established pattern used by every other visible-height calculator (themePickerVisible, keymapVisible, fbVisible, etc.), which render a representative probe frame with lipgloss.Height() to stay resilient to layout changes.

If the lyrics overlay structure ever changes, this hardcoded value will silently become incorrect. Aligning with the probe-based pattern would eliminate that fragility:

♻️ Probe-based refactor
-// 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))
-}
+// lyricsVisibleHeight returns the number of lyrics lines to show.
+func (m *Model) lyricsVisibleHeight() int {
+	probeSections := []string{
+		titleStyle.Render("L Y R I C S"),
+		"",
+		"x",
+		"",
+		helpKey("Esc", "Close"),
+	}
+	probeFrame := ui.FrameStyle.Render(strings.Join(probeSections, "\n"))
+	fixedHeight := lipgloss.Height(probeFrame)
+	limit := maxPlVisible
+	if m.heightExpanded {
+		limit = m.height
+	}
+	return max(3, min(limit, m.height-fixedHeight))
+}
🤖 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 57 - 66, The lyricsVisibleHeight function
hardcodes an overhead of 4 which breaks the probe-based pattern used elsewhere;
replace the hardcoded subtraction with a probe-frame height calculation like the
other visible-height helpers: build a representative probe of the lyrics overlay
(mirroring renderLyricsOverlay's header/top spacing and footer/help key
structure), call lipgloss.Height on that probe, and use that value instead of
the literal 4 when computing return max(3, min(limit, m.height - probeHeight));
keep references to maxPlVisible, m.heightExpanded and m.height as in the current
function.

@bjarneo
Copy link
Copy Markdown
Owner

bjarneo commented May 7, 2026

Thank you for both. Looks like it will be fine. Great additions!

@bjarneo bjarneo merged commit 4afb888 into bjarneo:main May 7, 2026
1 check passed
@zambetti zambetti deleted the expanded-height-fixes 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