Commit Graph
100 Commits
Author SHA1 Message Date
vicrodh dabf4a88c8 feat(kiosk): Now Playing — queue/history tabs, cover<->lyrics toggle with synced follow
Left panel toggles cover/synced lyrics (reuses LyricsLinesView, gated on status==2 to dodge the geometry recursion footgun); right panel is a Queue/History list (Flickable+VerticalLayout so rows top-anchor). The lyrics-sync engine bailed unless a desktop/immersive surface was open, so a kiosk-lyrics-follow flag on ShellState widens lyrics_surface_open().

cc
2026-07-12 12:43:56 -06:00
vicrodh 8cdf26cd34 perf(kiosk): window the album grid so only visible cards mount
cc
2026-07-12 12:43:56 -06:00
vicrodh 26f9e3e1a2 feat(kiosk): shell — mount immersive overlay, hide transport on Now Playing, fixed footer, drop Search tile
The immersive overlay hangs inside AppShell, so the kiosk shell mounts its own copy. The transport bar is hidden on Now Playing (redundant there) and the content height no longer subtracts it then, so the NavRail stays pinned. Footer Search tile removed — the back-bar carries a persistent search field.

cc
2026-07-12 12:43:56 -06:00
vicrodh 67d81ea415 feat(kiosk): boot windowed by default, fullscreen opt-in via QBZ_KIOSK_FULLSCREEN
Forcing fullscreen at kiosk boot trapped the user with no way out (Esc/F11 don't leave it, the kiosk shell has no titlebar control). Fullscreen is now opt-in for a real appliance image; the persisted-kiosk-profile case opens in a normal window.

cc
2026-07-12 12:43:56 -06:00
vicrodh c36cea7445 feat(kiosk): lightweight KioskLocalLibrary + KioskMyQBZ views
KioskLocalLibrary (tabs Albums/Artists/Folders/Tracks, reading
LocalLibraryState, driving LocalLibraryActions; open-album routes to the
local-album detail) and KioskMyQBZ (Mixtapes/Collections tabs over
MyQbzState, MyQbzActions.open-card). Both reuse the shared kiosk cards/grid
+ ListView for tracks. Wired onto the local-library / mixtapes / collections
routes. With this every kiosk content view is the light version. Validated
via slintcheck. (v1: no album-window / tracks-load-more hooks yet.)

cc
2026-07-11 08:39:38 -06:00
vicrodh 9041b0e749 feat(kiosk): forward nav, window chrome, NPB gating, list windowing
- Add the forward nav button beside back (NavState.can-forward/request-forward).
- The kiosk back bar owns the window chrome (no HeaderBar): macOS leaves the
  ~78px traffic-light inset, Linux frameless draws the custom WindowControls
  on the right. OS-agnostic (the kiosk is not restricted to any platform).
- Hide ALL NPB view modes not compatible with kiosk (New/Classic/Small/Large
  + Miniplayer + Immersive) while in the kiosk profile; only the profile
  toggle remains.
- Track lists (Library/Search) use ListView so only the visible band mounts
  — the plain VerticalLayout spiked CPU on entry, which a Pi cannot afford.

cc
2026-07-11 08:16:24 -06:00
vicrodh 6032791be4 feat(kiosk): Now Playing centerpiece + NPB miniplayer/immersive consistency
KioskNowPlaying: a 50/50 full screen (big cover + title/artist + AudioStamp
+ SeekBar + the reused 7-control TransportControls on the left, the Up-Next
queue on the right), reading NowPlayingState/QueueState and calling
QueueState.panel-opened() on init. New ContentView.now-playing route; the
NavRail Now Playing tile navigates there instead of opening immersive. Also
hide the Miniplayer + Immersive items from the NPB view menu while in the
kiosk profile (todos o ninguno) so it is not a second-window/overlay mix.
Validated via slintcheck.

cc
2026-07-10 21:38:57 -06:00
vicrodh f1d965b04e feat(kiosk): live Kiosk<->Desktop toggle in the NPB layout menu
Add a "Kiosk mode" / "Desktop mode" item to both PlayerBarSmall and
PlayerBar view menus (label from the new ShellState.kiosk-profile flag).
It fires media-action("npb-view","","toggle-profile"); Rust flips the
persisted ui_prefs.profile and swaps the shell in place via set_screen
(KioskShell/AppShell share state, so no restart). The toggle keys off the
current screen, not kiosk_profile_active(), so a QBZ_PROFILE env override
doesnt pin it. ShellState.kiosk-profile is seeded at window setup.

cc
2026-07-10 21:17:12 -06:00
vicrodh dc51c62c7f feat(kiosk): add Local Library + MyQBZ tiles, center NavRail icons
NavRail grows to 8 tiles (Discover, Library, Local Library, MyQBZ, Search,
Now Playing, Visualizer, Settings) routing through the same header-menu
routes. Also center each tile's icon: a fixed-size Image does not center
itself in a VerticalLayout, so wrap it in a centering HorizontalLayout.

cc
2026-07-10 20:34:49 -06:00
vicrodh ad9a23bab6 feat(kiosk): lightweight KioskAlbum + KioskArtist detail views
Replace the reused AlbumPageView/ArtistPageView on the kiosk album/artist
routes with Pi-light detail: KioskAlbum (cover + title + tappable artist +
Play/Shuffle + number/title/duration track list, reading AlbumState) and
KioskArtist (round portrait + Play/Radio + discography as KioskCard shelves
per release section, skipping the \"other\" bucket, reading ArtistState). Both
bind the shared state so covers push in. Validated via slintcheck.

cc
2026-07-10 19:54:56 -06:00
vicrodh 172dbf8eb6 fix(kiosk): position NavRail tiles manually so they distribute
Layout-based attempts (child stretch, mount stretch, explicit HL width) all
left the tiles collapsed to icon width packed on the left. Position the 6
tiles by absolute x + explicit width derived from root.width instead; the
rail width comes from the KioskShell VerticalLayout, so the tiles now span
the panel evenly.

cc
2026-07-10 19:49:50 -06:00
vicrodh 45cbe24206 feat(kiosk): lightweight KioskSearch + KioskLibrary views
Replace the heavy reused SearchResultsView and FavoritesView on the kiosk
search/favorites routes with Pi-light versions built from the shared kiosk
cards: KioskAlbumGrid (wrapping grid of KioskCard), KioskArtistCard (round),
KioskTrackRow. KioskSearch tabs All/Albums/Tracks/Artists/Playlists reading
SearchState; KioskLibrary tabs Tracks/Albums/Artists/Playlists/Labels reading
FavoritesState. Both bind the shared models so the artwork pipeline pushes
covers in with no request hook (album covers windowed = first ~60 for now).
Validated via slintcheck.

cc
2026-07-10 19:21:48 -06:00
vicrodh f27d5e864c fix(kiosk): pin NavRail row to root.width so tiles distribute
The tiles still collapsed to the bottom-left even with per-tile
horizontal-stretch: a HorizontalLayout nested in the rail's VerticalLayout
was taking its preferred (content) width, not the rail width. Drop the
wrapper, make the tile row an explicit width: root.width child with the
hairline as an overlay, and the 6 tiles now spread evenly across the panel.

cc
2026-07-10 19:21:48 -06:00
vicrodh ce2db8cb54 feat(kiosk): lightweight KioskDiscover with simple cards
Replace the heavy reused HomeView on the kiosk home route with a Pi-light
Discover: KioskCard (square art + quality badge + title + artist, tap-to-
open, no hover/context-menu chrome) in horizontal shelves, reading the SAME
DiscoverState model so the artwork pipeline pushes covers in with no request
hook. Tab pill (Home/Editor's Picks/For You). First of the mockup-matching
lightweight views; also guards single-key hotkeys while typing in the kiosk
search field. Validated via slintcheck.

cc
2026-07-10 18:39:24 -06:00
vicrodh 01311db29c fix(kiosk): stretch NavRail tiles across the full width
The tiles collapsed to icon width, packed bottom-left with truncated labels
(a horizontal-stretch set only on the child component root is not honored by
the parent layout). Set horizontal-stretch at each mount site + fill the rail
height so the 6 tiles distribute evenly as full-height touch targets.

cc
2026-07-10 18:39:24 -06:00
vicrodh dc32cafbd5 fix(kiosk): resolve is_creating_mini() at window setup
The P0 kiosk block read a creating_mini local that only exists inside
select_slint_backend(); at the main-window setup site it is out of scope.
Call crate::miniplayer::is_creating_mini() directly (same source the backend
hook uses) so the qbz bin compiles. Confirmed: the kiosk shell builds
(release) and launches.

cc
2026-07-10 17:21:53 -06:00
vicrodh 93e523f51b feat(kiosk): QBZ_KIOSK_NO_FULLSCREEN dev escape hatch
Skip fullscreen-at-boot when QBZ_KIOSK_NO_FULLSCREEN is set so the kiosk
layout can be previewed in a normal window (the kiosk shell exposes no
titlebar control to leave fullscreen). reduce-motion still applies.

cc
2026-07-10 16:39:06 -06:00
vicrodh 408c945c44 feat(kiosk): persistent search field in the KioskShell back bar
The kiosk drops the HeaderBar (where the desktop search box lives), so add a
search LineEdit to the back bar: Enter submits through SearchActions.submit
(the same seam the header uses) and lands on the results view. This makes
Search functional end-to-end in kiosk (query -> SearchResultsView -> tap a
result -> the album/artist detail views mounted in P2). Validated via the
slintcheck stub.

cc
2026-07-10 15:02:38 -06:00
vicrodh af34a8e40c feat(kiosk): full ContentView route parity in KioskShell (P2)
Extend the KioskShell content mount to every ContentView route AppShell
hosts (discover-browse, recent-albums, local-album, artist-releases,
musician, label(-releases), award(-albums), location, purchases, purchase-
album, playlist(-manager), mixtapes, collections, mixtape-detail,
discography-builder, offline/blacklist managers, local-library, mix,
playlist) plus the offline placeholder + qobuz-view-blocked gate — a
verbatim copy of AppShell so navigation reaches the same real views by
construction. Discover, Search, and the album/artist detail views tapped
from them are functional. Validated through the Slint compiler (slintcheck
stub). ShellContent DRY extraction still deferred.

cc
2026-07-10 14:59:07 -06:00
vicrodh 242a02055a feat(kiosk): KioskShell touch shell + NavRail (P1, axis B)
Add AppScreen.kiosk mounting a KioskShell sibling of AppShell in the same
AppWindow: a thin back bar + persistent PlayerBarSmall + a bottom NavRail of
6 tiles replace the HeaderBar/Sidebar, sharing every *State global verbatim.
The NavRail routes through the SAME NavState routes the desktop uses
(header-menu-navigate / request-settings / ContentView.search). Content
reuses the real Home/Album/Artist/Favorites/Search/Settings views (verbatim
subset of AppShell mounts) with the arithmetic content-height pattern to
avoid the layout-collapse footgun. Rust lands the Kiosk screen post-login
when the kiosk profile is active. Validated through the Slint 1.16.1
compiler (slintcheck stub); remaining routes + touch-interaction remap are
P2/P3.

cc
2026-07-10 14:48:53 -06:00
vicrodh 7e6e7ade17 feat(kiosk): QBZ_PROFILE=kiosk runtime profile (P0, axis A)
Add a kiosk startup profile for small-panel touch appliances (Pi/SBC): the
main window boots fullscreen and forces reduce-motion regardless of renderer
tier. Resolution is env-first (QBZ_PROFILE=kiosk, so the kiosk image sets it
in the autostart) with a persisted ui_prefs.profile fallback; the miniplayer
window is never affected. No .slint touched. The kiosk image still pins
QBZ_RENDERER=gl and the XS ui_scale preset via env (separate knobs).

cc
2026-07-10 14:17:49 -06:00
vicrodh a9949b8cf2 chore: fix stale packaging and monitor references to the deleted tree
cc
2026-07-10 01:05:47 -06:00
vicrodh 15ff525d43 ci(dependabot): track the crates/ workspace, drop npm and src-tauri
cc
2026-07-10 01:04:42 -06:00
vicrodh ca1fa56a5b docs(contributing): route contributors to the crates/ Slint tree
cc
2026-07-10 01:04:31 -06:00
vicrodh 13fce17bd1 refactor(qbz-app): drop Tauri-only boot/graphics/settings modules
Remove boot_watchdog, graphics_detection, and settings::{download,legal,
library,image_cache} (only src-tauri consumed them). Split
graphics_autoconfig: keep Environment/detect_environment/detect_gpu_name
(used by the Slint diagnostics panel), drop the WebKit Recommendation half.

cc
2026-07-10 01:02:38 -06:00
vicrodh 39204a41ff chore(cleanup): remove legacy Tauri/vite dev scripts and legacy-tree-notice workflow
cc
2026-07-10 01:00:06 -06:00
vicrodh 6f2dc90ef9 chore(cleanup): drop root npm/vite/svelte build config, patches, and .env.example
cc
2026-07-10 00:59:53 -06:00
vicrodh f8a42d3f42 chore(cleanup): remove legacy Svelte src/ and Tauri src-tauri/ trees
The live app is the Slint UI under crates/. Both trees are preserved at
tag legacy-tauri-svelte for reference.

cc
2026-07-10 00:58:35 -06:00
vicrodh e8d7f28f0e chore: bump flake to 2.0.1
cc
2026-07-09 18:15:42 -06:00
vicrodh 5ba00ab4ef ci(linux): save the amd64 slint-linux cache on main (was never written)
release-linux had save-if:false deferring cache writes to build-slint.yml,
which no longer runs — so the amd64 `slint-linux` dep cache was never created
and every release/snap amd64 build ran fully cold (no dep cache; qbz_ui
recompiles regardless). release-linux-aarch64 already saves (save-if:true),
which is why only the aarch64 cache existed. Save on main so pre-flights warm
it and tag runs restore it.

cc
2026-07-09 12:09:54 -06:00
vicrodh 235bad253a ci(snap): skip the Snap Store publish when no store credentials are set
The publish step failed the whole run with "login_data is empty" when
SNAPCRAFT_STORE_CREDENTIALS is unset, even though both binaries compiled and
the amd64 snap packed fine. Move the secret to job level and gate the publish
step on it: with no secret the snap is still built and uploaded as an artifact,
publish is skipped (green run) instead of failing. Applies to both arches.

cc
2026-07-09 10:13:02 -06:00
vicrodh 207d928fd8 ci(snap): add an arm64 track to release-snap (native runner, dump repack)
Both arches now publish from CI: the existing amd64 track plus a new
build-binary-arm64 (native ubuntu-24.04-arm, free for public repos, reuses
release-linux-aarch64's cargo cache) -> snap-arm64 that dumps the prebuilt
aarch64 binary with packaging/snap-slint/snapcraft-arm64.yaml. This CI upload
is the sole publish path; Launchpad / "Build from GitHub" is not used.

cc
2026-07-09 08:13:58 -06:00
vicrodh 50d6b6d4da docs(release): carry the 2.0.x codename into the 2.0.1 TAG_DETAILS
New rule: the release codename stays fixed across a minor line and only changes
at the next minor (2.1.x). 2.0.1 therefore keeps "Rebuild 破 (You Can (Not)
Advance)"; the About-modal literal is unchanged.

cc
2026-07-08 22:56:05 -06:00
vicrodh 59754b4169 Merge pre-release into main for 2.0.1 2026-07-08 22:48:48 -06:00
vicrodh 078cce0e32 docs(release): keep 2.0.0 TAG_DETAILS, add a 2.0.1 one-liner section on top
The About modal and website render the release's TAG_DETAILS; for the 2.0.1
point release, preserve the full 2.0.0 story and prepend a terse 2.0.1 section
(one line per fix) plus the 2.0.1 compare link.

cc
2026-07-08 22:48:29 -06:00
vicrodh 7ea9e2567b chore: bump version to 2.0.1
Workspace + lockfiles, the dead-Tauri consistency version fields, the metainfo
<release> entry, and the release docs (TAG_DETAILS.md / CHANGELOG.md).

The About-modal codename and the TAG_DETAILS h1 are left as __CODENAME__ /
__SUBTITLE__ placeholders on purpose — the owner names the release before the
tag (2.0.0 was "Rebuild 破 (You Can (Not) Advance)").

cc
2026-07-08 18:43:27 -06:00
vicrodh bb01545996 Merge bugfix/queue-context-menu-actions into pre-release 2026-07-08 18:38:32 -06:00
vicrodh 222001a9eb fix(queue): wire the Remove all after and Add to playlist row actions
The per-track context menu in the Queue sidebar had two placeholder items
with no click handler: "Remove all after" and "Add to playlist". Wire both
end to end.

- QueueState gains remove-all-after(int) and add-to-playlist(int), resolved
  from the page-local row index like remove-upcoming (search-filter aware).
- Core/player gain remove_upcoming_after(upcoming_index): truncates the
  upcoming list in PLAY order (shuffle-aware, reuses remove_upcoming_track),
  clears the stop-after marker if it lands in the removed range, and emits a
  single QueueUpdated. The pre-existing remove_after (absolute-index, not
  shuffle-aware) was dead code and stays only for its unit coverage.
- Add to playlist reuses the save-as-playlist picker handoff seeded with the
  single row's track id.

cc
2026-07-08 18:38:27 -06:00
vicrodh a84519c10e Merge bugfix/viz-paused-cpu into pre-release
cc
2026-07-08 16:57:28 -06:00
vicrodh f5ed2ec1fe fix(viz): stop the visualizer pipeline while paused
The NPB Large dock idled at ~2.5% CPU paused: the visualizer gates on
visibility only, so the FFT producer kept re-transforming the stale
ring buffer at 30fps and the drain timer kept churning the 28 bar
models. Producer now parks when enabled-but-paused (same bounded
200ms park_timeout shape as the existing disabled-park; the atomic is
a lone Relaxed store, submit path untouched); the drain early-returns
when not playing and unparks the producer on the resume edge, so
fresh bars land in <100ms. The paused flag is stored at every
playing-state flip: toggle, track meta seed, the poll loop dirty-push
(catch-all for MPRIS/tray/hotkeys/QConnect within one 450ms tick),
the cast poll while casting, stop-after, queue end and the
unavailable-skip halts. Bars freeze at their last values while
paused; immersive freezes too — intended. Legacy Tauri tree's tap
literal gained the field to keep compiling.

cc
2026-07-08 16:57:28 -06:00
vicrodh fef978fc76 Merge feature/qconnect-device-name into pre-release
cc
2026-07-08 15:26:39 -06:00
vicrodh 5d50158edb feat(qconnect): restore the device-name setting from Tauri
Tauri let the user rename how QBZ announces itself to Qobuz Connect;
the Slint port lost the UI entirely while keeping the persistence and
the announce path (same device_name key in qconnect_settings.db, read
by connect() -> bootstrap_remote_presence). Restored 1:1: a text row
in Settings > Playback next to the QConnect startup select (LineEdit
mirroring the ListenBrainz token row, placeholder = the resolved
default 'Qbz - {hostname}'), a new settings-string callback channel
through the settings chain, and a live-service setter so a rename
takes effect on the next connection without an app restart — exactly
Tauri's semantics (no live re-announce; the row description says so).
Trim-only validation like Tauri; empty clears the override. Two new
msgids across the 8 locales.

cc
2026-07-08 15:26:39 -06:00
vicrodh 029bb0583f Merge feature/recent-albums-view-all into pre-release
cc
2026-07-08 14:07:16 -06:00
vicrodh d3b00b78ef feat(home): Recently Played Albums gets a View-all full page
The rail's 'View all' opens a dedicated page listing the recently
played albums (the dedicated album history from the #567 fix, cap 24).
Built on the AwardAlbumsView skeleton stripped to essentials: fixed
header + windowed AlbumCollectionView wired per the new sampler
contract, scroll-restore, shared scrollbar. Works offline (local
history — the AppShell arm deliberately skips the offline gate) with
mixed-source rows routing through the existing open-album funnel
(qobuz/plex/local) and source-split artwork dispatch mirroring the
rail's. Full nav integration: NavEntry::RecentAlbums round-trips
through last_nav so view persistence restores it (a local JSON read —
cannot crash-loop), ADR-004 back button via the standard record +
navigate + flags shape. Zero new msgids — title, empty state and the
View-all link all reuse existing strings across the 8 locales.

cc
2026-07-08 14:07:16 -06:00
vicrodh addc18b219 Merge bugfix/windowing-recursion-sweep into pre-release
cc
2026-07-08 13:43:50 -06:00
vicrodh 671e2c4cb7 fix(ui): kill the windowing recursion class — snapshot-sampled bands + crash-chain watchdog
Third and final hit of the 'Recursion detected' panic class
(i-slint-core properties.rs:583), this time crashing DiscoverBrowse on
startup view-restore (and crash-looping via persistence). Root cause
symbolized from the field backtrace: every .slint 'changed x =>'
compiles to a ChangeTracker whose watched expression evaluates
SYNCHRONOUSLY at component instantiation, and conditional/repeated
components instantiate from ensure_updated INSIDE whatever property
evaluation triggered them — so a windowing band that reads layout
results (viewport-y, flick.height, self.width, absolute-position) can
re-enter a property mid-evaluation. Timing-sensitive: needs the model
filling in the same pass that instantiates the grid, which startup
view-restore hits deterministically.

Class-kill, uniform on every windowed surface (owner mandate — no
survivor exceptions): band state is now plain properties written ONLY
by an 80ms sampler Timer (event-loop context, nothing on the property
stack) in the shared AlbumGrid, the horizontal Carousel and the search
grid. No changed handlers, no init-time reads; the ±1-viewport margin
absorbs the sampling cadence; consumers' declarative wiring unchanged
(inputs are polled, never observed — contract documented in-file).

Startup crash-chain watchdog ('perro policía'): a counter file armed
before risky init and cleared by the same liveness signals as the
renderer sentinel. One pre-liveness death -> next boot resets exactly
ui_prefs last_nav/last_view (the field-verified surgical bypass) with
a recovery toast; two -> additionally bypasses the session-persist
queue restore for that boot, and the exit save keeps the preserved
snapshot instead of clobbering it with the empty queue. Recovers this
incident and the #558 loop automatically. One msgid x8.

cc
2026-07-08 13:43:50 -06:00
vicrodh 11ed6c6dea Merge bugfix/search-artist-carousel-height into pre-release
cc
2026-07-08 11:51:51 -06:00
vicrodh 42d7375dab fix(ui): size the Search artists carousel for the taller ArtistCard
ArtistCard grew for the Recommendations subtitle line, and Search
cards fill that same slot ('N albums') plus the Follow chip — but the
two Search carousel instantiations kept the 220px default, clipping
the chip. Pass card-height 256px, the same value Recommendations
sizes the identical stack with. ArtistCard itself untouched.

cc
2026-07-08 11:51:51 -06:00
vicrodh 9a66b6dc1e Merge chore/legacy-tree-notices into pre-release
cc
2026-07-08 11:47:28 -06:00
vicrodh ec1f48b581 chore: legacy-tree notices — PR bot, sentinel READMEs, PR template
Two contributors in one week sent solid PRs against the retired
Tauri/Svelte tree (#561, #564) because nothing in the repo says it is
dead. Three zero-risk notices, no code touched: a pull_request_target
bot that leaves one sticky comment when a PR touches src//src-tauri/
(target on purpose — fork PRs cannot be commented under plain
pull_request; the PR code is never checked out), sentinel READMEs at
both tree roots (what GitHub renders when browsing), and a PR template
checkbox. The tree deletion + local reference worktree is the 2.0.2
follow-up (owner hold: no dependency risk allowed before 2.0.1).

cc
2026-07-08 11:47:28 -06:00
vicrodh e576d4d210 Merge bugfix/home-customize-566-567 into pre-release
cc
2026-07-08 11:46:57 -06:00
vicrodh 338d20df9b fix(home): render all 13 configurator sections; own recent-albums history
Two Home bugs, one dropped port. #566: four configurator sections
(favoriteAlbums 'Library Albums', qobuzMixes, releaseWatch, topArtists)
were offered and persisted but had no HomeView render arm and no data
in load_home — enabling them produced an invisible row. All four were
genuine Tauri-Home features (DiscoveryView.svelte arms :632/:653/:660/
:666, loaded unconditionally), so this is the full port, not a trim:
own HomeState fields (separate lifecycle from ForYouState), shared
foryou fetch pipelines (favorite_album_cards / fetch_release_watch /
top_artist_cards extracted pub(crate), For You behavior untouched),
Home-scoped artwork targets, and HomeView arms mirroring ForYouView.
load_home joins the three fetches concurrently with the index — zero
added latency. A HOME_RENDERABLE guard next to descriptors_for is now
the single source of truth: an enabled id without a render arm can
never emit an invisible descriptor again.

#567: 'Recently Played Albums' starved because albums derived from the
24-track history (long albums collapse to ~4 distinct). recently.rs
now keeps a separate album history (dedupe by id at record time, own
24 cap) with additive migration from the legacy bare-array format;
tracks keep their cap; load()/load_albums() signatures unchanged.

Zero new msgids (all three titles reuse For You's). qbz-app tests
10/10; slintcheck green.

cc
2026-07-08 11:46:57 -06:00
vicrodh 99a2079df1 Merge bugfix/html-entity-decode into pre-release
cc
2026-07-08 11:11:29 -06:00
vicrodh 604902521f Merge bugfix/lenient-parse-extension into pre-release
cc
2026-07-08 11:11:29 -06:00
vicrodh c21a9393a7 fix(text): decode HTML entities in API prose — including malformed bare forms
Qobuz prose fields carry HTML entities the plain-text Slint renderer
showed literally — the artist bio credit rendered '&copy John Book
/TiVo' (the API sample itself ends '&copy  Mariano Prunes /TiVo': a
malformed entity with NO semicolon, which the old decoder never
matched). New qbz_text_utils::decode_html_entities: ~90 named
entities, full numeric dec/hex with cp1252 quirk range, and a
word-boundary tolerance for the bare no-semicolon forms restricted to
copy/reg/amp/nbsp (documented tradeoff; 'AC&DC' never decodes). Wired
INTO the existing strip_html pipeline so every treated funnel (album/
label/playlist descriptions, bios) inherits it, plus four new direct
sites: bio source credit, magazine story title/excerpt, and the
track/album copyright lines in the info modals. 19 unit tests.

cc
2026-07-08 11:08:01 -06:00
vicrodh e73dcf65da fix(parse): lenient per-item parsing everywhere a bad item blanked a list
Extends the #556/#565 fix (one malformed item in a JSON array nuked
the whole list via from_value::<Vec<T>>().unwrap_or_default()) from
favorites to every remaining silent-blank site found by audit: the
search parse_page (EVERY search tab — scalars now parse separately,
items per-item), For You fav-albums/fav-artists rails, the daily-mix
favorite_tracks source, and qbz-qobuz get_dynamic_suggest. The helper
moves to qbz-models::lenient (the one crate all consumers reach) with
unit tests; favorites re-uses it via alias, local copy deleted. The
~30 endpoints whose parse errors PROPAGATE stay as-is — honest errors
stay honest. New parse_page test covers a poisoned item surviving.

cc
2026-07-08 11:00:15 -06:00
vicrodh 982716acb7 test(core): fix album_with_artists fixture — missing composer/version
qbz-core tests did not compile since the Album model gained these
fields; the test-only helper never got them. Found while adding the
poisoned-item parse_page test.

cc
2026-07-08 11:00:15 -06:00
vicrodh 2f258e1933 Merge fix/macos-titlebar-settings into pre-release
cc

# Conflicts:
#	crates/qbz-ui/ui/settings/AppearanceSettings.slint
2026-07-08 10:53:01 -06:00
Victor RH c671bcd85e Merge pull request #569 from afonsojramos/charlottetown
fix(settings): hide title bar section on macOS
2026-07-08 10:52:32 -06:00
vicrodh 0e55f6a24f fix(ui): macOS title bar — restart-to-apply toggle, hide-completely row hidden
PR #569 follow-through with the owner-corrected scope: on macOS BOTH
titlebar modes work (native bar, or traffic lights integrated in the
QBZ header) — what's broken is hot-applying the toggle: the overlay
window attributes are fixed at creation, but the .slint chrome
(no-frame, header drag+inset, Purchases placement) flipped reactively,
hiding the buttons one way and overlapping them the other.

Split pref from applied state: the toggle edits use-system-title-bar
(persisted), the chrome reads a new system-title-bar-active seeded at
startup; the handler mirrors pref->active live on Linux only (behavior
byte-identical there), macOS keeps the session value + the existing
restart toast. The hide-title-bar row is macOS-hidden (that part of
the PR was right); the system-bar toggle stays.

cc
2026-07-08 10:52:30 -06:00
vicrodh a65d1a9be5 Merge feature/album-favorite-toggle into pre-release
cc
2026-07-08 09:05:02 -06:00
vicrodh 860f49ca3e feat(favorites): album favorite action is a real toggle everywhere
Follow-through on 6a0c3d5e (state-visible hearts): the card-level
album 'favorite' arm was still add-only — a filled heart re-added and
toasted 'Added to favorites'. It now toggles, mirroring the header
arm: fav_cache-keyed, optimistic set_album_row_favorite + rollback on
error, cache written on success only, reco logging on ADD only (the
Capa B search-interaction record is now gated to adds, like tracks).
AlbumCard's menu item goes state-conditional (Remove from Library +
filled heart), and the now-playing '+' flyout album entry becomes
state-honest through a new NowPlayingState.album-favorite (seeded per
track in refresh_now_playing_meta, flipped live by
set_album_row_favorite — which now also syncs the open album page's
header heart, closing the stale-header undo loop). Favorites' own
intercepted unfavorite (fade + row removal) is untouched, both view
modes. One msgid x8 locales.

cc
2026-07-08 09:05:02 -06:00
vicrodh 872aaf22f1 Merge bugfix/artist-album-favorite-sync into pre-release
cc
2026-07-08 08:27:18 -06:00
vicrodh 6a0c3d5e58 fix(favorites): album cards show and sync favorite state app-wide
Port of PR #564 by @afonsojramos (his fix targeted the retired Svelte
tree; the live Slint app had the same bug, wider): AlbumCardItem
carried is-favorite and AlbumCard rendered the filled heart, but only
the Favorites tab ever populated it — artist discography, album
suggestions, search, home, discover, foryou and reco cards all showed
empty hearts for favorited albums. The six mapping funnels now seed
the flag from fav_cache, and a new set_album_row_favorite() flips it
by album id across every visible card model (mirroring the tracks'
set_row_favorite), wired to the card heart and header toggle with
optimistic flip + rollback, and to the Favorites unfavorite.

Album favorite analysis and Svelte reference: afonsojramos (PR #564).

cc
2026-07-08 08:27:18 -06:00
vicrodh 6d9e95d883 Merge feature/qconnect-startup-setting into pre-release
cc
2026-07-07 17:37:30 -06:00
vicrodh 00a44e123f feat(qconnect): startup behavior setting — remember state / on / off
Restores the Tauri-era Qobuz Connect startup setting, now in
Settings > Playback per owner request. Reuses the shared
qconnect-app::startup module (QconnectStartupMode +
compute_effective_startup, unit-tested) and Tauri's exact
qconnect_settings.db keys (startup_mode / last_known_state), so an
upgrading user's choice survives. last_known_state is written at the
manual toggle's success points only — internal teardowns (D5 offline
force-disconnect, bootstrap failure cleanup) never clobber the
remembered state, mirroring Tauri's command layer. Startup auto-connect
runs post-enter_shell (online sessions only, once per process) through
the same connect() path as the toggle, on Tauri's bounded retry
schedule, bailing silently under offline mode instead of toasting per
retry. Select row mirrors the quality-retries row; 5 msgids x8 locales.

cc
2026-07-07 17:37:30 -06:00
Victor RH 65d4f92880 Merge pull request #563 from eldios/bugfix/external/flake-nix-missing-libjack2
fix(nix): add libjack2 to buildInputs
2026-07-07 17:06:06 -06:00
vicrodh b88905d1c1 Merge feature/dutch-locale into pre-release
cc
2026-07-07 16:14:24 -06:00
vicrodh 836478ed90 feat(i18n): Dutch locale — 8th bundled language
Ports PR #561 by @pbaart into the live gettext system (the PR
translated the retired Svelte catalog): full nl.po (1481 msgids, 13
plurals, msgfmt-clean, zero untranslated), 262 strings taken directly
from the contribution via the legacy-English bridge and the rest
translated using the contributor's terminology as the style guide
(nummer/afspeellijst/wachtrij/bibliotheek, informal je). qbz-i18n
registration (2-form plural, resolve_auto nl_NL->nl, 21/21 tests) and
the language dropdown at index 8 (Nederlands) across ui_prefs,
reseed_i18n_labels and state.slint — same shape as the ru/ja commits.

Dutch translation credit: pbaart (PR #561).

cc
2026-07-07 16:14:24 -06:00
vicrodh 33460f9f9b fix(ui): log viewer footer = Copy all + Upload; advanced stays behind [+]
Owner-corrected UX: the compact view keeps exactly TWO buttons — the
screen must serve fast. Upload moves from the advanced row to the
always-visible footer (with its uploaded-URL row un-gated so the link
shows in compact view too); level/search/refresh/auto-tail, the
diagnostics-bundle copy, open-file and clear stay behind [+]. Reverts
the open-expanded default from fe43c698.

cc
2026-07-07 14:50:32 -06:00
vicrodh d0fadbc906 fix(playlist): mixed-playlist Plex rows render on raw connectivity, not mode
E13 gated the Plex sidecar rows of a mixed Qobuz playlist's offline
detail on mode == InducedOffline. A logged-out session (#553 start
offline) reports RealOffline WITH connectivity Up, so a Plex+Qobuz
playlist rendered EMPTY: Plex hidden by the mode check, the Qobuz
block snapshot-bound. Whether the Plex server is reachable depends on
raw connectivity, not on why the session is offline — gate on that.
Field log: paste.rs/d0d7f (mode=RealOffline, connectivity Up,
offline_session=true).

cc
2026-07-07 13:38:10 -06:00
vicrodh fe43c698d0 fix(ui): log viewer opens expanded — upload/copy actions visible by default
The compact-by-default state hid the Upload and diagnostics-bundle
buttons behind the [+] toggle, defeating the point of the new Share
logs entry (users looking to share logs saw only Copy all). The [-]
still collapses. The upload already ships the full diagnostics report
+ the last 200 redacted lines — no content change needed.

cc
2026-07-07 13:21:04 -06:00
vicrodh eca07b146c fix(ui): grouped sections back to full mounts — abs-position binding recurses
Field-hit on LL Albums grouped (alpha): deriving each section grid's
content-offset from an absolute-position DELTA binding panics with
'Recursion detected' (i-slint-core properties.rs:583). Evaluating the
target-row chain through absolute-position can land inside the parent
layout solver's own pass, and the grid's height/columns read back
self.width — re-entry. The Timer-seed guard only covered init-time
reads, not binding-evaluation context. Grouped windowing needs
Rust-computed per-section offsets (backlog); the flat grids — the big
win — keep their windowing unchanged.

cc
2026-07-07 12:21:54 -06:00
vicrodh dc284bb272 fix(playback): declare the boxed Send future in auto_skip_unavailable's signature
The async recursion (play_audible -> auto_skip_unavailable ->
after_track_change -> play_audible) made the future's Send-ness
self-referential: with inferred impl-Future types rustc hits a query
cycle ('cannot satisfy ...: Send') at the Box coercion. Returning the
concrete Pin<Box<dyn Future + Send>> from the signature — the shape
async_recursion expands to — cuts the cycle; the inner Box::pin at the
recursion point is gone.

cc
2026-07-07 12:06:32 -06:00
vicrodh 39304dbef5 Merge bugfix/hyprland-wgpu-surface-timeout into pre-release
cc
2026-07-07 09:36:08 -06:00
vicrodh 6a920f6f2a Merge feature/renderer-autodetect-hardening into pre-release
cc

# Conflicts:
#	crates/qbz-ui/translations/de/LC_MESSAGES/qbz-ui.po
#	crates/qbz-ui/translations/en/LC_MESSAGES/qbz-ui.po
#	crates/qbz-ui/translations/es/LC_MESSAGES/qbz-ui.po
#	crates/qbz-ui/translations/fr/LC_MESSAGES/qbz-ui.po
#	crates/qbz-ui/translations/ja/LC_MESSAGES/qbz-ui.po
#	crates/qbz-ui/translations/pt/LC_MESSAGES/qbz-ui.po
#	crates/qbz-ui/translations/ru/LC_MESSAGES/qbz-ui.po
#	crates/qbz/src/main.rs
2026-07-07 09:14:48 -06:00
vicrodh 4c93f85aa3 Merge fix/log-noise-filter into pre-release
cc
2026-07-07 09:14:23 -06:00
vicrodh 7bf79378d2 Merge feature/guest-local-mode into pre-release
cc

# Conflicts:
#	crates/qbz-ui/translations/de/LC_MESSAGES/qbz-ui.po
#	crates/qbz-ui/translations/en/LC_MESSAGES/qbz-ui.po
#	crates/qbz-ui/translations/es/LC_MESSAGES/qbz-ui.po
#	crates/qbz-ui/translations/fr/LC_MESSAGES/qbz-ui.po
#	crates/qbz-ui/translations/ja/LC_MESSAGES/qbz-ui.po
#	crates/qbz-ui/translations/pt/LC_MESSAGES/qbz-ui.po
#	crates/qbz-ui/translations/ru/LC_MESSAGES/qbz-ui.po
2026-07-07 09:14:23 -06:00
vicrodh 6a46e1e818 Merge bugfix/556-favorites-lenient-parse into pre-release
cc
2026-07-07 09:14:23 -06:00
vicrodh cd4082909e Merge feature/ll-tracks-sorting into pre-release
cc
2026-07-07 09:14:23 -06:00
vicrodh ba45e1f216 Merge bugfix/single-instance-559 into pre-release
cc
2026-07-07 09:14:23 -06:00
vicrodh 7575b81623 Merge feature/log-uploader-visible into pre-release
cc

# Conflicts:
#	crates/qbz-ui/translations/de/LC_MESSAGES/qbz-ui.po
#	crates/qbz-ui/translations/es/LC_MESSAGES/qbz-ui.po
#	crates/qbz-ui/translations/fr/LC_MESSAGES/qbz-ui.po
#	crates/qbz-ui/translations/ja/LC_MESSAGES/qbz-ui.po
#	crates/qbz-ui/translations/pt/LC_MESSAGES/qbz-ui.po
#	crates/qbz-ui/translations/ru/LC_MESSAGES/qbz-ui.po
2026-07-07 09:14:09 -06:00
vicrodh c1efa24034 Merge fix/gentoo-overlay-hardening into pre-release
cc
2026-07-07 09:13:47 -06:00
vicrodh c6c331553b Merge bugfix/skip-unavailable-track into pre-release
cc
2026-07-07 09:13:47 -06:00
vicrodh 722dda025e Merge feature/viewport-scroll-plumbing into pre-release
cc
2026-07-07 09:13:34 -06:00
vicrodh 7d3fc2a3aa feat(renderer): autonomous degradation ladder + UI loop watchdog
Hardens the renderer auto-detect so users never need the manual
options (the anti-videogame-settings goal).

Ladder (phase 1): a failed start now walks one rung down instead of
jumping to GL — wgpu (default adapter) -> wgpu (opposite
PowerPreference) -> GL -> software. The alt rung exists because the
#542 family is an ADAPTER mixup, not a wgpu failure; its survival is
version-stamped at disarm time (renderer_wgpu_alt) so later starts arm
it directly. GL-rung failures continue to software through the
non-auto branch (the persisted 'gl' carries the 'auto-gl' sentinel
value; a user-picked gl arms the literal 'gl' — unambiguous).
Auto-degradations are version-keyed (renderer_auto_degraded): a new
build re-probes auto once, the ladder re-degrades within a start if
the stack is still broken; manual picks clear both markers.

Sentinel disarm reworked: was a fixed 5s timer that lost the race
against late startup crashes (#558's swapchain error loops forever);
now first real input / close request / clean exit, with a 30s
no-touch fallback. The 5s timer keeps only its DPR-persist duty.
User-picked 'software' no longer arms (it cannot fail to start).

Watchdog (phase 2, #555 diagnosis aid): background thread probes the
event loop's cross-thread dispatch latency every 2s (30s startup
grace, clamped against suspend, 10 consecutive >250ms probes flag
once). Detection only — never switches tiers; surfaces as the 'UI
Loop Latency' Diagnostics row + bundle line so field reports carry a
number. The #555 lag is renderer-independent, so this measures the
layer where it actually lives.

One msgid x7 (the degradation toast is now rung-generic).

cc
2026-07-07 08:00:31 -06:00
vicrodh 11467fce2a fix(player): volume tick log at debug, not info
A slider drag delivers dozens of SetVolume commands per second and
each logged an info line from the audio thread — 758 of 1025 lines in
a #555 field log. Log-level change only; the audio path is untouched.

cc
2026-07-07 06:48:23 -06:00
vicrodh 8581d39585 fix(log): demote zbus/tracing D-Bus dumps out of the default filter
zbus 5 logs every dispatched/read D-Bus message as a multi-KB Debug
dump at INFO (through the tracing-log bridge, which also emits
tracing::span events). With any MPRIS applet polling GetAll this
flooded the file sink within ~1s of startup and drowned everything
else — both #555 field logs confirmed it (one diagnostics upload
covered exactly one second of runtime). Filtered records now cost
nothing (log! checks the level before formatting). RUST_LOG still
replaces the whole default, so full zbus tracing stays available.

cc
2026-07-07 06:48:23 -06:00
vicrodh a4dce34b28 feat(ui): login disclaimer line for the guest offline mode
One sentence under the subscription notice so the always-visible
'Start offline' link is self-explanatory: QBZ works as an offline
player without a Qobuz account, with no access to the Qobuz library.
One msgid x7 locales.

cc
2026-07-06 21:59:39 -06:00
vicrodh b6e56e3b47 feat(session): guest local-only profile — offline mode without a prior login
The 'Start offline' link was gated on has-previous-session and the
shell backstop refused to open without a recorded user, so a user who
never logged in (e.g. a purchases-only Qobuz account, #553) could not
use the app at all. The link is now always visible and the offline
shell falls back to the GUEST profile — user 0, the Tauri-era fallback
this port used to refuse — building a real per-user store set (local
library, mixtapes, prefs) under users/0. AppRuntime::activate_offline
already resolved user 0 and never persists it as the last-user marker,
so the guest never masquerades as a real session.

First real login takes possession: AppRuntime::activate renames the
users/0 data+cache dirs to the account's id when that account has no
profile on this machine yet (existing profiles always win — the guest
dirs stay parked, no merge). Best-effort, login never breaks on a
failed rename.

cc
2026-07-06 21:47:00 -06:00
vicrodh bcd4574225 fix(favorites): parse favorites items leniently instead of all-or-nothing
serde_json::from_value::<Vec<T>>(...).unwrap_or_default() nuked the
ENTIRE tab when one favorite among thousands failed to fit the model
(a delisted or odd-shaped item): the count badge kept the separately
parsed total while the list rendered empty — 'Tracks 6125' over 'No
favorite tracks yet' (#556, 6125-track library on macOS; a 101-track
library never hit the poison item). Items now deserialize one by one;
misfits are skipped with a warn carrying the item id and error, plus a
summary line, so the reporter's log will name the exact offender.
Applies to all four get_favorites tabs (tracks/albums/artists/labels).

cc
2026-07-06 21:12:42 -06:00
vicrodh 763b5ae81f feat(ui): always-visible Share logs entry in the settings sub-nav
The log uploader was reachable only through Settings > Developer >
Open log viewer, so users filing issues never found it and reports
arrived without logs. A pinned entry at the bottom of the settings
sub-nav (visible from every section) now opens the same log viewer
(copy + upload). One new msgid x6 locales.

cc
2026-07-06 20:57:00 -06:00
vicrodh a2141ebe63 fix(app): single-instance guard on Linux via session-bus name (#544, #559)
Tauri parity regression: the old app shipped
tauri-plugin-single-instance; the Slint rebuild lost it, so every click
on a pinned shortcut or launcher entry spawned another full player
(reported on Hyprland in #544 and KDE in #559). The first instance now
owns the com.blitzfc.qbz session-bus name (auto-grantable inside
Flatpak — the name is the app id, no finish-args change); a second
launch asks the owner to raise its window through the already-exported
MPRIS Raise method (full name org.mpris.MediaPlayer2.com.blitzfc.qbz)
and exits. Blocking zbus 5 + async-io on purpose (the tokio feature is
forbidden graph-wide); any D-Bus failure falls through as primary so
the guard never blocks startup.

The issues' other symptoms (generic Wayland icon, forgotten window
position) trace to the missing app_id in 2.0.0 — already fixed by
dae6e7f6 (#544): compositors key both the icon and geometry-restore
rules on app_id.

cc
2026-07-06 20:56:52 -06:00
vicrodh e379aa65f6 feat(library): sort control for the Tracks tab (SQL-side)
The Tracks tab is server-paginated (200 rows/page), so the new 8-option
sort dropdown (Default / Artist / Title / Year / Date added, mirroring
the Albums toolbar control) rides the SQL query through a validated
ORDER BY allowlist in search_with_filter_page (same pattern as the
albums page query), re-queries page 1 on change (gen-bumped, stale
in-flight pages drop), and persists in locallibrary_prefs. Every sort
key ends in id so LIMIT/OFFSET pagination is deterministic across
ties. The once-only Plex page-1 merge re-sorts client-side with a
comparator matching the SQL semantics. Group modes keep their
client-side visual reorder on top, unchanged. Zero new msgids — all
labels already exist.

cc
2026-07-06 20:56:02 -06:00
vicrodh 1d16d84468 ci(gentoo): require the amd64 dist in the overlay Manifest, retry downloads
A Manifest published without its amd64 DIST entry bricks emerge for
every amd64 user, which is worse than failing the job; a 3x30s retry
absorbs release-asset propagation lag right after the tag. Also
documents the #557 root cause at the top: the overlay must never be
edited by hand — an ebuild change without Manifest regeneration is a
guaranteed digest mismatch; template changes are republished by
re-dispatching this workflow.

cc
2026-07-06 20:30:49 -06:00
vicrodh 41d93e5e5f fix(playback): auto-skip unavailable tracks instead of parking on them
Tauri regression: the Svelte playbackService caught TrackUnavailable play
errors and called autoSkipToNext (bounded at 5 consecutive skips, issue
Tauri#467); the Slint port only logged and cleared the spinner, so the
queue cursor parked on the dead track and playback stopped.

play_audible's error arm now detects TERMINAL unavailability (substring
contract over the flattened ApiError Display: TrackUnavailable /
NonStreamable / NoQualityAvailable — transient errors keep NOT skipping,
the client already retries those), toasts, honors stop-after-this-song
on the failed track, and advances through the real machinery
(advance_to_playable + after_track_change, async recursion boxed).
Superseded fetches (user already picked another track) never skip.
Counter resets when audio actually starts. One new msgid x6 locales.

cc
2026-07-06 19:55:49 -06:00
vicrodh 54b8dc4708 perf(ui): element windowing for the search albums grid
Ports the band + hysteresis scheme into SearchResultsView's bespoke
AlbumGrid (no window-changed — search artwork is dispatched per loaded
page onto the directly-rendered model) and wires the Albums tab
instance to the view's Flickable.

cc
2026-07-06 19:55:18 -06:00
vicrodh 0a05d9e4f7 perf(ui): window grouped sections; post-layout band seed via Timer
Grouped section grids now receive the windowing inputs when the surface
is windowed; each section's content-offset is the absolute-position
delta to the collection root — scroll-invariant (both live in the same
Flickable content) and exact in plain Flickable/for chains. Section
grids do not forward window-changed: grouped artwork stays full
dispatch, so windowing there is mount-only.

The band seed moves from init to a one-shot Timer a tick after mount:
evaluating the target bindings from init would pull absolute-position
while layout is still evaluating (the documented recursion panic).
Until it fires, the default [0,0] band keeps at most one row mounted
for a frame.

cc
2026-07-06 19:55:18 -06:00
vicrodh 059d34cda9 perf(ui): element windowing for Discover/Label/Artist/Award grids
Flips windowed on the four AlbumCollectionView consumers with their
scroll wiring and documented content-offset approximations. Mount-only:
artwork stays per-page full dispatch (index-keyed, append-only models),
so covers are already on the model when a card mounts. Load-more
triggers untouched (grid height formula is windowing-invariant).

cc
2026-07-06 19:55:07 -06:00
vicrodh 19ac438097 perf(ui): horizontal element windowing in the shared Carousel
Column band from viewport-x (visible plus one viewport of margin each
side) with the same contract hysteresis as AlbumGrid; off-band cards
are same-footprint placeholder tiles. Element-count only, always on —
section loaders dispatch artwork for the whole strip up front, so a
card mounting later picks its cover from the model instantly. Covers
every Home/ForYou/Discover/Search strip through one component.

cc
2026-07-06 19:55:07 -06:00