feat(qt): local "View more" routes into the LocalLibrary tabs

The three local sections' View more links now leave the search surface
entirely and land on the matching Local Library tab. Sending them to the
Qobuz results page instead would show a page with no on-device content at
all, which reads as the link having done nothing.

  local-album  -> Albums tab
  local-artist -> Artists tab
  local        -> Tracks tab, pre-filtered with the live query

The query pre-filters the TRACKS tab only. Albums and artists have no search
box of their own, so handing them a query would be a lie - the list would
come back unfiltered while something claimed otherwise.

The active tab is QML-owned state, so the route is a pending property the
view consumes, mirroring the localPendingArtist mechanism that already
existed for "go to artist" on a local album. It is released after being
applied, because the property CHANGE is the trigger and an unreleased route
could not fire twice in a row. The consumer also runs on mount, so a route
set while the view was unmounted still lands.

Setting the tracks filter writes the view's own search state as well as
issuing the query, or the search box would render empty next to a filtered
list.

Contract: qbz-nix-docs/qt-frontend/2026-08-03-cortinilla-parity, commit C9
(sections 3.4-3.5).

Gate: build clean, 258/258 tests, both QML audits OK, offscreen smoke
0 complaints + home published.

cc
This commit is contained in:
vicrodh committed 2026-08-03 17:33:01 -06:00
1 parent 7e58f2f43c
commit 4806584ee2
4 files changed
+83

No files matched your search

@@ -190,9 +190,32 @@ Rectangle {
selectedArtist = pending
QbzLocal.clearPendingArtist()
}
// A TAB route from the cortinilla's local "View more" links. Same shape
// as consumePendingArtist above: the property change is the trigger, so
// it must be released after it is applied or the same link cannot fire
// twice in a row.
function consumePendingRoute() {
var raw = QbzLocal.localPendingRoute
if (raw === "") return
var route
try { route = JSON.parse(raw) } catch (e) { QbzLocal.clearPendingRoute(); return }
if (route.tab) activeTab = route.tab
// The query pre-filters the TRACKS tab only; the albums and artists
// tabs have no search box of their own.
if (route.tab === "tracks" && route.query) {
// Set the view's own search state too, not just the query, or the
// box would render empty while the list is filtered.
root.tracksSearch = route.query
QbzLocal.tracksSearch(route.query)
}
QbzLocal.clearPendingRoute()
}
Connections {
target: QbzLocal
function onLocalPendingArtistChanged() { root.consumePendingArtist() }
function onLocalPendingRouteChanged() { root.consumePendingRoute() }
}
// Mount: load the default tab. Tab switches load on demand (each tab is
@@ -200,6 +223,7 @@ Rectangle {
Component.onCompleted: {
QbzLocal.loadTab(root.activeTab)
consumePendingArtist()
consumePendingRoute()
}
onActiveTabChanged: {
QbzLocal.loadTab(activeTab)