- New Discogs section on Settings → Connections with personal token input
- Discogs added as fallback metadata source option alongside iTunes/Deezer
- Token saved to discogs.token config key
- Discogs added to API rate monitor gauges (60/min with auth)
- Help text links to discogs.com/settings/developers for token generation
- Add bit_depth, sample_rate, bitrate columns to track_downloads table
- Read audio info from file via Mutagen when recording provenance
- Source Info popover shows "Audio: 24-bit · 96.0kHz · 2304kbps"
- These values are captured from the original file before transcoding,
so users can see the original specs even after Blasphemy Mode converts
FLAC to lossy format
- Rate monitor: 2-column grid, smaller text/badges, compact gauge cards,
hide status badge on very small screens
- Notifications: full-width toast, repositioned bell button, panel fills
screen width with proper margins
- Global search: responsive bar width, full-width when active, results
panel positioned for mobile viewport
- All fixed-position elements (bell, help, search) repositioned for
mobile with smaller touch targets
- New 'webhook' then-action: sends HTTP POST with JSON payload to any
user-configured URL (Gotify, Home Assistant, Slack, n8n, etc.)
- Config: URL, optional custom headers (Key: Value per line with
variable substitution), optional custom message
- Payload includes all event variables as JSON fields
- 15s timeout, errors on 400+ status codes
- Follows exact same pattern as Discord/Pushbullet/Telegram handlers
- Frontend: config fields, config reader, icon, help docs
- Updated changelogs with webhook, M3U fix, orchestrator hardening
- autoSavePlaylistM3U() returns early for album downloads (detected by
playlistId prefix) — albums are already grouped by media servers,
M3U just creates empty duplicate playlists (Navidrome auto-imports them)
- Fix broken isAlbum detection — data-context was always "playlist",
now uses reliable playlistId prefix matching
- Update toggle label: "playlists and albums" → "playlists"
- Update hint text to explain albums are skipped and why
- Manual Export M3U button still works for both (explicit user action)
- Each of the 6 download clients initializes independently via
_safe_init() — one failing client no longer kills the orchestrator
- All methods guarded against None clients with appropriate fallbacks
- Init failures logged at startup and tracked in _init_failures list
- Copy Debug Info shows "Download Client Failures" section when any
client failed to initialize, or "ALL" if orchestrator itself is dead
- Merge enrichment worker status into rate monitor WebSocket payload
- Hide old enrichment pills — rate monitor cards now show: service name,
worker status badge, arc gauge, calls/min, 1h/24h counts, budget bar
- Debounce idle detection with 5s grace period — prevents status
flickering between Running and Idle on every worker cycle
- Responsive grid layout with richer card design
- Backend: include api_rates (per-service calls/min + Spotify endpoints)
and spotify_rate_limit (active, remaining, trigger endpoint) in debug-info
- Frontend: format API rates table with service name, cpm, limit, percentage,
and Spotify endpoint breakdown. Show bold warning block when rate limited
with trigger endpoint, remaining time, and retry-after value
- New core/api_call_tracker.py — centralized tracker with rolling 60s
timestamps (speedometer) and 24h minute-bucketed history (charts)
- Instrument all 9 service client rate_limited decorators to record
actual API calls with per-endpoint tracking for Spotify
- 1-second WebSocket push loop for real-time gauge updates
- Modern radial arc gauges with service brand colors, glowing active
arc, endpoint dot, 0/max scale labels, smooth CSS transitions
- Click any gauge to open detail modal with 24h call history chart
(Canvas 2D, HiDPI, gradient fill, grid lines, danger zone band)
- Spotify modal shows per-endpoint history lines with color legend
and live per-endpoint breakdown bars
- Rate limited state indicator — blinking red badge with countdown
timer appears on gauge card when Spotify ban is active
- REST endpoint GET /api/rate-monitor/history/<service> for chart data
- Responsive grid layout (5 cols desktop, 3 tablet, 2 phone)
- New "Concurrent Downloads" dropdown on Settings page (1-10, default 3)
- Saved to download_source.max_concurrent config key
- All 6 batch creation sites use configured value instead of hardcoded 3
- Soulseek-only album downloads still use 1 worker (source reuse per user)
- Hybrid/YouTube/Tidal/Qobuz/Deezer albums use full configured concurrency
- Per-section loading spinners (artists/albums/tracks) shown until each
NDJSON chunk arrives, auto-replaced with real content on receipt
- Active tab content auto-re-renders as streaming data arrives for both
enhanced search and global search
- Global search lazy-loads artist images for iTunes/Deezer via
/api/artist/{id}/image fallback (album art), matching enhanced search
- Source endpoint now streams artists/albums/tracks as separate NDJSON
lines as each search type completes — iTunes users see artists in ~3s
instead of waiting 9+ seconds for all 3 rate-limited calls to finish
- Enhanced search _fetchAlternateSource reads stream with ReadableStream
reader, merges each chunk into source data, re-renders tabs immediately
- Global search uses same streaming pattern via _gsFetchSourceStream
- No data loss: streamed data merges incrementally, primary response
preserves already-received alternate source data
- Remove direct request_scan() calls from album and singles import —
emit batch_complete through automation engine instead, matching the
same chain as download batches (scan → DB update)
- Show current track name in import queue status display instead of
just processed/total count
- Change <int:track_id> to <track_id> on 5 library track endpoints —
Jellyfin uses GUID strings, int converter rejected them with 404 (#237)
- Add PUT /api/library/clear-match endpoint — sets service ID to NULL
and match status to not_found, allowing users to undo wrong matches (#236)
- Add "Clear Match" button in the manual match modal for all services
- Add bottom padding to .page to prevent floating buttons (bell, help)
from overlapping track action buttons at page bottom (#237)
- New discovery_artist_blacklist table with NOCASE name matching
- Filter blacklisted artists from all 6 discovery pool queries, hero
endpoint, and recent releases via SQL subquery and Python set check
- Name-based filtering means one block covers all sources (Spotify/iTunes/Deezer)
- Hover any discovery track row → ✕ button to quick-block that artist
- 🚫 button on Discover hero opens management modal with search-to-add
(powered by enhanced search) and list of blocked artists with unblock
- CRUD API: GET/POST/DELETE /api/discover/artist-blacklist
- Updated changelogs
- Add MusicBrainz to Cache Browser: stats pill, source filter, dedicated
browse endpoint, cards with matched/failed status indicators
- Add Clear MusicBrainz and Clear Failed MB Only to cache clear dropdown
- Move MusicBrainz into Cache Health "By Source" bar chart alongside
Spotify/iTunes/Deezer instead of isolated metric row
- Rename ambiguous "Failed Lookups" to "Failed MB Lookups" in summary cards
- Add browse-musicbrainz and clear-musicbrainz API endpoints
- Add musicbrainz_total/musicbrainz_failed to cache stats response
- Add Global Search Bar and MusicBrainz cache to changelogs
- Strip cloned inline onclick on global search play button swap to prevent
simultaneous stream search + library play
- Include album thumb_url in library-check response and resolve relative
Plex paths to full URLs with base URL + token
- Pass album art through to playLibraryTrack from both global and enhanced
search library check handlers
- Add Plex music library locations as candidate dirs in _resolve_library_file_path
- Remove debug console.log from _gsDeactivate
Persistent Spotlight-style search bar at bottom-center, accessible
from any page via click, /, or Ctrl+K. Hidden on Downloads page
where enhanced search already exists.
Features matching enhanced search:
- Clear button when input has text
- Source tabs with live switching
- Source badges, library check, play buttons
- Album click opens download modal directly
- Artist click navigates to detail page
- Tab switching stays open (timestamp guard)
- Mobile responsive
1. LB Discover page cards now use the dropdown (Download/Sync) instead
of the old single button with full choice dialog
2. Dropdown position auto-flips downward when button is near viewport top
3. Dropdown centered on button instead of right-aligned
4. Sync completion toast now includes playlist name and ⚡ indicator
5. Download modal already shows ⚡ prefix on playlist name as indicator
All changes purely additive — existing flows unaffected.
Live status: updateYouTubeModalSyncProgress was hardcoded to youtube-*
element IDs but each source uses its own prefix (listenbrainz-*, tidal-*,
deezer-*, etc). Now tries all prefixes to find the correct elements.
Fixes Wing It sync progress AND a pre-existing bug where normal LB/Tidal
sync from the modal wouldn't show live progress.
Wing It button added to sync_complete phase so it persists after sync.
Fixed tracks lookup with state.playlist?.tracks fallback. Increased
button size in modal to match other action buttons.
The discoverMetadata (needed for bubble creation) was only set for
playlist IDs starting with discover_lb_, listenbrainz_, or source
SoulSync. Wing It uses wing_it_ prefix which wasn't matched.
Added wing_it_ to the condition so bubbles appear on dashboard
and sidebar during Wing It downloads.
Wing It bypasses Spotify/iTunes/Deezer matching and uses raw track
names directly. User chooses Download or Sync from a choice dialog.
Download: opens Download Missing modal with force-download-all
pre-checked. wing_it flag skips wishlist for failed tracks.
Sync: new POST /api/wing-it/sync endpoint runs _run_sync_task with
raw track dicts. Live inline sync status display on the LB card
using the same progress elements as normal sync. Unmatched tracks
skip wishlist via _skip_wishlist flag on sync_service.
Button in three places:
- Next to "Start Discovery" in all discovery modals (fresh phase)
- Next to "Download Missing"/"Sync" after discovery (discovered phase)
- Next to "Download" on ListenBrainz cards (Discover page)
Fixed force-download toggle ID, sync progress field names
(total_tracks/matched_tracks not total/matched). All changes
purely additive — normal flows unaffected.
Discovery status polling was skipped when WebSocket was connected
(8 places), assuming socket events would push updates. But no
WebSocket events exist for discovery progress — the table stayed
on "Pending..." forever. Now always polls the status endpoint.
Pre-existing bug, not caused by recent changes.
Complete replacement of the old bottom-center stacking toast system:
Compact Toasts: Single toast at a time, bottom-right above buttons.
Pill shape with type-colored left border stripe, icon, message, and
optional "Learn more" link. Slides in, fades out after 3.5s. Click
to dismiss. New toasts replace the current one smoothly.
Notification Bell: 44px circle button next to the helper (?), with
red badge counter for unread notifications. Click opens panel.
Notification Panel: Glass popover above bell button showing history
of last 50 notifications. Each entry has type icon, message, relative
timestamp, and optional help link. Unread dot indicator. Clear All
button. Marks all as read when panel opens.
Same showToast(message, type, helpSection) signature — all 842
callers unchanged. Deduplication preserved. Updated version modal
and helper What's New.
Three issues fixed:
1. Stale discography data from a previous Artists page search was used
instead of fetching for the current library artist. Now detects
library page context and forces fresh fetch when names differ.
2. Enhanced view may not be loaded, so metadata IDs (spotify/itunes/
deezer) are fetched from the enhanced endpoint on demand.
3. Fixed undefined spotifyId reference — now uses properly scoped
metadataArtistId variable.
Falls back to name-based search when no metadata IDs are available.
Better error message directs users to Artists page as alternative.
The button called openDiscographyModal() which expected discography
data in artistsPageState — but the library page never populated it.
Now fetches discography on-demand from /api/artist/<id>/discography
when called from the library page, using the artist's DB ID and name.
Dashboard Recent Syncs and Sync page history were showing album
downloads, wishlist processing, and redownloads alongside actual
playlist syncs. Now filters to only show entries with sync_type
'playlist' (or no type for legacy entries).
New tool card shows blocked source count. "View Blacklist" opens a
modal listing all blacklisted sources with track name, filename,
username, service icon, and time ago. Each entry has a remove button
to unblock. Empty state explains how to blacklist from Source Info.
Bar was using var(--accent) which can be dark/invisible against the
modal background. Now uses bright green gradient with glow shadow.
Also thicker (8px) and queries DOM fresh each poll tick to prevent
stale references.
Three separate table cells with fixed widths replaced by one compact
cell with a flex group. Buttons are 24px each, 2px gap, fade in on
row hover. Removes ~70px of wasted horizontal space per track row.
Artist Radio and Enhance Quality buttons moved from the page header
into the artist hero section (after badges, before genres). Add to
Watchlist stays in the top-right header where it was.
Pipeline parity: redownload/start now fetches full track details from
the selected metadata source (Spotify/iTunes/Deezer) for real
track_number, disc_number, and album context. Sets explicit album
context flags so post-processing uses the standard album download path.
Stuck batch fix: active_count was 0, decremented to -1 on completion,
so batch never detected as complete. Now initializes active_count=1
and queue_index=1 since we submit the worker directly.
Button timing: Download Selected handler wired up immediately before
streaming starts, reads from window._redownloadCandidates which
updates live as results arrive. No longer blocked by slow Soulseek.
Track number: _extract_track_number_from_filename requires separator
after digits so "50 Cent" is not parsed as track 50.
Progress: real download stats from /api/downloads/status. Handles
streaming sources showing "Processing..." when no transfer found.
Step 1 and Step 2 action buttons (Cancel, Search/Download) now render
in a sticky footer outside the scrollable body — always visible
regardless of how many results are shown. Footer has backdrop blur
and top border separator matching the modal glass theme.
Step 2 of the redownload modal now streams results as each download
source responds instead of waiting for all sources to finish. Tidal/
YouTube/Qobuz columns appear instantly while Soulseek searches.
Backend: search-sources endpoint uses ThreadPoolExecutor + NDJSON
streaming — one JSON line per source as it completes.
Frontend: reads the NDJSON stream, appends columns with fade-in
animation as each source responds. Download button enables as soon
as any results arrive.
Each source gets its own column with results grouped and sorted by
confidence. Visual confidence bars, format badges, and source-specific
metadata (Soulseek username/slots). Best overall match auto-selected.
Major redesign:
- All metadata sources shown as side-by-side columns (not tabs)
- Frosted glass modal background with blur(40px) saturate(1.4)
- Album cover art in header from DB thumb_url (resolved for Plex)
- 1100px width, all elements scaled up, white text on accent buttons
Bug fixes:
- Deezer: use global singleton client, title-only fallback search,
strip version suffixes from query
- Track.__init__: added missing popularity=0 parameter
- Overlay: dedicated .redownload-overlay class avoids CSS conflicts
New track_downloads table records every download with full source data:
service type (soulseek/youtube/tidal/etc), username, remote filename,
file size, and audio quality. Recorded at all 3 post-processing
completion points.
Source Info button (ℹ) on each track in the enhanced library view shows
a popover with download provenance: service, username, original filename,
size, quality, download date. Includes "Blacklist This Source" button
that stores the real username+filename (not guessed local filenames).
Removed broken "Delete & Blacklist" option from Smart Delete since it
had no access to real source data. Blacklisting now done exclusively
from the Source Info popover where actual provenance data exists.
Added blacklist CRUD API endpoints (GET/POST/DELETE /api/library/blacklist).
Three-step redownload flow in the enhanced library view:
1. Metadata Source — searches Spotify/iTunes/Deezer simultaneously,
shows results with match scores, flags current match
2. Download Source — searches all active download sources (Soulseek,
YouTube, Tidal, etc.), shows candidates with format/bitrate/size/
confidence, flags blacklisted sources
3. Download — starts download, polls for progress, deletes old file
on success, updates DB path
Also integrates the download blacklist into the download pipeline —
_attempt_download_with_candidates now skips blacklisted sources
automatically during all downloads (wishlist, playlist sync, etc.).
New redownload button (↻) on each track row in enhanced library view.
Post-processing hook deletes old file and updates DB track path after
successful redownload.
Track delete in the enhanced library now shows three options:
- Remove from Library: DB record only (existing behavior)
- Delete File Too: DB + os.remove() the file from disk
- Delete & Blacklist: DB + file removal + add source to blacklist
New download_blacklist table stores rejected sources (username + filename)
with CRUD methods. Blacklist will be checked by the download pipeline
and the upcoming track redownload modal.
Smart delete modal styled with the same glass/dark theme as other
SoulSync modals, with color-coded destructive options.
Dashboard enrichment chips show 'Yielding' instead of 'Paused' when
workers are auto-paused during downloads. Tooltips show 'Yielding for
downloads' for full context. Distinguishes user-paused from auto-paused.
Also handles edge case where user manually resumes a worker during
downloads — adds to override set so the loop doesn't re-pause it.
Override resets when downloads finish so next download session re-pauses.
CAA art can be higher resolution (1200x1200+) but quality is
inconsistent — some releases have cellophane-wrapped photos or
low-quality scans. Spotify/iTunes/Deezer art is lower res (640x640)
but consistently clean and official.
New toggle: Settings → Post-Processing → "Use MusicBrainz Cover Art
Archive for album art" (off by default). Applies to both embedded
art and cover.jpg downloads.
New "Server Playlists" tab (default on Sync page) lets users compare
mirrored playlists against their media server and fix match issues.
- Dual-column comparison: source tracks (left) vs server tracks (right)
- Smart matching: exact title first, then fuzzy artist+title (≥75%)
- Find & Add: search library to fill missing slots at correct position
- Swap: replace matched tracks with different versions
- Remove: delete tracks from server playlists with confirmation
- Title similarity percentage badge on each match
- Disambiguation modal when multiple mirrored playlists share a name
- Album art on source tracks, server tracks, and search results
- Cross-column click-to-scroll highlighting
- Filter buttons (All/Matched/Missing/Extra) with live counts
- Escape key and backdrop click to close modals
- Mobile responsive (stacked columns under 768px)
- Works with Plex, Jellyfin, and Navidrome
New dashboard section shows recent syncs as scrolling cards with
playlist art, source badge, match percentage bar, and health color.
Click any card to open a detail modal showing every track's match
status, confidence score, album art, and download/wishlist status.
Per-track data is now cached in sync_history.track_results for all
sync paths: server-sync (playlist→media server), download missing
tracks, and wishlist processing. SyncResult carries match_details
from the sync service. Both image URLs and matched track info are
preserved for review.
Features:
- Staggered card entrance animation, delete button on hover
- Filter bar: All/Matched/Unmatched/Downloaded
- Color-coded confidence badges (green/amber/red)
- Unmatched tracks show "→ Wishlist" status
- 32px album art thumbnails per track row
- Auto-refreshes every 30 seconds on dashboard
- Falls back gracefully for old syncs without track_results
Users who keep manual searches in slskd as reminders were losing
them when SoulSync auto-cleaned at 200+ entries. New toggle in
Settings → Downloads → Soulseek: "Auto-clear slskd search history"
(on by default, preserving current behavior). When disabled, both
the hourly cleanup automation and the full cleanup step skip the
search history maintenance.
Same song from different albums was blocked from entering the
wishlist by a name+artist dedup check. Added toggle in Settings →
Library → File Organization: "Allow duplicate tracks across albums"
(on by default). When enabled, the dedup is skipped — different
album versions of the same song can coexist in the wishlist for
complete discography downloads. The UNIQUE constraint on track ID
still prevents the exact same track from being added twice.
ETH address was wrong in the support modal. Also fixed clipboard
copy failing on HTTP (Docker) — navigator.clipboard requires HTTPS.
Added textarea fallback for insecure contexts, and shows the address
in a toast as last resort if both methods fail.
With metadata-only listing, tracks aren't pre-loaded. Now shows
loading overlay while fetching tracks via /api/tidal/playlist/<id>,
then dismisses overlay and opens discovery modal. Overlay is hidden
at every exit point (error, empty, success) to prevent it from
blocking the modal.