Commit graph

1042 commits

Author SHA1 Message Date
Broque Thomas
ef8cff3c69 Version bump to 2.34 2026-04-19 18:40:58 -07:00
Broque Thomas
35d6fff916 Fix wishlist albums cycle forced to 1 concurrent worker
Auto-wishlist albums cycle was passing is_album=True to
_get_batch_max_concurrent which returns 1 for soulseek mode.
This restriction is for folder-based album grabs from a single
peer, not individual track downloads. Wishlist always does
single-track downloads regardless of cycle, so it should use
the user's configured concurrency setting.
2026-04-19 17:29:46 -07:00
Broque Thomas
5fb1972361 Fix downloads nav badge dropping to 300 after page open
_adlFetch() fetches /api/downloads/all?limit=300 then _adlUpdateBadge()
was counting active statuses from that truncated array, overwriting
the real server-side count maintained by WebSocket. Removed the
badge update from _adlFetch — the WebSocket status push already
keeps it accurate.
2026-04-19 17:28:08 -07:00
Broque Thomas
dd3b71bc9a Fix discovery search quality and server playlist track positioning
Fix modal results: sort standard album versions above live, remix,
cover, soundtrack, remaster, and deluxe variants so users see the
original studio track first instead of obscure versions.

Plex Find & Add: tracks were always appended to the end of the
playlist because addItems ignores position. Now moves the track
to the correct slot after adding via moveItem.
2026-04-19 17:26:29 -07:00
Broque Thomas
5b9c9bc6fa Sort Fix modal results to prioritize standard album versions
Discovery Fix modal search results now sort standard album versions
above live recordings, remixes, covers, soundtracks, remasters,
deluxe editions, and other variants. Fixes cases where searching
"Mother Danzig" returned a live version first, or "Even Flow Pearl
Jam" returned a soundtrack instead of the original from Ten.
2026-04-19 17:21:02 -07:00
Broque Thomas
a3c8b9ecdd Add unmatch button, video naming template, and fix slskd log spam
Unmatch: found tracks in playlist discovery now have a red X button
to remove bad matches. Clears match data, sets back to Not Found,
persists in DB for mirrored playlists, and respects user choice on
re-discovery runs (won't re-match automatically).

Video naming: new path template in Settings with $artist, $title,
$artistletter, $year variables. Default unchanged ($artist/$title-video)
so existing Plex setups aren't affected.

slskd logs: Clean Search History automation skips when Soulseek is
not the active download source, eliminating connection error spam.
2026-04-19 17:16:22 -07:00
Broque Thomas
122a6999b3 Add customizable music video naming and fix slskd log spam
Video naming: new path template in Settings → Paths & Organization
with $artist, $artistletter, $title, $year variables. Default
unchanged ($artist/$title-video → Artist/Title-video.mp4) so
existing Plex setups aren't affected. Users can remove the -video
suffix or reorganize however they like.

slskd logs: the Clean Search History automation now skips when
Soulseek is not the active download source, eliminating noisy
connection error logs for users who don't use Soulseek.
2026-04-19 16:57:36 -07:00
Broque Thomas
a23bce5966 Auto Wing It fallback for failed playlist discovery tracks
When playlist discovery fails to match a track on any metadata API,
instead of marking it "Not Found" and excluding it from downloads,
automatically build stub metadata from the raw source title/artist
and include it in the download queue. Soulseek searches with the
raw data, post-processing enhances whatever it can find.

All 7 discovery workers updated: YouTube, ListenBrainz, Tidal,
Deezer, Spotify Public, Beatport, and automated mirrored playlists.
Amber "Wing It" badge distinguishes stubs from real API matches.
Fix button still available so users can manually find a proper match.
Wing It stubs persist in DB for mirrored playlists and are
re-attempted on future discovery runs. Failed wing-it downloads
skip wishlist per-track (checked by wing_it_ ID prefix) so real
matched failures in the same batch still go to wishlist normally.
2026-04-19 16:05:50 -07:00
BoulderBadgeDad
e0f036df08
Merge pull request #328 from JohnBaumb/feature/deep-linking
Add URL-based deep linking for SPA navigation
2026-04-19 13:07:46 -07:00
BoulderBadgeDad
12087f2407
Merge pull request #327 from kettui/fix/artist-details-refactoring
Refactor artist-detail discography flow
2026-04-19 12:53:35 -07:00
Broque Thomas
f9de081bd5 Fix library page crash when All letter filter is used
soul_id.startsWith() threw TypeError for non-string values, crashing
the entire card rendering pipeline. Letter-specific filters worked
because the problematic artist wasn't in those filtered results.
Added String() wrapper on all 3 soul_id.startsWith calls and a
try-catch around individual card rendering so one bad card can't
take down the whole page.
2026-04-19 12:48:53 -07:00
JohnBaumb
7d311451cb feat: URL-based deep linking for SPA navigation
- Flask catch-all route serves index.html for client-side paths, excluding api/static/auth/callback/status prefixes.- navigateToPage pushes history state so URL reflects current page.- popstate listener handles browser back/forward without reloading.- Initial load reads window.location to restore the page after refresh or direct link.- artist-detail and playlist-explorer fall back to parent pages since they need runtime context.
2026-04-19 10:46:58 -07:00
Antti Kettunen
33b4ea6429
Refine artist-detail discography flow
- move artist-detail discography resolution onto the shared source-priority metadata service
- keep the variant dedup helper in the UI-facing adapter
- pass the chosen source through completion checks
- add coverage for the new adapter and dedup behavior
2026-04-19 20:38:49 +03:00
Antti Kettunen
17865fe712
Refactor artist discography completion metadata flow
Move completion checks into metadata_service and make them follow the configured metadata source priority.

Drop the old test-mode path, remove the web_server wrapper indirection, and keep artist inference on explicit release metadata instead of guessing from a track search.

Add coverage for the source-priority completion behavior and the safer artist-name handling.
2026-04-19 15:40:08 +03:00
Broque Thomas
0380e88bb0 Fix MusicBrainz tab missing from enhanced search results
MusicBrainz was returned by the backend as an alternate source but the
frontend's orderedSources list didn't include it, so it was never fetched.
2026-04-18 23:52:48 -07:00
Broque Thomas
2bd8d2ac7a Version bump to 2.33 2026-04-18 23:39:26 -07:00
Broque Thomas
63230ae39c Add bottom padding to library pagination for scroll breathing room 2026-04-18 23:35:28 -07:00
Broque Thomas
533091f605 Update What's New — live log viewer now mentions search and smart filtering 2026-04-18 23:26:04 -07:00
Broque Thomas
3404812a1e Improve live log viewer — fix level filters, faster updates, add search
- Fix level filter showing nothing: now uses heuristic classification
  for print() output (error/traceback/failed→ERROR, warn→WARNING, etc.)
  in addition to exact logger format matching
- Speed up WebSocket updates from 2s to 0.5s polling
- Add search box with 300ms debounce — filters both initial load and live
- Use DocumentFragment for batch DOM appends (performance)
- Increase line cap from 1000 to 2000
- Backend search parameter support in /api/logs/tail
2026-04-18 23:13:51 -07:00
Broque Thomas
8b0e619fa1 Add live log viewer on Settings → Logs tab
Terminal-style real-time log viewer with:
- Log file selector (app, post-processing, acoustid, source reuse)
- Color-coded log levels (DEBUG gray, INFO blue, WARNING yellow, ERROR red)
- Level filter buttons (All/Debug/Info/Warn/Error)
- Auto-scroll with toggle, copy and clear buttons
- Live updates via WebSocket (2s polling, pushes new lines)
- Initial load fetches last 200 lines via REST API
- 1000-line display cap with oldest lines trimmed

Also fixes Advanced tab settings (Discovery Pool, Security, etc.) being
hidden inside collapsed Library Preferences section body — misplaced
closing div caused them to be invisible.
2026-04-18 22:57:15 -07:00
Broque Thomas
c0c38268f5 Fix tool help modal not closable from Automations page
The close button and backdrop click handlers were only attached when
the Tools page was visited (initializeToolHelpButtons). Automation
builder '?' buttons open the same modal but the close handlers were
never set up. Added inline onclick handlers to the modal HTML and a
global Escape key listener so closing works from any page.
2026-04-18 21:49:30 -07:00
Broque Thomas
07d67e8517 Fix Your Albums section opening playlist modal instead of album modal
Your Albums cards on the Discover page were using the YouTube/playlist
modal (openDownloadMissingModalForYouTube) instead of the album modal
(openDownloadMissingModalForArtistAlbum). Now displays with proper
album hero section and uses album download context for file organization.
2026-04-18 21:44:18 -07:00
Broque Thomas
aa8f97e3d5 Add optional ReplayGain analysis to post-processing pipeline
New toggle in Settings → Library → Post-Processing: "Apply ReplayGain
tags after download". When enabled, analyzes loudness via ffmpeg's
ebur128 filter and writes track-level ReplayGain gain/peak tags.
Runs after metadata tagging but before lossy copy so both files get
the tags. Off by default — adds a few seconds per track.

Applied to both album and playlist/single download paths.
2026-04-18 21:04:01 -07:00
Broque Thomas
288994e081 Update What's New with all today's features and fixes
Added: Genre Whitelist, Standalone Full Refresh, Folder Terminology
Rebrand, and 12 bug fixes (duplicate detector, single track downloads,
liked songs source label, metadata crash, scan button stuck, deep scan
logging, settings tab flash, standalone verify speed, and more).
2026-04-18 20:41:59 -07:00
Broque Thomas
288776a7f3 Add genre whitelist for filtering junk tags during enrichment
New core/genre_filter.py with ~180 curated default genres. When strict
mode is enabled in Settings → Library Preferences → Genre Whitelist,
only whitelisted genres pass through during enrichment. Junk tags from
Last.fm (artist names, radio shows, playlist names) are silently dropped.

Applied at all 10 genre write points: Spotify, Last.fm, AudioDB, Deezer,
Discogs, iTunes, Qobuz enrichment workers + post-processing genre merge
+ initial download artist/album creation.

Strict mode is OFF by default — zero behavior change for existing users.
First enable auto-populates the whitelist with defaults. Users can add,
remove, search, and reset genres via the Settings UI.
2026-04-18 20:23:53 -07:00
Broque Thomas
c6de707f94 Fix single track search results downloading with album context
When clicking a track in enhanced or global search, the download modal
correctly showed SINGLE but the download used is_album_download=true,
causing the file to be organized under the album path template instead
of the singles template. Now enhanced_search_track_ and gsearch_track_
prefixes pass album metadata for tagging but set is_album_download=false.
2026-04-18 19:46:20 -07:00
Broque Thomas
1564d7bd53 Fix Liked Songs playlist misidentified as YouTube source
The source detection chain for playlist hero sections didn't handle
the 'spotify:liked-songs' playlist ID prefix, falling through to the
default 'YouTube' label. Added 'spotify:' prefix check.
2026-04-18 19:21:25 -07:00
Broque Thomas
6036e02011 Fix library scan button stuck on 'Stop' and stale count shown as 'failed'
Dashboard scan polling checked for 'completed' but backend sets 'finished'.
Added 'finished' to the completion check so polling stops, button resets,
stats refresh, and toast fires correctly. Also fixed deep scan reporting
stale record removals as 'failed' instead of 'successful'.
2026-04-18 18:19:49 -07:00
Broque Thomas
f8e4adde41 Remove redundant output path mirror from standalone config section
The disabled path field on the Connections tab was showing stale data
(always ./Transfer) because it read from the DOM before settings loaded.
Removed it entirely — the output path is configured on the Downloads tab.
Standalone section now just shows description + verify button.
2026-04-18 17:53:07 -07:00
Broque Thomas
db7714c4db Fix settings page showing all tabs on first load
Non-active tab groups were visible during async data loading because
switchSettingsTab ran after the awaits. Moved it before async calls and
added CSS defaults to hide non-connections groups, preventing any flash.
2026-04-18 17:50:05 -07:00
Broque Thomas
efe8280e23 Rebrand folder terminology: Download→Input, Transfer→Output, Staging→Import
All user-facing labels, docs, help text, tooltips, error messages, and debug
info output updated. Backend config keys, variable names, actual path values,
and Docker volume mounts are completely unchanged — zero functional impact.
2026-04-18 17:35:20 -07:00
Broque Thomas
cef1162c5e Update What's New with today's features and fixes 2026-04-18 16:07:33 -07:00
Broque Thomas
b17a6e2dd7 Add per-artist metadata source override for watchlist scans
Users can now override which metadata provider (Spotify, Deezer, Apple Music,
Discogs) is used when scanning a specific watchlist artist for new releases.
The selector appears in the artist config modal and only shows sources the
artist has enrichment IDs for. Default behavior is unchanged — all artists
use the global metadata source unless explicitly overridden.
2026-04-18 16:05:05 -07:00
Broque Thomas
381e37ecf7 Enhance logging, debug info, and add Troubleshooting docs section
- Move Log Level dropdown from Downloads tab to Advanced tab (Settings)
- Fix staging path config key (import.staging_folder → import.staging_path)
- Fix library stats showing 0 (use get_database_info_for_server like dashboard)
- Add Troubleshooting & Support docs section (log files, debug info, common issues, reporting)
- Beef up Copy Debug Info: ffmpeg version, runner type, Discogs status, wishlist count,
  music library paths, music videos dir, log level, metadata source, hybrid priority,
  lossy copy config, auto import, duplicate tracks, replace quality, log file listing
- Add GitHub issue link footer to debug output
- Add discogs to enrichment worker list in debug endpoint
2026-04-18 15:05:19 -07:00
BoulderBadgeDad
f636014b9a
Merge pull request #316 from kettui/fix/reduce-ui-stalls
Reduce UI stalling during enhanced search, add caching & small concurrency optimizations
2026-04-18 12:32:55 -07:00
Broque Thomas
020ce6d765 Hide discovery modal sync buttons in standalone mode
"Sync This Playlist" buttons in YouTube/Tidal/Deezer/Spotify/Beatport/
ListenBrainz discovery modals were not gated by _isSoulsyncStandalone.
Added check to the hasSpotifyMatches condition that generates them.
2026-04-18 09:15:19 -07:00
Broque Thomas
33911a7001 Show Sync page in standalone mode, only hide sync buttons
The Sync page was hidden entirely for standalone users, blocking
access to playlist browsing, discovery, and downloads. Now the page
is accessible — only the sync-to-server buttons are hidden since
there's no server to push playlists to.
2026-04-18 09:00:55 -07:00
Broque Thomas
ad9d5817cf Fix 'Loading playlist...' stuck forever on error
openDownloadMissingModal showed loading overlay but didn't hide it
on error paths (playlist not found, fetch failure). The overlay
persisted across page navigation, blocking the entire UI.
2026-04-18 08:37:31 -07:00
Broque Thomas
f4aaab8a66 Reorganize Settings Library tab with collapsible sections
Three collapsible categories, collapsed by default:
- Paths & Organization (file templates + music library paths)
- Post-Processing (metadata, tags, conversion, lyrics)
- Library Preferences (import, content filter, stats, playlists, M3U)

Section headers have data-stg=library so they only appear on the
Library tab. Bolder headers with accent-colored arrows and subtle
border. Collapse state preserved when switching settings tabs.
2026-04-18 08:28:12 -07:00
Antti Kettunen
688e84ce4d Fix MusicBrainz label, primarily show tabs with fetched results only 2026-04-18 14:57:12 +03:00
Antti Kettunen
02f190efc6 Reduce enhanced search stalls
Delay alternate-source fan-out until the primary enhanced-search response arrives, and stagger those follow-up requests so they do not all compete at once. Also parallelize artist, album, and track lookups inside each metadata source request to shorten the time the UI thread spends waiting on remote APIs. This keeps the single-worker web UI more responsive under the app's chatty search flow.
2026-04-18 14:46:50 +03:00
Broque Thomas
841ad42fdd Fix MusicBrainz tab not appearing in enhanced search
Source fetch list was hardcoded without musicbrainz — tabs only showed
for sources that were pre-fetched in the parallel search loop.
2026-04-18 02:10:12 -07:00
Broque Thomas
4f5025d526 Add MusicBrainz search tab, wider global search, bump to v2.32
New MusicBrainz tab in Enhanced and Global search — finds tracks and
albums on MusicBrainz's community database with Cover Art Archive
images. Covers obscure tracks that Spotify/Deezer/iTunes miss.

- core/musicbrainz_search.py: search adapter with Track/Artist/Album
  dataclasses, Cover Art Archive integration, smart query parsing
- Albums deduplicated (keeps best version with date and art)
- No artist results shown (MusicBrainz has no artist images)
- Album detail with full tracklist for download modal
- Smart word-boundary splitting for queries without separators
- Global search results container widened from 620px to 920px
- UI version bumped to 2.32
2026-04-18 02:06:27 -07:00
Broque Thomas
70005968b6 Restructure version modal and What's New for v2.31
SoulSync Standalone Library is now the first section in both the
version modal and What's New popup. Auto-Import section updated with
all improvements (recursive scan, singles, tag preference, AcoustID).
New Downloads & Soulseek section groups download-related improvements.
Recent Fixes cleaned up — feature items moved to proper sections.
2026-04-18 01:28:19 -07:00
Broque Thomas
b4475152a9 Hide all sync buttons in standalone mode, bump UI to v2.31
All sync-related buttons hidden when active server is SoulSync
Standalone. Covers static buttons (querySelectorAll on status update)
and dynamic modal buttons (_isSoulsyncStandalone flag).
UI version bumped to 2.31 (Docker stays at 2.3).
2026-04-18 01:14:54 -07:00
Broque Thomas
12e9c0034b Hide Sync page in sidebar for SoulSync standalone mode
No media server to sync playlists to — sync page is irrelevant.
M3U generation is still available via settings toggle and download
modal buttons for standalone users who want playlist files.
2026-04-18 00:27:38 -07:00
Broque Thomas
d688e7fa15 Update What's New and version modal with standalone library and import fixes 2026-04-17 23:29:04 -07:00
Broque Thomas
c009acdbb6 Add SoulSync Standalone toggle to Settings page
Fourth server option on the Connections tab with SoulSync logo and
'Standalone' label. Config panel shows Transfer folder path and
Verify Folder button. Test connection counts audio files in the
Transfer folder. Settings save/load properly detects soulsync toggle.
2026-04-17 20:56:05 -07:00
Broque Thomas
bbf5af1ce1 Fix auto-import rescan race condition, coverage penalty, and UI
Race condition: scanner re-scanned folders while post-processing was
still moving files, causing partial matches and ghost failures. Now
tracks in-progress paths and skips them on subsequent scans.

Coverage penalty fix: individual tracks that match at 80%+ confidence
now auto-import even when overall album coverage is low (e.g. 2 of 18
tracks present). Previously low coverage killed the entire import.

Import page: stats bar, filter pills, Scan Now, Approve All, Clear
History (clears imported + failed), live scan progress.
2026-04-17 19:37:42 -07:00
Broque Thomas
a2e3ce8000 Fix auto-import track numbers, dates, cover art, and track name display
- Track numbers defaulted to 1 instead of using metadata source values
- Release dates not captured, causing missing year in path templates
- Cover art missing for Deezer (direct image_url not checked)
- Track names in expanded view showed Unknown (wrong JSON field name)
- Read year/date from embedded file tags as fallback
- Add Deezer get_album_metadata/get_album_tracks fallbacks
- Handle Deezer tracks.data response format
2026-04-17 19:05:52 -07:00
Broque Thomas
d2c6979ce4 Recursive staging scan, singles support, and improved import UI
Auto-import now scans the staging folder recursively — any folder
structure depth works (Artist/Album/tracks, Album/tracks, etc.).
Loose audio files are treated as singles with tag/filename/AcoustID
identification.

Import results UI redesigned:
- Click cards to expand per-track match details with confidence scores
- Shows identification method badge (Tags, Folder Name, AcoustID)
- Per-track grid: track name, matched filename, confidence percentage
- Time ago labels, folder path, better status badges
- Approve/Dismiss buttons use event.stopPropagation for clean UX
2026-04-17 17:48:00 -07:00
Broque Thomas
c20529dbb5 Fix auto-import toggle visual and import page refresh
Toggle appeared off when running because CSS :checked rules were
scoped to .repair-master-toggle. Added auto-import-toggle-label
selectors. Refresh now re-renders whichever tab is active.
2026-04-17 17:07:19 -07:00
Broque Thomas
1446c7b63a Fix auto-import toggle visual flicker
Toggle state was set by browser click, then immediately overwritten by
the status reload callback. Now optimistically sets the toggle and
status text before the API call, reverting only on failure.
2026-04-17 16:33:05 -07:00
Broque Thomas
eb2218ec8d Add file deletion option to album delete on enhanced library page
Album delete now shows a smart delete dialog with two options:
- Remove from Library (DB only, files untouched)
- Delete Files Too (removes DB records AND deletes audio files from
  disk, cleans up empty album folder)

Backend /api/library/album/<id> DELETE now accepts ?delete_files=true
parameter, resolves each track's file path, and removes files before
deleting DB records. Reports files_deleted and files_failed counts.
2026-04-17 15:26:25 -07:00
Broque Thomas
619b7ab4be Update What's New and version modal with recent fixes 2026-04-17 15:05:57 -07:00
Broque Thomas
aede7dd089 Fix download modal freezing by moving M3U save to completion only
autoSavePlaylistM3U was called on every 2-second poll cycle once any
track completed, flooding the server with heavyweight M3U generation
requests (fuzzy matching all tracks against the DB). This exhausted
Flask's thread pool, causing the batch status endpoint to hang and
killing the poller — making the modal freeze mid-download.

Now fires once when the batch completes instead of on every poll.
2026-04-17 15:01:45 -07:00
Broque Thomas
7415485b9a Harden download modal polling against premature completion
- Include completed batches in poll cycle so late task updates still
  render (prevents modal freezing when batch completes before all rows
  update)
- Require server to report no active tasks before client-side
  completion fires (prevents phase=complete from prematurely ending UI)
- Apply same fix to backoff poller and WebSocket resubscription

Note: modal stalling issue persists — setInterval stops firing after
~12 cycles for unknown reasons. Needs deeper browser-level debugging.
2026-04-17 14:52:18 -07:00
Broque Thomas
57259b6e3a Always run HTTP polling for download modal updates
The global download poller was disabled when WebSocket was connected,
but WebSocket connections can silently stop delivering messages without
triggering a disconnect event (room subscription lost, server emit
error, proxy timeout). This left modals frozen while downloads
continued server-side. Removing the socketConnected gate ensures the
2-second HTTP poll always runs as a fallback alongside WebSocket.
2026-04-17 14:08:48 -07:00
Broque Thomas
1384b966e8 Fix Unknown Artist when adding playlist tracks to wishlist
When adding tracks to wishlist from a playlist download modal,
process.artist was undefined (only set for album downloads) and
defaulted to {name: 'Unknown Artist'}. This got stored in
source_info.artist_name and was prioritized over the track's own
artist data during wishlist download post-processing.

Now resolves the artist per-track from the track's own artists array,
falling back to the process-level artist only for album downloads
where it's actually set correctly.
2026-04-17 14:01:56 -07:00
BoulderBadgeDad
6b70d7331c
Merge pull request #295 from pxjx22/fix/socketio-polling-fallback
Fix Socket.IO client fallback order
2026-04-17 13:12:22 -07:00
Broque Thomas
168b4c21dd Fix batch panel collapse button not clickable when collapsed
Title text was pushing the toggle button out of the 44px collapsed
panel. Now hides the title and centers the button when collapsed.
2026-04-17 12:58:29 -07:00
Broque Thomas
8f7a3b4861 Use SoulSync confirm dialog for batch cancel instead of browser alert 2026-04-17 12:29:50 -07:00
Broque Thomas
04b8c02ea9 Reject junk artist Soulseek results and cancel downloads on wishlist clear
Soulseek results from "Various Artists", "VA", "Unknown Artist", and
"Unknown Album" folders are now rejected before scoring. These
compilation folders rarely contain properly tagged files for the target
artist.

Clearing the wishlist now also cancels any active wishlist download
batch and resets the auto-processing flag, so downloads don't keep
running after the source tracks are removed.
2026-04-17 12:24:50 -07:00
Broque Thomas
9898bd1190 Add batch context panel to Downloads page
Split Downloads page into main list (left) and batch panel (right).
Each active batch gets a color-coded card with artwork thumbnail,
progress bar, per-track status with download percentages, and
expandable track list. Download rows get matching color indicators.

- Click batch name to open its download/wishlist modal
- Filter icon narrows main list to one batch with clear banner
- Collapsible panel toggle for full-width list view
- Completed batches fade out after 15 seconds
- 7-day batch history with source type color dots
- Artwork fallback shows colored initial when no art available
- Per-track progress: download %, spinner for searching, proc label
- source_page column on sync_history for UI origin tracking
- /api/downloads/all includes batch summaries and per-track progress
- /api/downloads/batch-history endpoint for history queries
- Responsive layout, overflow-x hidden to prevent scroll flicker
2026-04-17 12:18:00 -07:00
Broque Thomas
5e62229d00 Navigate to downloads page when wishlist is already processing
Clicking the Download Wishlist button while auto-processing was active
only showed a toast telling the user to check the Downloads page. Now
navigates there directly so progress is immediately visible.
2026-04-17 11:15:19 -07:00
Broque Thomas
6989701d65 Include album name in Soulseek search queries
Priority 0 query (artist + album + title) was gated behind a download
mode check that excluded Soulseek, the source that benefits most from
it. Soulseek searches match against file paths where users organize as
Artist/Album/Track — without the album name, ambiguous artist names
could match wrong-artist results (e.g. "Bleakness" as an album folder
instead of an artist). Removed the mode gate so all sources get the
most specific query first.
2026-04-17 11:12:07 -07:00
Broque Thomas
a4415db339 Skip slskd polling when Soulseek is not active or disconnected
Dashboard stats (every 10s) and download status endpoint were
unconditionally calling slskd transfers/downloads API, causing
connection timeout spam for users with a slskd URL configured but
using YouTube/Tidal/etc as their download source. Now checks both
download source mode and status cache before making the API call.
2026-04-17 09:35:18 -07:00
Broque Thomas
2429d87dbe Update What's New and version modal with recent features and fixes
Adds April 17 entries for Auto-Import, Wishlist Nebula, automation group
management, bidirectional artist sync, provider-agnostic discovery, live
sidebar badges, and critical source ID embedding fix. Version modal
reorganized to lead with current features and summarize earlier v2.2 work.
2026-04-17 07:46:07 -07:00
Broque Thomas
308773ea7c Add Auto-Import — background staging folder watcher with smart matching
Full auto-import pipeline: background worker watches the staging folder,
identifies music using embedded tags → folder name parsing → AcoustID
fingerprinting, matches files to metadata source tracklists, and
processes high-confidence matches through the existing post-processing
pipeline automatically.

Worker: AutoImportWorker with start/stop/pause/resume, configurable
scan interval (default 60s), confidence threshold (default 90%), and
auto-process toggle. Processes one folder per cycle, alphabetical
order. Disc folder detection, stability checking, content hash dedup.

Confidence gate: 90%+ auto-processes silently, 70-90% queued as
pending review with approve/dismiss actions, <70% flagged for manual
identification. Track matching uses weighted algorithm (title 45%,
artist 15%, track number 30%, album tag 10%).

Database: auto_import_history table tracks every scan result with
folder hash, match data JSON, confidence, status, timestamps.

API: 7 endpoints — status, toggle, settings (GET/POST), results
(filtered/paginated), approve, reject.

UI: Auto tab on Import page with enable toggle, confidence slider,
scan interval selector. Live result cards with album art, confidence
bar (green/yellow/red), status badges, match stats. 5-second polling.
2026-04-17 06:51:08 -07:00
Broque Thomas
922b350983 Show all server playlists with synced/unsynced visual separation
Server Playlists tab now displays ALL playlists from the media server,
split into two sections: Synced Playlists (with mirrored/history match,
full opacity, "Synced" badge, "Open Editor" action) and Other Server
Playlists (everything else, dimmed at 70%, "View Tracks" action). Both
sections have header with icon, title, and count. Unsynced cards fade
to full opacity on hover.
2026-04-16 22:19:20 -07:00
Broque Thomas
a867bba18f Bidirectional artist sync, repair jobs grid, deezer column fix
Artist Sync button on enhanced library page now does true bidirectional
sync: Phase 1 pulls new albums/tracks from the media server using the
DatabaseUpdateWorker in deep scan mode (preserves enrichment), Phase 2
removes stale DB entries for files no longer on disk. Works for Plex,
Jellyfin, and Navidrome. Toast shows +albums, +tracks, -stale counts.

Repair jobs tab redesigned: 2-column grid layout with glass gradient
cards, accent top line on hover, hover lift effect, job description
text below name, running state with pulsing accent bar. Responsive
to single column under 900px.

Fixed deezer_artist_id → deezer_id column name on artists table lookup.
2026-04-16 19:13:03 -07:00
Broque Thomas
09d358ef69 Fix watchlist scan false failures, Spotify backfill, and wishlist remove
Watchlist scanner: empty discography (no new releases in lookback) was
treated as API failure, causing "Failed to get artist discography" for
artists like Kendrick Lamar who simply had no recent releases. Now
distinguishes None (API failure → try next source) from [] (success,
no new tracks). Spotify backfill now uses the authenticated client
instance instead of creating a fresh unauthenticated one.

Wishlist nebula: album remove now sends album_name (API updated to
accept album_name as fallback alongside album_id). Track remove
re-renders the nebula after deletion. Toned down processing pulse
animation.

Updated test to verify fallback triggers on API failure (None), not
on empty results.
2026-04-16 18:06:45 -07:00
Broque Thomas
f59c564382 Wishlist Nebula — expanded view redesign, live processing, download flow
Expanded albums: click album tile to reveal track list with per-track
remove buttons. Art shrinks to banner, title/count move to static
position, tracks scroll at 200px max-height. Handles many albums with
flex-wrap and align-items: flex-start.

Expanded singles: visible labels below 44px art circles, remove button
on hover. No longer tooltip-only.

Live processing: polls wishlist stats every 5s, detects auto-processing
and manual download batches. Orbs pulse with accent glow during active
processing. Nebula auto-refreshes when tracks complete (count decreases).
Polling stops on page navigation.

Download flow: single "Download Wishlist" button opens category choice
dialog (Albums/Singles with counts). If processing is already active,
shows toast or reopens existing download modal. Toned down processing
pulse animation (3s cycle, scale 1.03, brightness 1.1).
2026-04-16 17:09:25 -07:00
Broque Thomas
16ad6184ed Wishlist Nebula enhancements — artist photos, art ring, animations
Eight visual upgrades to the wishlist nebula:

1. Watchlist artist photos used for orb images (cross-referenced via API)
2. Hover tooltip with artist name and track count
3. Pulse animation on orbs when their category is next in auto-processing
4. Album art ring — tiny covers orbit each orb in a slow spinning ring
5. Staggered entry animation — orbs fade in and float up on page load
6. Click artist name to navigate to Artists page with search pre-filled
7. Improved label styling with accent color hover + underline
8. Responsive art ring sizing per orb size class
2026-04-16 16:33:16 -07:00
Broque Thomas
dd26437125 Wishlist Nebula — artist orb visualization replacing category cards
Bespoke wishlist page design: each artist is a glowing orb sized by
track count (sm/md/lg), with a spinning conic gradient ring colored by
artist name hash. Click to expand — albums appear as satellite rows
with cover art, singles as compact pills. Remove buttons on hover at
every level (album, single track).

Search bar filters orbs in real-time. Download Albums/Singles buttons
trigger the existing category download flow. Orbs sorted by track count
(biggest artists first). Responsive layout with mobile breakpoints.
2026-04-16 16:24:56 -07:00
Broque Thomas
cd157fc692 Fix wishlist button intermittently not navigating to page
The active-process check could hang or return stale data, making the
button feel unresponsive. Added 2-second timeout with AbortController,
fast path for already-visible client process, and graceful fallback to
navigateToPage on rehydration failure or timeout.
2026-04-16 15:39:19 -07:00
Broque Thomas
baed8ed8b6 Fix worker orb tooltips rendering behind dashboard content
Added z-index: 10 to .dashboard-header to elevate its stacking context
above the dashboard content sections. Bumped all 13 worker orb tooltip
z-index values from 1000 to 5000. The tooltips now render above section
headers when they drop downward from the header into the content area.
2026-04-16 14:23:38 -07:00
Broque Thomas
85b470809e Add automation group management — rename, delete, bulk toggle, drag-drop
Full automation page upgrade with group management and drag-and-drop:

Backend: batch_update_group() and bulk_set_enabled() DB methods, new
PUT /api/automations/group and POST /api/automations/bulk-toggle endpoints.

Group headers: rename (inline edit), delete (choice dialog — keep
automations or delete all), bulk toggle (enable/disable all in group).
Actions appear on hover, styled as small icon buttons.

Drag and drop: non-system cards are draggable between group sections.
Drop zones show dashed accent border feedback. Collapsed sections
auto-expand on 500ms drag-hover. System/Hub sections dimmed during drag.
dragenter counter pattern handles child element bubbling.

Delete group dialog: glass card modal with three options — keep
automations (move to My Automations), delete everything, or cancel.
2026-04-16 14:19:13 -07:00
Broque Thomas
7317bd7c55 Live sidebar badges for Watchlist/Wishlist, update Wishlist icon to star
Sidebar nav badges now update from HTTP polling (every 10s), WebSocket
pushes, and page init — counts stay current regardless of which page
the user is on. Wishlist icon changed from music note to star in both
sidebar and page title to distinguish from Artists page.
2026-04-16 13:47:42 -07:00
Broque Thomas
4c9bab356d Fix dashboard header layout — center worker orbs, reposition quick-nav
Worker orbs now centered across the full header width instead of
right-aligned. Watchlist/Wishlist buttons moved to absolute top-right
corner in their own container, preventing tooltip overflow from pushing
them to a second line. Import button removed from header (accessible
via sidebar).

Responsive: at 900px quick-nav drops to static full-width row, worker
orb tooltips hidden on mobile (status visible via orb color/spinner,
details on Tools page). At 768px everything stacks vertically.
2026-04-16 12:36:12 -07:00
Broque Thomas
d9b4e5b853 Add smart Library Status card to Dashboard with deep scan support
Adaptive card on the Dashboard showing library state with four modes:
- No server: gold accent, directs to Settings
- Disconnected: gold warning with troubleshooting guidance
- Empty library: blue accent with prominent Scan Now button
- Healthy: green accent with stats grid (artists/albums/tracks/DB size),
  Refresh button (incremental) and Deep Scan button (full re-check)

Stats displayed as mini cards with individual icons. Animated glow orb,
gradient accent top line, shimmer progress bar during scans. Deep scan
added to /api/database/update endpoint (deep_scan flag) — re-checks
every track, adds new ones, removes stale, preserves enrichment data.
Confirmation dialog explains what deep scan does before starting.
2026-04-16 11:45:29 -07:00
Broque Thomas
1c8a25cff9 Fix 'Delete File Too' silently failing when file path cannot be resolved
When the DB stored a path the resolver couldn't map to a local file
(common with Navidrome virtual paths or Docker path mismatches), file
deletion was silently skipped — the DB record was removed but the file
stayed on disk with no indication to the user.

Now logs the resolution failure with the stored path, returns a
file_error in the API response, and the frontend shows a warning toast
explaining the file wasn't deleted plus a second toast with the specific
reason (e.g. Navidrome 'Report Real Path' instructions).
2026-04-16 08:43:16 -07:00
Broque Thomas
60d737f7ab Add Tools sidebar page with grouped layout and Library Maintenance hero
Dashboard Tools & Operations section replaced with a compact link card.
All 10 tool cards moved to a dedicated Tools page in the sidebar, grouped
into three sections: Database & Scanning, Metadata & Cache, Management.

Library Maintenance promoted to hero position at the top of the page with
accent top bar, logo, enable toggle, and tabbed content (Jobs, Findings,
History) — no longer buried in a modal. openRepairModal() now navigates
to the Tools page. Repair modal HTML removed.

Tool initialization extracted from loadDashboardData() into a dedicated
initializeToolsPage() with idempotent event listener wiring. Container
sizing updated to use margin: 20px (matching Dashboard/Stats) instead of
max-width: 1400px for consistent full-width appearance across all pages.
2026-04-16 08:11:18 -07:00
Broque Thomas
cf18590794 Promote Watchlist and Wishlist from modals to full sidebar pages
Watchlist and Wishlist are now proper sidebar pages with full design
treatment matching the app's established visual language — glass
containers, gradient headers, accent lines, card hover effects.

Watchlist page: artist grid with sort (name/scan date/date added),
search filter, last scan summary strip, live scan activity, batch
selection, all existing sub-modals (artist config, global settings,
artist detail slideout) preserved and working.

Wishlist page: stats strip (album count, singles count, next cycle),
category cards with mosaic backgrounds, track list with inline search
filter, batch operations, download integration. Auto-processing
detection on header button shows download progress modal when active.

Header buttons rewired to navigate to pages. All refresh points updated
to reinitialize pages instead of reopening modals. Timer/polling cleanup
on page navigation. Artist detail overlay converted to fixed positioning.
2026-04-15 23:00:17 -07:00
Broque Thomas
316d4cb466 Picard-style MusicBrainz album consistency for tag embedding
Recording MBIDs are now pulled from the matched release tracklist instead
of independent match_recording() searches, guaranteeing the recording ID
is consistent with the selected release. Batch-level artist name is used
for release cache keys so all tracks hit the same preflight-cached entry
even when Soulseek metadata spells the artist differently. A post-batch
consistency pass (run_album_consistency) rewrites album-level tags on all
files after the batch completes — the safety net that prevents Navidrome
album splits even when per-track lookups drift.
2026-04-15 21:34:24 -07:00
Broque Thomas
bf123fed63 Reject Qobuz 30-second sample/preview downloads
Two-layer detection: (1) check the Qobuz API response for sample=True
before downloading, and (2) validate actual file duration with mutagen
after download — if under 35 seconds, delete and return None. Qobuz
returns valid audio files for previews (~2-5MB FLAC) that pass the
existing 100KB size check, so duration is the reliable signal.
2026-04-15 20:41:48 -07:00
Broque Thomas
9d77c403cc Fix Spotify enrichment worker infinite loop on pre-matched artists
Artists with an existing spotify_artist_id but NULL spotify_match_status
were fetched by the priority queue every ~3 seconds. _process_artist
returned early (preserving the ID) without marking the status, so the
same artist was re-queued indefinitely — burning CPU and inflating API
call counters. Now marks the artist as 'matched' on the early-return
path.
2026-04-15 20:37:20 -07:00
Broque Thomas
8866c4654b Add inbound music request API and webhook automation trigger
New POST /api/v1/request endpoint accepts a search query from external
sources (Discord bots, Home Assistant, curl) and triggers the
search-match-download pipeline asynchronously. Returns a request_id
for status polling via GET /api/v1/request/<id>. Optional notify_url
for callback on completion.

Also adds webhook_received trigger type and search_and_download action
type to the automation engine, so users can build custom flows like
"when webhook received → search & download → notify Discord".

Includes info panel in Settings showing endpoint URL and curl example.
2026-04-15 20:35:39 -07:00
Broque Thomas
e1cda4eb59 Fix 'Replace lower quality on import' setting not persisting
The import section appeared twice in the saveSettings object literal —
the second key (staging_path only) silently overwrote the first
(replace_lower_quality). JavaScript uses last-wins for duplicate keys.
Merged into a single import block.
2026-04-15 20:03:38 -07:00
Broque Thomas
41b5cd1f34 Fix allow_duplicate_tracks setting not saving and wishlist dropping cross-album tracks
Two bugs: (1) 'wishlist' was missing from the settings save whitelist,
so the toggle silently reset to ON on every page reload. (2) The
wishlist cleanup function unconditionally removed tracks sharing the
same name+artist regardless of album, ignoring the allow_duplicates
setting. Now when allow_duplicates is on, the dedup key includes the
album name so same song from different albums can coexist.
2026-04-15 19:39:34 -07:00
Broque Thomas
b89ff796bf Fix OAuth callback port hardcoding and add diagnostic logging
Auth instruction pages and log messages now use the actual configured
callback port instead of hardcoding 8888. Added startup logging that
prints whether SOULSYNC_SPOTIFY/TIDAL_CALLBACK_PORT env vars were
detected, helping diagnose Unraid/Docker env var issues. Also fixes
uses_main_port detection for custom callback ports and moves the
wishlist button handler to global init so it works on all pages.
2026-04-15 18:38:13 -07:00
Broque Thomas
09e08831f9 Fix discovery modal footer stuck on 'Discovering...' after completion
The `complete` flag from polling responses was never forwarded into the
transformed status object passed to `updateYouTubeDiscoveryModal`, so
the `if (status.complete)` block that swaps the footer from the
'Discovering...' spinner to the Sync / Download Missing buttons never
fired. Fixed for all three affected sources: Tidal, Deezer, and Spotify
Public — both the WebSocket and HTTP polling paths for each.
2026-04-15 15:26:52 -07:00
BoulderBadgeDad
19a5256feb
Merge pull request #301 from kettui/fix/respect-metadata-provider-in-more-jobs
Respect the primary metadata provider in more jobs
2026-04-15 13:04:05 -07:00
Broque Thomas
aac75d6a3b Fix Explore tab checkmark badge not persisting after refresh
Explored status was stored only in frontend memory; on reload the badge
disappeared because the API never returned it. Added explored_at column
to mirrored_playlists (auto-migrated), written when build-tree completes,
and read back via SELECT * so the badge survives page refreshes.
2026-04-15 12:09:41 -07:00
Antti Kettunen
03711c10df Make metadata gap filler source-aware
Only fetch source track details when ISRC enrichment is enabled, and show resolved source/track provenance in the UI.
2026-04-15 21:35:57 +03:00
Broque Thomas
211c7b451a Rename mirrored playlists 'Refresh' button to 'Update list'
Avoids confusion with the core mirrored playlist refresh functionality.
2026-04-15 10:33:01 -07:00
Broque Thomas
fe399636b2 Fix Spotify API calls leaking when Deezer/iTunes is primary source
Spotify was being called for album/artist data fetching across multiple
background workers and the Artists page search even when the user had
Deezer or iTunes set as their primary metadata source. Being authenticated
for playlist sync was treated as permission to use Spotify for everything.

- watchlist_scanner: add _spotify_is_primary_source() that checks both
  auth and primary source config; use it for all album/artist data fetching
  (discovery pool, recent album caching, playlist curation, similar artist
  ID matching, proactive ID backfill). _spotify_available_for_run() is kept
  for sync_spotify_library_cache which must run regardless of primary source
- repair_jobs/metadata_gap_filler: gate Spotify ISRC lookup on primary
  source being 'spotify'; MusicBrainz lookup unaffected
- repair_jobs/unknown_artist_fixer: replace hardcoded spotify_client with
  source-aware client selection — primary source ID tried first, each ID
  matched to its correct client (fixes latent bug passing Deezer IDs to
  Spotify)
- web_server.py /api/match/search: Artists page search was hardcoded to
  spotify_client.search_artists(); now uses _get_metadata_fallback_client()
  so results come from the configured primary source
2026-04-15 09:47:43 -07:00
BoulderBadgeDad
3618f3fa7f
Merge pull request #298 from kettui/fix/respect-metadata-provider-in-album-completeness-job
Honor primary metadata source in album_completeness job and associated repair flow
2026-04-15 07:15:15 -07:00
Broque Thomas
251c27e006 Add Last.fm Track Radio to Discover page
Adds a new Last.fm Radio section to the Discover page that lets users
search a track on Last.fm, generate a similar-tracks playlist, and run
it through the existing discovery/download/sync pipeline. Also generates
playlists automatically from top listening history during watchlist scans
(max once per week).

- core/lastfm_client.py: Add get_similar_tracks() using track.getsimilar
- core/listenbrainz_manager.py: Add save_lastfm_radio_playlist() with
  deterministic MBID (MD5 seed), cleanup limit of 5 for lastfm_radio type
- web_server.py: Add /api/lastfm/configured, /api/lastfm/search/tracks,
  /api/lastfm/radio/generate, /api/discover/listenbrainz/lastfm-radio;
  fix playlist['name'] KeyError in discovery worker that was resetting
  phase back to 'fresh' after completion
- core/watchlist_scanner.py: Add _generate_lastfm_radio_playlists() with
  weekly throttle, called at end of scan_all_watchlist_artists()
- webui/index.html: Add #lastfm-radio-section above ListenBrainz section,
  hidden unless Last.fm API key is configured
- webui/static/script.js: Search/generation/card-load functions; fix
  discovery modal labels (Last.fm Radio vs ListenBrainz), description
  update on completion, belt-and-suspenders completion handling inside
  updateYouTubeDiscoveryModal; fix album/duration display for tracks
  without metadata; music note SVG placeholder for missing art
- webui/static/style.css: Styles for search bar, dropdown, result rows
2026-04-14 23:41:36 -07:00
Broque Thomas
1475e3882c Reposition mini player to sit left of bell/help buttons at same baseline 2026-04-14 20:08:20 -07:00