Commit Graph
281 Commits
Author SHA1 Message Date
vicrodh 4dddd36073 feat(slint): per-context genre filter + favorites genre filtering
Make genre_filter.rs selection per-context (HashMap keyed by "discover" /
"favorites" + a current context the popup edits), so favorites filters
independently from Discover. Discover keeps its server-side path
unchanged (reads filter_ids("discover") explicitly; its genre buttons set
the "discover" context on open). Favorites get a genre button in both
toolbars that opens the shared popup in the "favorites" context, plus the
popup overlay; derive_tracks / derive_albums filter client-side by the
selected genre NAMES (selected_names expands parents to descendants).
on_toggle / on_clear branch on the current context to re-derive favorites
instead of re-fetching Discover. AlbumSummary gains an optional `genre`
(favorites track album objects carry it; TrackItem can't, so a track
id->genre map backs the tracks filter). Persisted per-context (legacy
flat selection migrates to "discover").

cc
2026-05-27 08:03:56 -06:00
vicrodh 9c273119a6 feat(slint): favorites load error + retry state
navigate_favorites stores the load error in FavoritesState.load-error
(cleared on each load attempt); the view shows a "Couldn't load
favorites" + detail + Retry block when set, and the tracks/albums tabs
are gated so the error replaces them instead of showing alongside an
empty state. Retry re-navigates the current tab.

cc
2026-05-27 07:40:22 -06:00
vicrodh 2fce47bd06 feat(slint): favorites tracks group-by (Off / Album / Artist / Name)
Add a group QbzSelect to the Tracks toolbar. derive_tracks reorders the
visible rows by the group key (album+title, artist+album+title, or
title) so a group's tracks sit together. Visible group-header rows are
deferred until the list is virtualized (the list is a plain for today);
this is the group-ordering half, matching how Tauri's tracks tab uses
grouping as the ordering mechanism.

cc
2026-05-27 07:13:07 -06:00
vicrodh 39b7b6d2f1 feat(slint): favorites albums group-by (Off / A-Z / Artist)
Add a group QbzSelect to the Albums toolbar. derive_albums buckets the
filtered set by artist name or by the title's first letter (# for
non-alphabetic), sections ordered alphabetically, rendered through the
shared AlbumCollectionView grouped path. albums-shown carries the
post-filter count so the count line + no-match state stay correct in
grouped mode.

cc
2026-05-27 07:05:17 -06:00
vicrodh 66a9f9da22 fix(slint): favorite state on album cards + un-favorite in place
AlbumCardItem gains is-favorite (filled accent heart on the card +
"Remove from favorites" in the list-row menu) — favorites albums set it
true so the heart shows its state, which it never did before.

Un-favoriting from the favorites list (track heart or album card heart)
now removes the item: the row/card fades out (new `removing` field drives
a 250ms opacity animation on TrackRow / AlbumCard / AlbumListRow), the
favorite is removed on the server (+ fav_cache for tracks), and the row
is dropped from both the full and visible models after the fade. All
TrackItem / AlbumCardItem initializers carry the new fields.

cc
2026-05-27 06:56:26 -06:00
vicrodh 57bace3e1d feat(slint): favorites albums search + sort + shuffle
Add an expandable search (title / artist), a sort QbzSelect
(Default / Title A-Z / Title Z-A / Artist A-Z) and a Shuffle header
action (play a random visible album) to the Albums tab. albums-visible
derives from the full albums set via derive_albums (shared model on the
no-filter/default fast path, filtered+sorted clone otherwise — the
label.rs::derive_releases pattern), and the count reflects the filter.

cc
2026-05-26 23:09:17 -06:00
vicrodh b4b11a59e5 feat(slint): favorites albums list view + grid/list toggle
Replace the bare favorites albums grid with the shared
AlbumCollectionView plus a grid/list segmented toggle and a count line —
adding the list view that was missing (the AlbumListRow extras now fill
via the shared album_map). Album card actions (play / queue / favorite /
go-to) route through a new album-action callback to the album
media-action arms; the artist link still opens the artist.

