feat(qt): Detected device limit row, its refresh triggers and cache drop

The last unported row of Settings > Audio. Read-only by design: a Hz
dropdown is the documented root-cause trap, because it promises rates the
app cannot request when Qobuz sells four discrete tiers. The value is
Rust-composed from the probe and the row hides on an empty summary; the
fallback caveat renders only when the probe fell back to the common set.
No new msgids -- both strings already exist, translated, in all 8 catalogs.

Six refresh triggers, one more than the reference: startup, the toggle, a
backend switch, an output-device change, reset-to-defaults, and the device
refresh/release button. That last one is Qt-only and the easiest to miss --
it is the hotplug case, where the hardware behind a "System default"
selection changes without the selection changing.

Ordering is persist -> refresh -> publish, and the refresh goes BEFORE
apply_audio rather than merely before the handler's closing publish:
apply_audio publishes on its own way out, an earlier serialize that would
ship a stale summary. The symptom of getting this wrong is a row that is
only right after reopening Settings.

The boot trigger publishes the snapshot itself after awaiting the refresh.
The boot snapshot is spawned separately and can serialize first, which left
the row absent for a user who already had the toggle on until some
unrelated mutation happened to republish.

A cap change drops the tier-keyed audio cache, symmetric with the streaming
-quality change: the cache is quality-blind, so bytes fetched under the old
tier would keep serving and the feature would be inert on anything already
played. The first refresh of a process is exempt -- see the CapChange
classification, which exists because clearing there wiped the disk cache on
every launch.

cc
This commit is contained in:
vicrodh committed 2026-08-17 11:09:37 -06:00
1 parent 3aed6d3ea6
commit 05c5799192
3 files changed
+157 -6

No files matched your search

+35 -4
View File
@@ -5,10 +5,10 @@
// truth. The audio path itself is PROTECTED: this panel only calls the
// settings keys settings_qt.rs already dispatches.
//
// Not shipped (no backend seam in this port — see settings_qt.rs):
// - "Detected device limit" + its fallback disclosure (#638): the device-cap
// probe (`device_cap.rs`) is Slint glue that was never ported, so there is
// no measured value to show.
// "Detected device limit" + its fallback disclosure (#638 fix 3) ARE shipped
// (2026-08-17): the probe moved out of the Slint binary crate into
// `qbz-app::device_cap`, and both rows read the cap cache off the settings
// document. They are read-only by design — see the comment at the rows.
//
// The HiFi Wizard IS shipped (2026-08-11): the last row of OUTPUT opens
// settings/DacWizardModal.qml, mounted at the SettingsView root. Its logic
@@ -50,6 +50,37 @@ Column {
onToggled: function (v) { QbzBridge.settingsBool("limit-quality-to-device", v) }
}
}
// Read-only detected line — deliberately NOT a control (#638 fix 3): a Hz
// dropdown was the documented root-cause trap (it promised rates the app
// cannot request; Qobuz sells four discrete tiers), and with real detection
// there is nothing left to pick. The value is Rust-composed from the probe
// ("192 kHz · Hi-Res+") and is empty while nothing is cached, which is what
// hides the row rather than showing a blank value.
SettingRow {
visible: root.doc.limitQualityToDevice === true
&& (root.doc.deviceCapSummary || "") !== ""
label: QbzSession.tr("Detected device limit", QbzSession.trRev)
Text {
text: root.doc.deviceCapSummary || ""
color: theme.textPrimary
font.pixelSize: theme.fontBody
font.weight: theme.weightMedium
verticalAlignment: Text.AlignVCenter
}
}
// Fallback disclosure (#638 Decision B): plain informative copy — no
// warning icon, no error styling. The cap still applies on the assumed
// common set; it just is not measured truth for this hardware.
Text {
visible: root.doc.limitQualityToDevice === true
&& (root.doc.deviceCapSummary || "") !== ""
&& root.doc.deviceCapDetected === false
width: parent.width
text: QbzSession.tr("Your device's exact limits could not be read on this system, so a common set is assumed. The cap still applies, but may not match your hardware exactly.", QbzSession.trRev)
color: theme.textMuted
font.pixelSize: 12
wrapMode: Text.WordWrap
}
SettingsSpacer { }
SettingsDivider { }