Wire the per-history-entry scroll memory (added for Local Library) into
every top-level view: Home/Discover, Album, Local Album, Artist, Musician,
Label, Label Releases, Location, Favorites, Mix, Playlist, Playlist Manager,
Offline Manager, Search, Settings.
Each view's primary scroll container reports its viewport-y and restores it
on back/forward via NavState.restore-scope (scope_for in main.rs maps each
entry to a stable id; tab views carry the tab, e.g. "fav:albums"). The
container restores once per arm, only after content lays out, then clears
the scope. A `changed sr-armed` trigger covers views that persist across
sub-tab navigation (Home's Discover tabs, Favorites' shared list, Playlist
A->B->A) where no fresh mount occurs.
ScrollView-based views (Offline Manager, Settings) use visible-height for
the overflow check; ListView/Flickable views use height. Favorites' shared
albums/artists/playlists/labels Flickable matches the active tab's scope
dynamically.
cc
Ephemeral folders: open / browse / play a folder without indexing it —
album-grouped pane, CUE seek fast-path (no full re-decode per virtual track),
folder-path persistence + rehydrate, "already playing" add-vs-replace choice
dialog (ephemeral-only), and now-playing / queue / menu gating so an ephemeral
track exposes no persistence actions.
Folders tree brought 1:1 with Tauri:
- Rail header: select-mode, collapse-all, open-ephemeral, tree search.
- Resizable rail (default ~20% narrower; drag handle, min 50% default /
max 50% of the content area).
- Subfolder cover grid + grid/list toggle, recursive track count, name search;
compact icon bulk bar (folder-check / play-next / queue / playlist / clear).
- Tree is the default mode; circular checkboxes homologated with TrackRow;
compact rows (smaller type + icon, no ambiguous trailing count).
- Flat/Tree icon toggle (disc-album / folder-tree), floated right.
Per-source TrackRow context menu (logic, not a blanket trim): Qobuz/offline =
full; local = play/next/queue + add-to-playlist; plex/ephemeral = play/next/
queue only.
App-wide standard: Math.round pixel-snap on icon centering across the UI to
kill sub-pixel blur (CircleAction, header back/forward/collapse, and the rest).
NowPlayingBar context icon -> turntable (frees disc-album for the Folders
toggle). New assets: folder-check, folder-tree, turntable.
cc
Committed together because these changes share main.rs, playlist.rs and
PlaylistView.slint, and the new quality field is atomic across every
TrackItem builder, so they cannot compile as separate whole-file commits.
- Universal play-from-here: a single play_track_in_context dispatcher builds
the queue from the current view's VISIBLE list and starts at the clicked
track (playlist, album, artist, favorites, label, mix, search). Cached
views order by the visible model; re-fetch views (album / artist top
tracks) reorder to the in-page filter; the search "most popular" hero
prepends to the results queue. Drops the playlist-track / mix-track arms
that always started at track 1 (the clicked id was read from the wrong
media-action slot).
- Album-name links in track rows open the album page (the ("album","open")
media-action arm was missing and fell through to a no-op).
- Unified track-row quality badge: a new quality-detail field on TrackItem,
populated from bit-depth / sample-rate in every builder through a shared
crate::quality::detail, rendered with a bare (no chip background/border,
centered text) QualityBadgeFull. Restores the exact bit-depth / sample-rate
line that had regressed to a plain CD / Hi-Res tier.
- Playlist track list: reserve the favorite / download / context-menu columns
in the column header, match the row's 36px cover cell, and anchor the
scrollbar to the view edge (matches Local Library > Tracks).
cc
Extract PlaylistView's inline bulk action bar into a shared
MultiSelectBar primitive driven by a [BulkAction] model (id / label /
danger / needs-selection) emitting action(id). PlaylistView now declares
its two actions (Select all, Remove) as data; the Favorites tracks tab
will reuse the same bar with its larger action set instead of copying
it. Same visual (count label + bordered/hover buttons, ADR-008).
cc
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
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
Sidebar:
- Fix "Recursion detected" panic from focusing the playlist search input in init (defer via a single-shot Timer).
- Right-click context menus: playlist (Edit, Hide from sidebar, Move to folder with folder search + Move to root) and folder (Edit, Hide from sidebar), built on ContextMenu/ContextMenuItem; remove the unreachable hover "..." button.
- Filter hidden playlists/folders out of the list; add folders::set_folder_hidden.
- Folder list in the move-to-folder menu scrolls (cap 5 rows); close-on-click-outside so the folder-search input no longer dismisses the menu.
- Sort-menu Refresh now uses a dedicated refresh-cw icon.
Toast: in-app ToastKind/ToastState + Toast overlay mounted at the app root, crate::toast (show/hide, per-kind auto-hide via a reusable Timer, cross-thread *_weak helpers); surface play album/artist load failures.
SearchView: title inline with NavButtons + searchType filters floated right; tab strip on its own row; Most-popular hero for a track uses the new SearchTrackHero, sized to the artist card.
Playlist Manager: list view renders compact PmListRow rows instead of cards; grid/folder card fonts tightened to match Tauri; folder card centered with hover-reveal edit; search input narrowed to 70%.
cc
Port the Playlist Manager: header/toolbar (search/filter/sort/folder-flat
toggle/view-mode/new-folder/counter), FOLDERS card grid (accent icon, count,
edit), PLAYLISTS grid/list/tree, folder CRUD (FolderEditModal), move-to-
folder, per-card actions, and arrow-button reorder (custom sort). Reached
from the sidebar "Manage playlists" item; backend 100% reused via
QbzCore/library.db (no new commands). Pointer-event drag-and-drop and
gradient folder colors are deferred follow-ups.
This commit also carries the cross-cutting wiring the session's features all
share — state.slint globals/structs, NavEntry variants, main.rs dispatch +
action wiring, artwork targets, app.slint exports, AppShell mounts — so HEAD
is the verified-green tree (cargo check + release build clean).
cc
Polish + consistency:
- Compact playlist header (cover 150px; nav->header spacing
homologated with AlbumView); description truncated to ~2 lines +
"Read more" modal; custom-artwork buttons only on cover hover.
- New Typography.section (18px) token for hero/section titles,
applied across Album / Artist / Mix / Playlist + Home carousels.
- Shorter track rows app-wide (50px row, 36px artwork, -1 fonts);
+3px column-header/divider gap; wider track-number column.
- Reusable ExpandableSearch (the JumpNavBar page-search pattern:
magnifier -> opens left -> X to close) in the playlist action row.
- Custom ListScrollbar (thicker, min thumb height, own gutter) since
Slint's ListView scrollbar can't be styled.
Sort:
- Default / Title / Artist / Album / Duration with asc/desc per
field, plus Custom (manual order persisted in library.db with
up/down reorder). Client-side search + sort combined.
WIP: track drag-and-drop onto sidebar playlists
- DragState/DragActions globals, drag source on the shared TrackRow,
follow-cursor DragGhost, sidebar playlist rows self-detect the drop
via absolute-position, multi-select group drag, add_tracks_to_playlist
on drop, Flickables freeze scroll during a drag.
- KNOWN ISSUE: the ghost is offset from the cursor (the row
absolute-position + pointer mapping is off, esp. inside Flickables).
Diagnostic logging is left in (TrackRow debug() + a start log) to
pin down the wrong term before the generic fix.
cc
Large playlists (2000+ tracks) were sluggish. Addressed the root
causes; this strategy is reusable for the library / mixtapes / tracks
views.
- Virtualization: the track list is now a ListView (only visible
rows are instantiated) instead of a Flickable with a for over all
rows. The header + column header are pinned above it.
- Smaller images: track rows use ImageSet::smallest() (new) instead
of best() — they are 40px thumbnails, so best() was downloading
mega/large covers.
- Per-target decode size: row thumbnails decode to 96px instead of
the 264px card size, so a 2000-row model holds ~75MB of pixels
rather than ~550MB.
- Track-number column widened to fit 4-digit positions.
- Back-to-top button: appears once scrolled past one viewport
(bottom-right), scrolls the list home. A shared rule for big lists.
- Custom-artwork buttons only show on cover hover.
cc
Wires qbz-library into the Slint app (new library_db module) so the
local, Tauri-shared organization data is read/written from the same
per-user library.db (<data_dir>/qbz/users/<id>/library.db). The DB is
opened per-operation on a blocking thread (rusqlite Connection is
!Send).
- Header collage: the playlist cover is now a 4-up collage of the
first tracks' covers (Tauri parity); falls back to the server
image, then the first track, then a placeholder.
- Custom artwork: owner-only buttons on the cover set/clear a custom
image. Set copies the picked file into <cache_dir>/qbz/artwork and
stores the path in playlist_settings.custom_artwork_path (shared
with Tauri); it's loaded directly from disk and overrides the
collage.
- Edit description: the edit modal gains a description field
(prefilled), saved through update_playlist alongside the name.
cc
The playlist detail view gains an edit mode (owner-only), reusing
TrackRow's multi-select-mode:
- A "Select" toggle in the header turns on per-row checkboxes and a
bulk action bar (N selected, Select all, Remove).
- toggle-select is now context-aware (playlist vs artist Popular);
PlaylistState tracks selected-count for the bar.
- Remove calls remove_tracks_from_playlist with the selected ids,
then reloads the playlist and leaves edit mode.
Track reordering is the remaining playlist-editing piece (deferred —
it is the most involved).
cc
Completes playlist CRUD. The detail header shows an edit button only
for the user's own playlists (is-owner = the open id is in the sidebar
list); it opens an EditPlaylistModal:
- Save -> update_playlist (rename); refreshes the detail header name
and the sidebar list.
- Delete -> delete_playlist; closes the modal, navigates back, and
refreshes the sidebar.
Adds the pen-line icon (Lucide) used by the edit button.
cc
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