Rename Search page id from 'downloads' to 'search', bump to 2.43

Phase 3b of the Search/Artists unification. The Search page's
internal id was 'downloads', which clashed with the actual Downloads
page (id 'active-downloads') and confused anyone reading the code.
Renamed to 'search' across HTML, navigation, DOM selectors, and the
deep-link route list.

Backwards compat: navigateToPage('downloads') aliases to 'search'
at the top of the function; /downloads URL still serves index.html
and the client router resolves the page correctly; profile ACL
checks accept both 'search' and 'downloads' so existing profiles
with 'downloads' in allowed_pages keep working without migration.

Sidebar label unchanged. Zero visual change — pure internal tidy.
pull/361/head
Broque Thomas 1 month ago
parent 68d46c5aba
commit 6992e2e5b5

@ -68,7 +68,7 @@ class TestSpaRoutes:
"""Deep-link paths for valid client pages should serve index.html."""
@pytest.mark.parametrize("page", [
'dashboard', 'sync', 'downloads', 'discover', 'artists',
'dashboard', 'sync', 'search', 'downloads', 'discover', 'artists',
'automations', 'library', 'import', 'settings', 'help',
'issues', 'stats', 'watchlist', 'wishlist', 'active-downloads',
'artist-detail', 'playlist-explorer', 'hydrabase', 'tools',

@ -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.42"
_SOULSYNC_BASE_VERSION = "2.43"
def _build_version_string():
"""Append short commit hash to version when available (e.g. 2.35+abc1234)."""
@ -320,7 +320,7 @@ def get_spotify_client_for_profile(profile_id=None):
return spotify_client # Fall back to global
# Valid page IDs for profile permission validation
VALID_PAGE_IDS = {'dashboard', 'sync', 'downloads', 'discover', 'artists', 'automations', 'library', 'import', 'settings', 'help'}
VALID_PAGE_IDS = {'dashboard', 'sync', 'search', 'downloads', 'discover', 'artists', 'automations', 'library', 'import', 'settings', 'help'}
def check_download_permission():
"""Check if current profile has download permission. Returns error response or None if allowed."""
@ -22809,6 +22809,17 @@ def get_version_info():
"title": "What's New in SoulSync",
"subtitle": f"Version {SOULSYNC_VERSION} — Latest Changes",
"sections": [
{
"title": "Search Page Renamed to /search",
"description": "The Search page's internal id is now 'search' instead of 'downloads', which no longer conflicts with the real Downloads page. URL /downloads still works for bookmarks and external links",
"features": [
"• Sidebar label unchanged (still reads 'Search') — no visual change",
"• URL now /search; /downloads stays as an alias so no existing link breaks",
"• DOM id renamed to #search-page; all internal references follow",
"• Profile ACL stored as 'search' going forward; existing profiles with 'downloads' in allowed_pages still resolve through a legacy-compat check",
"• Phase 3b of the Search/Artists unification project",
],
},
{
"title": "Search Source Picker — Pick Where You're Searching",
"description": "The Search page's Enhanced/Basic toggle is replaced by a single 'Search from' dropdown so you can explicitly pick which source to query instead of fanning out to every provider",

@ -98,7 +98,7 @@
<option value="">Default (Discover)</option>
<option value="dashboard">Dashboard</option>
<option value="sync">Sync</option>
<option value="downloads">Search</option>
<option value="search">Search</option>
<option value="discover">Discover</option>
<option value="artists">Artists</option>
<option value="automations">Automations</option>
@ -113,7 +113,7 @@
<div id="new-profile-allowed-pages" class="profile-page-checkboxes">
<label><input type="checkbox" value="dashboard" checked> Dashboard</label>
<label><input type="checkbox" value="sync" checked> Sync</label>
<label><input type="checkbox" value="downloads" checked> Search</label>
<label><input type="checkbox" value="search" checked> Search</label>
<label><input type="checkbox" value="discover" checked> Discover</label>
<label><input type="checkbox" value="artists" checked> Artists</label>
<label><input type="checkbox" value="automations" checked> Automations</label>
@ -196,7 +196,7 @@
<span class="nav-icon"><svg class="nav-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 3 21 3 21 8"/><line x1="4" y1="20" x2="21" y2="3"/><polyline points="21 16 21 21 16 21"/><line x1="15" y1="15" x2="21" y2="21"/><line x1="4" y1="4" x2="9" y2="9"/></svg></span>
<span class="nav-text">Sync</span>
</button>
<button class="nav-button" data-page="downloads">
<button class="nav-button" data-page="search">
<span class="nav-icon"><svg class="nav-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/><line x1="11" y1="8" x2="11" y2="14"/><polyline points="8 11 11 14 14 11"/></svg></span>
<span class="nav-text">Search</span>
</button>
@ -1843,7 +1843,7 @@
</div>
<!-- Downloads Page -->
<div class="page" id="downloads-page">
<div class="page" id="search-page">
<!--
This top-level container replicates the QSplitter from downloads.py,
creating the two-panel layout for the page.

@ -5454,10 +5454,11 @@ const _gsState = {
function _gsUpdateVisibility() {
const bar = document.getElementById('gsearch-bar');
if (!bar) return;
// Hide on downloads page where enhanced search already exists
const onDownloads = typeof currentPage !== 'undefined' && currentPage === 'downloads';
bar.style.display = onDownloads ? 'none' : '';
if (onDownloads && _gsState.active) _gsDeactivate();
// Hide on the Search page where the unified search already exists. Accept the
// legacy 'downloads' id for callers that predate the page rename.
const onSearchPage = typeof currentPage !== 'undefined' && (currentPage === 'search' || currentPage === 'downloads');
bar.style.display = onSearchPage ? 'none' : '';
if (onSearchPage && _gsState.active) _gsDeactivate();
}
function _gsDeactivate() {
@ -5866,8 +5867,8 @@ async function _gsClickTrack(artistName, trackName, albumName, trackId, imageUrl
);
} catch (e) {
console.error('Error opening track download:', e);
// Fallback: navigate to enhanced search
navigateToPage('downloads');
// Fallback: navigate to the unified Search page
navigateToPage('search');
setTimeout(() => {
const input = document.getElementById('enhanced-search-input');
if (input) { input.value = `${artistName} ${trackName}`.trim(); input.dispatchEvent(new Event('input')); }

@ -3599,10 +3599,15 @@ function closeHelperSearch() {
// ═══════════════════════════════════════════════════════════════════════════
const WHATS_NEW = {
'2.43': [
// --- April 23, 2026 (later) ---
{ date: 'April 23, 2026 (later)' },
{ title: 'Search Page Renamed to /search', desc: 'The Search page\'s internal id is now "search" instead of the confusing "downloads" (which clashed with the actual Downloads page). Sidebar label unchanged. URL is now /search; /downloads still resolves so old bookmarks keep working. Profile ACL "Page Access" now saves as "search"; existing profiles with "downloads" in allowed_pages still resolve through a legacy-compat check. Phase 3b of the Search/Artists unification project', page: 'search' },
],
'2.42': [
// --- April 23, 2026 ---
{ date: 'April 23, 2026' },
{ title: 'Search Source Picker — Pick Where You\'re Searching', desc: 'The Search page\'s Enhanced/Basic toggle is replaced by a single "Search from" dropdown at the top. Choose All sources (Auto — keeps today\'s multi-source fan-out), Spotify, Apple Music, Deezer, Discogs, Hydrabase, MusicBrainz, or Soulseek (raw files). Picking a specific source hits only that provider — no more surprise Spotify rate-limit hits from flows that didn\'t need Spotify. "Soulseek" routes to the raw-file search (what "Basic" used to do), so one picker now covers both modes. Loading text reflects the selected source (e.g., "Searching across Apple Music..."). Phase 3 of the Search/Artists unification project', page: 'downloads' },
{ title: 'Search Source Picker — Pick Where You\'re Searching', desc: 'The Search page\'s Enhanced/Basic toggle is replaced by a single "Search from" dropdown at the top. Choose All sources (Auto — keeps today\'s multi-source fan-out), Spotify, Apple Music, Deezer, Discogs, Hydrabase, MusicBrainz, or Soulseek (raw files). Picking a specific source hits only that provider — no more surprise Spotify rate-limit hits from flows that didn\'t need Spotify. "Soulseek" routes to the raw-file search (what "Basic" used to do), so one picker now covers both modes. Loading text reflects the selected source (e.g., "Searching across Apple Music..."). Phase 3 of the Search/Artists unification project', page: 'search' },
],
'2.41': [
// --- April 22, 2026 (late night) ---

@ -189,7 +189,9 @@ let currentProfile = null;
function getProfileHomePage() {
if (!currentProfile) return 'dashboard';
if (currentProfile.home_page) return currentProfile.home_page;
// Legacy profiles stored the Search page as 'downloads'.
const home = currentProfile.home_page === 'downloads' ? 'search' : currentProfile.home_page;
if (home) return home;
return currentProfile.is_admin ? 'dashboard' : 'discover';
}
@ -206,7 +208,11 @@ function isPageAllowed(pageId) {
if (pageId === 'settings') return currentProfile.is_admin;
const ap = currentProfile.allowed_pages;
if (!ap) return true; // null = all pages
return ap.includes(pageId);
if (ap.includes(pageId)) return true;
// Legacy compat: the Search page used to be saved as 'downloads'.
if (pageId === 'search' && ap.includes('downloads')) return true;
if (pageId === 'downloads' && ap.includes('search')) return true;
return false;
}
function canDownload() {
@ -1993,7 +1999,7 @@ function initializeNavigation() {
}
const _DEEPLINK_VALID_PAGES = new Set([
'dashboard', 'sync', 'downloads', 'discover', 'artists', 'automations',
'dashboard', 'sync', 'search', 'downloads', 'discover', 'artists', 'automations',
'library', 'import', 'settings', 'help', 'issues', 'stats', 'watchlist',
'wishlist', 'active-downloads', 'artist-detail', 'playlist-explorer',
'hydrabase', 'tools'
@ -2141,6 +2147,9 @@ function initializeDownloadManagerToggle() {
}
function navigateToPage(pageId, options = {}) {
// Backwards-compat alias — the Search page used to live under id 'downloads'.
if (pageId === 'downloads') pageId = 'search';
if (pageId === currentPage) return;
// Permission guard — redirect to home page if not allowed
@ -2179,7 +2188,7 @@ function navigateToPage(pageId, options = {}) {
}
}
// Show/hide global search bar (hide on downloads page where enhanced search exists)
// Show/hide global search bar (hide on search page where the unified search lives)
if (typeof _gsUpdateVisibility === 'function') _gsUpdateVisibility();
// Show/hide discover download sidebar based on page
@ -2236,7 +2245,7 @@ async function loadPageData(pageId) {
initializeSyncPage();
await loadSyncData();
break;
case 'downloads':
case 'search':
initializeSearch();
initializeSearchModeToggle();
initializeFilters();

@ -1330,9 +1330,9 @@ function initializeSearchModeToggle() {
dropdown.classList.remove('hidden');
updateToggleButtonState();
}
// Hide the page header + search mode toggle to reclaim space
const header = document.querySelector('#downloads-page .downloads-header');
const modeToggle = document.querySelector('.search-mode-toggle-container');
// Hide the page header + source picker to reclaim space
const header = document.querySelector('#search-page .downloads-header');
const modeToggle = document.querySelector('.search-source-picker-container');
const slskdPlaceholder = document.querySelector('#enhanced-search-section .search-results-container');
if (header) header.classList.add('enh-results-active-hide');
if (modeToggle) modeToggle.classList.add('enh-results-active-hide');
@ -1346,8 +1346,8 @@ function initializeSearchModeToggle() {
updateToggleButtonState();
}
// Restore hidden elements
const header = document.querySelector('#downloads-page .downloads-header');
const modeToggle = document.querySelector('.search-mode-toggle-container');
const header = document.querySelector('#search-page .downloads-header');
const modeToggle = document.querySelector('.search-source-picker-container');
const slskdPlaceholder = document.querySelector('#enhanced-search-section .search-results-container');
if (header) header.classList.remove('enh-results-active-hide');
if (modeToggle) modeToggle.classList.remove('enh-results-active-hide');

Loading…
Cancel
Save