From 052547c04a9b1c7b76cedc951ce0de8f330073d9 Mon Sep 17 00:00:00 2001 From: BoulderBadgeDad Date: Mon, 29 Jun 2026 15:51:43 -0700 Subject: [PATCH] discover: reorder sections into Spotify-style grouping + blank optional track fields _reorderDiscoverSections() runs after all loaders and moves each section (incl. ones nested in the mid-page #discover-bylt-sections) into the intended top-to-bottom order: Genre Explorer -> Your/Year Mixes -> for-you (listening/recommended/BYLT) -> new (recent/genres/seasonal) -> library (artists/ albums) -> deeper digs (undiscovered/labels/deep cuts) -> stations & tools (Last.fm/ListenBrainz/ Build). Hero + Artist Map stay near the top; hidden collapsed-mix sections are left invisible. Also: ListenBrainz/Last.fm recording playlists carry no album or duration, so _normalizeTrack now leaves album blank (not "Unknown Album") and renderCompactPlaylist blanks the duration when 0 (not "0:00"), matching the original displayListenBrainzTracks behaviour. 64 script-integrity tests green. --- webui/static/discover.js | 47 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/webui/static/discover.js b/webui/static/discover.js index 95e13f48..822eb9e2 100644 --- a/webui/static/discover.js +++ b/webui/static/discover.js @@ -55,10 +55,51 @@ async function loadDiscoverPage() { loadDiscoveryBlacklist() // Blocked artists list ]); + // Reorder sections into the intended top-to-bottom grouping now that every loader has run. + _reorderDiscoverSections(); + // Check for active syncs after page load checkForActiveDiscoverSyncs(); } +// #discover redesign: sections were authored in code-order (and several inject into a mid-page +// sub-container, #discover-bylt-sections), so the page reads as a jumble. After all loaders run, +// move each section into the intended Spotify-style order. appendChild MOVES nodes — including ones +// nested in #discover-bylt-sections — into .discover-container, so the cache sections get pulled out +// to their own slots while the BYLT container keeps just its "Because you listen to" rows. Hidden / +// collapsed sections (old mix tables, Daily Mixes) are left untouched and stay invisible. The hero +// and the Artist Map hub are left in place near the top. +function _reorderDiscoverSections() { + const container = document.querySelector('.discover-container'); + if (!container) return; + const ORDER = [ + { id: 'cache-genre-explorer' }, // Genre Explorer (pills) — quick browse + { id: 'your-mixes-section' }, // Made for you + { id: 'year-mixes-section' }, + { id: 'listening-recs-section' }, // For you + { id: 'recommended-artists-section' }, + { id: 'discover-bylt-sections' }, // Because You Listen To (cache sections pulled out below) + { title: 'recent releases' }, // New + { id: 'cache-genre-releases' }, // New In Your Genres + { id: 'seasonal-albums-section' }, + { id: 'your-artists-section' }, // Your library + { id: 'your-albums-section' }, + { id: 'cache-undiscovered' }, // Deeper digs + { id: 'cache-label-explorer' }, // From Your Labels + { id: 'cache-deep-cuts' }, + { title: 'last.fm radio' }, // Stations & tools + { title: 'listenbrainz' }, + { title: 'build a playlist' }, + ]; + const byTitle = (t) => Array.from( + document.querySelectorAll('.discover-container .discover-section, #discover-bylt-sections .discover-section')) + .find(s => (s.querySelector('.discover-section-title')?.textContent || '').toLowerCase().includes(t)); + ORDER.forEach(spec => { + const node = spec.id ? document.getElementById(spec.id) : byTitle(spec.title); + if (node && node.parentElement) container.appendChild(node); + }); +} + async function checkForActiveDiscoverSyncs() { // Check if Fresh Tape sync is active try { @@ -4405,7 +4446,9 @@ function _normalizeTrack(track) { return { name: td.name || td.track_name || track.track_name || 'Unknown Track', artist: (a0 && (a0.name || a0)) || td.artist_name || track.artist_name || 'Unknown Artist', - album: (td.album && td.album.name) || td.album_name || track.album_name || 'Unknown Album', + // album/duration are optional (ListenBrainz recording playlists carry neither) — leave blank + // rather than printing "Unknown Album" / "0:00". + album: (td.album && td.album.name) || td.album_name || track.album_name || '', cover: (td.album && td.album.images && td.album.images[0] && td.album.images[0].url) || track.album_cover_url || '', durationMs: td.duration_ms || track.duration_ms || 0, }; @@ -4419,7 +4462,7 @@ function renderCompactPlaylist(container, tracks) { const coverUrl = t.cover || '/static/placeholder-album.png'; const durationMin = Math.floor(t.durationMs / 60000); const durationSec = Math.floor((t.durationMs % 60000) / 1000); - const duration = `${durationMin}:${durationSec.toString().padStart(2, '0')}`; + const duration = t.durationMs > 0 ? `${durationMin}:${durationSec.toString().padStart(2, '0')}` : ''; html += `