Files
qbz/crates/qbz-ui/ui/settings/DiagnosticsPanel.slint
T
vicrodh 99da4c381b feat(slint): pre-launch dev panel, queue controls, appearance persistence, reco cache settings
Sleep timer (queue footer): tokio monotonic deadline + 1Hz countdown that
fires core().pause() at expiry; presets + custom minutes via a popover.

Stop after this song: 4 QbzCore passthroughs + playback consume-before-advance
seam and the load-bearing gapless-prefetch gate, plus the queue marker/menu so
the Queue persists when playback halts.

qbz-log crate: TeeLogger wrapping env_logger (stderr + 5000-line ring +
~/.local/share/qbz/logs/qbz.log) with write-time secret redaction (regex +
live-secret registry) and a diagnostics bundle builder.

Dev panel logs: in-app viewer that shows content the moment it opens, advanced
controls behind a discreet [+], "Copy all" as the sole footer button, a copy
button on the upload URL, and a redacted public paste upload.

Diagnostics: qbz-app::diagnostics (extracted, src-tauri untouched) feeding a
7-section panel with the live active output device + sample rate/format (pactl)
and a complete redacted report for the upload.

Report an Issue: wires the header menu item to a 1:1 Tauri modal that opens the
GitHub bug-report template.

Appearance persistence: 6 toggles that were live-only now persist via ui_prefs
+ handler arm + startup seed (window-title-show, show-volume-steppers,
sidebar-playlist-collage, local-library-track-artwork, in-app-toasts,
theme-filter).

Recommendations cache (per-tab only): TTL select 24/36/48/72h + Refresh now in
ExternalRecoView; no change to the other Discover tabs.

Bump workspace version 0.1.0 -> 1.2.15 so app_version is correct in the
diagnostics report and the QConnect About string. i18n es/de/fr/pt for all new
strings.

cc
2026-06-29 13:19:06 -06:00

391 lines
14 KiB
Plaintext

