From 8bb653ab3e9f99119f37b8010f1cf1287285e2c6 Mon Sep 17 00:00:00 2001 From: Test User Date: Tue, 21 Jul 2026 02:06:02 +0200 Subject: [PATCH] Clamp the progress gauge ratio MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The player position can overrun a stale or wrong duration (streams, hand-written track files), and ratatui's LineGauge asserts its ratio into 0..=1 — the TUI died with "ratio should be between 0 and 1" mid-playback. Clamp instead, with render regression tests for both the overrun and the zero-duration case. Co-Authored-By: Claude Fable 5 --- cbd-tui/src/app/now_playing.rs | 53 +++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/cbd-tui/src/app/now_playing.rs b/cbd-tui/src/app/now_playing.rs index 119a1e2..e5ec91b 100644 --- a/cbd-tui/src/app/now_playing.rs +++ b/cbd-tui/src/app/now_playing.rs @@ -142,10 +142,16 @@ impl NowPlaying { .constraints([Constraint::Min(10), Constraint::Max(completion_size)]) .split(now_playing_layout[1]); + // Clamped: the player's position can overrun a stale or wrong + // duration (streams, hand-written track files), and + // `LineGauge` panics on ratios outside 0..=1. let ratio = if duration.is_zero() { 0.0 } else { - position.as_secs_f64().div(duration.as_secs_f64()) + position + .as_secs_f64() + .div(duration.as_secs_f64()) + .clamp(0.0, 1.0) }; let progress = LineGauge::default() @@ -180,3 +186,48 @@ impl NowPlaying { } } } + +#[cfg(test)] +mod tests { + use super::*; + use ratatui::{backend::TestBackend, Terminal}; + + /// A now-playing pane mid-track, built directly (no `update_track`, + /// which fires a desktop notification). + fn now_playing(position_ms: u32, duration_ms: u32) -> NowPlaying { + NowPlaying { + play_state: PlayState::Playing, + duration: Some(Duration::from_millis(duration_ms.into())), + modifiers: QueueModifiers::default(), + position: Some(Duration::from_millis(position_ms.into())), + track: Some(Track { + path: "/fs/radio.cbd-track.toml".to_string(), + artist: "artist".to_string(), + title: "title".to_string(), + duration: None, + album: None, + }), + } + } + + fn render(pane: &NowPlaying) { + let backend = TestBackend::new(60, 12); + let mut terminal = Terminal::new(backend).expect("test terminal"); + terminal + .draw(|f| pane.render(f, f.area())) + .expect("draw must not panic"); + } + + /// The position can overrun a stale or wrong duration (streams, + /// hand-written track files); the gauge must clamp instead of hitting + /// ratatui's `ratio should be between 0 and 1` panic. + #[test] + fn progress_gauge_survives_position_past_duration() { + render(&now_playing(90_000, 60_000)); + } + + #[test] + fn progress_gauge_survives_a_zero_duration() { + render(&now_playing(5_000, 0)); + } +}