aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-10-13 09:59:53 +0200
committerDanilo M. <danix@danix.xyz>2026-10-13 09:59:53 +0200
commitde8294eab208b95cef9bd376d98fb232e3a4c4c4 (patch)
tree9b6e11acacf9353308c46b405f7b75532d3eb827
parent9de89ae1512928283c1de7360e0313e1e2975b47 (diff)
downloadquickshell-de8294eab208b95cef9bd376d98fb232e3a4c4c4.tar.gz
quickshell-de8294eab208b95cef9bd376d98fb232e3a4c4c4.zip
fix(island): review findings in the controls panel
Edit mode let a right-click and the wheel through to the tile, so a right-click opened the drawer and scroll changed volume, against the spec. The drag area now takes both buttons and the wheel. A scroll up on a level already above the cap, such as a mic set to 120% elsewhere, snapped it down to the cap. The cap now only stops a step up. Prefs logs a failed write, which printErrors off had silenced: the new order would otherwise vanish at the next restart with no trace. Its parsed object is renamed from data to doc, so it cannot shadow a base property of that name. The spec now says what was built: JSON.parse rather than a JsonAdapter, and the helper list as it is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
-rw-r--r--desktop/Prefs.qml13
-rw-r--r--desktop/island/Controls.js9
-rw-r--r--desktop/island/ControlsPanel.qml4
-rw-r--r--desktop/island/test_controls.js3
-rw-r--r--docs/superpowers/specs/2026-10-13-controls-panel-design.md5
5 files changed, 25 insertions, 9 deletions
diff --git a/desktop/Prefs.qml b/desktop/Prefs.qml
index dd2fdfc..d56b34c 100644
--- a/desktop/Prefs.qml
+++ b/desktop/Prefs.qml
@@ -34,12 +34,12 @@ Singleton {
// The whole parsed object, so a write keeps keys this version does not
// know about.
- property var data: ({})
+ property var doc: ({})
function setControls(list) {
root.controls = Controls.normalize(list);
- const out = Object.assign({}, root.data, { controls: root.controls });
- root.data = out;
+ const out = Object.assign({}, root.doc, { controls: root.controls });
+ root.doc = out;
file.setText(JSON.stringify(out, null, 2) + "\n");
}
@@ -49,7 +49,7 @@ Singleton {
const v = JSON.parse(file.text());
if (v && typeof v === "object" && !Array.isArray(v)) parsed = v;
} catch (e) {}
- root.data = parsed;
+ root.doc = parsed;
const next = "controls" in parsed ? Controls.normalize(parsed.controls) : Controls.defaults();
// The watch also fires on our own write; compare so it is a no-op.
if (JSON.stringify(next) !== JSON.stringify(root.controls)) root.controls = next;
@@ -61,9 +61,12 @@ Singleton {
atomicWrites: true
watchChanges: true
printErrors: false
+ // printErrors is off for the missing-file read; a failed write still
+ // has to be heard, or the new order silently vanishes at restart.
+ onSaveFailed: error => console.warn("Prefs: writing", root.path, "failed:", error)
onFileChanged: reload()
onLoaded: root.reparse()
// Missing file: the defaults, and nothing written.
- onLoadFailed: { root.data = {}; root.controls = Controls.defaults(); }
+ onLoadFailed: { root.doc = {}; root.controls = Controls.defaults(); }
}
}
diff --git a/desktop/island/Controls.js b/desktop/island/Controls.js
index 39865b2..0ca2f6c 100644
--- a/desktop/island/Controls.js
+++ b/desktop/island/Controls.js
@@ -88,9 +88,14 @@ function fill(volume) {
}
// One scroll step, rounded to the percent so repeated steps do not drift.
+// The cap only stops a step up: a level already above it (set elsewhere,
+// e.g. a mic at 120% from pavucontrol) is left alone by up and stepped from
+// by down, never snapped to the cap.
function step(volume, delta, max) {
- const v = Math.round(((volume > 0 ? volume : 0) + delta) * 100) / 100;
- return Math.max(0, Math.min(max, v));
+ const cur = volume > 0 ? volume : 0;
+ const v = Math.round((cur + delta) * 100) / 100;
+ if (delta < 0) return Math.max(0, v);
+ return Math.max(cur, Math.min(max, v));
}
// The slot a dragged tile lands in, from its centre in grid coordinates.
diff --git a/desktop/island/ControlsPanel.qml b/desktop/island/ControlsPanel.qml
index a21b892..a7872d7 100644
--- a/desktop/island/ControlsPanel.qml
+++ b/desktop/island/ControlsPanel.qml
@@ -98,6 +98,10 @@ Column {
anchors.fill: parent
enabled: panel.editing
preventStealing: true
+ // Both buttons and the wheel, so in edit mode nothing
+ // reaches the tile: no drawer page, no volume change.
+ acceptedButtons: Qt.LeftButton | Qt.RightButton
+ onWheel: wheel => wheel.accepted = true
cursorShape: pressed ? Qt.ClosedHandCursor : Qt.OpenHandCursor
property real sx: 0
property real sy: 0
diff --git a/desktop/island/test_controls.js b/desktop/island/test_controls.js
index f84e472..b2db873 100644
--- a/desktop/island/test_controls.js
+++ b/desktop/island/test_controls.js
@@ -82,6 +82,9 @@ assert.strictEqual(C.step(1.48, 0.05, 1.5), 1.5);
assert.strictEqual(C.step(0.98, 0.05, 1.0), 1);
assert.strictEqual(C.step(0.1 + 0.2, 0.05, 1), 0.35);
assert.strictEqual(C.step(undefined, 0.05, 1), 0.05);
+// Above the cap (set elsewhere): up leaves it, down steps from where it is.
+assert.strictEqual(C.step(1.2, 0.05, 1.0), 1.2);
+assert.strictEqual(C.step(1.2, -0.05, 1.0), 1.15);
// slotAt: pitch 70, 4 columns, 7 tiles.
assert.strictEqual(C.slotAt(35, 35, 70, 4, 7), 0);
diff --git a/docs/superpowers/specs/2026-10-13-controls-panel-design.md b/docs/superpowers/specs/2026-10-13-controls-panel-design.md
index 582ffbe..627fd28 100644
--- a/docs/superpowers/specs/2026-10-13-controls-panel-design.md
+++ b/docs/superpowers/specs/2026-10-13-controls-panel-design.md
@@ -37,7 +37,8 @@ read), breaktimer and KDE Connect tiles (status displays, the drawer has them).
## Preferences store
-`Prefs.qml` is a `FileView` with a `JsonAdapter` on
+`Prefs.qml` is a `FileView`, parsed with `JSON.parse` rather than a
+`JsonAdapter` so unknown keys and a broken file survive, on
`$XDG_STATE_HOME/quickshell/desktop.json`, falling back to
`~/.local/state/quickshell/desktop.json` when the variable is unset. State
home rather than the runtime directory, because preferences must survive a
@@ -126,7 +127,7 @@ corner enters edit mode, where:
Edit mode ends on the pencil, Escape, or the panel closing. Hiding every tile
is allowed; the panel then shows only the strip.
-The order helpers in `Controls.js` are pure: `known()`, `normalize(list)` (drop
+The order helpers in `Controls.js` are pure: `defaults()`, `tile(id)`, `icon(id, on)`, `normalize(list)` (drop
unknown ids and duplicates), `hidden(list)`, `move(list, from, to)`,
`hide(list, id)`, `show(list, id)`, `fill(volume)`, `step(volume, delta,
max)`, `slotAt(x, y, pitch, cols, count)`. `test_controls.js` runs