Commit Graph
9 Commits
Author SHA1 Message Date
vicrodh ed7fa588ea fix(qt): elevated controls lift on hover instead of darkening
Buttons that rest on surfaceElevated used surfaceHover for their hover
fill: a 6% white wash meant for rows that rest transparent. Over an
elevated button it lands DARKER than the rest fill — on a modal's dark
ground the Cancel/Save/footer buttons went near-black under the pointer.

QbzTheme gains surfaceElevatedHover (a lift of surfaceElevated, derived
so every palette has it), its ambient half-alpha twin, and the
elevatedFill / elevatedHoverFill pair an ambient-aware control paints.
Every site with the old pairing (65 across modals, toolbars, selects,
circle actions, chips and cards) now hovers to the lift.

cc
2026-09-14 00:49:54 -06:00
vicrodh 2fc03d325d fix(qt): restore window lifecycle and improve grid artwork
Restore tray-aware close and quit confirmation, circular label covers, and missing label artist portraits. Publish artwork progressively, preload two grid rows, and skip redundant motion reports while preserving data refreshes.

Include regression tests and isolated artwork benchmarks. Verified Linux build, QML checks, and scrolling smoke tests.

cx
2026-09-09 21:58:25 -06:00
vicrodh b1dba198c9 feat(qt): kiosk profile release hardening
Integrate the Kiosk profile hardening round: 12 dedicated Kiosk views plus
22 shared opt-in hosts, shared navigation history with Back/Forward, Albums
as the default library tab, native paged Local Library (artists/tracks),
lightweight MyQBZ grid and detail, Ephemeral lifecycle, bounded lists and
artwork sized to the physical frame, static skeletons, 64px transport bar,
Settings touch reflow and a persistent Immersive exit.

Desktop keeps its prior behavior: every shared component defaults to the
desktop path and only opts into Kiosk behavior through explicit properties
(origin-relative ScrollMemory, viewport-bounded artwork requests).

Regression coverage: kiosk navigation and artwork Node tests added to the
runtime gate, scroll-origin fixtures for fixed and grouped rows, i18n
strings for all eight locales.

Verified by 750 release tests, six QML audits, Kiosk smokes at 800x480 and
1280x800 and a Desktop A/B with seven pixel-identical captures.

cc
2026-09-07 08:23:11 -06:00
vicrodh 93ba7584b0 fix(qt): polish purchases queue and search loading
cx
2026-09-05 14:39:07 -06:00
vicrodh db1d36537b feat(purchases): genre filter, zebra rows, and a list that no longer overlaps
The list view's quality column was 110 px for a badge that is 136 px
wide, so the badge ran into the release-date column; it is 150 now, the
album list's width. Both list rows and track rows draw the odd-row
zebra every other list in the app draws. The filter panel gains a Genre
section built from the catalog genre on each purchased album (albums
tab only, transient like group and sort), counted and summarised with
the other filters.

cc
2026-09-02 07:11:03 -06:00
vicrodh e41235f791 feat(purchases): readable dates, a release-date column and sort, column labels
The list printed the purchase date as "9/1/26" and nothing else, so a
row said neither when the record came out nor, at a glance, what the
number meant. Rows now carry the catalog's original release date next to
the purchase date, both as "Sep 1, 2026", under a column-label strip
like the album list's, and the toolbar sorts by release date (`released`)
beside the four existing keys.

cc
2026-09-01 23:59:36 -06:00
vicrodh 0756e96940 fix(purchases): draw the shared quality badge instead of the raw tier word
The purchase card and list row printed `qualityTier` as text ("hires")
under the artist, the one surface in the app that did. Use the same
icon-only QualityMini on the card and QualityBadgeFull on the row that
every other album card and list row draws, with the exact quality
(DSD64/DSD128, 24-bit / 96 kHz) in the badge's tooltip and detail line.

cc
2026-09-01 23:44:43 -06:00
vicrodh 26ceeab828 feat(qt): mixed playlists, page fade, category filter, per-context recents
Six parity fronts against the Slint/Tauri references, plus the applied-filters
tooltip the app never had.

