Commit Graph
852 Commits
Author SHA1 Message Date
vicrodh ca8fc13044 fix(ui): sync zoom store
cx
2026-01-19 22:38:24 -06:00
vicrodh 2bdd20aaeb fix(ui): sync zoom limits
cx
2026-01-19 22:28:46 -06:00
vicrodh b1803e4d85 fix(ui): clamp wheel zoom
cx
2026-01-19 22:22:21 -06:00
vicrodh 4e0327635e fix(settings): inline tooltip descriptions
cx
2026-01-19 22:16:37 -06:00
vicrodh a3e2068929 fix(ui): add dropdown scrollbars
cx
2026-01-19 22:14:11 -06:00
vicrodh 0e5026fa08 fix(ui): normalize quality badge widths
cx
2026-01-19 22:11:28 -06:00
vicrodh 4941a38a2b Add Download functionality to PlaylistDetailView
- Added onTrackOpenFolder and onTrackReDownload to Props interface
- Pass download props to menuActions in TrackRow
- Only show Download options for Qobuz tracks (not local tracks)
- Wire up handlers in main app component
- Completes Download menu implementation across all views
2026-01-19 21:53:59 -06:00
vicrodh 378ba36443 Fix Download menu not showing in TrackRow-based views
- Added onDownload, isTrackDownloaded, onOpenFolder, onReDownload to TrackMenuActions interface
- Pass download props from menuActions to TrackMenu in TrackRow
- Removed debug logging
- Fixes Download menu missing in HomeView, AlbumDetailView, and FavoritesView
2026-01-19 21:46:14 -06:00
vicrodh 4abb06ff89 Fix Svelte 5 const tag placement in SearchView
- Moved {@const isTrackDownloaded} to be direct child of {#each} loops
- Removed invalid placement inside <div> elements
- Fixed both track list instances (all results and dedicated tracks tab)
2026-01-19 21:20:43 -06:00
vicrodh b1abb00a8b Wire up Download handlers in main app component
- Added handleTrackOpenFolder, handleTrackReDownload, and checkTrackDownloaded handlers
- Imported openTrackFolder from downloadState.ts
- Connected all handlers to HomeView, AlbumDetailView, SearchView, and FavoritesView
- Download submenu now fully functional across all Qobuz track views
2026-01-19 21:17:47 -06:00
vicrodh ebca7c0546 Add Download functionality to all Qobuz track context menus
- Created open_track_folder Rust command to open folder containing downloaded track
- Added frontend wrappers: openTrackFolder and reDownloadTrack in downloadState.ts
- Updated all Qobuz views to support Download submenu:
  - HomeView: Added track download props and handlers
  - AlbumDetailView: Added onTrackOpenFolder and onTrackReDownload
  - SearchView: Added track download actions to both TrackMenu usages
  - FavoritesView: Added download submenu to both track list variants
- Downloaded tracks now show submenu with 'Open containing folder' and 'Re-download' options
- Completes Phase 2 Task 2.1: Context Menu Homogenization (Download)
2026-01-19 21:15:06 -06:00
vicrodh 4ed2b4c2ab Add Download submenu to TrackMenu component
Added Download menu item with submenu for downloaded tracks:
- If not downloaded: Shows 'Download' button
- If downloaded: Shows 'Download' submenu with:
  - 'Open containing folder' (onOpenFolder)
  - 'Re-download' (onReDownload)

New props added to TrackMenu:
- onDownload: Download track
- isTrackDownloaded: Boolean to determine if track is downloaded
- onOpenFolder: Open folder containing downloaded track
- onReDownload: Re-download track

Matches pattern used in AlbumMenu.svelte for albums.
2026-01-19 21:04:38 -06:00
vicrodh c7fdc52741 Expand HomeView context menu from 1 to 8 actions
HomeView 'Continue Listening' tracks now have complete context menu:
- onPlayNow ✓
- onPlayNext ✓ (NEW)
- onPlayLater ✓ (NEW)
- onAddToPlaylist ✓ (NEW)
- onShareQobuz ✓ (NEW)
- onShareSonglink ✓ (NEW)
- onGoToAlbum ✓ (NEW)
- onGoToArtist ✓ (NEW)

This matches the complete context menu available in AlbumDetailView,
FavoritesView, and other Qobuz track displays.

Files modified:
- src/lib/components/views/HomeView.svelte - Added track action props
- src/routes/+page.svelte - Passed track action handlers to HomeView
2026-01-19 20:52:07 -06:00
vicrodh ee8978819c Revert "Speed up ALAC playback by using streaming decoder"
This reverts commit 20eac2f578.
2026-01-19 20:45:50 -06:00
vicrodh 20eac2f578 Speed up ALAC playback by using streaming decoder
Removed special case that forced ALAC to use symphonia fallback which
decoded the entire file (~30MB) before playback, causing 6+ second delays.

Now ALAC uses rodio's Decoder first (supports ALAC via symphonia-all
feature) which streams instead of decoding everything upfront.

