Version 2.4.1
Patch release. ~144 commits since 2.4.0.
Highlights
Watchlist no longer re-downloads compilation tracks. Strict 0.85 album-name fuzzy match was failing on every soundtrack/compilation because Spotify and the media-server scan name them differently. One user got 7 copies of the same song. Now strips qualifier parentheticals (Music From..., OST, Deluxe Edition, Remastered) and uses a relaxed threshold with a volume/disc/part guard.
Duplicate detector catches slskd dedup orphans. Files like Song_<19-digit-timestamp>.mp3 got bucketed apart from the canonical Song.mp3 because of inconsistent media-server tag parsing. Added a second pass that re-buckets by canonical filename stem with the slskd suffix stripped.
Slskd dedup cleanup after import. Stops new orphans from being created in the first place — every successful import now prunes timestamp siblings of the canonical filename in the source folder.
Spotify auth flow reworked. Cleaner connection states, no more completion-sync race, real toasts on failure, simpler service status reads. Spotify worker pauses when Spotify isn't your primary source. Daily call budget capped at 500.
Match engine fixes. Featured-artist tracks and soundtrack tracks now match correctly during discography completion checks (used to be reported missing because they were matched against the wrong artist).
Provider-neutral wishlist + quality scanner. Both used to hardcode Spotify; now respect your configured primary metadata source. Bulk watchlist add falls back through every cached source ID before failing.
Parallel singles import (3 workers). Long backlogs finish ~3x faster.
Beatport tab hidden temporarily. Cloudflare Turnstile blocks the scraper, official OAuth API closed to public devs. Backend kept in code, revival is one HTML edit.
Performance. Service worker for cover art + installable PWA. Static assets 1y browser cache, discover pages 5min.
Security. Socket.IO same-origin default. /api/settings admin-only.
Bug round-up. #434 (config DB lock spam), #399 (bulk discography source context), Tidal auth port shown wrong, Discogs gracefully handles missing token, automation errors surface in last_error, etc.
Internal. Major web_server.py decomposition — ~30 routes / workers / helpers lifted into focused modules under core/. Search endpoints alone shed 612 lines + 94 new tests; automation endpoints shed 383 + 72 new tests.