feat(qbz-qt): close the two declared debts on the album and artist pages

The Last.fm similar-albums row exists now. The recommendation building was
already frontend-agnostic in qbz-external-reco, so this only adapts QbzCore to
its catalog trait and publishes the result; the row seeds on the album's artist
and excludes what the two Qobuz rows already show, including ids the
pre-resolution dedup misses. Resolved rows cache per album for thirty days in
the same store the Discover tab uses, so re-opening an album makes no Last.fm
or Qobuz calls, and only a non-empty result is cached — a transient failure
must not hide the row until restart. With Last.fm not connected it resolves to
nothing and makes no network call, so the rail simply never appears.

Relationship rows navigate. They carry a name rather than a catalog id, so the
click resolves through MusicBrainz first and only a CONFIRMED match opens an
artist — landing the user on a same-name musician is worse than a row that does
nothing. The role travels with the name so a match in another role is not
mistaken for this one.

cc
This commit is contained in:
vicrodh committed 2026-07-28 16:45:45 -06:00
1 parent 960715c4ac
commit d76f694781
8 files changed
+282 -5

No files matched your search

+15
View File
@@ -86,6 +86,10 @@ Rectangle {
for (var i = 0; i < more.length; i++) if (more[i].artUrl) urls.push(more[i].artUrl)
var sug = album.suggestions || []
for (i = 0; i < sug.length; i++) if (sug[i].artUrl) urls.push(sug[i].artUrl)
// The Last.fm row's covers ride the same dispatch — without this its
// cards would render as empty frames.
var sim = album.similarAlbums || []
for (i = 0; i < sim.length; i++) if (sim[i].artUrl) urls.push(sim[i].artUrl)
if (urls.length > 0) QbzShell.sidebarArtworkWindow(JSON.stringify(urls))
}
@@ -731,6 +735,17 @@ Rectangle {
items: album.suggestions || []
coverMap: root.coverMap
}
// Last.fm row. Absent — not empty — when Last.fm is not connected:
// external_reco_qt returns nothing and makes no network call, so the
// rail simply never becomes visible. Reuses the same delegate as the
// two Qobuz rows rather than a fourth card variant.
Item { visible: (album.similarAlbums || []).length > 0; width: 1; height: 40 }
SectionRail {
visible: (album.similarAlbums || []).length > 0
title: QbzSession.tr("Similar albums", QbzSession.trRev)
items: album.similarAlbums || []
coverMap: root.coverMap
}
}
}
+14 -5
View File
@@ -554,6 +554,10 @@ Rectangle {
property string title: ""
property var rows: []
property string iconName: "user"
/// The MusicBrainz role this group represents ("member", "producer",
/// …) — passed to the resolver so a same-name match in another role is
/// not treated as this musician.
property string roleKey: ""
property bool expanded: false
signal toggled()
visible: rows.length > 0
@@ -574,11 +578,13 @@ Rectangle {
label: modelData.name
tooltip: modelData.tooltip
iconName: relGroup.iconName
// POC-NOTE: the Slint row resolves the musician through
// MusicBrainz and lands on the Qobuz artist (or its
// MusicianPageView). Neither exists here, so the row is
// informational — see GLUE in the handoff report.
navigable: false
// Relationship rows carry a NAME, not a catalog id, so the
// click resolves through MusicBrainz first. Only a confirmed
// match navigates (resolve_musician logs and stays put
// otherwise) — landing the user on a same-name artist is worse
// than the row doing nothing.
navigable: true
onClicked: QbzArtist.resolveMusician(modelData.name, relGroup.roleKey || "")
}
}
Text {
@@ -1561,6 +1567,7 @@ Rectangle {
&& (mbRelationships.members || []).length > 0
title: QbzSession.tr("Members & Former", QbzSession.trRev)
rows: mbRelationships.members || []
roleKey: "member"
iconName: "user"
expanded: root.membersExpanded
onToggled: root.membersExpanded = !root.membersExpanded
@@ -1570,6 +1577,7 @@ Rectangle {
&& (mbRelationships.groups || []).length > 0
title: QbzSession.tr("Member Of", QbzSession.trRev)
rows: mbRelationships.groups || []
roleKey: "member of"
iconName: "music"
expanded: root.groupsExpanded
onToggled: root.groupsExpanded = !root.groupsExpanded
@@ -1579,6 +1587,7 @@ Rectangle {
&& (mbRelationships.collaborators || []).length > 0
title: QbzSession.tr("Collaborators", QbzSession.trRev)
rows: mbRelationships.collaborators || []
roleKey: "collaborator"
iconName: "user"
expanded: root.collabsExpanded
onToggled: root.collabsExpanded = !root.collabsExpanded