Fallback chain is now:
1. Rodio Decoder (streaming) - instant
2. MP4 fallback (streaming) - instant
3. Symphonia (full decode) - slow, last resort

This brings ALAC playback start time from ~6 seconds to instant,
matching FLAC and other formats.
2026-01-19 20:41:57 -06:00
vicrodh cdaf02bf3a Fix ALAC playback by defaulting to stereo when channel info missing
ALAC files (and some other formats) don't include channel information in
the initial codec params, causing playback to fail with error:
'No channel info in codec params'

Changed to use .unwrap_or(2) instead of .ok_or_else() to default to
stereo (2 channels) which is the most common case and matches the
behavior in metadata extraction.

Fixes playback of ALAC (.m4a) files in Local Library.
2026-01-19 20:38:38 -06:00
vicrodh dec6eab109 Add explicit f64 type annotations for all sample_rate reads
Fixed missing type annotations in row.get() calls for sample_rate.
Without explicit type annotation, Rust was inferring the wrong type
causing 'Invalid column type Real' errors when reading from database.

Added ::<_, f64> type annotation to all three locations:
- row_to_track (line 1168)
- get_playlist_local_tracks (line 1772)
- get_playlist_local_tracks_with_qobuz (line 1821)

This is the critical fix for the database migration error.
2026-01-19 20:23:33 -06:00
vicrodh 88d4bc8471 Fix sample_rate type mismatches for LocalAlbum and inserts
Changed LocalAlbum.sample_rate from u32 to f64 to match database REAL type.
Fixed database queries to read sample_rate as f64 instead of u32.
Removed incorrect i64 casts when inserting sample_rate values.

This fixes the 'Invalid column type Real' error that prevented the app
from loading after the database migration.
2026-01-19 20:14:09 -06:00
vicrodh 4080cfbfc8 Add cleanup of temp table in sample rate migration
Drop any existing local_tracks_new table before starting migration
to handle cases where a previous migration attempt failed mid-execution.

This prevents 'table already exists' errors when retrying the migration.
2026-01-19 20:07:32 -06:00
vicrodh ba8a0d3fd7 Fix sample rate migration to handle NULL file_size_bytes values
Use COALESCE in migration SELECT to safely handle NULL file_size_bytes
values that may exist in older database versions. This prevents the
'NOT NULL constraint failed' error when migrating from INTEGER to REAL
sample_rate type.

The migration now uses COALESCE(file_size_bytes, 0) to ensure all
rows have a valid file size value, defaulting to 0 for missing data.

Fixes critical migration failure that prevented app startup for users
with existing databases.
2026-01-19 19:59:01 -06:00
vicrodh 1e7475e308 Fix sample rate decimal precision for bit-perfect playback (CRITICAL)
Problem:
- sample_rate stored as INTEGER in DB and u32 in Rust
- Fractional rates (44.1, 88.2, 176.4 kHz) stored as 44100, 88200, 176400 Hz
- Integer division (44100 / 1000 = 44) lost decimals
- DAC badge showed "resampling 44 → 44.1 kHz" false positive
- Breaks bit-perfect guarantee - user requirement: "no es negociable"

Root Cause:
- DB schema: sample_rate INTEGER (44100 Hz)
- Frontend: Math.round(44100 / 1000) = 44 kHz (lost .1)
- Badge: Compared 44 kHz vs 44.1 kHz → false resampling warning

Solution:
1. Changed LocalTrack.sample_rate from u32 to f64 (models.rs)
2. Changed AudioProperties.sample_rate from u32 to f64 (models.rs)
3. Changed DB schema from INTEGER to REAL (database.rs)
4. Added migration to convert existing data (database.rs)
5. Updated metadata extraction to cast to f64 (metadata.rs)
6. Fixed frontend to use float division without rounding (+page.svelte)

