7115 Commits
Author SHA1 Message Date
vicrodh 455441014b chore: bump flake to 2.1.2
Version and both per-arch tarball hashes, computed with nix-prefetch-url from
the published assets at their final release URLs.

cc
2026-09-17 06:05:27 -06:00
vicrodh e537c7fd41 merge: QBZ 2.1.2
cc
2026-09-17 01:29:37 -06:00
vicrodh 546aaa1bc0 fix(macos): call the tray store's real accessor in the Dock policy
The Dock-hiding policy read the tray settings through `.get()`, which does not
exist on `TraySettingsState`; the method is `get_settings()`, as the same file
already calls it twelve lines above. The call sits inside a macOS cfg block, so
Linux and Windows compiled cleanly and only the two macOS release legs failed.

Verified by a release build on the Mac mini.

cc
2026-09-16 23:57:51 -06:00
vicrodh 77c2a0d513 chore: bump version to 2.1.2
The workspace version and the release documents landed with their own changes;
this adds the metainfo release entry the Flathub manifest and the software
centres read. Snap is stamped from the tag and needs no edit.

cc
2026-09-16 22:29:25 -06:00
vicrodh a972853b62 merge: accept numeric Subsonic ids so non-compliant servers sync
cc
2026-09-16 22:17:29 -06:00
vicrodh ae63fdb4ad fix(subsonic): accept numeric ids from servers that send them
The specification types ids as strings and Navidrome, Gonic and Airsonic send
them that way, so the song rows deserialized them as `String`. Funkwhale sends
integers: the album list already tolerated both through `json_id`, the song
rows did not, and a pod connected, listed its albums and then aborted the whole
library sync with "invalid type: integer 2059, expected a string".

Ids, album ids and cover-art tokens now accept either shape and normalize to
text; the value is opaque to this client and only ever travels back in a query
string. A numeric `coverArt` in an album row also stopped resolving, which cost
that album its cover in silence.

Verified by unit test with a Funkwhale-shaped payload; string ids map exactly
as before.

cc
2026-09-16 22:17:23 -06:00
vicrodh a40c1021d0 merge: the 2.1.2 What's New deck, its copy and the contributor credits
A visual deck that opens once per version, its five cards written by the owner
and translated into all eight locales, a panel wide enough that the card art is
no longer cropped, and the release notes capped to a readable measure inside it.

Carries the authorship-and-responsibility statement for README and CONTRIBUTING,
and credits Maxou56800 in the About chips, the README list and the 2.1.2 notes.

