SettingsExportActions.confirm() -> settings::export_settings: read the
include-auth gate, close the modal, then off the event loop build the bundle
via qbz_app::settings::bundle::export(ExportSource::Desktop, ..) (no new
export logic), open a native rfd save dialog seeded with
qbz-settings-YYYYMMDD.qbzb, write it 0600 via write_bundle_file, and toast
the "qbzd settings import <file>" step. Errors surface a toast; cancel is a
no-op.
cc
Add the desktop "Export settings…" surface for the qbzd daemon epic (T18):
a Developer-settings row that opens a modal with the single default-off
"Include Qobuz login and integration tokens" gate, its warning line, and a
Cancel/Export footer. Confirm() is a Rust callback (wired next). New
SettingsExportState/SettingsExportActions globals, exported from app.slint
so the Rust layer binds them, and the modal mounted in AppShell. New strings
translated across all 8 locales (en es de fr pt ru ja nl).
cc
Implements owner requirement 2 (03-setup-tui.md): `qbzd setup`, a six-screen
raspi-config-grade configurator over the daemon's real stores — zero new
persistence. ratatui 0.30 (IV5: unifies on the lock's unicode-width 0.2.2;
0.29 forbidden). A synchronous event loop on a blocking thread; disk/HTTP only
at screen entry, `r`, save and the immediate actions, on workers with a spinner
(no I/O on keystrokes). Non-tty exits 2 with the verbatim copy; alt-screen +
panic-hook restore.
Screens: Account (T5 login engine; inline masked token paste, suspend-and-run
browser login, logout confirm), Audio (the §3.2.3 constraint matrix + 7
cascades verified 1:1 vs the desktop code, device picker re-deriving the
§3.2.2 grouping/BP-badge rules, DSD guard), Playback (ask->fallback rule,
infinite-radio read-only), QConnect (startup/name/volume_mode, live preview),
Network (whole-file qbzd.toml rewrite preserving every key), Import/Export
(pure renderer of the T12 bundle plan; interactive device re-pick; default-off
auth gate). Saves reuse T11 write_one + reload/status classification (§4.3);
dirty marker + Save/Discard/Stay. All user-facing strings centralized.
cc
Port the desktop system-browser OAuth (crates/qbz/src/auth.rs) into the daemon
CLI with three daemon-specific changes: the one-shot listener binds an ephemeral
port (never the control-API port, D6), a CSPRNG state nonce binds the callback,
and the command only validates + persists the token into the daemon root then
best-effort nudges a running daemon. Adds validate_token / login_browser /
login_paste / login_with_token_arg / nudge_reload plus logout; no email+password
surface anywhere. cli/copy.rs carries the verbatim 02 §2.2 timeout, success and
logout strings.
cc
IV6 build peak: 795 MB max RSS (summed across the cargo-capped build's
process tree via /proc VmRSS polling; /usr/bin/time -v is not installed
on this box). aws-lc-rs in graph: 0 (single rustls provider: ring
v0.17.14 throughout).
cc
PR #612 added the three Catppuccin flavors but grouped all of them under ThemeCategory::Dark. Latte is the LIGHT flavor (its palette already builds light via bg_is_light); move it to the Light category arm so the theme picker groups it correctly. Frappé/Macchiato stay Dark.
cc
Two build breaks from the View-all work: (1) MostPlayedAlbumsState/Actions were export global in state.slint but not re-exported from the app.slint entry, so slint codegen never emitted their Rust bindings — added them to the app.slint export list (verified: the generated app.rs now defines both). (2) adding plays to AlbumCardItem forced every Rust struct literal to set it — added plays: 0 to the six construction sites (album_map/artist/external_reco/foryou/home/search); most_played_item overrides it after card_to_item. slintcheck's .slint validation can't catch the export-list gap (it only bites when the qbz crate consumes the generated types), hence the miss.
cc
Adds the remaining three Catppuccin flavor palettes to complete the set:
- Latte (light)
- Frappé (dark)
- Macchiato (dark)
All colors transcribed from the official Catppuccin palette.
Themes follow the existing StdSpec builder pattern and are fully
integrated into the theme registry and UI.
Full-page listing opened from the rail's View all (both tabs now wire show-view-all -> HomeActions.open-most-played-albums). New MostPlayedAlbumsView (mirrors RecentAlbumsView) with a search box; AlbumCardItem gains a plays field shown ONLY here (a gated line in AlbumCard). Rust: navigate_most_played_albums loads all_albums(), caches the ranked rows, renders the grid + fires artwork (MostPlayedAlbumsPage target, Qobuz vs Plex/local split); the search box re-filters the cached rows by title/artist and re-renders (mirrors the Qobuz-playlists filter). NavEntry::MostPlayedAlbums + AppShell mount + ContentView variant. Count label in 8 locales. slintcheck green; qbz-crate Rust pending a full build (30GB wall).
cc
Wires the rail (top-20 by play count) into both tabs, mirroring the favoriteAlbums pattern: HomeState/ForYouState.most-played-albums DiscoverSection; foryou::most_played_album_cards maps the ranked rows onto AlbumCard; home.rs builds it inline (local, no fetch) and For You rides the favorites branch; HomeMostPlayedAlbum/ForYouMostPlayedAlbum artwork targets + apply arms; render arms in HomeView/ForYouView (self-hide while empty); title in 8 locales. slintcheck green. View-all page (search + play count) is the next increment.
cc
Data foundation for the new Most Played Albums rail. New qbz-app store album_play_history (SQLite, mirrors play_history): one album_play_event row per track-start + an album_meta side table; top_albums(n)/all_albums() rank by COUNT(*) GROUP BY album_id, tie-broken by most-recent play. Hooked in playback::record_recent next to the recently/artist stores, same album identity. Registers the mostPlayedAlbums section id in discover_prefs (Home + For You, default off). The Qobuz API exposes no most-played endpoint (verified vs the inferred OpenAPI), so the ranking is local. 15 unit tests green (store 5 + prefs 10).
cc
The merged #599 typed DsdErrorReport.state as Arc<SharedState>, but the audio thread passes thread_state — a SharedState, which is itself a #[derive(Clone)] handle of Arc<Atomic..> fields (a clone already shares the state). The extra Arc broke the qbz-player build in the combined pre-release (E0308 at the four DoP / native-DSD wrap sites), failing the test-crates CI. Switches the field + new() to SharedState and drops the now-redundant Arc::new in the two DSD tests. Verified with scripts/cargo-test.sh (workspace, no UI): all crates green.
cc
After #606 the offline Clear-cache purges only the transient cache dirs (tracks/tracks-cmaf/artwork), never the organized downloaded albums — those are removed per-item from the offline manager. The old copy ('Remove every downloaded track. This cannot be undone.') was misleading. Relabels the row 'Clear cache', rewrites the description to say downloads are kept and point to the manager, and softens the toasts to 'Cache cleared' / 'Couldn't clear the cache'. 8 locales.
cc
The immersive VolumeBar only called set-volume (live) and never persist-volume, so a volume set in the immersive view was lost on restart/track-change. Adds a released(float) callback emitted on pointer-up, wired to NowPlayingState.persist-volume — parity with the NPB sliders. Pure .slint; persist-volume already exists in Rust.
cc
Adds a compact dropdown in the Library Albums (favoriteAlbums) row header, left of the page chevrons: Recently added (default), First added (reverse), Random (shuffle). Reorders the cached base list in Rust with no re-fetch and re-fires artwork by the new index. Also corrects the stale HomeView comment that claimed the recently-played refresh lives in the toolbar (it is a row-header button). 8 locales updated.
cc