From 97545feae752f4b6d92e2b1fbe554dbebaa7f249 Mon Sep 17 00:00:00 2001 From: vicrodh <103399283+vicrodh@users.noreply.github.com> Date: Sun, 6 Sep 2026 11:43:49 -0600 Subject: [PATCH] fix(discover): preserve sources on existing Most Played album cards Use one mapper for the carousel and full history page. Carry the stored routing source and normalized badge vocabulary through both surfaces, including existing rows, downloads and media servers. Cover existing snapshots and catalog aliases in the Qt suite; all 741 tests pass with temporary XDG data. cx --- crates/qbz-qt/src/browse_qt.rs | 20 +-------- crates/qbz-qt/src/home_qt.rs | 76 ++++++++++++++++++++++++++++------ 2 files changed, 64 insertions(+), 32 deletions(-) diff --git a/crates/qbz-qt/src/browse_qt.rs b/crates/qbz-qt/src/browse_qt.rs index fbaf167a0..741b2d97c 100644 --- a/crates/qbz-qt/src/browse_qt.rs +++ b/crates/qbz-qt/src/browse_qt.rs @@ -777,25 +777,7 @@ fn load_history(generation: u64) { || r.title.to_lowercase().contains(&needle) || r.artist.to_lowercase().contains(&needle) }) - .map(|r| HomeCard { - is_pinned: crate::sidebar_qt::is_pinned("album", &r.album_id), - // Heart, same seed as every other album card — this - // page's rows are built here rather than through - // `home_qt::map_*`, so it needs its own stamp. - is_favorite: crate::fav_cache_qt::is_album_favorite(&r.album_id), - id: r.album_id, - title: r.title, - artist: r.artist, - artist_id: r.artist_id, - year: r.year, - quality_tier: r.quality_tier, - quality_label: r.quality_label, - art_url: r.artwork_url, - // The 20px the 286px card is taller than every other - // album card (AlbumCard.slint:508). - plays: r.plays, - ..HomeCard::default() - }) + .map(crate::home_qt::map_played_album) .collect::>() } else { crate::recently_qt::load_albums() diff --git a/crates/qbz-qt/src/home_qt.rs b/crates/qbz-qt/src/home_qt.rs index d10535ded..fe7da4078 100644 --- a/crates/qbz-qt/src/home_qt.rs +++ b/crates/qbz-qt/src/home_qt.rs @@ -878,19 +878,7 @@ fn build_candidates( // (`album_play_history`, the shared per-app SQLite store). No network. let most_played: Vec = qbz_app::settings::album_play_history::top_albums(20) .into_iter() - .map(|r| HomeCard { - is_pinned: crate::sidebar_qt::is_pinned("album", &r.album_id), - is_favorite: crate::fav_cache_qt::is_album_favorite(&r.album_id), - id: r.album_id, - title: r.title, - artist: r.artist, - artist_id: r.artist_id, - year: r.year, - quality_tier: r.quality_tier, - quality_label: r.quality_label, - art_url: r.artwork_url, - ..HomeCard::default() - }) + .map(map_played_album) .collect(); if !most_played.is_empty() { out.push(HomeSection { @@ -2080,6 +2068,41 @@ pub(crate) fn map_flat_album(album: Album) -> HomeCard { } } +/// History stores keep their raw routing source. Badge data uses the shared +/// display vocabulary, including old rows, without requiring another play. +fn history_source_badges(source: &str) -> Vec { + if source.is_empty() { + return Vec::new(); // Legacy history means catalog, never inferred local. + } + vec![if source == "qobuz_purchase" { + source.to_string() + } else { + qbz_source::SourceBadge::from_word(Some(source)) + .as_str() + .into() + }] +} + +/// Shared by the Most Played carousel and its complete, filtered page. +pub(crate) fn map_played_album(r: qbz_app::settings::album_play_history::AlbumPlayRow) -> HomeCard { + HomeCard { + is_pinned: crate::sidebar_qt::is_pinned("album", &r.album_id), + is_favorite: crate::fav_cache_qt::is_album_favorite(&r.album_id), + sources: history_source_badges(&r.source), + source: r.source, + id: r.album_id, + title: r.title, + artist: r.artist, + artist_id: r.artist_id, + year: r.year, + quality_tier: r.quality_tier, + quality_label: r.quality_label, + art_url: r.artwork_url, + plays: r.plays, + ..HomeCard::default() + } +} + /// Map one recently-played album (local history) onto a card. The stored ISO /// release date is localized here exactly as the discover cards do. pub(crate) fn map_recent_album(a: crate::recently_qt::RecentAlbum) -> HomeCard { @@ -2099,6 +2122,7 @@ pub(crate) fn map_recent_album(a: crate::recently_qt::RecentAlbum) -> HomeCard { }, quality_tier: a.quality_tier, quality_label: a.quality_label, + sources: history_source_badges(&a.source), source: a.source, art_url: a.artwork_url, ..HomeCard::default() @@ -2384,6 +2408,32 @@ mod tests { } } + #[test] + fn old_most_played_rows_keep_their_source_on_both_card_surfaces() { + for (source, badge) in [ + ("local", "local"), + ("plex", "plex"), + ("jellyfin", "jellyfin"), + ("navidrome", "subsonic"), + ("qobuz_download", "offline"), + ("qobuz_purchase", "qobuz_purchase"), + ("qobuz", "qobuz"), + ("qobuz_connect_remote", "qobuz"), + ] { + let card = map_played_album(qbz_app::settings::album_play_history::AlbumPlayRow { + album_id: "stored-before-source-badge-fix".into(), + source: source.into(), + plays: 23, + ..Default::default() + }); + let wire = serde_json::to_value(card).unwrap(); + assert_eq!(wire["source"], source); + assert_eq!(wire["sources"], serde_json::json!([badge])); + assert_eq!(wire["plays"], 23); + } + assert!(history_source_badges("").is_empty()); + } + #[test] fn quality_tier_mapping() { assert_eq!(quality_tier(None), "");