cc
2026-09-16 21:52:55 -06:00
vicrodh 24c0e91a1b docs(release): credit Maxou56800 in the README list and the 2.1.2 notes
The shared-layer conflict fence (#795) reaches the release notes beside the
QConnect fixes it belongs with, and the README contributor list gains its line.

cc
2026-09-16 21:24:26 -06:00
vicrodh 8f179c6454 docs(about): credit Maxou56800 in the contributor chips
The local-playback conflict fence released in the shared session loop (#795),
reported and verified against the official Android client.

cc
2026-09-16 21:22:36 -06:00
vicrodh 88bbcddb81 i18n(qt): the 2.1.2 deck copy, in all eight locales
The owner's copy, verbatim in English, translated into the other seven. The
deck now says what each card is for instead of announcing that a menu moved:
the settings card invites the reader into Settings, the cache card hands the
choice to them, and the Connect card says out loud that the feature is still
being worked on.

The loudness card's title reuses the existing "Volume normalization" msgid
rather than adding a near-identical string, so the card and the Audio settings
label can never drift apart. Adding a second spelling of it is a duplicate
definition, which msgfmt rejects outright.

cc
2026-09-16 21:22:36 -06:00
vicrodh 26279a0435 fix(qt): widen the What's New modal so the card art stops being cropped
The deck's art is 16:9 and the image covers its box, so at 820 wide the box sat
near 1.34 and Qt cropped roughly a quarter of every frame's width — taking with
it what the right edge of a screenshot is there to show, such as the renderer
name on the iOS capture. At 1040 the box lands near 1.72 and the crop all but
disappears.

The release notes ride in the same panel and are prose, so their column is
capped at 720 and centred; a line that spans the full 1040 reads measurably
worse. Narrow windows keep the full width.

cc
2026-09-16 21:22:35 -06:00
vicrodh 316e442ecc docs: state how model assistance is disclosed and who answers for a change
The human who signs a commit is its author and is responsible for it. Models
are disclosed in the PR body and never added as co-authors, because credit
without accountability is worth nothing: an agent cannot answer a review
comment six months from now.

Spells out what that means for the three things contributors send. Findings
are welcome verified or not, as long as the report says plainly what was
observed and what was not reproduced. Fixes must be verified by the person
sending them, with what they ran and what they saw. A generated code review is
not a review: submitting an unfiltered list moves the work of separating real
from imaginary onto the maintainer.

cc
2026-09-16 21:22:35 -06:00
vicrodh 91c9eebd4a merge: bring the shared-layer conflict-fence fix (#795) into the carousel branch
Pulled in so the next What's New build carries it for the owner's smoke.

cc
2026-09-16 19:58:09 -06:00
Victor RH dfb4c27476 merge: release the local-playback conflict fence in the shared layer too (#795)
Clears local_playback_conflict_pending in the success branch of the local-playing takeover, matching the abandon and failure branches beside it.

Defensive reinforcement, not the root cause: both hosts already clear that flag when the takeover publish succeeds (qbz-qt/src/qconnect_qt.rs, qbzd/src/qconnect/publish.rs, since f6e1c4e02), and the fence that actually kept dropping renderer commands after a reconnect was the takeover echo never being confirmed, fixed in bca798374 by confirming it through its own action uuid. Holding the invariant in the shared session loop as well means a host that stops clearing it, or a new host, cannot reintroduce the stuck fence.

Reported and verified against the official Android client by Maxime Berthault.
2026-09-16 19:57:25 -06:00
vicrodh d5dc4b08dd docs(release): 2.1.2 release notes and changelog
Tag details and changelog for 2.1.2, from the 162 commits since v2.1.1:
playback memory profiles, rebuilt volume normalization, the Storage section,
the Qobuz Connect hardening round and the quality-of-life pass, with the fixes
grouped by area.

cc
2026-09-16 19:07:28 -06:00
vicrodh 443c75a64a feat(qt): a visual What's New deck that opens once per version
The modal fetched the matching GitHub release and rendered its markdown, and
it only ever opened from the hamburger menu, so the release's headline changes
reached nobody who did not go looking for them. It now opens on the curated
deck for the running version: five cards, each one screenshot collage plus a
title, two lines and a button that closes the modal and lands on the setting it
just described. The rendered release notes become the deck's last card, so
nothing is lost and a version without a deck opens straight into them.

The cards' text lives in Rust and travels to the eight catalogs as ordinary
msgids; the artwork carries no text, so one set of images serves every locale.
The scrim over the art is painted by QML rather than baked in, which keeps the
contrast in both themes. Card changes are instant and there is no auto-advance:
a continuous transition would present the whole window for its duration, and
the repaint-pulse contract reserves that for the shared shell pulse.

Dismissing the deck records the running version, so it opens by itself exactly
once; the hamburger row reopens it at any time.

cc
2026-09-16 19:07:22 -06:00
vicrodh 105e4bc533 merge: integrate macOS and miniplayer fixes for 2.1.2
cx
2026-09-16 17:47:52 -06:00
vicrodh aa64525cfe fix(qt): harden macOS reopening and miniplayer appearance
Restore hidden windows from Dock/Finder and add a macOS-only Dock visibility setting. Share the app background with the miniplayer, improve theme contrast and translucent controls, and allow dragging from artwork and free areas.

Bump the workspace version to 2.1.2. Add lifecycle and miniplayer regression coverage. Linux release, QML software/OpenGL tests and isolated startup passed; native macOS validation remains pending.

Refs #792, #793

cx
2026-09-16 17:47:33 -06:00
Maxou56800 2cfe474aaf fix(qconnect): release the conflict fence after a local takeover 2026-09-16 23:58:51 +02:00
vicrodh ac8fbbfa64 fix(audio): recover sinks vacated by direct ALSA reservations
Capture the matching sink before ReserveDevice1 can hide it, including successful first opens. Recover it after PCM teardown without substituting the current default output. Bound pactl recovery calls and retain failed targets for retry.

Validation: 148 audio tests pass, including 6 sink recovery regressions. Physical DAC/browser/tray smoke remains pending.

cx
2026-09-16 10:46:04 -06:00
vicrodh 14bf49d8bd merge: integrate QConnect hardening and DAC release fixes
cx
2026-09-16 10:21:25 -06:00
vicrodh 2e7ed0c277 fix(qt): preserve saved resume state after silent owner restoration
A shutdown that restores only the owner queue must not persist the stopped player position over the saved owner resume point. Track that teardown outcome and retain the existing session snapshot.

cx
2026-09-16 09:36:55 -06:00
vicrodh 567f633117 fix(qt): release audio at quit and fence late playback
Request output release with the first quit watchdog arm, independently of network teardown. Permanently reject late playback/reinit commands, interrupt abandoned feeders, and await release acknowledgement. Restore the owner queue at Connect shutdown without scheduling playback.

cx
2026-09-16 09:34:27 -06:00
vicrodh c1707d23e2 fix(player): release paused outputs and rebuild resumable sources
Wake blocked streaming readers without canceling their download before device teardown. Recreate streaming and direct DSD playback at the saved position after pause; paused seeks keep the DAC released. Prevent direct DSD resume after leaving ALSA.

cx
2026-09-16 09:33:56 -06:00
vicrodh 41e365efab fix(audio): recover the suspended DAC before changing routes
Only acknowledge the recorded sink, retaining failed recovery for reset retries. Restore the Slint device refresh settle delay and release before resetting routing settings. Abort an ALSA backend change when release fails.

cx
2026-09-16 09:33:56 -06:00
vicrodh ca5f13966e fix(qconnect): preserve the final queue insertion slot
Accept the append slot without clamping it onto the last row. Reject stale coordinates and preserve adjacent no-op moves and server-authoritative occurrence ordering. Covers beginning, middle and end moves.

cx
2026-09-16 09:31:36 -06:00
vicrodh bc97d1aabf fix(qconnect): retain renderer UUIDs in topology events
Preserve UUID identity in ADD and UPDATE frames so identical device names can resolve the local renderer. Cover wire decoding and UUID-first resolution with missing-UUID fallbacks.

cx
2026-09-16 09:12:30 -06:00
vicrodh 12582ac3c5 chore: sync QConnect hardening with pre-release
cx
2026-09-16 09:00:29 -06:00
vicrodh 6687c799d9 fix(qconnect): name the commanded occurrence in the acknowledgement report
The report that answers a renderer command carried no queue ids at all. An
omitted id is 0 on the wire and 0 is the FIRST queue item, so every
command-driven track change told the session "I am at the head of the queue"
until the next periodic report corrected it, and controllers visibly jumped
there. The ids are now resolved against the synchronized queue, the same
resolution the periodic report uses, and stay omitted when they do not
resolve: an id the server cannot match is still never sent, which is what
40a2fa618 protected and 1d2873045 already restored for transitions.

PARTIAL. It reduces the controller's jump but does not eliminate it: an
induced smoke still shows one. The remainder is not this field — the ids on
the wire are correct — and tracks the audio backend instead: with PipeWire
output the jump disappears, with ALSA Direct it appears, and that path closes
and reopens the device on every track change while QBZ reports every 2s where
the official renderer reports every ~10s. Investigation and evidence in
qbz-nix-docs/qconnect-hardening-2.1.2/Q14.5-PROPUESTA.md §14.

cc
2026-09-16 08:27:21 -06:00
vicrodh 1983b8846c fix(qbzd): enforce locked QConnect volume policy
Gate remote volume and mute before state changes or success reports, and advertise the runtime volume policy on owner and delegated joins.

Cover the daemon sink and engine with locked/software regressions. Validated by 506 tests, release build, and volume/handoff smoke.

cx
2026-09-16 00:21:06 -06:00
vicrodh 6d6c72df8c merge: QConnect hardening 2.1.2 — autoplay targets, reconnect name, takeover fence, -1 occurrences
Q-20 renderer targets with autoplay in the session, Q-14 the persisted device
name on reconnect, Q-14.5 the takeover fence that discarded every renderer
command after reconnecting while playing, and the negative queue_item_id that
made the last row of every queue unreachable from a controller. Plus the album
view's Add to queue, which was playing later, and the report diagnostics that
found all of it.

Each fix carries its regression test and was smoke-tested by hand against the
iOS client on 2026-09-15.

cc
2026-09-15 20:59:05 -06:00
vicrodh 831f2bfb98 chore(qconnect): log the cursor ids every renderer report carries
Three separate hunts this session ended at the same blind spot: the periodic
report never printed the ids it announces, and a peer renderer's echo printed
none of the cursor it reports. Both are what a controller builds its row
selection from, so a wrong or missing id there looks like "the controller
ignores us" with nothing in the log to say why.

Allowlisted scalars only, at debug, following the rule already written for
loop_mode: the payloads themselves stay unlogged because they can carry session
and delegated-credential material.

cc
2026-09-15 20:58:26 -06:00
vicrodh 02d9873cd9 fix(qt): the album view's Add to queue appends instead of playing later
The album track row folded every enqueue mode that was not "next" into
"later", so "Add to queue" inserted on the manual block's tail — right after
the current track, under the "Next in queue" header — instead of appending at
the end. Three adds during a smoke test left the queue with the same recording
three rows apart, which then fed a duplicate-occurrence hunt in QConnect.

TrackRow emits "next" | "later" | "queue" and enqueue_album_track has an arm
for each: the fold is a leftover from when that path had no block-tail arm. The
mode now travels verbatim. No other surface collapses modes.

cc
2026-09-15 20:58:26 -06:00
vicrodh 61013314a0 fix(qconnect): read a negative queue_item_id as an absent occurrence
Commanding the LAST row of a queue was impossible from a controller: the tap
reached QBZ as nothing at all, no error, no log, while every other row worked
and repeat-all hid the whole thing. The server marks "there is no such item"
with queue_item_id = -1, and it sends that for next_track precisely when the
row it commands is the last one. The decoder treated any negative id as an
invalid payload and rejected the entire batch, so the SET_STATE carrying the
user's tap was dropped before it ever became a command.

A renderer occurrence with a negative id now decodes as absent, which is what
it means, and only on the renderer SET_STATE path where the wire capture is.
Queue-list decoding keeps its strict conversion.

Both hosts share this decoder, so the daemon had the same unreachable last row
and is fixed with it. Wire-captured and verified by smoke test on 2026-09-15:
the tap that logged "Renderer commands decode error: negative value where
unsigned expected in 'queue_track_with_context.queue_item_id': -1" now logs
"Renderer commands decoded: 1" and the renderer moves.

cc
2026-09-15 20:58:13 -06:00
vicrodh bca7983740 fix(qconnect): confirm the local-queue takeover by its own action uuid
Reconnecting while local audio plays left every renderer command discarded:
local authority claimed the renderer, published its queue and then waited for
an echo it could never accept, so the takeover fence stayed armed. The
controller kept seeing a healthy device because outbound reports never stop,
while track changes, pause and seek were all dropped until an unrelated local
action republished the queue.

The takeover's QueueLoadTracks travels in the same burst as AskForQueueState
and SetActiveRenderer, and the pending-action slot holds one action, so that
publication is never correlated and its echo stays unconfirmed. It is now
confirmed by the action uuid the server echoes back, independent of that slot.
The echo is also evaluated before the playback-conflict gate, since accepting
it materializes nothing and only retires the fence.

Adds the rejected-echo diagnostic and the inbound action uuid to the debug log:
the previous silence there cost three rounds of bisecting. Regression tests
cover both halves and fail without the fix.

Verified by smoke test on both paths, manual reconnect and a real WebSocket
drop: the queue is accepted in ~180 ms and the iOS client controls immediately.

cc
2026-09-15 18:18:52 -06:00
vicrodh 01a9c612e8 fix(qconnect): announce the persisted device name when the socket reconnects
The initial connect threaded the custom device name into the join payload, but
the in-loop reconnect passed None on both hosts, so the rejoin announced the
default "Qbz - {hostname}" while the local identity kept resolving the saved
name. One device_uuid with two friendly names breaks the renderer self-match
when ADD_RENDERER omits the uuid, and the renderer then goes mute.

The reconnect now reads the same persisted name the local identity, the
deferred renderer join and the LAN projection already use. Nothing else in the
reconnect lane changes: no order, timing, backoff, join reason or rejoin
difference. A debug line records the announced name.

Verified by smoke test: a real WebSocket drop rejoined announcing the custom
name, and the iOS client kept both the name and control.

cc
2026-09-15 18:18:41 -06:00
vicrodh 43d8b56cf7 fix(qconnect): align renderer targets when the session carries autoplay
align_remote_occurrence required the local queue to equal queue_items plus
autoplay_items, but materialization only installs queue_items. With autoplay
suggestions in the session every SET_STATE with a track failed with "local
queue does not match synchronized occurrences".

The strict occurrence fence now compares the local queue against queue_items
only. Queue targets keep the occurrence lookup, shuffle projection and cursor
move; an autoplay target is loaded by id without touching the local queue,
cursor or shuffle projection. Verified by smoke test against the official
iOS client with autoplay, including shuffle.

cc
2026-09-15 15:32:37 -06:00
vicrodh 34bc282f9c merge: integrate the library shutdown race fix into pre-release
test-crates failed on a timing-dependent deadlock in the library service's
shutdown test. Shutdown now releases the worker slot before joining, so a
concurrent scan answers at once; a deterministic regression test covers it.

cc
2026-09-15 12:32:42 -06:00
vicrodh dbe17d35c2 fix(library): release the worker slot before joining on shutdown
LibraryService::shutdown took the worker handle in an `if let` scrutinee,
which keeps the slot's MutexGuard alive through the block and therefore
through the join. A scan requested meanwhile waited for the whole running
scan instead of returning "closed", and the shutdown test deadlocked on its
own release channel whenever the joining thread won the race: test-crates on
pre-release failed with a 5 s Timeout, reproduced locally once in 1340
single-CPU runs.

Take the handle in its own statement so the guard drops before the join. A
new deterministic test issues a scan while shutdown joins a blocked worker
and requires an immediate "closed" answer; it timed out before the change.
qbz-library passes 213 tests, and 1296 single-CPU stress runs of the
original test pass.

cc
2026-09-15 12:32:42 -06:00
vicrodh 85fdb6d563 merge: integrate the Qt 6.8 QbzArrayModel fix into pre-release
The test-crates Qt gate on 6.8.3 failed to load QbzArrayModel, which would
have left AlbumView and the kiosk album and playlist lists broken in the Qt
6.8/6.9 release builds. The model is now assigned from its rows and the
delegate is handed to the model; the gate QML suites pass on 6.8.3 and 6.11.

cc
2026-09-15 11:51:09 -06:00
vicrodh 801b168686 fix(qt): make QbzArrayModel load and render on Qt 6.8 and 6.9
The test-crates gate on Qt 6.8.3 failed to compile QbzArrayModel:
DelegateModel.model has no change signal there, so onModelChanged was a
non-existent property and the component, with AlbumView and the kiosk album
and playlist lists, did not load. x86_64 Linux release builds use Qt 6.8.3;
aarch64, macOS and Windows use 6.9.3.

Assign the model from onRowsChanged instead, capturing the viewport before
the swap. A probe also showed Qt 6.8.3 renders zero rows when the delegate
is set on the view rather than on an external DelegateModel (6.11 forwards
it), so the three hosts and the gate test now hand their delegate to the
model.

tst_array_model passes 6/6 on Qt 6.8.3 and 6.11.2; with Qt 6.8.3 qmllint,
the six QML audits, all fifteen gate QML suites and the Node gate tests
pass.

cc
2026-09-15 11:51:09 -06:00
vicrodh 9eb3ceda26 merge: integrate 2.1.2 stabilization for track rows and Jellyfin
Track rows play on double click on every desktop surface, keep the play
button's single click, and preserve drag, recycling and Shift/Ctrl
selection; kiosk rows keep tap-to-play (#790).

Jellyfin authenticates with the MediaBrowser Authorization header required
by 12.x, fixes incremental sync on 10.8-10.10, and is covered by a
cross-version compatibility lab passing 10.8.13 through 12.1 (#502).
Media-server albums edit their metadata sidecar from the album header, API
keys and the stored token are redacted from logs, and a saved Jellyfin
session no longer reads as disconnected after a server test.

Verified by smoke test: a full album played through Jellyfin.

cc
2026-09-15 11:15:27 -06:00
vicrodh 7db317d601 fix(qt): keep a saved Jellyfin session visible after a server test
With a stored token, Test answered "Server reachable. Sign in to connect.",
which read as a lost session although the connection was intact. Show the
existing saved-connection status instead when a credential is stored.

cc
2026-09-15 11:15:06 -06:00
vicrodh dc8e2fd030 fix(qt): edit media-server album metadata from the album header
The per-track editor already opened Plex, Jellyfin and Subsonic albums on
their QBZ metadata sidecar, but the album header's pencil always opened the
local-library target and stopped at "Metadata editing is available for local
file versions only". Share the remote target construction between both entry
points so the header edits the same sidecar. Direct writes stay refused for
media-server files, as they already are for SACD images.

cc
2026-09-15 11:15:06 -06:00
vicrodh 65a1104db9 fix(log): redact media-server API keys and the stored Jellyfin token
Jellyfin stream urls now carry ApiKey and requests carry Token="..." in the
MediaBrowser header; neither form matched the redaction patterns. Add
patterns for ApiKey/api_key query values and a quoted Token value, and
register the stored Jellyfin token with the literal redaction layer whenever
media-server settings are bound or saved.

cc
2026-09-15 11:15:06 -06:00
vicrodh f0dff94517 test(jellyfin): add a cross-version server compatibility lab
Self-hosted servers often run on stable distributions and lag behind
upstream releases. The opt-in script starts one disposable Jellyfin
container per version on 127.0.0.1, completes the startup wizard through the
API, serves a synthetic 24-bit/96 kHz FLAC library with an embedded cover
and runs the qbz-jellyfin live suite plus the qbz-source sweep-to-audio test
against it. It also records whether the server still accepts the legacy
token header.

The default matrix 10.8.13, 10.9.11, 10.10.7, 10.11.11 and 12.1 passes.

cc
2026-09-15 11:15:06 -06:00
vicrodh d3688b2a97 fix(jellyfin): bind both delta parameters for 10.8-10.10 servers
Jellyfin 10.8 through 10.10 write the minDateLastSaved SQL clause against
the @MinDateLastSavedForUser parameter while binding @MinDateLastSaved.
Sent alone, 10.9.11 and 10.10.7 answer HTTP 500 to any date, so every
incremental Jellyfin sync after the first failed; 10.8 carries the same
typo. Both parameters filter the item's DateLastSaved on every version, and
10.11.11 and 12.1 apply them as the identical filter, so send both with the
same value.

The existing future-date live test could not catch this. A new one requires
a year-2000 delta to return the whole library; it failed with HTTP 500 on
10.10.7 before the change and passes on 10.8.13, 10.9.11, 10.10.7, 10.11.11
and 12.1.

cc
2026-09-15 11:15:06 -06:00
vicrodh 5a1b29d7e5 fix(jellyfin): authenticate with the header Jellyfin 12 requires
Jellyfin 12.0 disables legacy authorization by default and its migration
turns it off on upgraded servers (server PR #15559). Sign-in still worked,
but every later request carried the token in X-Emby-Token, so the first
library read answered 401 and the panel reported "Signed in, but library
access failed" (#502).

Send every authenticated request with Authorization: MediaBrowser ... Token
and the install's persisted DeviceId, percent-encode the values the server
URL-decodes, stream with ApiKey instead of api_key, and read libraries from
/UserViews with a 404-only fallback to the obsolete /Users/{id}/Views route
for 10.8. Both modern forms are read by every server since 10.8.

A socket fixture now pins the header shape, the absence of legacy headers
and query keys, the views route and its fallback. Live suites pass against
12.1.0 and 10.11.11, and a token issued by 10.11.11 keeps working after the
same server is migrated to 12.1, so stored sessions need no re-login.

cc
2026-09-15 11:14:45 -06:00
vicrodh 0e7e1802f5 fix(qt): play track rows on double click, never on a single click
Closes #790. TrackRow defaulted clickPlays to true and only the album view
turned it off, so playlists, search, the queue and the library started
playback on a single click with a pointer cursor while the album page needed
a double click.

Desktop track rows now share one policy: the play disc plays on its first
click, a single click on the row body does nothing and a double click on any
part that is not a control plays once. Artist and album links react only over
the drawn name, so the whitespace beside them belongs to the row body. Dead
and play-blocked rows stay inert on double click. The same policy covers the
local row gutter, purchase rows, track rows in the library feed and the
immersive suggestions list. Kiosk rows keep tap-to-play because touch has no
hover play disc.

Drag, select-mode Shift/Ctrl toggling and delegate recycling keep their
existing paths. A new QML suite drives real clicks on the row components
(17 cases, 7 failing before the fix) and joins the runtime gate and
test-crates workflow. Verified by smoke test.

cc
2026-09-15 08:33:25 -06:00
vicrodh 230d55d4bb merge: integrate playback hardening and renderer volume controls
Integrate bounded audio acquisition, transport handoff guards, signed audio
settings, remote volume capability enforcement and available-master cache
reuse. Preserve the independent PCM conversion commit for selective rollback.

The resulting tree matches the release binary verified by regression tests
and the local DAC smoke. Keep the existing session-resume interaction after
QConnect playback documented as a separate follow-up.

cx
2026-09-14 18:07:26 -06:00