Both NOT NULL and profile v2 migrations now include album_deezer_id and
artist_deezer_id columns, use safe shared-column data copy instead of
SELECT *, and add album_deezer_id to UNIQUE constraints.
Users can now choose between iTunes/Apple Music and Deezer as their free
metadata source in Settings. Spotify always takes priority when authenticated;
the fallback handles all lookups when it's not.
Core changes:
- DeezerClient: full metadata interface (search, albums, artists, tracks)
matching iTunesClient's API surface with identical dataclass return types
- SpotifyClient: configurable _fallback property switches between iTunes/Deezer
based on live config reads (no restart needed)
- MetadataService, web_server, watchlist_scanner, api/search, repair_worker,
seasonal_discovery, personalized_playlists: all direct iTunesClient imports
replaced with fallback-aware helpers
Database:
- deezer_artist_id on watchlist_artists and similar_artists tables
- deezer_track_id/album_id/artist_id on discovery_pool and discovery_cache
- Full CRUD for Deezer IDs: add, read, update, backfill, metadata enrichment
- Watchlist duplicate detection by artist name prevents re-adding across sources
- SimilarArtist dataclass and all query/insert methods handle Deezer columns
Bug fixes found during review:
- Similar artist backfill was writing Deezer IDs into iTunes columns
- Discover hero was storing resolved Deezer IDs in wrong column
- Status cache not invalidating on settings save (source name lag)
- Watchlist add allowing duplicates when switching metadata sources
New library_history table logs every completed download and every new
track imported from Plex/Jellyfin/Navidrome. A "History" button next
to "Recent Activity" on the dashboard opens a modal with Downloads and
Server Imports tabs, album art thumbnails, quality/source badges, and
pagination.
- Watchlist nullable migration now preserves profile_id column and composite
UNIQUE constraints when rebuilding the table
- Profile support migration always repairs missing profile_id columns on all
tables, even if the migration metadata key already exists (handles tables
rebuilt by other migrations)
- Confirm dialog z-index raised to 100000 to appear above profile picker
overlay (99999), fixing invisible delete confirmation
The migration to make spotify_artist_id nullable was using fragile string
matching against CREATE TABLE SQL, which silently failed for some databases.
Now uses PRAGMA table_info to reliably detect the NOT NULL flag.
Automatically mirrors every parsed playlist (Spotify, Tidal, YouTube, Beatport) to a local database so they're always accessible — even if a service subscription lapses or the browser closes.
- New "Mirrored" tab on the Sync page with source-branded cards showing discovery/download status
- Auto-mirrors on successful parse (upsert — re-parsing updates the existing mirror, no duplicates)
- Click any mirrored playlist to browse its full track list, then run it through the discovery pipeline
- Cards dynamically reflect live state: Discovering → Discovered → Downloading → Downloaded
- Download modal rehydrates after page refresh — click a "Downloading..." card to resume viewing progress
- All phase transitions (start, complete, cancel, error, modal close) keep card and backend state in sync
- Profile-scoped via profile_id, consistent with other features
Allow multiple users to share a single SoulSync instance with isolated personal data. Each profile gets its own watchlist, wishlist, discovery pool, similar artists, and bubble snapshots — while sharing the same music library, database, and service credentials.
- Netflix-style profile picker on startup when multiple profiles exist
- Optional PIN protection per profile; admin PIN required when >1 profiles
- Admin-only profile management (create, edit, rename, delete)
- Profile avatar images via URL with colored-initial fallback
- Zero-downtime SQLite migration — all existing data maps to auto-created
admin profile
- Single-user installs see no changes — profile system is invisible until
a second profile is created
- WebSocket count emitters scoped to profile rooms (watchlist/wishlist)
- Background scanners (watchlist, wishlist, discovery) iterate all profiles
Incremental database updates now detect when artists or albums have been removed from your media server (Plex, Jellyfin, or Navidrome) and automatically clean them up from SoulSync's database. Previously, deleted content would persist as ghost entries until you ran a full refresh. Removal counts are reported in the scan results. Includes safety checks to prevent accidental mass deletion if the server is unreachable or returns incomplete data.
- matching_engine.py: Add 'single edit' and 'album edit' tokens and clarify radio edit comment so edit/cut variants are recognized as different cuts rather than being silently normalized away.
- database/music_database.py: Fix SQL param ordering by appending server_source to params; add a pre-step to strip "(with ...)" / "[with ...]" only when used inside brackets (so titles like "Stay With Me" are preserved); stop removing edit/version tokens in the generic cleanup and document that radio/single/album edits are treated as distinct by the similarity scorer to avoid incorrect matches.
- web_server.py: Increase DB match confidence threshold from 0.70 to 0.80 and update the runtime check accordingly.
These changes prevent edit/cut variants from being conflated with original recordings, improve title normalization for "with" featuring syntax in brackets, and fix a params ordering bug and a too-low match threshold.