fix(qt): bound the queue panels, and make the gate catch what it missed

Follow-up to 66ba12bd5, which fixed the coverflow but left two of its
neighbours and a gate that could not see either of them.

QueueTabsPanel built one ImmQueueRow per queue track when opened —
including every collapsed 0-height history slot, which on the reported
session was 1162 of 1935. The slot stays (its height is what makes the
collapsed-history arithmetic work); the row inside is now built only
within a viewport of the visible band.

SuggestionsPanel and AppShell's QueuePanel move behind Loaders. The
queue panel was never the freeze — its rows are paginated at 40 in
Rust — but a closed panel should not be constructed, and the audit
cannot know about a cap that lives on the other side of the bridge.
Gating it also let SuggestionsPanel drop the onVisibleChanged half of
its load hook: mounting IS becoming visible now.

The audit is the real content of this commit. Written narrow — only a
model fed by unbounded user data counts, because the wide rule reports
145 sites and a gate that noisy gets switched off in a day — it then
reported the pre-fix tree CLEAN. A gate that cannot catch the bug it
was written for is worth nothing, so it was tested against that tree
and fixed on two counts:

  - CROSS-FILE. The `visible:` sat in ImmersiveView.qml while the two
    Repeaters over the whole queue lived in CoverflowPanel.qml. There
    is now a transitive index: a component is heavy if its own file has
    an unbounded view or if it mounts something that does.
  - MULTI-LINE. The coverflow's gate spans two lines, so a first-line
    read saw `QbzShaderScene.scene === 0`, matched no known gate, and
    skipped it.

With both, it finds all five mounts on the pre-fix tree, including the
two that froze the app. Known gates also grew past tab predicates: the
coverflow was keyed on QbzImmersive.mode.

Verified on a copy of the real session state with the 1935-row queue
restored: launches to a painted Discover, idle at 1.0% CPU.

cc
This commit is contained in:
vicrodh committed 2026-08-18 07:48:56 -06:00
1 parent 0ed9db2f3f
commit 47a7641fc2
6 files changed
+174 -90

No files matched your search

+49 -35
View File
@@ -144,7 +144,7 @@ Item {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: function (mouse) { rowMenu.openAtCursor(moreArea, mouse.x, mouse.y) }
onClicked: function (mouse) { root.openRowMenu(moreArea, mouse.x, mouse.y) }
}
}
@@ -187,44 +187,58 @@ Item {
anchors.fill: parent
enabled: !root.selectMode
acceptedButtons: Qt.RightButton
onClicked: function (mouse) { rowMenu.openAtCursor(rcArea, mouse.x, mouse.y) }
onClicked: function (mouse) { root.openRowMenu(rcArea, mouse.x, mouse.y) }
}
CardMenu {
id: rowMenu
menuWidth: 220
entries: {
var m = [
{ "label": QbzSession.tr("Play now", QbzSession.trRev), "icon": "play-fill", "action": "play" },
{ "label": QbzSession.tr("Play next", QbzSession.trRev), "icon": "list-start", "action": "next" },
{ "label": QbzSession.tr("Play later", QbzSession.trRev), "icon": "list-plus", "action": "later" },
{ "label": QbzSession.tr("Add to queue", QbzSession.trRev), "icon": "list-end", "action": "queue" },
{ "label": QbzSession.tr("Add to mixtape", QbzSession.trRev), "icon": "cassette-tape", "action": "add-to-mixtape" },
{ "label": QbzSession.tr("Add to playlist", QbzSession.trRev), "icon": "list-music", "action": "add-to-playlist" },
]
if (root.canGoAlbum) m.push({ "label": QbzSession.tr("Go to album", QbzSession.trRev), "icon": "disc-3", "action": "go-album" })
if (root.canGoArtist) m.push({ "label": QbzSession.tr("Go to artist", QbzSession.trRev), "icon": "user", "action": "go-artist" })
return m
}
onPicked: function (a) {
if (a === "play") root.playRequested()
// LOCAL-mode adds: one-element selection through the bulk entry
// point, which owns the only source-aware row -> ref resolver
// (see the file header).
else if (a === "add-to-playlist" || a === "add-to-mixtape") {
QbzLocal.bulkAction("track", JSON.stringify([String(root.item.id)]), a)
// LAZY. This is a QtQuick.Controls Popup with a Repeater over its
// entries, and it was built EAGERLY for every row — so a long list
// constructed one whole popup per visible row, and rebuilt them all
// on every scroll step, to show a menu only ever opened on click.
// Same lazy-Loader idiom this file already uses for the clipboard
// helper and the info modal.
Loader {
id: rowMenuLoader
active: false
sourceComponent: CardMenu {
menuWidth: 220
entries: {
var m = [
{ "label": QbzSession.tr("Play now", QbzSession.trRev), "icon": "play-fill", "action": "play" },
{ "label": QbzSession.tr("Play next", QbzSession.trRev), "icon": "list-start", "action": "next" },
{ "label": QbzSession.tr("Play later", QbzSession.trRev), "icon": "list-plus", "action": "later" },
{ "label": QbzSession.tr("Add to queue", QbzSession.trRev), "icon": "list-end", "action": "queue" },
{ "label": QbzSession.tr("Add to mixtape", QbzSession.trRev), "icon": "cassette-tape", "action": "add-to-mixtape" },
{ "label": QbzSession.tr("Add to playlist", QbzSession.trRev), "icon": "list-music", "action": "add-to-playlist" },
]
if (root.canGoAlbum) m.push({ "label": QbzSession.tr("Go to album", QbzSession.trRev), "icon": "disc-3", "action": "go-album" })
if (root.canGoArtist) m.push({ "label": QbzSession.tr("Go to artist", QbzSession.trRev), "icon": "user", "action": "go-artist" })
return m
}
else if (a === "go-album") {
QbzLocal.openAlbum(root.item.albumId)
QbzShell.navigateTo("localalbum")
} else if (a === "go-artist") {
// Local/Plex artists have no catalog id — a NAME route into
// the Local Library Artists tab (local_album_actions.rs).
QbzLocal.openArtistByName(root.item.artist)
QbzShell.navigateTo("local")
} else {
root.enqueueRequested(a)
onPicked: function (a) {
if (a === "play") root.playRequested()
// LOCAL-mode adds: one-element selection through the bulk entry
// point, which owns the only source-aware row -> ref resolver
// (see the file header).
else if (a === "add-to-playlist" || a === "add-to-mixtape") {
QbzLocal.bulkAction("track", JSON.stringify([String(root.item.id)]), a)
}
else if (a === "go-album") {
QbzLocal.openAlbum(root.item.albumId)
QbzShell.navigateTo("localalbum")
} else if (a === "go-artist") {
// Local/Plex artists have no catalog id — a NAME route into
// the Local Library Artists tab (local_album_actions.rs).
QbzLocal.openArtistByName(root.item.artist)
QbzShell.navigateTo("local")
} else {
root.enqueueRequested(a)
}
}
}
}
/// Build the popup on first use, then open it.
function openRowMenu(anchor, x, y) {
rowMenuLoader.active = true
rowMenuLoader.item.openAtCursor(anchor, x, y)
}
}