- Create FavoritesEditModal with preset icon grid
- Add custom image upload button
- Add tab reorder UI with up/down buttons
- Integrate edit button in Favorites page header (top-right)
- Add i18n keys for EN and ES
- Wire modal save handler to backend
cc
- Create favorites_preferences.rs with FavoritesPreferences struct
- Add database table (custom_icon_path, custom_icon_preset, tab_order)
- Implement State wrapper and Tauri commands
- Register state and commands in lib.rs
- Default preset: heart, default order: tracks/albums/artists/playlists
cc
- Replace native title with custom tooltip similar to DAC/EXC badges
- Add hover state and styled tooltip with context info
- Add debug logs to investigate onClick callback issue
cp
- Added $effect to reload preferences each time SettingsView is visible
- Fixes autoplay mode not persisting when navigating away and back
- Settings view doesn't unmount, so onMount only runs once per session
cp
- Implemented custom tooltip for StackIcon (similar to AudioOutputBadges)
- Shows 'Playing from', context name, and 'Click to navigate' hint
- Added onContextClick={handleContextNavigation} to ExpandedPlayer
- Fixed onClick prop not being passed, enabling navigation
cp
Added translations for:
- Autoplay behavior setting
- Continue within source / Play only selected track options
- Show context icon setting and tooltip
Both English and Spanish translations added.
Changed StackIcon tooltip to use native browser title (matches QualityBadge style).
Removed arrow indicator from context icon (cursor change is sufficient).
cp
Problem:
- Users updating from previous versions would crash on startup
- Table created with new column caused "no column named show_context_icon" error
- Existing users had old schema without the column
Solution:
- Step 1: Create table with old schema (backwards compatible)
- Step 2: Check if show_context_icon column exists using pragma_table_info
- Step 3: Add column ONLY if it doesn't exist (safe migration)
- Step 4: Insert default row
This ensures:
✅ New installs work (table created fresh)
✅ Existing users migrate seamlessly (column added, settings preserved)
✅ No data loss
✅ Idempotent (can run multiple times safely)
Tested scenarios:
- Fresh install (no DB) → creates table, adds column, inserts defaults
Result: id=1, autoplay_mode='continue', show_context_icon=1
- Existing DB without column → adds column, preserves user settings
Result: id=1, autoplay_mode='track_only' (preserved), show_context_icon=1
- Existing DB with column → no changes (idempotent)
Migration logs added for debugging.
cp
StackIcon Component:
- Now accepts onClick prop
- Changed from div to button element
- Added arrow (→) that appears on hover
- Tooltip text changed to "Click to go to: {source}"
- Visual feedback: hover background + active scale
Navigation Logic (+page.svelte):
- Added handleContextNavigation function
- Switch based on context_type:
- album → handleAlbumClick
- playlist → navigate + set selectedPlaylistId
- artist_top → handleArtistClick
- favorites → navigate to favorites
- home_list → navigate to home
- search → navigate to search
- Import getCurrentContext from playbackContextStore
UI Integration:
- ExpandedPlayer: Added onContextClick prop, passed to StackIcon
- FocusMode: Added onContextClick prop, passed to StackIcon
- Both receive handleContextNavigation callback
Phase 3.7 COMPLETE ✅
cp
Frontend:
- Add show_context_icon to PlaybackPreferences interface
- Add setShowContextIcon function to store
- StackIcon now subscribes to preferences and checks show_context_icon
- Icon hidden when setting is false (context still tracked)
UI (Settings → Playback):
- Add toggle: "Show track playing context in player"
- Tooltip with bilingual explanation (EN/ES)
- Default: enabled (true)
- Persists across restarts
Context tracking ALWAYS active regardless of icon visibility.
Phase 3.6 COMPLETE ✅
cp
Context creation:
- Creates 'home_list' context for Continue Listening section
- Uses 'continue_listening' as ID and 'Continue Listening' as label
- Tracks user's listening history in a context
- Stack icon will show "Playing from: Continue Listening"
Home view now fully integrated with context system.
cp
Context creation:
- Creates 'search' context when playing track from results
- Uses search query as ID and 'Search: {query}' as label
- Applies to both All tab (first 6) and Tracks tab (full list)
- Stack icon will show "Playing from: Search · {query}"
Search view now fully integrated with context system.
cp
Context creation:
- Creates 'favorites' context when playing track
- Uses 'favorites' as ID and 'Favorites' as label
- Uses filteredTracks for context (respects search/filter)
- Stack icon will show "Playing from: Favorites"
Favorites view now fully integrated with context system.
cp
Context creation:
- Creates 'artist_top' context when playing from Top Tracks
- Uses artist ID and name
- Passes track index from UI loop
- Stack icon will show "Playing from: Artist · {name}"
Artist view now fully integrated with context system.
cp
Context creation:
- Creates 'playlist' context when playing track
- Uses playlist ID and name
- Filters out local tracks (Qobuz tracks only in context)
- Finds track index in Qobuz tracks list
- Stack icon will show "Playing from: Playlist · {name}"
Playlist view now fully integrated with context system.
cp
Added createContextAndPlayDisplayTrack helper that:
- Creates playback context with provided type/id/label/tracks
- Calls handleDisplayTrackPlay to start playback
- Will be used by Playlist, Artist, Favorites, Home views
Next: Implement context creation in each view component internally
(following AlbumDetailView pattern but keeping logic in views)
cp
Stack icon now appears in artist/album area across all players:
- FocusMode: icon before artist (size 12px)
- MiniPlayer: icon at start of artist-album row (size 12px)
- Consistent sizing: 12px (compact players), 16px (expanded player)
- Visual hierarchy: Title → [icon] Artist · Album
All 4 players updated and consistent.
cp
Icon now appears before artist/album metadata instead of title.
Changes:
- NowPlayingBar: icon in song-meta row (size 12px)
- ExpandedPlayer: icon in new artist-album-row with separator (size 16px)
- Removed song-title-row wrapper (not needed)
- Icon placement: [icon] Artist · Album
Remaining: FocusMode and MiniPlayer (next commit)
cp
CRITICAL LOGIC FIX:
The autoplay setting was misunderstood. Corrected behavior:
BEFORE (wrong):
- Setting controls whether context is created
- 'continue' = create context
- 'track_only' = clear context
AFTER (correct):
- Context is ALWAYS created when playing from a source (album/playlist)
- Stack icon ALWAYS appears
- Setting only affects what happens at END of track:
- 'continue' = play next track in context
- 'track_only' = stop after current track
Why this makes sense:
- You're playing from an album = that's the context
- Stack icon shows the origin (informative)
- Setting controls continuation behavior (not context existence)
Also:
- Added logging to handleAutoplayModeChange
- Will investigate why setting doesn't persist
cp
UI Fix:
- Add :has(.radio-group) rule to allow flexible height
- Set align-items: flex-start for radio group rows
- Prevents radio buttons from being squashed
Logging Enhancement:
- Log autoplay_mode preference value
- Log track index and total tracks
- Log when no album tracks found
- Will help diagnose why context isn't being created
cp
CRITICAL FIX: Context was being cleared instead of created.
Root cause:
- AlbumDetailView had internal handlers but used parent's onTrackPlay
- Parent's handleAlbumTrackPlay had no context logic
- Result: context always cleared (default behavior)
Solution:
- Moved context creation logic to handleAlbumTrackPlay in +page.svelte
- Checks autoplay_mode preference
- Creates context if 'continue', clears if 'track_only'
- Removed duplicate logic from AlbumDetailView (was never executed)
Now when clicking a track:
1. handleAlbumTrackPlay checks preference
2. Creates/clears context accordingly
3. Stack icon should appear/disappear correctly
Test with default setting ('continue'):
- Click any track → should see context creation logs
- Stack icon should appear immediately
cp
- Replace onMount/onDestroy with $effect rune for better reactivity
- Add console logging to trace context changes
- Log when setPlaybackContext/clearPlaybackContext are called
- Log when StackIcon receives context updates
This will help debug why stack icon might not appear immediately.
To test:
1. Open browser console
2. Click a track in an album
3. Check logs for '[PlaybackContext]' and '[StackIcon]'
4. Verify context is being set and icon is receiving updates
cp
- Call initPlaybackContextStore() on app mount
- Call initPlaybackPreferences() on app mount
- Load backend state into frontend stores
- Phase 3 backend + UI complete
- Wiring for track actions pending (will be done per-page)
cp
- Add radio buttons: Continue within source / Play only selected track
- Load preference on settings mount
- Save changes immediately via handleAutoplayModeChange
- Styled with hover states and proper spacing
cp
- Add 'Play track only' option (clears context, single track)
- Add 'Play from here' option (sets context from current position)
- Update TrackMenu component with new actions
- Update TrackRow to pass new menu actions
- Backend integration ready for Phase 3 (settings + wiring)
cp
- Create StackIcon component with reactive context subscription
- Display Layers icon before track title when context exists
- Show tooltip with origin info on hover
- Add to: NowPlayingBar, ExpandedPlayer, FocusMode, MiniPlayer
- Icon hidden when no playback context (single track)
- Responsive sizing per player (14px-18px)
cp
- Add PlaybackContext module with ContextManager
- Context tracks semantic origin (album, playlist, artist top, etc.)
- Separate from queue - defines what 'next' means
- Backend: Rust struct with track IDs and position tracking
- Frontend: Svelte store with reactive subscriptions
- Tauri commands for context CRUD operations
- Context is nullable (single track playback has no context)
cp
ALSA Direct is currently non-functional due to CPAL limitations:
- CPAL cannot enumerate or open hw:X,Y devices required for bit-perfect playback
- Only named devices (front:, iec958:, sysdefault:) work, but they all use ALSA plugins with resampling
- User's DAC cannot achieve bit-perfect playback (stuck at 44.1kHz regardless of track sample rate)
Changes:
- Filter out ALSA Direct from backend selector dropdown
- Auto-switch users from ALSA Direct to Auto on settings load
- Add comments indicating this is temporary until alsa-rs rewrite
This prevents user frustration with a broken backend while we evaluate implementing
direct ALSA support via alsa-rs (bypassing CPAL entirely).
Critical Fix:
- CPAL only enumerates named ALSA devices (front:, sysdefault:, etc.)
- CPAL does NOT enumerate hw:X,Y or plughw:X,Y devices
- These must be created manually from aplay -l card list
Solution:
- Parse aplay -l to get card numbers and descriptions
- Manually create hw:X,0 and plughw:X,0 for each card
- Assume max_sample_rate: 384000 (devices are capable)
- Description: "Cambridge Audio USB Audio 2.0 (Direct Hardware - Bit-perfect)"
For user's system (based on aplay -l):
- hw:0,0 → CS201 (Direct Hardware - Bit-perfect)
- hw:2,0 → HDA NVidia (Direct Hardware - Bit-perfect)
- hw:3,0 → sof-hda-dsp (Direct Hardware - Bit-perfect)
- hw:4,0 → Cambridge Audio USB Audio 2.0 (Direct Hardware - Bit-perfect) ← USER NEEDS THIS
- Plus plughw: variants for each
These devices will now appear in the dropdown and can be selected for
true bit-perfect playback without resampling.
UX Problem:
- 4 devices all showed "Cambridge Audio USB Audio 2.0"
- Selecting one would select all (Map key collision)
- User couldn't distinguish between hw:4,0, front:CARD=C20, iec958:CARD=C20
Solution:
- Detect duplicate display names in deviceOptions
- Append [device_id] to duplicates for uniqueness
- Example: "Cambridge Audio USB Audio 2.0 (Direct Hardware - Bit-perfect) [hw:4,0]"
Now User Sees:
- Cambridge Audio USB Audio 2.0 (Direct Hardware - Bit-perfect) [hw:4,0] ← TRUE bit-perfect
- Cambridge Audio USB Audio 2.0 [front:CARD=C20,DEV=0] ← Front output with plugins
- Cambridge Audio USB Audio 2.0 [iec958:CARD=C20,DEV=0] ← S/PDIF with plugins
- Cambridge Audio USB Audio 2.0 [sysdefault:CARD=C20] ← System default with dmix
Each option is now unique and selectable. The hw:4,0 option is clearly
marked as "Direct Hardware - Bit-perfect" for audiophiles.
Critical Fix - Use CPAL Device Names:
- CPAL enumerates devices → these IDs actually work for playback
- aplay -L provides friendly descriptions → update CPAL device descriptions
- Stop creating hw:X,Y devices that CPAL doesn't recognize
- Fixes: "Device not found → fallback to default → audio from laptop"
Flow:
1. enumerate_via_cpal() returns devices CPAL can actually use
2. enumerate_via_aplay() calls #1, then parses aplay -L for descriptions
3. Updates device.description with friendly names from aplay -L
4. Frontend shows: "Cambridge Audio USB Audio 2.0" not "front:CARD=C20,DEV=0"
Loading Overlay:
- Full-screen semi-transparent overlay during device enumeration
- Animated spinner (Loader2 icon)
- "Loading audio devices... / Parsing hardware information"
- Prevents user thinking app is frozen during 4-5 second aplay parse
For Bit-Perfect Playback:
- User should select devices like front:CARD=C20,DEV=0 (CPAL knows these)
- These are more direct than sysdefault:CARD= (which has dmix/plugins)
- Not as direct as hw:4,0 but CPAL can actually find them
PipeWire and PulseAudio backends unchanged - still working as before.
Critical Fix:
- Changed hw: devices from hw:CARD=C20,DEV=0 to hw:4,0
- CPAL only accepts numeric format (hw:X,Y), not named format
- Parse aplay -l to get actual card numbers and device numbers
Before: hw:CARD=C20,DEV=0 → Device not found → fallback to default
After: hw:4,0 → CPAL finds device → bit-perfect playback works
Example from user's system:
- aplay -l shows: card 4: C20 [Cambridge Audio USB Audio 2.0], device 0
- We now create: hw:4,0 (not hw:CARD=C20,DEV=0)
- Description still shows: "Cambridge Audio USB Audio 2.0 (Direct Hardware - Bit-perfect)"
This fixes the issue where selecting the DAC device would play through
laptop speakers instead because CPAL couldn't find the device.
ALSA Device Filtering (45 → ~12 devices):
- Aggressively filter to show only useful devices for audiophiles
- Keep: default, sysdefault:CARD=, front:CARD=, hdmi:CARD=, iec958:CARD=
- Blacklist: surround* variants, usbstream:, pipewire, pulse, bare sysdefault
- Reduces 45 listed devices to ~12 actually useful ones
Add hw:CARD=XXX,DEV=0 Devices for Bit-Perfect Playback:
- Auto-generate hw: devices for each detected card
- hw: = direct hardware access without ALSA plugins (true bit-perfect)
- sysdefault:CARD= has dmix/resampling plugins (NOT bit-perfect)
- Description shows "(Direct Hardware - Bit-perfect)" to distinguish
- This fixes user's issue: DAC was stuck at 44.1kHz with sysdefault:CARD=C20
Loading Indicator:
- Show "Loading devices..." while parsing aplay -L output
- Fixes 4-5 second frozen UI on ALSA Direct backend selection
- isLoadingDevices state added to SettingsView
Stop Playback on Device Change:
- Call stop_playback() in handleBackendDeviceChange
- Prevents stuck/dead audio streams when changing output device
- Fixes: "After any config change, song in player stops working"
User can now:
1. Select hw:CARD=C20,DEV=0 for true bit-perfect to DAC
2. See loading indicator instead of frozen UI
3. Change device without breaking playback
4. Only see relevant devices (not 7 surround variants per card)
AUR (Arch Linux):
- Added to optdepends: 'alsa-utils: Better ALSA device names and descriptions'
- Users get notification to install, but not required
Flatpak:
- Included alsa-utils module in bundle (builds from source)
- Version 1.2.12 from alsa-project.org
- Sandboxed environment can now execute aplay -L internally
- Disabled alsamixer and xmlto (not needed, reduces size)
- Users get real device descriptions automatically
Snap (future - not yet implemented):
- Created snapcraft.yaml template
- stage-packages includes alsa-utils
- Will be bundled when Snap support is implemented
AppImage (future - not yet implemented):
- Created README.md documenting dependencies
- AppImage uses host system commands (aplay from host)
- Users need to install alsa-utils on their system
- Fallback to CPAL works if not installed
Summary by format:
- .deb/.rpm: Recommends alsa-utils (auto-installed, can be removed)
- AUR: optdepends (user notified, optional)
- Flatpak: Bundled (always available)
- Snap: Will be bundled (future)
- AppImage: Host dependency (user installs)
ALSA Backend Fallback:
- enumerate_via_aplay() - Preferred method with real hardware descriptions
- enumerate_via_cpal() - Fallback without descriptions (frontend will translate)
- enumerate_alsa_devices() tries aplay first, falls back to CPAL on failure
- Warning log suggests installing alsa-utils if aplay fails
Edge Case Handled:
- System has ALSA installed but not alsa-utils package
- CPAL detects ALSA host → AlsaBackend available
- aplay -L command fails → fallback to CPAL enumeration
- User sees translated names instead of real hardware descriptions
- App still works, just with less friendly device names
Package Dependencies:
- Added alsa-utils as "Recommends" for .deb packages (Debian/Ubuntu)
- Added alsa-utils as "Recommends" for .rpm packages (Fedora/RHEL)
- "Recommends" = installs by default but not required (apt/dnf behavior)
- If user explicitly removes alsa-utils, app falls back gracefully
In practice alsa-utils is almost always installed with ALSA, but this
ensures we handle the edge case properly.
ALSA Backend Changes:
- Parse aplay -L output to get real hardware descriptions
- Description from hardware: "Cambridge Audio Cambridge Audio USB Audio 2.0, USB Audio"
- No more hardcoded CARD_NAMES for specific hardware
- Still uses CPAL to verify device exists and get max sample rate
- Blacklist filtering preserved (null, lavrate, samplerate, etc.)
Frontend Changes:
- Prefer device.description (from aplay -L) when available
- Falls back to getDevicePrettyName() translation only if no description
- ALSA Direct shows: "Cambridge Audio ... USB Audio, Front output / input"
- Not: "Cambridge Audio USB Front Output" (hardcoded translation)
Removed Hardcoding:
- Removed CS201 → "Cambridge Audio USB" (was specific to user's DAC)
- Removed C20 → "Cambridge Audio"
- Generic CARD_NAMES remain (NVidia, Intel HD Audio, etc.)
This makes device names work for ANY hardware, not just known cards.
Each backend should use native tools:
- ALSA: aplay -L
- PipeWire: pw-cli/pactl (future)
- PulseAudio: pactl (future)