feat(slint): you-may-also-like discovery section with sticky dismiss

Closes the network sidebar with the fifth section — tag-based "You may
also like" discovery, with a per-tag dismiss store so a thumbs-down
sticks across artist pages that share the same primary genre tag.

Backend (`qbz-integrations` + `qbz-core`):
  - DiscoveryArtist and DiscoveryResponse DTOs added to
    qbz-integrations::musicbrainz::models.
  - QbzCore::musicbrainz_discover_artists ports
    v2_get_discovery_artists end to end (sans the RecoState local
    "play count > 2" filter, which has no equivalent in the Slint
    shell yet): primary-tag search, exclude seed + similar +
    dismissed, deterministic shuffle keyed on seed_mbid, validate
    each candidate on Qobuz, fall back to the secondary tag if we
    have fewer than 5 matches.
  - normalize_artist_name re-exported at the qbz-core crate root so
    the dismiss handler in qbz-slint can use the same key shape that
    the discovery pipeline filters on.
  - Deterministic shuffle implemented with a simple xorshift PRNG
    seeded from a stable hash of seed_mbid — keeps qbz-core free of
    the rand dep.

Slint controller (`crates/qbz-slint/src/`):
  - discovery_dismiss.rs — small JSON store at
    `<data-dir>/qbz/discovery_dismiss.json` keyed by lowercased tag
    -> list of normalized artist names. dismissed_for_tag is passed
    into the discovery call as the filter source; dismiss() is
    invoked by the thumbs-down handler.
  - artist::load_mb_discovery / apply_mb_discovery /
    remove_discovery_artist plumb the pipeline result through the
    standard load-off-thread / apply-on-UI split. Discovery state
    includes primary_tag so the dismiss handler can persist under
    the right key.
  - main.rs chains discovery after relationships in the same MB
    enrichment task. NetworkSidebarActions.discovery-dismissed (now
    carrying mbid + name) normalizes the name, persists the
    dismiss, and removes the row from the visible list.

Slint UI (`ui/state.slint`, `ui/artist/ArtistPageView.slint`):
  - discovery-tag property added to NetworkSidebarState.
  - YOU MAY ALSO LIKE section after Relationships — one row per
    candidate (SidebarLink + a 24x24 thumbs-down button on the
    right). Clicking the link navigates via the artist-clicked
    callback when a Qobuz id is present.
  - thumbs-down.svg icon added to ui/assets/icons (Lucide
    ThumbsDown).

cc
This commit is contained in:
vicrodh committed 2026-05-20 10:39:53 -06:00
1 parent 72083adb7f
commit 165674d8a8
9 files changed
+529 -16

No files matched your search

@@ -554,6 +554,23 @@ pub struct LocationDiscoveryResponse {
pub next_offset: usize,
}
/// One candidate from the "you may also like" tag-based discovery pipeline.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DiscoveryArtist {
pub mbid: String,
pub name: String,
pub qobuz_id: Option<u64>,
}
/// Result of the tag-based discovery pipeline. `primary_tag` is the tag
/// the discovery was seeded with — frontends save dismissals keyed by
/// it so a "thumbs down" stays sticky for that tag across artists.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DiscoveryResponse {
pub artists: Vec<DiscoveryArtist>,
pub primary_tag: String,
}
// ============ Musician Types ============
/// Musician confidence level for MusicBrainz <-> Qobuz matching