Drop Show/Hide Results button + auto-restore cached results on navigate-back

Cin flagged two related UX issues during PR review:

1. The "Show Results / Hide Results" toggle next to the search bar served
   no real purpose — there was nothing else on the Search page worth seeing
   instead of results, so toggling visibility was always pointless overhead.

2. Navigating away from /search via a sidebar link dismissed the dropdown
   (the click was caught by the outside-click handler). Coming back left
   the input populated but the results hidden, requiring a Show Results
   click or a fresh search. The cached state was intact in the controller
   the whole time — just not rendered.

Both fixed by the same direction: dropdown visibility becomes a pure
function of query state, never user-toggleable. The closure now exposes
`_searchPageRestoreOnEnter` so subsequent calls to `initializeSearchModeToggle`
re-render from the controller's cached state instead of early-returning.

Removes the button HTML, click handler, `updateToggleButtonState` function,
the desktop + responsive CSS for `.enhanced-search-btn`, and the orphaned
`.btn-icon` rule. Net -94 lines.
This commit is contained in:
Broque Thomas 2026-04-23 22:11:49 -07:00
parent 77d20e9aa8
commit 258644fd9f
5 changed files with 19 additions and 113 deletions

View file

@ -1975,10 +1975,6 @@
placeholder="Search for artists, albums, or tracks...">
<button id="enhanced-cancel-btn" class="enhanced-cancel-btn hidden"></button>
</div>
<button id="enhanced-search-btn" class="enhanced-search-btn">
<span class="btn-icon">👁️</span>
<span class="btn-text">Show Results</span>
</button>
</div>
<!-- Enhanced Search Dropdown (Overlay Panel) -->

View file

@ -3456,6 +3456,8 @@ const WHATS_NEW = {
{ title: 'Interactive Help Updated for Unified Search', desc: 'The click-for-help annotations and the "Your First Download" guided tour were rewritten for the new Search page. Stale annotations pointing at removed elements (Basic/Enhanced toggle button, side-panel queues, download-manager controls) are deleted. The first-download tour now runs on /search and opens with the source picker. PAGE_TOUR_MAP accepts both "search" and the legacy "downloads" id so old bookmarks still match a tour. Retired the standalone "Browse Artists" tour', page: 'help', unreleased: true },
{ title: 'Unified Source-Picker Controller (Search Page + Global Widget)', desc: 'Internal refactor — the source picker state machine (query, active source, per-query cache, fallbacks, loading state, configured-source discovery) is now a single createSearchController factory in shared-helpers.js. Both the full Search page and the sidebar global search popover consume the same controller with per-surface wiring (DOM elements, Soulseek handoff, unconfigured-source click). About 380 lines of near-duplicated state + fetch + render code consolidated into one implementation, so a bug fix or behavior tweak to the picker lands everywhere at once. Zero UX change — every keystroke, icon click, cache hit, rate-limit fallback, and unconfigured-source redirect behaves identically to before', page: 'search', unreleased: true },
{ title: 'Fix Clean Search History Automation Failing with AttributeError', desc: 'The hourly Clean Search History maintenance automation was crashing with "DownloadOrchestrator object has no attribute base_url". Root cause: the check `soulseek_client.base_url` was written before the orchestrator refactor — `soulseek_client` is now a DownloadOrchestrator that wraps individual download clients, with the real Soulseek client at `.soulseek`. Two other call sites in web_server.py already used the correct `soulseek_client.soulseek.base_url` pattern; this one was missed. Now matches the same getattr-guarded pattern and the hourly cleanup runs again', page: 'stats' },
{ title: 'Search Results Always Visible — Show/Hide Button Removed', desc: 'The "Show Results / Hide Results" toggle next to the search bar is gone. There was nothing else on the page worth seeing instead of results, so toggling visibility never made sense. Cin flagged it during PR review. Dropdown visibility is now a pure function of query state — empty input hides it, results show it', page: 'search', unreleased: true },
{ title: 'Cached Search Results Restore on Navigate-Back', desc: 'Previously, navigating away from /search via a sidebar link dismissed the dropdown (the click registered as outside-click). When you came back, the input still held your query but the results were hidden until you typed again or clicked Show Results. Now the per-query cache renders automatically when you re-enter /search, so your results are right where you left them. Cin flagged the round-trip during PR review', page: 'search', unreleased: true },
],
'2.39': [
// --- April 22, 2026 ---

View file

@ -329,11 +329,6 @@
width: 100%;
}
.enhanced-search-bar-container button {
width: 100%;
min-height: 44px;
}
.filter-group {
flex-wrap: wrap;
}

View file

