diff --git a/static/v3/songs.js b/static/v3/songs.js
index ed86acd..b3688c8 100644
--- a/static/v3/songs.js
+++ b/static/v3/songs.js
@@ -710,6 +710,10 @@
async function loadTree() {
const host = document.getElementById('v3-songs-tree');
if (!host) return;
+ // Capture expanded groups BEFORE the "Loading…" wipe below, so a reload
+ // (e.g. toggling select mode) restores them instead of collapsing all.
+ const openArtists = new Set(
+ [...host.querySelectorAll('details[open]')].map((d) => d.getAttribute('data-artist')));
host.innerHTML = '
Loading…
';
// Page through ALL artists — the endpoint clamps size to 100, so a
// single request would silently truncate libraries with >100 artists.
@@ -726,18 +730,33 @@
if (!artists.length) { host.innerHTML = '
' +
+ checkbox +
+ '' +
'' + esc(s.title) + '' +
- (chips ? '' + chips + '' : '') +
+ (chips ? '' + chips + '' : '') +
(fl ? '' + fl + '' : '') +
accuracyBadge(k, 'tree') +
- '' +
+ // Same fav / save-for-later / overflow-menu cluster as the grid
+ // card. Always shown (like the arrangement chips), not hover-
+ // revealed. wireCards() binds all three for any [data-fn].
+ '
' +
+ '' +
+ '' +
+ '' +
+ '
' +
'
'); }).join('') + '
').join('') + '
').join('');
wireCards(host);
}
@@ -945,6 +964,21 @@
e.stopImmediatePropagation();
toggleSelect(card.getAttribute('data-fn'), card);
}, true);
+
+ // Same bulletproof guard for the list/tree view. Without it, clicking a
+ // song row (or its arrangement chip) in select mode falls through to the
+ // per-card play handler and starts playback instead of selecting. The
+ // group headers sit OUTSIDE any [data-fn], so closest() is null
+ // for them and their native expand/collapse is left untouched.
+ const treeEl = byId('v3-songs-tree');
+ if (treeEl) treeEl.addEventListener('click', (e) => {
+ if (!state.selectMode) return;
+ const card = e.target.closest('[data-fn]');
+ if (!card || !treeEl.contains(card)) return;
+ e.preventDefault();
+ e.stopImmediatePropagation();
+ toggleSelect(card.getAttribute('data-fn'), card);
+ }, true);
const setView = (v) => {
state.view = v;
byId('v3-songs-grid-btn').className = 'px-3 py-2 text-sm ' + (v === 'grid' ? 'bg-fb-primary text-white' : 'text-fb-textDim');
@@ -1097,4 +1131,4 @@
if (active && active.id === 'v3-songs') applyScoreRefresh();
});
}
-})();
+})();
\ No newline at end of file