cc
2026-05-26 22:46:52 -06:00
vicrodh e9836184e7 feat(slint): favorites tracks multi-select + bulk remove
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
2026-05-26 22:40:06 -06:00
vicrodh 6fc337a472 style(slint): shrink track row selection checkbox to ~60%
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
2026-05-26 22:31:34 -06:00
vicrodh 43b0c28aee feat(slint): favorites tracks Play all + Shuffle header actions
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
2026-05-26 22:28:19 -06:00
vicrodh 708fd50c87 feat(slint): favorites tracks search + results count
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
2026-05-26 22:18:02 -06:00
vicrodh fd5246a7ce fix(slint): favorites track row album link did not navigate
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
2026-05-26 22:10:09 -06:00
vicrodh 8dedc2a958 feat(slint): favorites tracks show album column + favorite heart
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
2026-05-26 21:35:35 -06:00
vicrodh d531f60c9c refactor(slint): promote multi-select bulk bar to a shared primitive
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
2026-05-26 21:30:21 -06:00
vicrodh 610c6c1f79 refactor(slint): share album grid/list/grouped renderer
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
2026-05-26 21:25:13 -06:00
vicrodh 1c9c9de40b refactor(slint): promote ToggleButton to a shared primitive
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
2026-05-26 21:18:35 -06:00
vicrodh 12048bcee0 fix(slint): label albums missing artist/quality/date + favorite + load-more
/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
2026-05-26 20:10:09 -06:00
vicrodh 56c924d8ea feat(slint): Label landing page + All Releases sub-view
Port the Tauri LabelView module to native Slint, reusing existing
components and extending where needed (no duplication).

Landing (new LabelPageView): circular header + description read-more +
Follow toggle, sticky JUMP TO bar, Popular Tracks (multi-select, 5->20->50
reveal, Play all), and Releases (+ See all) / Critics' Picks / Playlists /
Artists / More Labels carousels.

All Releases sub-view (LabelReleasesView): circular header + album count,
toolbar (local search, group-by-artist, Hi-Res filter, sort) + grid/list
toggle, load-more, states. TYPE column now populated.

Split the label route: ContentView::label -> LabelPageView (landing),
ContentView::label-releases -> LabelReleasesView; add NavEntry::LabelReleases.
label.rs load_label_page parses /label/page + getAlbums + explore + the
favorite-labels set; derive_releases re-derives the sub-view list.
Reuse Carousel/PlaylistCarousel/ArtistCarousel/TrackRow/AlbumCard/
AlbumListRow/JumpNavBar/QbzSelect/ExpandableSearch; extend ArtistCard/
ArtistCarousel (follow-mode/follow-kind); promote CircleAction to a shared
primitive; JumpNavBar gains show-search. Wire label media-actions:
follow, play-top, open (more-labels), see-all.

cc
2026-05-26 18:35:42 -06:00
vicrodh b2004e6867 feat(slint): TrackRow favorites + artist/album links, PlaylistView, LabelView, scrollbar
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
2026-05-26 16:36:02 -06:00
vicrodh a5383ba502 chore(slint): flag the remaining unwired Appearance settings red
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
2026-05-26 16:36:02 -06:00
vicrodh 4e329b4c8b chore(slint): flag unwired Appearance settings for the Tauri to Slint audit
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
2026-05-26 15:09:29 -06:00
vicrodh 178d135328 feat(slint): Playlist Manager + search most-popular hero polish
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
2026-05-26 15:09:29 -06:00
vicrodh 93a7bbbc3c feat(slint): double-click a track row to play/pause
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
2026-05-26 15:09:29 -06:00
vicrodh d30e3d658d feat(slint): sidebar context menus, in-app toast, SearchView + Playlist Manager polish
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
2026-05-26 12:21:59 -06:00
vicrodh 2bf6feaa6d feat(slint): Playlist Manager view + shared nav/state/shell wiring
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
2026-05-26 01:07:08 -06:00
vicrodh 7aa3018cf6 feat(slint): replicate Settings > Appearance section (1:1 UI, mostly unwired)
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
2026-05-26 01:07:08 -06:00
vicrodh 9f70a5ac5a feat(slint): sidebar Playlists toolbar + folders + micro-collage refinements
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
2026-05-26 01:07:08 -06:00
vicrodh c5c124fb0c feat(slint): Discover "View all" pages + reusable AlbumListRow + recently-played metadata
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
2026-05-26 01:07:08 -06:00
vicrodh 21af0f63f8 feat(slint): favorites tabs as history pages + homologated nav-bar/title
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
2026-05-25 17:41:35 -06:00
vicrodh abff875a0e fix(slint): album card title no longer clamped to the artist name width
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
2026-05-25 17:05:44 -06:00
vicrodh de826bf560 feat(slint): playlist detail polish, sort (incl. custom) + WIP track drag
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
2026-05-22 18:26:55 -06:00
vicrodh 85223b03ff perf(slint): virtualize playlist track list + lighter artwork
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
2026-05-22 09:27:45 -06:00
vicrodh 4fe034d86b feat(slint): playlist folders in the sidebar
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
2026-05-22 08:44:37 -06:00
vicrodh 5ac5c60c57 feat(slint): playlist collage + custom artwork + edit description
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
2026-05-22 08:30:16 -06:00
vicrodh 5aae0195f6 feat(slint): playlist track editing (multi-select remove)
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
2026-05-22 07:01:21 -06:00
vicrodh 75cdb10ee1 feat(slint): rename + delete playlist (edit modal)
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
2026-05-22 06:47:57 -06:00
vicrodh f387114ecf feat(slint): sidebar playlists list + create-playlist modal
- 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
2026-05-22 03:57:54 -06:00
vicrodh 1be78193e2 feat(slint): playlist detail view
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
2026-05-22 03:02:09 -06:00
vicrodh 75d8571be3 feat(slint): clickable artist name on AlbumCard -> artist page
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
2026-05-22 02:54:52 -06:00
vicrodh db8a6ac5b1 feat(slint): wire track context-menu actions (favorite/radio/share/playlist/nav)
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
2026-05-22 02:54:52 -06:00
vicrodh 1ebec6cbc1 refactor(slint): one shared TrackRow + TrackItem across all surfaces
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
2026-05-22 00:31:42 -06:00
vicrodh 469c2495bf refactor(slint): mix rows reuse SearchTrackRow; For You uses HomeSkeleton
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
2026-05-21 23:46:13 -06:00
vicrodh 61882166f7 feat(slint): rebuild mix views to AlbumView-level rendering
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
2026-05-21 22:48:49 -06:00
vicrodh 3b8f0a4597 feat(slint): Qobuz mix detail views (DailyQ / WeeklyQ / FavQ / TopQ)
The For You Qobuz Mixes tiles now open functional mix detail pages.

  - New MixView: header (mix name + description + Play all) over the
    mix's track list (reusing SearchTrackRow). Clicking a track plays
    the whole mix from there; Play all starts at the top.
  - crate::mix resolves each mix to a track list from available
    backend: DailyQ/WeeklyQ -> /dynamic/suggest seeded from the local
    play-history track ids; FavQ -> favorite tracks shuffled (xorshift,
    no rng dep); TopQ -> tracks aggregated from the user's playlists.
    The loaded list is cached so play-all / play-from-track build the
    queue without re-fetching.
  - Backend: ported /dynamic/suggest into qbz-qobuz
    (QobuzClient::get_dynamic_suggest — POST, items parsed leniently
    into Vec<Track>) + QbzCore::get_dynamic_suggest.
  - ContentView.mix + NavEntry::Mix { kind } so each mix is a history
    entry; the tile click records + navigates. New MixTrack artwork
    target. playback::play_tracks generalizes the radio queue builder
    with a start index (radio reuses it at 0).