@ -35,11 +35,18 @@ function initializeSearch() {
// ===============================
let searchModeToggleInitialized = false;
// Set by the closure on first init; called by subsequent invocations to
// re-display the search dropdown from the controller's cached state.
// Solves the "results vanish on navigate-back" UX issue — a sidebar nav
// click is treated as outside-click and dismisses the dropdown, so when
// the user returns to /search we need to re-render whatever was cached.
let _searchPageRestoreOnEnter = null;
function initializeSearchModeToggle() {
// Only initialize once to prevent duplicate event listeners
// Subsequent invocations: just re-display cached results so they don't
// vanish on navigate-back. Skip the duplicate-listener setup.
if (searchModeToggleInitialized) {
console.log('Search mode toggle already initialized, skipping...');
if (_searchPageRestoreOnEnter) _searchPageRestoreOnEnter();
return;
}
@ -61,7 +68,6 @@ function initializeSearchModeToggle() {
// the controller up with Search-page-specific DOM + callbacks.
const enhancedInput = document.getElementById('enhanced-search-input');
const enhancedSearchBtn = document.getElementById('enhanced-search-btn');
const enhancedCancelBtn = document.getElementById('enhanced-cancel-btn');
const enhancedDropdown = document.getElementById('enhanced-dropdown');
const loadingState = document.getElementById('enhanced-loading');
@ -202,6 +208,12 @@ function initializeSearchModeToggle() {
});
searchController.init();
// Expose a re-render hook so navigate-back to /search restores cached
// results instead of leaving the dropdown hidden.
_searchPageRestoreOnEnter = () => {
if (searchController.state.query) _renderFromState(searchController.state);
};
// Live search with debouncing
if (enhancedInput) {
enhancedInput.addEventListener('input', (e) => {
@ -238,35 +250,6 @@ function initializeSearchModeToggle() {
});
}
if (enhancedSearchBtn) {
enhancedSearchBtn.addEventListener('click', (e) => {
// Prevent click from bubbling to document (which would close the dropdown)
e.stopPropagation();
// Get fresh references (in case we navigated away and back)
const dropdown = document.getElementById('enhanced-dropdown');
const results = document.getElementById('enhanced-results-container');
if (!dropdown) return;
// Toggle the dropdown visibility to show/hide previous search results
if (dropdown.classList.contains('hidden')) {
// Check if there are results to show by looking for actual content
const hasResults = results &&
!results.classList.contains('hidden') &&
results.children.length > 0;
if (hasResults) {
showDropdown();
} else {
showToast('No previous results to show. Type to search!', 'info');
}
} else {
hideDropdown();
}
});
}
if (enhancedCancelBtn) {
enhancedCancelBtn.addEventListener('click', () => {
enhancedInput.value = '';
@ -1017,10 +1000,7 @@ function initializeSearchModeToggle() {
function showDropdown() {
const dropdown = document.getElementById('enhanced-dropdown');
if (dropdown) {
dropdown.classList.remove('hidden');
updateToggleButtonState();
}
if (dropdown) dropdown.classList.remove('hidden');
// 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');
@ -1032,10 +1012,7 @@ function initializeSearchModeToggle() {
function hideDropdown() {
const dropdown = document.getElementById('enhanced-dropdown');
if (dropdown) {
dropdown.classList.add('hidden');
updateToggleButtonState();
}
if (dropdown) dropdown.classList.add('hidden');
// Restore hidden elements
const header = document.querySelector('#search-page .downloads-header');
const modeToggle = document.querySelector('.search-source-picker-container');
@ -1044,27 +1021,6 @@ function initializeSearchModeToggle() {
if (modeToggle) modeToggle.classList.remove('enh-results-active-hide');
if (slskdPlaceholder) slskdPlaceholder.classList.remove('enh-results-active-hide');
}
function updateToggleButtonState() {
// Get fresh references
const btn = document.getElementById('enhanced-search-btn');
const dropdown = document.getElementById('enhanced-dropdown');
if (!btn || !dropdown) return;
const btnIcon = btn.querySelector('.btn-icon');
const btnText = btn.querySelector('.btn-text');
if (dropdown.classList.contains('hidden')) {
// Dropdown is hidden - button should say "Show Results"
if (btnIcon) btnIcon.textContent = '👁️';
if (btnText) btnText.textContent = 'Show Results';
} else {
// Dropdown is visible - button should say "Hide Results"
if (btnIcon) btnIcon.textContent = '🙈';
if (btnText) btnText.textContent = 'Hide Results';
}
}
}
async function performSearch() {

View file

@ -33338,38 +33338,6 @@ div.artist-hero-badge {
color: #fff;
}
.enhanced-search-btn {
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 10px;
padding: 10px 20px;
color: rgba(255, 255, 255, 0.8);
font-family: 'Segoe UI', sans-serif;
font-size: 13px;
font-weight: 600;
cursor: pointer;
display: flex;
align-items: center;
gap: 6px;
transition: all 0.2s ease;
flex-shrink: 0;
}
.enhanced-search-btn:hover {
background: rgba(255, 255, 255, 0.12);
color: #fff;
transform: none;
box-shadow: none;
}
.enhanced-search-btn:active {
transform: scale(0.97);
}
.btn-icon {
font-size: 14px;
}
/* Enhanced Search Status */
.enhanced-search-status {
display: flex;
@ -33576,12 +33544,6 @@ div.artist-hero-badge {
gap: 8px;
}
.enhanced-search-btn {
width: 100%;
justify-content: center;
padding: 10px 16px;
}
#enhanced-search-input {
font-size: 14px;
}
@ -33602,11 +33564,6 @@ div.artist-hero-badge {
margin-right: 6px;
}
.enhanced-search-btn {
padding: 9px 14px;
font-size: 12px;
}
/* Better album/track results on mobile */
.album-result-item {
margin-bottom: 8px;