Migration:
- Checks if sample_rate is INTEGER
- Creates temp table with REAL, copies data, drops old, renames
- Preserves all data and indexes
- Runs automatically on app start

Result:
- 44100 Hz stored as 44100.0
- Division: 44100.0 / 1000.0 = 44.1 kHz (exact)
- Badge: 44.1 kHz = 44.1 kHz → no resampling (correct)
- Bit-perfect guarantee restored

Files modified:
- src-tauri/src/library/models.rs - Changed sample_rate to f64 in structs
- src-tauri/src/library/metadata.rs - Cast sample_rate to f64
- src-tauri/src/library/database.rs - Changed schema to REAL + migration
- src/routes/+page.svelte - Fixed division to preserve decimals
2026-01-19 19:49:34 -06:00
vicrodh 61fa05da93 Fix critical shuffle queue bug, Local Library context, and sample rate crash
1. QUEUE SHUFFLE FIX (CRITICAL):
   - When shuffle enabled and set_queue called, starting track was placed randomly in shuffle_order
   - This caused queue UI to show only tracks AFTER that random position
   - Example: 11 tracks, start at index 5, but index 5 was at position 7 in shuffle → only 3 tracks visible
   - FIX: When shuffle enabled, swap starting track to position 0 in shuffle_order
   - Now all tracks appear in queue UI as expected

2. LOCAL LIBRARY CONTEXT FIX:
   - handleLocalTrackPlay was clearing context immediately after LocalLibraryView set it
   - FIX: Removed clearPlaybackContext() call - LocalLibraryView handles context correctly
   - Context icon should now display for Local Library playback

3. SAMPLE RATE CRASH FIX:
   - show_track_notification expected u32 but received f64 (44.1, 88.2, etc.)
   - Error: "invalid type: floating point 44.1, expected u32"
   - FIX: Changed sample_rate parameter from u32 to f64 in notification command and format_quality
   - Bonus: Format shows "44.1" for fractional rates, "48" for whole numbers

Files modified:
- src-tauri/src/queue/mod.rs - Fixed set_queue to move start_index to front of shuffle_order
- src/routes/+page.svelte - Removed clearPlaybackContext from handleLocalTrackPlay
- src-tauri/src/commands/notification.rs - Changed sample_rate to f64 with proper formatting
2026-01-19 19:13:07 -06:00
vicrodh 3d8c049226 Fix Qobuz album shuffle to start with random track and add debug logs
- Modified handleShuffleAlbum to pick random starting track (same fix as Local Library)
- Added comprehensive debug logs to handleAlbumTrackPlay to diagnose queue issues
- Fixed favoritesStore crash when response.tracks is undefined (defensive checks)

Fixes:
- Qobuz album shuffle no longer always starts with first track
- App no longer crashes on startup with "undefined is not an object (evaluating 'response.tracks.items')"
2026-01-19 19:02:46 -06:00
vicrodh a1220b3a06 Fix Local Library shuffle to start with random track and add debug logs
- Modified handleShuffleAllAlbum to pick random starting track instead of always starting with first
- Added comprehensive debug logs to track queue creation and size
- Logs will help diagnose why only 1-3 tracks appear in queue UI instead of all tracks
2026-01-19 18:48:15 -06:00
vicrodh ed7984a029 fix(context): fix playback context tracking bugs
Multiple critical fixes for context tracking:

1. Add 'local_library' to backend context type parser
   - Was rejecting 'local_library' causing "Invalid context type" error
   - Now properly recognized in set_playback_context command

2. Fix shuffle to not always start with first track
   - Removed logic that moved current track to front of shuffle
   - Now finds current track position in shuffled order instead
   - Shuffle is truly random, not biased to original order

