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
Two text-side refinements to the artist + album views.
Typography:
- Bumps every scale level by 1px so body text reads at 15px instead
of 14px (legal 13, link 14, body/subtitle 15, button 17, heading
19, title 25, wordmark 29). All views adopt the new scale through
the existing tokens — the artist + album descriptions feel
noticeably more readable without per-component overrides.
strip_html (new shared module `crates/qbz-slint/src/strip_html.rs`):
- Single implementation now used by both album::map_album and
artist::map_artist for editorial copy (reviews, biographies).
The previous album-local helper handled only <br> and </p>; the
artist-local helper handled neither, so multi-paragraph
biographies collapsed into one wall of text.
- <br>, <br/>, <br /> (any case + spacing) -> \n. </p>, </P> ->
\n\n. Stripped tags like <strong>, <em>, <a> still leave their
text inline. (Slint's `Text` is single-style, so true inline
bold/italic isn't possible without splitting into multiple Text
elements — deferred.)
- Broader entity table: amp/lt/gt/quot/apos/nbsp/copy/reg/mdash/
ndash/hellip/curly quotes plus numeric copy variants.
- Runs of 3+ newlines collapse to a paragraph break so stacked tags
don't blow the layout open. Unit tests for each path.
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
M1 progress. Adds the foundation token layer (palette, semantic
colors, spacing, radius, typography) bound to the QBZ dark theme,
three primitives (QbzCheckbox, QbzPrimaryButton, QbzTextLink), and
the LoginScreen feature component.
The login screen mirrors the Svelte LoginView: centered card, logo,
QBZ wordmark, Qobuz ToS checkbox, "Sign in via Browser" button,
system-browser and offline links, and the legal disclaimer. Colors
are lifted verbatim from src/app.css; strings use @tr() markers.
Rust side: typed AppCommand enum; UI callbacks dispatch through it.
M2 wires the command handler to AppRuntime.
cc