feat(qt): award pages, reachable from the album sidebar and from each other

Ports the Slint/Tauri award pair: the landing (gold laurel hero, magazine
line, follow heart, "Acclaimed Releases" with its count and See All, the
preview grid and the "Other awards" rail) and the paginated listing with
its client-side search. Routes "award" and "awardalbums", the label pair's
shape — one controller for both, since they share the hero, the follow
state and the identity.

The three rules from the Tauri review are in the module header because they
are the ones a re-implementation gets wrong: the grid is ALWAYS
/award/getAlbums (/award/page is user-scoped and may omit releases, so it
feeds the hero and nothing else); the favorite split is plural-read,
singular-write, because the backend derives `award_ids` from the write
kind; and getAlbums has no has_more, its `total` is a hint, so the only
sound predicate is `total > loaded`.

The album sidebar's AWARDS cards navigate now — with an id directly, and by
NAME through the resolver when Qobuz omits it, which it does. They were
also reading `album.awards` when the field lives on `album.header`, so the
block had rendered EMPTY for every album since it was written: an empty
Repeater draws nothing and reports nothing, and the sidebar's own gate
counted the same empty list. The LABEL card beside it had a "no label view
yet" note and no handler, years after the label view landed.

Beyond the reference: an all-awards dropdown pinned to the page's top-right,
so any award is reachable without first finding an album that won it. It
costs nothing new — the /award/explore crawl already existed to resolve
names to ids.

The award icon was lucide's MICROPHONE under the wrong filename. Search had
routed around it with `crown` and left a note; kiosk was missed and drew a
mic for "Most popular". The asset is the laurel the other two frontends use,
and kiosk now asks for the crown it wanted.

cc
This commit is contained in:
vicrodh committed 2026-08-16 18:56:20 -06:00
1 parent 60bf5b8365
commit dc2496e5c0
18 files changed
+1691 -41

No files matched your search

+28 -3
View File
@@ -46,7 +46,14 @@ Rectangle {
readonly property var album: JSON.parse(QbzAlbum.albumJson)
readonly property var header: album.header || ({})
readonly property var tracks: album.tracks || []
readonly property var awards: album.awards || []
// `header.awards`, NOT `album.awards`. It is a field of AlbumHeader
// (src/album_qt.rs:171), not of AlbumViewData — read one level too high it
// is `undefined`, `|| []` swallows that, and the AWARDS block simply never
// renders. Which is what it did, for every album, since the block was
// written: an empty Repeater draws nothing and reports nothing, and
// `hasSidebar` counted the same empty list, so an album whose ONLY sidebar
// content was its awards showed no sidebar at all.
readonly property var awards: header.awards || []
property var coverMap: ({})
// Client-side track search (AlbumActions.search equivalent).
property string trackQuery: ""
@@ -1162,7 +1169,13 @@ Rectangle {
iconName: "disc"
gradA: "#6366f1"
gradB: "#8b5cf6"
// POC-NOTE: no label view yet.
// The label page HAS existed for a long time — the
// header chip above (:691) has been opening it all
// along. This card carried a "no label view yet"
// note and no handler, so the sidebar's LABEL entry
// looked clickable (it hovers) and did nothing.
onClicked: if ((header.labelId || "") !== "")
QbzHome.openLabel(header.labelId)
}
}
Column {
@@ -1178,7 +1191,19 @@ Rectangle {
iconName: "award"
gradA: "#b45309"
gradB: "#eab308"
// POC-NOTE: no award view yet.
// `modelData` is the (id, name) pair
// album_qt publishes. Qobuz omits the id on
// some /album/get entries, so with one we open
// directly and without one we hand the NAME to
// the resolver — 1:1 with the reference's
// "open" / "resolve-open" split
// (AlbumPageView.slint:1049-1055).
onClicked: {
if ((modelData[0] || "") !== "")
QbzHome.openAward(modelData[0], modelData[1] || "")
else if ((modelData[1] || "") !== "")
QbzHome.openAwardByName(modelData[1])
}
}
}
}