From 9361c2996578fde60283e933c4373c3db86e8f8d Mon Sep 17 00:00:00 2001
From: Broque Thomas <26755000+Nezreka@users.noreply.github.com>
Date: Wed, 22 Apr 2026 13:36:36 -0700
Subject: [PATCH] Route all artist-detail callers to the standalone page, bump
to 2.45
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Phase 4a of the Search/Artists unification. The app had two artist-
detail implementations: the standalone page Library navigates to via
navigateToArtistDetail (its own route, deep-link support, highlights
Library in the sidebar), and an inline state inside the Artists page
reached via selectArtistForDetail. They rendered similar content but
were separate code paths and kept drifting apart (PR #356 just had
to fix source propagation in both).
Every external caller of selectArtistForDetail (9 sites across
api-monitor.js, discover.js, downloads.js, search.js) now calls
navigateToArtistDetail(id, name, source) directly. Removed ~63 lines
of the navigate-then-setTimeout-then-select dance. Source context
(Spotify/iTunes/Deezer/etc.) carries cleanly through via the new
third argument.
Artists sidebar entry, its inline search, and selectArtistForDetail
all still work — they just have no external callers. Phase 4b will
retire the sidebar entry and artists.js.
---
web_server.py | 13 +++++++++++-
webui/static/api-monitor.js | 11 ++--------
webui/static/discover.js | 40 +++++++------------------------------
webui/static/downloads.js | 25 +++--------------------
webui/static/helper.js | 5 +++++
webui/static/library.js | 6 +++---
webui/static/search.js | 13 +-----------
7 files changed, 33 insertions(+), 80 deletions(-)
diff --git a/web_server.py b/web_server.py
index 7b01e306..8865c21f 100644
--- a/web_server.py
+++ b/web_server.py
@@ -37,7 +37,7 @@ _log_dir = Path(_log_path).parent
logger = setup_logging(_log_level, _log_path)
# App version — single source of truth for backup metadata, version-info endpoint, etc.
-_SOULSYNC_BASE_VERSION = "2.44"
+_SOULSYNC_BASE_VERSION = "2.45"
def _build_version_string():
"""Append short commit hash to version when available (e.g. 2.35+abc1234)."""
@@ -22809,6 +22809,17 @@ def get_version_info():
"title": "What's New in SoulSync",
"subtitle": f"Version {SOULSYNC_VERSION} — Latest Changes",
"sections": [
+ {
+ "title": "Artist Links Everywhere Go to the Same Page",
+ "description": "Clicking an artist result in Search, Discover, the API Monitor, or anywhere else now lands on the standalone artist detail page Library already uses — instead of swapping into the Artists page's inline detail view",
+ "features": [
+ "• One artist detail page, not two — less navigation surprise and a stable URL (/artist-detail) for deep links",
+ "• Source context (Spotify/iTunes/Deezer/etc.) now carries cleanly into the destination so non-Spotify albums load from the right provider",
+ "• Removed the navigate-then-setTimeout dance at 9 callsites (api-monitor, discover, downloads, search, library recommendations)",
+ "• Artists sidebar entry and its inline search still work for now — next phase retires that page entirely",
+ "• Phase 4a of the Search/Artists unification project",
+ ],
+ },
{
"title": "Remove Embedded Download Manager from Search Page",
"description": "The Search page used to carry a second copy of the Download Manager (active + finished queues, clear/cancel-all buttons) that was hidden by default and duplicated the dedicated Downloads page. That duplicate is gone",
diff --git a/webui/static/api-monitor.js b/webui/static/api-monitor.js
index 3784e28c..3dcbd1b7 100644
--- a/webui/static/api-monitor.js
+++ b/webui/static/api-monitor.js
@@ -2382,16 +2382,9 @@ async function openWatchlistArtistDetailView(artistId, artistName) {
source = spotify_artist_id ? 'spotify' : discogs_artist_id ? 'discogs' : deezer_artist_id ? 'deezer' : 'itunes';
}
if (discogId) {
- // Close detail overlay and navigate to Artists page
+ // Close detail overlay and navigate to the standalone artist detail page
closeWatchlistArtistDetailView();
- // Navigate to Artists page and load discography
- navigateToPage('artists');
- setTimeout(() => {
- selectArtistForDetail(
- { id: discogId, name: artistName, image_url: artist.image_url || '' },
- { source: source }
- );
- }, 200);
+ navigateToArtistDetail(discogId, artistName, source);
}
});
diff --git a/webui/static/discover.js b/webui/static/discover.js
index 6763054f..e70e3b65 100644
--- a/webui/static/discover.js
+++ b/webui/static/discover.js
@@ -739,16 +739,7 @@ async function checkRecommendedWatchlistStatuses(artists) {
async function viewRecommendedArtistDiscography(artistId, artistName) {
closeRecommendedArtistsModal();
-
- const artist = {
- id: artistId,
- name: artistName
- };
-
- // Use same navigation pattern as hero slider
- navigateToPage('artists');
- await new Promise(resolve => setTimeout(resolve, 100));
- await selectArtistForDetail(artist);
+ navigateToArtistDetail(artistId, artistName);
}
async function checkAllHeroWatchlistStatus() {
@@ -830,25 +821,8 @@ async function viewDiscoverHeroDiscography() {
return;
}
- // Create artist object matching the expected format
- const artist = {
- id: artistId,
- name: artistName,
- image_url: discoverHeroArtists[discoverHeroIndex]?.image_url || '',
- genres: discoverHeroArtists[discoverHeroIndex]?.genres || [],
- popularity: discoverHeroArtists[discoverHeroIndex]?.popularity || 0
- };
-
console.log(`🎵 Navigating to artist detail for: ${artistName}`);
-
- // Navigate to Artists page
- navigateToPage('artists');
-
- // Small delay to let the page load
- await new Promise(resolve => setTimeout(resolve, 100));
-
- // Load the artist details
- await selectArtistForDetail(artist);
+ navigateToArtistDetail(artistId, artistName);
}
function showDiscoverHeroEmpty() {
@@ -4639,9 +4613,9 @@ function _renderYourArtistCard(artist) {
const watchlistClass = artist.on_watchlist ? 'active' : '';
const hasId = artist.active_source_id && artist.active_source_id !== '';
- // Navigate to artist page (name click)
+ // Navigate to standalone artist detail page (name click)
const navAction = hasId
- ? `event.stopPropagation(); navigateToPage('artists'); setTimeout(() => selectArtistForDetail({id:'${escapeForInlineJs(artist.active_source_id)}', name:'${escapeForInlineJs(artist.artist_name)}', image_url:'${escapeForInlineJs(img)}'}), 200)`
+ ? `event.stopPropagation(); navigateToArtistDetail('${escapeForInlineJs(artist.active_source_id)}', '${escapeForInlineJs(artist.artist_name)}')`
: '';
// Open info modal (card body click) — pass pool ID so we can look up all data
@@ -4820,7 +4794,7 @@ async function openYourArtistInfoModal(poolId) {
Explore
-