Commit Graph
54 Commits
Author SHA1 Message Date
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 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 1efa6fc745 feat(slint): Discover Editor's Picks + For You tabs
Makes the three-tab Discover pill (Home / Editor's Picks / For You)
functional, plus the matching Discover header-dropdown rows. Mirrors
Tauri's DiscoveryView: all tabs draw from one fetch and swap which
curated section set is shown — no extra round-trips on tab switch.

home.rs:
  - load_home now builds three section sets from a single
    get_discover_index call (cloning the editorial containers):
      * home — the existing balanced set.
      * editorPicks — editorial only (New Releases, Qobuzissimes,
        Press Accolades, Most Streamed, Ideal Discography, Albums of
        the Week), ordered per Tauri's DEFAULT_PREFS.editorPicks.
      * forYou — Release Watch (get_release_watch "artists"). The
        deeper personalized sections (Qobuz mixes, similar albums,
        spotlight) need reco-DB backend the Slint MVP lacks, so this
        is the honest first cut.
  - apply_home caches all three sets in a thread-local; select_tab
    swaps HomeState.sections to the chosen set and returns the
    Section-targeted artwork jobs to re-fire. Reuses the existing
    Section artwork target — no new targets.
  - map_full_album maps release-watch's catalog Album to a card.

Slint:
  - HomeState gains active-tab; HomeActions.select-tab wired to the
    pill (active state derived from active-tab) and to the Discover
    dropdown rows (route "discover-<tab>") via header-menu-navigate.
  - The Home-only sections (Recently Played albums/tracks, Popular)
    are gated to active-tab == "home" so Editor's Picks / For You
    render just their own set.

cc
2026-05-20 21:33:06 -06:00
vicrodh a40c6ce569 feat(slint): Library > Favorites view (Tracks / Albums / Artists)
Wires the Library header dropdown to a real Favorites view. Mirrors
Tauri's FavoritesView.svelte core: a tab bar over per-tab content,
each tab lazy-loaded on first open.

Slint:
  - ContentView gains `favorites`. NavMenuEntry gains a `route`
    field; the Library dropdown rows carry "favorites-tracks" /
    "-albums" / "-artists" / "-playlists" / "-labels". The
    HeaderMenuOverlay click bubbles the route up through AppShell ->
    AppWindow -> Rust (new header-menu-navigate callback).
  - FavoritesState / FavoritesActions globals. Tracks reuse
    SearchTrackItem (so the shared SearchTrackRow renders them with
    per-row cover art), albums reuse AlbumCardItem + the Discover
    AlbumCard, artists use a new FavoriteArtistItem round-portrait
    grid.
  - New ui/favorites/FavoritesView.slint: title, tab bar with live
    counts + active underline, and the three populated tabs.
    Playlists / Labels show "Coming soon" pending their backing
    views. AppShell routes ContentView.favorites.

Rust:
  - crate::favorites controller — load_favorites parses the raw
    get_favorites JSON (`{ <type>: { items, total } }`) into typed
    qbz-models Track / Album / Artist, maps to the Slint structs.
    Lazy per-tab: select-tab and the header route both call
    navigate_favorites, which fetches only the opened tab.
  - artwork::ArtworkTarget gains FavoriteTrack / FavoriteAlbum /
    FavoriteArtist; covers stream in via the shared pipeline.
  - main.rs: navigate_favorites + on_header_menu_navigate +
    FavoritesActions (select-tab / open-album / open-artist /
    play-track / track-action). No new qbz-core method —
    get_favorites already existed.

Deferred: Playlists + Labels tab content, full pagination (one 500
page is fetched per tab), favorites preferences (tab order).

cc
2026-05-20 20:53:37 -06:00
vicrodh cdb93341d0 feat(slint): ArtistsByLocationView (Port 3 complete — third click target)
Wires the Origin section's location link to the scene-discovery
view. Completes the three artist-network click targets (Musician,
Label, Location).

Slint:
  - ContentView gains `location`; NavEntry mirrors with the full
    `Location { mbid, area_id, area_name, country, genres, tags }`
    payload so back/forward replays the scene.
  - LocationViewState / LocationViewActions globals carry the scene
    header (label + genre summary) and a paginated artist grid
    (LocationArtistItem: qobuz id, name, genres line, image).
  - New `ui/location/ArtistsByLocationView.slint`: a scene header
    (map-pin + location + genre summary) over a wrapping round-
    portrait artist grid with load-more. open-artist routes the
    card click to the artist page.
  - AppShell routes ContentView.location; new map-pin.svg (Lucide).

Rust:
  - `crate::location_view` controller — load_scene, apply_scene,
    append_scene, reset_scene, artwork_jobs. Maps LocationCandidate
    rows to the grid (qobuz_name fallback to mb_name).
  - artwork::ArtworkTarget gains LocationArtist { index }.
  - The Origin metadata load now caches LocationParams (area +
    affinity seeds) in a cross-thread Mutex; reset_network_sidebar
    clears it so a stale click can't fire for the wrong artist.
    artist::location_params() exposes it to the click handler.
  - main.rs: navigate_location + apply_entry route +
    on_location_clicked (reads the cached params) +
    LocationViewActions open-artist / load-more.

Backend (prior commit) supplied discover_artists_by_location;
this commit is the Slint + wiring layer.

cc
2026-05-20 20:31:08 -06:00
vicrodh 302f708694 feat(slint): LabelReleasesView (Port 3, second of three click targets)
Wires the artist network sidebar's Labels section to a real
destination. Mirrors Tauri's LabelReleasesView.svelte core: a label
header (image + name + release count) over a wrapping album grid
with load-more pagination. (Sort / filter Hi-Res / group-by-artist
from the Tauri view are deferred — the core browse experience is in.)

Slint:
  - ContentView gains `label`; NavEntry mirrors with
    `Label { id, name }`.
  - LabelState / LabelActions globals carry the header (id, name,
    image-url + decoded image) and the AlbumCardItem grid + total.
  - New `ui/label/LabelReleasesView.slint`: label header card,
    then the same absolute-position album grid + load-more pattern
    the artist/musician views use. Reuses the Discover AlbumCard
    (artist-as-subtitle is correct here — a label's catalog is
    various artists).
  - AppShell routes ContentView.label and forwards open-album +
    media-action.

Rust:
  - `crate::label` controller — load_label (parallel-ish
    get_label_page + get_label_albums), load_more_albums,
    apply_label, append_albums, apply_image (label header), and
    artwork_jobs. extract_label_image handles the flexible
    /label/page image value (string or mega/extralarge/.../small
    object), same precedence as the Svelte.
  - artwork::ArtworkTarget gains LabelAlbum { index }.
  - main.rs: navigate_label + apply_entry route + on_label_clicked
    (sidebar) + LabelActions::on_load_more.
  - No new qbz-core methods — get_label_page / get_label_albums
    already existed.

cc
2026-05-20 20:09:09 -06:00
vicrodh cfe083898d feat(slint): MusicianPageView (Port 3, first of three click targets)
Wires the sidebar's musician-clicked callback into a real
destination. Mirrors Tauri's MusicianPageView.svelte plus the
"jump to artist page when Confirmed" shortcut.

Slint:
  - ContentView gains a `musician` variant; NavEntry mirrors with
    `Musician { name, role }` so the back/forward history can
    re-fire the page with the original payload.
  - MusicianState / MusicianActions globals carry the resolved
    musician metadata (name, role, confidence) + the paginated
    Appears On grid (MusicianAppearanceItem mirrors the Tauri
    interface — album_id, title, artist_name, year,
    role_on_album, artwork URL/handle).
  - New `ui/musician/MusicianPageView.slint`: header (avatar +
    name + role), Bands & Projects chips when populated, and an
    Appears On grid built from the same absolute-position pattern
    the artist release grid uses. Load more button drives
    MusicianActions::load-more.
  - AppShell routes ContentView.musician to MusicianPageView and
    forwards open-album.

Rust:
  - `crate::musician` controller — load_musician, apply_musician,
    load_more_appearances, append_appearances, reset_musician,
    artwork_jobs. Pure data + UI plumbing; the orchestration lives
    in qbz-core.
  - artwork::ArtworkTarget gains MusicianAppearance { index } so
    the existing artwork pipeline can target the new grid.
  - main.rs gains `navigate_musician` (the navigation entry point
    apply_entry now also routes to) and `on_musician_clicked`
    (does the resolve, jumps to navigate_artist on Confirmed,
    otherwise records + navigates to the Musician view).
  - MusicianActions::on_load_more wired to load_more_appearances
    + append.

cc
2026-05-20 19:17:51 -06:00
vicrodh b874661bbf feat(slint): search loading fade + 1s idle-close on header menus
New LoadingDots primitive (three pulsing dots over an optional label)
shown while a search is running. The result sections fade in once the
results arrive instead of showing empty placeholder colors. The
LoadingDots component is reusable for other heavy loads (large grids,
playlist manager) when they get the same treatment.

The header nav-tab menu now closes after 1s of pointer idle (was 1.5s)
per the project rule that no menu stays open more than 1s idle.
PopupWindow context menus already close on click-outside, which is the
idle behaviour for click-triggered popups.

cc
2026-05-19 20:13:14 -06:00
vicrodh 6a584803f1 fix(slint): trim header nav-tab width by 6px
Drop 2px of left padding and 4px of right padding (after the caret) on
the Discover / Library / Local Library / My QBZ tabs.

cc
2026-05-19 12:30:04 -06:00
vicrodh 80f861779c fix(slint): symmetric spacing on the header sidebar toggle
The collapse toggle is a bare icon; the window-edge margin (Spacing.md)
was wider than its ~10px gap to the Discover tab. Drop the tab cluster
x to Spacing.xs so the toggle glyph sits symmetrically.

cc
2026-05-19 12:13:36 -06:00
vicrodh bb6fd1e0f3 feat(slint): minimal search results view, functional end to end
SearchResultsView renders the four SearchState categories; mounted in
AppShell on ContentView.search. Phase 1 is now functional end to end:
header input -> debounced search -> QbzCore::search_all -> results.
Phase 2 builds this into the tabbed dashboard.

cc
2026-05-19 11:46:16 -06:00
vicrodh 7073b6553f feat(slint): wire header search input with debounced live search
The header TextInput now submits on Enter (records history, opens the
results page) and live-searches on edit. Live search debounces 300 ms
via a Slint Timer and requires 2+ characters. A monotonic version
counter guards navigate_search so a stale load cannot overwrite newer
results.

cc
2026-05-19 11:42:12 -06:00
vicrodh 862d408789 fix(slint): crisp button icons, tighter tabs, homologate nav buttons
Icon buttons (header lyrics/queue/hamburger, queue footer) centered
their glyph at a fractional offset (e.g. (36-19)/2 = 8.5px), which
renders blurry — the offset is now rounded to whole pixels. The
header tab spacing dropped from 4px to 2px. The Discover toolbar's
back/forward pair drifted 3px from where the album/artist/settings
views place it; both toolbar clusters now anchor their vertical
center to y=25 so the [<] [>] pair holds one position across views.

cc
2026-05-19 05:05:17 -06:00
vicrodh 9a964477e5 fix(slint): unblock header menu hover-switch, tighten panel + tabs
The click-outside scrim was a full-window TouchArea — it covered the
tabs and intercepted their hover events, so hover-switching between
menus still did not work (same failure mode as the old PopupWindow
grab). The scrim now starts below the header, leaving the tabs
hoverable. The panel also dropped from header-height + 6px to 45px,
removing the unjustified gap between the tab and its dropdown. Header
tab labels lost another 2px of right padding.

cc
2026-05-19 04:53:03 -06:00
vicrodh 5972e8052b feat(slint): in-window overlay for nav-tab dropdowns with hover-switch
Add HeaderMenuOverlay, a plain in-window Rectangle rendered as the last
child of the AppShell root so it draws on top of the header and content
region. Unlike a PopupWindow it does not grab the pointer, so the header
tabs stay hoverable while a menu is open — hovering from one tab to
another overwrites HeaderMenuState and switches the menu instantly.

Behaviour preserved from the old PopupWindow menus:
  - hover-to-open and click-to-open
  - 1.5s idle auto-close, restarted on pointer activity over the panel
    or its rows and on every tab (re)publish via a changed handler
  - click-outside-close via a full-window transparent scrim
  - Discover home-item row still switches to ContentView.home
  - glyphs on all menus except Discover
Menu panel visuals are unchanged: surface-main bg, 1px border-muted,
Radius.sm, 24px drop shadow, 188px wide.

cc
2026-05-19 04:41:11 -06:00
vicrodh b6f5631f51 refactor(slint): publish nav-tab menus into HeaderMenuState global
The four header nav tabs each owned a PopupWindow for their dropdown.
A PopupWindow grabs the pointer while open, so hovering a sibling tab
delivered no hover event and the menus could not be hover-switched.

Add a HeaderMenuState global (open-index, anchor-x, items, home-item)
and the NavMenuEntry struct to state.slint. Tabs now write their
identity into the global on hover/click instead of showing a popup;
the menu panel itself will be rendered by a separate in-window overlay.
Move NavMenuItem into its own file so the overlay can reuse it.

cc
2026-05-19 04:37:26 -06:00
vicrodh 3aa902292e fix(slint): toolbar vertical align, header toggle, queue menu clicks
The Discover toolbar's side layouts had no explicit height, so they
filled the 56px bar and y resolved to 0 — the tab pill, genre filter
and gear stuck to the top instead of centering. Both now carry
height = preferred-height.

The header sidebar toggle dropped from a 19px to a 14px icon: it now
matches the nav-tab icon size and tint, and the 30px box centers it
on whole pixels (was 5.5px → blurry).

The queue row's click TouchArea was declared after the content, so it
sat on top and ate the context-menu button's clicks — the [...] just
skipped to the track. It is now declared before the content, so the
menu button keeps its own clicks.

cc
2026-05-19 04:32:37 -06:00
vicrodh fe815318db fix(slint): center search placeholders, fix queue menu trigger
Search placeholders rode at the top of the input because the text
had no height to center within — they now span the box like the
TextInput. The queue row context-menu trigger is always visible with
a proper 32px hit area (matching the album track rows) instead of a
hover-only 24px dot cluster, so a near-miss no longer falls through
to the row and skips the queue.

cc
2026-05-19 04:13:12 -06:00
vicrodh 3134b83d72 feat(slint): move the sidebar toggle into the header
The sidebar collapse toggle is now an icon-only button at the far
left of the header, before the Discover tab. Removed from NavButtons,
which is now just the back/forward history pair.

cc
2026-05-19 04:13:12 -06:00
vicrodh 787973dfc3 feat(slint): make header search a real input, narrow to 80%
Replace the header search field's static placeholder text with a real
TextInput plus an empty-state placeholder, matching the album
track-search pattern so the field clearly reads as an input. Reduce the
search field width to 80% of its prior size (224/320px to 179/256px).

cc
2026-05-19 03:20:12 -06:00
vicrodh b6355dc202 feat(slint): auto-close nav-tab dropdowns on idle
Add a 1.5s idle-close timer to each header nav-tab dropdown. The timer
is armed when the menu opens and reset on any pointer hover or movement
over a menu row; on timeout the popup closes itself. Single-open is
already enforced by Slint, since only one PopupWindow can be open per
window, so sweeping across hover-to-open tabs closes the previous menu
automatically. Hover-to-open and click-to-open both remain working.

cc
2026-05-19 03:20:06 -06:00
vicrodh 6b31abcea4 style(slint): restyle sidebar toggle and history nav buttons
Make the sidebar collapse toggle a bare icon button with no background
pill, brightest text-primary tint, and a 2px larger icon. Restyle the
back/forward history buttons to match the Discover carousel page
navigators (circular elevated buttons) instead of the tab pill style.

cc
2026-05-19 03:10:07 -06:00
vicrodh 0606826df4 feat(slint): match queue footer to Tauri and add the track menu
Footer now carries the five Tauri controls with Tauri's icons — clear
(trash-list), save (add-to-list), infinite, sleep timer (clock), and
search; the timer button is visual-only for now. Queue rows gain a
context menu 1:1 with the Tauri queue track menu (Remove from queue,
Stop after this, Remove all after, Add to playlist, Track info); only
Remove is wired, the rest are placeholders.

cc
2026-05-19 02:50:21 -06:00
vicrodh 21b0ade357 fix(slint): tighten the header right-side controls by 2px
cc
2026-05-19 02:50:05 -06:00
vicrodh 095888a269 feat(slint): open header tab menus on hover
The nav-tab dropdowns now open when the pointer enters the tab, no
click required. NavTab gained a hovered callback fired from a
changed has-hover handler; click-to-open still works.

cc
2026-05-19 02:08:13 -06:00
vicrodh 25ac4a0e35 feat(slint): build out the queue sidebar UI
Port the Tauri QueuePanel into QueueSidebar.slint: a Queue/History tab
switch, a highlighted NOW PLAYING card with a favorite heart toggle, a
paginated UP NEXT list with a Tauri-style counter (UP NEXT (N of M, K
left)) and prev/next page controls, four footer actions (clear, save as
playlist, infinite play, search) plus an inline queue search field.

Tabs and actions are plain text / plain icon buttons with no pill
backgrounds, per the QBZ design rule. All strings go through @tr. The
panel-open width animation is preserved; opening the panel fires
panel-opened so the controller pulls a fresh snapshot.

cc
2026-05-19 01:49:56 -06:00
vicrodh 9dd7002c5d fix(slint): center header tab labels, brighten and shrink them
Tab labels rode high against their glyph and the header because a
bare Text box sits high on its descender space. They now use an
explicit row-height box with vertical-alignment, drop one point to
11px, and take the brightest text color.

cc
2026-05-19 00:03:06 -06:00
vicrodh 5ae303157a feat(slint): wire volume mute toggle in the player bar
The volume icon now toggles mute: it stashes the current level, drops
the player to zero, and swaps to the volume-x glyph. Unmute restores
the level, and any non-zero set-volume (slider drag or step) clears
the mute so the two controls stay consistent.

cc
2026-05-18 23:31:44 -06:00
vicrodh c0490175f7 feat(slint): homologate menu surfaces with a defined muted border
Dropdown and context menus now sit on the content background
(surface-main) with a more defined border (border-muted, ~22% white)
instead of the elevated surface plus hairline border, so the popover
edge reads cleanly against the content instead of looking coarse.

cc
2026-05-18 23:06:29 -06:00
vicrodh c273506800 feat(slint): wire shuffle, repeat and volume buttons in player bar
Shuffle toggles via core; repeat cycles off/all/one and swaps to the
repeat-1 glyph for one-track mode. Volume minus/plus step the level
by 5%. State flows back through NowPlayingState.

cc
2026-05-18 22:43:31 -06:00
vicrodh f283de57e3 feat(slint): match header dropdown icons, align tab text, tighten menus
Header nav tabs now use the same icons as Tauri (compass, music
library, hard drive, cassette) and the dropdown sub-items carry
leading glyphs everywhere except Discover, which stays text-only.
Tab label text is centered vertically against its glyph and the
header. Dropdown and context menus drop from Radius.md to Radius.sm
so their corners match buttons and album cards.

cc
2026-05-18 22:43:31 -06:00
vicrodh 1f6159f3ba feat(slint): split sidebar toggle from nav buttons, animate side panels
NavButtons now renders the collapse toggle and history buttons as two
spaced pills instead of separating them with a bar. Queue and Lyrics
sidebars animate their own width open/closed (160ms ease-in-out) so
they slide in like the main sidebar instead of appearing abruptly.

cc
2026-05-18 22:43:13 -06:00
vicrodh 7e6e548598 feat(slint): minimal playback wiring + Queue, plus detail/Settings fixes
Playback (minimal, album-focused):
- playback.rs controller: play an album (set queue + start audio),
  play a single track, enqueue, play a queue index, transport
  (play/pause, next, previous, seek, volume)
- the audible step uses Player::play_track (fetch -> download -> play);
  the protected audio backend is untouched
- a 450ms poll loop pushes position/progress/times/playing onto
  NowPlayingState and auto-advances at end of track
- NowPlayingState / QueueState globals; NowPlayingBar bound to real
  state (song card, seek bar, transport, volume); QueueSidebar lists
  the queue and rows play on click
- a played track is recorded into the recently-played store

Detail views:
- album description and artist biography stay with the metadata,
  filling the width to the right of the artwork (not below the header)
- header nav tabs gained a dropdown caret and 2px side padding

Settings > Playback defaults are now opt-out: show context icon,
restore session, resume position and stream-uncached default on;
streaming-only stays opt-in; initial buffer defaults to 2s.

Known gaps: queue/now-playing artwork not yet threaded (Image is not
Send); autoplay-mode preference not consulted by auto-advance; buffer
progress line stays at 0.

cc
2026-05-18 22:16:13 -06:00
vicrodh 33df7bce76 feat(slint): collapsible sidebar, Queue/Lyrics panels, nav menus
Shell chrome (steps 1-3):
- Left sidebar is collapsible — a toggle in the nav cluster (left of the
  [<] [>] pair) and a draggable right edge that resizes 200-420px and
  collapses below 160px. ShellState global drives it.
- Queue and Lyrics right sidebars: 320px panels rendered as layout
  siblings, so opening one pushes the content area narrower rather than
  overlaying it. The header captions/queue buttons toggle them
  (mutually exclusive) and light up when active.
- The four nav tabs (Discover/Library/Local Library/My QBZ) open
  dropdown menus listing their sub-sections; Discover -> Home is wired.

Detail views (steps 4-5):
- Track rows: the even-row tint is now clearly fainter than the hover
  tint so the two states no longer look identical.
- The favorite and offline-cache glyphs in album track rows are real
  buttons (hover state, emit media-action).
- Album header artist link uses the metadata text color, bold, instead
  of the accent blue.
- Album description and artist biography span the full content width
  below the header block.

cc
2026-05-18 21:54:31 -06:00
vicrodh 99e3513d68 feat(slint): NowPlayingBar rebuild, Settings constraints, Discover fixes
NowPlayingBar (start of the playback-bar work):
- height raised to 110px
- SeekBar component: three-line track (total / cached / progress), a
  hover thumb, elapsed/remaining times, and a hover time bubble with a
  downward caret marking the seek position
- song card: background and border dropped, gains the extra height,
  album/artist links and the context icon kept (context icon now
  always shown)
- transport + secondary buttons copied from Tauri (shuffle, prev,
  play, next, repeat, add, favorite; cast, picture-in-picture,
  fullscreen, normalization; volume cluster). Queue/Lyrics stay in the
  header — and the header now orders them lyrics-then-queue.

Settings constraints (1:1 with Tauri):
- Exclusive Mode enabled only on ALSA; DAC Passthrough only on
  PipeWire; Lock Output Device disabled while DAC Passthrough is on;
  Gapless disabled while Streaming Only is on
- backend change cascades: forces off DAC passthrough / force-bit-
  perfect / exclusive / gapless as appropriate; DAC-passthrough and
  streaming-only cascades; initial-buffer slider right-aligned

Discover fixes:
- navigation [<] [>] pair homologated to the same top/left margin
  across every view
- album/artist description moved back into the metadata column (it now
  fills the width there) with uniform secondary text color
- genre filter button hugs its label instead of stretching
- Recently Played Albums and Tracks modules always render, with a
  placeholder until the play history has data
- wider spacing between Discover rows; the Discover gear is no longer
  a Settings shortcut (it is the per-tab sort/show-hide control)

cc
2026-05-18 21:22:40 -06:00
vicrodh edca636a16 feat(slint): Settings Audio/Playback 1:1 with Tauri
Rebuild both Settings panels to match the Tauri app — every option
present, on the correct tab, and fully functional (persisted and
applied to the live player).

Audio: streaming quality, limit-quality-to-device, maximum sample rate,
backend, output device, ALSA plugin, hardware volume, exclusive mode,
reserve DAC, DAC passthrough, force bit-perfect, allow quality
fallback, sync-on-startup, lock output device, and a reset button.
Conditional rows (ALSA plugin, hardware volume, max sample rate, force
bit-perfect, lock output device) are gated on Rust-computed flags.

Playback: continue-after-track, show context icon, gapless, restore
session, resume position, stream uncached, initial buffer size (slider),
streaming only, quality-retry behavior. Note gapless and the streaming
options live on the Playback tab though the qbz-audio struct stores them.

- ui_prefs.rs: JSON store for the frontend-only streaming-quality pref
- QbzSlider primitive for the integer buffer-size control
- volume normalization row removed (Tauri hides it)

Caveat: toggling reserve-DAC persists and is read live, but re-arming
the device reservation guard mid-session needs a player-side API that
does not exist yet; it takes effect on next launch.

cc
2026-05-18 20:47:12 -06:00
vicrodh 12ed5a7c4a feat(slint): browser-like back/forward navigation history
Add a navigation history so pages can be walked back and forward.

- nav.rs: a back/forward stack of NavEntry (home, album, artist,
  settings); record() on fresh navigation, go_back/go_forward to walk it
- NavButtons: a [<] [>] pair styled like the Discover tab group; inline
  beside the Discover tabs and in place of the per-page Back button on
  album, artist and settings views
- All navigation routes through Rust (open-album/-artist record entries;
  the gear and header menu use NavState.request-settings) so the history
  stays accurate; back/forward re-apply entries, re-loading entity pages
- AppShell listens for the mouse back/forward buttons
- can-back / can-forward drive the buttons' enabled state

Also in this round:

- Album header description: render a pre-truncated short string so it
  wraps to a couple of lines instead of overflowing the header; the full
  text stays in the Read-more modal
- Header queue button uses an ordered-list icon (the previous one read
  as a playlist)

cc
2026-05-18 20:17:54 -06:00
vicrodh e766bed21a fix(slint): anchor the header right controls to the right edge
The right-controls HorizontalLayout had no explicit width, so a layout
left to fill its parent reported the full header width — the right
anchor `x` then resolved to a negative offset and the queue/lyrics/
hamburger buttons piled up over the Discover tab. Give the row an
explicit width so it anchors correctly.

cc
2026-05-18 19:51:47 -06:00
vicrodh 1eeaf07a87 feat(slint): queue and lyrics buttons in the header
Add Queue and Lyrics icon buttons to the left of the hamburger. Visual
for now — they will toggle the right-side sidebar once it lands.

cc
2026-05-18 19:25:32 -06:00
vicrodh 644fd88dc5 feat(slint): hamburger user row with subscription tier
The header user element is now just the hamburger icon. The menu it
opens leads with a user row showing the display name and the Qobuz
subscription tier, carried through from the login/restore session.

cc
2026-05-18 18:07:48 -06:00
vicrodh 7eb9627eab feat(slint): context menus for album cards and track rows
Add a first cut of context menus, opened from the "more" (ellipsis)
button on Discover album cards and on album track rows.

- ContextMenu / ContextMenuItem primitives (PopupWindow-hosted panel)
- AlbumCard: the overlay action buttons are now interactive (they gate
  their pointer handling so non-hovered cards still open on click); the
  more button opens a menu — Open album, Play, Add to queue, Add to
  favorites
- TrackRow: a hover-revealed more button opens a menu — Play, Add to
  queue, Add to favorites; AlbumTrackItem and TrackData carry the
  track id
- Menu actions bubble up as a single media-action(kind, id, action)
  callback through the shell; main.rs logs it for now — playback and
  favorites wiring lands with the playback session
- user and list-plus icons

cc
2026-05-18 17:32:56 -06:00
vicrodh a9d5f8d30d fix(slint): center the header search field on the window
The three header columns shared free space by stretch but kept their
own preferred widths, so the wide tab row pushed the center column —
and the search field with it — off true window center. Anchor the
tabs left and the user element right, and position the search field
absolutely at the window center instead.

cc
2026-05-18 17:17:31 -06:00
vicrodh 5d78fb0a09 feat(slint): add Settings page with Audio and Playback panels
Introduce the Settings page, reachable from the Discover gear and the
header menu. Two sub-sections backed by the existing UI-agnostic stores.

- Audio panel: backend, output device and sample-rate dropdowns;
  toggles for exclusive mode, DAC passthrough, PipeWire force
  bit-perfect, gapless, normalization and quality fallback
- Playback panel: autoplay mode, show context icon, remember session
  and resume position
- settings.rs controller owns AudioSettingsStore + PlaybackPreferencesStore
  at the shared global path; persists changes and applies audio ones to
  the live Player (reload_settings, plus reinit_device for routing-
  critical changes); a backend switch re-enumerates output devices
- QbzToggle (switch) and QbzSelect (PopupWindow dropdown) primitives,
  SettingRow layout helper, chevron-down icon
- SettingsState global, ContentView::settings, settings-bool /
  settings-select callbacks wired through the shell

cc
2026-05-18 17:08:24 -06:00
vicrodh 4e143bbcd5 feat(qbz-slint): artist detail page
Clicking an artist (from the album page's artist line) opens the
artist detail view: a circular portrait, name, biography, action
buttons, a Popular Tracks list, and a Discography carousel.

- artist.rs: fetches the artist page via QbzCore::get_artist_page and
  maps name, biography (HTML-stripped), portrait URL, top tracks and
  releases; the portrait loads through the shared image cache.
- ArtistPageView.slint reuses AlbumCard for the discography (through
  the Carousel) and AlbumTrackItem for the track list.
- NavState becomes a three-way ContentView (home / album / artist) and
  AppShell routes the content region accordingly. The album page's
  artist line and the artist page's release cards are navigable.

cc
2026-05-18 15:59:47 -06:00
vicrodh ece878ed49 feat(qbz-slint): drop content borders, add Cider-style carousel fades
- Removes the hairline borders around the content region; the chrome
  and content surface colors already delineate the regions cleanly.
- Carousels gain edge-fade gradients: clipped cards dissolve into the
  content background at the left and right edges, shown only when there
  is more to page in that direction — like Cider's row edges.

cc
2026-05-18 15:27:37 -06:00
vicrodh b9be2dc0d9 feat(qbz-slint): three-section header with responsive tabs
The header is now three equal sections — navigation tabs (left), the
search field (center, so it lands at the window center), and the user
element (right) — fixing the tabs stretching to full width under the
search field.

- Nav tabs regain Lucide icons (compass, library, hard-drive,
  cassette-tape) and use a 12px label.
- The user element uses a hamburger icon instead of the QBZ logo, which
  aliased badly as a scaled PNG.
- Responsive: below 1140px the tabs drop their icons; below 960px the
  search field narrows to 70%.

cc
2026-05-18 15:27:36 -06:00
vicrodh 8c27de5651 feat(qbz-slint): header redesign, Lucide icons, scrollbar fixes
- Replace every Unicode glyph placeholder with Lucide icons via QbzIcon
  (player transport, carousel nav, album-card overlay, album-page
  actions, gear, search, checkbox, sidebar).
- Header: the search field is centered to the window width and gets a
  search icon; the header and player bar now use the elevated surface,
  so all chrome (header, sidebar, player bar) shares one color over the
  darker content area.
- The sidebar user card moves to the header as a user element (name +
  QBZ logo) that opens a dropdown — Settings, Keyboard Shortcuts,
  Documentation, Report an Issue, About, plus Log Out and Close. Log Out
  clears the session and returns to login; Close quits the app.
- Content ScrollViews disable the horizontal scrollbar — no view scrolls
  horizontally, so the phantom reserved lane is gone.

cc
2026-05-18 14:58:58 -06:00
vicrodh 6b772b41b0 feat(qbz-slint): album detail page
Clicking an album card opens the album detail view: a back button,
the album header (224px artwork, title, artist, and info line — year,
label, genre, track count, duration — plus play / shuffle / favorite /
more action buttons), and the track list with a column header (#,
title, artist, duration, quality).

- album.rs: fetches the album via QbzCore::get_album, maps it (info
  line, per-track quality tier, durations) onto the AlbumState global,
  and loads the header artwork through the shared image cache.
- AlbumPageView.slint, with NavState driving the shell content region
  between Home and the album page.
- Album clicks bubble AlbumCard -> Carousel -> HomeView -> AppShell ->
  AppWindow -> main; the back button returns to Home.

cc
2026-05-18 14:38:45 -06:00