Artists page hero section:
- Large portrait artist photo (400x480px, rounded rectangle)
- Blurred saturated background from artist image
- 2.6em bold name with text shadow
- Real service logo badges (Spotify, MusicBrainz, Deezer, iTunes,
Last.fm, Genius, Tidal, Qobuz) — matching library page
- Genre pills merged from metadata cache + Last.fm tags
- Last.fm bio with read more/show less toggle
- Last.fm listener count + playcount stats (large bold numbers)
- Backend enriches discography response with artist_info from
metadata cache + library (all service IDs, Last.fm data, genres)
Album/Single/EP cards:
- Full-bleed cover art filling entire card with gradient overlay
- Album name + year overlaid at bottom over dark gradient
- Image zoom on hover, accent glow for dynamic-glow cards
- Responsive grid (220px desktop, 170px tablet, 140px mobile)
Similar artist cards:
- Full-bleed image cards matching library artist card style
- Gradient overlay with name at bottom, aspect-ratio 0.8
- Grid-controlled sizing via existing responsive breakpoints
Genre explorer (multi-source):
- Queries all allowed sources (iTunes+Deezer always, Spotify when
authed) via _get_genre_allowed_sources() helper
- Deezer genre support: genre_id mapping from search results,
one-time backfill from stored raw_json, album-to-artist propagation
- Genre deep dive deduplicates artists across sources
- Source dots on artists/tracks in deep dive modal
- Artist clicks route through source-specific client
- Album endpoint falls back across sources when IDs don't match
- Genre explorer cached 24hr in-memory, positioned at top of
Discover page below hero slider
All changes mobile responsive with proper breakpoints.
Genre explorer and deep dive modal now combine data from all available
metadata sources (iTunes + Deezer always, Spotify when authenticated).
Artists are deduplicated by name across sources, preferring entries
with images. Source dots (green/red/purple) indicate data origin.
Deezer genre support:
- Extract genre_id from Deezer album search responses via ID-to-name
mapping table (26 Deezer genre categories)
- Extract full genre names from Deezer get_album responses
- One-time backfill updates existing cached albums from stored raw_json
- Propagate album genres to Deezer artist entities
Cross-source album routing:
- /api/discover/album endpoint uses source-specific client (iTunes or
Deezer) based on the item's source, not just the active fallback
- Spotify path falls back to active fallback when album not found
- Track clicks use album_id directly instead of name-based resolution
- resolve-cache-album adds partial match and live search fallback
Other fixes:
- Genre explorer positioned at top of Discover page (below hero)
- Genre explorer results cached 24hr in-memory for fast reload
- Related genres computed from all albums by matched artists
- Artist clicks open Artists page with discography (not library detail)
- Discovery pool genre queries restored to source-filtered (Browse by
Genre tabs stay source-isolated as designed)
Discovery pool lists (matched and failed) now have a search input
that filters tracks client-side by name, artist, or playlist.
Matched tracks get a "Rematch" button that opens the fix modal in
cache-only mode — deletes the old cache entry and saves the new
match directly to the discovery cache via /api/discovery-pool/rematch.
This works regardless of whether a mirrored playlist track exists.
Failed tracks retain the existing "Fix Match" flow unchanged.
- Overview and setup sections now list all 6 download sources
- Services table adds HiFi (no auth) and Deezer (ARL token)
- Enhanced Search documents multi-source tabs (Spotify/iTunes/Deezer)
- Download Sources table adds HiFi and Deezer rows
- Post-processing explains AcoustID skip for streaming sources and
the new artist/title verification for streaming candidates
- File Organization documents $albumtype, $disc, and all template vars
- Quality Profiles adds callout for per-source fallback toggle
- Settings credentials list adds HiFi and Deezer entries
Each streaming source (Tidal, Qobuz, HiFi, Deezer) now has an "Allow
quality fallback" checkbox in Settings. When disabled, the source only
tries the exact quality selected — if unavailable, it skips and lets
the orchestrator try the next source. Default is ON (current behavior).
Modal now uses a fixed 600px height from open — results scroll within
a dedicated area instead of growing the modal and pushing inputs up.
This eliminates the layout shift that caused accidental result clicks.
Other fixes:
- Input fields now have labels (Track, Artist)
- Overlay dismiss uses mousedown with stopPropagation to prevent
accidental close when clicking near inputs
- Reduced results from 50 to 20 for faster response
- Clean minimal design matching app style
- Mobile: full-screen modal, stacked inputs with 44px touch targets
- Search bar: stripped heavy purple chrome, minimal dark input style
- Dropdown: inline flow instead of overlay, hides page header when active
- Section labels: flat uppercase text, no bordered glass boxes
- Artist cards: full-bleed photo with gradient overlay and name at bottom
(matches library page style), flexbox wrap layout with fixed dimensions
- Album cards: discover-style dark cards in horizontal scroll on desktop,
wrap to 2-per-row on mobile
- Track rows: clean flat list, subtle hover, smaller cover art
- Source tabs: compact pills with per-source accent colors
- Renamed grid classes (enh-artists-grid, enh-albums-grid, enh-tracks-list)
to avoid collision with generic .artists-grid rule
- Mobile: downloads-main-panel min-width:0 fix for 1190px overflow,
cards use calc(50% - 8px) for 2-per-row fill, touch-friendly targets
- Tidal and Qobuz SVG logos inverted on artist detail hero badges
- New Artist Radio button: clears queue, plays random artist track, enables radio
- Play buttons on Last.fm top tracks (hover reveal, resolves from library)
- Fixed inline JS escaping with data attribute delegation
The enhanced view reorganize modal had a hardcoded default path template
instead of loading the user's saved template from settings. Now fetches
the saved album_path template from /api/settings on modal open.
- Stats page: database storage donut chart with per-table breakdown and total size
- Discover page: 5 new sections mined from metadata cache (zero API calls):
Undiscovered Albums, New In Your Genres, From Your Labels, Deep Cuts, Genre Explorer
- Genre Deep Dive modal: artists (clickable → artist page), popular tracks,
albums with download flow, related genre pills, in-library badges
- All cache queries filtered by active metadata source (Spotify/iTunes/Deezer)
- Stale cache entries (404) gracefully fall back to name+artist resolution
- Album cards show "In Library" badge, artist avatars scaled by prominence
- Stats page: full mobile layout with compact cards, charts, ranked lists
- Artist hero: stacked layout, compact image/name/badges, top tracks below
- Enhanced library: meta header/expanded header stack vertically, track table
collapses action columns into bottom sheet popover on mobile
- Automations: builder sidebar collapses, inputs go full width
- Hydrabase/Issues/Help: responsive stacking and compact layouts
- Fix grid blowout: add min-width:0 to stats grid children and overflow:hidden
Integrates play history data into the discovery algorithm:
- Listening profile: _get_listening_profile() builds user's top artists,
genres, play counts, and listening velocity from the last 30 days
- Artist genre cache: pre-built from local DB for O(1) genre lookups
- Release Radar: +10 genre affinity, +15 artist familiarity, -10 overplay
penalty. Weights rebalanced to 45% recency + 25% popularity + bonuses
- Discovery Weekly: serendipity scoring within tiers — boosts unheard
artists in preferred genres, penalizes overplayed artists
- Recent Albums: adaptive time window (21-60 days) based on listening
velocity — heavy listeners get fresher content, casual listeners more
- New "Because You Listen To" sections: personalized carousels based on
user's top 3 played artists via similar artists + genre fallback
- New endpoint: /api/discover/because-you-listen-to with artist images
- Frontend: BYLT sections with artist photo headers on discover page
- All changes gracefully fall back when no listening data exists
Full stats dashboard that polls Plex/Jellyfin/Navidrome for play
history and presents it with Chart.js visualizations:
Backend:
- ListeningStatsWorker polls active server every 30 min
- listening_history DB table with dedup, play_count/last_played on tracks
- get_play_history() and get_track_play_counts() for all 3 servers
- Pre-computed cache for all time ranges (7d/30d/12m/all) rebuilt each sync
- Single cached endpoint serves all stats data instantly
- Stats query methods: top artists/albums/tracks, timeline, genres, health
Frontend:
- New Stats nav page with glassmorphic container matching dashboard style
- Overview cards (plays, time, artists, albums, tracks) with accent hover
- Listening timeline bar chart (Chart.js)
- Genre breakdown doughnut chart with legend
- Top artists visual bubbles with profile pictures + ranked list
- Top albums and tracks ranked lists with album art
- Library health: format breakdown bar, unplayed count, enrichment coverage
- Recently played timeline with relative timestamps
- Time range pills with instant switching via cache
- Sync Now button with spinner, last synced timestamp
- Clickable artist names navigate to library artist detail
- Last.fm global listeners shown alongside personal play counts
- SoulID badges on matched artists
- Empty state when no data synced yet
- Mobile responsive layout
DB migrations: listening_history table, play_count/last_played columns,
all with idempotent CREATE IF NOT EXISTS / PRAGMA checks.
- Add get_artist_top_tracks to Last.fm client (up to 100 tracks)
- Include lastfm_listeners, lastfm_playcount, lastfm_tags, lastfm_bio,
and soul_id in artist detail API response
- New endpoint: /api/artist/<id>/lastfm-top-tracks for lazy loading
- Hero layout: image (160px) | center (name, badges, genres, bio,
listener/play stats, progress bars) | right card (scrollable top
100 tracks from Last.fm)
- Badges 36px with hover lift, bio in subtle card with Read More
toggle, Last.fm tags merged with existing genres
- Numbers formatted: 1234567 → 1.2M
- Graceful degradation: sections hidden when Last.fm data unavailable
Lossy copy now supports MP3, Opus, and AAC (M4A) codecs with a
configurable dropdown in settings. Each codec uses the appropriate
ffmpeg encoder (libmp3lame/libopus/aac) and Mutagen tag writer
(ID3/Vorbis/MP4). Quality tag, filename substitution, and Blasphemy
Mode file cleanup all work per-codec. Backward compatible — existing
configs default to MP3.
Orphan detector: add normalized tag matching that strips parentheticals
and brackets (feat. X, [FLAC 16bit], etc.) and tries first-artist-only
for comma-separated artists. Prevents false orphan flags for tracks
like "The Mountain (feat. Dennis Hopper...)" that exist in DB as
"The Mountain". All lookups remain O(1) set operations.
Orphan fix: replace auto-delete with user choice prompt. Single Fix
and Fix All both show modal asking "Move to Staging" or "Delete".
Move to Staging relocates file to import staging folder for proper
re-import with metadata matching. Fix action flows through API
endpoint → repair_worker.fix_finding → _fix_orphan_file handler.
Staging path uses docker_resolve_path for container compatibility.
SoulID worker generates deterministic soul IDs for all library entities:
- Artists: hash(name + debut_year) — searches iTunes + Deezer APIs,
verifies correct artist by matching discography against local DB
albums via MusicMatchingEngine, pools years from both sources and
picks the earliest. Falls back to hash(name) if no match found.
- Albums: hash(artist + album)
- Tracks: song ID hash(artist + track) + album ID hash(artist + album + track)
Dashboard button with trans2.png logo, rainbow spinner, hover tooltip.
Worker orb with rainbow effect. SoulSync badge on library artist cards.
DB migration adds soul_id columns with indexes to artists/albums/tracks.
Migration version flag auto-resets artist soul IDs when algorithm changes.
1. Fix filename parser pattern order — "01 - Title" now matched
before "Artist - Title", preventing track numbers being treated
as artist names (e.g., "08" no longer becomes the artist)
2. Tag priority over filename parsing — shared _read_staging_file_metadata()
helper reads title, artist, albumartist, album, track_number, disc_number
from Mutagen tags. Only falls back to filename parsing when BOTH title
AND artist tags are empty. Applied to all 3 staging scan sites.
3. Improved match scoring — rebalanced from title(0.5)+tracknum(0.5) to
title(0.45)+artist(0.15)+tracknum(0.30)+album_bonus(0.10). Files
whose album tag matches the selected album get boosted.
4. Auto-group detection — new /api/import/staging/groups endpoint groups
staging files by album+artist tags. Frontend shows "Auto-Detected
Albums" section with one-click search. Match endpoint accepts
optional file_paths filter to scope matching to a specific group.
- Use correct server request types: 'tracks', 'albums', 'artists',
'artist.albums', 'album.tracks' (were singular, caused timeouts)
- Normalize artists to strings (server may send dicts)
- Use native plugin IDs (iTunes/Spotify) instead of soul_id for
album/artist/track IDs so downstream endpoints can resolve them
- Carry soul_id and plugin_id in external_urls for routing
- Pass plugin param from frontend to server for correct client routing
(iTunes vs Deezer vs Spotify) with isdigit() fallback
- Route source=hydrabase to iTunes client for artist images
- Include external_urls in enhanced search API response
- Reduce WebSocket timeout from 15s to 8s
- Remove stale hydrabase.enabled check, use is_connected() directly
- Add hydrabase to frontend alternate source fetch list
- Normalize Hydrabase artists to strings (server may send dicts),
fixing silent crashes that prevented albums/tracks from appearing
Adds rainbow color interpolation synced to the same ~3s cycle as the
CSS rainbow-spinner animation. Applies to orb core, glow, pulse rings,
connection lines, and spark particles.
Tabs, cards, form rows, buttons, toggles, and inputs all get
tactile hover/focus/active states with lifts, glows, and accent
highlights to clarify which section the user is editing.
- Add debouncedAutoSaveSettings() to moveHybridSource and toggleHybridSource
- Skip unconfigured sources at search time with is_configured() check
- Add get_source_status() to orchestrator, include in settings API response
- Auto-disable unconfigured sources in UI on settings load
Failed tracks had candidates from the initial search but no way to
retry with a different source. Now clickable like not_found tracks
to open the manual match modal.
- Remove redundant enable checkbox — fallback dropdown is the enable
- Hydrabase option only appears in dropdown when connected
- Connect/disconnect dynamically adds/removes dropdown option
- _is_hydrabase_active checks fallback_source == hydrabase (not config toggle)
- Fallback client returns hydrabase_client when selected, iTunes if disconnected
- Auto-reconnect respects fallback selection for dev_mode handling
- hydrabase added to settings save service list for persistence
- Status shows green Connected on page load when auto-connected
Add Hydrabase section to Settings → Connections with enable toggle,
WebSocket URL, API key, auto-connect, and connect/disconnect button.
_is_hydrabase_active() now checks hydrabase.enabled config in addition
to dev_mode — either path activates it. Default disabled, zero change
for existing users. Dev admin page stays behind dev mode password.
Non-admin: 3-tab layout (Music Services | Server | Scrobbling).
Admin: just ListenBrainz, no tabs (unchanged).
Server tab auto-detects active server (Plex/Jellyfin) and shows
library name dropdowns instead of raw ID inputs. Modal has max-height
with scroll, tab bar with accent underline indicator.
Adds Tidal per-profile OAuth with token storage on profile row.
Auth initiation stores profile_id in PKCE state, callback detects
it and stores encrypted tokens per-profile instead of globally.
Personal settings modal now shows Spotify, Tidal, Server Library,
and ListenBrainz sections for non-admin profiles. Admin sees only
ListenBrainz (unchanged). Server library selection wired into
playlist sync via _apply_profile_library.
Full per-profile support: Spotify (credentials + OAuth + playlists),
Tidal (OAuth + token storage), server library (Plex/Jellyfin/Navidrome),
ListenBrainz (existing). All backwards compatible — upgrading users
see zero change.
New pipelines: Startup Recovery (3 automations), Import Pipeline (3),
Weekly Deep Clean (5), Beatport Fresh (1). Fix deploy calling
nonexistent loadAutomationsPage — now calls loadAutomations so the
list updates immediately after deployment.