Tauri's exact generation (listened-track analysis payloads, playlist
play-stats ranking) is approximated; same surfaces + playback, sourced
from the backend the MVP has.

cc
2026-05-21 08:16:31 -06:00
vicrodh c19a5b84dd feat: For You — radio, album suggestions, rediscover, more-from-library
Completes the remaining For You sections by porting two Qobuz API
endpoints down into qbz-core and adding the local "forgotten
favorites" approximation.

Backend ports (qbz-models + qbz-qobuz + qbz-core):
  - /album/suggest -> AlbumSuggestResponse, QobuzClient::get_album_suggest,
    QbzCore::get_album_suggest.
  - /radio/artist + /radio/album -> RadioResponse, get_radio_artist /
    get_radio_album on the client + core. (Faithful ports of the legacy
    src-tauri/api/client.rs methods — same paths, params, shapes.)

For You sections:
  - Radio Stations — album-seeded radio tiles (recent + favorite
    albums, deduped, capped 12), rendered with RadioCard; click starts
    a real Qobuz album radio.
  - More From Your Library — /album/suggest seeded from the most recent
    played album (else first favorite).
  - Rediscover Your Library — favorite albums not in the local
    play-history (approximation of Tauri's reco-DB forgotten
    favorites).
  - Spotlight's RADIO card + the artist "radio" action now create a
    real Qobuz artist radio (playback::play_artist_radio /
    play_album_radio build a queue from the radio track list and play).

Section order now matches Tauri: Qobuz Mixes, Release Watch, Radio
Stations, Recently Played Tracks/Albums, Your Top Artists, More From
Your Library, Rediscover, Artists to Follow, Spotlight.

