Replace Enhanced/Basic toggle with source picker, bump to 2.42

Phase 3 of the Search/Artists unification. The Search page's two-mode
toggle is replaced by a single 'Search from' dropdown: All sources
(Auto), Spotify, Apple Music, Deezer, Discogs, Hydrabase, MusicBrainz,
or Soulseek (raw files). Auto keeps today's fan-out behavior for
backwards compatibility; picking a specific source hits only that
provider. 'Soulseek' routes to the raw-file basic section, so one
picker covers both old modes. Loading text and the enhanced fetch
now respect the selected source. Zero API changes — uses the source
param added in 2.40 and the shared fetch helper from 2.41.
This commit is contained in:
Broque Thomas 2026-04-22 13:06:13 -07:00
parent 377343326f
commit 68d46c5aba
5 changed files with 135 additions and 41 deletions

View file

@ -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.41"
_SOULSYNC_BASE_VERSION = "2.42"
def _build_version_string():
"""Append short commit hash to version when available (e.g. 2.35+abc1234)."""
@ -22809,6 +22809,19 @@ def get_version_info():
"title": "What's New in SoulSync",
"subtitle": f"Version {SOULSYNC_VERSION} — Latest Changes",
"sections": [
{
"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",
"features": [
"• Choose from: All sources (Auto), Spotify, Apple Music, Deezer, Discogs, Hydrabase, MusicBrainz, or Soulseek (raw files)",
"• Auto keeps today's multi-source fan-out behavior — no change if you want the current results",
"• 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) — one picker now covers both modes",
"• Loading text shows the selected source (e.g., 'Searching across Apple Music and your library...')",
"• Phase 3 of the Search/Artists unification project — builds on the shared fetch helper from 2.41",
],
"usage_note": "Sidebar → Search → pick a source in the 'Search from' dropdown above the search bar."
},
{
"title": "Shared Enhanced-Search Fetch Helper",
"description": "Internal refactor — the Search page and the global search widget now route through one shared fetch helper, so future source-picker work only needs wiring in one place",

View file

@ -1869,18 +1869,21 @@
</div>
</div>
<!-- Search Mode Toggle -->
<div class="search-mode-toggle-container">
<div class="search-mode-toggle" data-active="enhanced">
<button class="search-mode-btn active" data-mode="enhanced">
<span class="mode-icon"></span>
<span class="mode-label">Enhanced Search</span>
</button>
<button class="search-mode-btn" data-mode="basic">
<span class="mode-icon">🔍</span>
<span class="mode-label">Basic Search</span>
</button>
<div class="toggle-slider"></div>
<!-- Search Source Picker (replaces Enhanced/Basic toggle) -->
<div class="search-source-picker-container">
<label for="search-source-select" class="search-source-picker-label">Search from</label>
<div class="search-source-picker-wrapper">
<select id="search-source-select" class="search-source-picker-select">
<option value="auto" selected>All sources (Auto)</option>
<option value="spotify">Spotify</option>
<option value="itunes">Apple Music</option>
<option value="deezer">Deezer</option>
<option value="discogs">Discogs</option>
<option value="hydrabase">Hydrabase</option>
<option value="musicbrainz">MusicBrainz</option>
<option value="soulseek">Soulseek (raw files)</option>
</select>
<span class="search-source-picker-caret"></span>
</div>
</div>

View file

@ -3599,6 +3599,11 @@ function closeHelperSearch() {
// ═══════════════════════════════════════════════════════════════════════════
const WHATS_NEW = {
'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' },
],
'2.41': [
// --- April 22, 2026 (late night) ---
{ date: 'April 22, 2026 (late night)' },

View file

@ -56,41 +56,38 @@ function initializeSearchModeToggle() {
return;
}
const toggleContainer = document.querySelector('.search-mode-toggle');
const modeBtns = document.querySelectorAll('.search-mode-btn');
const sourceSelect = document.getElementById('search-source-select');
const basicSection = document.getElementById('basic-search-section');
const enhancedSection = document.getElementById('enhanced-search-section');
if (!toggleContainer || !modeBtns.length || !basicSection || !enhancedSection) {
console.warn('Search mode toggle elements not found');
if (!sourceSelect || !basicSection || !enhancedSection) {
console.warn('Search source picker elements not found');
return;
}
searchModeToggleInitialized = true;
console.log('✅ Initializing search mode toggle (first time only)');
console.log('✅ Initializing search source picker (first time only)');
modeBtns.forEach(btn => {
btn.addEventListener('click', () => {
const mode = btn.dataset.mode;
// Current source selection — 'auto' (fan-out) by default. Soulseek routes
// to the raw-file basic search; everything else routes to enhanced.
let currentSearchSource = sourceSelect.value || 'auto';
// Update button active states
modeBtns.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
const applySourceSelection = (value) => {
currentSearchSource = value;
if (value === 'soulseek') {
basicSection.classList.add('active');
enhancedSection.classList.remove('active');
} else {
basicSection.classList.remove('active');
enhancedSection.classList.add('active');
}
};
// Update toggle slider position
toggleContainer.setAttribute('data-active', mode);
applySourceSelection(currentSearchSource);
// Toggle sections
if (mode === 'basic') {
basicSection.classList.add('active');
enhancedSection.classList.remove('active');
console.log('Switched to basic search mode');
} else {
basicSection.classList.remove('active');
enhancedSection.classList.add('active');
console.log('Switched to enhanced search mode');
}
});
sourceSelect.addEventListener('change', (e) => {
applySourceSelection(e.target.value);
console.log('Search source →', currentSearchSource);
});
// Initialize enhanced search
@ -221,7 +218,14 @@ function initializeSearchModeToggle() {
showDropdown();
const loadingText = document.getElementById('enhanced-loading-text');
if (loadingText) {
loadingText.textContent = `Searching across ${currentMusicSourceName} and your library...`;
const _sourceLabelMap = {
spotify: 'Spotify', itunes: 'Apple Music', deezer: 'Deezer',
discogs: 'Discogs', hydrabase: 'Hydrabase', musicbrainz: 'MusicBrainz',
};
const _sourceName = currentSearchSource && currentSearchSource !== 'auto'
? (_sourceLabelMap[currentSearchSource] || currentSearchSource)
: currentMusicSourceName;
loadingText.textContent = `Searching across ${_sourceName} and your library...`;
}
loadingState.classList.remove('hidden');
emptyState.classList.add('hidden');
@ -241,7 +245,10 @@ function initializeSearchModeToggle() {
_enhancedSearchData = { db_artists: [], primary_source: null, sources: {}, searchId, query };
try {
const data = await enhancedSearchFetch(query, { signal: abortController.signal });
const data = await enhancedSearchFetch(query, {
source: currentSearchSource,
signal: abortController.signal,
});
console.log('Enhanced results:', data);
// Store multi-source state

View file

@ -32832,10 +32832,76 @@ div.artist-hero-badge {
}
/* ========================================= */
/* SEARCH MODE TOGGLE SYSTEM */
/* ========================================= */
/* SEARCH SOURCE PICKER (replaces Enhanced/Basic toggle) */
/* ========================================= */
/* Toggle Container */
.search-source-picker-container {
display: flex;
justify-content: center;
align-items: center;
gap: 12px;
margin: 20px 0;
}
.search-source-picker-label {
color: rgba(255, 255, 255, 0.7);
font-family: 'Segoe UI', sans-serif;
font-size: 13px;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.06em;
}
.search-source-picker-wrapper {
position: relative;
display: inline-flex;
align-items: center;
}
.search-source-picker-select {
appearance: none;
-webkit-appearance: none;
background: rgba(30, 30, 30, 0.8);
border: 1px solid rgba(64, 64, 64, 0.4);
border-radius: 10px;
color: #ffffff;
padding: 10px 40px 10px 16px;
font-family: 'Segoe UI', sans-serif;
font-size: 14px;
font-weight: 500;
cursor: pointer;
min-width: 220px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.search-source-picker-select:hover {
border-color: rgba(138, 43, 226, 0.6);
}
.search-source-picker-select:focus {
outline: none;
border-color: rgba(138, 43, 226, 0.9);
box-shadow: 0 2px 10px rgba(138, 43, 226, 0.35);
}
.search-source-picker-select option {
background: #1e1e1e;
color: #ffffff;
}
.search-source-picker-caret {
position: absolute;
right: 14px;
top: 50%;
transform: translateY(-50%);
pointer-events: none;
color: rgba(255, 255, 255, 0.6);
font-size: 12px;
}
/* Legacy toggle (kept for any other references, but unused by the Search page) */
.search-mode-toggle-container {
display: flex;
justify-content: center;