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 ? `` : ''}
' : ''}
' : ''}