Album view (local/Plex):
- Rebuild LocalAlbumView as a near copy of the Qobuz AlbumPageView (whole-page
scroll, 224px cover, action row, toolbar with quality badge + track search,
column header, source-aware rows) minus the label/awards sidebar and Qobuz
context menus. Add a per-album client-side track search.
Recently Played (Home) — D:
- Carry the track source (qobuz|plex|local) through the recently-played store so
the cards resolve source-aware artwork (PlexThumb / local file), the album-card
play routes to local/Plex playback instead of the empty Qobuz path, and the
card click opens the right album view.
- Fix a regression from the Plex version split: local_queue_track now derives the
content-hash plex_album_key for album_id (navigation) while album_group_key keeps
the per-edition split key, so "go to album" and recent Plex albums open correctly.
Local artist navigation — B:
- Implement open_artist (was a TODO): latch the requested artist and select it once
the Artists tab loads. The shared open-artist is now source-aware (numeric id ->
Qobuz artist; name -> LocalLibrary Artists tab). Wire the LocalAlbum header artist
link and the now-playing "Go to artist" for Plex/local.
Folder delete -> Recently Played cascade:
- Removing a Local Library folder now also prunes its albums/tracks from the
recently-played store (album_keys_in_folder + recently::prune_albums).
Covers — C:
- Robust on-disk folder cover lookup (cover/folder/front/art/<album>.jpg, any image
as a last resort), shared by the Folders subcards and now-playing/album rows.
cc
A Plex album_key is a hash of artist+album, so two distinct Plex albums
that share a title (e.g. a matched album plus loose files added as a second
album) collapsed into one cache album and their tracks interleaved
(1,1,2,2,...) in the album view with no way to pick a version.
Capture parentRatingKey (the real per-album Plex id) on sync, store it in a
new plex_cache_tracks column, and key the album-view version grouping on it
so each Plex album becomes its own version in the existing header picker
(best quality first). Pre-resync rows lack the column and fall back to the
old single-group behaviour; a re-sync backfills it.
cc
Two Plex-port fixes (both in the Plex play/data path):
1. Progressive streaming — play starts in ~1s instead of ~10s. play_plex_audible
no longer downloads the whole file before play_data; it resolves only the
part URL (new additive qbz_plex::plex_resolve_part_url, no body GET) and feeds
it into the player's streaming source (play_streaming_dynamic) via a shared
feeder module (remote_stream.rs, extracted from the QConnect renderer's HTTP
feeder — both now share it). Bit-perfect preserved: the original /file bytes
are streamed (no transcode), device init derives from the decoded stream; the
protected audio backend is untouched. Cast/DLNA keep the whole-file path.
Falls back to whole-file play_data on a non-direct part / streaming-setup error.
2. Re-sync no longer wipes hydrated quality — the sync did plex_cache_clear()
(full wipe) BEFORE the per-section save_tracks, defeating save_tracks' COALESCE
hydration carry-over, so already-hydrated albums reverted to a bare FLAC badge
on every settings-open / background-refresh / section-toggle. Replaced the full
wipe with a new additive plex_cache_prune_sections(keep) that removes only the
DE-selected sections, keeping the selected sections (and their hydration);
save_tracks then re-applies its per-section carry-over.
cc
Hydration:
- qbz-plex: additive plex_hydrate_album_quality(base, token, rating_keys) — per
key plex_get_track_metadata (batch 5, 5s/call timeout, skip failures) then the
COALESCE write-back plex_cache_update_track_quality; awaited before re-read.
- qbz-player/qbz-core: patch_plex_quality / patch_plex_queue_quality patch the
in-memory queue in place by rating_key (for the now-playing stamp).
- qbz-slint: album-open trigger (gated on DB-NULL, not a value heuristic) then
refresh of all badge surfaces — album-card grid (re-read aggregate + patch
every model + the raw filter source), album-detail header + rows, now-playing
stamp. Re-sync stays routed through plex_cache_save_tracks (carry-over).
Quality badge consistency (the four surfaces must agree — and pre-hydration
show the filetype, not nothing):
- One shared classifier crate::quality::badge(format, bit_depth, sample_rate) ->
(tier, detail, tooltip); the album card, album-detail header and track rows
all route through it (kills the per-surface duplication that produced
"96 kHz" vs "96000 kHz"). sample_rate normalized to kHz via a >=1000 guard so
Qobuz's already-kHz values are never re-divided (no "0.041 kHz").
- New "lossless" tier: an un-hydrated lossless file (FLAC/WAV/ALAC… with unknown
bit depth) shows a generic filetype badge ("FLAC") instead of an empty badge,
rendered in QualityBadge (compact chip) + QualityBadgeFull; upgrades to
"24-bit / 96 kHz" after hydration. Local/Qobuz/mp3 unchanged.
cc
Both tabs were Plex-blind. Loader-level merge (no qbz-library change):
- qbz-plex: additive PlexCachedArtist + plex_cache_get_artists() (aggregates
the flat plex_cache_tracks by normalized artist) + plex_cache_count_tracks().
- Artists tab: fold Plex artists into the local set before merge_artists
(same-name local+Plex collapse, summed tracks); artist-detail album grid +
counts switch to the Plex-aware get_albums_metadata_page; portraits fall back
custom/Qobuz -> Plex thumb -> album cover, dispatched via spawn_local_or_plex_loads.
- Tracks tab: prepend the bounded Plex set (<=5000) once at offset 0; later
pages stay pure-local so the virtualized 16K-row LIMIT/OFFSET path is intact.
- Tracks badge now includes the Plex count (matches albums/artists badges).
- All gated on plex_settings enabled (Plex OFF = byte-for-byte local-only).
Plex track id namespaced into [2^40, 2^41) so it cannot collide with local
track ids (< 2^40) or ephemeral ids (>= 2^48) in the merged Tracks tab — the
string rating_key is preserved in file_path, so playback is unaffected.
cc
Move the Plex LAN client, hand-rolled XML metadata parsing, and the
SQLite plex_cache.db logic out of src-tauri/src/plex/mod.rs into a new
qbz-plex core crate so it can be shared by every frontend (Tauri, Slint,
TUI) per ADR-006. Plex previously lived entirely in src-tauri (~1715
lines), which blocked the Slint Local Library port from shipping Plex.
The extraction is mechanical: the module was already almost entirely
plain Rust (reqwest + rusqlite + serde + hand-rolled XML). The only
Tauri coupling was a dead plex_play_track command bound to AppState
(unreferenced; the live path is commands_v2::v2_plex_play_track, which
calls plex_resolve_track_media + AppState.player itself). That dead fn
and the vestigial #[tauri::command] attributes are dropped.
src-tauri/src/plex/mod.rs becomes a `pub use qbz_plex::*;` re-export so
every existing crate::plex::* call site compiles unchanged. Full Tauri
workspace cargo check stays green; qbz-plex tests pass.
cc