3. Add missing await to all setPlaybackContext calls
   - PlaylistDetailView: await context creation
   - FavoritesView: make setFavoritesContext async + await calls
   - ArtistDetailView: await context creation
   - HomeView: await context creation
   - SearchView: await + use home_list type (search type doesn't exist)

   This fixes intermittent context tracking (race condition)

Fixes: shuffle starting with first song, Local Library context error,
intermittent playlist/favorites/artist/home/search context tracking

cc
2026-01-19 18:31:18 -06:00
vicrodh f2c3e8aedd feat(local-library): implement playback context tracking
Add proper context tracking for Local Library playback so the stack
icon shows correctly when playing from Local Library.

Changes:
- Add 'local_library' to ContextType enum (frontend and backend)
- Add 'Local Library' display label in context info
- Import setPlaybackContext in LocalLibraryView
- Call setPlaybackContext when playing tracks from Local Library
  - For album view: context is the album with album title
  - For tracks view: context is "Local Tracks"
- Track IDs and position are properly set for context navigation

Fixes issue where Local Library playback showed stale Qobuz context.

cc
2026-01-19 18:17:04 -06:00
vicrodh 873dcee10e feat(local-library): add shuffle button to album detail view
Add shuffle functionality to Local Library album view with button
that enables shuffle mode before playing all tracks.

Changes:
- Import Shuffle icon from lucide-svelte
- Add handleShuffleAllAlbum function
- Add shuffle button next to Play All button
- Add shuffle-btn CSS styling matching secondary button pattern

cc
2026-01-19 18:17:04 -06:00
vicrodh 0bbf6435b4 fix(queue): implement proper Fisher-Yates shuffle algorithm
Previous implementation had a critical bug in the shuffle algorithm
that used the same seed value for all iterations, resulting in
weak/predictable shuffling.

Changes:
- Add rand crate dependency for proper PRNG
- Fix Fisher-Yates to use unique random value per iteration
- Use StdRng with timestamp-based seed for reproducible randomness
- Each shuffle iteration now gets a new random number from the RNG

This fixes shuffle working inconsistently across different contexts
(albums, playlists, continuous play).

cc
2026-01-19 18:17:04 -06:00
vicrodh 603b794838 Document Flatpak limitations and NAS access in README
Add comprehensive Flatpak section with:
- Warning about PipeWire bit-perfect limitations in sandbox
- Instructions for ALSA Direct workaround
- NAS/network storage access commands (flatpak override)
- Note in Audio Backends section about Flatpak restrictions

This helps users make informed installation choices before downloading.
2026-01-19 17:39:17 -06:00
vicrodh 038e70bb85 Add Flatpak bit-perfect limitation warning for PipeWire
Display warning banner in Settings when user enables DAC Passthrough
with PipeWire backend in Flatpak environment.

The warning informs users that:
- PipeWire cannot guarantee bit-perfect in Flatpak sandbox
- ALSA Direct backend is recommended for true bit-perfect audio

This addresses the fundamental limitation where Flatpak portal does
not allow control of PipeWire daemon sample rate configuration, even
with elevated permissions.
2026-01-19 17:21:21 -06:00
vicrodh 7b596a25c2 Fix PipeWire detection and App ID migration in Flatpak
Two critical fixes for Flatpak sandbox environment:

1. PipeWire detection now uses pactl instead of pw-cli
   - pw-cli is not available in GNOME Flatpak runtime
   - pactl works for both PipeWire and PulseAudio (compatibility layer)
   - Fixes missing PipeWire backend in Settings

2. App ID migration now works in Flatpak sandboxes
   - Detects Flatpak environment and uses absolute paths
   - Accesses old sandbox (com.blitzkriegfc.qbz) via HOME directory
   - Added read-only permission to old App ID in manifest
   - Properly migrates config/data/cache from old to new sandbox
2026-01-19 16:10:31 -06:00
vicrodh 6cd9cb43c4 Fix ALSA utils SHA256 checksum for Flatpak build 2026-01-19 15:39:55 -06:00
vicrodh 8fecbf80a6 Merge feature/app-id-rename: App ID rename and Flatpak improvements
BREAKING CHANGE: App ID changed from com.blitzkriegfc.qbz to com.blitzfc.qbz

Changes:
- Renamed Flatpak App ID and all metadata files
- Fixed MPRIS name: qbz_nix → qbz
- Added NAS support permissions: /mnt, /media, /run/media
- Updated CI workflow for new filenames
- Updated tauri.conf.json bundle identifier

Flatpak features:
- Runtime detection (/.flatpak-info check)
- Automatic data migration from old App ID
  - Migrates config, data, and cache directories
  - Non-destructive (preserves old directories)
  - Runs on startup before any other initialization
- New Settings section (Flatpak-only)
  - Filesystem permission guidance
  - Copy-paste ready commands for NAS/SSHFS
  - Professional UX per Flatpak best practices

Migration is automatic and transparent for existing users.
2026-01-19 15:17:32 -06:00
vicrodh 8175e14fb0 Add Flatpak sandbox section in Settings UI
Features:
- New Settings section that appears ONLY when running in Flatpak
- Displays clear instructions for granting filesystem permissions
- Copy-paste ready commands for common scenarios (NAS, SSHFS)
- Explains Flatseal as GUI alternative
- Professional, defensive UX per prompt requirements

The section is completely hidden when not in Flatpak.
Users see actionable guidance with examples.
2026-01-19 15:17:05 -06:00
vicrodh a0467fb10a Add Flatpak detection and automatic data migration
Features:
- Flatpak runtime detection (/.flatpak-info check)
- Automatic migration of user data from old App ID to new ID
  - Config: ~/.config/com.blitzkriegfc.qbz → ~/.config/qbz
  - Data: ~/.local/share/com.blitzkriegfc.qbz → ~/.local/share/qbz
  - Cache: ~/.cache/com.blitzkriegfc.qbz → ~/.cache/qbz
- Migration runs on startup before any other initialization
- Old directories are preserved (manual cleanup required)
- Tauri commands for frontend integration:
  - is_running_in_flatpak()
  - get_flatpak_help_text()

Migration is non-destructive and idempotent.
2026-01-19 15:14:14 -06:00
vicrodh d2f61974d8 Rename App ID: com.blitzkriegfc.qbz → com.blitzfc.qbz
Changes:
- Renamed Flatpak manifest, desktop file, and AppStream metainfo
- Updated App ID in all metadata files
- Fixed MPRIS name: qbz_nix → qbz
- Added NAS support permissions: /mnt, /media, /run/media
- Updated CI workflow to use new filenames
- Updated tauri.conf.json bundle identifier

BREAKING CHANGE: Users will need to migrate data from old App ID directories
Data migration will be implemented in next commit
2026-01-19 15:12:21 -06:00
vicrodh e2afc25175 Merge feature/ux-improvements-180226: UX improvements and playback context
Major UX improvements:
- Playback context tracking from albums/playlists/artists/favorites/home/search
- Stack icon showing context origin with clickable navigation
- Autoplay behavior setting with persistence
- Context menu actions (Play track only, Play from here)
- Track row pause on hover across all views
- Volume indicators while dragging (all 4 players)
- Favorites customization (custom icon, background, sticky nav, search)
- Theme expansion (Nord, Dracula, Tokyo Night, Catppuccin, Rosé Pine, Breeze, Adwaita)
- Layout fix: NowPlayingBar moved outside app-body flex row

Database migrations (safe for existing users 1.1.5+):
- playback_preferences table (autoplay + show_context_icon)
- favorites_preferences table (custom_icon_path, preset, icon_background, tab_order)
- Favorites icon now stored in cache dir with auto-migration

Audio backend: ✅ UNTOUCHED (bit-perfect playback preserved)

Known gaps (to fix in next branch):
- Track focus on context navigation not implemented
- Volume indicator in FocusMode/MiniPlayer not confirmed tested

# Conflicts:
#	src/lib/components/AlbumCard.svelte
2026-01-19 15:04:12 -06:00
vicrodh b172b55bcf fix(favorites): migrate custom icon storage to cache
cx
2026-01-19 14:51:18 -06:00
vicrodh 7d9071442f fix(layout): keep NowPlayingBar out of app-body
cx
2026-01-19 14:51:14 -06:00
vicrodh 8cf462e240 Merge feature/alsa-direct: ALSA Direct backend for bit-perfect audio
Implements native ALSA hw: device support for bit-perfect playback with:
- Direct PCM access bypassing PipeWire/Pulse resampling
- Automatic format detection (S16_LE, S24_LE, S32_LE, FLOAT_LE)
- Optional hardware mixer volume control via ALSA
- HW badge indicator for ALSA Direct mode
- DAC badge improvements with device name tooltip
- Lightweight hardware status polling for sample rate verification

Database migrations are safe for existing users (1.1.5+):
- alsa_hardware_volume column added with NULL-safe handling
- Defaults to disabled for backwards compatibility

UI improvements:
- Improved ticker animation on album cards and tooltips
- Badge tooltip shows actual device name with scrolling
- Fixed badge width consistency across quality types
2026-01-19 13:58:30 -06:00
vicrodh ebe5f0f187 Fix badge width: ensure min-width regardless of quality badge size
Added min-width: 70px to .audio-badges-row to prevent badges from becoming too narrow when QualityBadge is small (e.g., MP3 on local library).
2026-01-19 13:57:33 -06:00
vicrodh 80d349e9b0 Apply improved ticker animation to album cards (same as badge tooltip)
Changes:
- Reduced speed from 80 to 40 pixels per second for better readability
- Added typewriter-style animation with pauses and fast return
- Slow scroll (50% of time), pause, then quick return (10% of time)

IMPORTANT FOR MERGE: If this conflicts with other branches modifying the same animation, THIS VERSION should be kept as it provides better UX (tested and approved by user).
2026-01-19 13:43:07 -06:00
vicrodh ebbf5a6e3a Make ticker return faster like typewriter carriage
Adjusted keyframes so scroll is slow (50% of time) and return is quick (10% of time), mimicking mechanical typewriter carriage return.
2026-01-19 13:32:10 -06:00
vicrodh fa7cb51e91 Add pauses to ticker animation for smoother cycling
Animation now pauses at start and end positions before moving, eliminating jarring jumps when loop restarts.
2026-01-19 13:31:07 -06:00
vicrodh 29dfb78124 Reduce ticker animation speed to improve readability
Reduced speed from 80 to 40 pixels per second to make device name more readable and reduce stuttering.
2026-01-19 13:29:52 -06:00
vicrodh 0ee2891f44 Reduce badge tooltip width to 2/3 of original size 2026-01-19 13:28:52 -06:00
vicrodh de08d841b0 Fix ticker animation to trigger on tooltip open, not hover
Animation now starts automatically when tooltip appears if device name overflows, since tooltip has pointer-events: none and cannot be hovered.
2026-01-19 13:28:01 -06:00
vicrodh 5f3b4631e6 Add ticker animation for long device names in badge tooltip
Adds scrolling animation when device names overflow tooltip width, borrowed from AlbumCard component pattern. Also fixes accessibility warning by adding role and aria-label to badge container.
2026-01-19 13:26:56 -06:00
vicrodh 6166b7c206 Reduce console spam: change hardware status log from info to debug
The 1s polling of get_hardware_audio_status was logging every second:
"Hardware audio status: 192000Hz, format: Some("S32_LE")"

This is useful for debugging but too noisy for normal usage.

Change:
- log::info! → log::debug! (line 52)

Result:
- Console stays clean during playback
- Log still available with RUST_LOG=debug for debugging
- Polling continues working, just silently
2026-01-19 13:20:49 -06:00
vicrodh ea7614f639 Fix badge issues: restore lightweight 1s polling for hardwareStatus only
This reverts the event-driven approach (9242b95) and restores lightweight
polling, but ONLY for hardwareStatus.

Problems with event-driven approach:
1. False yellow warnings: hardwareStatus had stale data from previous song
   - Hardware: 88200 Hz (correct)
   - Cached hardwareStatus: 44100 Hz (from previous song)
   - Badge logic: 88.2 !== 44.1 → WARNING (false positive)

2. Lost device name: outputStatus never updated after mount
   - loadStatus() only called once on mount
   - outputStatus.device_name became stale
   - Badge tooltip showed "Default" instead of "hw:4,0"

Solution - Lightweight polling (best of both worlds):
- Call loadStatus() once on mount (loads everything)
- Poll ONLY hardwareStatus every 1s (no device enumeration)
- get_hardware_audio_status just reads /proc/asound (very cheap)
- No CPAL device enumeration (no console spam)
- No get_pipewire_sinks calls (no ALSA errors)

Benefits:
- Badge always shows current hardware status (accurate resampling detection)
- Device name preserved (loaded once, doesn't change)
- Zero console spam (no device enumeration)
- Very lightweight (just read /proc/asound file)
- ~1000x cheaper than full loadStatus() polling

This gives us accurate bit-perfect validation without the overhead.
2026-01-19 13:11:28 -06:00