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
-