From 7714b51a505616928dc83d26393b2a21699ac749 Mon Sep 17 00:00:00 2001 From: Broque Thomas <26755000+Nezreka@users.noreply.github.com> Date: Sun, 26 Apr 2026 13:32:30 -0700 Subject: [PATCH] Lift version modal data into helper.js, delete /api/version-info MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The version modal pulled its content from /api/version-info — a 295-line hand-curated Python dict in web_server.py. The "What's New" panel pulled its content from WHATS_NEW in helper.js. Same release notes, two files, two languages, hand-edited at every release — drift was inevitable (and happened: the kettui-fix entries I added recently differed in detail between the two surfaces). This commit makes helper.js the single editing surface: - Adds VERSION_MODAL_SECTIONS const in helper.js right beside WHATS_NEW, with a comment block documenting the relationship: WHATS_NEW is the per-version detailed log used by the helper popover; VERSION_MODAL_SECTIONS is the curated highlight reel shown by the sidebar version button. Both edited at release time, both in the same file. - Rewires showVersionInfo() in downloads.js to read from those consts directly. No backend round-trip; the changelog content ships in the same JS bundle the browser already loaded. - Deletes the /api/version-info route and its 295-line version_data dict. - Updates the line-39 comment to drop the now-stale "version-info endpoint" reference. Note: this is collocation, not true unification. WHATS_NEW and VERSION_MODAL_SECTIONS are still two distinct structures with overlapping content, linked by a comment convention rather than a shared schema. A deeper refactor (e.g. a `featured` flag on WHATS_NEW entries that the modal aggregates) was rejected as out-of-scope — the curated section titles ("Earlier in v2.3", "Recent Fixes") aren't 1:1 mappable to WHATS_NEW entries. Saving for a follow-up if the drift problem persists. Risk audit: - Load order: helper.js loads at line 7967, downloads.js at line 7873. Both classic scripts execute synchronously before any clickable interaction, so showVersionInfo (only invoked on the version-button onclick) always sees both consts defined. - populateVersionModal() unchanged — receives the same {title, subtitle, sections: [{title, description, features, usage_note?}]} shape. - Stale-cache window during deploy: old downloads.js hitting a 404 on the deleted endpoint falls through to the existing catch + toast path ("Failed to load version information"). Cache-buster ?v=static_v resolves on next page load. 553 tests pass. helper.js + downloads.js parse cleanly. No residual references to /api/version-info anywhere in the repo. --- web_server.py | 298 +----------------------------------- webui/static/downloads.js | 35 +++-- webui/static/helper.js | 309 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 327 insertions(+), 315 deletions(-) diff --git a/web_server.py b/web_server.py index 30e7fd79..c473e037 100644 --- a/web_server.py +++ b/web_server.py @@ -36,7 +36,7 @@ _log_path = config_manager.get('logging.path', 'logs/app.log') _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. +# App version — single source of truth for backup metadata, system-info, update check, etc. # Semver: MAJOR.MINOR.PATCH. Bump at each dev→main release. _SOULSYNC_BASE_VERSION = "2.4.0" @@ -22767,302 +22767,6 @@ def check_for_update(): 'is_docker': os.path.exists('/.dockerenv'), }) -@app.route('/api/version-info', methods=['GET']) -def get_version_info(): - """ - Returns version information and release notes, matching the GUI's VersionInfoModal content. - This provides the same data that the GUI version modal displays. - """ - version_data = { - "version": SOULSYNC_VERSION, - "title": "What's New in SoulSync", - "subtitle": f"Version {SOULSYNC_VERSION} — Latest Changes", - "sections": [ - { - "title": "Reorganize Queue: Race-Condition Hardening (kettui Review)", - "description": "Three concurrency / dedupe issues kettui caught in his review of PR #377, plus two related polish items from the same pass.", - "features": [ - "• Worker pick + status flip is now atomic — fixes a window where a cancel() landing between 'pick next queued' and 'flip to running' could mark an item cancelled but the worker still ran it", - "• Replaced the lock + wakeup-event pair with a single threading.Condition so newly-queued items can't sleep up to 60s waiting for the next wakeup tick (the old pair had an empty-check / clear-event race)", - "• enqueue_many now holds the queue lock for the whole batch and tracks a per-batch seen set, so duplicate album_ids inside one bulk call are deduped against each other (not just against pre-existing items)", - "• Reorganize-preview Apply button no longer gets stuck disabled when an early return / network error skipped the re-enable line — moved into a finally", - "• DB helpers get_album_display_meta and get_artist_albums_for_reorganize now let exceptions bubble instead of swallowing them as 'not found' / empty list — a real DB outage now surfaces as a 500 to the user instead of looking like a missing album", - ], - }, - { - "title": "Reorganize Queue with Live Status Panel", - "description": "Reorganizing albums is no longer a foreground operation that locks the page. Click → enqueue → keep working. A status panel surfaces live progress.", - "features": [ - "• Per-album Reorganize and Reorganize All both enqueue into a single FIFO queue with a backend worker that drains one item at a time", - "• Buttons stay clickable — spam-clicking the same album silently dedupes (returns 'already queued' instead of 409-ing)", - "• Status panel at the top of the artist actions bar shows: active item (progress bar, current track, moved/skipped/failed counts), queued count, and recently-finished items with success/warning indicators", - "• Click the panel to expand: full queue list with per-item cancel buttons; running item can't be cancelled mid-flight (Python threads aren't cleanly killable, post-process spawns subprocesses)", - "• 'Cancel All' button drops every queued item at once — the running one continues", - "• Items belonging to a different artist than the page you're on are flagged with the artist name so cross-artist progress is obvious", - "• Each queued item carries its own metadata source pick (Spotify / iTunes / Deezer / Discogs / Hydrabase) — switching modal selections per album works", - "• 'Reorganize All' is now one backend call instead of N JS-driven calls — the loop runs server-side and is much faster", - "• Continue-on-failure: a single failed album never stalls the queue; the worker logs and moves on", - "• Retired the old single-slot reorganize state endpoint plus the polling loops that depended on it", - ], - }, - { - "title": "Fix Wrong-Artist Tracks Silently Downloading", - "description": "A critical bug where searching for a track could silently download a completely different artist's song with the same name", - "features": [ - "• Example: searching 'Maduk — Leave A Light On' on Tidal was downloading Tom Walker's unrelated song of the same name, then embedding Maduk's metadata into Tom Walker's audio", - "• Root cause 1: candidate artist gate used `< 0.4` similarity but Maduk/Tom Walker scored exactly 0.400, slipping past the fencepost — raised to `< 0.5`", - "• Root cause 2: AcoustID verification returned SKIP (accept) instead of FAIL (quarantine) when title matched but artist was clearly different — now FAILs when artist similarity is below 0.3", - "• Preserves SKIP for the ambiguous 0.3–0.6 range (covers, collabs, formatting differences) so legitimate tracks aren't falsely quarantined", - "• Both pre-download candidate validation AND post-download verification are now fixed — defense in depth", - ], - }, - { - "title": "Tidal Search Falls Back on Long Queries", - "description": "Tidal's search chokes on long remix-credit queries — now retries with progressively-shortened variants when the original returns 0 results", - "features": [ - "• Example: 'maduk transformations remixed fire away fred v remix' returned 0; now falls back to shorter queries until Tidal finds the track", - "• Up to 4 shortened variants tried, capped total 5 requests, 100ms between attempts", - "• Qualifier-safe: Live/Remix/Acoustic/Extended searches only accept fallback results that still contain the qualifier — studio version never replaces a '(Live)' request", - "• Returns empty if no variant preserves qualifiers — same outcome as before", - ], - }, - { - "title": "Manual Discovery Fixes Persist Across Restart", - "description": "When you manually fix a discovery match, the fix is now saved under your active metadata source instead of always 'spotify' — so Deezer/iTunes/Discogs/Hydrabase users' fixes actually survive restart and re-scan", - "features": [ - "• Affected Tidal, Deezer, Spotify Public, YouTube, and Discovery Pool manual fixes", - "• Symmetric with how the auto-discovery worker saves — no more mismatch", - "• Existing Spotify-primary users unaffected (the hardcoded value matched their source)", - ], - }, - { - "title": "Watchlist Content Filters Fixed", - "description": "Global Override settings and live-version detection now behave the way the UI implies", - "features": [ - "• Scheduled auto-watchlist now honors Watchlist → Global Override (was bypassing it and using per-artist defaults)", - "• 'Live' detection tightened — no more false positives on titles like 'What We Live For' or 'Live Forever'", - "• Same fix applies to the Library Maintenance Live/Commentary Cleaner", - "• Still catches (Live), - Live, Live at/from/in/on/version/session/recording, Unplugged, In Concert", - ], - }, - { - "title": "Discography Backfill", - "description": "New maintenance job that fills gaps in your library — scans each artist's full discography and finds what you're missing", - "features": [ - "• Scans each artist in your library against metadata source discographies", - "• Creates findings for missing tracks — review and click 'Add to Wishlist' to queue downloads", - "• Respects all content filters (live, remix, acoustic, compilation, instrumental)", - "• Release type filters (album, EP, single) with configurable defaults", - "• Optional 'auto-add to wishlist' setting — create findings AND push to wishlist in one pass", - "• 3-option fix prompt (Add to Wishlist / Just Clear / Cancel) for manual review", - "• Batched in-memory library matching — same fast path the Library pages use", - "• Opt-in, disabled by default — runs weekly, processes up to 50 artists per run", - "• Rate-limited to avoid hammering metadata APIs", - ], - }, - { - "title": "Repair 'Run Now' Honored While Paused", - "description": "Force-running a repair job no longer stalls forever when the master repair worker is paused", - "features": [ - "• Jobs queued via 'Run Now' run to completion even if the master worker is paused", - "• Fixes silent stalls where Discography Backfill logged 'scanning 50 artists' then did nothing", - "• Master-pause still blocks scheduled runs — this only affects explicit user-triggered runs", - ], - }, - { - "title": "Multi-Artist Tagging", - "description": "Enhanced control over how multiple artists are written to audio file tags", - "features": [ - "• Configurable artist separator: comma, semicolon, or slash", - "• Multi-value ARTISTS tag for Navidrome/Jellyfin multi-artist linking", - "• 'Move featured artists to title' mode — primary artist in ARTIST tag, others as (feat. ...) in title", - "• All opt-in with defaults matching current behavior", - ], - }, - { - "title": "Enriched Downloads Page", - "description": "Download cards now show rich metadata instead of just filenames", - "features": [ - "• Album artwork thumbnail on each download card", - "• Artist name, album name, and source badge", - "• Quality badge appears after post-processing", - "• Falls back gracefully for transfers without metadata context", - ], - }, - { - "title": "Template Variable Delimiters", - "description": "Use ${var} syntax to append literal text to template variables", - "features": [ - "• ${albumtype}s produces 'Albums', 'Singles', 'EPs'", - "• Both $var and ${var} syntaxes work in all templates", - "• Validation updated to accept delimited variables", - ], - }, - { - "title": "Reorganize All Albums", - "description": "Bulk reorganize all albums for an artist from the enhanced library view", - "features": [ - "• New 'Reorganize All' button in the artist header", - "• Processes albums sequentially with progress toasts", - "• Continues on error — one failed album doesn't block the rest", - "• Uses the same template and endpoint as per-album reorganize", - ], - }, - { - "title": "SoulSync Standalone Library", - "description": "Use SoulSync without Plex, Jellyfin, or Navidrome — manage your library directly", - "features": [ - "• New 'Standalone' server option in Settings → Connections", - "• Downloads and imports write artist/album/track to the library database immediately", - "• Pre-populated enrichment IDs (Spotify, Deezer, MusicBrainz) — workers skip re-discovery", - "• Deep scan finds untracked files in Transfer → moves to Staging for processing", - "• Deep scan removes stale DB records when files are deleted from disk", - "• Sync page and sync buttons hidden automatically in standalone mode", - "• Full library page, artist detail, discography, and enhanced view all work standalone", - ], - "usage_note": "Go to Settings → Connections and click the 'Standalone' button. No media server needed." - }, - { - "title": "Auto-Import", - "description": "Background import folder watcher that automatically identifies and imports music into your library", - "features": [ - "• Recursive scan — any folder depth (Artist/Album/tracks, Album/tracks, loose files)", - "• Single file support — loose audio files identified via tags, filename, or AcoustID", - "• Tag-based identification preferred over weak metadata matches (85% confidence for tagged files)", - "• AcoustID fingerprinting fallback for untagged or ambiguous files", - "• Stats bar, filter pills (All/Review/Imported/Failed), Scan Now, Approve All, Clear History", - "• Expandable track match details with per-track confidence scores", - "• Race condition fix prevents duplicate processing during multi-track albums", - ], - "usage_note": "Enable on the Import page Auto tab. Set your import folder in Settings." - }, - { - "title": "Wishlist Nebula", - "description": "Wishlist redesigned as an interactive artist orb visualization", - "features": [ - "• Each artist is a glowing orb with their photo — album fans and single moons orbit around them", - "• Click orbs to expand and see albums/singles, download directly from the nebula", - "• Processing state shows live progress with spinning ring animation", - "• Stats strip at top shows total artists, albums, singles, and tracks", - ], - "usage_note": "Click Wishlist in the sidebar to see the Nebula view." - }, - { - "title": "Automation Group Management", - "description": "Organize and manage your automation groups with full control", - "features": [ - "• Rename, delete, and bulk-toggle automation groups from group headers", - "• Drag-and-drop automations between groups to reorganize", - "• Delete confirmation dialog with group name and automation count", - ], - "usage_note": "Right-click or use the action buttons on group headers in the Automations page." - }, - { - "title": "Bidirectional Artist Sync & Server Playlists", - "description": "Artist sync now goes both ways, and server playlists show full coverage", - "features": [ - "• Artist Sync pulls new content from your media server AND removes stale library entries", - "• Deep scan mode fetches full metadata for newly discovered tracks", - "• Server playlist view shows all playlists with clear synced vs unsynced visual separation", - ], - }, - { - "title": "Provider-Agnostic Discovery", - "description": "Discovery features work with any configured metadata source instead of requiring Spotify", - "features": [ - "• Similar artist matching, discovery pool, and incremental updates use source priority", - "• Falls back through Spotify, iTunes, and Deezer in configured order", - "• MusicMap URL encoding fixed for artists with special characters", - "• Freshness check simplified to age-based — backfill handles missing IDs separately", - ], - }, - { - "title": "Dashboard & Navigation", - "description": "Dashboard improvements and sidebar navigation enhancements", - "features": [ - "• Library Status card on Dashboard — shows server state, track counts, scan buttons", - "• Tools page in sidebar — all maintenance tools moved from Dashboard modal", - "• Watchlist and Wishlist promoted to full sidebar pages with live count badges", - "• AcoustID scanner scans full library with actionable fix options (retag, redownload, delete)", - ], - }, - { - "title": "MusicBrainz & Metadata Fixes", - "description": "Critical tag embedding fix and Picard-style album consistency", - "features": [ - "• Fix: source ID tags (Spotify, MusicBrainz, Deezer, AudioDB) were silently skipped on every download — now embed correctly", - "• Picard-style release preference scoring prevents Navidrome album splits", - "• Source tags wiped when metadata enhancement is skipped or fails", - "• Spotify API no longer called when Deezer/iTunes is the configured primary source", - ], - }, - { - "title": "Downloads & Soulseek Improvements", - "description": "Better download management, search accuracy, and queue control", - "features": [ - "• Downloads batch panel — color-coded batch cards with progress, cancel, expand, and 7-day history", - "• Soulseek search queries now include album name — reduces wrong-artist downloads", - "• Reject Soulseek results from Various Artists/VA/Unknown Artist folders", - "• Clearing wishlist now cancels the active wishlist download batch", - "• Album delete with 'Delete Files Too' option on enhanced library page", - "• Fix download modal freezing mid-download — M3U auto-save was exhausting server threads", - "• Fix Unknown Artist when adding playlist tracks to wishlist", - "• Fix slskd timeout spam when Soulseek is not the active download source", - ], - }, - { - "title": "Recent Fixes", - "description": "Bug fixes from recent releases and community reports", - "features": [ - "• Fix watchlist scan false failures — empty discography no longer reported as error", - "• Fix deezer_artist_id column error on enhanced library sync", - "• Fix wishlist button intermittently not navigating to page", - "• Fix worker orb tooltips rendering behind dashboard content", - "• Fix OAuth callback port hardcoding — custom ports now respected", - "• Fix allow duplicates setting not saving", - "• Fix wishlist dropping cross-album tracks when duplicates enabled", - "• Fix replace lower quality setting not persisting", - "• Fix Spotify enrichment worker infinite loop on pre-matched artists", - "• Reject Qobuz 30-second sample/preview downloads", - "• Fix library page crash on All filter — non-string soul_id broke card rendering", - "• Auto Wing It fallback for failed discovery — unmatched tracks download via Soulseek with raw metadata", - "• Lidarr download source now production-ready — full orchestrator integration", - "• Fix album track lookup hardcoded to Spotify — now uses configured primary source", - "• Fix M3U showing all tracks as missing — regenerate with real paths after post-processing", - "• Fix AcoustID retag not writing corrected tags to audio file", - "• Fix wishlist albums cycle stuck at 1 concurrent worker instead of configured value", - "• Fix downloads badge dropping to 300 after opening Downloads page", - "• Fix server playlist Find & Add inserting at wrong position on Plex", - "• Smarter Fix modal results — standard album versions sorted above live/remix/cover/soundtrack variants", - "• Unmatch discovery tracks — red ✕ button to remove bad matches from playlist discovery", - "• Customizable music video naming — path template with $artist, $title, $year variables", - "• Fix soulseek log spam when not configured as download source", - ], - }, - { - "title": "Earlier in v2.3", - "description": "Major features from earlier in this release cycle", - "features": [ - "• Centralized Downloads page with live-updating list and filter pills", - "• First-Run Setup Wizard — 7-step guided configuration", - "• Music Videos — search and download from YouTube", - "• Inbound Music Request API for external tools (Discord bots, Home Assistant)", - "• Lidarr download source (development) — 7th source for Usenet/torrent via Lidarr", - "• Graceful shutdown — all workers respond to shutdown signals immediately", - "• Unknown Artist prevention with 3-tier metadata fallback", - "• Deezer multi-artist tagging using contributors field", - "• Artist Map — Watchlist Constellation, Genre Map, and Artist Explorer canvas modes", - "• Discogs integration — enrichment worker, fallback source, enhanced search tab", - "• Wing It mode, Global Search Bar, Redesigned Notifications", - "• Server Playlist Manager, Sync History Dashboard, Playlist Explorer", - "• Enhanced Library Manager with inline tag editing and write-to-file", - "• Automation Signals, Multi-Source Search Tabs, Rich Artist Profiles", - ], - }, - ] - } - return jsonify(version_data) - def _simple_monitor_task(): """The actual monitoring task that runs in the background thread. diff --git a/webui/static/downloads.js b/webui/static/downloads.js index 30a9d2bd..1720038b 100644 --- a/webui/static/downloads.js +++ b/webui/static/downloads.js @@ -5752,27 +5752,26 @@ async function showVersionInfo() { } catch (e) { /* ignore */ } } + // Build version data straight from helper.js — single source of truth. + // No backend round-trip; the changelog content is shipped in the same + // bundle the browser already loaded. + const version = (typeof _getCurrentVersion === 'function') + ? _getCurrentVersion() + : (btn ? btn.textContent.trim().replace('v', '') : ''); + const sections = (typeof VERSION_MODAL_SECTIONS !== 'undefined') + ? VERSION_MODAL_SECTIONS + : []; + const versionData = { + version, + title: "What's New in SoulSync", + subtitle: version ? `Version ${version} — Latest Changes` : 'Latest Changes', + sections, + }; + try { - console.log('Fetching version info...'); - - // Fetch version data from API - const response = await fetch('/api/version-info'); - if (!response.ok) { - throw new Error('Failed to fetch version info'); - } - - const versionData = await response.json(); - console.log('Version data received:', versionData); - - // Populate modal content populateVersionModal(versionData, hadUpdate ? updateInfo : null); - - // Show modal const modalOverlay = document.getElementById('version-modal-overlay'); - modalOverlay.classList.remove('hidden'); - - console.log('Version modal opened'); - + if (modalOverlay) modalOverlay.classList.remove('hidden'); } catch (error) { console.error('Error showing version info:', error); showToast('Failed to load version information', 'error'); diff --git a/webui/static/helper.js b/webui/static/helper.js index 663fa00c..90c2d45d 100644 --- a/webui/static/helper.js +++ b/webui/static/helper.js @@ -3669,6 +3669,315 @@ const WHATS_NEW = { ], }; +// ═══════════════════════════════════════════════════════════════════════════ +// VERSION MODAL — curated highlight reel +// ═══════════════════════════════════════════════════════════════════════════ +// +// `WHATS_NEW` above is the per-version detailed log used by the "What's New" +// helper-popover panel — short one-liners, internal page links, every entry +// shown on every browse-back through versions. +// +// `VERSION_MODAL_SECTIONS` (this block) is the curated highlight reel shown +// when the user clicks the version button in the sidebar. It's NOT a +// mechanical view of WHATS_NEW — it's editorial curation: bigger-picture +// sections, bullet-list expansions, optional "usage" hints at the bottom. +// Some sections aggregate across multiple WHATS_NEW entries ("Recent Fixes", +// "Earlier in v2.3"); some don't have a 1:1 WHATS_NEW counterpart at all. +// +// Both consts live here so a release editor only opens one file. At release +// time: +// 1. Add the per-version block to `WHATS_NEW` (one entry per shipped item). +// 2. Promote any items worth a modal-section into `VERSION_MODAL_SECTIONS` +// at the top of the array (latest highlights lead). +// 3. Roll older sections down or merge them into a "Recent Fixes" / +// "Earlier in vX.Y" aggregator section as they age out of the spotlight. +// +// Section shape: { title, description, features: [bullet strings], +// usage_note?: 'optional hint shown at the bottom' } +const VERSION_MODAL_SECTIONS = [ + { + title: "Reorganize Queue: Race-Condition Hardening (kettui Review)", + description: "Three concurrency / dedupe issues kettui caught in his review of PR #377, plus two related polish items from the same pass.", + features: [ + "• Worker pick + status flip is now atomic — fixes a window where a cancel() landing between 'pick next queued' and 'flip to running' could mark an item cancelled but the worker still ran it", + "• Replaced the lock + wakeup-event pair with a single threading.Condition so newly-queued items can't sleep up to 60s waiting for the next wakeup tick (the old pair had an empty-check / clear-event race)", + "• enqueue_many now holds the queue lock for the whole batch and tracks a per-batch seen set, so duplicate album_ids inside one bulk call are deduped against each other (not just against pre-existing items)", + "• Reorganize-preview Apply button no longer gets stuck disabled when an early return / network error skipped the re-enable line — moved into a finally", + "• DB helpers get_album_display_meta and get_artist_albums_for_reorganize now let exceptions bubble instead of swallowing them as 'not found' / empty list — a real DB outage now surfaces as a 500 to the user instead of looking like a missing album", + ], + }, + { + title: "Reorganize Queue with Live Status Panel", + description: "Reorganizing albums is no longer a foreground operation that locks the page. Click → enqueue → keep working. A status panel surfaces live progress.", + features: [ + "• Per-album Reorganize and Reorganize All both enqueue into a single FIFO queue with a backend worker that drains one item at a time", + "• Buttons stay clickable — spam-clicking the same album silently dedupes (returns 'already queued' instead of 409-ing)", + "• Status panel at the top of the artist actions bar shows: active item (progress bar, current track, moved/skipped/failed counts), queued count, and recently-finished items with success/warning indicators", + "• Click the panel to expand: full queue list with per-item cancel buttons; running item can't be cancelled mid-flight (Python threads aren't cleanly killable, post-process spawns subprocesses)", + "• 'Cancel All' button drops every queued item at once — the running one continues", + "• Items belonging to a different artist than the page you're on are flagged with the artist name so cross-artist progress is obvious", + "• Each queued item carries its own metadata source pick (Spotify / iTunes / Deezer / Discogs / Hydrabase) — switching modal selections per album works", + "• 'Reorganize All' is now one backend call instead of N JS-driven calls — the loop runs server-side and is much faster", + "• Continue-on-failure: a single failed album never stalls the queue; the worker logs and moves on", + "• Retired the old single-slot reorganize state endpoint plus the polling loops that depended on it", + ], + }, + { + title: "Fix Wrong-Artist Tracks Silently Downloading", + description: "A critical bug where searching for a track could silently download a completely different artist's song with the same name", + features: [ + "• Example: searching 'Maduk — Leave A Light On' on Tidal was downloading Tom Walker's unrelated song of the same name, then embedding Maduk's metadata into Tom Walker's audio", + "• Root cause 1: candidate artist gate used `< 0.4` similarity but Maduk/Tom Walker scored exactly 0.400, slipping past the fencepost — raised to `< 0.5`", + "• Root cause 2: AcoustID verification returned SKIP (accept) instead of FAIL (quarantine) when title matched but artist was clearly different — now FAILs when artist similarity is below 0.3", + "• Preserves SKIP for the ambiguous 0.3–0.6 range (covers, collabs, formatting differences) so legitimate tracks aren't falsely quarantined", + "• Both pre-download candidate validation AND post-download verification are now fixed — defense in depth", + ], + }, + { + title: "Tidal Search Falls Back on Long Queries", + description: "Tidal's search chokes on long remix-credit queries — now retries with progressively-shortened variants when the original returns 0 results", + features: [ + "• Example: 'maduk transformations remixed fire away fred v remix' returned 0; now falls back to shorter queries until Tidal finds the track", + "• Up to 4 shortened variants tried, capped total 5 requests, 100ms between attempts", + "• Qualifier-safe: Live/Remix/Acoustic/Extended searches only accept fallback results that still contain the qualifier — studio version never replaces a '(Live)' request", + "• Returns empty if no variant preserves qualifiers — same outcome as before", + ], + }, + { + title: "Manual Discovery Fixes Persist Across Restart", + description: "When you manually fix a discovery match, the fix is now saved under your active metadata source instead of always 'spotify' — so Deezer/iTunes/Discogs/Hydrabase users' fixes actually survive restart and re-scan", + features: [ + "• Affected Tidal, Deezer, Spotify Public, YouTube, and Discovery Pool manual fixes", + "• Symmetric with how the auto-discovery worker saves — no more mismatch", + "• Existing Spotify-primary users unaffected (the hardcoded value matched their source)", + ], + }, + { + title: "Watchlist Content Filters Fixed", + description: "Global Override settings and live-version detection now behave the way the UI implies", + features: [ + "• Scheduled auto-watchlist now honors Watchlist → Global Override (was bypassing it and using per-artist defaults)", + "• 'Live' detection tightened — no more false positives on titles like 'What We Live For' or 'Live Forever'", + "• Same fix applies to the Library Maintenance Live/Commentary Cleaner", + "• Still catches (Live), - Live, Live at/from/in/on/version/session/recording, Unplugged, In Concert", + ], + }, + { + title: "Discography Backfill", + description: "New maintenance job that fills gaps in your library — scans each artist's full discography and finds what you're missing", + features: [ + "• Scans each artist in your library against metadata source discographies", + "• Creates findings for missing tracks — review and click 'Add to Wishlist' to queue downloads", + "• Respects all content filters (live, remix, acoustic, compilation, instrumental)", + "• Release type filters (album, EP, single) with configurable defaults", + "• Optional 'auto-add to wishlist' setting — create findings AND push to wishlist in one pass", + "• 3-option fix prompt (Add to Wishlist / Just Clear / Cancel) for manual review", + "• Batched in-memory library matching — same fast path the Library pages use", + "• Opt-in, disabled by default — runs weekly, processes up to 50 artists per run", + "• Rate-limited to avoid hammering metadata APIs", + ], + }, + { + title: "Repair 'Run Now' Honored While Paused", + description: "Force-running a repair job no longer stalls forever when the master repair worker is paused", + features: [ + "• Jobs queued via 'Run Now' run to completion even if the master worker is paused", + "• Fixes silent stalls where Discography Backfill logged 'scanning 50 artists' then did nothing", + "• Master-pause still blocks scheduled runs — this only affects explicit user-triggered runs", + ], + }, + { + title: "Multi-Artist Tagging", + description: "Enhanced control over how multiple artists are written to audio file tags", + features: [ + "• Configurable artist separator: comma, semicolon, or slash", + "• Multi-value ARTISTS tag for Navidrome/Jellyfin multi-artist linking", + "• 'Move featured artists to title' mode — primary artist in ARTIST tag, others as (feat. ...) in title", + "• All opt-in with defaults matching current behavior", + ], + }, + { + title: "Enriched Downloads Page", + description: "Download cards now show rich metadata instead of just filenames", + features: [ + "• Album artwork thumbnail on each download card", + "• Artist name, album name, and source badge", + "• Quality badge appears after post-processing", + "• Falls back gracefully for transfers without metadata context", + ], + }, + { + title: "Template Variable Delimiters", + description: "Use ${var} syntax to append literal text to template variables", + features: [ + "• ${albumtype}s produces 'Albums', 'Singles', 'EPs'", + "• Both $var and ${var} syntaxes work in all templates", + "• Validation updated to accept delimited variables", + ], + }, + { + title: "Reorganize All Albums", + description: "Bulk reorganize all albums for an artist from the enhanced library view", + features: [ + "• New 'Reorganize All' button in the artist header", + "• Processes albums sequentially with progress toasts", + "• Continues on error — one failed album doesn't block the rest", + "• Uses the same template and endpoint as per-album reorganize", + ], + }, + { + title: "SoulSync Standalone Library", + description: "Use SoulSync without Plex, Jellyfin, or Navidrome — manage your library directly", + features: [ + "• New 'Standalone' server option in Settings → Connections", + "• Downloads and imports write artist/album/track to the library database immediately", + "• Pre-populated enrichment IDs (Spotify, Deezer, MusicBrainz) — workers skip re-discovery", + "• Deep scan finds untracked files in Transfer → moves to Staging for processing", + "• Deep scan removes stale DB records when files are deleted from disk", + "• Sync page and sync buttons hidden automatically in standalone mode", + "• Full library page, artist detail, discography, and enhanced view all work standalone", + ], + usage_note: "Go to Settings → Connections and click the 'Standalone' button. No media server needed.", + }, + { + title: "Auto-Import", + description: "Background import folder watcher that automatically identifies and imports music into your library", + features: [ + "• Recursive scan — any folder depth (Artist/Album/tracks, Album/tracks, loose files)", + "• Single file support — loose audio files identified via tags, filename, or AcoustID", + "• Tag-based identification preferred over weak metadata matches (85% confidence for tagged files)", + "• AcoustID fingerprinting fallback for untagged or ambiguous files", + "• Stats bar, filter pills (All/Review/Imported/Failed), Scan Now, Approve All, Clear History", + "• Expandable track match details with per-track confidence scores", + "• Race condition fix prevents duplicate processing during multi-track albums", + ], + usage_note: "Enable on the Import page Auto tab. Set your import folder in Settings.", + }, + { + title: "Wishlist Nebula", + description: "Wishlist redesigned as an interactive artist orb visualization", + features: [ + "• Each artist is a glowing orb with their photo — album fans and single moons orbit around them", + "• Click orbs to expand and see albums/singles, download directly from the nebula", + "• Processing state shows live progress with spinning ring animation", + "• Stats strip at top shows total artists, albums, singles, and tracks", + ], + usage_note: "Click Wishlist in the sidebar to see the Nebula view.", + }, + { + title: "Automation Group Management", + description: "Organize and manage your automation groups with full control", + features: [ + "• Rename, delete, and bulk-toggle automation groups from group headers", + "• Drag-and-drop automations between groups to reorganize", + "• Delete confirmation dialog with group name and automation count", + ], + usage_note: "Right-click or use the action buttons on group headers in the Automations page.", + }, + { + title: "Bidirectional Artist Sync & Server Playlists", + description: "Artist sync now goes both ways, and server playlists show full coverage", + features: [ + "• Artist Sync pulls new content from your media server AND removes stale library entries", + "• Deep scan mode fetches full metadata for newly discovered tracks", + "• Server playlist view shows all playlists with clear synced vs unsynced visual separation", + ], + }, + { + title: "Provider-Agnostic Discovery", + description: "Discovery features work with any configured metadata source instead of requiring Spotify", + features: [ + "• Similar artist matching, discovery pool, and incremental updates use source priority", + "• Falls back through Spotify, iTunes, and Deezer in configured order", + "• MusicMap URL encoding fixed for artists with special characters", + "• Freshness check simplified to age-based — backfill handles missing IDs separately", + ], + }, + { + title: "Dashboard & Navigation", + description: "Dashboard improvements and sidebar navigation enhancements", + features: [ + "• Library Status card on Dashboard — shows server state, track counts, scan buttons", + "• Tools page in sidebar — all maintenance tools moved from Dashboard modal", + "• Watchlist and Wishlist promoted to full sidebar pages with live count badges", + "• AcoustID scanner scans full library with actionable fix options (retag, redownload, delete)", + ], + }, + { + title: "MusicBrainz & Metadata Fixes", + description: "Critical tag embedding fix and Picard-style album consistency", + features: [ + "• Fix: source ID tags (Spotify, MusicBrainz, Deezer, AudioDB) were silently skipped on every download — now embed correctly", + "• Picard-style release preference scoring prevents Navidrome album splits", + "• Source tags wiped when metadata enhancement is skipped or fails", + "• Spotify API no longer called when Deezer/iTunes is the configured primary source", + ], + }, + { + title: "Downloads & Soulseek Improvements", + description: "Better download management, search accuracy, and queue control", + features: [ + "• Downloads batch panel — color-coded batch cards with progress, cancel, expand, and 7-day history", + "• Soulseek search queries now include album name — reduces wrong-artist downloads", + "• Reject Soulseek results from Various Artists/VA/Unknown Artist folders", + "• Clearing wishlist now cancels the active wishlist download batch", + "• Album delete with 'Delete Files Too' option on enhanced library page", + "• Fix download modal freezing mid-download — M3U auto-save was exhausting server threads", + "• Fix Unknown Artist when adding playlist tracks to wishlist", + "• Fix slskd timeout spam when Soulseek is not the active download source", + ], + }, + { + title: "Recent Fixes", + description: "Bug fixes from recent releases and community reports", + features: [ + "• Fix watchlist scan false failures — empty discography no longer reported as error", + "• Fix deezer_artist_id column error on enhanced library sync", + "• Fix wishlist button intermittently not navigating to page", + "• Fix worker orb tooltips rendering behind dashboard content", + "• Fix OAuth callback port hardcoding — custom ports now respected", + "• Fix allow duplicates setting not saving", + "• Fix wishlist dropping cross-album tracks when duplicates enabled", + "• Fix replace lower quality setting not persisting", + "• Fix Spotify enrichment worker infinite loop on pre-matched artists", + "• Reject Qobuz 30-second sample/preview downloads", + "• Fix library page crash on All filter — non-string soul_id broke card rendering", + "• Auto Wing It fallback for failed discovery — unmatched tracks download via Soulseek with raw metadata", + "• Lidarr download source now production-ready — full orchestrator integration", + "• Fix album track lookup hardcoded to Spotify — now uses configured primary source", + "• Fix M3U showing all tracks as missing — regenerate with real paths after post-processing", + "• Fix AcoustID retag not writing corrected tags to audio file", + "• Fix wishlist albums cycle stuck at 1 concurrent worker instead of configured value", + "• Fix downloads badge dropping to 300 after opening Downloads page", + "• Fix server playlist Find & Add inserting at wrong position on Plex", + "• Smarter Fix modal results — standard album versions sorted above live/remix/cover/soundtrack variants", + "• Unmatch discovery tracks — red ✕ button to remove bad matches from playlist discovery", + "• Customizable music video naming — path template with $artist, $title, $year variables", + "• Fix soulseek log spam when not configured as download source", + ], + }, + { + title: "Earlier in v2.3", + description: "Major features from earlier in this release cycle", + features: [ + "• Centralized Downloads page with live-updating list and filter pills", + "• First-Run Setup Wizard — 7-step guided configuration", + "• Music Videos — search and download from YouTube", + "• Inbound Music Request API for external tools (Discord bots, Home Assistant)", + "• Lidarr download source (development) — 7th source for Usenet/torrent via Lidarr", + "• Graceful shutdown — all workers respond to shutdown signals immediately", + "• Unknown Artist prevention with 3-tier metadata fallback", + "• Deezer multi-artist tagging using contributors field", + "• Artist Map — Watchlist Constellation, Genre Map, and Artist Explorer canvas modes", + "• Discogs integration — enrichment worker, fallback source, enhanced search tab", + "• Wing It mode, Global Search Bar, Redesigned Notifications", + "• Server Playlist Manager, Sync History Dashboard, Playlist Explorer", + "• Enhanced Library Manager with inline tag editing and write-to-file", + "• Automation Signals, Multi-Source Search Tabs, Rich Artist Profiles", + ], + }, +]; + function _getCurrentVersion() { const btn = document.querySelector('.version-button'); return btn ? btn.textContent.trim().replace('v', '') : '2.4.0';