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.
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.
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.
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.
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
The sidebar player was a poor use of vertical real estate and created the
collapsed-state layout issues. The mini player is now a fixed 360px widget
at bottom-right (above the bell/help buttons), matching the convention of
most streaming apps.
Changes:
- Removed media player from sidebar; sidebar spacer now pushes support/version
section to bottom as before
- New .mini-player-body horizontal layout: album art | track info | controls
- Added prev/next skip buttons (mini-nav-btn) with same skip logic as the
Now Playing modal; updateNpPrevNextButtons() now syncs both sets
- .media-player.idle now display:none (widget hides entirely when no track)
- Progress bar is a flush full-width line at the top of the widget
- Volume slider kept as hidden DOM element for JS compatibility; volume is
set via the Now Playing modal
- Toast container moved up to bottom:174px to stay above the mini player
- expand-hint button updated to four-corner expand icon, opens NP modal
- All volumeSlider and click-exclusion references updated for null-safety
New core/replaygain.py module uses FFmpeg's ebur128 filter (already a
project dependency) to analyze integrated loudness and true peak, then
writes ReplayGain 2.0 tags (-18 LUFS reference) to MP3 (TXXX frames),
FLAC/OGG/Opus (Vorbis comments), and M4A/MP4 (freeform atoms).
Three analysis modes in the enhanced library view:
- Per-track RG button: synchronous single-track analysis (~1-3 s)
- Album "ReplayGain" button: background job writing both track gain
and album gain (mean LUFS across all album tracks) to every file
- Bulk bar "ReplayGain" button: batch track-gain for selected tracks
read_file_tags() in tag_writer.py extended with four new optional keys
(replaygain_track_gain/_peak, replaygain_album_gain/_peak) so existing
RG values surface in the tag-preview diff view. Purely additive — no
existing endpoints or DB schema changed.
Gear button next to View All opens a sources modal letting users pick
which connected services (Spotify, Tidal, Last.fm, Deezer) contribute
artists to the Your Artists carousel. Setting saved via standard
/api/settings endpoint under discover.your_artists_sources.
- GET /api/discover/your-artists/sources returns enabled config + which
services are currently connected
- _fetch_and_match_liked_artists skips sources not in the enabled list
- Disconnected services shown dimmed and non-interactive in modal
- Saving with nothing selected blocked with error toast
- Remove z-index from .sidebar-header (fixes artist map overlap)
- Add padding-bottom to #automations-list-view (search bar overlap fix)
Add a 10vh bottom padding rule for #automations-list-view in webui/static/style.css to provide extra spacing at the bottom of the automations list and prevent content from being obscured by fixed UI elements (e.g., footer).
Add padding-bottom: 10vh to #settings-page .settings-content so the
bottom section is not obscured by the floating search bar overlay.
Closes#292 (item 3)
Replace div badges with data-url/onclick handlers by semantic <a> elements (with href, target="_blank" and rel="noopener noreferrer") for clickable artist badges, keeping non-clickable badges as divs. Update CSS to target .artist-hero-badge and unify hover/image rules instead of relying on data-url attribute, preserving visual behavior and removing pointer cursor for non-clickable divs. Also remove rendering of the server_source badge from the artist meta panel. These changes improve accessibility, security, and maintainability of badge markup and styling.
Version bump to 2.3 with rewritten What's New modal covering all
changes since v2.2. Docker publish workflow default updated.
Sidebar improvements:
- Header stays pinned at top while nav and player scroll beneath it
- Media player collapses to compact single-line when no track is
playing, expands to full size when playback starts
Fixes:
- Server playlists endpoint Plex Tag object crash (getattr fix)
- Server playlists tab auto-refreshes after download completion
- Fixed dead code syntax error in archived version notes
sync-tab-content had overflow:hidden which clipped long content like
the file import preview table and server playlist editor. Changed to
overflow-y:auto so all sync tabs scroll when content exceeds the
container height.
New sidebar page showing every download task across the app in a unified
live-updating list. Tracks from Sync, Discover, Artists, Search, and
Wishlist all appear in one place.
Features:
- Filter pills: All / Active / Queued / Completed / Failed
- Section headers grouping by status category
- Track position (3 of 19) for album/playlist batches
- Album art, artist/album metadata, batch context, error messages
- Status dots with accent glow for active, green for complete, red fail
- Clear Completed button removes terminal items from tracker
- Nav badge shows active download count from any page via WebSocket
Fixes artist [object Object] display — handles all format variations
(list of dicts, list of strings, dict, string) for artist and album
fields in the API response.
- Moved _downloadMusicVideo to top-level scope so global search can use
it (was inside enhanced search conditional that only runs on downloads page)
- Global search video cards use base64 data attributes to avoid JSON
escaping issues in onclick handlers
- Darkened thumbnail overlay during download for better progress visibility
- Larger progress ring (52px) with accent-colored glow shadow
Click any video card in Music Videos tab to download. Flow:
1. Search primary metadata source for clean artist/title
2. Fall back to YouTube title parsing if no match
3. Download video via yt-dlp (best quality MP4)
4. Save to configured Music Videos folder as Artist/Title-video.mp4
UI shows circular progress ring on the thumbnail during download,
green checkmark on completion, red X on error (clickable to retry).
Cards are non-interactive while downloading.
Backend: /api/music-video/download and /api/music-video/status endpoints
YouTube client: download_music_video() method keeps video format
New "Music Videos" pill tab alongside Spotify/Deezer/iTunes/Discogs
in both enhanced search and global search. Searches YouTube via yt-dlp
and displays results in a video card grid with 16:9 thumbnails, play
overlay, duration badge, channel name, and view count.
- Backend: /api/enhanced-search/source/youtube_videos endpoint with
search_videos() method on YouTubeClient returning YouTubeSearchResult
- Frontend: Video grid layout with responsive cards, YouTube red tab
color, proper section hiding when switching between metadata and
video tabs
- Global search: Full parity with enhanced search video rendering
- No download functionality yet — display only
New toggle in Settings > Appearance disables backdrop blur (220
instances), animations (238), transitions (961), and box shadows
(804) across the entire UI via a single body class. Significantly
reduces GPU/CPU usage on low-end devices. Default off — no change
for existing users. Applied from localStorage on load to prevent
flash.
Library page: new dropdown filter to show artists matched or unmatched
to any metadata source (Spotify, MusicBrainz, Deezer, Discogs, etc).
Select "No Discogs" to find artists needing manual Discogs matching.
Filter applied as WHERE clause on the source ID columns.
Discogs enrichment: added to valid_services whitelist, _enrichment_locks,
and _run_single_enrichment handler. The Enrich button was returning an
error when Discogs was selected from the dropdown.
Entries are now compact cards that expand on click to reveal source
details. Shows expected vs downloaded title/artist with red mismatch
highlighting. Source artist column added to DB. Streaming track IDs
extracted from the id||name filename pattern. File and ID always on
their own line to avoid edge-case misplacement.
Track original source filename, track ID, and AcoustID verification
result for every download. Helps debug wrong-file downloads from
streaming sources like Tidal. Each column migrated independently
for crash safety. Frontend shows source detail line and color-coded
AcoustID badge per entry. Button renamed to "Download History".
Visual overhaul of the API Configuration section: each service frame
is now a collapsible accordion with brand-colored dots, chevron
indicators, and smooth expand/collapse animations. Includes an
Expand All / Collapse All toggle. No functional changes — all element
IDs, save/load logic, and tab switching preserved.
New download_source column on library_history table records which source
(Soulseek, Tidal, Qobuz, HiFi, YouTube, Deezer) each track was downloaded
from. Extracted from context username during post-processing.
Frontend shows source badge alongside quality badge on each download entry.
Source breakdown bar below tabs shows per-source totals with color-coded
chips (e.g., "Soulseek: 847 | Tidal: 203"). Includes DB migration for
existing installs. Existing entries show quality only (source is NULL).
Replaced single "Change" button with per-source rows showing match
status for each provider (Spotify, Apple Music, Deezer, Discogs).
Each row has Fix/Match button that searches that specific source API,
plus clear button to remove individual matches.
- Per-source search uses _search_service (same as enrichment modal)
- Backend: added Discogs to valid providers, empty ID clears match
- Fixed provider validation to accept 'discogs' alongside others
- Clear sets DB column to NULL instead of rejecting empty string
Initial load fetches 200 tracks instead of all. "Load More" button at
bottom shows (200 of 5,000) and loads next batch on click. Backend now
returns total count alongside limited results for both album and singles
categories. Rendering logic unchanged — just operates on smaller sets.
Playlist Pipeline — single automation that runs the full playlist lifecycle:
refresh → discover → sync → download missing. Replaces 4-automation signal
chains. Phase-aware progress display (Phase 1/4, 2/4, etc.), guard function
prevents concurrent runs, fire-and-forget wishlist at end. Re-sync loop
catches newly downloaded tracks on next scheduled run.
- New action type 'playlist_pipeline' with handler, blocks endpoint config,
builder UI (playlist select, process all, skip wishlist checkboxes),
help modal, result display map, and Hub template
- Removed 3 redundant Hub templates (Release Radar, Discovery Weekly,
Playlist Auto-Sync) — all replaced by the pipeline
- Fixed sync completion polling (status is 'finished' not 'complete')
- Fixed refresh handler progress hijack (null out _automation_id)
- Fixed matched_tracks field access from sync_states result
Also in this commit:
- Wishlist badges on enhanced search and global search tracks (amber)
- Discogs added to manual enrichment modal search + artist/album dropdowns
- Profile PIN forgot recovery on profile selection dialog
cover.jpg was always written from Spotify/iTunes URL (640x640) when the
first track in an album reached _download_cover_art before MusicBrainz
lookup completed. Later tracks with MBID skipped because file existed.
Fix: when cover.jpg exists but is small (<200KB) and we now have a CAA
MBID, attempt to upgrade it with the high-res CAA version. If CAA fetch
fails, keep existing cover — no pointless overwrites.
Also adds Forgot PIN recovery to the profile selection PIN dialog,
reusing the same credential verification flow as the launch lock screen.
Backend reset endpoint now accepts profile_id parameter.
- Image proxy endpoint (/api/image-proxy) for canvas CORS — allowlisted CDNs,
browser-like UA for Deezer, 24h cache headers. Direct CORS first, proxy fallback.
- Server-side 5-min cache on all artist map endpoints with auto-invalidation
on watchlist add/remove, scan complete, and new MusicMap discoveries.
- Explorer fetches similar artists from MusicMap on-the-fly when none stored,
saves to DB for instant future visits. Validates artist names against
Spotify/iTunes API before loading map — rejects gibberish with 404.
- Genre map per-genre cap removed (was 300 backend, 400 frontend).
- Center node in Explorer uses type 'center' not 'watchlist' — no longer
misidentified as a watchlist artist.
- Error overlay auto-dismisses after 2.5s and returns to Discover page.
- Helper What's New restructured with dated sections (April 4/3/2/1, March),
trimmed from ~80 to ~38 entries, date headers styled as purple dividers.
- Version modal updated with Artist Map section and recent fixes.
Full-width section with three mode cards: Watchlist (existing), Genres
(placeholder), and Explorer (placeholder). Dark gradient background
with purple/blue ambient glow, animated dot grid overlay, and polished
card hover effects. Replaces the old small hero button.
Responsive: 3-column desktop, 1-column mobile.
Toolbar redesigned: three-section layout (back+brand / centered search
with icon / tool buttons). Brand icon in accent purple. Zoom buttons in
compact pill group. Tool labels visible on desktop, icon-only on mobile.
Added keyboard shortcuts modal (keyboard icon button): lists all 10
shortcuts with styled kbd elements.
Fixes:
- Mouse wheel zoom min matched to button zoom min (0.02)
- Right-click no longer triggers left-click info modal (button filter)
- Removed external "Open on Spotify" link from context menu
- Search results dropdown centered under search bar
- Keyboard: Escape close, +/- zoom, F fit, S search, H toggle similar
- Right-click context menu: Artist Info, View Discography, Watchlist,
Open on Spotify — glass-style with auto-close
- Filter toggle button + H key: hide/show similar artists for clean
watchlist-only overview
- Removed node dragging (caused visual desync with offscreen buffer)
- Better loading progress text
- Cleanup: keyboard handler removed on close, context menu hidden
- Metadata cache backfill: batch-lookup all node names across all sources
to fill missing IDs, images, and genres
- Source-aware navigation: View Discography passes correct source to
artist page so non-active-source artists load correctly
- Constellation hover effect: 800ms delay, fade in/out animation, dim
overlay with glowing connection lines to related artists
- Click ripple animation on node selection
- Related artists list in info modal with clickable navigation
- Rich tooltip with artist photo, name, genres on hover
- Removed node dragging (caused visual desync with offscreen buffer)
- Performance: cached constellation lookups, lighter cache query
(no raw_json), canvas.width for proper DPR overlay coverage
Visual canvas map on Discover page showing watchlist artists as large
anchor bubbles surrounded by their similar artists, sized by relevance.
Layout: golden angle spiral for watchlist nodes with push-apart guarantee,
spiral packing with spatial grid collision detection for similar artists.
Offscreen buffer rendering for smooth pan/zoom (single drawImage blit).
Features:
- 320px watchlist bubbles, similar sized 25-55% by rank/occurrence
- Search bar with instant filter + animated zoom-to-node
- Tooltip with artist photo, name, genre tags on hover
- Touch support: single finger pan, pinch zoom, tap to click
- Zoom +/- buttons and fit-to-screen with smooth 250ms animation
- Click opens artist info modal (same as Your Artists)
- Loading overlay with image count progress
- Async image loading via createImageBitmap (non-blocking)
YOUR ARTISTS (major feature):
- Aggregates liked/followed artists from Spotify, Tidal, Last.fm, Deezer
- Matches to ALL metadata sources (Spotify, iTunes, Deezer, Discogs)
- DB-first matching: library → watchlist → cache → API search (capped)
- Image backfill from Spotify API for artists missing artwork
- Carousel on Discover page with 20 random matched artists
- View All modal with search, source filters, sort, pagination
- Artist info modal: hero image, matched source badges, genres, bio,
listeners/plays from Last.fm, watchlist toggle, view discography
- Auto-refresh with loading state on first load, polls until ready
- Deduplication by normalized name across all services
DEEZER OAUTH:
- Full OAuth flow: /auth/deezer + /deezer/callback
- Settings UI on Connections tab (App ID, Secret, Redirect URI)
- Token stored encrypted, auto-included in API calls
- get_user_favorite_artists() for liked artists pool
SERVICE CLIENTS:
- Spotify: added user-follow-read scope + get_followed_artists()
- Tidal: get_favorite_artists() with V2/V1 fallback
- Last.fm: get_authenticated_username() + get_user_top_artists()
FAILED MB LOOKUPS MANAGER:
- Manage button on Cache Health modal
- Browse/filter/search all failed MusicBrainz lookups
- Search MusicBrainz directly and manually match entries
- Optimized cache health queries (11 → 4 consolidated)
- Dashboard cache stats now poll every 15s
EXPLORER IMPROVEMENTS:
- Discover button on undiscovered playlist cards
- Status badges: explored/wishlisted/downloaded/ready
- Auto-refresh during discovery via polling
- Redesigned controls: prominent Explore button, icons
BUG FIXES:
- Fix album artist splitting on collab albums (collab mode fed
album-level artists instead of per-track)
- Fix cover.jpg not moving during library reorganize (post-pass sweep)
- Fix cover.jpg missing when album detection takes fallback path
- Fix wishlist auto-processing toast spam (was firing every 2s)
- Fix media player collapsing on short viewports
- Fix watchlist rate limiting (~90% fewer API calls)
- Configurable spotify.min_api_interval setting
- Better Retry-After header extraction
- Encrypt Last.fm and Discogs credentials at rest
- Add $discnum template variable (unpadded disc number)
Added min-height and flex-shrink: 0 to prevent the sidebar flex layout
from compressing the player to zero height. Desktop 120px, tablet 100px,
phone 90px. The sidebar-spacer absorbs compression instead.
Explore button moved to its own row below playlist cards with gradient,
search icon, and "Explore Selected Playlist" label. Impossible to miss.
Mode toggle redesigned as pill segmented control with grid/list icons.
Action bar buttons get inline SVG icons (checkmark, square, heart).
Primary buttons use gradient + glow shadow treatment.
Build hint shows "Select a playlist above, then explore" → updates to
"Ready: [name]" when playlist selected. Discovery poller refreshes
cards every 5s while active. Button re-enables as "Open" after modal
launch so user can reopen closed discovery modal.
1. Discover button on undiscovered playlist cards — triggers discovery
directly from Explorer instead of redirecting to Sync page. Button
changes to "Open" to reopen modal after closing.
2. Status badges on playlist cards: checkmark (in library), heart
(wishlisted), star (fully discovered), percentage (needs discovery).
Meta line shows "N in library · M wishlisted" counts.
3. Auto-refresh: polls every 5s during active discovery to update cards.
WebSocket listener for discovery:progress events. Cards refresh when
discovery completes.
4. Explored tracking: playlists get green checkmark badge after tree is
built (session-only, resets on reload).
Backend: new get_mirrored_playlist_status_counts with fail-safe design —
core discovery counts use simple reliable queries, library/wishlist
counts are best-effort extras that won't break discovery detection.
Card layout redesigned: badges inline with playlist name, discover
button below meta text, no more absolute positioning overlaps.
New feature: Failed MusicBrainz Lookups management modal accessible
from Cache Health. Browse all failed lookups with type filter tabs,
search bar, pagination. Click any entry to search MusicBrainz and
manually match — saves MBID at 100% confidence. Clear individual
entries or bulk clear all.
Backend: 4 new endpoints — failed-mb-lookups list, mb-entry delete,
musicbrainz/search (artist/release/recording), mb-match save.
Performance: Cache health stats consolidated from 11 queries to 4
using CASE expressions. Added partial index on musicbrainz_cache for
failed lookups. Dashboard cache stats now poll every 15s instead of
single fire-and-forget fetch. Failed MB type counts cached on frontend,
only re-fetched after mutations.
Also includes: library reorganize now moves cover.jpg via post-pass
sidecar sweep, and changelog updates.
- Watchlist artist list: discogs_artist_id in API response
- Watchlist source badges: Discogs badge on artist cards
- Watchlist config modal: discogs_artist_id in SQL query, WHERE clause,
response, and linked provider section with badge
- CSS for watchlist-source-discogs and watchlist-provider-badge.discogs
- Add _extract_discogs_fields to metadata cache — handles Discogs field
names (title vs name, images array, Artist - Title format)
- Worker uses _fetch_and_cache_artist/_fetch_and_cache_album helpers
that cache raw data while returning it for enrichment
- All search/lookup methods cache results for repeat queries
- Cache browser: Discogs stat pill, source filter, clear button, badge
- Fixes albums showing as 'Unknown' and artists missing images in cache
- SpotifyClient: add _discogs lazy-load property, route _fallback to
DiscogsClient when configured (requires token, falls back to iTunes)
- web_server: _get_metadata_fallback_client returns DiscogsClient when
selected and token present
- Enhanced search: Discogs added as source tab with NDJSON streaming,
only available when token configured
- Alternate sources list includes Discogs when token is set
- Frontend: source labels, tab styling, fetch list all include Discogs
- Consistent with iTunes/Deezer pattern — same interfaces, same routing
- Add Discogs to WORKER_DEFS in worker-orbs.js so it participates
in the floating orb animation like all other enrichment workers
- Use SVG logo image instead of text
- Fix spinner and state CSS to match exact pattern of other workers
- Circular button with "dc" logo text, matching exact pattern of
AudioDB/Deezer/Spotify/iTunes/Last.fm/Genius/Tidal/Qobuz buttons
- Spinner animation when active, dimmed when paused, green when complete
- Hover tooltip showing status, current item, and progress stats
- Click to toggle pause/resume with config persistence
- WebSocket status handler updates button state in real-time
- 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
- 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)
- 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
- 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