// Settings > Developer — inline Diagnostics panel (1:1 port of the Tauri
// DiagnosticsPanel.svelte). A master collapsible holding seven sub-sections
// (System / Playback / Qobuz Connect / Cast / Audio / Graphics / Environment),
// each a small saved-vs-runtime table. Five sections are 3-column
// (Setting | Runtime | glyph); Audio and Graphics are 4-column
// (Setting | Saved | Runtime | glyph) — gated by the per-section `show-saved`.
//
// The data model is the DiagnosticsState global + DiagRow struct (state.slint):
// seven `[DiagRow]` models (one per section, 1:1 with the Tauri row-builders),
// plus refresh()/export-clipboard()/cast-scan() callbacks driven from Rust.
// Section open/closed state is pure UI (private `open` per DiagSection); no
// Rust round-trip. Per-row labels are plain Rust strings (Tauri hardcodes them
// too); only section titles + column headers + buttons go through @tr.
import { Theme } from "../foundation/semantic-colors.slint";
import { Typography } from "../foundation/typography.slint";
import { Radius } from "../foundation/radius.slint";
import { DiagRow, DiagnosticsState } from "../state.slint";
import { QbzIcon } from "../primitives/QbzIcon.slint";
// One table row. 4-col when `show-saved`, else 3-col (Saved column omitted).
// The trailing glyph maps `status`: 0 info(·) / 1 match(✓) / 2 mismatch(✗).
component DiagRowView inherits Rectangle {
in property <DiagRow> entry;
in property <bool> show-saved;
height: 28px;
HorizontalLayout {
padding-left: 10px;
padding-right: 10px;
spacing: 10px;
Text {
text: root.entry.label;
color: Theme.text-secondary;
font-size: 12px;
vertical-alignment: center;
width: 38%;
overflow: elide;
}
if root.show-saved: Text {
text: root.entry.saved;
color: Theme.text-primary;
font-size: 11px;
vertical-alignment: center;
horizontal-stretch: 1;
overflow: elide;
}
Text {
text: root.entry.runtime;
color: Theme.text-primary;
font-size: 11px;
vertical-alignment: center;
horizontal-stretch: 1;
overflow: elide;
}
Text {
width: 20px;
horizontal-alignment: center;
vertical-alignment: center;
font-weight: Typography.bold;
text: root.entry.status == 1 ? "✓" : root.entry.status == 2 ? "✗" : "·";
color: root.entry.status == 1 ? #4caf50 : root.entry.status == 2 ? #f44336 : Theme.text-muted;
}
}
Rectangle {
y: parent.height - 1px;
height: 1px;
background: Theme.border-subtle;
}
}
// Collapsible section: clickable header (chevron + title) toggles a private
// `open` (seeded from `default-open`); when open, a column header + the rows.
component DiagSection inherits VerticalLayout {
in property <string> title;
in property <[DiagRow]> rows;
in property <bool> show-saved: false;
in property <bool> default-open: true;
private property <bool> open: root.default-open;
spacing: 0px;
Rectangle {
height: 34px;
TouchArea {
mouse-cursor: pointer;
clicked => {
root.open = !root.open;
}
}
HorizontalLayout {
spacing: 6px;
VerticalLayout {
alignment: center;
QbzIcon {
width: 14px;
height: 14px;
tint: Theme.text-secondary;
source: root.open
? @image-url("../assets/icons/chevron-down.svg")
: @image-url("../assets/icons/chevron-right.svg");
}
}
Text {
text: root.title;
color: Theme.text-primary;
font-size: 13px;
font-weight: Typography.semibold;
vertical-alignment: center;
horizontal-stretch: 1;
}
}
}
if root.open: VerticalLayout {
spacing: 0px;
Rectangle {
height: 26px;
HorizontalLayout {
padding-left: 10px;
padding-right: 10px;
spacing: 10px;
Text {
text: @tr("Setting");
width: 38%;
color: Theme.text-muted;
font-size: 11px;
vertical-alignment: center;
}
if root.show-saved: Text {
text: @tr("Saved");
horizontal-stretch: 1;
color: Theme.text-muted;
font-size: 11px;
vertical-alignment: center;
}
Text {
text: @tr("Runtime");
horizontal-stretch: 1;
color: Theme.text-muted;
font-size: 11px;
vertical-alignment: center;
}
Rectangle {
width: 20px;
}
}
}
for r in root.rows: DiagRowView {
entry: r;
show-saved: root.show-saved;
}
}
}
// Action button — auto-width, 34px high, styled like DeveloperSettings'
// SecondaryButton. `enabled: false` dims it and swallows the click.
component DiagButton inherits Rectangle {
in property <string> label;
in property <bool> enabled: true;
callback clicked();
height: 34px;
width: btn-label.preferred-width + 28px;
border-radius: Radius.sm;
border-width: 1px;
border-color: Theme.border-subtle;
background: root.enabled && ta.has-hover ? Theme.surface-hover : Theme.surface-elevated;
HorizontalLayout {
alignment: center;
padding-left: 14px;
padding-right: 14px;
btn-label := Text {
text: root.label;
color: root.enabled ? Theme.text-secondary : Theme.text-muted;
font-size: Typography.body;
font-weight: Typography.medium;
vertical-alignment: center;
}
}
ta := TouchArea {
mouse-cursor: root.enabled ? pointer : default;
clicked => {
if (root.enabled) {
root.clicked();
}
}
}
}
export component DiagnosticsPanel inherits VerticalLayout {
spacing: 4px;
private property <bool> panel-open: false;
private property <bool> cast-open: true;
// Master toggle (chevron + title + muted note). First expand triggers the
// initial load if it hasn't happened yet.
Rectangle {
height: 44px;
TouchArea {
mouse-cursor: pointer;
clicked => {
root.panel-open = !root.panel-open;
if (root.panel-open && !DiagnosticsState.loaded) {
DiagnosticsState.refresh();
}
}
}
HorizontalLayout {
spacing: 8px;
VerticalLayout {
alignment: center;
QbzIcon {
width: 16px;
height: 16px;
tint: Theme.text-secondary;
source: root.panel-open
? @image-url("../assets/icons/chevron-down.svg")
: @image-url("../assets/icons/chevron-right.svg");
}
}
Text {
text: @tr("Diagnostics");
color: Theme.text-primary;
font-size: Typography.body;
font-weight: Typography.semibold;
vertical-alignment: center;
}
Text {
text: @tr("Runtime vs saved configuration snapshot.");
color: Theme.text-muted;
font-size: 11px;
vertical-alignment: center;
horizontal-stretch: 1;
overflow: elide;
}
}
}
if root.panel-open: VerticalLayout {
spacing: 4px;
// Actions: Refresh + Export-to-clipboard.
HorizontalLayout {
spacing: 8px;
alignment: start;
DiagButton {
label: @tr("Refresh");
clicked => {
DiagnosticsState.refresh();
}
}
DiagButton {
label: DiagnosticsState.copied ? @tr("Exported") : @tr("Export to clipboard");
enabled: DiagnosticsState.loaded;
clicked => {
DiagnosticsState.export-clipboard();
}
}
}
if DiagnosticsState.error != "": Text {
text: DiagnosticsState.error;
color: #f44336;
font-size: 12px;
wrap: word-wrap;
}
if DiagnosticsState.loaded: VerticalLayout {
spacing: 4px;
Text {
text: "QBZ v" + DiagnosticsState.app-version;
color: Theme.text-muted;
font-size: 11px;
}
DiagSection {
title: @tr("System");
rows: DiagnosticsState.system-rows;
default-open: true;
}
DiagSection {
title: @tr("Playback");
rows: DiagnosticsState.playback-rows;
default-open: true;
}
DiagSection {
title: @tr("Qobuz Connect");
rows: DiagnosticsState.qconnect-rows;
default-open: true;
}
// Cast — bespoke collapsible: needs its own on-demand Scan button.
VerticalLayout {
spacing: 0px;
Rectangle {
height: 34px;
TouchArea {
mouse-cursor: pointer;
clicked => {
root.cast-open = !root.cast-open;
}
}
HorizontalLayout {
spacing: 6px;
VerticalLayout {
alignment: center;
QbzIcon {
width: 14px;
height: 14px;
tint: Theme.text-secondary;
source: root.cast-open
? @image-url("../assets/icons/chevron-down.svg")
: @image-url("../assets/icons/chevron-right.svg");
}
}
Text {
text: @tr("Cast Discovery");
color: Theme.text-primary;
font-size: 13px;
font-weight: Typography.semibold;
vertical-alignment: center;
horizontal-stretch: 1;
}
}
}
if root.cast-open: VerticalLayout {
spacing: 4px;
HorizontalLayout {
alignment: start;
DiagButton {
label: DiagnosticsState.cast-scanning ? @tr("Scanning…") : @tr("Scan for devices");
enabled: !DiagnosticsState.cast-scanning;
clicked => {
DiagnosticsState.cast-scan();
}
}
}
if DiagnosticsState.cast-rows.length > 0: VerticalLayout {
spacing: 0px;
Rectangle {
height: 26px;
HorizontalLayout {
padding-left: 10px;
padding-right: 10px;
spacing: 10px;
Text {
text: @tr("Setting");
width: 38%;
color: Theme.text-muted;
font-size: 11px;
vertical-alignment: center;
}
Text {
text: @tr("Runtime");
horizontal-stretch: 1;
color: Theme.text-muted;
font-size: 11px;
vertical-alignment: center;
}
Rectangle {
width: 20px;
}
}
}
for r in DiagnosticsState.cast-rows: DiagRowView {
entry: r;
show-saved: false;
}
}
}
}
DiagSection {
title: @tr("Audio");
rows: DiagnosticsState.audio-rows;
show-saved: true;
default-open: true;
}
DiagSection {
title: @tr("Graphics");
rows: DiagnosticsState.graphics-rows;
show-saved: true;
default-open: true;
}
DiagSection {
title: @tr("Environment");
rows: DiagnosticsState.env-rows;
default-open: false;
}
}
}
}