Five surfaces the Qt frontend was missing, landed together because they share
five wiring files (build.rs, main.rs, shell_bridge.rs, AppShell.qml,
HeaderBar.qml) and splitting them would produce commits that do not build.
ARTIST PAGE
- The Playlists section used a hand-rolled Rectangle delegate: a MouseArea with
no onClicked, and a RoundedImage with no fit:, so the default crop blew an
800x380 playlist banner up inside a 200x200 box and showed the middle 47% at
2.1x. Replaced by the real cards/PlaylistCard.qml — already live on five other
surfaces — mounted in a new views/PlaylistRail.qml modelled on SectionRail.
ArtistPlaylist publishes the five roles the card reads.
- The portrait gains what the album cover has: the right-click menu the Slint
reference already has (Add/Change/Remove image, Open in browser, Save as…) and
the lightbox this port invented for albums and playlists. Custom images are
keyed by artist NAME into the same custom_artwork.json the Slint build writes,
so they round-trip between the two.
- CoverLightbox is now aspect-aware. It forced a square box on the premise that
covers are square; artist portraits are not (1080x720 measured in the cache).
- Removing an override invalidates the pipeline's resolved path for that url:
artwork_qt::cached_path consults the override FIRST, so coverMap held the
custom file and the header kept showing the picture the user had just deleted.
- artwork_qt::local_path is the inverse of file_url. trim_start_matches("file://")
is not: it leaves the percent-escapes in place, so any path holding % # or ?
came back as a name that does not exist on disk.
- One new msgid, "View image", in the seven non-en locales — matching its sibling
"View cover", since en carries none of that family and falls back to the msgid.
CUSTOM THEME
- The editor, the colour picker and the swatch grid, ported 1:1 from
AppearanceSettings.slint and ColorPicker.slint. The 11 editable base tokens,
their expansion into the full 55-value palette and the on-disk format all stay
in the shared qbz-theme crate, so a theme authored in the shipping Slint build
already rendered here and now round-trips both ways.
- Hue is normalised 0..1 at every Qt.hsva boundary; the reference stores 0..360,
and porting the number instead of the domain would render every swatch red.
- The first-selection seed is captured BEFORE set_theme persists the slug,
because current_slug() re-reads it from disk — a seed taken afterwards would
resolve "custom", find no file and snapshot the default it was meant to replace.
- The in-memory base is dropped when the theme row enters "custom": it is
memoized for the life of the process and filled at bridge construction, so a
process that booted without the file would keep serving the OLED default even
after the shipping Slint build wrote the user's theme.
- The disk write is debounced to drag-end and atomic; the reference persists on
every drag frame with a truncating write, on a file the other build has open.
ABOUT + WHAT'S NEW
- Both modals, with their producers. render_markdown is transcribed line for line
from the reference: h1 and h2 collapse to one level, an indent-0 bullet becomes
a section plus a TOC entry, bold and backticks are stripped rather than styled.
Text.MarkdownText is a different subset and would have rendered a different
document from the same source. Pinned by tests, including the real 2.0.2 body.
- The codename is a single const, so the release checklist has one Qt-side place
to bump.
DIAGNOSTICS
- The panel in Settings > Developer, the Report an Issue modal and its menu row.
- The log bundle carried eight hand-built header fields where the reference
carries about seventy, so every issue filed from a Qt build arrived with no
kernel, no distro, no install method, no GPU, no exclusive-mode / DAC /
sample-rate state and no playback context. The report is injected INTO
qbz_log::bundle rather than replacing it, keeping the <details> wrapper that
makes a pasted bundle readable.
- The GTK / WebKit / GSK / GDK rows are CUT. In a Qt process those libraries are
not mapped, so their runtime column can only read "—", and their saved column
would come from a graphics.json this binary never reads or writes — foreign
state presented as this app's configuration.
SHELL
- The four always-on-top overlays (toast, tooltip, art preview, immersive) relied
on declaration order and carried no z, so any z-bearing modal buried them: a
toast raised by a background event behind an opaque panel is never seen. They
now sit in a band above the modals, keeping their relative order.
- build.rs asked cargo to watch ../../.git/HEAD, which does not exist in a git
worktree (.git is a file). Cargo treats a missing rerun-if-changed target as
always-dirty, so the whole build script — per-shader qsb, the recursive asset
sweep, cxx-qt/moc codegen — re-ran on every cargo invocation.
Verified: cargo test -p qbz-qt 343 passed, cargo test -p qbz-library 83 passed,
qml_singleton_xref and qml_resolution_audit clean over 35 bridges and 265 QML
files, and qmlcachegen compiles every one of them. Smoke-tested by hand.
Contracts, recon and adversarial reviews:
qbz-nix-docs/qt-frontend/2026-08-14-six-item-round/
cc
184 lines
6.5 KiB
QML
184 lines
6.5 KiB
QML
// Report an issue — the QML port of crates/qbz-ui/ui/shell/ReportIssueModal.slint,
|
|
// opened from the header hamburger menu.
|
|
//
|
|
// Structure, scrim, faked shadow and the self-gated mount follow
|
|
// LogViewerModal.qml; `z: 3000` explicitly, ADR-009 as this port spells it.
|
|
// Colour literals are `Qt.rgba(...)` floats: Slint writes #RRGGBBAA and Qt
|
|
// reads #AARRGGBB, and five modals in this tree shipped an invisible scrim or
|
|
// shadow from copying one verbatim (fixed 2026-08-14).
|
|
//
|
|
// The open state is LOCAL, not a bridge document: nothing in Rust needs to know
|
|
// this modal is up, and the two buttons call verbs that already exist
|
|
// (QbzShell.logOpen, QbzShell.reportIssueOpen). The header menu sets `open`.
|
|
//
|
|
// No pills (ADR-008): both buttons are bordered radiusSm rectangles.
|
|
|
|
import QtQuick
|
|
import com.blitzfc.qbz
|
|
import "../controls"
|
|
import "../theme"
|
|
|
|
Item {
|
|
id: root
|
|
|
|
QbzTheme { id: theme }
|
|
|
|
property bool open: false
|
|
|
|
anchors.fill: parent
|
|
z: 3000
|
|
visible: root.open
|
|
enabled: root.visible
|
|
|
|
function t(s) { return QbzSession.tr(s, QbzSession.trRev) }
|
|
|
|
FocusScope {
|
|
anchors.fill: parent
|
|
focus: root.visible
|
|
Keys.onEscapePressed: root.open = false
|
|
}
|
|
|
|
// Dimmed backdrop; a click closes.
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
color: Qt.rgba(0, 0, 0, 0.75)
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
onClicked: root.open = false
|
|
}
|
|
}
|
|
|
|
// Faked 32px drop shadow — the LogViewerModal/QbzConfirmModal precedent; a
|
|
// real blurred shadow owes its own parity pass.
|
|
Rectangle {
|
|
anchors.centerIn: panel
|
|
width: panel.width + 8
|
|
height: panel.height + 8
|
|
radius: theme.radiusMd
|
|
color: Qt.rgba(0, 0, 0, 0.5)
|
|
}
|
|
|
|
Rectangle {
|
|
id: panel
|
|
anchors.centerIn: parent
|
|
width: Math.min(root.width - 80, 520)
|
|
height: Math.min(root.height - 80, 300)
|
|
radius: theme.radiusMd
|
|
color: theme.surfaceCard
|
|
border.width: 1
|
|
border.color: theme.borderSubtle
|
|
|
|
// Swallow clicks so the scrim below does not close the modal.
|
|
MouseArea { anchors.fill: parent }
|
|
|
|
Column {
|
|
anchors.fill: parent
|
|
anchors.margins: 20
|
|
spacing: 14
|
|
|
|
// ---- Header: title + close X.
|
|
Item {
|
|
width: parent.width
|
|
height: 28
|
|
Text {
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
text: root.t("Report an issue")
|
|
color: theme.textPrimary
|
|
font.pixelSize: theme.fontHeading
|
|
font.weight: theme.weightSemibold
|
|
}
|
|
Rectangle {
|
|
anchors.right: parent.right
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
width: 28
|
|
height: 28
|
|
radius: theme.radiusSm
|
|
color: closeArea.containsMouse ? theme.surfaceHover : "transparent"
|
|
QbzIcon {
|
|
anchors.centerIn: parent
|
|
width: 17
|
|
height: 17
|
|
name: "x"
|
|
tintName: closeArea.containsMouse ? "primary" : "muted"
|
|
}
|
|
MouseArea {
|
|
id: closeArea
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: root.open = false
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---- Intro: the manual, redacted log-sharing explanation.
|
|
Text {
|
|
width: parent.width
|
|
height: parent.height - 28 - 36 - 28
|
|
text: root.t("To make bug and issue resolution easier, it's recommended to share the application logs. We have been careful to mask the logs we send so no sensitive data is included. The process is manual, never automatic, so you can verify what is being shared.")
|
|
color: theme.textSecondary
|
|
font.pixelSize: theme.fontBody
|
|
wrapMode: Text.WordWrap
|
|
}
|
|
|
|
// ---- Actions: Go to logs (secondary) / Create issue (primary).
|
|
Row {
|
|
anchors.right: parent.right
|
|
spacing: 10
|
|
|
|
Rectangle {
|
|
width: goLabel.implicitWidth + 28
|
|
height: 36
|
|
radius: theme.radiusSm
|
|
border.width: 1
|
|
border.color: theme.borderSubtle
|
|
color: goArea.containsMouse ? theme.surfaceHover : theme.surfaceElevated
|
|
Text {
|
|
id: goLabel
|
|
anchors.centerIn: parent
|
|
text: root.t("Go to logs")
|
|
color: theme.textSecondary
|
|
font.pixelSize: theme.fontLink
|
|
font.weight: theme.weightMedium
|
|
}
|
|
MouseArea {
|
|
id: goArea
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: {
|
|
root.open = false
|
|
QbzShell.logOpen()
|
|
}
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
width: createLabel.implicitWidth + 28
|
|
height: 36
|
|
radius: theme.radiusSm
|
|
color: createArea.containsMouse ? theme.accentHover : theme.accent
|
|
Text {
|
|
id: createLabel
|
|
anchors.centerIn: parent
|
|
text: root.t("Create issue report")
|
|
color: theme.accentText
|
|
font.pixelSize: theme.fontLink
|
|
font.weight: theme.weightMedium
|
|
}
|
|
MouseArea {
|
|
id: createArea
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: {
|
|
root.open = false
|
|
QbzShell.reportIssueOpen()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|