1. Artist name normalization fallback:
- If "Mikael Åkerfeldt" finds no results, also try "Mikael Akerfeldt"
- Helps with artists whose Qobuz name lacks accents
2. Increased track search limits:
- Playlist artists: search up to 30 tracks (was 9)
- API search: fetch 100-500 results to find enough matches
- Needed because search results mix artist's tracks with appearances
This should fix:
- Missing Opeth tracks (all 9 found were already in playlist)
- Mikael Åkerfeldt not found (Qobuz has "Mikael Akerfeldt")
Ensures genre names are consistent (English) for all users regardless
of their Qobuz registration country. This affects the genre filter
in Home and Favorites views.
Qobuz returns localized genre names based on user's registered address,
not app language. This caused genre filtering to fail when user sees
"América Latina" instead of "Latin".
Solution: Pass lang=en explicitly when fetching artist albums for genre
verification. This ensures consistent English genre names regardless of
user's registration country.
Also simplified the incompatible genres list since we only need English now.
1. Add localized genre names to blocklist:
- Spanish: "América latina", "música latina", "infantil", etc.
- French: "variété française", "comptines", etc.
- German: "Kindermusik", etc.
2. Upgrade step 4a logging to INFO level to debug missing Opeth tracks
- Shows which playlist artists are being searched
- Shows how many tracks found vs skipped (already in playlist)
Pass client reference to search_artist_tracks_by_qobuz_id instead of
acquiring a new lock, which caused deadlock when step 4c already held
the qobuz_client lock.
Two improvements:
1. Accent normalization in artist validation
- Now "Mikael Åkerfeldt" matches "Mikael Akerfeldt" in Qobuz
- Uses existing normalize_name() function for comparison
2. Qobuz similar artists as fallback (step 4c)
- When pool has <20 tracks after MusicBrainz relationships
- Fetches similar artists from Qobuz API for each playlist artist
- These artists are guaranteed to exist in Qobuz
- Still applies genre compatibility filter
This ensures playlists with few MusicBrainz relationships (like solo
artists) still get meaningful suggestions from Qobuz's own similarity
data.
Add more genres and keywords to filter:
- Electronic/Dance: trance, techno, house, EDM, dubstep, etc.
- Spoken word: audiobook, hörbuch, podcast, meditation, ASMR
- Country: country, bluegrass, americana
- New age: healing, spa, yoga, mindfulness
- Additional title keywords: "gelesen von", "read by", "trance mix"
This prevents suggestions like:
- Tony Parsons (trance DJ) in Iron Maiden playlist
- Tony Parsons (German audiobook narrator) in metal playlist
When validating if an artist exists in Qobuz, now also checks their
album genres against a blocklist. This prevents false matches like:
- "Martin Lopez" (bachata singer) matching when we want the drummer
- "Martin Mendez" (merengue) matching when we want the bassist
Incompatible genres: bachata, merengue, reggaeton, salsa, cumbia,
k-pop, j-pop, gospel, children's music, etc.
The validation fetches up to 5 albums and checks both genre metadata
and album titles for incompatible keywords.
Add pre-validation step that checks if an artist has a dedicated Qobuz
page before searching for their tracks. This prevents false matches for:
- Session musicians who don't have their own catalog (e.g., Martin Lopez
the drummer returning tracks from unrelated Martin Lopez artists)
- Names that exist in MusicBrainz but not in Qobuz's artist database
The validation searches Qobuz artists for exact name match with
albums_count > 0. Also matches tracks by Qobuz artist ID when possible
(more precise than name matching).
Also fix outdated test assertions for config defaults.
- For 2-word names: require EXACT same words (no extra words allowed)
- 'Martin Lopez' vs 'Tomas Martin Lopez' → REJECT (different word count)
- 'George Harrison' vs 'Harrison, George' → ACCEPT (same words, different order)
- Add normalize_name() to handle accents (Martín -> Martin)
- Require ALL words of shorter name to match longer name
- Remove permissive 'contains' checks that allowed partial matches
- For 2-3 word names, require 66%+ match ratio
- Prevents 'Martín Méndez' from matching 'Tomas Martin Lopez'
- Increase pool sizes (INITIAL_POOL 18->30, EXPANDED_POOL 60->100, MAX_POOL 120->200)
- Add auto-refresh when available tracks fall below threshold (MIN_AVAILABLE_THRESHOLD=12)
- Add artist identity validation to filter incompatible genres (prevents merengue in metal playlists)
- Log when artists are skipped due to incompatible disambiguation
- Increase backend defaults (max_artists 20->30, tracks_per_artist 5->6, max_pool_size 100->150)
- Hide button only shows when content filtering is enabled
- Use custom blind-eye.svg icon
- Remove border for cleaner look
- Banner also conditional on filtering being enabled
cc
- Replaced prominent blacklist button with smaller tertiary button
- Button now opens dropdown with descriptive help text:
"Hide this artist - Don't show in searches, playlists, discover, etc."
- Updated terminology from "blacklist" to "hide/show"
- Updated banner and toast messages for consistency
- Uses EyeOff icon instead of Ban for softer visual
cc
- PlaylistModal: convert $derived with get(t) to regular function
- LocalLibraryTagEditorModal: rename 't' to 'track' in map to avoid
shadowing the t store
cc
vite-plugin-svelte CSS extraction fails when $t() is used inside $derived().
This causes "[postcss] Unknown word <script" errors that appear to be cache
issues but are actually a known Svelte 5 / vite-plugin-svelte incompatibility.
Files fixed:
- DownloadButton.svelte: use titleKey pattern with function resolver
- DACSetupWizard.svelte: use labelKey pattern resolved in WizardStepper
- RemoteControlSetupGuide.svelte: use labelKey pattern
- WizardStepper.svelte: support optional labelKey with $t() in template
Also added download.* translation keys to en.json and es.json.
cc
- Add backend endpoint for /discover/playlists API
- Add PlaylistTagFilter dropdown component for filtering by genre
- Add QobuzPlaylistCard with CSS blur background effect (color-thief style)
- Add HorizontalScrollRow header snippet for custom headers with scroll controls
- Add session cache store for caching API responses
- Add i18n keys for playlist filter and discover section
cc
Old database schemas created before locale support was added
caused "no such column: locale" errors when fetching artists.
The migration detects this and drops the old table so it gets
recreated with the correct schema.
Fixes Top Artists section not appearing in Home view.
Security improvements based on code review:
1. Credential Encryption (Critical)
- Replace XOR obfuscation with AES-256-GCM encryption
- Derive key from machine ID (tied to specific machine)
- Add migration support for legacy XOR format
- Automatic re-encryption on first load of legacy credentials
2. HTTPS Default (Medium)
- Change default `secure` setting to `true` for new installs
- Existing users keep their current setting
3. Configurable CORS Origins (High)
- Replace hardcoded domain whitelist with SQLite-backed storage
- Default origins: vicrodh.github.io, control.qbz.lol
- Add commands: get/add/remove origins, restore defaults
- Origins can be customized for third-party clients
- Server restarts automatically when origins change
New dependencies:
- aes-gcm: AES-256-GCM authenticated encryption
- sha2: SHA-256 for key derivation
- Move Remote Control section to end of Settings (after Updates)
- Make section collapsible, closed by default
- Keep Experimental badge visible when collapsed
- Update nav sidebar order to reflect new position
- Remove unused .section-title-row CSS selectors
Add /api/events endpoint using Server-Sent Events (SSE) as a more
efficient alternative to WebSocket for pushing playback state updates
to PWA clients. SSE provides automatic reconnection and is simpler
to implement on the client side.
- Add tokio-stream dependency with sync feature for BroadcastStream
- Implement sse_handler with 15s keep-alive ping
- Keep /api/ws for backwards compatibility
- Add shuffle/repeat fields to PlaybackEvent struct
- Emit queue:state event when shuffle/repeat changes via API
- Add queue event listener in desktop frontend
- Include shuffle/repeat in WebSocket broadcasts
- PWA updates shuffle/repeat from WebSocket messages
This enables bidirectional sync: changes from PWA update desktop UI,
and changes from desktop update PWA via WebSocket.
Update the local volume state when receiving playback events from the
backend. This ensures the volume slider reflects changes made via
remote control or other external sources.
The playback event listener was only started when currentTrack existed,
creating a chicken-and-egg problem: external playback (from PWA) couldn't
set currentTrack because the listener wasn't active to receive the event.
Now the listener is always active, allowing remote control to update the
player state even when nothing is playing locally.
Call notifyListeners() right after setting currentTrack when track
changes from remote control. Also update isPlaying and currentTime
from the event to ensure player UI reflects correct state.
Added debug logging for queue track mismatches.
When a track change is detected from an external source (e.g., PWA
remote control), also sync the full queue state. This fixes the issue
where desktop UI showed empty queue after PWA started playback.
Add emit_playback_update() to notify both desktop UI (Tauri event) and
PWA (WebSocket) when playback state changes from remote commands.
Previously, desktop UI would only update on the next polling cycle (up
to 5 seconds), making the remote control feel unresponsive.
- GET /api/favorites - Get user favorites (albums, tracks, artists)
- POST /api/favorites/add - Add item to favorites
- POST /api/favorites/remove - Remove item from favorites
- POST /api/album/play - Play album (clears queue and adds all tracks)
- Add RemoteControlSetupGuide component with step-by-step instructions
- Include certificate installation guides for iOS, macOS, Windows, Android
- Mark remote control section as "Experimental" with red badge
- Add Setup Guide button to open the wizard
- Add translations for wizard (en/es)
- POST /api/queue/shuffle - set shuffle mode
- POST /api/queue/repeat - set repeat mode (off/all/one)
- Return current shuffle/repeat state in response
When tracks are changed externally (via PWA remote control), the desktop
player now detects the track_id mismatch and fetches the new track info
from the backend queue. This fixes bidirectional sync where skipping
tracks on the PWA wasn't reflected in the QBZ desktop UI.
Features:
- Axum REST API server for remote control
- WebSocket real-time playback updates
- Token-based authentication with QR pairing
- LAN-only security middleware
- Settings UI for enabling/configuring remote control
Features:
- Redesigned welcome step with comprehensive disclaimer
- Added DAC info extension to all wizard steps
- Fixed stepper navigation when going backwards
- Added custom gandalf tooltip with nerdy text
- Applied ChatGPT micro-adjustments (warnings, hints)
- Full i18n support (EN/ES/FR/DE)
Step 1 (Precheck):
- Add inactive warning: "If any service is inactive, stop here..."
Step 2 (Other distro):
- Change to "Ensure these packages (or equivalents) are installed"
Step 3 (Detect DAC):
- Add input warning: "This must match exactly..."
Step 4 (Backup):
- Update hint about user responsibility for keeping backup
Step 6 (Pulse):
- Change "browsers" to "browsers and apps"
Step 7 (WirePlumber):
- Add rules note: "These rules apply only to the detected DAC"
All translations updated for EN/ES/FR/DE
- Reference "The Hi-Fi Wizard/Mage" for the gandalf icon
- Add "if you haven't already" to acknowledge power users
- Keep it playful without being patronizing
- Match button height to dropdown (40px)
- Increase gandalf icon size (24px) for better visibility
- Add custom hover tooltip with larger gandalf icon
- Tooltip shows title and description explaining the wizard
- Add tooltip translations for EN/ES/FR/DE
- Add DAC short name to pulse config filename for multi-DAC support
- Add getCreatedConfigPaths() for dynamic file path generation
- Show configured DAC name/description in done step
- Add configuredFor translation to all locales (EN/ES/FR/DE)
Step 5 (PipeWire config):
- Add sample rate checkboxes (44.1, 48, 88.2, 96, 176.4, 192 kHz)
- Pre-fill from DAC query if available, otherwise empty for manual selection
- Show "detected" badge for rates found by query
- Dynamic command updates based on selection
Step 7 (WirePlumber config):
- Use DAC short name in filename: 99-qbz-dac-{name}.conf
- Extract name from node.name or description
- Include DAC description in config file comment
- Use selected sample rates instead of hardcoded values
This enables proper multi-DAC separation - each DAC gets its own config file.
- Welcome step: checkbox to confirm understanding of system changes
- Backup step: checkbox to confirm backup was created
- Start/Next buttons disabled until checkboxes are checked
- Add translations for checkboxes (EN/ES/FR/DE)
- Clear default input value (use empty string)
- Add ALSA hardware capabilities query via:
- /proc/asound/cardX/stream0 for USB devices
- aplay --dump-hw-params for detailed hw params
- Parse rate ranges [min max] and expand to common rates
- Now shows actual DAC capabilities, not just current config
- Remove redundant subtitle text from DAC wizard welcome (all languages)
- Fix collapsed sidebar settings menu to expand upward instead of down
- Menu now opens to the right and grows upward, avoiding player overlap
- Add complete French (FR) translations for dacWizard section
- Add complete German (DE) translations for dacWizard section
- Add qobuz-player (TUI) to bit-perfect app selector in all 4 languages