Files
qbz/docs/release-1.1.15/CHANGELOG_FULL.md
T

191 lines
9.8 KiB
Markdown

# QBZ 1.1.15 Full Changelog
This document details all changes introduced in version 1.1.15 relative to the v1.1.14 tag.
**Summary:** ~200 commits across all branches merged into pre-release; 19 files changed in the multi-select/navigation work alone.
---
## New Features
### Purchases view
- New `PurchasesView` component browses the authenticated user's Qobuz purchases via the V2 API
- Local download registry stored in a dedicated SQLite table; tracks which purchases have been downloaded and to which local path
- V2 backend commands: `v2_get_purchases`, `v2_get_downloaded_purchases`, `v2_mark_purchase_downloaded`
- Downloads organized into quality subfolders to match the Qobuz file naming convention
- Mock data removed; real API integration only
### Your Mixes
- New **Your Mixes** card section on the Home view
- Four mix types surfaced: **Daily Q** (`DynamicSuggestView`), **Weekly Q** (`WeeklySuggestView`), **Fav Q** (`FavQView`), **Top Q** (`TopQView`)
- Each view: full track list with artwork, quality badges, shuffle, search/filter, and now multi-select
- Video/animated assets for mix cover placeholders
### Multi-select bulk actions
All track list views support multi-select mode:
| View | Source |
|---|---|
| AlbumDetailView | `TrackRow` checkbox via existing selectable props |
| ArtistDetailView | Custom track rows with injected checkbox |
| PlaylistDetailView | Virtualized list; `VirtualizedTrackList` extended with `selectable`/`selected`/`onToggleSelect` |
| FavoritesView | Virtualized list via same props |
| DynamicSuggestView (Daily Q) | `TrackRow` checkbox |
| WeeklySuggestView | `TrackRow` checkbox |
| FavQView | `TrackRow` checkbox |
| TopQView | `TrackRow` checkbox |
| LabelView | Custom track rows with injected checkbox |
**BulkActionBar component** (`src/lib/components/BulkActionBar.svelte`):
- Sticky-bottom bar with count label, queue split-button (Add to Queue / Play Next), Add to Playlist, Add/Remove Favorites, Remove from Playlist
- Animated entry (`slideUp` 180 ms)
- `placement` prop retained for future use; all current views use bottom placement
- Positioned after all track rows; views already carry `padding-bottom: 100px` so no track is ever hidden
**Backend V2 commands added** (`src-tauri/src/commands_v2.rs`):
- `v2_add_tracks_to_queue_next` — insert tracks at front of queue
- `v2_bulk_toggle_favorites` — toggle favorite state for a list of track IDs in one call
**i18n keys added** (all four locales: en, es, de, fr):
- `actions.select`
- `actions.cancelSelection`
- `actions.selectedTracks` (interpolated: `{count} selected`)
### Custom artist images
- `customArtistImageStore` — Svelte store keyed by artist ID; populated from a bulk backend fetch on login
- Right-click context menu on any artist image opens a file picker; selected image is resized (max 800 px) and thumbnailed (200 px) in the Rust backend and stored in the app data directory
- V2 backend commands: `v2_set_custom_artist_image`, `v2_remove_custom_artist_image`, `v2_get_custom_artist_image`, `v2_bulk_get_custom_artist_images`
- `resolveArtistImage(id, fallback)` helper used across: `HomeView`, `SearchView`, `FavoritesView`, `ArtistDetailView`, `LabelView`
- Images sync immediately on assignment without requiring navigation
### Image lightbox
- `ImageLightbox` component: full-screen overlay, click-outside or Escape to dismiss, natural image dimensions
- Wired to artist photo (ArtistDetailView) and album artwork (AlbumDetailView)
- Round mask removed by design — images display at their natural crop
### Qobuz Radio
- Album radio button in AlbumDetailView header
- Artist radio and track radio in ArtistDetailView and track context menus
- Submenu lists available Qobuz radio variants for the selected artist/track
- V2 backend: `v2_create_qobuz_artist_radio`, `v2_create_qobuz_track_radio`
### Session restore
- App serializes active view + selected item IDs to a session file on navigation
- On launch, `restoreView` in `navigationStore` rehydrates without triggering data re-fetches
- Startup page setting in Settings → General: `home`, `library`, `purchases`, `last`
### Shareable and deep links — Odesli/Songlink integration
- `LinkResolverService` — backend service that accepts any streaming platform URL
- Phase 1: direct platform API calls for Spotify, Apple Music, Tidal, Deezer, YouTube Music, Amazon Music
- Phase 2: falls back to Odesli universal API (`song.link`) when direct resolution fails
- Falls back again to smart Qobuz search (title + artist) if Odesli has no Qobuz entry
- Share outward: track context menu → "Share via Songlink" copies a `song.link` URL to clipboard
- Paste inward: paste any recognized URL to open the Qobuz equivalent in-app
### Font selector
- `fontFamily` setting in Appearance; persisted in user config
- Applied via CSS custom property on `:root`; immediate effect
- Options include system-ui, Inter, Roboto, and a handful of monospace/serif choices
### System / Auto-theme
- `autoThemeStore` and V2 commands `v2_generate_auto_theme`, `v2_detect_system_theme`
- Desktop environment detection: KDE (reads `~/.config/kdeglobals`), GNOME (GSettings schema), others
- On DEs without an accent API: extracts a dominant palette from the current album artwork using a k-means color quantizer in the Rust backend
- Generated colors injected as CSS custom properties; can be individually overridden with the inline color picker in Appearance settings
- Bootstrap restore: theme is re-applied from stored config on startup before the first paint
---
## Redesigns
### Label view
Previous design was a flat list of popular tracks. New design is a full-page layout with:
1. **Hero header** — label logo/image, name, album count, carousel jump-links
2. **Popular Tracks section** — play-all controls, multi-select, show-more/show-less (5 → 20 → 50)
3. **Releases** — horizontal scroll carousel with "See All" pagination
4. **Playlists** — horizontal scroll carousel
5. **Artists** — horizontal scroll carousel with custom artist image support
---
## Improvements
### Audio
- **PipeWire force bit-perfect** — new option in Audio settings; sets `node.rate` to track's native sample rate and disables PipeWire's built-in resampler, delivering raw PCM to the hardware node without any mixer involvement
- Bit-perfect remains non-negotiable: `try_from_device_config()` path is preserved; this option is additive
### Performance
- **L1 cache: 400 MB, L2 cache: 800 MB** — raised from previous defaults; reduces backend re-fetches for users with large streaming histories
- Label view artist image carousel deferred to idle frames via batched `Promise.allSettled`; prevents paint jank on first render
### UI/UX
- **UI scale options** — 85% and 95% added between the existing 80% and 100% steps
- **Track multi-select persists during scroll** — selection state held in a `Set<number>` (not DOM state); survives virtual scroll culling in Favorites and Playlist views
---
## Bug Fixes
### Multi-select action bar positioning (ADR-001 + layout)
**Problem:** `BulkActionBar` placed before the column header (`position: sticky; bottom: 0`) was rendered at the *top* of the track list, not the bottom. On scroll, it floated over tracks.
**Root cause (ADR-001):** AlbumDetailView and WeeklySuggestView used `filter(t => ...)` inside async functions — `t` shadows the svelte-i18n `$t` store, breaking `vite-plugin-svelte`'s CSS extraction pipeline. This caused the entire component's CSS to be dropped at runtime.
**Fix:**
- `filter(t => ...)` → `filter(track => ...)` / `filter(trk => ...)` in all affected functions
- `BulkActionBar` moved to the end of the track row container, after `{/each}`, still inside the scrollable section
- All affected views already had `padding-bottom: 100px`; no track is ever hidden under the sticky bar
### Scroll position bleeding between items
**Problem:** `scrollPositions: Map<ViewType, number>` used a single key per view type. Opening Album B after Album A would restore Album A's saved scroll offset in Album B.
**Fix:** Key changed to `"viewType:itemId"` — e.g. `"album:MAQJXZA123456"`, `"artist:300213"`, `"playlist:42"`. Views without a specific item (search, favorites tabs) retain the view-type-only key unchanged. The `scrollKey()` helper builds the composite string; `saveScrollPosition` and `getSavedScrollPosition` accept an optional `itemId` parameter.
### Scroll position TTL
**Problem:** Saved positions were permanent; an album visited weeks ago would restore a stale offset.
**Fix:** `ScrollEntry` now stores `{ scrollTop, savedAt: Date.now() }`. `getSavedScrollPosition` returns 0 if `Date.now() - savedAt > 3_600_000` (1 hour). Constant `SCROLL_TTL_MS` at the top of `navigationStore.ts` for easy adjustment.
---
## Architecture (Internal)
> This section is for contributors. Users can skip it.
### V2 multi-crate migration (Codex)
The entire Tauri command layer was migrated from a monolithic `src-tauri/src/` module to a workspace of independent Rust crates with no Tauri dependency:
| Crate | Responsibility |
|---|---|
| `qbz-core` | Shared types, error types, DB utilities |
| `qbz-models` | Domain model structs (Track, Album, Artist, …) |
| `qbz-player` | Audio engine, queue, playback state |
| `qbz-audio` | PipeWire/ALSA backend wrappers |
| `qbz-qobuz` | Qobuz API client |
| `qbz-cache` | Two-level cache (L1 in-memory, L2 SQLite) |
| `qbz-library` | Local library scan and indexing |
| `qbz-integrations` | Last.fm, MPRIS, Odesli, Plex |
All frontend `invoke()` calls use `v2_*` or `runtime_*` prefixes. Legacy command modules (the old `src-tauri/src/library/commands.rs` et al.) have been deleted or reduced to stubs; no V2 command delegates to a legacy function. This separation means the player, library, and integrations layers can run headless (TUI, CLI) without Tauri being present.
The `commands_v2.rs` file added in this release adds the multi-select bulk commands and the purchase registry commands on top of the already-migrated stack.