Add the Windows experimental badge and source build prerequisites for Arch, Gentoo, NixOS and Windows. Clarify how to build against an installed Windows Qt SDK and invite help with documentation.
cx
Observe process survival through the full smoke window, isolate profiles and the session bus, restore hidden SDKs and reap private process groups. Reuse the Qt log gate and retain smoke evidence on failures. Add subprocess regressions to the test-crates gate.
cx
Translate the project statement, document the implemented offline grace window, and link the bundled DST provenance and license notices. Align the release codename, ARM64 requirements and AppStream date.
cx
Detect sector layout from Master TOC signatures and strip physical framing while preserving logical addressing, bounds and file mutation checks. Add reader and scanner regressions with synchronized CI presence guards.
Verified full decoding of 53 tracks across five real images, seek and previous-reader comparisons, isolated release UI opening and scanning, and disc, DSD, library and Qt tests.
cx
Publish the open sequence after the active session and its document on the Qt thread. Keep boot restore and metadata updates from requesting navigation.
cx
Retain artist IDs in new history snapshots and enable artist links in the carousel and View all. Route local sources to Local Library Artists and resolve older Qobuz snapshots through the existing album lookup.
cx
Record the outgoing tab state and scroll before switching, restore same-view history entries, and route sidebar tab requests through the same path without duplicating the initial landing. Add shared CI and local regressions for source filtering and tab transitions.
cx
Filter physical album variants in the compatibility detail view and cancel stale native queries and unfiltered retries. Load album rows when switching out of the native model.
cx
Carry transport failure into Qt startup with a process-local fail-fast bus address. Preserve a responsive bus when remote forwarding fails, and retry the original session on the next launch. Add the silent-bus xcb release regression to the shared CI/local runtime gate.
cx
Classify one-bit queue metadata as DSD before PCM formatting. Display older misclassified snapshots as DSD64, DSD128 and higher multiples without rewriting the stored history, and pass the detail to both carousel and full-page cards.
Verify native local-track to queue metadata and old snapshots in the passing 741-test Qt suite. Audio behavior is unchanged.
cx
Use one mapper for the carousel and full history page. Carry the stored routing source and normalized badge vocabulary through both surfaces, including existing rows, downloads and media servers.
Cover existing snapshots and catalog aliases in the Qt suite; all 741 tests pass with temporary XDG data.
cx
Save the local album identifier and active filter in the account navigation file at clean shutdown. Restore it on remembered startup, preserve launcher link and crash recovery precedence, and fall back to Local Library when the album is unavailable.
Cover profile isolation, a later Home exit, invalid context and startup policy with persistence fixtures. All 738 Qt tests pass with a temporary XDG profile.
cx
Carry the physical source list through Discover pins and Library feeds, including logical albums with multiple origins. Reuse the Local Library badge vocabulary and shared AlbumCard rendering so local origins remain visible in Discover, Library and MyQBZ.
Validated with 738 Qt tests and the six QML audits.
cx
Store browser searches and facet selections per profile with atomic writes. Prefer navigation history when available and restore saved choices before QML can publish construction defaults. Keep the startup crash guard and unrelated preferences intact.
Validate with six QML audits and two passing profile-isolation and persistence regressions.
cx
Probe Qt's default backend in a disposable graphics-only child before runtime and the main window. After proven failure, allow one software presentation attempt without persisting a renderer preference. Preserve explicit backend policy and saved-GPU validation.
Bound and reap children, keep deep links in the parent, and avoid blocked diagnostic pipes. Cover healthy/failing decisions, overrides, missing executables, paths with spaces and watchdog cleanup in the passing Qt suite.
cx
Apply a request-local 15-second deadline without limiting shared streaming requests or canceling store activation. Preserve saved tokens on network failures and release the splash into the existing login/offline route with the error visible.
Verify header stalls, partial and trickling bodies, cancellation, late responses and account retries with local HTTP fixtures plus boot-controller tests. All 103 Qobuz and 733 Qt tests pass.
cx
Use one two-second deadline across native Unix connect, D-Bus authentication, name ownership and URL/window forwarding. Own and shut down the transport on cancellation, preserve unconfirmed links, and keep the primary server alive.
Cover stalled and delayed phases, filesystem/abstract buses, ownership release and early UI handoff with isolated fixtures. All 733 Qt tests pass; async-io features remain unchanged.
cx
Deploy the official x64 redistributable DLLs into the shared MSI/ZIP payload and reject missing, debug, incompatible or shadowed CRT imports. Smoke both deployed products with isolated profiles and record loaded Qt/CRT paths.
Validate with 14 PE fixtures, PowerShell parsing, actionlint and shellcheck. Native clean-Windows acceptance remains pending.
cx
Correct dead platform declarations, stale rustdoc and test warnings; validate complete gettext headers. Install LLD in the Qt job and explicitly validate committed shader packs without rebaking them. Keep the independent shader bake gate and add three build-policy tests.
The full local gate passed 2398 workspace tests and 715 Qt tests, QML audits, shader baking and the isolated offscreen smoke without compiler, rustdoc or gettext warnings.
cx
Initialize the four new optional AlbumSummary fields in all six test fixtures. Preserve absent metadata semantics and restore workspace test compilation.
cx
Keep queue reads from starving controls without weakening real queue-write serialization. Restore official proto3 zero defaults and next/previous loop, shuffle and autoplay semantics.
Project controls from the active peer state and target exact upcoming queue occurrences without optimistic cursor changes or local playback fallback. Cover takeover, seek, mute, wire decoding and manual skip regressions in the CI gate.
cx
Group identical redacted diagnostics across stderr, file and the live ring. Flush repeat summaries on visible transitions, exports and shutdown while keeping periodic counts for sustained bursts.
cx
Restore downward stream-format downloads while keeping purchased masters on the download grant. Make Purchases playback prefer the highest complete local copy, repair duplicate copy inventory, and move copy management into a bounded modal with safe library and record actions.
cx
Parse resilient SACD images, decode DST, scan ISO releases into the local library, and keep explicit opens ephemeral.
Ship decoder notices across distribution formats and align DSD album filtering and version selection.
cx
Claiming the official client's `qobuzapp://` scheme was a side effect
of installing. On Linux that displaces nobody; on macOS and Windows the
official client registers the same scheme, and on Windows — where QBZ
is only starting to be adopted — taking the browser's links over at
install time is not a reasonable default. `qbz://` (our own scheme)
stays registered unconditionally.
Settings > Integrations > "Open Qobuz links in QBZ" (link_handler_qt,
pref `qobuz_links_enabled` beside the other Integrations opt-ins),
default on for Linux/macOS and OFF on Windows. The registration moves
only when the toggle is flipped; the one startup reconciliation is
conservative — a claim pointing at this executable while the setting is
off (the 2.0.x MSI wrote one) is dropped, nothing is ever claimed on its
own.
Windows writes/removes HKCU\Software\Classes\qobuzapp (deleting only a
tree whose command names this exe); macOS uses Launch Services and hands
the default back to the first other handler on off; Linux runs xdg-mime
on. The MSI no longer registers qobuzapp and the workflow's protocol
checks cover qbz only. The Windows and macOS arms build on their
platform gates, not on this box.
cc
The "In library" Albums section gains the release buckets' sort select
(same five keys, persisted by artist_prefs under the pseudo release type
"library"; rows sorted in QML off the document's seed). Every album
section — each catalog bucket and the library grid — gains a split
button right of its sort (controls/QbzSplitButton.qml, sized like the
sm select): the body plays the whole section in its sort order as one
queue replacing the current one (`QbzPlayer.playAlbums` ->
`playback_qt::play_albums`, each album resolved through the single-album
path, failures skipped), the chevron offers Play random (one album of
the section) and Play selected, which arms a per-section select mode:
the cards show their check bottom-right inside the art
(`AlbumCard.selectMarkBottom`), the button reads "Play selected (n)" and
plays the checked albums in sort order, and the menu swaps that row for
Cancel selection. One section is armed at a time; changing artist or
tab disarms. No queue options on purpose.
cc
With both source switches on over an artist whose purchases are not
hearted, the library tab's section set was empty, the strip unmounted,
and the switches — which live in the strip — went with it, leaving no
way to reset the filter. "About" (the strip's back-to-top) is now always
present in library mode, landing on the page top since the library
column carries no bio anchor; the active tab on a mode switch is still
the first real section.
The trailing slot moves to the far right of the bar, past the search
toggle: the field grows LEFT over the tabs when it opens, so controls
left of the magnifier were covered while typing.
cc
A track clicked under "In library" played the artist's POPULAR queue:
every row of the page went through `QbzPlayer.playArtistTrack`, whose
queue is TOP_QUEUE and which starts that list at row 0 for any id it
does not hold — the exact symptom in #737, including "it works only when
the song is also popular". The library rows now play through
`QbzLibrary.libraryPlayVisible` with the tab's full filtered list, the
source-aware seam LibraryView uses; `PopularTrackRow` takes a
`playHandler` instead of knowing which tab it is in.
The tab itself becomes a first-class view over `artist.libraryItems`:
- Tracks section modelled on Popular Tracks — header action cluster
(Play all, select mode, the shared ⋯ menu re-pointed at the library
list), 10-row preview built from a numeric model, Load more / View
less opening the whole list.
- JUMP TO follows the mode: in library only the Tracks / Albums sections
that have rows, resolving anchors against the library column (the
catalog set stayed up before and "Popular Tracks" scrolled to a section
that was not drawn).
- Purchases / favourites switches float in the strip while the tab is
active, with the Library > Albums/Tracks semantics (neither = union,
one = that source, both = intersection) and page-local state.
- A cleared heart fades the row (purchases stay); the settle republishes
the document and the row leaves. When the last row goes the page
returns to the catalog instead of an empty tab.
- Selection and bulk actions run over the active mode's lists; a mode
switch clears the selection and re-arms the warm from cache.
The view no longer parses `QbzLibrary.libraryJson` per binding.
cc
`QbzArtist.warmLibrary` lets the artist page's "In library" toggle re-arm
the Library feed warm (a fetch only if the session never loaded it, a
cache repaint otherwise). `QbzJumpNavBar.trailing` is a host-filled slot
between the tab strip and the magnifier, sized by its children and
costing nothing when empty, so a page can float mode-specific controls
in the strip without reflowing the tabs.
cc
The artist page's "In library" count was a snapshot of the Library feed
taken when the document was built, over a feed that only loads when the
Library view is opened and that never gains a row for a favourite created
afterwards. On a cold session, or after an account switch, every artist
opened with an invisible toggle; hearting an album on the page moved
nothing until the next session.
- library_qt: `artist_library_items` computes the tab's rows with the
Library > Albums/Tracks rule (favourites ∪ purchases, one row per
entity, purchase row representative, `is_favorite` = also hearted);
local/Plex and `following` rows never enter. A track is a member
through its performer OR its album's main artist (new
`FeedItem::album_artist_id`, fed by the new `AlbumSummary::artist`,
which the wire carries — search-results capture).
- library_qt: `reconcile_qobuz_favorite` inserts the row of a freshly
hearted track/album (one `/track/get` or `/album/get`, mapped by the
same producers the full load uses) and drops the `favorites` rows of a
cleared one, keeping a purchase row with its heart cleared.
- artist_qt: the rows ride inside the artist document (`libraryItems`),
and `refresh_library_items` / `apply_favorite_change` republish them
through `publish_patch` when the feed lands or a heart settles. A
debug-level log prints the membership split for the open artist.
- main: `settle_favorite` is the one settle point (reconcile, fan out,
republish on membership change) for the Library and queue-panel
hearts; `open_artist` warms the feed in the background; `reload_library`
hands the open page its rows; `warm_library` serves the toggle from
cache.
Tests: membership by performer or album artist, exclusion of local /
following, dedup on the purchase row, and the `top_queue` contract that
a foreign id starts the popular list at row 0 (the #737 defect class).
cc
Preserve the remaining queue when shuffle is disabled, expose clear transport state tooltips, retain Library source filters for the session, and add independent track-artwork preferences for Queue and Library.
cx
Add random access for DSF, DFF, and SACD demuxers, replace live DoP/native sources without releasing ALSA, and expose seek through Qt, MPRIS, and the daemon API.
cx
Show SACD images in folder grouping, keep track details responsive, avoid repeated album reload work, and exclude disabled or unconfigured media servers from filters and sync.
cx
Measured on a 24-core box: 52 threads held 82 glibc malloc arenas, 257 MB
of fragmented anonymous mappings at idle, and the 400 MB in-memory audio
cache kept every played track until another evicted it. main now calls
mallopt(M_ARENA_MAX, 2) before the first thread (an explicit
MALLOC_ARENA_MAX wins), the tokio runtime runs eight workers instead of
one per core, and the playback poll moves the L1 audio cache to the L2
disk cache after 300 s with nothing audible locally, remotely or on a
cast. Idle RSS went from 728 MB to 537 MB; the trim verified at exactly
300 s after a pause.
cc
The scan never treated an .iso as audio; images only entered the library
through Open > disc image. Each root's scan now walks its .iso files,
asks qbz_disc::sacd::is_sacd_image for the SACDMTOC signature (PVD plus
Master TOC sector, four 2 KB reads at most, no directory parsing) so a DVD
or data ISO leaves silently, skips images already imported with the same
size and mtime without any I/O, and adopts the rest through the
generation-based import. The row builder moved from sacd_qt into
qbz_library::sacd_scan and the manual open is now its client, with the
translated fallback labels. Parser and import rejections are scan errors.
cc
DSD gets its own Quality chip and Hi-Res goes back to 24-bit PCM only, in
the MediaFilter, the native catalog SQL (tracks CASE and the materialized
album quality_tier) and the legacy QML filters. A Format chip would be the
same predicate, so there is none. Schema version 6 re-derives the album
tiers that were materialized as hires.
cc
/album/get sends streamable_at and release_date_stream per track: a future
value marks a pre-release, null a pulled track. Track carries both fields
and Track::is_upcoming applies the one rule (future streamable_at wins,
then a future release date); the album, playlist, search and label rows
publish qobuzUpcoming and the unavailable glyph reads "This track is not
yet available" instead of "no longer". Artist page rows carry no dates and
keep the generic copy. New msgid in the seven translated catalogs.
cc
The now-playing stamp bubble came out wider than its lines with the text
offset, and never reproduced in isolation. The three stamp ToolTips now
bind width/height straight to the content's implicit size with zero
paddings, insets and margins, leaving nothing for the style to add.
cc
A KEF LSX reached the end of every track and never advanced (#733); its
poll answers were swallowed, so an INFO log could not tell a mute renderer
from one that never reports STOPPED. The DLNA poll now logs a failed
position read, GetTransportInfo faults degrade to the last transport
command instead of failing the whole read, and a RelTime frozen inside the
end guard for five polls without STOPPED counts as end-of-track. A user
pause never counts. Pure helper with unit tests.
cc
rodio's Decoder::new leaves byte_len unset and the source non-seekable, so
symphonia's MP3 demuxer never estimates the duration of a file without a
Xing/Info/VBRI frame. The local play path then fell back to duration 0 and
the playback clock stayed clamped at 0 for the whole track, which the Slint
2.0.2 frontend read as a stuck load and restarted every 45 s (#734).
The decoder is now built with the buffer length, and an unknown (0)
duration no longer clamps the position. Regression fixture: a 2 s CBR MP3
written without an Info tag.
cc
Apply interface scaling through Qt at startup, complete keyboard navigation, move account migration into setup and progress modals, and defer Subsonic sync while the server scans.
cx
After a recovery advance (`recovery: engine empty … poll: advancing to X`)
the poll loop reset `last_track_id` and started the next track, but for
1-2 s (CMAF session + init segment, or a DSD→PCM conversion) the player
kept reporting the id of the track that had just ENDED. The 1 Hz tick took
that as a track change: `sync_current_to_id` rewound the queue cursor one
row and `refresh_now_playing` republished the previous track (now-playing,
MPRIS, notification), then the next tick flipped back — three
notifications per change, the middle one a ghost, intermittent because it
depends on the tick landing in that window. With the cursor rewound, the
gapless stage asked `peek_upcoming(1)` and was handed the track already
playing, so it played twice (smoke 2026-09-02, local DSD row; the queue had
no duplicate).
`stale_after_advance` remembers the ended id at the advance (0 under
repeat-one, where old == new); while the player still reports it and
nothing plays, the tick does its bookkeeping and skips the edge; any other
id or running audio clears it. The gapless stage also refuses a successor
equal to the player's own current track id. No change to the audio
backend.
cc
Same shape as the listen-log presence check: the workspace run already
executes them; the counts keep a renamed module or a dropped cfg from
shrinking coverage without a trace. The workflow calls the script, so the
mirror stays exact; its header names the new check.
cc
A new top-level Settings section, Import / Export, gathers everything that
moves data in or out of this install:
- SETTINGS PORTABILITY: the settings bundle export moves here from
Developer, with a note on what the bundle does not hold (favorites,
playlists, blacklist, purchases).
- BLACKLIST: export the blocked artists and albums as a JSON one user can
hand to another; import merges additively and never touches the enabled
flag (qbz_app::settings::blacklist_portable, frontend-agnostic).
- ACCOUNT MIGRATION: create a snapshot while signed in with the old
account, then migrate it into the account signed in now, with the local
profile copy and its three opt-in toggles (media servers, scrobblers,
listening history, all on). The disclaimer states that subscriptions and
purchases do not move. Progress and results are status lines in the
settings document; the favorites cache re-warms and the sidebar reloads
after a run, the local half asks for a restart.
Also: the header shows a translated "Member" for accounts without a
subscription, and the login screen says what a member can and cannot do.
50 new strings in all eight catalogs.
cc
A frontend-agnostic crate for moving a Qobuz account's favorites and
playlists into another account, additively (contract
qbz-nix-docs/specs/2026-09-02-account-migration-contract.md §9):
- snapshot: capture the signed-in account (five favorite kinds with
favorited_at, owned playlists with track ids in order, followed
playlists) into users/<uid>/account_migration/snapshot-*.json; the same
capture is the target's live state at migration time.
- plan: the pure delta against the target and the ledger. Same-name
playlists holding nothing foreign are the same playlist and get the
missing tracks appended; anything foreign means a different playlist and
a " (migrated)" copy. Favorites are written oldest first so the target's
own stamps keep the chronology.
- apply: one favorite per call with pacing and bounded retries, 50-track
chunks, subscriptions re-followed; every success lands in the ledger
before the next write, so a run resumes and a second run adds nothing.
- local: copy the old profile directory row by row (library folders and
scanned tracks on the fast path, playlist folders, settings keyed by
qobuz_playlist_id remapped through the ledger, pinned items, orders,
preferences, blacklist; media servers, scrobblers and listening history
behind options). Never the favorites cache, the subscription clock, the
session or the purchases.
The writes are the ones the app already issues when a user hearts or
creates a playlist; nothing touches subscriptions, purchases or account
data.
cc
The 30-day offline grace (D4) was produced by the Qt shell alone, and only
when a login was REJECTED; its purge predicate had no caller in the live
tree, so past the window the CMAF cache was blocked from playing but never
removed (Tauri purged it; Slint recorded the gap). With member accounts now
signing in, the clock has to key on the entitlement, not on a rejection,
and it has to run wherever a session activates.
`qbz_app::subscription_lifecycle` records the verdict from the core's
session (`offline_streaming` clears the clock; a member keeps it running
from its first observation) and purges the offline cache once the window
has passed, through the cache a host registers with
`AppRuntime::set_offline_cache`. `activate_at` runs it for every host (Qt
via `activate`, qbzd directly), a daily ticker per session re-checks it,
and a cache registered after activation is enforced against on
registration. qbzd registers no cache and keeps the verdict. The Qt
producers are gone; `offline_playback_allowed` stays the read gate.
cc