Add a select toggle to the Tracks header that enters multi-select mode
(per-row checkboxes via the shared TrackRow) and shows the shared
MultiSelectBar with Select all / Remove from favorites / Clear. Selection
lives on the rendered tracks-visible rows (mirrors playlist.rs); the
toggle-select router arm and recount gain a Favorites case. Bulk remove
loops remove_favorite, syncs fav_cache, then reloads the tab. Play next /
queue / add-to-playlist / make-offline are deferred (each needs a backend
path: batch enqueue / multi-id picker / offline cache).
cc
The multi-select checkbox circle was 22px and read as oversized. Drop it
to 13px (border-radius 6.5px) with an 8px check; the existing
(parent - self)/2 centering keeps it centered in the play/number cell.
cc
Retain the loaded favorite tracks as a play-ready Vec<Track> and add
Play all + Shuffle CircleAction buttons by the title (Tracks tab only),
wired to playback::play_tracks. Mirrors the Mix/Playlist transport
(those expose exactly these two; no batch play-next path exists yet).
cc
Add an expandable search over the loaded favorite tracks (title /
artist / album) plus a results-count line. tracks-visible (what the list
renders) derives from the full tracks set: an empty query shares the
model so artwork stays live, a query forks a filtered clone carrying the
already-decoded artwork (the LabelState albums/visible pattern). Adds a
no-match empty state.
cc
The Tracks row routed every media-action through track-action, which
forces kind="track"; an album-name click arrived as ("track", album-id,
"open") and was handled as an artist open, so only the artist link
worked. Route album/artist "open" through the wired open-album /
open-artist actions; everything else still goes to track-action.
cc
load_favorites looped a single 500-item fetch; users with larger
libraries silently lost everything past the first page. Page through
get_favorites (offset += page len) until a short page or offset >= total,
capped at 10k (mirrors Tauri's fetchAllFavorites). Applies to tracks,
albums, artists and labels.
cc
map_track now carries the album title (was dropped, blanked as ""), and
the Favorites tracks rows enable show-album + show-favorite so the album
column and the un-favorite heart render — matching the Tauri favorites
tracks row. Offline-cache download button stays off until that path is
wired.
cc
Extract PlaylistView's inline bulk action bar into a shared
MultiSelectBar primitive driven by a [BulkAction] model (id / label /
danger / needs-selection) emitting action(id). PlaylistView now declares
its two actions (Select all, Remove) as data; the Favorites tracks tab
will reuse the same bar with its larger action set instead of copying
it. Same visual (count label + bordered/hover buttons, ADR-008).
cc
DiscoverBrowseView and LabelReleasesView each carried their own album
grid column math, flat-list (AlbumListHeader + AlbumListRow loop), and
LabelReleasesView additionally the group-by-section rendering. Extract
all of it into a shared AlbumCollectionView (with an inner AlbumGrid),
driven by albums / grouped / is-grouped / view-mode / show-source props.
Both views now mount the one renderer; behavior is preserved (the empty
length>0 guards mirror the previous per-block guards). Readies the same
renderer for the Favorites albums tab.
cc
LabelReleasesView and DiscoverBrowseView each carried a verbatim copy of
the 34px segmented/toolbar toggle. Move it to primitives/ToggleButton and
import it in both. No visual change — same surface, one definition.
cc
Move map_album (V2 nested-first with flat fallback), tier/tier_hires,
release-type classification, to_item and sort_album_items out of
label.rs into a shared album_map module. favorites.rs now maps albums
through it too, so favorite album cards gain the AlbumListRow extras
(release_type / quality_detail / track_count / plain_year) and the
hires-without-bit-depth tier fallback — one implementation to maintain
ahead of the Favorites album list/grid toolbar port.
cc
/label/getAlbums returns the V2 nested album shape (DiscographyAlbumDto:
artists[], audio_info, dates, track_count) — confirmed against the Android
decompiled sources — but it was deserialized into the flat Album model,
which dropped artist, quality and release date (Unknown Artist in
group-by-artist, no quality badge, no date, empty list-row columns).
qbz-models Album: capture the nested V2 fields (audio_info, dates,
track_count, release_type) as optional serde(default) — backward
compatible with the flat-shape endpoints.
label.rs map_album: read nested-first (quality from audio_info, date from
dates, count from track_count, artist from artists[] main-artist, explicit
release_type) with flat fallback. Fixes the releases grid + list columns,
the landing Releases carousel, and Critics' Picks.
Wire ("album","favorite") -> add_favorite + toast (works on every album
card via the shared component).
LabelReleases load-more now driven by has_more (the endpoint caps each
page well below the catalog, so total was an unreliable gate).
cc
CHECKPOINT — this batch is NOT yet acceptable; substantial feedback is pending. Committed so the work is preserved for rework next session, after a max-effort Tauri code review. Areas: TrackRow favorite toggle/state + clickable artist/album (TrackItem gains is-favorite/artist-id/album-id + fav_cache, populated across all builders); PlaylistView header buttons + cover-art hover overlay; LabelView full AlbumCard + grid/list toggle; vertical scrollbar moved to the right gutter across ~12 views.
cc
Marks every non-functional Appearance row (Theme, Language, Font, UI Scale, notifications, window-title, player views, system tray, immersive) with the SettingRow 'unsupported' red label. Only the wired 'Show playlist cover collages' and the 'Detected' status row stay normal. Red = not actually connected, so what still needs wiring is visible at a glance.
cc
Add a SettingRow 'unsupported' flag that renders the label red. Mark the Title Bar and Window Controls sections: that Tauri custom-window-decoration paradigm does not carry over to Slint's TitleBar/Plasma chrome.
cc
Playlist Manager: list view renders compact rows with total play time; folder grid cards no longer clip the playlist count (content-driven height); list view shows compact folder chips; the move-to-folder menu gains a folder search and a 4-row scroll cap. Search: the most-popular track hero is resized to the artist-card height with the quality shown as centered text instead of a clipped badge.
cc
Mirrors the play-cell click on the row body: double-clicking a track plays it, or toggles play/pause when it is already the current track. Applies to every TrackRow (album, artist, search, playlist, mix).
cc
Sidebar:
- Fix "Recursion detected" panic from focusing the playlist search input in init (defer via a single-shot Timer).
- Right-click context menus: playlist (Edit, Hide from sidebar, Move to folder with folder search + Move to root) and folder (Edit, Hide from sidebar), built on ContextMenu/ContextMenuItem; remove the unreachable hover "..." button.
- Filter hidden playlists/folders out of the list; add folders::set_folder_hidden.
- Folder list in the move-to-folder menu scrolls (cap 5 rows); close-on-click-outside so the folder-search input no longer dismisses the menu.
- Sort-menu Refresh now uses a dedicated refresh-cw icon.
Toast: in-app ToastKind/ToastState + Toast overlay mounted at the app root, crate::toast (show/hide, per-kind auto-hide via a reusable Timer, cross-thread *_weak helpers); surface play album/artist load failures.
SearchView: title inline with NavButtons + searchType filters floated right; tab strip on its own row; Most-popular hero for a track uses the new SearchTrackHero, sized to the artist card.
Playlist Manager: list view renders compact PmListRow rows instead of cards; grid/folder card fonts tightened to match Tauri; folder card centered with hover-reveal edit; search input narrowed to 70%.
cc
Port the Playlist Manager: header/toolbar (search/filter/sort/folder-flat
toggle/view-mode/new-folder/counter), FOLDERS card grid (accent icon, count,
edit), PLAYLISTS grid/list/tree, folder CRUD (FolderEditModal), move-to-
folder, per-card actions, and arrow-button reorder (custom sort). Reached
from the sidebar "Manage playlists" item; backend 100% reused via
QbzCore/library.db (no new commands). Pointer-event drag-and-drop and
gradient folder colors are deferred follow-ups.
This commit also carries the cross-cutting wiring the session's features all
share — state.slint globals/structs, NavEntry variants, main.rs dispatch +
action wiring, artwork targets, app.slint exports, AppShell mounts — so HEAD
is the verified-green tree (cargo check + release build clean).
cc
Add an Appearance sub-section to the Slint settings, mirroring Tauri's:
theme + auto-theme, font/UI-scale/language, startup page, sidebar playlist
collage (wired to the sidebar flag), notifications/toasts/tray, title bar +
window controls, immersive + miniplayer. Controls reuse QbzToggle/QbzSelect
and the AudioSettings/PlaybackSettings layout. Most controls are visual
stubs (AppearanceState + TODO callbacks) pending backend wiring.
cc
PLAYLISTS header: smaller title, recursive search (hides title), create
modal at 1:1 with Tauri (name/description/folder/public), sort menu, and a
collapse chevron. Folders: accent-tinted icon, open/close chevron as the
primary affordance (no more purposeless "..."), count shown on hover only.
Playlists render a 2x2 micro-collage of track covers (opt-out) instead of
the glyph, sourced from the existing playlist images (no extra fetch).
cc
Per-module discover See-All pages (new releases, press accolades, ideal
discography, qobuzissimes, albums of the week, most streamed) reachable
from each carousel's "View all" link: a paginated grid OR list view with
client-side search, genre filter, and a fixed header. Adds a reusable
AlbumListRow (columns #/ITEM/TYPE/SOURCE/QUALITY/TRACKS/YEAR, SOURCE
hideable, reuses QualityBadgeFull) with play-on-hover + even/odd zebra.
Recently Played albums now carry genre + localized release date + quality
(captured at play time, no extra fetch). Shared home::map_album /
card_to_item reused; grid-only AlbumCardItem literals get ..Default.
cc
Album-card hover overlay now shows the full release date as "MMM D, YYYY"
(e.g. "Nov 6, 2025") instead of the year alone. New dates::release_label
helper keeps the month/day/year order fixed but localizes the month via
chrono format_localized (the app ships 5 languages; current_locale() is the
single hook to thread the real UI language once Slint wires translations —
English for now). Applied to the Discover and Favorites album mappings.
Also fixes Favorites > Albums cards, which hardcoded an empty genre and a
year-only label: they now read album.genre and the full date, matching the
Discover cards 1:1. The data was already in the getUserFavorites response
(qbz-models Album parses genre + release_date_original) — no backend change.
cc
Each favorites tab is now its own history entry (NavEntry::Favorites{tab},
mirroring Discover) recorded from both the header nav and the in-view tab
bar, so back/forward moves between tabs instead of exiting Favorites. Add
the back/forward NavButtons at the same position as every other page
(11px top padding + 22px gap) — FavoritesView was the only view missing
it — and shrink the "Favorites" heading to Typography.section (the album
name size in AlbumView).
cc
The clickable-artist change bound the artist TouchArea width to the artist
text's preferred-width, which clamped the whole stretch:1 title column to
the (often short) artist name — truncating titles to a few letters and
shoving the quality badge inward instead of letting it float right. Drop
the explicit width; the inner Text fills the column (width: 100%) and elides.
cc
Port #470's UI behavior: add an "Auto" entry at the top of the backend
dropdown that, on select, resolves to the best available backend (PipeWire
if present, else System) and persists that concrete value. The rebuilt
snapshot then moves the dropdown onto the resolved backend, so backend_type
is never left null/Auto. Concrete backends keep mapping by their shifted
index; System wiring comes from available_backends() (merged backend half).
cc
The catch-up merge staged the pre-release lock (--theirs) before cargo
regenerated it, so the committed lock dropped qbz-slint's GUI dependency
tree (slint / accesskit / winit / skia / ab_glyph, ...). Commit the
regenerated lock so the checked-in slint-mvp lock matches the workspace.
cc
qbzd was a premature daemon experiment that never shipped (gated off
behind QBZD_ENABLED, no user access). Its ratatui 0.29 dependency pinned
unicode-width =0.2.0, which conflicts with slint's annotate-snippets in a
shared workspace. Remove it entirely; it will be reimplemented from
scratch later on the clean, UI-agnostic shell.
- Delete crates/qbzd daemon crate + workspace member (drops ratatui,
crossterm and the other qbzd-only deps from the lock).
- Delete src-tauri qbzd_discovery (mDNS) + its commands/state wiring in
lib.rs; drop the direct mdns-sd dep (Chromecast keeps its own via
qbz-cast).
- Delete remoteApi/remoteEvents/playbackTargetStore; neutralize
commandRouter to local-only dispatch (isRemote/skipIfRemote are now
always-false no-ops so the ~100 guard call-sites keep compiling).
- Strip qbzd branches from playbackService and the remote banner from
NowPlayingBar; excise qbzd from CastPicker (Chromecast/DLNA kept).
- Remove qbzd i18n keys (5 locales), README daemon node, gitignore note.
cargo check (crates + src-tauri) and svelte-check all green.
cc
OOTB playback froze (seekbar advancing, no audio) on hosts where the
configured backend was not usable — e.g. PipeWire as the Linux default but
`pactl` absent (minimal Debian/Ubuntu). "Auto" (a null backend_type) fell
through to a legacy CPAL path that forced the track rate onto the shared
default device, producing a suspended/silent stream and leaving a
process-wide stuck audio handle that survived Reset.
- Add a cross-platform "System" backend (CpalDefaultBackend now on Linux
too): opens the OS default output at its negotiated rate with rodio
resampling and an explicit ~100ms CPAL buffer (MixerDeviceSink has zero
internal buffering, so the bare default underran and stayed suspended).
Shared, no exclusivity, no `pactl` — mixes with other apps like any
normal player.
- Make System the OOTB default and what Reset lands on; seed it on first
run. Drop the per-startup #375 NULL->PipeWire backfill migration.
- Resolve a null backend_type to System in the player so the legacy CPAL
path is never reached (covers legacy installs and headless callers).
- "Auto" in Settings is now a resolve-and-set action: it picks the best
available backend (PipeWire if available, else System), stores that
concrete value and updates the dropdown + device picker, so backend_type
is never null.
- Explicit PipeWire (DAC passthrough) and ALSA Direct (exclusive) configs
are untouched and remain bit-perfect.
cc
cpal 0.17.3 requires alsa 0.11, which in turn pulls alsa-sys 0.4. The
`links = "alsa"` constraint permits only one alsa-sys in the dependency
graph, so qbz-audio's direct alsa stack is raised in lockstep:
alsa 0.10 -> 0.11 and alsa-sys 0.3 -> 0.4. Both workspace lockfiles
(crates/ and src-tauri/) are updated to cpal 0.17.3 / alsa 0.11.0 /
alsa-sys 0.4.0.
No source changes required: alsa_direct.rs uses only the stable
high-level alsa API (pcm, HwParams, Format, Direction, ValueOr, mixer),
and the snd_lib_error_set_handler FFI in alsa_error_handler.rs is
unchanged in alsa-sys 0.4. Both workspaces pass cargo check.
cc
The stream-recreate decision only forced a rebuild on a decoded-format change
for bit-perfect backends (DAC passthrough, ALSA Direct, CoreAudio exclusive).
The default/PipeWire path relied on Stop dropping the stream to get a fresh one
at the new rate; once that drop was deferred to avoid a track-change click
(e93fcaec), the PipeWire node stayed locked to the first track's rate for all
subsequent tracks — a regression vs 1.2.10. Make any decoded-format change
rebuild the stream on every backend; same-rate tracks still reuse it, so the
click fix is preserved.
Verified on hardware via pw-top: PipeWire without passthrough now switches rate
per track; PipeWire with passthrough and ALSA Direct unchanged (no regression).
cc
is_pipewire_available() shelled out to `pactl info`, which lives in
pulseaudio-utils and is absent on PipeWire-only systems — hiding the PipeWire
backend even though PipeWire was running. Detect via the
$XDG_RUNTIME_DIR/pipewire-0 socket first (no extra package required), with
pw-cli and pactl as fallbacks.
cc
A single transient 5xx / timeout / reset on the next track's file/url or a CMAF
segment used to be terminal and skipped the track. Add a retry wrapper (3
attempts, exponential backoff + jitter) around get_file_url, the init segment,
and per-segment fetches. Retry only transient errors (network/timeout, 5xx, 429
honoring Retry-After); a real 404 is classified as TrackUnavailable and
propagates immediately to the bounded skip path.
cc
Long sessions died when a single transient blip flipped the app to offline and
the queue then skipped every cached-but-not-downloaded track. Harden the
detector:
- Consecutive-failure hysteresis: declare offline only after 2 failed polls,
not a single blip.
- Replace the HEAD https://1.1.1.1 probe with a concurrent race of cheap
generate_204 endpoints, so a saturated link (long Hi-Res tracks) no longer
times the probe out.
- Positive liveness override: if a segment downloaded within the last 45s we
are online by definition, regardless of the probe.
cc
Recovered from 63ecc324 (authored on the native-port branch, never merged to
pre-release). Backend-only, no Slint. Scales the prefetch cap down on slow
links and during audio underruns, and exposes the per-segment download signal
the offline detector reuses as a positive liveness override (#467).
cc
Polish + consistency:
- Compact playlist header (cover 150px; nav->header spacing
homologated with AlbumView); description truncated to ~2 lines +
"Read more" modal; custom-artwork buttons only on cover hover.
- New Typography.section (18px) token for hero/section titles,
applied across Album / Artist / Mix / Playlist + Home carousels.
- Shorter track rows app-wide (50px row, 36px artwork, -1 fonts);
+3px column-header/divider gap; wider track-number column.
- Reusable ExpandableSearch (the JumpNavBar page-search pattern:
magnifier -> opens left -> X to close) in the playlist action row.
- Custom ListScrollbar (thicker, min thumb height, own gutter) since
Slint's ListView scrollbar can't be styled.
Sort:
- Default / Title / Artist / Album / Duration with asc/desc per
field, plus Custom (manual order persisted in library.db with
up/down reorder). Client-side search + sort combined.
WIP: track drag-and-drop onto sidebar playlists
- DragState/DragActions globals, drag source on the shared TrackRow,
follow-cursor DragGhost, sidebar playlist rows self-detect the drop
via absolute-position, multi-select group drag, add_tracks_to_playlist
on drop, Flickables freeze scroll during a drag.
- KNOWN ISSUE: the ghost is offset from the cursor (the row
absolute-position + pointer mapping is off, esp. inside Flickables).
Diagnostic logging is left in (TrackRow debug() + a start log) to
pin down the wrong term before the generic fix.
cc
Large playlists (2000+ tracks) were sluggish. Addressed the root
causes; this strategy is reusable for the library / mixtapes / tracks
views.
- Virtualization: the track list is now a ListView (only visible
rows are instantiated) instead of a Flickable with a for over all
rows. The header + column header are pinned above it.
- Smaller images: track rows use ImageSet::smallest() (new) instead
of best() — they are 40px thumbnails, so best() was downloading
mega/large covers.
- Per-target decode size: row thumbnails decode to 96px instead of
the 264px card size, so a 2000-row model holds ~75MB of pixels
rather than ~550MB.
- Track-number column widened to fit 4-digit positions.
- Back-to-top button: appears once scrolled past one viewport
(bottom-right), scrolls the list home. A shared rule for big lists.
- Custom-artwork buttons only show on cover hover.
cc
Local, Tauri-shared playlist folders (library.db) now organize the
sidebar:
- New folders.rs controller (create / rename / delete / move via
qbz-library); sidebar.rs reworked to a flattened entries model
(folder headers + grouped playlists + root playlists) with a
session expand cache, rebuilt without a refetch on expand/move.
- Sidebar renders folders (expandable, with playlist counts) and
playlists (indented inside folders); a hover "..." opens a context
popup: Move to folder (root + each folder) for playlists, Delete
folder for folders.
- Header gains a New-folder button + a CreateFolderModal; folders
reload after create/delete; moves are optimistic + persisted.
Adds folder-open / folder-plus icons.
cc
Wires qbz-library into the Slint app (new library_db module) so the
local, Tauri-shared organization data is read/written from the same
per-user library.db (<data_dir>/qbz/users/<id>/library.db). The DB is
opened per-operation on a blocking thread (rusqlite Connection is
!Send).
- Header collage: the playlist cover is now a 4-up collage of the
first tracks' covers (Tauri parity); falls back to the server
image, then the first track, then a placeholder.
- Custom artwork: owner-only buttons on the cover set/clear a custom
image. Set copies the picked file into <cache_dir>/qbz/artwork and
stores the path in playlist_settings.custom_artwork_path (shared
with Tauri); it's loaded directly from disk and overrides the
collage.
- Edit description: the edit modal gains a description field
(prefilled), saved through update_playlist alongside the name.
cc
The playlist detail view gains an edit mode (owner-only), reusing
TrackRow's multi-select-mode:
- A "Select" toggle in the header turns on per-row checkboxes and a
bulk action bar (N selected, Select all, Remove).
- toggle-select is now context-aware (playlist vs artist Popular);
PlaylistState tracks selected-count for the bar.
- Remove calls remove_tracks_from_playlist with the selected ids,
then reloads the playlist and leaves edit mode.
Track reordering is the remaining playlist-editing piece (deferred —
it is the most involved).
cc
Completes playlist CRUD. The detail header shows an edit button only
for the user's own playlists (is-owner = the open id is in the sidebar
list); it opens an EditPlaylistModal:
- Save -> update_playlist (rename); refreshes the detail header name
and the sidebar list.
- Delete -> delete_playlist; closes the modal, navigates back, and
refreshes the sidebar.
Adds the pen-line icon (Lucide) used by the edit button.
cc
- SidebarState/SidebarActions + crate::sidebar: the user's playlists
load into the left-nav list on shell entry; clicking a row opens
the playlist detail view, with the open playlist highlighted
(active-id). Empty state shown when there are none.
- The sidebar "+" opens a CreatePlaylistModal (name field + public
toggle); submit calls create_playlist, refreshes the sidebar, and
navigates to the new playlist.
cc
First piece of the playlist module: a detail view at the AlbumView /
MixView level, reusing the shared TrackRow (show-artwork + show-album +
context menu, so Add to playlist / Go to album / etc. work in it).
- PlaylistState + crate::playlist controller (get_playlist -> header
metadata + TrackItem rows; cached track list backs play-all /
per-track play; artwork jobs resolve row covers + the header cover).
- PlaylistView.slint: cover + "Playlist" label + name + description +
"owner - N tracks - duration" info line + circular actions (Play
all, Shuffle, Favorite) over a column-headed track list.
- ContentView.playlist + NavEntry::Playlist (back/forward history) +
navigate_playlist; the Favorites > Playlists card now opens it.
- media-action: playlist play-all / shuffle / favorite (add/remove
favorite) + per-track play-from-here.
cc
Ports Tauri's behavior where clicking an album card's artist name
navigates to that artist (the card body still opens the album).
- AlbumCardItem gains artist-id; populated in every album-card
constructor (home/search/label/foryou/favorites). Artist-page
release cards leave it empty (their subtitle slot shows the year,
not the artist, so it stays non-clickable).
- AlbumCard wraps the artist name in a TouchArea that emits
media-action("artist", artist-id, "open") and brightens on hover
only when an id is known.
- main.rs handles ("artist","open") -> open the artist page;
FavoritesView (which only forwarded clicked) now forwards the
artist action.
cc
Connects the previously-inert track context-menu items now that the
backends exist:
- Add to library -> add_favorite("track").
- Go to album / Go to artist -> resolve the track (get_track) to its
album / performer and navigate there.
- Create radio -> Qobuz track radio (new get_radio_track core wrapper
over the existing qbz-qobuz endpoint + playback::play_track_radio).
- Share Qobuz link -> https://www.qobuz.com/track/{id} to clipboard
(new arboard dep + share module).
- Share Song.link -> Odesli (api.song.link) pageUrl resolution to
clipboard.
- Add to playlist -> a global picker modal (PlaylistPickerModal +
PlaylistPickerState/Actions + playlist_picker controller) listing
the user's playlists; picking one calls add_tracks_to_playlist.
Add to mixtape / Make available offline / Track info stay unwired
(no backend yet).
cc
Collapses the three near-identical track rows (AlbumView's inline
TrackRow, ArtistPage's inline TrackRow with multi-select, and the
shared SearchTrackRow) into a single primitives/TrackRow driven by
flags, backed by one TrackItem (superset). Foundation for the
upcoming playlist module, which renders the artwork variant heavily.
- New primitives/TrackRow.slint with: show-artwork (cover+number vs
number cell), show-album, show-favorite, show-download,
multi-select-mode (selection checkbox). One media-action wires
play / play-next / queue / favorite / download / toggle-select +
the TrackContextMenu.
- TrackItem is the unified model (superset of the old AlbumTrackItem
+ SearchTrackItem: number, album, selected, artwork all present;
unused fields stay empty per surface).
- Migrated AlbumView (show-artwork:false, show-download:true),
ArtistPage Popular (show-artwork:false, multi-select-mode),
Search, Favorites, Mix (show-artwork:true, +album/+favorite).
Deleted SearchTrackRow.slint and both inline TrackRows.
- Rust: AlbumTrackItem + SearchTrackItem -> TrackItem across
album/artist/search/favorites/mix/main; search's internal row
struct renamed TrackRow -> TrackRowData to free the TrackRow name
for the Slint component. 46 tests pass.
cc
Track row reuse: the mix views had a bespoke MixTrackRow with a dead
favorite/more controls (no context menu), while search / favorites /
artist already share SearchTrackRow (which has the TrackContextMenu).
Consolidated:
- SearchTrackRow gains optional show-album (album column) +
show-favorite (standalone heart) — off by default, so search /
favorites rows are unchanged. SearchTrackItem gains an `album`
field.
- MixView drops MixTrackRow/MixTrackItem and renders SearchTrackRow
with show-album + show-favorite. A row "play" starts the mix from
that track; the context-menu actions (play next / queue / favorite
/ view album / view artist / ...) now bubble through media-action
and work in the mix views too.
For You skeleton: the For You tab showed a bare spinner during its
lazy load; it now shows the same pulsing HomeSkeleton as Home (the
static Qobuz Mixes tiles stay visible above it). Editor's Picks is
served from the shared discover-index cache (Tauri parity) and shows
the skeleton during the initial load via the existing HomeView guard.
cc
The mix detail pages were a thin header + flat track list. Rebuilt to
mirror Tauri's playlist-detail layout at the AlbumView port quality.
- Header: a per-mix gradient artwork (224px, same gradients as the
QobuzMixesRow tiles) + "Qobuz Mixes" label + title + description +
"N tracks • duration" info line + circular action buttons (Play
all, Shuffle, Save-as-playlist, Refresh) — matching AlbumView's
header anatomy.
- Track list with a real column header (#, Title, Album, Duration,
Quality) over rich MixTrackRows: artwork thumbnail + number/play
cell (TrackPlayCell, artwork-on), title + explicit badge, artist,
album column, duration, quality, favorite + more controls.
- MixTrackItem gains an album column; MixState carries track-count +
total-duration for the info line.
- Play all / per-track play (from that track), Shuffle (plays a
random order without mutating the list), and Refresh (re-fetches)
wired through media-action.
Fixes a wiring bug: the tile click sends id=kind/action="open", but
the handler matched on the action and passed "open" as the mix kind —
now it reads the kind from id.
cc