MIXED PLAYLISTS — the bug. A mixed playlist is a QOBUZ playlist carrying local
and Plex rows through library.db's playlist_local_tracks / playlist_plex_tracks
sidecar tables. This port could WRITE those rows (the picker) and COUNT them
(the Playlist Manager badge), but playlist_qt::load built its rows exclusively
from the Qobuz API response, so a track added to a playlist never appeared on
the page it was added to. Two comments elsewhere claimed the merge already
happened; they were describing the reference. Ported literally:
read_sidecar_rows_blocking (healing + bulk Plex resolve) and interleave_rows
(absolute-slot claim, same-slot collisions all emit), merged in load, with the
merged queue adopted as the open-detail snapshot.

Routing had to split with it. local_playlist_qt::open_id() was the one
predicate for every action, and a mixed detail adopts that same snapshot — so
it now means "a local:<uuid> detail OR a mixed one", which is right for
play/shuffle/row-play and wrong for the writes. Remove and reorder route on the
new local_detail_open() instead; without that, removing a local row from a
Qobuz playlist wrote into the local-playlist repo, found nothing, and reported
success. Removal by source is a third arm: the Qobuz arm parses the display id
as a u64 playlist_track_id, and a local row's display id is a library.db rowid
in the same numeric space. Reorder is refused on a mixed detail for the same
collision, in Rust and in the view.

PAGE FADE. 300 ms OpacityAnimator on the mounted page, OutCubic, honouring
reduceMotion. Zero added latency: the router's Loader is synchronous, the UI
thread is frozen through the mount and no frame exists in which a fade-OUT
could be shown, so the page snaps transparent in the same turn as the rebuild
and only the reveal is animated. An Animator and not a NumberAnimation because
it runs on the render thread — measured 0.53 vs 0.11 of progress with the GUI
thread blocked — and not on the shared pulse because the pulse arrives on the
very thread the mount blocks.

The first cut faded a veil coloured from the pane, and that was wrong in a way
that only shows in the configuration the app actually runs in: AppShell paints
the pane surface-main@0.22 while the ambient background is on, so a veil at
full opacity covered 22% of the page and the fade read as a flicker. Fading the
page has no such failure mode and keeps the ambient art visible.

SPLASH COPY. The login screen was already 1:1 (18 msgids, verified by diff).
The splash was missing the legal disclaimer the reference pins to the bottom of
the screen, drew "QBZ" / "QOBUZ PLAYER" as bare literals (English in all eight
locales), and hardcoded the dark palette — black on the other 34 themes.

CATEGORY FILTER on the Home playlists rail. Slugs now travel per card and the
offered set comes from the same /discover/index response, so the filter is
client-side over the cached 40 with no extra request. The selection lives in
Rust because HomeView is destroyed on every navigation; the reference keeps its
own for the same reason. New trigger + popup, and a `leading` slot on
QbzSectionHeader to hang it off the title line.

APPLIED-FILTERS TOOLTIP. A third QbzTooltip mode taking [{group, values}],
below the control with an upward caret. Overflow is staged summary and never
scroll — the overlay is enabled:false by construction, so a scrollable tooltip
could not be scrolled. Wired where a filter is INVISIBLE (collapsed to a count
or a glyph): the genre chip in four hosts, the Home tags, the Local Library and
Purchases funnels, the Artist Scene genres.

RECENTS BY CONTEXT. QueueTrack already carried context_kind and exactly one
function threw it away: record_queue_track wrote both album histories on every
track start whatever the user had put on, so one 40-track playlist filled a
24-entry album rail with that playlist's contents. album_play_events gains an
additive context_kind column (the other frontend names its columns on insert,
so it writes '' and '' counts as an album — no history reinterpreted), plus a
new playlist_play_history.db and a "Recently Played Playlists" rail, default on.
A separate file rather than a field in recently_played.json, which is a
destructive round trip between frontends: serde drops what it does not know and
the next write puts the truncated object back.