Still backend-blocked: the Qobuz mix detail views (DailyQ/WeeklyQ/etc.)
and the smart radio_engine (the Qobuz radio used here is the simpler
/radio/* endpoint, not the local pool builder).

cc
2026-05-21 06:43:53 -06:00
vicrodh 83b7721b10 feat(slint): For You Qobuz Mixes row
Ports Tauri's QobuzMixesRow as the first For You section — four
220px navigation tiles (DailyQ / WeeklyQ / FavQ / TopQ) with the
1:1 gradients, badge, name, and description. Pure navigation, no
per-tile data. The mix detail views they open don't exist in the
Slint MVP yet, so the click is logged for now.

cc
2026-05-21 02:01:23 -06:00
vicrodh c27f6e1b9f feat(slint): For You Spotlight section
Ports Tauri's SpotlightLite — one favorite artist highlighted with a
hero and a content row. Backed by get_artist_page.

  - Spotlight component: header (title + "Shine a light…" subtitle),
    hero (round portrait + ARTIST label + name + play-top-tracks /
    open-artist circle buttons), and a draggable Flickable row
    mixing a TOP TRACKS card, a RADIO card, and the artist's albums.
  - New RadioCard (port of RadioCardLite): a 180px tile with the seed
    artwork centered on an elevated panel, a wordmark label over it,
    and a title/subtitle below; hover reveals play. (The Tauri
    palette-derived panel colour is approximated with a neutral
    elevated panel.)
  - foryou::load_spotlight rotates among the top-5 favorites by
    wall-clock seconds, fetches get_artist_page, and extracts up to 6
    albums (album > live > ep-single > compilation, deduped) plus
    whether the artist has top tracks. ForYouState gains the
    spotlight-* fields; two new artwork targets feed the portrait +
    album covers.
  - Wiring: ForYouView renders Spotlight after Artists to Follow.
    play-top-tracks routes to the existing artist play-top action;
    "start radio" has no artist-radio backend yet so it falls back to
    playing top tracks (logged).

Remaining For You: Radio Stations section (old card design), Qobuz
Mixes row, and the reco-DB sections (More from library, Rediscover).

cc
2026-05-21 01:51:31 -06:00
vicrodh 85ecf79a2d feat(slint): dedicated For You view with artist sections
Replaces the ad-hoc For You (a single album set crammed into
HomeState.sections) with a dedicated, ordered ForYouView/ForYouState
so the tab can mix album, track, and artist carousels — closer to
Tauri's For You. Lazy-loaded once on first open.

Sections (all reuse existing cards, no new card components):
  - Release Watch (album Carousel) — get_release_watch.
  - Recently Played Tracks (SlimCarousel) — local play-history.
  - Recently Played Albums (album Carousel) — local play-history.
  - Your Top Artists (ArtistCarousel) — the user's favorite artists
    via get_favorites.
  - Artists to Follow (ArtistCarousel) — similar artists seeded from
    a few favorites via get_similar_artists, excluding ones already
    followed (Follow chip shown).

Plumbing:
  - crate::foryou controller (load/apply/artwork). Five ForYou
    artwork targets feed the dedicated models.
  - select_tab("forYou") clears HomeState.sections (the home-content
    `for` renders nothing) and the For You content comes from
    ForYouView; HomeView routes the For You tab to ForYouView and
    gains an open-artist callback (wired through AppShell).
  - ensure_for_you_loaded lazy-loads on tab open (pill, header
    dropdown, and back/forward history).
  - home.rs no longer builds a For You section set (removed the
    redundant release-watch/favorites fetch on every home load).

Still to come (next increments): Qobuz Mixes row, Radio Stations
(old card design), Spotlight hero, and the reco-DB sections (More
from library, Rediscover) — those need backend the MVP lacks or the
mix detail views.

cc
2026-05-21 00:33:11 -06:00
vicrodh 21a909185f fix(slint): genre popup grew to full window height
The popup root Rectangle set width but no height, so as a direct
child of the home overlay it defaulted to the parent's full height —
the panel ballooned vertically. Bind height to the content layout's
preferred-height so it hugs its content.

cc
2026-05-20 23:35:26 -06:00
vicrodh 9fce0939d1 feat(slint): genre filter advanced view + child-genre filtering
Adds the advanced view to the Discover genre filter and recovers the
child-genre filtering the discovery-v2 home redesign dropped.

Child-genre recovery:
  - The old (pre-redesign) home expanded a parent selection to its
    descendant genre NAMES and filtered content client-side; the
    discovery-v2 redesign passed only the explicitly-selected ids to
    the discover index, so selecting a parent stopped covering its
    children. Here filter_ids() expands the selection to all loaded
    descendant ids and feeds that to get_discover_index; selecting a
    genre eager-loads its descendant subtree so the expansion is
    complete.

Advanced view:
  - Search box + a scrollable 3-level genre tree (parent / child /
    grandchild), flattened in Rust into visible GenreTreeRow rows
    honoring per-node expansion. Each row: indent by level, expand
    arrow (lazy-loads children on first expand), checkbox, name, and
    loaded child count. Search returns a flat match list across all
    loaded genres.
  - Opening the advanced view eager-loads every parent's children so
    counts show up front; grandchildren stay lazy until a child is
    expanded.

State/wiring:
  - GenreTreeRow struct; GenreFilterState gains tree + search-query;
    GenreFilterActions gains toggle-expand + search.
  - genre_filter STATE moves to LazyLock (HashMap/HashSet aren't
    const) and gains children map, expanded set, and search query.
  - main.rs wires toggle-expand (lazy child load), search, and the
    advanced-open eager child load; toggle now eager-loads
    descendants before the re-fetch.

cc
2026-05-20 23:06:43 -06:00