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
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
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
Ports Tauri's Filter-by-genre popup (simple grid view). The selection
is shared across the three Discover tabs — matching Tauri's single
"home" genre context — and re-fetches the discover index with the
chosen genre ids.
UI:
- New GenreFilterPopup: header (icon + title + close), Remember
selection + Advanced view toggles (QbzToggle), a 3-column grid of
parent-genre chips with an accent fill + check circle when
selected, and a Clear filter footer. Anchored top-right under the
button, with a click-away backdrop.
- GenreButton turns accent-filled and shows "N genres" / "1 genre"
when a filter is active (else "Filter by genre"), 1:1 with Tauri.
- Advanced view (search + 3-level tree + counts) is the next
increment; its toggle is present and shows a "coming soon" stub.
State / backend:
- GenreFilterState + GenreFilterActions globals; GenreChip struct.
- crate::genre_filter loads the parent genres (QbzCore::get_genres)
and owns the shared selection, persisted to
<data-dir>/qbz/genre_filter.json when Remember is on (dropped
when off). Stale ids are pruned against the loaded genres.
- load_home now takes genre_ids and threads them into
get_discover_index. The home-load path is extracted into a shared
`reload_home(active_tab)` used by the initial shell load and by
genre toggle/clear; it rebuilds all three section sets, restores
the active tab via select_tab, and re-fires artwork.
cc
Three of the four Discover > Home quick-wins.
Loading skeleton (#1):
- New HomeSkeleton replaces the spinner + "Loading your music…"
text shown while the first fetch is in flight. Grey placeholder
blocks shaped like two real carousels (title bar + a row of
square art with two text lines), gently pulsing via a shared
900ms Timer phase + animate opacity — no more artifact titles
during load.
Recently Played Tracks + Popular albums (#3, #4):
- New SlimCarousel: a paginated 4x3 slim-card grid (12 per page),
capped at two pages, sliding one viewport width per page. The
prev/next controls only appear when there is a second page
(>12 items); 12 or fewer renders as a single static page with
no arrows. Replaces the old non-paginated GridSection that grew
into a tall wall of rows.
- home.rs caps `recent` and `popular` to 24 items (two pages) so
the carousel never has hidden overflow; the Popular/Recent
artwork-job indices are unchanged.
GridSection is retained — the artist ReleaseGrid still uses it.
Note on #2 (Recently Played Albums "spurious AlbumCard"): there is
only one AlbumCard component (discover/AlbumCard.slint, used by 5
call sites incl. this carousel via Carousel) — no duplicate to
delete. The cards look bare because the local play-history store
keeps only id/title/artist/artwork (no quality/year/ribbon), not
because of a different component.
cc
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
Home, Album and Artist views now show the rotating LoadingSpinner (same
as search) above a label while the initial load runs, replacing the
bare Loading… text.
The top-result-artist dedupe moved from map_search_all into apply_search
and replace_category: the full artists list is kept in SearchState.artists
(used by the Artists tab grid), and a separate artists-carousel drops the
duplicate next to the Most-popular hero on the All tab.
cc
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
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
The right controls (genre filter + gear) used x = parent.width -
self.width - 32px on a width-less HorizontalLayout, which fills the
toolbar so self.width = full width and the cluster landed far left,
on top of the history arrows. Side layouts now carry an explicit
width = preferred-width. The three-tab bar is no longer centered: it
sits next to the history buttons with a 16px gap. The home-gear icon
grew 2px.
cc
Anchor the Discover toolbar's history buttons and genre/gear controls
to the toolbar edges and absolutely center the three-tab nav pill so it
sits at the true horizontal center. Swap the sort gear icon to the
home-gear asset.
cc
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
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
Discover Home now has both recently-played modules: a carousel of
album cards and the slim track grid (renamed "Recently Played
Tracks").
- recently.rs records track plays with their album metadata;
load_albums() derives the distinct recent albums from that history
- home.rs threads a recent_albums list into HomeData; HomeView renders
it as a carousel; the artwork pipeline gains ArtworkTarget::RecentAlbum
Also raises the header-gradient contrast: the artwork color is now
saturation-boosted off its average and normalized to a fixed peak
brightness instead of just darkened, so the tint is actually visible.
cc
Close the Recently Played Tracks tech debt: a JSON-backed play-history
store at the shared QBZ data path, surfaced as a slim-card section on
Discover Home (it hides while empty).
- recently.rs: load() reads the store newest-first; record() prepends a
track, dedupes by id and caps the list — the playback session calls
record() when a track starts
- home.rs threads a `recent` list into HomeData; HomeView renders a
"Recently Played" GridSection; artwork pipeline gains an
ArtworkTarget::Recent
The data path is complete end to end; only record() awaits the
playback session.
cc
The ScrollView reserved a horizontal-scrollbar gutter at the bottom of
the Discover Home even with the scrollbar policy set to always-off. A
Flickable scrolls on the wheel with no scrollbars and no gutters; its
viewport-width is pinned to its own width so the clipped carousels can
never scroll horizontally.
cc
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
The carousels' clipped, oversized inner track inflated the scroll
content's preferred width, so the vertical ScrollView reserved a
bottom gutter for a horizontal scrollbar that is never shown. Pin the
ScrollView viewport-width to its visible width so horizontal content
can never overflow.
cc
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
Add a compact "slim" card variant and a 4-column grid section that
renders it, used for the Popular albums row on Discover Home.
- SlimCard: 60px row with optional rank, 44px thumbnail, title/subtitle
- GridSection: 4-column grid; cards absolutely positioned since Slint
GridLayout cannot host a `for`
- state.slint: SlimItem struct and HomeState.popular model
- home.rs: load_home now returns HomeData { sections, popular };
map_slim maps the discover most-streamed container
- artwork pipeline: ArtworkTarget enum routes decoded covers to either
a section card or a popular card
cc
- 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
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
- The sidebar now uses the elevated surface (#1a1a1a) over the #0f0f0f
content area, matching the QBZ dark theme (--bg-secondary over
--bg-primary).
- Discover Home gains a proper 56px toolbar with a bottom border: the
tab pill on the left, a "Filter by genre" button and a settings gear
on the right. The toolbar is fixed; only the section carousels scroll
below it.
cc
Discover sections now use a Carousel: a clipped, sliding track of
album cards with prev/next page controls, paginated by slice like the
Svelte DiscoverySection. With no ScrollView the row height is exactly
the card height — the horizontal scrollbar no longer injects vertical
space, so the earlier internal-scroll problem is resolved structurally
rather than patched.
per-page is derived from the viewport width and self-corrects on
resize. Replaces SectionRow.
cc
Corrections from testing feedback:
- Remove the greeting — the current QBZ discovery view has no greeting,
only the tab selector. Drops HomeState.greeting and the chrono dep.
- Quality badge is now the icon-only badge (hi-res.svg, or cd.svg in a
framed square), not a numeric text pill. AlbumCardItem carries a
quality tier instead of a quality string.
- Tighten the album card: deterministic 274px height, title and artist
in a 2px-spaced stack — removes the large gap between them.
- SectionRow no longer scrolls vertically (vertical-scrollbar-policy
always-off, height pinned to the card height).
- Artwork is downscaled to 264px on decode instead of holding ~600px
source textures.
cc
After sign-in the app loads the real Qobuz discover index and renders
the Home view: a time-based greeting with the user's name, the tab
pill (Home / Editor's Picks / For You), and discover sections — New
Releases, Press Accolades, Ideal Discography, Qobuzissimes, Albums of
the Week — each a horizontally scrolling row of album cards.
- state.slint: AlbumCardItem / DiscoverSection structs, HomeState global.
- discover/: AlbumCard (artwork area, award ribbon, title, artist,
quality badge), SectionRow, HomeView.
- home.rs: discover-index fetch and mapping (award-ribbon priority,
quality string), greeting, and event-loop model application. Unit
tests for ribbon, quality, and greeting logic.
- AppShell content region is now the HomeView.
Album artwork images load in the next increment (artwork pipeline);
cards currently show the elevated placeholder. No fixtures — all data
is the live discover index.
cc