ITEMS PER CAROUSEL, recovered from Tauri and never ported to Slint. It lives in
a NEW TABLE of discover_prefs.db, not in the prefs blob: to_json rebuilds the
object from the keys the running binary knows and save overwrites the whole
column, so a new key would survive only until the other build's next toggle.
Default is uncapped, i.e. today's behaviour.

Two DiscoverySectionId arms were added to the frozen Slint tree, obliged by the
shared enum's exhaustive matches — same two-line follow MostPlayedAlbums took.
Not verifiable here: compiling that crate pulls qbz-ui.

Also fixes two recommendations_qt tests that had been red since c02e7713d,
which turned the two Weekly rows into ordering slots that deliberately do not
self-hide while the assertions kept the pre-split behaviour. Verified by stash
that they fail without any of this change: the code was right, the tests stale.

Eight locales updated. Release build clean, offscreen smoke gate green,
qbz-qt 421/421 and qbz-app 215/215.

cc
2026-08-19 07:00:35 -06:00
vicrodh 7d98ad044f feat(purchases): the Qt frontend — list, album detail, routing and gating
Two screens, a controller and a bridge. `QbzPurchases` publishes one JSON
document per screen plus a revision counter, so QML re-parses even when the text
is byte-identical.

- The list: two tabs with per-type counters, a server-side search on a 300 ms
  debounce, and a toolbar that DIFFERS by tab — Albums gets group, sort,
  direction, two filters and grid/list; Tracks gets group and the filters only.
  Building the Albums toolbar on both would have invented two controls.
- The detail: a format dropdown whose selection re-scopes every downloaded mark,
  the progress section and Add-to-Library together; a determinate bar counted in
  TRACKS, never bytes; goodies as an album-level action counted separately.
- The album download is the frontend loop, not the Rust command the reference
  registered and never called. Sequential, concurrency 1, cancellable, and a
  failed track does not abort the album.
- Both prefs are read at runtime, not merely written. `show_purchases` is the
  master gate and `nav_tb_purchases` only relocates the entry to the title bar;
  both default false, so Purchases still ships hidden.
- Scanned local rows keep their raw `qobuz_purchase` source so the gold badge
  survives in list mode as well as grid, while still classifying under the
  Offline source filter. Remote feed rows deliberately stay `qobuz`: stamping
  them would badge every purchase the user merely OWNS as locally downloaded.

Defects fixed after the screens were written, none of which any test could have
caught:

- `QbzPurchases.boot()` was called nowhere. Without the Qt-thread hop every
  publish is a silent no-op, so both screens would have stayed permanently empty
  with nothing logged. The cause was an ownership gap rather than the code:
  `Main.qml` belonged to no lane because the plan listed the files each lane
  CREATES rather than the files it TOUCHES, and "belongs to nobody" is
  indistinguishable from "done" until something runs.
- A second download of the same album nested inside the first. The store rewrites
  its destination to the album subfolder after the first successful track so that
  Add-to-Library adds the album rather than the whole music root, and the download
  planner read that value first — producing `.../Album [q]/Artist/Album [q]/...`,
  one level deeper each time. The picked ROOT and the resolved ALBUM FOLDER are
  now separate fields with names that cannot be confused, with a regression test.
- A tab counter could stick at zero forever with its list plainly full: the two
  ids calls each swallow their error, and the reference's backfill from the list
  response was missing.
- Cancelling made the whole progress block vanish instead of showing the
  cancelled state, whose msgid was already translated in all eight catalogs.
- Add-to-Library re-walked the entire library when the folder id it needed was
  already in hand.
- A live language switch and a logout left the previous state behind.

Deliberate divergences from the contract, each recorded with its reason: the page
header is a plain `fontSection` title with no icon tile, because nothing else in
this application announces itself over a large coloured square; purchase download
status travels inside the album document rather than on the shared cache channel,
which is keyed by track id alone and would let the two features overwrite each
other on the same row.

Gate: release build clean, QML audits 278 files / 37 bridges, `cargo test -p
qbz-qt` 398 passing, offscreen smoke green with zero QML complaints.

Not verified, and cannot be from here: every populated screen. This account owns
nothing, so only the empty states have ever rendered.

cc
2026-08-16 11:54:05 -06:00