From f9de081bd5fe2e9846b9e6871172d204e0ccd2e0 Mon Sep 17 00:00:00 2001 From: Broque Thomas <26755000+Nezreka@users.noreply.github.com> Date: Sun, 19 Apr 2026 12:48:53 -0700 Subject: [PATCH] Fix library page crash when All letter filter is used soul_id.startsWith() threw TypeError for non-string values, crashing the entire card rendering pipeline. Letter-specific filters worked because the problematic artist wasn't in those filtered results. Added String() wrapper on all 3 soul_id.startsWith calls and a try-catch around individual card rendering so one bad card can't take down the whole page. --- web_server.py | 1 + webui/static/helper.js | 1 + webui/static/script.js | 11 +++++++---- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/web_server.py b/web_server.py index 8133fe86..dfafa15e 100644 --- a/web_server.py +++ b/web_server.py @@ -22480,6 +22480,7 @@ def get_version_info(): "• Fix replace lower quality setting not persisting", "• Fix Spotify enrichment worker infinite loop on pre-matched artists", "• Reject Qobuz 30-second sample/preview downloads", + "• Fix library page crash on All filter — non-string soul_id broke card rendering", ], }, { diff --git a/webui/static/helper.js b/webui/static/helper.js index bcfafb22..9d58d6b3 100644 --- a/webui/static/helper.js +++ b/webui/static/helper.js @@ -3625,6 +3625,7 @@ const WHATS_NEW = { { title: 'Improved Deep Scan Logging', desc: 'Per-artist log lines now show "0 new tracks (150 existing updated)" instead of misleading "0 tracks". Completion message shows "library up to date" when nothing is new' }, { title: 'Faster Standalone Verify', desc: 'Standalone verify button now stops counting after 10 audio files instead of 100, reducing verification time from 60+ seconds to near-instant on large libraries' }, { title: 'MusicBrainz Search Tab', desc: 'New search tab in Enhanced and Global search — find tracks and albums on MusicBrainz\'s community database. Cover art from Cover Art Archive. Click results to open download modal with full tracklist. Finds obscure tracks that Spotify/Deezer/iTunes miss', page: 'downloads' }, + { title: 'Fix Library Page Crash on All Filter', desc: 'Library page could crash with "No artists found" when viewing all artists if any artist had a non-string soul_id. Individual letter filters worked because the problematic artist wasn\'t in those results. Card rendering is now fault-tolerant — one bad artist card can\'t take down the whole page', page: 'library' }, // --- April 17, 2026 --- { date: 'April 17, 2026' }, diff --git a/webui/static/script.js b/webui/static/script.js index 81c84b60..a6e20828 100644 --- a/webui/static/script.js +++ b/webui/static/script.js @@ -43824,7 +43824,10 @@ function displayLibraryArtists(artists) { if (!grid) return; // Build all cards as HTML string for single DOM write (much faster than createElement loop) - grid.innerHTML = artists.map((artist, i) => buildLibraryArtistCardHTML(artist, i)).join(''); + grid.innerHTML = artists.map((artist, i) => { + try { return buildLibraryArtistCardHTML(artist, i); } + catch (e) { console.error('Failed to render artist card:', artist.name, e); return ''; } + }).join(''); // Attach click handlers via event delegation (single listener vs 75+ individual) grid.onclick = (e) => { @@ -43872,7 +43875,7 @@ function buildLibraryArtistCardHTML(artist, index) { if (artist.tidal_id) badges.push({ logo: TIDAL_LOGO_URL, fb: 'TD', title: 'Tidal', url: `https://tidal.com/browse/artist/${artist.tidal_id}` }); if (artist.qobuz_id) badges.push({ logo: QOBUZ_LOGO_URL, fb: 'Qz', title: 'Qobuz', url: `https://www.qobuz.com/artist/${artist.qobuz_id}` }); if (artist.discogs_id) badges.push({ logo: DISCOGS_LOGO_URL, fb: 'DC', title: 'Discogs', url: `https://www.discogs.com/artist/${artist.discogs_id}` }); - if (artist.soul_id && !artist.soul_id.startsWith('soul_unnamed_')) badges.push({ logo: '/static/trans2.png', fb: 'SS', title: `SoulID: ${artist.soul_id}`, url: null }); + if (artist.soul_id && !String(artist.soul_id).startsWith('soul_unnamed_')) badges.push({ logo: '/static/trans2.png', fb: 'SS', title: `SoulID: ${artist.soul_id}`, url: null }); // Watchlist badge const hasActiveSourceId = currentMusicSourceName === 'iTunes' @@ -44479,7 +44482,7 @@ function updateArtistDetailPageHeaderWithData(artist) { if (artist.tidal_id) badges.push(_hb(TIDAL_LOGO_URL, 'TD', 'Tidal', `https://tidal.com/browse/artist/${artist.tidal_id}`)); if (artist.qobuz_id) badges.push(_hb(QOBUZ_LOGO_URL, 'Qz', 'Qobuz', `https://www.qobuz.com/artist/${artist.qobuz_id}`)); if (artist.discogs_id) badges.push(_hb(DISCOGS_LOGO_URL, 'DC', 'Discogs', `https://www.discogs.com/artist/${artist.discogs_id}`)); - if (artist.soul_id && !artist.soul_id.startsWith('soul_unnamed_')) badges.push(_hb('/static/trans2.png', 'SS', `SoulID: ${artist.soul_id}`, null)); + if (artist.soul_id && !String(artist.soul_id).startsWith('soul_unnamed_')) badges.push(_hb('/static/trans2.png', 'SS', `SoulID: ${artist.soul_id}`, null)); badgesContainer.innerHTML = badges.join(''); } @@ -66406,7 +66409,7 @@ async function loadStatsData() { ${i + 1} ${item.image_url ? `` : ''}
-
${item.id ? `${_esc(item.name)}` : _esc(item.name)}${item.soul_id && !item.soul_id.startsWith('soul_unnamed_') ? ' ' : ''}
+
${item.id ? `${_esc(item.name)}` : _esc(item.name)}${item.soul_id && !String(item.soul_id).startsWith('soul_unnamed_') ? ' ' : ''}
${item.global_listeners ? _fmt(item.global_listeners) + ' global listeners' : ''}
${_fmt(item.play_count)} plays