From 96317a1abc9500d4e2d6173b126f1c40f7dd38aa Mon Sep 17 00:00:00 2001
From: HANCORE-linux <230438592+HANCORE-linux@users.noreply.github.com>
Date: Tue, 13 Oct 2026 23:53:57 +0200
Subject: [PATCH 01/18] fix(widgets): keep profile selections open and harden
panel actions
---
hancore.shibumi.ai/BarWidget.qml | 2 +-
hancore.shibumi.ai/ShibumiPanel.qml | 2 +-
hancore.shibumi.audio/ShibumiPanel.qml | 2 +-
hancore.shibumi.battery/ShibumiPanel.qml | 2 +-
hancore.shibumi.bluetooth/ShibumiPanel.qml | 2 +-
hancore.shibumi.brightness/ShibumiPanel.qml | 2 +-
hancore.shibumi.center/ShibumiPanel.qml | 2 +-
.../ShibumiPanel.qml | 2 +-
hancore.shibumi.cpu/ShibumiPanel.qml | 2 +-
hancore.shibumi.gpu/ShibumiPanel.qml | 2 +-
hancore.shibumi.media/ShibumiPanel.qml | 2 +-
hancore.shibumi.memory/ShibumiPanel.qml | 2 +-
hancore.shibumi.network/ShibumiPanel.qml | 2 +-
.../PowerProfilePanel.qml | 18 +++++++++++-------
hancore.shibumi.power-profile/ShibumiPanel.qml | 2 +-
hancore.shibumi.quick-access/Service.qml | 2 +-
hancore.shibumi.status/ShibumiPanel.qml | 2 +-
hancore.shibumi.storage/ShibumiPanel.qml | 2 +-
hancore.shibumi.temperature/ShibumiPanel.qml | 2 +-
hancore.shibumi.update-center/ShibumiPanel.qml | 2 +-
hancore.shibumi.workspaces/ShibumiPanel.qml | 2 +-
shared/presentation/ShibumiPanel.qml | 2 +-
tests/power-runtime-smoke.qml | 4 ++--
tests/quick-access-plugin-smoke.qml | 4 ++--
widgets/ShibumiPanel.qml | 2 +-
25 files changed, 37 insertions(+), 33 deletions(-)
diff --git a/hancore.shibumi.ai/BarWidget.qml b/hancore.shibumi.ai/BarWidget.qml
index 75155e7d..9bca4f4f 100644
--- a/hancore.shibumi.ai/BarWidget.qml
+++ b/hancore.shibumi.ai/BarWidget.qml
@@ -276,7 +276,7 @@ Ui.Panel {
visible: root.displayMode !== "icon"
anchors.verticalCenter: parent.verticalCenter
text: root.usagePercent >= 0
- ? String(root.usagePercent).padStart(2, "0") + "%" : "··"
+ ? String(root.usagePercent).padStart(2, "0") + "%" : "—"
color: root.widgetInk
font.family: root.bar ? root.bar.fontFamily : Commons.Style.font.family
font.pixelSize: root.tokens.labelSize
diff --git a/hancore.shibumi.ai/ShibumiPanel.qml b/hancore.shibumi.ai/ShibumiPanel.qml
index 1a61c513..2cfb41af 100644
--- a/hancore.shibumi.ai/ShibumiPanel.qml
+++ b/hancore.shibumi.ai/ShibumiPanel.qml
@@ -145,7 +145,7 @@ PanelWindow {
focusPrimed = false
beginFocusPrime()
Qt.callLater(function() {
- if (root.open && target) target.forceActiveFocus()
+ if (root !== null && root.open && target) target.forceActiveFocus()
})
}
diff --git a/hancore.shibumi.audio/ShibumiPanel.qml b/hancore.shibumi.audio/ShibumiPanel.qml
index 1a61c513..2cfb41af 100644
--- a/hancore.shibumi.audio/ShibumiPanel.qml
+++ b/hancore.shibumi.audio/ShibumiPanel.qml
@@ -145,7 +145,7 @@ PanelWindow {
focusPrimed = false
beginFocusPrime()
Qt.callLater(function() {
- if (root.open && target) target.forceActiveFocus()
+ if (root !== null && root.open && target) target.forceActiveFocus()
})
}
diff --git a/hancore.shibumi.battery/ShibumiPanel.qml b/hancore.shibumi.battery/ShibumiPanel.qml
index 1a61c513..2cfb41af 100644
--- a/hancore.shibumi.battery/ShibumiPanel.qml
+++ b/hancore.shibumi.battery/ShibumiPanel.qml
@@ -145,7 +145,7 @@ PanelWindow {
focusPrimed = false
beginFocusPrime()
Qt.callLater(function() {
- if (root.open && target) target.forceActiveFocus()
+ if (root !== null && root.open && target) target.forceActiveFocus()
})
}
diff --git a/hancore.shibumi.bluetooth/ShibumiPanel.qml b/hancore.shibumi.bluetooth/ShibumiPanel.qml
index 1a61c513..2cfb41af 100644
--- a/hancore.shibumi.bluetooth/ShibumiPanel.qml
+++ b/hancore.shibumi.bluetooth/ShibumiPanel.qml
@@ -145,7 +145,7 @@ PanelWindow {
focusPrimed = false
beginFocusPrime()
Qt.callLater(function() {
- if (root.open && target) target.forceActiveFocus()
+ if (root !== null && root.open && target) target.forceActiveFocus()
})
}
diff --git a/hancore.shibumi.brightness/ShibumiPanel.qml b/hancore.shibumi.brightness/ShibumiPanel.qml
index 1a61c513..2cfb41af 100644
--- a/hancore.shibumi.brightness/ShibumiPanel.qml
+++ b/hancore.shibumi.brightness/ShibumiPanel.qml
@@ -145,7 +145,7 @@ PanelWindow {
focusPrimed = false
beginFocusPrime()
Qt.callLater(function() {
- if (root.open && target) target.forceActiveFocus()
+ if (root !== null && root.open && target) target.forceActiveFocus()
})
}
diff --git a/hancore.shibumi.center/ShibumiPanel.qml b/hancore.shibumi.center/ShibumiPanel.qml
index 1a61c513..2cfb41af 100644
--- a/hancore.shibumi.center/ShibumiPanel.qml
+++ b/hancore.shibumi.center/ShibumiPanel.qml
@@ -145,7 +145,7 @@ PanelWindow {
focusPrimed = false
beginFocusPrime()
Qt.callLater(function() {
- if (root.open && target) target.forceActiveFocus()
+ if (root !== null && root.open && target) target.forceActiveFocus()
})
}
diff --git a/hancore.shibumi.control-center/ShibumiPanel.qml b/hancore.shibumi.control-center/ShibumiPanel.qml
index 1a61c513..2cfb41af 100644
--- a/hancore.shibumi.control-center/ShibumiPanel.qml
+++ b/hancore.shibumi.control-center/ShibumiPanel.qml
@@ -145,7 +145,7 @@ PanelWindow {
focusPrimed = false
beginFocusPrime()
Qt.callLater(function() {
- if (root.open && target) target.forceActiveFocus()
+ if (root !== null && root.open && target) target.forceActiveFocus()
})
}
diff --git a/hancore.shibumi.cpu/ShibumiPanel.qml b/hancore.shibumi.cpu/ShibumiPanel.qml
index 1a61c513..2cfb41af 100644
--- a/hancore.shibumi.cpu/ShibumiPanel.qml
+++ b/hancore.shibumi.cpu/ShibumiPanel.qml
@@ -145,7 +145,7 @@ PanelWindow {
focusPrimed = false
beginFocusPrime()
Qt.callLater(function() {
- if (root.open && target) target.forceActiveFocus()
+ if (root !== null && root.open && target) target.forceActiveFocus()
})
}
diff --git a/hancore.shibumi.gpu/ShibumiPanel.qml b/hancore.shibumi.gpu/ShibumiPanel.qml
index 1a61c513..2cfb41af 100644
--- a/hancore.shibumi.gpu/ShibumiPanel.qml
+++ b/hancore.shibumi.gpu/ShibumiPanel.qml
@@ -145,7 +145,7 @@ PanelWindow {
focusPrimed = false
beginFocusPrime()
Qt.callLater(function() {
- if (root.open && target) target.forceActiveFocus()
+ if (root !== null && root.open && target) target.forceActiveFocus()
})
}
diff --git a/hancore.shibumi.media/ShibumiPanel.qml b/hancore.shibumi.media/ShibumiPanel.qml
index 1a61c513..2cfb41af 100644
--- a/hancore.shibumi.media/ShibumiPanel.qml
+++ b/hancore.shibumi.media/ShibumiPanel.qml
@@ -145,7 +145,7 @@ PanelWindow {
focusPrimed = false
beginFocusPrime()
Qt.callLater(function() {
- if (root.open && target) target.forceActiveFocus()
+ if (root !== null && root.open && target) target.forceActiveFocus()
})
}
diff --git a/hancore.shibumi.memory/ShibumiPanel.qml b/hancore.shibumi.memory/ShibumiPanel.qml
index 1a61c513..2cfb41af 100644
--- a/hancore.shibumi.memory/ShibumiPanel.qml
+++ b/hancore.shibumi.memory/ShibumiPanel.qml
@@ -145,7 +145,7 @@ PanelWindow {
focusPrimed = false
beginFocusPrime()
Qt.callLater(function() {
- if (root.open && target) target.forceActiveFocus()
+ if (root !== null && root.open && target) target.forceActiveFocus()
})
}
diff --git a/hancore.shibumi.network/ShibumiPanel.qml b/hancore.shibumi.network/ShibumiPanel.qml
index 1a61c513..2cfb41af 100644
--- a/hancore.shibumi.network/ShibumiPanel.qml
+++ b/hancore.shibumi.network/ShibumiPanel.qml
@@ -145,7 +145,7 @@ PanelWindow {
focusPrimed = false
beginFocusPrime()
Qt.callLater(function() {
- if (root.open && target) target.forceActiveFocus()
+ if (root !== null && root.open && target) target.forceActiveFocus()
})
}
diff --git a/hancore.shibumi.power-profile/PowerProfilePanel.qml b/hancore.shibumi.power-profile/PowerProfilePanel.qml
index ef925fc5..98140f60 100644
--- a/hancore.shibumi.power-profile/PowerProfilePanel.qml
+++ b/hancore.shibumi.power-profile/PowerProfilePanel.qml
@@ -35,14 +35,16 @@ ShibumiPanel {
function activateSelected() {
if (!powerService || selectedIndex < 0 || selectedIndex >= powerState.profiles.length) return
- if (powerService.setProfile(powerService.profiles[selectedIndex]))
- ownerWidget.close()
+ powerService.setProfile(powerService.profiles[selectedIndex])
}
// Ui.Button animates its fill for 120ms after hover/cursor leaves. Derive
// the label from that painted fill, not the already-cleared state flags.
function profileForegroundRole(fill) {
- const surface = panel.renderedSurfaceColor
+ const surface = panel.renderedSurfaceColor.a < 1
+ ? panel.surfaceOverrideEnabled ? panel.surfaceColorOverride
+ : panel.shibumiTokens ? panel.shibumiTokens.panelBackground : Commons.Color.popups.background
+ : panel.renderedSurfaceColor
const background = Qt.rgba(fill.r * fill.a + surface.r * (1 - fill.a),
fill.g * fill.a + surface.g * (1 - fill.a),
fill.b * fill.a + surface.b * (1 - fill.a), 1)
@@ -173,8 +175,7 @@ ShibumiPanel {
: Commons.Border.controlSpec("normal",
panel.bar ? panel.bar.foreground : Commons.Color.foreground, accent)
onClicked: {
- if (panel.powerService && panel.powerService.setProfile(modelData))
- panel.ownerWidget.close()
+ if (panel.powerService) panel.powerService.setProfile(modelData)
}
onHovered: function(h) {
if (h) { panel.cursorActive = false; panel.selectedIndex = index }
@@ -184,9 +185,12 @@ ShibumiPanel {
}
Text {
- visible: panel.powerState.profileActionRunning || panel.powerState.profileError !== ""
+ id: actionStatus
width: parent.width
- text: panel.powerState.profileError !== "" ? panel.powerState.profileError : "Applying profile…"
+ height: actionMetrics.height
+ FontMetrics { id: actionMetrics; font: actionStatus.font }
+ text: panel.powerState.profileError !== "" ? panel.powerState.profileError
+ : panel.powerState.profileActionRunning ? "Applying profile…" : ""
color: panel.powerState.profileError !== "" && panel.bar
? panel.bar.urgent : panel.bar ? panel.bar.foreground : Commons.Color.foreground
font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
diff --git a/hancore.shibumi.power-profile/ShibumiPanel.qml b/hancore.shibumi.power-profile/ShibumiPanel.qml
index 1a61c513..2cfb41af 100644
--- a/hancore.shibumi.power-profile/ShibumiPanel.qml
+++ b/hancore.shibumi.power-profile/ShibumiPanel.qml
@@ -145,7 +145,7 @@ PanelWindow {
focusPrimed = false
beginFocusPrime()
Qt.callLater(function() {
- if (root.open && target) target.forceActiveFocus()
+ if (root !== null && root.open && target) target.forceActiveFocus()
})
}
diff --git a/hancore.shibumi.quick-access/Service.qml b/hancore.shibumi.quick-access/Service.qml
index a04916ee..ce0b743c 100644
--- a/hancore.shibumi.quick-access/Service.qml
+++ b/hancore.shibumi.quick-access/Service.qml
@@ -516,7 +516,7 @@ Item {
actionProc.actionKind = String(kind || "")
actionProc.actionLabel = String(label || "")
actionProc.errorText = ""
- actionProc.command = command
+ actionProc.command = ["bash", "-c", 'exec "$@" &2; exit 7"]))
+ ["bash", "-c", "cat >/dev/null; printf 'denied by fixture\\n' >&2; exit 7"]))
return root.fail("failed wallpaper action did not start")
root.phase++
root.ticks = 0
@@ -719,7 +719,7 @@ ShellRoot {
!== "Could not apply broken.jpg. denied by fixture")
return root.fail("failed wallpaper action was not reported")
if (!quickAccessService.startSelectionAction("theme", "broken-theme",
- ["bash", "-c", "printf 'theme denied by fixture\\n' >&2; exit 8"]))
+ ["bash", "-c", "cat >/dev/null; printf 'theme denied by fixture\\n' >&2; exit 8"]))
return root.fail("failed theme action did not start")
root.phase++
root.ticks = 0
diff --git a/widgets/ShibumiPanel.qml b/widgets/ShibumiPanel.qml
index 1a61c513..2cfb41af 100644
--- a/widgets/ShibumiPanel.qml
+++ b/widgets/ShibumiPanel.qml
@@ -145,7 +145,7 @@ PanelWindow {
focusPrimed = false
beginFocusPrime()
Qt.callLater(function() {
- if (root.open && target) target.forceActiveFocus()
+ if (root !== null && root.open && target) target.forceActiveFocus()
})
}
From 21e799f6c9e7dbe133c7d6c785dd3c994383e604 Mon Sep 17 00:00:00 2001
From: HANCORE-linux <230438592+HANCORE-linux@users.noreply.github.com>
Date: Tue, 13 Oct 2026 23:53:57 +0200
Subject: [PATCH 02/18] fix(workspaces): settle Rings placement before focus
animation
---
hancore.shibumi.workspaces/BarWidget.qml | 22 +++++++++++++++++++++-
1 file changed, 21 insertions(+), 1 deletion(-)
diff --git a/hancore.shibumi.workspaces/BarWidget.qml b/hancore.shibumi.workspaces/BarWidget.qml
index e735c80b..584b270f 100644
--- a/hancore.shibumi.workspaces/BarWidget.qml
+++ b/hancore.shibumi.workspaces/BarWidget.qml
@@ -266,7 +266,8 @@ Ui.Panel {
}
onOpenedChanged: syncPanelLoader()
onFocusedWorkspaceIdChanged: observePacmanFocus()
- onRenderStyleChanged: resetPacmanTravel()
+ onRenderStyleChanged: { frameMotion.placed = false; resetPacmanTravel() }
+ onFrameTargetChanged: if (!frameTarget) frameMotion.placed = false
Component.onCompleted: {
initialRenderStyle = renderStyle
pacmanLastFocusedWorkspaceId = focusedWorkspaceId
@@ -305,6 +306,7 @@ Ui.Panel {
Item {
id: frameMotion
+ property bool placed: false
z: 0
visible: root.renderStyle === "rings" && root.frameTarget !== null
// Use resident animated cell geometry and the logical frame size.
@@ -314,8 +316,20 @@ Ui.Panel {
anchors.verticalCenter: parent.verticalCenter
width: Commons.Style.space(18)
height: width
+ onXChanged: {
+ if (placed || root.renderStyle !== "rings" || !root.frameTarget) return
+ const cells = Array.from(workspaceRow.children)
+ .filter(item => item.visible && item.implicitWidth > 0)
+ if (cells.length === root.displayedWorkspaceIds.length
+ && cells.every((item, index) =>
+ Math.abs(item.implicitWidth - Commons.Style.space(20)) < 1e-7
+ && Math.abs(item.width - item.implicitWidth) < 1e-7
+ && Math.abs(item.x - index * (item.width + workspaceRow.spacing)) < 1e-7))
+ placed = true
+ }
Behavior on x {
+ enabled: frameMotion.placed
NumberAnimation { duration: 190; easing.type: Easing.OutCubic }
}
@@ -386,6 +400,12 @@ Ui.Panel {
anchors.centerIn: parent
spacing: root.workspaceGap
width: root.workspaceContentWidth
+ onPositioningComplete: {
+ if (root.renderStyle === "rings" && root.frameTarget
+ && Array.from(children).filter(item => item.visible && item.implicitWidth > 0)
+ .every(item => Math.abs(item.implicitWidth - Commons.Style.space(20)) < 1e-7))
+ frameMotion.placed = true
+ }
Repeater {
id: workspaceRepeater
From 9e107f111b69194bbb72d3fdbcff297a0ee08edc Mon Sep 17 00:00:00 2001
From: HANCORE-linux <230438592+HANCORE-linux@users.noreply.github.com>
Date: Tue, 13 Oct 2026 23:53:57 +0200
Subject: [PATCH 03/18] feat(battery): use bounded plasma sparks while charging
---
hancore.shibumi.battery/BatteryRing.qml | 28 +++++++------------------
1 file changed, 7 insertions(+), 21 deletions(-)
diff --git a/hancore.shibumi.battery/BatteryRing.qml b/hancore.shibumi.battery/BatteryRing.qml
index c9574f51..ac6c2c6e 100644
--- a/hancore.shibumi.battery/BatteryRing.qml
+++ b/hancore.shibumi.battery/BatteryRing.qml
@@ -28,10 +28,9 @@ Item {
anchors.fill: parent
percent: root.percent; foreground: root.foreground; accent: root.accent
panelOpen: root.panelOpen; waves: root.waves
- onWaveFrameChanged: root.repaint()
}
- function repaint() { if (panelOpen && plasma && bolts) bolts.requestPaint() }
+ function repaint() { if (panelOpen && plasma && percent >= 10 && bolts) bolts.requestPaint() }
onForegroundChanged: repaint()
onAccentChanged: repaint()
@@ -54,13 +53,13 @@ Item {
Timer {
id: arcClock
interval: 160; repeat: true
- running: root.panelOpen && root.plasma
+ running: root.panelOpen && root.plasma && root.percent >= 10
onTriggered: {
const next = []
for (let i = 0; i < 5; i++) {
- const angle = -Math.PI / 2 + (Math.random() - 0.5) * 2.2
- const x = Math.cos(angle) * 1.2
- next.push({ points: root.jag([0, 0.45], [x, bolts.surface(x)]), flicker: 0.7 + Math.random() * 0.3 })
+ const angle = Math.random() * Math.PI * 2
+ const x = Math.cos(angle) * 1.6
+ next.push({ points: root.jag([0, 0.45], [x, 0.45 + Math.sin(angle) * 1.6]), flicker: 0.7 + Math.random() * 0.3 })
}
root.arcs = next
root.arcFrame = (root.arcFrame + 1) % 45
@@ -73,29 +72,16 @@ Item {
Canvas {
id: bolts
anchors.fill: parent
- visible: root.panelOpen && root.plasma
+ visible: root.panelOpen && root.plasma && root.percent >= 10
readonly property real radius: width / 2 - 4.5
- readonly property real ratio: Math.max(0, Math.min(1, liquid.displayedPercent / 100))
- // Match the shared WaterRing surface so every effect stays underwater.
- function surface(x) {
- const amplitude = root.waves ? Math.min(Commons.Style.space(3), 2 * radius * ratio, 2 * radius * (1 - ratio)) * 0.3 / radius : 0
- return 1 - 2 * ratio + amplitude * Math.sin((x + 1) * Math.PI * 2 - liquid.waveFrame / 72 * Math.PI * 2)
- }
onVisibleChanged: if (visible) requestPaint()
onPaint: {
- if (!root.panelOpen || !root.plasma) return
+ if (!root.panelOpen || !root.plasma || root.percent < 10) return
const ctx = getContext("2d"), center = width / 2, r = radius
ctx.clearRect(0, 0, width, height)
- if (ratio <= 0) return
ctx.save()
ctx.beginPath(); ctx.arc(center, center, r, 0, Math.PI * 2); ctx.clip()
- ctx.beginPath(); ctx.moveTo(center - r, center + r)
- for (let i = 0; i <= 40; i++) {
- const x = -1 + i / 20
- ctx.lineTo(center + x * r, center + surface(x) * r)
- }
- ctx.lineTo(center + r, center + r); ctx.closePath(); ctx.clip()
ctx.lineJoin = "round"; ctx.lineCap = "round"
const glow = root.lightAccent, ink = root.brightInk
for (const arc of root.arcs) {
From edb3ec3552718c8734c98704cee436ec333090b9 Mon Sep 17 00:00:00 2001
From: HANCORE-linux <230438592+HANCORE-linux@users.noreply.github.com>
Date: Tue, 13 Oct 2026 23:53:57 +0200
Subject: [PATCH 04/18] feat(appearance): expose Frost for V2 shells
---
docs/configuration.md | 30 ++++++++++++++++++-
docs/control-center-v4.md | 4 +--
docs/getting-started.md | 3 +-
.../styles/shibumi/VisualTokens.qml | 2 +-
.../BarSurfaceSettings.qml | 3 +-
tests/control-center-smoke.qml | 2 +-
6 files changed, 37 insertions(+), 7 deletions(-)
diff --git a/docs/configuration.md b/docs/configuration.md
index 83eef2bb..c62459e5 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -23,8 +23,9 @@ unavailable rather than silently falling back. The settings include:
boundaries;
- per-widget enabled state and appearance;
- bar style and semantic accent;
-- V1 border, frost, shadow, and Radius 12/6 settings;
+- V1 border, shadow, and Radius 12/6 settings;
- V2 bar-border and panel/tooltip-border settings;
+- Frost transparency for both V1 and V2;
- workspace mode and presentation;
- G1 launcher identity;
- image and media picker styles;
@@ -38,6 +39,33 @@ normalized when they violate the schema. Setters accept queued changes; only
matching file readback confirms persistence. Unknown fields in the State entry,
including nested settings, survive ordinary settings writes.
+### Frost and optional compositor blur
+
+**Bars → Frost** makes the V1 or V2 shell translucent (alpha 0.68). It does
+not enable compositor blur. Full, Fit, and Dock retain their existing fixed
+shell shadows; Notch remains shadowless. Panel surfaces remain unchanged.
+
+For optional background blur on Hyprland 0.56, add the following to your own
+Lua configuration after the defaults. This enables global blur and applies a
+layer rule only to the `shibumi-bar` namespace. Enabling global blur affects
+every transparent window, not just layers with other blur rules. The alpha
+threshold below excludes the transparent area outside the shell from layer
+blur. Shibumi does not install or change compositor rules.
+
+```lua
+hl.config({ decoration = { blur = { enabled = true } } })
+hl.layer_rule({
+ name = "shibumi-bar-blur",
+ match = { namespace = "^shibumi-bar$" },
+ blur = true,
+ ignore_alpha = 0.1,
+})
+```
+
+The fields are defined in the [Hyprland 0.56 Lua rule parser](https://github.com/hyprwm/Hyprland/blob/36b2e0cfe0c6094dbc47bd42a437431315bb3087/src/config/lua/bindings/LuaBindingsConfigRules.cpp)
+and its [configuration example](https://github.com/hyprwm/Hyprland/blob/36b2e0cfe0c6094dbc47bd42a437431315bb3087/example/hyprland.lua).
+This optional compositor effect is separate from Frost's transparency.
+
### Appearance IPC
Maintainer automation must name the owning appearance profile explicitly:
diff --git a/docs/control-center-v4.md b/docs/control-center-v4.md
index a2f23893..3f653817 100644
--- a/docs/control-center-v4.md
+++ b/docs/control-center-v4.md
@@ -402,8 +402,8 @@ not secondary sections inside Icons:
shared Pickers route uses a content-matched compact panel height instead of
inheriting the taller generic Configure surface.
- **Bars** owns the active bar's supported surface and accent settings. V1
- exposes border, frost, shadow, and Radius 12/6. V2 exposes Bar Border and
- Panel + Tooltip; its fixed V2 radii and unsupported V1 effects are not
+ exposes border, frost, shadow, and Radius 12/6. V2 exposes Bar Border,
+ Panel + Tooltip, and Frost. Its fixed radii and shell shadows are not
presented as editable settings. Both generations expose the eight palette
roles supported by the accepted references: `color01` through `color07` and
foreground. The same tokens are consumed by both the V1 and V2 renderers.
diff --git a/docs/getting-started.md b/docs/getting-started.md
index 0c8fab24..4b7fde6b 100644
--- a/docs/getting-started.md
+++ b/docs/getting-started.md
@@ -41,8 +41,9 @@ The Control Center exposes the supported product settings:
- switch between the V1 and V2-derived shell styles;
- place the bar at the top or bottom and select its shell shape;
- choose a semantic accent from the active Omarchy theme;
-- configure V1 border, frost, shadow, and Radius 12/6 or the V2 bar and
+- configure V1 border, shadow, and Radius 12/6 or the V2 bar and
panel/tooltip borders;
+- enable Frost transparency in V1 and V2;
- choose workspace presentation and picker style;
- set per-widget presentation and enabled state; and
- rearrange widgets and control split or separator boundaries.
diff --git a/hancore.shibumi.bar/styles/shibumi/VisualTokens.qml b/hancore.shibumi.bar/styles/shibumi/VisualTokens.qml
index 9511f1e2..baa069de 100644
--- a/hancore.shibumi.bar/styles/shibumi/VisualTokens.qml
+++ b/hancore.shibumi.bar/styles/shibumi/VisualTokens.qml
@@ -65,7 +65,7 @@ Item {
readonly property bool panelBorderEnabled: v2Shell
? presentation.panelBorder !== false : borderEnabled
readonly property bool shadowEnabled: presentation.shadow === true
- readonly property bool frostEnabled: !v2Shell && presentation.frost === true
+ readonly property bool frostEnabled: presentation.frost === true
readonly property color paper: Commons.Color.background
readonly property color ink: Commons.Color.foreground
readonly property color sumi: Commons.Color.muted
diff --git a/hancore.shibumi.control-center/BarSurfaceSettings.qml b/hancore.shibumi.control-center/BarSurfaceSettings.qml
index 108b6049..35615113 100644
--- a/hancore.shibumi.control-center/BarSurfaceSettings.qml
+++ b/hancore.shibumi.control-center/BarSurfaceSettings.qml
@@ -26,7 +26,8 @@ Column {
label: "Panel + tooltip",
detail: "Outline connected panels and tooltips",
fallback: true
- }
+ },
+ { key: "frost", label: "Frost", detail: "Translucent V2 shell", fallback: false }
]
: [
{
diff --git a/tests/control-center-smoke.qml b/tests/control-center-smoke.qml
index 9fed684c..d5824784 100644
--- a/tests/control-center-smoke.qml
+++ b/tests/control-center-smoke.qml
@@ -1466,7 +1466,7 @@ ShellRoot {
if (!panel || !panel.settingsPageReady
|| panel.settingsPage !== "bars"
|| !panel.settingsPageItem
- || panel.settingsPageItem.surfaceEffectOptionCount !== 2
+ || panel.settingsPageItem.surfaceEffectOptionCount !== 3
|| panel.settingsPageItem.surfaceEffectPreviewCount !== 0
|| panel.settingsPageItem.splitActionPreviewCount !== 0
|| panel.settingsPageItem.surfaceRadiusOptionCount !== 0
From 0493e4dbee3ef9431c2f3e20ae3298a561abadb9 Mon Sep 17 00:00:00 2001
From: HANCORE-linux <230438592+HANCORE-linux@users.noreply.github.com>
Date: Tue, 13 Oct 2026 23:53:57 +0200
Subject: [PATCH 05/18] feat(memory): add an authorized in-panel sweep
---
hancore.shibumi.memory/MemoryPanel.qml | 130 ++++++++++++++----
hancore.shibumi.telemetry/SystemTelemetry.qml | 77 ++++++++++-
tests/contract-regression.sh | 5 +-
3 files changed, 175 insertions(+), 37 deletions(-)
diff --git a/hancore.shibumi.memory/MemoryPanel.qml b/hancore.shibumi.memory/MemoryPanel.qml
index 8583db5e..0251e96f 100644
--- a/hancore.shibumi.memory/MemoryPanel.qml
+++ b/hancore.shibumi.memory/MemoryPanel.qml
@@ -11,6 +11,14 @@ ShibumiPanel {
required property var telemetry
readonly property var usageRing: memoryRing
readonly property var hardwareInfoView: hardwareInfo
+ property QtObject sweepSource: null
+ property bool cacheSelected: true
+ property bool compactSelected: true
+ readonly property bool sweepAvailable: !!telemetry && typeof telemetry.startMemorySweep === "function"
+ readonly property bool sweepVisible: sweepAvailable && sweepSource === telemetry
+ readonly property bool sweepRunning: sweepAvailable && telemetry.memorySweepRunning === true
+ readonly property string sweepError: sweepAvailable ? String(telemetry.memorySweepError || "") : ""
+ function gib(value) { return typeof value === "number" && value >= 0 ? (value / 1024).toFixed(1) + " GiB" : "—" }
owner: ownerWidget
open: ownerWidget.opened
@@ -28,7 +36,7 @@ ShibumiPanel {
if (next) next.acquire("memoryPanel")
}
onTelemetryChanged: syncHardwareLease()
- onOpenChanged: syncHardwareLease()
+ onOpenChanged: { syncHardwareLease(); if (!open) sweepSource = null }
Component.onCompleted: syncHardwareLease()
Component.onDestruction: if (acquiredTelemetry) acquiredTelemetry.release("memoryPanel")
@@ -50,7 +58,7 @@ ShibumiPanel {
Text {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
- text: "MEMORY"
+ text: panel.sweepVisible ? "MEMORY / SWEEP" : "MEMORY"
color: panel.bar ? panel.bar.foreground : Commons.Color.foreground
font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
font.pixelSize: 13
@@ -100,6 +108,7 @@ ShibumiPanel {
}
Row {
+ visible: !panel.sweepVisible
width: parent.width
height: memoryRing.height
spacing: Commons.Style.space(16)
@@ -143,44 +152,111 @@ ShibumiPanel {
}
Rectangle {
+ visible: !panel.sweepVisible
width: parent.width
height: 1
color: panel.dividerColor
}
- Rectangle {
+ Column {
+ visible: panel.sweepVisible
width: parent.width
- height: 28
- radius: panel.renderedSurfaceRadius
- color: monitorMouse.containsMouse
- ? panel.controlPrimaryHoverColor
- : panel.bar ? panel.bar.urgent : Commons.Color.accent
-
- Behavior on color { ColorAnimation { duration: 120 } }
-
- Text {
- anchors.centerIn: parent
- text: "Open btop"
- color: panel.bar ? panel.bar.background : Commons.Color.background
- font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
- font.pixelSize: 11
- renderType: Text.NativeRendering
+ height: 50
+ SweepChoice {
+ label: "Page cache"; value: panel.gib(panel.telemetry ? panel.telemetry.memCachedMiB + panel.telemetry.memBuffersMiB : -1)
+ detail: "no guaranteed gain"; checked: panel.cacheSelected
+ onToggled: panel.cacheSelected = !panel.cacheSelected
}
-
- MouseArea {
- id: monitorMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: {
- panel.ownerWidget.close()
- panel.ownerWidget.openSystemMonitor()
+ SweepChoice {
+ label: "Compact memory"; value: "—"; detail: "no size estimate"
+ checked: panel.compactSelected
+ onToggled: panel.compactSelected = !panel.compactSelected
+ }
+ }
+ SweepLabel {
+ visible: panel.sweepVisible
+ width: parent.width; height: 13; font.pixelSize: 10
+ text: "Available: " + panel.gib(panel.sweepAvailable && panel.telemetry.memorySweepBeforeMiB >= 0
+ ? panel.telemetry.memorySweepBeforeMiB : panel.telemetry ? panel.telemetry.memAvailableMiB : -1)
+ + " → " + panel.gib(panel.sweepAvailable ? panel.telemetry.memorySweepAfterMiB : -1)
+ }
+ SweepLabel {
+ visible: panel.sweepVisible
+ width: parent.width; height: 10; font.pixelSize: 9
+ text: panel.sweepError || (panel.sweepRunning ? "Authorizing / sweeping…" : "One authorization in the Omarchy dialog · no terminal")
+ color: panel.sweepError ? panel.controlAccent : panel.controlMutedHigh
+ elide: Text.ElideRight
+ }
+ Row {
+ width: parent.width; height: 28; spacing: 8
+ MemoryAction {
+ label: panel.sweepVisible ? "Back" : "Open btop"
+ onTriggered: {
+ if (panel.sweepVisible) panel.sweepSource = null
+ else { panel.ownerWidget.close(); panel.ownerWidget.openSystemMonitor() }
+ }
+ }
+ MemoryAction {
+ label: "Sweep"
+ enabled: panel.sweepAvailable && (!panel.sweepVisible || (!panel.sweepRunning
+ && (panel.cacheSelected || panel.compactSelected)))
+ onTriggered: {
+ if (!panel.sweepAvailable) return
+ if (!panel.sweepVisible) {
+ panel.telemetry.clearMemorySweepResult()
+ panel.cacheSelected = true; panel.compactSelected = true
+ panel.sweepSource = panel.telemetry
+ } else panel.telemetry.startMemorySweep(panel.cacheSelected, panel.compactSelected)
}
}
}
}
}
+ component SweepLabel: Text {
+ color: panel.controlForeground
+ font.family: panel.bar ? panel.bar.fontFamily : Commons.Style.font.family
+ font.pixelSize: 11
+ renderType: Text.NativeRendering
+ textFormat: Text.PlainText
+ }
+
+ component SweepChoice: Item {
+ id: choice
+ required property string label
+ required property string value
+ required property string detail
+ required property bool checked
+ signal toggled()
+ width: parent.width; height: 25
+ enabled: !panel.sweepRunning
+ Rectangle {
+ y: 2; width: 12; height: 12
+ radius: Math.min(height / 2, panel.controlRadius)
+ color: choice.checked ? panel.controlAccent : panel.controlFillColor
+ border.width: 1; border.color: choice.checked ? panel.controlAccent : panel.controlBorderColor
+ SweepLabel { anchors.centerIn: parent; text: choice.checked ? "✓" : ""; font.pixelSize: 10; color: panel.bar ? panel.bar.background : Commons.Color.background }
+ }
+ SweepLabel { x: 20; width: parent.width - 88; text: choice.label; elide: Text.ElideRight }
+ SweepLabel { anchors.right: parent.right; text: choice.value }
+ SweepLabel { x: 20; y: 13; width: parent.width - 20; text: choice.detail; font.pixelSize: 9; color: panel.controlMutedHigh; elide: Text.ElideRight }
+ MouseArea { anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: choice.toggled() }
+ }
+
+ component MemoryAction: Rectangle {
+ id: action
+ required property string label
+ signal triggered()
+ width: (parent.width - 8) / 2; height: 28
+ radius: panel.renderedSurfaceRadius
+ opacity: enabled ? 1 : 0.45
+ color: actionMouse.containsMouse ? panel.controlPrimaryHoverColor
+ : panel.bar ? panel.bar.urgent : Commons.Color.accent
+ Behavior on color { ColorAnimation { duration: 120 } }
+ SweepLabel { anchors.centerIn: parent; text: action.label; color: panel.bar ? panel.bar.background : Commons.Color.background }
+ MouseArea { id: actionMouse; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onClicked: action.triggered() }
+ }
+
component MemoryStatRow: Item {
required property string label
required property string value
diff --git a/hancore.shibumi.telemetry/SystemTelemetry.qml b/hancore.shibumi.telemetry/SystemTelemetry.qml
index b076c640..dcc9da26 100644
--- a/hancore.shibumi.telemetry/SystemTelemetry.qml
+++ b/hancore.shibumi.telemetry/SystemTelemetry.qml
@@ -29,6 +29,12 @@ Scope {
property int memFreeMiB: 0
property int memBuffersMiB: 0
property int memCachedMiB: 0
+ property bool memorySampleReady: false
+ property bool memorySweepRunning: false
+ property bool memorySweepStarted: false
+ property string memorySweepError: ""
+ property real memorySweepBeforeMiB: -1
+ property real memorySweepAfterMiB: -1
readonly property int memUsedMiB: Math.max(0, memTotalMiB - memAvailableMiB)
readonly property int memPercent: memTotalMiB > 0
? Math.max(0, Math.min(100, Math.round(memUsedMiB / memTotalMiB * 100)))
@@ -97,7 +103,7 @@ Scope {
function parseMemory(text) {
let total = 0
- let available = 0
+ let available = -1
let free = 0
let buffers = 0
let cached = 0
@@ -113,13 +119,69 @@ Scope {
else if (parts[0] === "Buffers:") buffers = value
else if (parts[0] === "Cached:") cached = value
}
- if (total <= 0) return
+ if (total > 0) {
+ memTotalMiB = Math.round(total / 1024)
+ memAvailableMiB = Math.round(Math.max(0, available) / 1024)
+ memFreeMiB = Math.round(free / 1024)
+ memBuffersMiB = Math.round(buffers / 1024)
+ memCachedMiB = Math.round(cached / 1024)
+ }
+ memorySampleReady = total > 0 && available >= 0 && available <= total
+ }
+
+ function readMemoryNow() {
+ // Drain an older read before requesting the action's fresh snapshot.
+ memoryFile.waitForJob()
+ memorySampleReady = false
+ memoryFile.reload()
+ memoryFile.waitForJob()
+ return memorySampleReady
+ }
+
+ function clearMemorySweepResult() {
+ if (memorySweepRunning) return
+ memorySweepError = ""
+ memorySweepBeforeMiB = -1
+ memorySweepAfterMiB = -1
+ }
+
+ function startMemorySweep(pageCache, compact) {
+ if (memorySweepRunning || typeof pageCache !== "boolean"
+ || typeof compact !== "boolean" || !(pageCache || compact)) return false
+ clearMemorySweepResult()
+ memorySweepRunning = true
+ memorySweepStarted = false
+ if (!readMemoryNow()) {
+ memorySweepError = "Could not read fresh memory data"
+ memorySweepRunning = false
+ return false
+ }
+ memorySweepBeforeMiB = memAvailableMiB
+ const steps = ["status=0"]
+ if (pageCache) steps.push("sync && echo 3 > /proc/sys/vm/drop_caches || status=1")
+ if (compact) steps.push("echo 1 > /proc/sys/vm/compact_memory || status=1")
+ steps.push("exit \"$status\"")
+ memorySweepProcess.command = ["pkexec", "/bin/sh", "-c", steps.join("; ")]
+ memorySweepProcess.running = true
+ return true
+ }
+
+ function finishMemorySweep(exitCode, exitStatus) {
+ if (!memorySweepRunning) return
+ memorySweepError = exitStatus === 0 && exitCode === 126 ? "cancelled"
+ : exitStatus !== 0 || exitCode !== 0
+ ? "Sweep failed (exit " + exitCode + ")" : ""
+ if (readMemoryNow()) memorySweepAfterMiB = memAvailableMiB
+ else if (!memorySweepError) memorySweepError = "Could not read fresh memory data"
+ memorySweepRunning = false
+ }
- memTotalMiB = Math.round(total / 1024)
- memAvailableMiB = Math.round(available / 1024)
- memFreeMiB = Math.round(free / 1024)
- memBuffersMiB = Math.round(buffers / 1024)
- memCachedMiB = Math.round(cached / 1024)
+ Process {
+ id: memorySweepProcess
+ onStarted: root.memorySweepStarted = true
+ onRunningChanged: if (!running && root.memorySweepRunning && !root.memorySweepStarted)
+ root.finishMemorySweep(-1, 1)
+ onExited: function(exitCode, exitStatus) { root.finishMemorySweep(exitCode, exitStatus) }
}
function parseMemoryHardware(text) {
@@ -184,6 +246,7 @@ Scope {
path: "/proc/meminfo"
printErrors: false
onLoaded: root.parseMemory(text())
+ onLoadFailed: root.memorySampleReady = false
}
FileView {
diff --git a/tests/contract-regression.sh b/tests/contract-regression.sh
index 7b4af58e..dde7323b 100755
--- a/tests/contract-regression.sh
+++ b/tests/contract-regression.sh
@@ -983,9 +983,8 @@ rg -Fq 'result.ok === true' "$bluetooth_panel" \
[[ $(rg -c 'SystemTelemetry \{' hancore.shibumi.telemetry/Service.qml) -eq 1 ]] \
|| fail "system telemetry must have one process-wide owner"
-if rg -q 'Process \{' hancore.shibumi.telemetry/SystemTelemetry.qml; then
- fail "system telemetry must read procfs without child processes"
-fi
+[[ $(rg -c 'Process \{' hancore.shibumi.telemetry/SystemTelemetry.qml) -eq 1 ]] \
+ || fail "system telemetry must have one process-wide Memory Sweep worker"
rg -q '"service": "WorkspaceService.qml"' hancore.shibumi.workspaces/manifest.json \
|| fail "workspace state must have exactly one shipped owner"
workspace_service=hancore.shibumi.workspaces/WorkspaceService.qml
From 0634f13a968eb48bef8a70a06f43849c15f916e0 Mon Sep 17 00:00:00 2001
From: HANCORE-linux <230438592+HANCORE-linux@users.noreply.github.com>
Date: Tue, 13 Oct 2026 23:53:57 +0200
Subject: [PATCH 06/18] chore(compat): pin the Omarchy 4c1b4cb3 reference
---
AGENTS.md | 2 +-
.../omarchy-forward-compat-4c1b4cb3.json | 41 +++++++++++++++++++
.../omarchy-forward-compat-e71a816e.json | 41 +++++++++++++++++++
docs/architecture/quattro-compatibility.md | 11 ++---
docs/development/release.md | 3 +-
docs/development/testing.md | 6 +--
docs/development/validation-host.md | 2 +-
docs/release-readiness.md | 4 +-
scripts/collect-release-evidence | 2 +-
tests/baseline-contract-regression.sh | 6 +--
tests/documentation-regression.py | 2 +-
tests/lib/baselines.sh | 6 ++-
tests/panel-window-geometry-regression.sh | 5 ++-
tests/quattro-contract-regression.sh | 2 +-
14 files changed, 111 insertions(+), 22 deletions(-)
create mode 100644 contracts/baselines/omarchy-forward-compat-4c1b4cb3.json
create mode 100644 contracts/baselines/omarchy-forward-compat-e71a816e.json
diff --git a/AGENTS.md b/AGENTS.md
index e2616073..8efda57d 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -51,7 +51,7 @@ SHIBUMI_INSTALLED_SOURCE_OMARCHY_PATH=/tmp/omarchy-installed-source-v4.0.4 \
./tests/omarchy-installed-source-parity-contract-regression.sh
SHIBUMI_AGENTS_OMARCHY_PATH=/tmp/omarchy-v4.0.0 \
./tests/omarchy-agents-contract-regression.sh
-SHIBUMI_FORWARD_COMPAT_OMARCHY_PATH=/tmp/omarchy-forward-compat-50d687a1 \
+SHIBUMI_FORWARD_COMPAT_OMARCHY_PATH=/tmp/omarchy-forward-compat-4c1b4cb3 \
./tests/omarchy-forward-compat-contract-regression.sh
```
diff --git a/contracts/baselines/omarchy-forward-compat-4c1b4cb3.json b/contracts/baselines/omarchy-forward-compat-4c1b4cb3.json
new file mode 100644
index 00000000..c0045234
--- /dev/null
+++ b/contracts/baselines/omarchy-forward-compat-4c1b4cb3.json
@@ -0,0 +1,41 @@
+{
+ "schemaVersion": 2,
+ "id": "forward-compat-4c1b4cb3",
+ "profile": "forward-compat",
+ "repository": "https://github.com/basecamp/omarchy",
+ "sourceRevision": "4c1b4cb3a94447aff39af98e2fcc0490a9fb32e7",
+ "provenance": {
+ "kind": "git",
+ "revision": "4c1b4cb3a94447aff39af98e2fcc0490a9fb32e7"
+ },
+ "quickshellPackage": {
+ "name": "quickshell",
+ "version": "0.3.1-1"
+ },
+ "subtrees": [
+ {
+ "path": "shell",
+ "entryPolicy": "regular-files",
+ "entryCount": 266,
+ "inventorySha256": "f1f6cd9e400c0aa11e5b812efd948581ce212686f835c73d5d1e8e625a576a5c",
+ "structureSha256": "3e527a194453d12b36cf21f7644f17a1fb1e68cbc1748de2e5432055becc9c8f",
+ "contentSha256": "170a3d67efae89cae2f2db3f255f365a45d35762af8553c6c5b3df4fa0ab90eb"
+ },
+ {
+ "path": "bin",
+ "entryPolicy": "regular-files",
+ "entryCount": 513,
+ "inventorySha256": "836d38ea96542817b9a61a53bc6c388e8508cc8789a6ef3b375fabf3872112d3",
+ "structureSha256": "3796bf848481487b50df61f65980512e3451caffeb9f6814d539ab9f7c3fd7b0",
+ "contentSha256": "45a87998ba3811afad928abf6de2d521a2dfc5fc71aa284672176c3e9d73e18f"
+ },
+ {
+ "path": "config",
+ "entryPolicy": "regular-files",
+ "entryCount": 80,
+ "inventorySha256": "221a244b38a9d8a508ee84ab934b3471815845eb4882c431f7ba830d1b2ee6eb",
+ "structureSha256": "9f3b9cefbf7aaa2a7bffed333e5c345c096024dcff28b0b3f5dc4cfa0a1ddae4",
+ "contentSha256": "50196a92b8296e9d3bdac751775dc07e058349d60fdbd2a83d0a36d0d8441b0c"
+ }
+ ]
+}
diff --git a/contracts/baselines/omarchy-forward-compat-e71a816e.json b/contracts/baselines/omarchy-forward-compat-e71a816e.json
new file mode 100644
index 00000000..9e239aea
--- /dev/null
+++ b/contracts/baselines/omarchy-forward-compat-e71a816e.json
@@ -0,0 +1,41 @@
+{
+ "schemaVersion": 2,
+ "id": "forward-compat-e71a816e",
+ "profile": "forward-compat",
+ "repository": "https://github.com/basecamp/omarchy",
+ "sourceRevision": "e71a816e9032f040225e086c7256f73e8080ae45",
+ "provenance": {
+ "kind": "git",
+ "revision": "e71a816e9032f040225e086c7256f73e8080ae45"
+ },
+ "quickshellPackage": {
+ "name": "quickshell",
+ "version": "0.3.1-1"
+ },
+ "subtrees": [
+ {
+ "path": "shell",
+ "entryPolicy": "regular-files",
+ "entryCount": 264,
+ "inventorySha256": "1ebbe5ee3c0dbe254ba3ea3e6dec30fb091e288cd61748c1c580645b0770d7a6",
+ "structureSha256": "ca890064a1cc7436e5ed0038a9c71e4fa1264134a48d138677bf761a57a39e5f",
+ "contentSha256": "c472d5430e630232ca2102e38a432f8a7a9ebe5c33ccdf1675881940a9fa7b0e"
+ },
+ {
+ "path": "bin",
+ "entryPolicy": "regular-files",
+ "entryCount": 530,
+ "inventorySha256": "83ce7d6ebf92011cce054a3f656ee23ff6d459d380b96aeeeae92f07b67e93a2",
+ "structureSha256": "1cc745b203cf4dc9535a02a88ce695a8295971eb2158795dfaeca46b1adf7cf2",
+ "contentSha256": "e57d1d0f47d35bdbdb393be91a9181f5d6a8cfda0e9b377609846d3b2b224bba"
+ },
+ {
+ "path": "config",
+ "entryPolicy": "regular-files",
+ "entryCount": 80,
+ "inventorySha256": "221a244b38a9d8a508ee84ab934b3471815845eb4882c431f7ba830d1b2ee6eb",
+ "structureSha256": "9f3b9cefbf7aaa2a7bffed333e5c345c096024dcff28b0b3f5dc4cfa0a1ddae4",
+ "contentSha256": "50196a92b8296e9d3bdac751775dc07e058349d60fdbd2a83d0a36d0d8441b0c"
+ }
+ ]
+}
diff --git a/docs/architecture/quattro-compatibility.md b/docs/architecture/quattro-compatibility.md
index 749f0b3e..304287fa 100644
--- a/docs/architecture/quattro-compatibility.md
+++ b/docs/architecture/quattro-compatibility.md
@@ -18,7 +18,7 @@ below applies to its recorded single-output Omarchy 4.0.3 setup:
| Pinned official Omarchy source tag | `v4.0.4` (`c668141e9c42b13c80c9ca4ea108e11708c5e8a5`) |
| Pinned source-parity revision | `c668141e9c42b13c80c9ca4ea108e11708c5e8a5` |
| Immutable Agents reference | `v4.0.0` (`f0020448ca87329199de7cb12f2015ebc4a3e5e7`) |
-| Immutable forward-compatibility revision | `50d687a1f27063513cf42e6f33c3a4fcb17c24ed` |
+| Immutable forward-compatibility revision | `4c1b4cb3a94447aff39af98e2fcc0490a9fb32e7` |
| Quickshell package | `quickshell 0.3.1-1` |
| Recorded Beta.13 physical validation date (4.0.3) | 2026-09-13 |
@@ -27,8 +27,9 @@ source-parity checkout is pinned to the official `v4.0.4` tag. The 4.0.3 and
4.0.2 package and source manifests remain immutable optional compatibility
references. The older Agents-only gate remains explicitly pinned to `v4.0.0`;
the separate
-`50d687a1` snapshot proves bounded forward compatibility without following a
-moving branch.
+`4c1b4cb3` snapshot supersedes `e71a816e` as the bounded forward reference.
+Manifest, panel geometry and focused Quattro/style checks pass; this is not a
+complete candidate forward-gate pass and does not follow a moving branch.
The package-managed baseline records these authoritative production anchors:
@@ -51,7 +52,7 @@ consumed `shell`, `bin`, and `config` subtrees without conflating their claims:
records the full Git checkout of the official `v4.0.4` source tag;
- the immutable 4.0.3 package/source manifests retain the previous physical
acceptance baseline without claiming that acceptance for 4.0.4;
-- [`omarchy-forward-compat-50d687a1.json`](../../contracts/baselines/omarchy-forward-compat-50d687a1.json)
+- [`omarchy-forward-compat-4c1b4cb3.json`](../../contracts/baselines/omarchy-forward-compat-4c1b4cb3.json)
records the immutable forward-compatibility snapshot at the recorded upstream
revision. It does not follow the moving remote branch.
@@ -91,7 +92,7 @@ SHIBUMI_INSTALLED_SOURCE_OMARCHY_PATH=/path/to/omarchy-v4.0.4 \
./tests/omarchy-installed-source-parity-contract-regression.sh
SHIBUMI_AGENTS_OMARCHY_PATH=/path/to/omarchy-v4.0.0 \
./tests/omarchy-agents-contract-regression.sh
-SHIBUMI_FORWARD_COMPAT_OMARCHY_PATH=/path/to/omarchy-forward-compat-50d687a1 \
+SHIBUMI_FORWARD_COMPAT_OMARCHY_PATH=/path/to/omarchy-forward-compat-4c1b4cb3 \
./tests/omarchy-forward-compat-contract-regression.sh
```
diff --git a/docs/development/release.md b/docs/development/release.md
index 819abc10..6a8a8161 100644
--- a/docs/development/release.md
+++ b/docs/development/release.md
@@ -28,6 +28,7 @@ permission to change a host; each operation needs separate maintainer approval.
[packaging and AUR strategy](packaging.md).
8. On host baseline change, rerun every retained update arm before tagging;
do not silently drop an arm or choose an older rolling predecessor.
+9. Re-pin forward compatibility to current `quattro` with `scripts/generate-forward-baseline.py`; update pins in `tests/lib/baselines.sh`, `tests/baseline-contract-regression.sh`, `tests/quattro-contract-regression.sh`, `tests/panel-window-geometry-regression.sh`, `scripts/collect-release-evidence`, `tests/documentation-regression.py`, `AGENTS.md`, `docs/release-readiness.md`, `docs/architecture/quattro-compatibility.md`, `docs/development/testing.md`, `docs/development/validation-host.md`, and `docs/development/release.md`, provision the clean runner checkout `~/.local/share/shibumi/validation-baselines/omarchy-forward-compat-`, and have the maintainer update Actions variable `SHIBUMI_FORWARD_COMPAT_OMARCHY_PATH`.
## Version pin categories
@@ -125,7 +126,7 @@ SHIBUMI_INSTALLED_SOURCE_OMARCHY_PATH=/path/to/omarchy-v4.0.4 \
python3 tests/native-catalog-resource-regression.py
SHIBUMI_AGENTS_OMARCHY_PATH=/path/to/omarchy-v4.0.0 \
./tests/omarchy-agents-contract-regression.sh
-SHIBUMI_FORWARD_COMPAT_OMARCHY_PATH=/path/to/omarchy-forward-compat-50d687a1 \
+SHIBUMI_FORWARD_COMPAT_OMARCHY_PATH=/path/to/omarchy-forward-compat-4c1b4cb3 \
./tests/omarchy-forward-compat-contract-regression.sh
```
diff --git a/docs/development/testing.md b/docs/development/testing.md
index 5df37e4c..d094a994 100644
--- a/docs/development/testing.md
+++ b/docs/development/testing.md
@@ -71,7 +71,7 @@ The forward-compat job separately requires the immutable upstream snapshot used
by the engineering audit:
```bash
-SHIBUMI_FORWARD_COMPAT_OMARCHY_PATH=/path/to/omarchy-forward-compat-50d687a1 \
+SHIBUMI_FORWARD_COMPAT_OMARCHY_PATH=/path/to/omarchy-forward-compat-4c1b4cb3 \
./tests/omarchy-forward-compat-contract-regression.sh
```
@@ -99,8 +99,8 @@ non-overlapping claims; the Agents job selects its separate narrower manifest:
remain immutable optional compatibility references alongside 4.0.2; choose
a historical manifest with `SHIBUMI_OMARCHY_BASELINE_VERSION=4.0.3` or
`SHIBUMI_OMARCHY_BASELINE_VERSION=4.0.2`;
-- `contracts/baselines/omarchy-forward-compat-50d687a1.json` proves forward
- compatibility with the recorded upstream snapshot.
+- `contracts/baselines/omarchy-forward-compat-4c1b4cb3.json` records the
+ upstream snapshot pinned for the separate forward-compatibility gate.
All three manifests bind the complete consumed `shell`, `bin`, and `config`
subtrees by entry count, path inventory, entry type, executable state, symlink
diff --git a/docs/development/validation-host.md b/docs/development/validation-host.md
index 4a3c3a52..d3ca2b15 100644
--- a/docs/development/validation-host.md
+++ b/docs/development/validation-host.md
@@ -115,7 +115,7 @@ reviewed Shibumi checkout, match these manifest `sourceRevision` values exactly:
| --- | --- | --- |
| `omarchy-installed-source-parity-v4.0.4.json` | `c668141e9c42b13c80c9ca4ea108e11708c5e8a5` | `SHIBUMI_INSTALLED_SOURCE_OMARCHY_PATH` |
| `omarchy-agents-v4.0.0.json` | `f0020448ca87329199de7cb12f2015ebc4a3e5e7` | `SHIBUMI_AGENTS_OMARCHY_PATH` |
-| `omarchy-forward-compat-50d687a1.json` | `50d687a1f27063513cf42e6f33c3a4fcb17c24ed` | `SHIBUMI_FORWARD_COMPAT_OMARCHY_PATH` |
+| `omarchy-forward-compat-4c1b4cb3.json` | `4c1b4cb3a94447aff39af98e2fcc0490a9fb32e7` | `SHIBUMI_FORWARD_COMPAT_OMARCHY_PATH` |
After provisioning approval, clone `https://github.com/basecamp/omarchy` separately
for each profile and `git checkout --detach `. Check `HEAD^{commit}`,
diff --git a/docs/release-readiness.md b/docs/release-readiness.md
index 60b023d0..031bca86 100644
--- a/docs/release-readiness.md
+++ b/docs/release-readiness.md
@@ -40,7 +40,7 @@ remains deferred.
| Retained runtime paths | Historical Beta.13 → Beta.14.1 package arm (5), fresh checkout (6), Beta.15 (7), Beta.15.2 (7), and rolling frozen Beta.16.2 → candidate (7). No arm may be dropped. |
| Stage-1 visual evidence | Round 379 records DPR 1/1.6 rest and shared-code captures, seven-theme picker search, previewless themes, sorting, and an unchanged Update Center. Round 380 separately rechecks Pacman against 16.2 and the six retained hover styles against 379. No physical-output claim. |
| Stage-1 performance | Round 379: 670 sessions, 1,880 events, cold/warm N5; focus, style, bar and hover only, not media-scroll or Control Center events. Accepted by the maintainer, including the warm Kanji CPU group as variation. Focus R/B/changed-buffer counts are not identical in every group; 13/140 hover input-to-last-pixel measurements exceed 136.667 ms by at most 2.29 ms. No blanket speedup or scanout claim. Pacman has since been restored to 16.2; no new performance series is claimed. |
-| Compatibility preparation | The unchanged validator accepts forward manifest `50d687a1`; the old manifest remains. Stage-1 complete profiles stopped at the expected old-version payload pin, so all final complete gates must run again after release identities are aligned. |
+| Compatibility preparation | Forward manifest `4c1b4cb3` supersedes `e71a816e`; older manifests remain. Manifest, panel geometry and focused Quattro/style checks pass. Complete forward evidence after release-identity alignment remains required; the pin is not a complete compatibility pass. |
| Live installation and hardware | Keep-settings installation and reviewer live acceptance must be recorded separately. Fixtures do not establish physical multi-output, hardware, credential or real desktop input acceptance. |
| Release/package verification | Reproducible archive, checksum agreement, clean-commit gates and rehearsal, independent review, full collector and remote verification remain separate publication requirements. |
@@ -65,7 +65,7 @@ The release's installed-package and source-parity contracts target Omarchy
| Installed package profile | `installed-package-v4.0.4` |
| Source parity | `installed-source-parity-v4.0.4`, `c668141e9c42b13c80c9ca4ea108e11708c5e8a5` |
| Agents reference | `v4.0.0`, `f0020448ca87329199de7cb12f2015ebc4a3e5e7` |
-| Forward reference | `50d687a1f27063513cf42e6f33c3a4fcb17c24ed` |
+| Forward reference | `4c1b4cb3a94447aff39af98e2fcc0490a9fb32e7` |
The manifests in [`contracts/baselines/`](../contracts/baselines/) bind consumed
files and subtrees. Historical 4.0.3/4.0.2 manifests remain explicit compatibility
diff --git a/scripts/collect-release-evidence b/scripts/collect-release-evidence
index 4014d034..ba26d34a 100755
--- a/scripts/collect-release-evidence
+++ b/scripts/collect-release-evidence
@@ -59,7 +59,7 @@ EXTERNAL_BASELINES: tuple[tuple[str, Path], ...] = (
),
(
"SHIBUMI_FORWARD_COMPAT_OMARCHY_PATH",
- ROOT / "contracts/baselines/omarchy-forward-compat-50d687a1.json",
+ ROOT / "contracts/baselines/omarchy-forward-compat-4c1b4cb3.json",
),
)
diff --git a/tests/baseline-contract-regression.sh b/tests/baseline-contract-regression.sh
index de10f9bf..09ec1567 100755
--- a/tests/baseline-contract-regression.sh
+++ b/tests/baseline-contract-regression.sh
@@ -12,7 +12,7 @@ compat_package_baseline="$repo_root/contracts/baselines/omarchy-installed-packag
compat_source_baseline="$repo_root/contracts/baselines/omarchy-installed-source-parity-v4.0.2.json"
historical_package_baseline="$repo_root/contracts/baselines/omarchy-installed-package-v4.0.0.json"
historical_source_baseline="$repo_root/contracts/baselines/omarchy-installed-source-parity-v4.0.0.json"
-forward_compat_baseline="$repo_root/contracts/baselines/omarchy-forward-compat-50d687a1.json"
+forward_compat_baseline="$repo_root/contracts/baselines/omarchy-forward-compat-4c1b4cb3.json"
agents_baseline="$repo_root/contracts/baselines/omarchy-agents-v4.0.0.json"
predecessor_baseline="$repo_root/contracts/baselines/quickshell-dots-d0896fc-v2-deec8103.json"
installed_package_job="$repo_root/tests/omarchy-installed-package-contract-regression.sh"
@@ -184,9 +184,9 @@ for parity_subtree in shell config; do
done
jq -e '
- .id == "forward-compat-50d687a1"
+ .id == "forward-compat-4c1b4cb3"
and .profile == "forward-compat"
- and .sourceRevision == "50d687a1f27063513cf42e6f33c3a4fcb17c24ed"
+ and .sourceRevision == "4c1b4cb3a94447aff39af98e2fcc0490a9fb32e7"
and .provenance.kind == "git"
and .provenance.revision == .sourceRevision
and .quickshellPackage == {"name": "quickshell", "version": "0.3.1-1"}
diff --git a/tests/documentation-regression.py b/tests/documentation-regression.py
index 71616272..94bdf589 100755
--- a/tests/documentation-regression.py
+++ b/tests/documentation-regression.py
@@ -257,7 +257,7 @@ def legacy_storage_error(documents: dict[str, str]) -> str | None:
"contracts/baselines/omarchy-installed-package-v4.0.3.json",
"contracts/baselines/omarchy-installed-source-parity-v4.0.3.json",
"SHIBUMI_OMARCHY_BASELINE_VERSION=4.0.2",
- "contracts/baselines/omarchy-forward-compat-50d687a1.json",
+ "contracts/baselines/omarchy-forward-compat-4c1b4cb3.json",
"./tests/omarchy-installed-package-contract-regression.sh",
"./tests/omarchy-installed-source-parity-contract-regression.sh",
"./tests/omarchy-forward-compat-contract-regression.sh",
diff --git a/tests/lib/baselines.sh b/tests/lib/baselines.sh
index f9a6b44f..386879fb 100644
--- a/tests/lib/baselines.sh
+++ b/tests/lib/baselines.sh
@@ -177,7 +177,9 @@ shibumi_validate_omarchy_baseline_schema() {
or .id == "installed-package-v4.0.2"
or .id == "installed-source-parity-v4.0.2"
or .id == "forward-compat-ed7bae4a"
- or .id == "forward-compat-50d687a1" then
+ or .id == "forward-compat-50d687a1"
+ or .id == "forward-compat-e71a816e"
+ or .id == "forward-compat-4c1b4cb3" then
.quickshellPackage == {"name": "quickshell", "version": "0.3.1-1"}
elif .id == "installed-package-v4.0.0"
or .id == "installed-source-parity-v4.0.0" then
@@ -746,7 +748,7 @@ shibumi_load_omarchy_baseline() {
[[ -n $requested_path ]] \
|| shibumi_baseline_fail \
'OMARCHY_PATH is required for forward-compat' || return
- manifest="$shibumi_baseline_repo_root/contracts/baselines/omarchy-forward-compat-50d687a1.json"
+ manifest="$shibumi_baseline_repo_root/contracts/baselines/omarchy-forward-compat-4c1b4cb3.json"
;;
agents-current)
requested_path=${OMARCHY_PATH:-}
diff --git a/tests/panel-window-geometry-regression.sh b/tests/panel-window-geometry-regression.sh
index b9bf7d6f..d596a51f 100755
--- a/tests/panel-window-geometry-regression.sh
+++ b/tests/panel-window-geometry-regression.sh
@@ -16,8 +16,11 @@ fail() {
keyboard_panel="$OMARCHY_PATH/shell/Ui/KeyboardPanel.qml"
[[ -f $keyboard_panel ]] || fail 'pinned KeyboardPanel.qml is missing'
keyboard_panel_sha=96245f2da8d38baa0017caa285d596c485bd19a3a4d2cd1675bee9d84ffba42d
-if [[ $SHIBUMI_OMARCHY_SOURCE_REVISION == 50d687a1f27063513cf42e6f33c3a4fcb17c24ed ]]; then
+if [[ $SHIBUMI_OMARCHY_SOURCE_REVISION == 50d687a1f27063513cf42e6f33c3a4fcb17c24ed \
+ || $SHIBUMI_OMARCHY_SOURCE_REVISION == e71a816e9032f040225e086c7256f73e8080ae45 ]]; then
keyboard_panel_sha=e509278a51a7e9b45fa3a6fa630bedcd671a0f34202bfb7413a5b6a2f88d7e22
+elif [[ $SHIBUMI_OMARCHY_SOURCE_REVISION == 4c1b4cb3a94447aff39af98e2fcc0490a9fb32e7 ]]; then
+ keyboard_panel_sha=b7243c0397608a0a2a2dc2a3202a88c1e4b4b35d8da37895a1db8993422ce36f
fi
[[ $(sha256sum "$keyboard_panel" | awk '{print $1}') == "$keyboard_panel_sha" ]] \
|| fail 'KeyboardPanel.qml does not match pinned Omarchy source'
diff --git a/tests/quattro-contract-regression.sh b/tests/quattro-contract-regression.sh
index c980ad97..4865f7a8 100755
--- a/tests/quattro-contract-regression.sh
+++ b/tests/quattro-contract-regression.sh
@@ -104,7 +104,7 @@ for needle in \
done
case $SHIBUMI_OMARCHY_SOURCE_REVISION in
- 0534987009061cbe2dacdde4ad564092ab698d12|c668141e9c42b13c80c9ca4ea108e11708c5e8a5|50d687a1f27063513cf42e6f33c3a4fcb17c24ed)
+ 0534987009061cbe2dacdde4ad564092ab698d12|c668141e9c42b13c80c9ca4ea108e11708c5e8a5|50d687a1f27063513cf42e6f33c3a4fcb17c24ed|e71a816e9032f040225e086c7256f73e8080ae45|4c1b4cb3a94447aff39af98e2fcc0490a9fb32e7)
for needle in \
'target.shell = shell.pluginShellFor(manifest)' \
'target.manifest = shell.publicPluginManifest(manifest)' \
From 809015748c389912d1cdee803514e82aca7d2006 Mon Sep 17 00:00:00 2001
From: HANCORE-linux <230438592+HANCORE-linux@users.noreply.github.com>
Date: Tue, 13 Oct 2026 23:53:57 +0200
Subject: [PATCH 07/18] docs(release): align source installation and OPR
guidance
---
docs/development/packaging.md | 32 ++++++++++++++------------------
docs/development/release.md | 4 ++--
2 files changed, 16 insertions(+), 20 deletions(-)
diff --git a/docs/development/packaging.md b/docs/development/packaging.md
index aefa36f9..a741badc 100644
--- a/docs/development/packaging.md
+++ b/docs/development/packaging.md
@@ -1,7 +1,7 @@
-# Arch packaging and AUR publication
+# Source installation and planned OPR packaging
-Status: `0.1.1-beta.16.3` is the target GitHub prerelease; AUR publication
-is deferred.
+Status: install `0.1.1-beta.16.3` from source. OPR (omarchy-pkgs) packaging
+is planned; AUR publication is no longer the target.
Shibumi ships one versioned suite containing 24 separately validated Omarchy
Quattro plugin roots. Pacman owns the immutable program files; the Shibumi
@@ -49,13 +49,8 @@ NVIDIA telemetry; their absence does not prevent the core suite from starting.
## User lifecycle
-After AUR publication, the normal installation is one copyable command:
-
-```bash
-omarchy pkg aur add shibumi-shell && shibumi-shell install --yes
-```
-
-Package upgrades require an explicit staging step:
+Install from source using the [installation guide](../install.md).
+The planned package lifecycle requires an explicit staging step for upgrades:
```bash
omarchy update
@@ -144,7 +139,7 @@ A clean-chroot build must perform normal dependency resolution before publicatio
## Publication gates
-The AUR entry remains blocked until all of these are true:
+OPR publication remains blocked until all of these are true:
1. The exact candidate commit passes the source, package, and Health tests.
2. the validation system passes package install, source-to-package migration, update,
@@ -155,10 +150,11 @@ The AUR entry remains blocked until all of these are true:
package.
5. The immutable GitHub release asset exists and its SHA-256 equals PKGBUILD
and `.SRCINFO`.
-6. `scripts/check-aur-package --publish-check` passes against the remote tag
- and asset.
-7. The AUR package name can be registered and the one-command install is
- repeated from a clean supported Omarchy Quattro account.
-
-AUR unavailability blocks only gates 6–7. It does not block local package
-construction or the validation system lifecycle acceptance.
+6. OPR's package checks pass against the pinned release archive. The local
+ `scripts/check-aur-package --publish-check` validates the remote tag and
+ asset; it is not an OPR submission gate or an AUR publication target.
+7. The OPR package is accepted and installation is repeated from a clean
+ supported Omarchy Quattro account.
+
+OPR publication remains separate from local package construction and the
+validation system lifecycle acceptance.
diff --git a/docs/development/release.md b/docs/development/release.md
index 6a8a8161..c07e4f6b 100644
--- a/docs/development/release.md
+++ b/docs/development/release.md
@@ -24,8 +24,8 @@ permission to change a host; each operation needs separate maintainer approval.
5. Update user guides, architecture contracts, compatibility evidence, and
screenshot placeholders or captures affected by the release.
6. Confirm the repository visibility and remaining public-release blockers.
-7. Keep AUR publication behind the source, package, and clean-build gates in
- [packaging and AUR strategy](packaging.md).
+7. Keep planned OPR publication separate from source installation and behind
+ the source, package, and clean-build gates in [packaging](packaging.md).
8. On host baseline change, rerun every retained update arm before tagging;
do not silently drop an arm or choose an older rolling predecessor.
9. Re-pin forward compatibility to current `quattro` with `scripts/generate-forward-baseline.py`; update pins in `tests/lib/baselines.sh`, `tests/baseline-contract-regression.sh`, `tests/quattro-contract-regression.sh`, `tests/panel-window-geometry-regression.sh`, `scripts/collect-release-evidence`, `tests/documentation-regression.py`, `AGENTS.md`, `docs/release-readiness.md`, `docs/architecture/quattro-compatibility.md`, `docs/development/testing.md`, `docs/development/validation-host.md`, and `docs/development/release.md`, provision the clean runner checkout `~/.local/share/shibumi/validation-baselines/omarchy-forward-compat-`, and have the maintainer update Actions variable `SHIBUMI_FORWARD_COMPAT_OMARCHY_PATH`.
From fc3547603078db3d6a7b8a50cafac8940d876f00 Mon Sep 17 00:00:00 2001
From: HANCORE-linux <230438592+HANCORE-linux@users.noreply.github.com>
Date: Tue, 13 Oct 2026 23:53:57 +0200
Subject: [PATCH 08/18] refactor(embedding): share one host-widget embed helper
across plugins
---
contracts/backend-boundary-v1.json | 44 +++---
docs/architecture/shared-runtime-v1.md | 16 +-
hancore.shibumi.audio/AudioPanelBridge.qml | 22 +--
hancore.shibumi.audio/BarWidget.qml | 29 +---
hancore.shibumi.audio/HostWidgetEmbed.qml | 92 ++++++++++++
.../HostWidgetEmbed.qml | 92 ++++++++++++
.../MonitorPanelBridge.qml | 22 +--
hancore.shibumi.brightness/Service.qml | 44 ++----
hancore.shibumi.center/BarWidget.qml | 54 ++-----
hancore.shibumi.center/HostWidgetEmbed.qml | 92 ++++++++++++
hancore.shibumi.status/BarWidget.qml | 80 +++-------
hancore.shibumi.status/HostWidgetEmbed.qml | 92 ++++++++++++
scripts/check-production-boundary | 8 +-
scripts/sync-shared.sh | 5 +
shared/runtime/HostWidgetEmbed.qml | 92 ++++++++++++
tests/audio-media-plugins-regression.sh | 6 +-
tests/audio-plugin-smoke.qml | 8 +-
tests/bar-switch-regression.py | 103 +++++++++++++
tests/brightness-plugin-regression.sh | 8 +-
tests/center-plugin-regression.sh | 2 +-
tests/contract-regression.sh | 4 +-
tests/fixtures/BarSwitchHost.qml | 141 ++++++++++++++++++
tests/lib/isolated_process.py | 2 +-
tests/status-plugin-regression.sh | 6 +-
tests/status-plugin-smoke.qml | 6 +
25 files changed, 836 insertions(+), 234 deletions(-)
create mode 100644 hancore.shibumi.audio/HostWidgetEmbed.qml
create mode 100644 hancore.shibumi.brightness/HostWidgetEmbed.qml
create mode 100644 hancore.shibumi.center/HostWidgetEmbed.qml
create mode 100644 hancore.shibumi.status/HostWidgetEmbed.qml
create mode 100644 shared/runtime/HostWidgetEmbed.qml
create mode 100644 tests/bar-switch-regression.py
create mode 100644 tests/fixtures/BarSwitchHost.qml
diff --git a/contracts/backend-boundary-v1.json b/contracts/backend-boundary-v1.json
index 9de84f35..9d063eb2 100644
--- a/contracts/backend-boundary-v1.json
+++ b/contracts/backend-boundary-v1.json
@@ -302,11 +302,12 @@
{"source":"hancore.shibumi.ai/Service.qml","kind":"token","value":"entryPointUrl","count":2,"category":"hostIntegration","owner":"hancore.shibumi.ai","capability":"ai-usage"},
{"source":"hancore.shibumi.ai/Service.qml","kind":"token","value":"registeredWidgetSource","count":2,"category":"hostIntegration","owner":"hancore.shibumi.ai","capability":"ai-usage"},
{"source":"hancore.shibumi.ai/Service.qml","kind":"token","value":"standardWidgetSource","count":3,"category":"hostIntegration","owner":"hancore.shibumi.ai","capability":"ai-usage"},
- {"source":"hancore.shibumi.audio/BarWidget.qml","kind":"token","value":"entryPointUrl","count":2,"category":"hostIntegration","owner":"hancore.shibumi.audio","capability":"audio"},
- {"source":"hancore.shibumi.audio/BarWidget.qml","kind":"token","value":"registeredComponent","count":2,"category":"hostIntegration","owner":"hancore.shibumi.audio","capability":"audio"},
- {"source":"hancore.shibumi.audio/BarWidget.qml","kind":"token","value":"registeredSource","count":2,"category":"hostIntegration","owner":"hancore.shibumi.audio","capability":"audio"},
- {"source":"hancore.shibumi.audio/BarWidget.qml","kind":"token","value":"registeredWidgetComponent","count":2,"category":"hostIntegration","owner":"hancore.shibumi.audio","capability":"audio"},
- {"source":"hancore.shibumi.audio/BarWidget.qml","kind":"token","value":"registeredWidgetSource","count":2,"category":"hostIntegration","owner":"hancore.shibumi.audio","capability":"audio"},
+ {"source":"hancore.shibumi.audio/BarWidget.qml","kind":"token","value":"embed.resolveComponent","count":1,"category":"hostIntegration","owner":"hancore.shibumi.audio","capability":"audio"},
+ {"source":"hancore.shibumi.audio/BarWidget.qml","kind":"token","value":"embed.resolveSource","count":1,"category":"hostIntegration","owner":"hancore.shibumi.audio","capability":"audio"},
+ {"source":"hancore.shibumi.audio/AudioPanelBridge.qml","kind":"token","value":"embed.syncLoader","count":1,"category":"hostIntegration","owner":"hancore.shibumi.audio","capability":"audio"},
+ {"source":"hancore.shibumi.audio/HostWidgetEmbed.qml","kind":"token","value":"entryPointUrl","count":2,"category":"hostIntegration","owner":"hancore.shibumi.audio","capability":"audio"},
+ {"source":"hancore.shibumi.audio/HostWidgetEmbed.qml","kind":"token","value":"registeredWidgetComponent","count":2,"category":"hostIntegration","owner":"hancore.shibumi.audio","capability":"audio"},
+ {"source":"hancore.shibumi.audio/HostWidgetEmbed.qml","kind":"token","value":"registeredWidgetSource","count":2,"category":"hostIntegration","owner":"hancore.shibumi.audio","capability":"audio"},
{"source":"hancore.shibumi.bar/Bar.qml","kind":"token","value":"entryPointUrl","count":3,"category":"hostIntegration","owner":"hancore.shibumi.bar","capability":"bar-host-routing"},
{"source":"hancore.shibumi.bar/Bar.qml","kind":"token","value":"firstPartyServiceFor","count":2,"category":"hostIntegration","owner":"hancore.shibumi.bar","capability":"bar-host-routing"},
{"source":"hancore.shibumi.bar/Bar.qml","kind":"token","value":"registeredWidgetComponent","count":2,"category":"hostIntegration","owner":"hancore.shibumi.bar","capability":"bar-host-routing"},
@@ -314,27 +315,30 @@
{"source":"hancore.shibumi.bar/core/WidgetSlot.qml","kind":"token","value":"registeredWidgetComponent","count":6,"category":"hostIntegration","owner":"hancore.shibumi.bar","capability":"bar-host-routing"},
{"source":"hancore.shibumi.bar/services/HostWidgetResolver.qml","kind":"token","value":"Qt.createComponent","count":1,"category":"hostIntegration","owner":"hancore.shibumi.bar","capability":"bar-host-routing"},
{"source":"hancore.shibumi.bar/services/HostWidgetResolver.qml","kind":"token","value":"entryPointUrl","count":6,"category":"hostIntegration","owner":"hancore.shibumi.bar","capability":"bar-host-routing"},
- {"source":"hancore.shibumi.brightness/Service.qml","kind":"token","value":"entryPointUrl","count":2,"category":"hostIntegration","owner":"hancore.shibumi.brightness","capability":"displays"},
- {"source":"hancore.shibumi.brightness/Service.qml","kind":"token","value":"registeredComponent","count":2,"category":"hostIntegration","owner":"hancore.shibumi.brightness","capability":"displays"},
- {"source":"hancore.shibumi.brightness/Service.qml","kind":"token","value":"registeredSource","count":2,"category":"hostIntegration","owner":"hancore.shibumi.brightness","capability":"displays"},
- {"source":"hancore.shibumi.brightness/Service.qml","kind":"token","value":"registeredWidgetComponent","count":2,"category":"hostIntegration","owner":"hancore.shibumi.brightness","capability":"displays"},
- {"source":"hancore.shibumi.brightness/Service.qml","kind":"token","value":"registeredWidgetSource","count":2,"category":"hostIntegration","owner":"hancore.shibumi.brightness","capability":"displays"},
- {"source":"hancore.shibumi.center/BarWidget.qml","kind":"token","value":"entryPointUrl","count":2,"category":"hostIntegration","owner":"hancore.shibumi.center","capability":"weather"},
- {"source":"hancore.shibumi.center/BarWidget.qml","kind":"token","value":"registeredComponent","count":2,"category":"hostIntegration","owner":"hancore.shibumi.center","capability":"weather"},
- {"source":"hancore.shibumi.center/BarWidget.qml","kind":"token","value":"registeredSource","count":2,"category":"hostIntegration","owner":"hancore.shibumi.center","capability":"weather"},
- {"source":"hancore.shibumi.center/BarWidget.qml","kind":"token","value":"registeredWidgetComponent","count":2,"category":"hostIntegration","owner":"hancore.shibumi.center","capability":"weather"},
- {"source":"hancore.shibumi.center/BarWidget.qml","kind":"token","value":"registeredWidgetSource","count":2,"category":"hostIntegration","owner":"hancore.shibumi.center","capability":"weather"},
+ {"source":"hancore.shibumi.brightness/Service.qml","kind":"token","value":"embed.resolveComponent","count":1,"category":"hostIntegration","owner":"hancore.shibumi.brightness","capability":"displays"},
+ {"source":"hancore.shibumi.brightness/Service.qml","kind":"token","value":"embed.resolveSource","count":1,"category":"hostIntegration","owner":"hancore.shibumi.brightness","capability":"displays"},
+ {"source":"hancore.shibumi.brightness/MonitorPanelBridge.qml","kind":"token","value":"embed.syncLoader","count":1,"category":"hostIntegration","owner":"hancore.shibumi.brightness","capability":"displays"},
+ {"source":"hancore.shibumi.brightness/HostWidgetEmbed.qml","kind":"token","value":"entryPointUrl","count":2,"category":"hostIntegration","owner":"hancore.shibumi.brightness","capability":"displays"},
+ {"source":"hancore.shibumi.brightness/HostWidgetEmbed.qml","kind":"token","value":"registeredWidgetComponent","count":2,"category":"hostIntegration","owner":"hancore.shibumi.brightness","capability":"displays"},
+ {"source":"hancore.shibumi.brightness/HostWidgetEmbed.qml","kind":"token","value":"registeredWidgetSource","count":2,"category":"hostIntegration","owner":"hancore.shibumi.brightness","capability":"displays"},
+ {"source":"hancore.shibumi.center/BarWidget.qml","kind":"token","value":"embed.resolveComponent","count":1,"category":"hostIntegration","owner":"hancore.shibumi.center","capability":"weather"},
+ {"source":"hancore.shibumi.center/BarWidget.qml","kind":"token","value":"embed.resolveSource","count":1,"category":"hostIntegration","owner":"hancore.shibumi.center","capability":"weather"},
+ {"source":"hancore.shibumi.center/BarWidget.qml","kind":"token","value":"embed.syncLoader","count":1,"category":"hostIntegration","owner":"hancore.shibumi.center","capability":"weather"},
+ {"source":"hancore.shibumi.center/HostWidgetEmbed.qml","kind":"token","value":"entryPointUrl","count":2,"category":"hostIntegration","owner":"hancore.shibumi.center","capability":"weather"},
+ {"source":"hancore.shibumi.center/HostWidgetEmbed.qml","kind":"token","value":"registeredWidgetComponent","count":2,"category":"hostIntegration","owner":"hancore.shibumi.center","capability":"weather"},
+ {"source":"hancore.shibumi.center/HostWidgetEmbed.qml","kind":"token","value":"registeredWidgetSource","count":2,"category":"hostIntegration","owner":"hancore.shibumi.center","capability":"weather"},
{"source":"hancore.shibumi.center/SystemUpdateWidget.qml","kind":"token","value":"firstPartyServiceFor","count":2,"category":"hostIntegration","owner":"hancore.shibumi.center","capability":"system-update-state"},
{"source":"hancore.shibumi.media/BarWidget.qml","kind":"token","value":"firstPartyServiceFor","count":1,"category":"hostIntegration","owner":"hancore.shibumi.media","capability":"media"},
{"source":"hancore.shibumi.media/Service.qml","kind":"token","value":"firstPartyServiceFor","count":1,"category":"hostIntegration","owner":"hancore.shibumi.media","capability":"media"},
{"source":"hancore.shibumi.reactor/ReactorService.qml","kind":"token","value":"firstPartyService","count":2,"category":"hostIntegration","owner":"hancore.shibumi.reactor","capability":"bar-host-routing"},
{"source":"hancore.shibumi.reactor/ReactorService.qml","kind":"token","value":"firstPartyServiceFor","count":2,"category":"hostIntegration","owner":"hancore.shibumi.reactor","capability":"bar-host-routing"},
- {"source":"hancore.shibumi.status/BarWidget.qml","kind":"token","value":"entryPointUrl","count":2,"category":"hostIntegration","owner":"hancore.shibumi.status","capability":"system-tray"},
{"source":"hancore.shibumi.status/BarWidget.qml","kind":"token","value":"firstPartyServiceFor","count":3,"category":"hostIntegration","owner":"hancore.shibumi.status","capability":"system-tray"},
- {"source":"hancore.shibumi.status/BarWidget.qml","kind":"token","value":"registeredComponent","count":3,"category":"hostIntegration","owner":"hancore.shibumi.status","capability":"system-tray"},
- {"source":"hancore.shibumi.status/BarWidget.qml","kind":"token","value":"registeredSource","count":3,"category":"hostIntegration","owner":"hancore.shibumi.status","capability":"system-tray"},
- {"source":"hancore.shibumi.status/BarWidget.qml","kind":"token","value":"registeredWidgetComponent","count":2,"category":"hostIntegration","owner":"hancore.shibumi.status","capability":"system-tray"},
- {"source":"hancore.shibumi.status/BarWidget.qml","kind":"token","value":"registeredWidgetSource","count":2,"category":"hostIntegration","owner":"hancore.shibumi.status","capability":"system-tray"},
+ {"source":"hancore.shibumi.status/BarWidget.qml","kind":"token","value":"embed.resolveComponent","count":2,"category":"hostIntegration","owner":"hancore.shibumi.status","capability":"system-tray"},
+ {"source":"hancore.shibumi.status/BarWidget.qml","kind":"token","value":"embed.resolveSource","count":2,"category":"hostIntegration","owner":"hancore.shibumi.status","capability":"system-tray"},
+ {"source":"hancore.shibumi.status/BarWidget.qml","kind":"token","value":"embed.syncLoader","count":2,"category":"hostIntegration","owner":"hancore.shibumi.status","capability":"system-tray"},
+ {"source":"hancore.shibumi.status/HostWidgetEmbed.qml","kind":"token","value":"entryPointUrl","count":2,"category":"hostIntegration","owner":"hancore.shibumi.status","capability":"system-tray"},
+ {"source":"hancore.shibumi.status/HostWidgetEmbed.qml","kind":"token","value":"registeredWidgetComponent","count":2,"category":"hostIntegration","owner":"hancore.shibumi.status","capability":"system-tray"},
+ {"source":"hancore.shibumi.status/HostWidgetEmbed.qml","kind":"token","value":"registeredWidgetSource","count":2,"category":"hostIntegration","owner":"hancore.shibumi.status","capability":"system-tray"},
{"source":"hancore.shibumi.state/runtime/HostShell.qml","kind":"token","value":"firstPartyServiceFor","count":4,"category":"hostIntegration","owner":"hancore.shibumi.state","capability":"bar-host-routing"},
{"source":"hancore.shibumi.state/runtime/Runtime.qml","kind":"token","value":"firstPartyServiceFor","count":3,"category":"hostIntegration","owner":"hancore.shibumi.state","capability":"bar-host-routing"},
{"source":"hancore.shibumi.status/NotificationAdapter.qml","kind":"token","value":"firstPartyServiceFor","count":2,"category":"hostIntegration","owner":"hancore.shibumi.status","capability":"notifications"},
diff --git a/docs/architecture/shared-runtime-v1.md b/docs/architecture/shared-runtime-v1.md
index 12542af0..c384f929 100644
--- a/docs/architecture/shared-runtime-v1.md
+++ b/docs/architecture/shared-runtime-v1.md
@@ -17,11 +17,17 @@ components: no service, Process, Timer, poller, worker, backend, persistence,
singleton, or mutable runtime authority. Every presentation consumer declares
a direct State dependency, and State precedes those consumers in suite order.
-`ShibumiPanel.qml` is not passive because it retains three focus/popout
-lifecycle timers. It therefore remains the sole canonical source under
-`shared/presentation/`; `scripts/sync-shared.sh` continues to drift-check its
-existing plugin and `widgets/` copies. Bar-host and feature-owner sources
-otherwise live directly in their plugin roots.
+`ShibumiPanel.qml` retains its three focus/popout lifecycle timers and checked
+copies from `shared/presentation/`. `scripts/sync-shared.sh` also mirrors
+`shared/runtime/HostWidgetEmbed.qml` into Center, Status, Audio and Brightness.
+This local helper type-checks host handles without typed intermediate storage,
+preserves resolver refusals and resident children for the same owner/handle/URL;
+a real owner change revokes and reloads. Shutdown destroys children while their
+injected facade is still valid, then clears the loader records. Submission retries are bounded to ten
+existing consumer wakeups; it adds no timer or deferred feedback path. The
+proxies remain local, and Monitor's visual lease does not replace its backend
+owner. These separately registered plugins still require the complete admitted
+suite; this is checked vendoring, not another cross-plugin import exception.
Installation, update, repair and removal continue to operate on the complete
admitted suite. This dependency does not grant permission to weaken admission,
load a partial mixed-version set, or import arbitrary sibling/repository paths.
diff --git a/hancore.shibumi.audio/AudioPanelBridge.qml b/hancore.shibumi.audio/AudioPanelBridge.qml
index f94e9e31..5c4cbe43 100644
--- a/hancore.shibumi.audio/AudioPanelBridge.qml
+++ b/hancore.shibumi.audio/AudioPanelBridge.qml
@@ -13,7 +13,8 @@ Item {
required property var bar
required property Item ownerWidget
- property Component panelComponent: null
+ property var panelComponent: null
+ HostWidgetEmbed { id: embed }
property url panelSource: ""
property var panelSettings: ({})
property var backendReadyOverride: null
@@ -568,25 +569,17 @@ Item {
}
function injectPanel() {
- if (!panel.item) return
+ if (!root || embed.shuttingDown || !panel.item) return
// The official button and popup remain instantiated for their backend
// state, but Shibumi owns the only visible presentation and click target.
panel.inject(hostProxy, panelSettings)
}
function syncPanelSource() {
- panelLoader.sourceComponent = null
- panelLoader.source = ""
- if (panelComponent !== null) {
- panelLoader.sourceComponent = panelComponent
- } else if (String(panelSource)) {
- panelLoader.setSource(panelSource, {
- bar: hostProxy,
- moduleName: "omarchy.audio",
- manageIpc: false,
- settings: panelSettings
- })
- }
+ if (!root || embed.shuttingDown) return
+ embed.syncLoader(panelLoader, panelComponent, panelSource, {
+ bar: hostProxy, moduleName: "omarchy.audio", manageIpc: false, settings: panelSettings
+ }, ownerWidget)
}
onPanelSettingsChanged: injectPanel()
@@ -599,6 +592,7 @@ Item {
clearPeakLease()
panel.close()
// Destroy the official panel while its host facade is still valid.
+ embed.shutdown()
panelLoader.active = false
}
diff --git a/hancore.shibumi.audio/BarWidget.qml b/hancore.shibumi.audio/BarWidget.qml
index cd80d445..82d9d036 100644
--- a/hancore.shibumi.audio/BarWidget.qml
+++ b/hancore.shibumi.audio/BarWidget.qml
@@ -13,10 +13,11 @@ Ui.Panel {
manageIpc: false
Presentation.HostTokens { id: hostTokens; bar: root.bar; serviceShell: suiteShell }
property url popupSource: Qt.resolvedUrl("AudioPanel.qml")
- readonly property url backendPanelSource: registeredSource("omarchy.audio")
- property Component panelComponent: !audioServiceResolved
+ HostWidgetEmbed { id: embed; bar: root.bar }
+ readonly property url backendPanelSource: embed.resolveSource("omarchy.audio")
+ property var panelComponent: !audioServiceResolved
|| nativeBackendAccessEnabled ? null
- : String(backendPanelSource) ? null : registeredComponent("omarchy.audio")
+ : String(backendPanelSource) ? null : embed.resolveComponent("", "omarchy.audio")
property var backendReadyOverride: null
SuiteRuntime.HostShell { id: suiteShell; host: root.bar ? root.bar.shell : null }
property var leasedReportService: null
@@ -71,27 +72,6 @@ Ui.Panel {
return value === undefined || value === null ? fallback : value
}
- function registeredComponent(id) {
- if (bar && typeof bar.registeredWidgetComponent === "function")
- return bar.registeredWidgetComponent(id)
- const registry = bar ? bar.barWidgetRegistry : null
- if (registry) void(registry.revision)
- const widgets = registry && registry.widgets ? registry.widgets : ({})
- const entry = widgets[id]
- return entry ? entry.component : null
- }
-
- function registeredSource(id) {
- if (bar && typeof bar.registeredWidgetSource === "function")
- return bar.registeredWidgetSource(id)
- const registry = bar && bar.shell
- && "pluginRegistry" in bar.shell ? bar.shell.pluginRegistry : null
- const pluginManifest = registry && registry.installedPlugins
- ? registry.installedPlugins[String(id || "")] : null
- return registry && typeof registry.entryPointUrl === "function"
- ? registry.entryPointUrl(pluginManifest, "barWidget") : ""
- }
-
function officialSettings() {
return bar && typeof bar.widgetSettings === "function"
? bar.widgetSettings("G6", "omarchy.audio") : ({})
@@ -229,6 +209,7 @@ Ui.Panel {
Connections {
target: audioBridge
function onOutputVolumeChanged() {
+ if (!root) return
if (!root.wheelAdjustmentPending || !root.wheelCommitInFlight
|| root.wheelInFlightSerial !== root.wheelCommitSerial)
return
diff --git a/hancore.shibumi.audio/HostWidgetEmbed.qml b/hancore.shibumi.audio/HostWidgetEmbed.qml
new file mode 100644
index 00000000..3013e386
--- /dev/null
+++ b/hancore.shibumi.audio/HostWidgetEmbed.qml
@@ -0,0 +1,92 @@
+import QtQuick
+
+// Local host handles stay untyped until Loader's setter. Scheduling remains
+// with the consumer: no new timer, polling, or callLater feedback path.
+QtObject {
+ id: root
+ property var bar: null
+ property var shell: null
+ property var registry: null
+ property bool registryOnly: false
+ property bool shuttingDown: false
+ property var loads: []
+
+ function componentHandle(value) {
+ try { return value && value instanceof Component ? value : null }
+ catch (error) { return null }
+ }
+
+ function registryComponent(view, id) {
+ if (view) void(view.revision)
+ const entry = view && view.widgets ? view.widgets[String(id || "")] : null
+ return componentHandle(entry ? entry.component : null)
+ }
+
+ function resolveComponent(owner, id) {
+ // An explicitly injected service snapshot precedes legacy Bar lookups.
+ const injected = registryComponent(registry, id)
+ if (injected || registryOnly) return injected
+ if (bar) {
+ if (owner && typeof bar.registeredEmbeddedWidgetComponent === "function")
+ return componentHandle(bar.registeredEmbeddedWidgetComponent(owner, id))
+ if (typeof bar.registeredWidgetComponent === "function")
+ return componentHandle(bar.registeredWidgetComponent(id))
+ }
+ return registryComponent(bar && "barWidgetRegistry" in bar ? bar.barWidgetRegistry : null, id)
+ }
+
+ function resolveSource(id) {
+ if (registryOnly) return ""
+ if (bar && typeof bar.registeredWidgetSource === "function")
+ return bar.registeredWidgetSource(id)
+ const host = shell || (bar && "shell" in bar ? bar.shell : null)
+ const view = host && "pluginRegistry" in host ? host.pluginRegistry : null
+ const manifest = view && view.installedPlugins ? view.installedPlugins[String(id || "")] : null
+ return view && typeof view.entryPointUrl === "function" ? view.entryPointUrl(manifest, "barWidget") : ""
+ }
+
+ function clearLoader(loader) {
+ if (loader.item && "bar" in loader.item) loader.item.bar = null
+ loader.sourceComponent = null
+ loader.source = ""
+ }
+
+ function revoke() {
+ const previous = loads
+ loads = []
+ for (const record of previous) clearLoader(record.loader)
+ }
+
+ function shutdown() {
+ shuttingDown = true
+ // Destroy children while their injected host facade is still valid.
+ for (const record of loads)
+ if (record.loader.item !== null) record.loader.active = false
+ revoke()
+ }
+
+ function syncLoader(loader, handle, source, args, owner) {
+ if (shuttingDown || !loader) return false
+ const component = componentHandle(handle)
+ const url = component ? "" : String(source || "")
+ let record = loads.find(value => value.loader === loader)
+ if (!record || record.owner !== owner || record.component !== component || record.url !== url) {
+ loads = loads.filter(value => value.loader !== loader)
+ clearLoader(loader)
+ record = {loader: loader, owner: owner, component: component, url: url, attempts: 0}
+ loads = loads.concat([record])
+ }
+ if (!owner || (!component && !url)) return false
+ if (loader.item !== null) return true
+ // Reuse only existing consumer wakeups. Exhaustion cannot create a loop;
+ // a new owner or source begins a new bounded submission sequence.
+ if (loader.status === Loader.Loading || record.attempts >= 10) return false
+ record.attempts++
+ clearLoader(loader)
+ if (shuttingDown || loads.indexOf(record) < 0) return false
+ if (component) loader.sourceComponent = component
+ else loader.setSource(url, args)
+ return loader.item !== null
+ }
+
+}
diff --git a/hancore.shibumi.brightness/HostWidgetEmbed.qml b/hancore.shibumi.brightness/HostWidgetEmbed.qml
new file mode 100644
index 00000000..3013e386
--- /dev/null
+++ b/hancore.shibumi.brightness/HostWidgetEmbed.qml
@@ -0,0 +1,92 @@
+import QtQuick
+
+// Local host handles stay untyped until Loader's setter. Scheduling remains
+// with the consumer: no new timer, polling, or callLater feedback path.
+QtObject {
+ id: root
+ property var bar: null
+ property var shell: null
+ property var registry: null
+ property bool registryOnly: false
+ property bool shuttingDown: false
+ property var loads: []
+
+ function componentHandle(value) {
+ try { return value && value instanceof Component ? value : null }
+ catch (error) { return null }
+ }
+
+ function registryComponent(view, id) {
+ if (view) void(view.revision)
+ const entry = view && view.widgets ? view.widgets[String(id || "")] : null
+ return componentHandle(entry ? entry.component : null)
+ }
+
+ function resolveComponent(owner, id) {
+ // An explicitly injected service snapshot precedes legacy Bar lookups.
+ const injected = registryComponent(registry, id)
+ if (injected || registryOnly) return injected
+ if (bar) {
+ if (owner && typeof bar.registeredEmbeddedWidgetComponent === "function")
+ return componentHandle(bar.registeredEmbeddedWidgetComponent(owner, id))
+ if (typeof bar.registeredWidgetComponent === "function")
+ return componentHandle(bar.registeredWidgetComponent(id))
+ }
+ return registryComponent(bar && "barWidgetRegistry" in bar ? bar.barWidgetRegistry : null, id)
+ }
+
+ function resolveSource(id) {
+ if (registryOnly) return ""
+ if (bar && typeof bar.registeredWidgetSource === "function")
+ return bar.registeredWidgetSource(id)
+ const host = shell || (bar && "shell" in bar ? bar.shell : null)
+ const view = host && "pluginRegistry" in host ? host.pluginRegistry : null
+ const manifest = view && view.installedPlugins ? view.installedPlugins[String(id || "")] : null
+ return view && typeof view.entryPointUrl === "function" ? view.entryPointUrl(manifest, "barWidget") : ""
+ }
+
+ function clearLoader(loader) {
+ if (loader.item && "bar" in loader.item) loader.item.bar = null
+ loader.sourceComponent = null
+ loader.source = ""
+ }
+
+ function revoke() {
+ const previous = loads
+ loads = []
+ for (const record of previous) clearLoader(record.loader)
+ }
+
+ function shutdown() {
+ shuttingDown = true
+ // Destroy children while their injected host facade is still valid.
+ for (const record of loads)
+ if (record.loader.item !== null) record.loader.active = false
+ revoke()
+ }
+
+ function syncLoader(loader, handle, source, args, owner) {
+ if (shuttingDown || !loader) return false
+ const component = componentHandle(handle)
+ const url = component ? "" : String(source || "")
+ let record = loads.find(value => value.loader === loader)
+ if (!record || record.owner !== owner || record.component !== component || record.url !== url) {
+ loads = loads.filter(value => value.loader !== loader)
+ clearLoader(loader)
+ record = {loader: loader, owner: owner, component: component, url: url, attempts: 0}
+ loads = loads.concat([record])
+ }
+ if (!owner || (!component && !url)) return false
+ if (loader.item !== null) return true
+ // Reuse only existing consumer wakeups. Exhaustion cannot create a loop;
+ // a new owner or source begins a new bounded submission sequence.
+ if (loader.status === Loader.Loading || record.attempts >= 10) return false
+ record.attempts++
+ clearLoader(loader)
+ if (shuttingDown || loads.indexOf(record) < 0) return false
+ if (component) loader.sourceComponent = component
+ else loader.setSource(url, args)
+ return loader.item !== null
+ }
+
+}
diff --git a/hancore.shibumi.brightness/MonitorPanelBridge.qml b/hancore.shibumi.brightness/MonitorPanelBridge.qml
index 79fe57ac..81caf220 100644
--- a/hancore.shibumi.brightness/MonitorPanelBridge.qml
+++ b/hancore.shibumi.brightness/MonitorPanelBridge.qml
@@ -11,7 +11,8 @@ Item {
required property var bar
required property var ownerShell
required property Item ownerWidget
- property Component panelComponent: null
+ property var panelComponent: null
+ HostWidgetEmbed { id: embed }
property url panelSource: ""
property var panelSettings: ({})
property bool shuttingDown: false
@@ -141,18 +142,9 @@ Item {
function syncPanelSource() {
if (shuttingDown) return
- panelLoader.sourceComponent = null
- panelLoader.source = ""
- if (panelComponent !== null) {
- panelLoader.sourceComponent = panelComponent
- } else if (String(panelSource)) {
- panelLoader.setSource(panelSource, {
- bar: hostProxy,
- moduleName: "omarchy.monitor",
- manageIpc: false,
- settings: panelSettings
- })
- }
+ embed.syncLoader(panelLoader, panelComponent, panelSource, {
+ bar: hostProxy, moduleName: "omarchy.monitor", manageIpc: false, settings: panelSettings
+ }, ownerShell || ownerWidget)
}
function schedulePanelSync() {
@@ -167,13 +159,13 @@ Item {
hiddenClose.stop()
if (panel && panel.opened === true && typeof panel.close === "function")
panel.close()
+ embed.shutdown()
panelLoader.active = false
- panelLoader.sourceComponent = null
- panelLoader.source = ""
}
onPanelSettingsChanged: injectPanel()
onBarChanged: injectPanel()
+ onOwnerShellChanged: { embed.revoke(); schedulePanelSync() }
onPanelComponentChanged: schedulePanelSync()
onPanelSourceChanged: schedulePanelSync()
Component.onCompleted: schedulePanelSync()
diff --git a/hancore.shibumi.brightness/Service.qml b/hancore.shibumi.brightness/Service.qml
index 1ccf8a57..461e83d1 100644
--- a/hancore.shibumi.brightness/Service.qml
+++ b/hancore.shibumi.brightness/Service.qml
@@ -33,10 +33,16 @@ Item {
return selected
}
readonly property bool scopedHost: Qt.isQtObject(shell) && "pluginId" in shell
- readonly property url panelSource: scopedHost ? ""
- : registeredSource("omarchy.monitor")
- property Component panelComponent: String(panelSource) ? null
- : registeredComponent("omarchy.monitor")
+ HostWidgetEmbed {
+ id: embed
+ bar: root.scopedHost ? null : root.bar
+ shell: root.shell
+ registry: root.barWidgetRegistry
+ registryOnly: root.scopedHost
+ }
+ readonly property url panelSource: embed.resolveSource("omarchy.monitor")
+ property var panelComponent: String(panelSource) ? null
+ : embed.resolveComponent("", "omarchy.monitor")
readonly property var backend: bridge.panel
readonly property bool ready: bridge.ready
@@ -100,36 +106,6 @@ Item {
})
}
- function registeredSource(id) {
- if (bar && typeof bar.registeredWidgetSource === "function")
- return bar.registeredWidgetSource(id)
- const registry = Qt.isQtObject(shell) && "pluginRegistry" in shell
- ? shell.pluginRegistry : null
- const pluginManifest = registry && registry.installedPlugins
- ? registry.installedPlugins[String(id || "")] : null
- return registry && typeof registry.entryPointUrl === "function"
- ? registry.entryPointUrl(pluginManifest, "barWidget") : ""
- }
-
- function registeredComponent(id) {
- // Scoped 4.0.3 services receive the accepted public component snapshot
- // directly; the scalar shell.bar object is intentionally not a visual Item.
- const injected = barWidgetRegistry
- if (injected) void(injected.revision)
- const injectedWidgets = injected && injected.widgets ? injected.widgets : ({})
- const injectedEntry = injectedWidgets[String(id || "")]
- if (injectedEntry && injectedEntry.component) return injectedEntry.component
- if (scopedHost) return null
- if (bar && typeof bar.registeredWidgetComponent === "function")
- return bar.registeredWidgetComponent(id)
- const registry = bar && "barWidgetRegistry" in bar
- ? bar.barWidgetRegistry : null
- if (registry) void(registry.revision)
- const widgets = registry && registry.widgets ? registry.widgets : ({})
- const entry = widgets[String(id || "")]
- return entry && entry.component ? entry.component : null
- }
-
visible: false
width: 0
height: 0
diff --git a/hancore.shibumi.center/BarWidget.qml b/hancore.shibumi.center/BarWidget.qml
index 621c06b8..dc0a61da 100644
--- a/hancore.shibumi.center/BarWidget.qml
+++ b/hancore.shibumi.center/BarWidget.qml
@@ -13,6 +13,7 @@ Ui.Panel {
moduleName: "hancore.shibumi.center"
manageIpc: false
Presentation.HostTokens { id: hostTokens; bar: root.bar }
+ HostWidgetEmbed { id: embed; bar: root.bar }
property url calendarSource: Qt.resolvedUrl("CalendarPanel.qml")
property real availableWidth: 0
property var responsiveWidthProvider: null
@@ -78,9 +79,9 @@ Ui.Panel {
readonly property var weatherSettings: childSettings("omarchy.weather")
readonly property var clockSettings: childSettings("omarchy.clock")
readonly property var updateSettings: childSettings("omarchy.system-update")
- readonly property url updateSource: registeredSource("omarchy.system-update")
+ readonly property url updateSource: embed.resolveSource("omarchy.system-update")
property var updateComponent: String(updateSource) ? null
- : registeredComponent("omarchy.system-update")
+ : embed.resolveComponent("hancore.shibumi.center", "omarchy.system-update")
implicitWidth: bar && bar.vertical ? bar.barSize
: Math.round(centerRow.implicitWidth) + 2 * centerPadding
@@ -93,47 +94,10 @@ Ui.Panel {
? settings[id] : ({})
}
- function registeredComponent(id) {
- if (bar && typeof bar.registeredEmbeddedWidgetComponent === "function")
- return bar.registeredEmbeddedWidgetComponent(
- "hancore.shibumi.center", id)
- if (bar && typeof bar.registeredWidgetComponent === "function")
- return bar.registeredWidgetComponent(id)
- const registry = bar ? bar.barWidgetRegistry : null
- if (registry) void(registry.revision)
- const widgets = registry && registry.widgets ? registry.widgets : ({})
- const entry = widgets[id]
- return entry ? entry.component : null
- }
-
- function registeredSource(id) {
- if (bar && typeof bar.registeredWidgetSource === "function")
- return bar.registeredWidgetSource(id)
- const registry = bar && bar.shell
- && "pluginRegistry" in bar.shell ? bar.shell.pluginRegistry : null
- const pluginManifest = registry && registry.installedPlugins
- ? registry.installedPlugins[String(id || "")] : null
- return registry && typeof registry.entryPointUrl === "function"
- ? registry.entryPointUrl(pluginManifest, "barWidget") : ""
- }
-
- function syncOfficialLoader(loader, component, source, id, childSettingsValue) {
- loader.sourceComponent = null
- loader.source = ""
- if (component !== null) {
- loader.sourceComponent = component
- } else if (String(source)) {
- loader.setSource(source, {
- bar: root.bar,
- moduleName: id,
- settings: childSettingsValue
- })
- }
- }
-
function syncUpdateLoader() {
- syncOfficialLoader(updateLoader, updateComponent, updateSource,
- "omarchy.system-update", updateSettings)
+ embed.syncLoader(updateLoader, updateComponent, updateSource, {
+ bar: root.bar, moduleName: "omarchy.system-update", settings: updateSettings
+ }, root.bar)
}
function injectChild(item, id, childSettingsValue) {
@@ -148,7 +112,7 @@ Ui.Panel {
}
function scheduleOfficialSync() {
- officialSyncTimer.restart()
+ if (root && !embed.shuttingDown) officialSyncTimer.restart()
}
function childPanelWidget(pluginId) {
@@ -196,6 +160,10 @@ Ui.Panel {
restageTimer.restart()
scheduleOfficialSync()
}
+ Component.onDestruction: {
+ officialSyncTimer.stop()
+ embed.shutdown()
+ }
Timer {
id: restageTimer
diff --git a/hancore.shibumi.center/HostWidgetEmbed.qml b/hancore.shibumi.center/HostWidgetEmbed.qml
new file mode 100644
index 00000000..3013e386
--- /dev/null
+++ b/hancore.shibumi.center/HostWidgetEmbed.qml
@@ -0,0 +1,92 @@
+import QtQuick
+
+// Local host handles stay untyped until Loader's setter. Scheduling remains
+// with the consumer: no new timer, polling, or callLater feedback path.
+QtObject {
+ id: root
+ property var bar: null
+ property var shell: null
+ property var registry: null
+ property bool registryOnly: false
+ property bool shuttingDown: false
+ property var loads: []
+
+ function componentHandle(value) {
+ try { return value && value instanceof Component ? value : null }
+ catch (error) { return null }
+ }
+
+ function registryComponent(view, id) {
+ if (view) void(view.revision)
+ const entry = view && view.widgets ? view.widgets[String(id || "")] : null
+ return componentHandle(entry ? entry.component : null)
+ }
+
+ function resolveComponent(owner, id) {
+ // An explicitly injected service snapshot precedes legacy Bar lookups.
+ const injected = registryComponent(registry, id)
+ if (injected || registryOnly) return injected
+ if (bar) {
+ if (owner && typeof bar.registeredEmbeddedWidgetComponent === "function")
+ return componentHandle(bar.registeredEmbeddedWidgetComponent(owner, id))
+ if (typeof bar.registeredWidgetComponent === "function")
+ return componentHandle(bar.registeredWidgetComponent(id))
+ }
+ return registryComponent(bar && "barWidgetRegistry" in bar ? bar.barWidgetRegistry : null, id)
+ }
+
+ function resolveSource(id) {
+ if (registryOnly) return ""
+ if (bar && typeof bar.registeredWidgetSource === "function")
+ return bar.registeredWidgetSource(id)
+ const host = shell || (bar && "shell" in bar ? bar.shell : null)
+ const view = host && "pluginRegistry" in host ? host.pluginRegistry : null
+ const manifest = view && view.installedPlugins ? view.installedPlugins[String(id || "")] : null
+ return view && typeof view.entryPointUrl === "function" ? view.entryPointUrl(manifest, "barWidget") : ""
+ }
+
+ function clearLoader(loader) {
+ if (loader.item && "bar" in loader.item) loader.item.bar = null
+ loader.sourceComponent = null
+ loader.source = ""
+ }
+
+ function revoke() {
+ const previous = loads
+ loads = []
+ for (const record of previous) clearLoader(record.loader)
+ }
+
+ function shutdown() {
+ shuttingDown = true
+ // Destroy children while their injected host facade is still valid.
+ for (const record of loads)
+ if (record.loader.item !== null) record.loader.active = false
+ revoke()
+ }
+
+ function syncLoader(loader, handle, source, args, owner) {
+ if (shuttingDown || !loader) return false
+ const component = componentHandle(handle)
+ const url = component ? "" : String(source || "")
+ let record = loads.find(value => value.loader === loader)
+ if (!record || record.owner !== owner || record.component !== component || record.url !== url) {
+ loads = loads.filter(value => value.loader !== loader)
+ clearLoader(loader)
+ record = {loader: loader, owner: owner, component: component, url: url, attempts: 0}
+ loads = loads.concat([record])
+ }
+ if (!owner || (!component && !url)) return false
+ if (loader.item !== null) return true
+ // Reuse only existing consumer wakeups. Exhaustion cannot create a loop;
+ // a new owner or source begins a new bounded submission sequence.
+ if (loader.status === Loader.Loading || record.attempts >= 10) return false
+ record.attempts++
+ clearLoader(loader)
+ if (shuttingDown || loads.indexOf(record) < 0) return false
+ if (component) loader.sourceComponent = component
+ else loader.setSource(url, args)
+ return loader.item !== null
+ }
+
+}
diff --git a/hancore.shibumi.status/BarWidget.qml b/hancore.shibumi.status/BarWidget.qml
index 2216c5ed..7c02acd6 100644
--- a/hancore.shibumi.status/BarWidget.qml
+++ b/hancore.shibumi.status/BarWidget.qml
@@ -10,13 +10,14 @@ Item {
property var bar: null
property string moduleName: "hancore.shibumi.status"
property var settings: ({})
- readonly property url updateSource: registeredSource(
+ HostWidgetEmbed { id: embed; bar: root.bar }
+ readonly property url updateSource: embed.resolveSource(
"hancore.shibumi.update-center")
- readonly property url traySource: registeredSource("omarchy.tray")
+ readonly property url traySource: embed.resolveSource("omarchy.tray")
property var updateComponent: String(updateSource) ? null
- : registeredComponent("hancore.shibumi.update-center")
+ : embed.resolveComponent("hancore.shibumi.status", "hancore.shibumi.update-center")
property var trayComponent: String(traySource) ? null
- : registeredComponent("omarchy.tray")
+ : embed.resolveComponent("hancore.shibumi.status", "omarchy.tray")
property url trayDrawerSource: Qt.resolvedUrl("TrayDrawerPanel.qml")
property url trayAppMenuSource: Qt.resolvedUrl("TrayAppMenuPanel.qml")
property url notificationPanelSource: Qt.resolvedUrl("NotificationPanel.qml")
@@ -168,31 +169,6 @@ Item {
implicitWidth: visible ? contentWidth + 2 * horizontalInset : 0
implicitHeight: visible ? (bar ? bar.barSize : Commons.Style.space(35)) : 0
- function registeredComponent(id) {
- if (bar
- && typeof bar.registeredEmbeddedWidgetComponent === "function")
- return bar.registeredEmbeddedWidgetComponent(
- "hancore.shibumi.status", id)
- if (bar && typeof bar.registeredWidgetComponent === "function")
- return bar.registeredWidgetComponent(id)
- const registry = bar ? bar.barWidgetRegistry : null
- if (registry) void(registry.revision)
- const widgets = registry && registry.widgets ? registry.widgets : ({})
- const entry = widgets[id]
- return entry ? entry.component : null
- }
-
- function registeredSource(id) {
- if (bar && typeof bar.registeredWidgetSource === "function")
- return bar.registeredWidgetSource(id)
- const registry = bar && bar.shell
- && "pluginRegistry" in bar.shell ? bar.shell.pluginRegistry : null
- const pluginManifest = registry && registry.installedPlugins
- ? registry.installedPlugins[String(id || "")] : null
- return registry && typeof registry.entryPointUrl === "function"
- ? registry.entryPointUrl(pluginManifest, "barWidget") : ""
- }
-
function childSettings(id) {
return bar && typeof bar.widgetSettings === "function"
? bar.widgetSettings("G3", id) : ({})
@@ -243,36 +219,15 @@ Item {
injectChild(trayWidget, "omarchy.tray")
}
- function syncLoader(loader, component, source, id) {
- loader.sourceComponent = null
- loader.source = ""
- if (component !== null) {
- loader.sourceComponent = component
- } else if (String(source)) {
- loader.setSource(source, {
- bar: hostProxy,
- moduleName: id,
- settings: childSettings(id)
- })
- }
- }
-
- function syncTrayLoader() {
- syncLoader(trayLoader, trayComponent, traySource, "omarchy.tray")
- }
-
- function syncUpdateLoader() {
- updateLoader.sourceComponent = null
- updateLoader.source = ""
- if (updateComponent !== null) {
- updateLoader.sourceComponent = updateComponent
- } else if (String(updateSource)) {
- updateLoader.setSource(updateSource, {
- bar: root.bar,
- moduleName: "hancore.shibumi.update-center",
- settings: childSettings("hancore.shibumi.update-center")
- })
- }
+ function syncChildren() {
+ embed.syncLoader(updateLoader, updateComponent, updateSource, {
+ bar: root.bar, moduleName: "hancore.shibumi.update-center",
+ settings: childSettings("hancore.shibumi.update-center")
+ }, root.bar)
+ const trayId = "omarchy.tray"
+ embed.syncLoader(trayLoader, trayComponent, traySource, {
+ bar: hostProxy, moduleName: trayId, settings: childSettings(trayId)
+ }, root.bar)
}
function syncTrayDrawerLoader() {
@@ -458,7 +413,7 @@ Item {
}
function scheduleChildSync() {
- childSyncTimer.restart()
+ if (root && !embed.shuttingDown) childSyncTimer.restart()
}
onBarChanged: {
@@ -518,6 +473,8 @@ Item {
updateWidget.registeredBar = null
if (updateWidget && "bar" in updateWidget) updateWidget.bar = null
if (trayWidget && "bar" in trayWidget) trayWidget.bar = null
+ childSyncTimer.stop()
+ embed.shutdown()
}
QtObject {
@@ -599,8 +556,7 @@ Item {
id: childSyncTimer
interval: 0
onTriggered: {
- root.syncUpdateLoader()
- root.syncTrayLoader()
+ root.syncChildren()
root.injectChildren()
}
}
diff --git a/hancore.shibumi.status/HostWidgetEmbed.qml b/hancore.shibumi.status/HostWidgetEmbed.qml
new file mode 100644
index 00000000..3013e386
--- /dev/null
+++ b/hancore.shibumi.status/HostWidgetEmbed.qml
@@ -0,0 +1,92 @@
+import QtQuick
+
+// Local host handles stay untyped until Loader's setter. Scheduling remains
+// with the consumer: no new timer, polling, or callLater feedback path.
+QtObject {
+ id: root
+ property var bar: null
+ property var shell: null
+ property var registry: null
+ property bool registryOnly: false
+ property bool shuttingDown: false
+ property var loads: []
+
+ function componentHandle(value) {
+ try { return value && value instanceof Component ? value : null }
+ catch (error) { return null }
+ }
+
+ function registryComponent(view, id) {
+ if (view) void(view.revision)
+ const entry = view && view.widgets ? view.widgets[String(id || "")] : null
+ return componentHandle(entry ? entry.component : null)
+ }
+
+ function resolveComponent(owner, id) {
+ // An explicitly injected service snapshot precedes legacy Bar lookups.
+ const injected = registryComponent(registry, id)
+ if (injected || registryOnly) return injected
+ if (bar) {
+ if (owner && typeof bar.registeredEmbeddedWidgetComponent === "function")
+ return componentHandle(bar.registeredEmbeddedWidgetComponent(owner, id))
+ if (typeof bar.registeredWidgetComponent === "function")
+ return componentHandle(bar.registeredWidgetComponent(id))
+ }
+ return registryComponent(bar && "barWidgetRegistry" in bar ? bar.barWidgetRegistry : null, id)
+ }
+
+ function resolveSource(id) {
+ if (registryOnly) return ""
+ if (bar && typeof bar.registeredWidgetSource === "function")
+ return bar.registeredWidgetSource(id)
+ const host = shell || (bar && "shell" in bar ? bar.shell : null)
+ const view = host && "pluginRegistry" in host ? host.pluginRegistry : null
+ const manifest = view && view.installedPlugins ? view.installedPlugins[String(id || "")] : null
+ return view && typeof view.entryPointUrl === "function" ? view.entryPointUrl(manifest, "barWidget") : ""
+ }
+
+ function clearLoader(loader) {
+ if (loader.item && "bar" in loader.item) loader.item.bar = null
+ loader.sourceComponent = null
+ loader.source = ""
+ }
+
+ function revoke() {
+ const previous = loads
+ loads = []
+ for (const record of previous) clearLoader(record.loader)
+ }
+
+ function shutdown() {
+ shuttingDown = true
+ // Destroy children while their injected host facade is still valid.
+ for (const record of loads)
+ if (record.loader.item !== null) record.loader.active = false
+ revoke()
+ }
+
+ function syncLoader(loader, handle, source, args, owner) {
+ if (shuttingDown || !loader) return false
+ const component = componentHandle(handle)
+ const url = component ? "" : String(source || "")
+ let record = loads.find(value => value.loader === loader)
+ if (!record || record.owner !== owner || record.component !== component || record.url !== url) {
+ loads = loads.filter(value => value.loader !== loader)
+ clearLoader(loader)
+ record = {loader: loader, owner: owner, component: component, url: url, attempts: 0}
+ loads = loads.concat([record])
+ }
+ if (!owner || (!component && !url)) return false
+ if (loader.item !== null) return true
+ // Reuse only existing consumer wakeups. Exhaustion cannot create a loop;
+ // a new owner or source begins a new bounded submission sequence.
+ if (loader.status === Loader.Loading || record.attempts >= 10) return false
+ record.attempts++
+ clearLoader(loader)
+ if (shuttingDown || loads.indexOf(record) < 0) return false
+ if (component) loader.sourceComponent = component
+ else loader.setSource(url, args)
+ return loader.item !== null
+ }
+
+}
diff --git a/scripts/check-production-boundary b/scripts/check-production-boundary
index 93947b9f..8d264509 100755
--- a/scripts/check-production-boundary
+++ b/scripts/check-production-boundary
@@ -40,6 +40,7 @@ WORKER_POLICIES = {
}
RULE_KINDS = {"token", "provider", "firstPartyService", "helper"}
TOKEN_NAMES = {
+ "embed.resolveComponent", "embed.resolveSource", "embed.syncLoader",
"entryPointUrl",
"firstPartyService",
"firstPartyServiceFor",
@@ -84,8 +85,9 @@ PRIVATE_PATHS = (
re.compile(r"\.config/omarchy/plugins/omarchy\."),
)
PROVIDER_CALL = re.compile(
- r"\b(?:registeredSource|registeredComponent|registeredWidgetSource|"
- r"registeredWidgetComponent|standardWidgetSource)\s*\(\s*"
+ r"\b(?:(?:registeredSource|registeredComponent|registeredWidgetSource|"
+ r"registeredWidgetComponent|standardWidgetSource|resolveSource)\s*\(\s*|"
+ r"resolveComponent\s*\(\s*[\"'][^\"']*[\"']\s*,\s*)"
r"[\"'](omarchy\.[A-Za-z0-9_.-]+)[\"']"
)
FIRST_PARTY_CALL = re.compile(
@@ -577,6 +579,8 @@ def check_tree(root: Path, manifest_path: Path) -> list[str]:
if pattern.search(text):
add(findings, relative, "private component path", pattern.pattern)
+ # Dotted embed.* tokens are raw substring tripwires, not argument checks:
+ # comments/strings/xembed also count; aliases and bracket access can bypass them.
token_rules = rules_for(manifest, relative, "token")
protected_observed: Counter[str] = Counter()
for token in TOKEN_NAMES:
diff --git a/scripts/sync-shared.sh b/scripts/sync-shared.sh
index 2aa13c35..314dfe9e 100755
--- a/scripts/sync-shared.sh
+++ b/scripts/sync-shared.sh
@@ -16,7 +16,12 @@ esac
# ShibumiPanel retains three lifecycle timers, so it cannot enter the passive
# State presentation library. Keep its existing copies drift-checked until its
# scheduling contract can be separated without changing panel behavior.
+# HostWidgetEmbed shares checked host-embedding code without a cross-plugin import.
mappings=(
+ "shared/runtime/HostWidgetEmbed.qml:hancore.shibumi.center/HostWidgetEmbed.qml"
+ "shared/runtime/HostWidgetEmbed.qml:hancore.shibumi.status/HostWidgetEmbed.qml"
+ "shared/runtime/HostWidgetEmbed.qml:hancore.shibumi.audio/HostWidgetEmbed.qml"
+ "shared/runtime/HostWidgetEmbed.qml:hancore.shibumi.brightness/HostWidgetEmbed.qml"
"shared/presentation/WaterRing.qml:hancore.shibumi.cpu/WaterRing.qml"
"shared/presentation/WaterRing.qml:hancore.shibumi.battery/WaterRing.qml"
"shared/presentation/WaterRing.qml:hancore.shibumi.temperature/WaterRing.qml"
diff --git a/shared/runtime/HostWidgetEmbed.qml b/shared/runtime/HostWidgetEmbed.qml
new file mode 100644
index 00000000..3013e386
--- /dev/null
+++ b/shared/runtime/HostWidgetEmbed.qml
@@ -0,0 +1,92 @@
+import QtQuick
+
+// Local host handles stay untyped until Loader's setter. Scheduling remains
+// with the consumer: no new timer, polling, or callLater feedback path.
+QtObject {
+ id: root
+ property var bar: null
+ property var shell: null
+ property var registry: null
+ property bool registryOnly: false
+ property bool shuttingDown: false
+ property var loads: []
+
+ function componentHandle(value) {
+ try { return value && value instanceof Component ? value : null }
+ catch (error) { return null }
+ }
+
+ function registryComponent(view, id) {
+ if (view) void(view.revision)
+ const entry = view && view.widgets ? view.widgets[String(id || "")] : null
+ return componentHandle(entry ? entry.component : null)
+ }
+
+ function resolveComponent(owner, id) {
+ // An explicitly injected service snapshot precedes legacy Bar lookups.
+ const injected = registryComponent(registry, id)
+ if (injected || registryOnly) return injected
+ if (bar) {
+ if (owner && typeof bar.registeredEmbeddedWidgetComponent === "function")
+ return componentHandle(bar.registeredEmbeddedWidgetComponent(owner, id))
+ if (typeof bar.registeredWidgetComponent === "function")
+ return componentHandle(bar.registeredWidgetComponent(id))
+ }
+ return registryComponent(bar && "barWidgetRegistry" in bar ? bar.barWidgetRegistry : null, id)
+ }
+
+ function resolveSource(id) {
+ if (registryOnly) return ""
+ if (bar && typeof bar.registeredWidgetSource === "function")
+ return bar.registeredWidgetSource(id)
+ const host = shell || (bar && "shell" in bar ? bar.shell : null)
+ const view = host && "pluginRegistry" in host ? host.pluginRegistry : null
+ const manifest = view && view.installedPlugins ? view.installedPlugins[String(id || "")] : null
+ return view && typeof view.entryPointUrl === "function" ? view.entryPointUrl(manifest, "barWidget") : ""
+ }
+
+ function clearLoader(loader) {
+ if (loader.item && "bar" in loader.item) loader.item.bar = null
+ loader.sourceComponent = null
+ loader.source = ""
+ }
+
+ function revoke() {
+ const previous = loads
+ loads = []
+ for (const record of previous) clearLoader(record.loader)
+ }
+
+ function shutdown() {
+ shuttingDown = true
+ // Destroy children while their injected host facade is still valid.
+ for (const record of loads)
+ if (record.loader.item !== null) record.loader.active = false
+ revoke()
+ }
+
+ function syncLoader(loader, handle, source, args, owner) {
+ if (shuttingDown || !loader) return false
+ const component = componentHandle(handle)
+ const url = component ? "" : String(source || "")
+ let record = loads.find(value => value.loader === loader)
+ if (!record || record.owner !== owner || record.component !== component || record.url !== url) {
+ loads = loads.filter(value => value.loader !== loader)
+ clearLoader(loader)
+ record = {loader: loader, owner: owner, component: component, url: url, attempts: 0}
+ loads = loads.concat([record])
+ }
+ if (!owner || (!component && !url)) return false
+ if (loader.item !== null) return true
+ // Reuse only existing consumer wakeups. Exhaustion cannot create a loop;
+ // a new owner or source begins a new bounded submission sequence.
+ if (loader.status === Loader.Loading || record.attempts >= 10) return false
+ record.attempts++
+ clearLoader(loader)
+ if (shuttingDown || loads.indexOf(record) < 0) return false
+ if (component) loader.sourceComponent = component
+ else loader.setSource(url, args)
+ return loader.item !== null
+ }
+
+}
diff --git a/tests/audio-media-plugins-regression.sh b/tests/audio-media-plugins-regression.sh
index 0a79a535..556c18dd 100755
--- a/tests/audio-media-plugins-regression.sh
+++ b/tests/audio-media-plugins-regression.sh
@@ -140,11 +140,11 @@ grep -Fq 'horizontalAlignment: Text.AlignLeft' <<<"$compact_content" \
if grep -Fq 'horizontalAlignment: Text.AlignRight' <<<"$compact_content"; then
fail "compact audio value still shifts inside its fixed-width field"
fi
-rg -q 'registeredWidgetSource' "$audio_widget" \
+rg -q 'registeredWidgetSource' "$repo_root/hancore.shibumi.audio/HostWidgetEmbed.qml" \
|| fail "audio bridge does not resolve the official Omarchy source"
-rg -q 'registeredWidgetComponent' "$audio_widget" \
+rg -q 'registeredWidgetComponent' "$repo_root/hancore.shibumi.audio/HostWidgetEmbed.qml" \
|| fail "audio bridge does not resolve the official Omarchy component"
-rg -q 'registered(Source|Component)\("omarchy\.audio"\)' "$audio_widget" \
+rg -q 'embed\.resolveSource\("omarchy\.audio"\)' "$audio_widget" \
|| fail "audio bridge does not select the official Omarchy audio plugin"
rg -q 'readonly property color background: realBar && realBar\.background !== undefined' \
"$audio_bridge" \
diff --git a/tests/audio-plugin-smoke.qml b/tests/audio-plugin-smoke.qml
index 33e8979a..8aa7bbca 100644
--- a/tests/audio-plugin-smoke.qml
+++ b/tests/audio-plugin-smoke.qml
@@ -11,6 +11,7 @@ ShellRoot {
property real fullWidth: 0
property int wheelCallsBefore: 0
property var clickTargets: []
+ property var officialInstance: null
function fail(message) {
console.error("audio-plugin-smoke:", message)
@@ -83,7 +84,7 @@ ShellRoot {
Component {
id: audioPanelComponent
- Fixtures.AudioTestPanel {}
+ Fixtures.AudioTestPanel { id: officialPanel; Component.onCompleted: root.officialInstance = officialPanel }
}
Audio.BarWidget {
@@ -126,6 +127,11 @@ ShellRoot {
|| openedOfficial.backendKeyboardPanelVisible)
return root.fail("hidden official KeyboardPanel became visible")
audio.closeOfficialPanel()
+ const keptPanel = root.officialInstance
+ audio.bar = null
+ audio.bar = fakeBar
+ if (!keptPanel || root.officialInstance !== keptPanel)
+ return root.fail("bar handoff replaced the official audio panel")
root.fullWidth = audio.implicitWidth
audio.settings = ({ compact: true })
diff --git a/tests/bar-switch-regression.py b/tests/bar-switch-regression.py
new file mode 100644
index 00000000..12afe042
--- /dev/null
+++ b/tests/bar-switch-regression.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python3
+"""DPR1/full renderer lifecycle series; stub stock, no CLI/persistence/desktop acceptance."""
+import argparse
+import json
+from pathlib import Path
+import re
+import runpy
+import shutil
+import sys
+import tempfile
+sys.dont_write_bytecode = True
+from lib.isolated_process import run_bounded
+from lib.source_snapshot import snapshot, fingerprint, materialize
+ROOT = Path(__file__).resolve().parents[1]
+
+
+def main():
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument("--native-shell", required=True, type=Path)
+ args = parser.parse_args()
+ native = runpy.run_path(str(ROOT / "tests/native-runtime-regression.py"))["admitted_native"](args.native_shell)
+ steps = [(style, edge) for edge in ("top", "bottom")
+ for variant in ("shibumi", "full", "fit", "dock", "notch")
+ for style in (variant, "omarchy", variant)]
+ steps += [("shibumi", "top"), ("omarchy", "top"), ("full", "top"),
+ ("omarchy", "bottom"), ("shibumi", "bottom"), ("notch", "bottom"), ("notch", "top")]
+ with tempfile.TemporaryDirectory(prefix="bs-", dir="/tmp") as directory:
+ base = Path(directory)
+ for folder in ("home", "cache", "run", "bin", "native-runtime"):
+ (base / folder).mkdir(mode=0o700)
+ identities = {}
+ for plugin in sorted(ROOT.glob("hancore.shibumi.*")):
+ source = snapshot(plugin)
+ identities[plugin.name] = fingerprint(source)
+ materialize(source, base / plugin.name)
+ for module in ("Commons", "Ui"):
+ materialize({p[len(module)+1:]:v for p,v in native.items() if p.startswith(module+"/")}, base / module)
+ fixtures = ROOT / "tests/fixtures"
+ for name in ("AudioTestPanel.qml", "MonitorTestPanel.qml", "StatusTestWidget.qml", "native-runtime/StockBar.qml"):
+ shutil.copyfile(fixtures / name, base / name)
+ shutil.copyfile(fixtures / "BarSwitchHost.qml", base / "shell.qml")
+ (base / "hancore.shibumi.bar/core/BarPanel.qml").write_text('''import QtQuick
+import QtQuick.Window
+import "../styles/shibumi" as Styles
+Item {
+ id: panel
+ required property var bar
+ property var screen: null
+ Window {visible:panel.bar.hostReady && panel.bar.styleReady; width:1800; height:100
+ Loader {active:panel.bar.hostReady && panel.bar.styleReady; width:1800; height:panel.bar.barSize
+ sourceComponent:Component {Styles.BarSurface {bar:panel.bar;screenName:"fixture-output"}}}}
+}
+''')
+ (base / "census.py").write_text('''import json,os
+from pathlib import Path
+rows=[]
+for p in Path('/proc').glob('[0-9]*'):
+ try:
+ if (p/'exe').resolve().name == 'quickshell': rows.append(int(p.name))
+ except OSError: pass
+print(json.dumps(rows));raise SystemExit(0 if len(rows)==1 else 1)
+''')
+ for name, text in {"hyprctl":'{"int":10,"custom":"10 10 10 10"}', "fc-match":"monospace", "bash":"no"}.items():
+ path = base / "bin" / name
+ path.write_text("#!/bin/sh\nprintf '%s\\n' '"+text+"'\n"); path.chmod(0o700)
+ env = {"HOME":"/fixture/home", "XDG_CONFIG_HOME":"/fixture/home/.config", "XDG_CACHE_HOME":"/fixture/cache",
+ "XDG_DATA_HOME":"/fixture/home/data", "XDG_DATA_DIRS":"/fixture/home/data", "XDG_STATE_HOME":"/fixture/home/state",
+ "XDG_RUNTIME_DIR":"/fixture/run", "PATH":"/fixture/bin", "QT_QPA_PLATFORM":"offscreen", "QT_QUICK_BACKEND":"software",
+ "QML_DISABLE_DISK_CACHE":"1", "QT_SCALE_FACTOR":"1", "QT_FORCE_STDERR_LOGGING":"1", "LANG":"C.UTF-8",
+ "DBUS_SESSION_BUS_ADDRESS":"unix:path=/fixture/no-session", "DBUS_SYSTEM_BUS_ADDRESS":"unix:path=/fixture/no-system",
+ "PIPEWIRE_RUNTIME_DIR":"/fixture/run", "BASELINE":"/fixture/no-native-actions", "STEPS":json.dumps(steps)}
+ cmd = ["/usr/bin/bwrap", "--die-with-parent", "--new-session", "--unshare-all", "--ro-bind", "/usr", "/usr",
+ "--symlink", "usr/bin", "/bin", "--symlink", "usr/lib", "/lib", "--symlink", "usr/lib", "/lib64",
+ "--ro-bind", "/etc/fonts", "/etc/fonts", "--proc", "/proc", "--dev", "/dev", "--tmpfs", "/tmp",
+ "--bind", str(base), "/fixture", "--clearenv", "--chdir", "/fixture"]
+ for key, value in env.items(): cmd += ["--setenv", key, value]
+ cmd += ["/usr/bin/prlimit", "--core=0", "--fsize=16777216", "--", "/bin/sh", "-c",
+ "exec /usr/bin/quickshell -p /fixture --no-color > /fixture/shell.log 2>&1"]
+ print("BAR_SWITCH_INPUTS "+json.dumps({"plugins":identities,"steps":steps}), flush=True)
+ try:
+ result = run_bounded(cmd, timeout=180, maximum=1024*1024)
+ print((result.stdout+result.stderr).decode(errors="replace"), end="", flush=True)
+ finally:
+ log = base / "shell.log"
+ text = log.read_text(errors="replace") if log.exists() else ""
+ print(text, end="", flush=True)
+ print("BAR_SWITCH_CLEANUP "+json.dumps({"removed":not base.exists()}), flush=True)
+ rows = [json.loads(line.split("BAR_SWITCH_STEP ",1)[1]) for line in text.splitlines() if "BAR_SWITCH_STEP " in line]
+ census = [json.loads(line.split("BAR_SWITCH_CENSUS ",1)[1]) for line in text.splitlines() if "BAR_SWITCH_CENSUS " in line]
+ allowed = r'^\s*(?:INFO: (?:Launching config:|Shell ID:|Saving logs to|Configuration Loaded)|WARN quickshell.qmlscanner: Module path contains invalid characters for a module name:|DEBUG qml: BAR_SWITCH_)'
+ unexpected = [line for line in text.splitlines() if line.strip() and not re.match(allowed,line)]
+ assert result.returncode == 0 and "BAR_SWITCH_DONE" in text, "fixture failed"
+ assert not unexpected, unexpected
+ assert len(rows)==len(steps)==len(census) and len({r["pid"] for r in rows})==1, "incomplete series"
+ for index, row in enumerate(rows):
+ assert row["index"]==index and row["step"]==list(steps[index]) and census[index]==[row["pid"]], row
+ assert row["bar"] and row["bell"] and row["brightness"], row
+ assert all(row[name]=={"present":True,"bound":True,"loader":1} for name in ("center","update","tray","audio","monitor")), row
+ print("bar switch regression passed")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tests/brightness-plugin-regression.sh b/tests/brightness-plugin-regression.sh
index 41a07c5b..8dffabd1 100755
--- a/tests/brightness-plugin-regression.sh
+++ b/tests/brightness-plugin-regression.sh
@@ -59,15 +59,15 @@ if rg -q 'bar\.monitorService' "$repo_root/hancore.shibumi.brightness"; then
fi
rg -Fq 'property var bar: Qt.isQtObject(shell) && "bar" in shell ? shell.bar : null' "$service" \
|| fail "monitor service does not guard the versioned active bar facade"
-rg -q 'registeredComponent\("omarchy\.monitor"\)' "$service" \
+rg -Fq 'embed.resolveComponent("", "omarchy.monitor")' "$service" \
|| fail "monitor service does not retain the official Omarchy owner"
rg -Fq 'property var barWidgetRegistry: null' "$service" \
|| fail "scoped monitor service cannot receive the accepted widget snapshot"
-rg -Fq 'if (injectedEntry && injectedEntry.component) return injectedEntry.component' "$service" \
+rg -Fq 'registry: root.barWidgetRegistry' "$service" \
|| fail "scoped monitor service does not use the accepted native component"
-rg -Fq 'if (scopedHost) return null' "$service" \
+rg -Fxq ' registryOnly: root.scopedHost' "$service" \
|| fail "scoped monitor service falls through to wider registry access"
-rg -Fq '"barWidgetRegistry" in bar' "$service" \
+rg -Fq '"barWidgetRegistry" in bar' "$repo_root/hancore.shibumi.brightness/HostWidgetEmbed.qml" \
|| fail "monitor service cannot resolve the official owner on stock Quattro"
[[ $(rg -c '^ MonitorPanelBridge \{' "$service") -eq 1 ]] \
|| fail "monitor service does not own exactly one official bridge"
diff --git a/tests/center-plugin-regression.sh b/tests/center-plugin-regression.sh
index 116bca87..893a6533 100755
--- a/tests/center-plugin-regression.sh
+++ b/tests/center-plugin-regression.sh
@@ -263,7 +263,7 @@ for location_contract in \
rg -Fq "$location_contract" "$weather_panel" \
|| fail "weather location contract missing: $location_contract"
done
-rg -q 'registered(Source|Component)\("omarchy\.system-update"\)' "$center_widget" \
+rg -Fq 'embed.resolveComponent("hancore.shibumi.center", "omarchy.system-update")' "$center_widget" \
|| fail "center does not retain the official update action owner"
if rg -U -q 'onLoaded: \{[^}]*root\.scheduleOfficialSync' "$center_widget"; then
fail "loaded center children recursively reschedule their own loaders"
diff --git a/tests/contract-regression.sh b/tests/contract-regression.sh
index dde7323b..fa2a0a24 100755
--- a/tests/contract-regression.sh
+++ b/tests/contract-regression.sh
@@ -253,7 +253,7 @@ if rg -q 'registered(Component|Source)|registeredWidget(Component|Source)' \
hancore.shibumi.network/Service.qml; then
fail "native Network owner still resolves a host feature component"
fi
-rg -q 'registeredComponent\("omarchy\.monitor"\)' hancore.shibumi.brightness/Service.qml \
+rg -Fq 'embed.resolveComponent("", "omarchy.monitor")' hancore.shibumi.brightness/Service.qml \
|| fail "shipped monitor owner bypasses the stable widget resolver"
if rg -q 'registeredWidgetComponent\("omarchy\.bluetooth"\)' \
hancore.shibumi.bluetooth/Service.qml; then
@@ -576,7 +576,7 @@ rg -q 'onTabRequested' hancore.shibumi.center/CalendarPanel.qml \
|| fail "G8 calendar is missing sibling-panel keyboard routing"
rg -q 'Presentation\.ShibumiPillToolTip \{' hancore.shibumi.center/CalendarPanel.qml \
|| fail "G8 calendar navigation bypasses the Shibumi tooltip"
-if rg -q 'registeredSource\("omarchy\.indicators"\)' hancore.shibumi.center/BarWidget.qml; then
+if rg -q 'embed\.resolveSource\("omarchy\.indicators"\)' hancore.shibumi.center/BarWidget.qml; then
fail "G8 must not instantiate Quattro's stock indicator presentation"
fi
rg -q '"service": "Service.qml"' hancore.shibumi.status/manifest.json \
diff --git a/tests/fixtures/BarSwitchHost.qml b/tests/fixtures/BarSwitchHost.qml
new file mode 100644
index 00000000..66a8af8e
--- /dev/null
+++ b/tests/fixtures/BarSwitchHost.qml
@@ -0,0 +1,141 @@
+// Renderer/lifetime fixture only: no native CLI, persisted shell.json or stock desktop acceptance.
+// Handles stay scene-owned: this series does not assert the restricted-projection case.
+import QtQuick
+import Quickshell
+import Quickshell.Io
+import qs.Commons as Commons
+import "native-runtime" as Native
+import "hancore.shibumi.brightness" as Brightness
+import "hancore.shibumi.state/ShibumiConfig.js" as Config
+ShellRoot {
+ id: scene
+ property var steps: JSON.parse(Quickshell.env("STEPS"))
+ property int index: 0
+ property real stepDeadline: 0
+ property var step: steps[index]
+ property bool stock: step[0] === "omarchy"
+ property var components: ({})
+ property var currentBar: stock ? stockLoader.item : suiteLoader.item
+ // Fixture precondition: explicitly configure the inert official audio provider;
+ // the scoped resolver correctly refuses an unconfigured backend. No admission bypass.
+ property var layout: ({left:["hancore.shibumi.status","hancore.shibumi.audio","hancore.shibumi.brightness"],center:["hancore.shibumi.center"],right:["omarchy.audio"]})
+ property var config: ({id:stock?"omarchy.bar":"hancore.shibumi.bar",position:step[1],layout:layout})
+ QtObject {
+ id: state
+ property bool ready: true
+ property color selectedColor: "#7aa2f7"
+ property int revision: 0
+ property var config: {const c=Config.defaultConfig();c.presentation.shellStyle=scene.stock?"shibumi":scene.step[0];return c}
+ function groupSettingsForVariant(group, variant) {return config.widgets[group] || ({})}
+ }
+ QtObject {id: notifications; property bool liveAvailable:true; property string historyState:"ready"; property var pendingModel:({count:2}); property var pastModel:({count:3}); property bool doNotDisturb:false}
+ QtObject {id: statusService; property var notificationService:notifications; property bool stayAwake:false; property bool notificationsSilenced:false; property bool recording:false; property bool voxtypeActive:false}
+ QtObject {id: audioService; property bool nativeBackendEnabled:false; property real inputPeak:0; function report() {} function release() {}}
+ QtObject {id: updates; property int totalUpdateCount:4; property int packageCount:4; property int themeCount:0; property bool packageRefreshing:false; property bool themeRefreshing:false; property string packageError:""; property string themeError:""; property var packageState:({state:"ready"}); property var themeState:({checkedEpoch:1,degraded:false})}
+ QtObject {
+ id: host
+ property var bar: scene.currentBar
+ property var shellConfig: ({bar:scene.config})
+ function serviceFor(id) {return ({"hancore.shibumi.state":state,"hancore.shibumi.status":statusService,"hancore.shibumi.update-center":updates,"hancore.shibumi.audio":audioService,"hancore.shibumi.brightness":monitor})[id] || null}
+ function firstPartyServiceFor(id) {return null}
+ function pluginShellForId(id) {return host}
+ function pluginShellForBarEntry(id, name) {return host}
+ }
+ QtObject {
+ id: registry
+ property string pluginId: "hancore.shibumi.bar"
+ property int revision: 0
+ property var plugins: []
+ property var widgets: {void(revision);const out={};for(const id of Object.keys(scene.components))out[id]={component:scene.components[id],metadata:{pluginId:id,allowMultiple:false}};return out}
+ function metadataFor(id) {return widgets[id] ? widgets[id].metadata : null}
+ }
+ Brightness.Service {id: monitor; shell:host; barWidgetRegistry:registry}
+ Loader {
+ id: suiteLoader
+ active: !scene.stock
+ source: active ? "file:///fixture/hancore.shibumi.bar/Bar.qml" : ""
+ asynchronous: true
+ onLoaded: {
+ item.omarchyPath=Quickshell.env("BASELINE");item.shell=host
+ item.manifest=({id:"hancore.shibumi.bar",kinds:["bar"]})
+ item.barWidgetRegistry=registry;item.pluginRegistry=registry
+ item.barConfig=Qt.binding(function(){return scene.config})
+ }
+ }
+ // Extend the existing window stub with an injected registry and inert widget
+ // placement. This is not the native Stock.Bar layout or its admission policy.
+ Loader {
+ id: stockLoader
+ active: scene.stock
+ sourceComponent: Component {
+ Native.StockBar {
+ id: stockBar
+ shell:host; barWidgetRegistry:registry; barConfig:scene.config
+ position:scene.step[1]
+ property bool vertical:false
+ property color foreground:Commons.Color.foreground
+ property color barForeground:foreground
+ property color background:Commons.Color.background
+ property color urgent:Commons.Color.accent
+ property bool foregroundAnimationEnabled:false
+ property var activePopout:null
+ function registerClickTarget(target) {} function unregisterClickTarget(target) {}
+ function showTooltip(target,text) {} function hideTooltip(target) {}
+ function requestPopout(owner) {activePopout=owner} function releasePopout(owner) {if(activePopout===owner)activePopout=null}
+ Repeater {
+ id: placements
+ model: scene.layout.left.concat(scene.layout.center)
+ Loader {
+ required property string modelData
+ sourceComponent: scene.components[modelData] || null
+ onLoaded: {item.bar=stockBar;item.moduleName=modelData}
+ }
+ }
+ function widget(id) {for(let i=0;is.moduleName===id)
+ return s ? s.activeItem : null
+ }
+ function descendants(item) {return item ? Array.from(item.children || []).reduce((a,c)=>a.concat([c],descendants(c)),[]) : []}
+ function childState(child, bar) {
+ const bound=!!child && (child.bar===bar || (child.bar && child.bar.realBar===bar))
+ return {present:!!child,bound:bound,loader:child && child.parent && "status" in child.parent ? child.parent.status : -1}
+ }
+ function record() {
+ const b=currentBar, s=widget("hancore.shibumi.status"), c=widget("hancore.shibumi.center"), a=widget("hancore.shibumi.audio"), m=widget("hancore.shibumi.brightness")
+ const backend=descendants(a).find(x=>x.moduleName==="omarchy.audio")
+ const row={index:index,step:step,pid:Quickshell.processId,bar:!!b,
+ center:childState(c?c.updateBackend:null,b),update:childState(s?s.updateWidget:null,b),tray:childState(s?s.trayWidget:null,b),
+ bell:!!s && s.bar===b && s.notificationService===notifications && s.notificationPresented,
+ audio:childState(backend,b),monitor:childState(monitor.backend,b),brightness:!!m && m.bar===b}
+ const ready=row.bar && row.bell && row.brightness && ["center","update","tray","audio","monitor"].every(k=>row[k].present && row[k].bound && row[k].loader===Loader.Ready)
+ if(!ready && Date.now()
Date: Tue, 13 Oct 2026 23:53:57 +0200
Subject: [PATCH 09/18] refactor(runtime): route Omarchy host identifiers
through one contract layer
---
ARCHITECTURE.md | 7 ++++
contracts/backend-boundary-v1.json | 8 ++---
contracts/plugin-integration-resources.json | 7 +++-
docs/architecture/quattro-compatibility.md | 2 ++
hancore.shibumi.audio/Service.qml | 2 +-
hancore.shibumi.bar/Bar.qml | 4 +--
hancore.shibumi.bluetooth/Service.qml | 2 +-
hancore.shibumi.media/Service.qml | 4 +--
.../NetworkPanelBridge.qml | 3 +-
hancore.shibumi.quick-access/Service.qml | 6 ++--
hancore.shibumi.reactor/BoundedTextSource.qml | 3 +-
hancore.shibumi.state/ThemePalette.qml | 5 +--
.../runtime/HostContract.qml | 28 +++++++++++++++
hancore.shibumi.state/runtime/HostShell.qml | 2 +-
hancore.shibumi.state/runtime/Runtime.qml | 5 +--
hancore.shibumi.state/runtime/qmldir | 1 +
scripts/collect-release-evidence | 35 +++++++++++++------
scripts/shared_runtime_contract.py | 4 ++-
.../audio-network-ipc-contract-regression.sh | 8 ++---
tests/bar-host-registry-regression.sh | 14 ++++----
tests/contract-regression.sh | 2 +-
tests/network-ipc-routing-regression.sh | 2 ++
tests/network-plugin-regression.sh | 2 +-
tests/quattro-contract-regression.sh | 26 ++++++++++++++
tests/quick-access-plugin-regression.sh | 2 +-
tests/reactor-text-source-regression.py | 4 ++-
tests/scoped-state-regression.py | 2 +-
tests/shared-runtime-import-regression.py | 4 +--
tests/state-storage-regression.py | 2 +-
tests/test_package_release.py | 2 +-
tests/third-party-integration-regression.sh | 6 ++++
31 files changed, 151 insertions(+), 53 deletions(-)
create mode 100644 hancore.shibumi.state/runtime/HostContract.qml
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
index 0e09c06e..f9c4cfd8 100644
--- a/ARCHITECTURE.md
+++ b/ARCHITECTURE.md
@@ -403,6 +403,13 @@ Shibumi controls the bar window.
## Verified Host Contract
+`SuiteRuntime.HostContract` is the suite's single constant-only QML compatibility
+layer for first-party service admission, host marker/theme paths and IPC target
+names. Existing services retain handler ownership, file readers and the bounded
+visibility handoff; palette APIs and command routing are unchanged. Host versions
+remain in baseline manifests, which also supply the release collector's package
+probe expectations. The pinned Quattro contract binds this layer to host sources.
+
The installed loader and installation contracts were rechecked against
the validation system's Omarchy Quattro package `4.0.0.r1458.gfa6b5fc-1` on 2026-07-30.
The plugin registry, validator, installer, bar selector, bar loader, and
diff --git a/contracts/backend-boundary-v1.json b/contracts/backend-boundary-v1.json
index 9d063eb2..51429059 100644
--- a/contracts/backend-boundary-v1.json
+++ b/contracts/backend-boundary-v1.json
@@ -46,7 +46,7 @@
{"scope":"rule","source":"hancore.shibumi.status/Service.qml","kind":"helper","kindValue":"omarchy-voxtype-status","category":"omarchyBackend","owner":"hancore.shibumi.status","capability":"recording-voxtype","count":1,"backend":"omarchy.voxtype","removalStep":"Step 8"},
{"scope":"hostIdRule","target":"omarchy.agents","category":"omarchyBackend","owner":"hancore.shibumi.ai","capability":"ai-usage","count":13,"backend":"omarchy.agents","removalStep":"Step 8"},
{"scope":"hostIdRule","target":"omarchy.audio","category":"omarchyBackend","owner":"hancore.shibumi.audio","capability":"audio","count":13,"backend":"omarchy.audio","removalStep":"Step 4A"},
- {"scope":"hostIdRule","target":"omarchy.media","category":"omarchyBackend","owner":"hancore.shibumi.media","capability":"media","count":9,"backend":"omarchy.media","removalStep":"Step 7"},
+ {"scope":"hostIdRule","target":"omarchy.media","category":"omarchyBackend","owner":"hancore.shibumi.media","capability":"media","count":8,"backend":"omarchy.media","removalStep":"Step 7"},
{"scope":"hostIdRule","target":"omarchy.model-usage","category":"omarchyBackend","owner":"hancore.shibumi.ai","capability":"ai-usage","count":8,"backend":"omarchy.model-usage","removalStep":"Step 8"},
{"scope":"hostIdRule","target":"omarchy.monitor","category":"omarchyBackend","owner":"hancore.shibumi.brightness","capability":"displays","count":10,"backend":"omarchy.monitor","removalStep":"Step 6"},
{"scope":"hostIdRule","target":"omarchy.power","category":"omarchyBackend","owner":"hancore.shibumi.power-state","capability":"power-profile","count":5,"backend":"omarchy.power","removalStep":"Step 6"},
@@ -371,16 +371,16 @@
{"target":"omarchy.bluetooth","count":8,"category":"hostIntegration","owner":"hancore.shibumi.bluetooth","capability":"bluetooth"},
{"target":"omarchy.clock","count":7,"category":"hostIntegration","owner":"hancore.shibumi.center","capability":"bar-host-routing"},
{"target":"omarchy.dropbox","count":1,"category":"hostIntegration","owner":"quattro.host","capability":"bar-host-routing"},
- {"target":"omarchy.idle","count":4,"category":"hostOwnedProvider","owner":"omarchy.idle","capability":"idle-policy"},
+ {"target":"omarchy.idle","count":3,"category":"hostOwnedProvider","owner":"omarchy.idle","capability":"idle-policy"},
{"target":"omarchy.indicators","count":3,"category":"hostIntegration","owner":"quattro.host","capability":"bar-host-routing"},
{"target":"omarchy.keyboard-layout","count":1,"category":"hostIntegration","owner":"quattro.host","capability":"bar-host-routing"},
- {"target":"omarchy.media","count":9,"category":"omarchyBackend","owner":"hancore.shibumi.media","capability":"media","backend":"omarchy.media","removalStep":"Step 7","reason":"Remove the official media identity after the Shibumi MPRIS service owns state and actions."},
+ {"target":"omarchy.media","count":8,"category":"omarchyBackend","owner":"hancore.shibumi.media","capability":"media","backend":"omarchy.media","removalStep":"Step 7","reason":"Remove the official media identity after the Shibumi MPRIS service owns state and actions."},
{"target":"omarchy.menu","count":1,"category":"hostIntegration","owner":"quattro.host","capability":"bar-host-routing"},
{"target":"omarchy.microphone","count":1,"category":"hostIntegration","owner":"quattro.host","capability":"bar-host-routing"},
{"target":"omarchy.model-usage","count":8,"category":"omarchyBackend","owner":"hancore.shibumi.ai","capability":"ai-usage","backend":"omarchy.model-usage","removalStep":"Step 8","reason":"Remove the legacy model-usage provider identity after native provider adapters are authoritative."},
{"target":"omarchy.monitor","count":10,"category":"omarchyBackend","owner":"hancore.shibumi.brightness","capability":"displays","backend":"omarchy.monitor","removalStep":"Step 6","reason":"Remove the official monitor identity after native display ownership is activated."},
{"target":"omarchy.network","count":6,"category":"hostIntegration","owner":"hancore.shibumi.network","capability":"network","reason":"The completed native owner retains only the documented host IPC/widget-family compatibility identity; it loads no Omarchy Network feature backend."},
- {"target":"omarchy.notifications","count":5,"category":"hostOwnedProvider","owner":"omarchy.notifications","capability":"notifications"},
+ {"target":"omarchy.notifications","count":4,"category":"hostOwnedProvider","owner":"omarchy.notifications","capability":"notifications"},
{"target":"omarchy.power","count":5,"category":"omarchyBackend","owner":"hancore.shibumi.power-state","capability":"power-profile","backend":"omarchy.power","removalStep":"Step 6","reason":"Remove the compatibility power identity after Shibumi owns battery and profile state."},
{"target":"omarchy.shell","count":1,"category":"hostIntegration","owner":"quattro.host","capability":"bar-host-routing"},
{"target":"omarchy.system-update","count":12,"category":"omarchyBackend","owner":"hancore.shibumi.update-center","capability":"system-update-state","backend":"omarchy.system-update","removalStep":"Step 7","reason":"Remove the official update detector identity after Shibumi owns update state."},
diff --git a/contracts/plugin-integration-resources.json b/contracts/plugin-integration-resources.json
index e45a67d3..56a1b1ba 100644
--- a/contracts/plugin-integration-resources.json
+++ b/contracts/plugin-integration-resources.json
@@ -1,4 +1,9 @@
{
"schemaVersion": 1,
- "dynamicResources": []
+ "dynamicResources": [
+ {"kind": "ipc", "resource": "omarchy.audio", "owner": "hancore.shibumi.audio/Service.qml", "sourceExpression": "SuiteRuntime.HostContract.ipcTargets.audio"},
+ {"kind": "ipc", "resource": "omarchy.bluetooth", "owner": "hancore.shibumi.bluetooth/Service.qml", "sourceExpression": "SuiteRuntime.HostContract.ipcTargets.bluetooth"},
+ {"kind": "ipc", "resource": "omarchy.network", "owner": "hancore.shibumi.network/NetworkPanelBridge.qml", "sourceExpression": "SuiteRuntime.HostContract.ipcTargets.network"},
+ {"kind": "ipc", "resource": "omarchy.bar", "owner": "hancore.shibumi.state/runtime/Runtime.qml", "sourceExpression": "Local.HostContract.ipcTargets.bar"}
+ ]
}
diff --git a/docs/architecture/quattro-compatibility.md b/docs/architecture/quattro-compatibility.md
index 304287fa..bd972ee5 100644
--- a/docs/architecture/quattro-compatibility.md
+++ b/docs/architecture/quattro-compatibility.md
@@ -68,6 +68,8 @@ manifest.
## What Shibumi validates
+The Quattro source contract binds `SuiteRuntime.HostContract` as one layer for its first-party IDs, IPC targets and host marker/theme paths; it adds no host authority or runtime version discovery.
+
Before mutating user state, the Shibumi lifecycle verifies the Quattro APIs
used by its own 24-plugin suite: plugin validation and discovery, bar selection
and configuration, plugin-registry injection, and the shared keyboard-panel
diff --git a/hancore.shibumi.audio/Service.qml b/hancore.shibumi.audio/Service.qml
index c73192b8..dc075f78 100644
--- a/hancore.shibumi.audio/Service.qml
+++ b/hancore.shibumi.audio/Service.qml
@@ -246,7 +246,7 @@ Item {
IpcHandler {
enabled: root.backendAdmitted
- target: "omarchy.audio"
+ target: SuiteRuntime.HostContract.ipcTargets.audio
function open(): void { root.openPanel() }
function close(): void { root.closePanel() }
diff --git a/hancore.shibumi.bar/Bar.qml b/hancore.shibumi.bar/Bar.qml
index 3addd71e..d60f59d6 100644
--- a/hancore.shibumi.bar/Bar.qml
+++ b/hancore.shibumi.bar/Bar.qml
@@ -3617,7 +3617,7 @@ Item {
Process {
id: barHiddenProbe
running: root.hostReady
- command: ["bash", "-lc", "[[ -f $HOME/.local/state/omarchy/toggles/bar-off || -f $HOME/.local/state/omarchy/toggles/bar-hidden ]] && echo yes || echo no"]
+ command: ["bash", "-lc", "[[ -f " + SuiteRuntime.HostContract.path("$HOME", "barOffMarker") + " || -f " + SuiteRuntime.HostContract.path("$HOME", "barHiddenMarker") + " ]] && echo yes || echo no"]
stdout: SplitParser {
onRead: line => {
root.barToggledOff = String(line).trim() === "yes"
@@ -3632,7 +3632,7 @@ Item {
}
FileView {
- path: root.hostReady ? root.home + "/.local/state/omarchy/toggles" : ""
+ path: root.hostReady ? SuiteRuntime.HostContract.path(root.home, "togglesDir") : ""
watchChanges: true
printErrors: false
onFileChanged: root.requestBarHiddenProbe()
diff --git a/hancore.shibumi.bluetooth/Service.qml b/hancore.shibumi.bluetooth/Service.qml
index 78bb84cb..6c4f4930 100644
--- a/hancore.shibumi.bluetooth/Service.qml
+++ b/hancore.shibumi.bluetooth/Service.qml
@@ -103,7 +103,7 @@ Item {
// Preserve Quattro's public contract with one process-wide Shibumi owner.
IpcHandler {
- target: "omarchy.bluetooth"
+ target: SuiteRuntime.HostContract.ipcTargets.bluetooth
function open(): void { root.openPanel() }
function show(): void { root.openPanel() }
diff --git a/hancore.shibumi.media/Service.qml b/hancore.shibumi.media/Service.qml
index 0a8fd731..46ffe3e8 100644
--- a/hancore.shibumi.media/Service.qml
+++ b/hancore.shibumi.media/Service.qml
@@ -54,8 +54,8 @@ Item {
readonly property bool probeRunning: cavaProbe.running
readonly property string cavaThemePath: Quickshell.env("HOME")
+ "/.local/state/omarchy/current/theme/cava_theme"
- readonly property string themeNamePath: Quickshell.env("HOME")
- + "/.local/state/omarchy/current/theme.name"
+ readonly property string themeNamePath:
+ SuiteRuntime.HostContract.path(Quickshell.env("HOME"), "themeNameFile")
visible: false
width: 0
diff --git a/hancore.shibumi.network/NetworkPanelBridge.qml b/hancore.shibumi.network/NetworkPanelBridge.qml
index a878092a..26085cff 100644
--- a/hancore.shibumi.network/NetworkPanelBridge.qml
+++ b/hancore.shibumi.network/NetworkPanelBridge.qml
@@ -2,6 +2,7 @@ pragma ComponentBehavior: Bound
import QtQuick
import Quickshell.Io
+import "../hancore.shibumi.state/runtime" as SuiteRuntime
// Process-wide compatibility route for the documented Omarchy Network IPC
// target. It owns no host panel/backend and forwards presentation requests to
@@ -55,7 +56,7 @@ Item {
}
IpcHandler {
- target: "omarchy.network"
+ target: SuiteRuntime.HostContract.ipcTargets.network
function open(): void { root.summonNetworkPresentation("panel") }
function show(): void { root.summonNetworkPresentation("panel") }
diff --git a/hancore.shibumi.quick-access/Service.qml b/hancore.shibumi.quick-access/Service.qml
index ce0b743c..1bbe5f2c 100644
--- a/hancore.shibumi.quick-access/Service.qml
+++ b/hancore.shibumi.quick-access/Service.qml
@@ -100,7 +100,7 @@ Item {
readonly property string emptyText: loading ? "Loading " + title.toLowerCase() + "..."
: "No " + title.toLowerCase() + " found"
readonly property string currentThemeNamePath:
- home + "/.local/state/omarchy/current/theme.name"
+ SuiteRuntime.HostContract.path(home, "themeNameFile")
readonly property bool overlayLoaded: overlayLoader.item !== null
readonly property bool overlayActive: overlayLoader.active
readonly property bool pickerWorkersRunning: currentProc.running
@@ -279,7 +279,7 @@ Item {
if (!opened) return
requestSerial++
if (officialPickerProc.running)
- Quickshell.execDetached(["omarchy-shell", "image-selector", "cancel"])
+ Quickshell.execDetached([SuiteRuntime.HostContract.shellCommand, "image-selector", "cancel"])
opened = false
activeScreen = null
confirmDelete = false
@@ -775,7 +775,7 @@ Item {
"key=$1; gsub(/[[:space:]=]/, \"\", key); colors[key]=$2",
"} END { split(\"red green yellow blue magenta cyan\", names, \" \");",
"for (i=1; i<=6; i++) { color=colors[\"color\" i]; if (color == \"\") color=colors[names[i]];",
- "if (color != \"\") { printf \"%s%s\", sep, color; sep=\",\" } } print \"\" }' \"$d/colors.toml\" 2>/dev/null);",
+ "if (color != \"\") { printf \"%s%s\", sep, color; sep=\",\" } } print \"\" }' \"$d/" + SuiteRuntime.HostContract.fileName("themeColorsFile") + "\" 2>/dev/null);",
"printf '%s\\t%s\\t%s\\n' \"$author\" \"$repo\" \"$palette\""
].join(" "), "shibumi-theme-meta", directory]
themeMetaProc.running = true
diff --git a/hancore.shibumi.reactor/BoundedTextSource.qml b/hancore.shibumi.reactor/BoundedTextSource.qml
index d157148b..1b7f2812 100644
--- a/hancore.shibumi.reactor/BoundedTextSource.qml
+++ b/hancore.shibumi.reactor/BoundedTextSource.qml
@@ -2,6 +2,7 @@ pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
+import "../hancore.shibumi.state/runtime" as SuiteRuntime
// Watch metadata without FileView content reads. One bounded, short-lived
// reader per input; changes coalesce and stale/cancelled reads never publish.
@@ -10,7 +11,7 @@ Item {
required property string kind
property bool active: false
readonly property string home: Quickshell.env("HOME")
- readonly property string relativePath: kind === "theme" ? ".local/state/omarchy/current/theme.name"
+ readonly property string relativePath: kind === "theme" ? SuiteRuntime.HostContract.themeNameFile
: kind === "event" ? ".cache/qs-reactor-event"
: kind === "quotes" ? ".config/shibumi/quotes.txt" : ""
readonly property int maximum: kind === "quotes" ? 65536 : kind === "event" ? 4096 : 512
diff --git a/hancore.shibumi.state/ThemePalette.qml b/hancore.shibumi.state/ThemePalette.qml
index 2a88a9f8..d2956ede 100644
--- a/hancore.shibumi.state/ThemePalette.qml
+++ b/hancore.shibumi.state/ThemePalette.qml
@@ -5,6 +5,7 @@ import Quickshell
import Quickshell.Io
import qs.Commons as Commons
import "ThemePaletteModel.js" as ThemePaletteModel
+import "runtime" as SuiteRuntime
// Quattro owns the foundational background/foreground roles. Read the seven
// terminal swatches that its public Commons palette does not expose so the
@@ -16,9 +17,9 @@ Item {
readonly property string home: Quickshell.env("HOME")
readonly property string colorsPath:
- home + "/.local/state/omarchy/current/theme/colors.toml"
+ SuiteRuntime.HostContract.path(home, "themeColorsFile")
readonly property string themeNamePath:
- home + "/.local/state/omarchy/current/theme.name"
+ SuiteRuntime.HostContract.path(home, "themeNameFile")
readonly property string selectedId: ThemePaletteModel.selection(
config && config.presentation ? config.presentation.accent : "color01")
property color color01: Commons.Color.urgent
diff --git a/hancore.shibumi.state/runtime/HostContract.qml b/hancore.shibumi.state/runtime/HostContract.qml
new file mode 100644
index 00000000..ceb63438
--- /dev/null
+++ b/hancore.shibumi.state/runtime/HostContract.qml
@@ -0,0 +1,28 @@
+pragma Singleton
+
+import QtQuick
+
+// Revision-bound host identifiers only; no runtime authority or workers.
+QtObject {
+ id: contract
+ readonly property var firstPartyServices: ["omarchy.idle", "omarchy.media", "omarchy.notifications"]
+ readonly property string stateRoot: ".local/state/omarchy"
+ readonly property string togglesDir: stateRoot + "/toggles"
+ readonly property string barOffMarker: togglesDir + "/bar-off"
+ // Suite-side legacy alias, not a pinned host path; retained for compatibility.
+ readonly property string barHiddenMarker: togglesDir + "/bar-hidden"
+ readonly property string themeDir: stateRoot + "/current/theme"
+ readonly property string themeColorsFile: themeDir + "/colors.toml"
+ readonly property string themeNameFile: stateRoot + "/current/theme.name"
+ readonly property var ipcTargets: ({
+ bar: "omarchy.bar", audio: "omarchy.audio",
+ bluetooth: "omarchy.bluetooth", network: "omarchy.network"
+ })
+ readonly property string shellCommand: "omarchy-shell"
+
+ function isFirstPartyService(id) {
+ return firstPartyServices.indexOf(String(id || "")) >= 0
+ }
+ function path(home, key) { return home + "/" + contract[key] }
+ function fileName(key) { return contract[key].split("/").pop() }
+}
diff --git a/hancore.shibumi.state/runtime/HostShell.qml b/hancore.shibumi.state/runtime/HostShell.qml
index d348038d..581fee18 100644
--- a/hancore.shibumi.state/runtime/HostShell.qml
+++ b/hancore.shibumi.state/runtime/HostShell.qml
@@ -31,7 +31,7 @@ QtObject {
function firstPartyServiceFor(id) {
const key = String(id || "")
- if (["omarchy.idle", "omarchy.media", "omarchy.notifications"].indexOf(key) < 0) return null
+ if (!Local.HostContract.isFirstPartyService(key)) return null
if (scoped) return Local.Runtime.firstPartyServiceFor(key)
return host && typeof host.firstPartyServiceFor === "function"
? host.firstPartyServiceFor(key) : null
diff --git a/hancore.shibumi.state/runtime/Runtime.qml b/hancore.shibumi.state/runtime/Runtime.qml
index fb45752c..bd747689 100644
--- a/hancore.shibumi.state/runtime/Runtime.qml
+++ b/hancore.shibumi.state/runtime/Runtime.qml
@@ -3,6 +3,7 @@ pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Io
+import "." as Local
// Cooperative suite wiring, not a QML security boundary. Native backends and
// their workers stay in their existing service owners. No host discovery here.
@@ -94,7 +95,7 @@ QtObject {
property IpcHandler visibilityIpc: IpcHandler {
enabled: runtime.visibilityIpcArmed
&& runtime.visibilityBarOwner !== null
- target: "omarchy.bar"
+ target: Local.HostContract.ipcTargets.bar
function syncHidden(): void {
const owner = runtime.visibilityBarOwner
@@ -470,7 +471,7 @@ QtObject {
function firstPartyServiceFor(id) {
const key = String(id || "")
- if (["omarchy.idle", "omarchy.media", "omarchy.notifications"].indexOf(key) < 0) return null
+ if (!Local.HostContract.isFirstPartyService(key)) return null
const lease = _selected("hancore.shibumi.bar")
return lease && typeof lease.host.firstPartyServiceFor === "function"
? lease.host.firstPartyServiceFor(key) : null
diff --git a/hancore.shibumi.state/runtime/qmldir b/hancore.shibumi.state/runtime/qmldir
index 813e5888..3aa0e4f2 100644
--- a/hancore.shibumi.state/runtime/qmldir
+++ b/hancore.shibumi.state/runtime/qmldir
@@ -1,3 +1,4 @@
singleton Runtime 1.0 Runtime.qml
+singleton HostContract 1.0 HostContract.qml
Provider 1.0 Provider.qml
HostShell 1.0 HostShell.qml
diff --git a/scripts/collect-release-evidence b/scripts/collect-release-evidence
index ba26d34a..1efb5548 100755
--- a/scripts/collect-release-evidence
+++ b/scripts/collect-release-evidence
@@ -19,16 +19,21 @@ from typing import Any, Callable
ROOT = Path(__file__).resolve().parents[1]
-HOST_PROBES: tuple[tuple[str, tuple[str, ...], str], ...] = (
- ("omarchyPackage", ("pacman", "-Q", "omarchy"), "omarchy 4.0.4-"),
- (
- "omarchySettingsPackage",
- ("pacman", "-Q", "omarchy-settings"),
- "omarchy-settings 4.0.4-",
- ),
- ("quickshellPackage", ("pacman", "-Q", "quickshell"), "quickshell 0.3.1-"),
- ("quickshellVersion", ("quickshell", "--version"), "Quickshell 0.3.1"),
-)
+HOST_PROBE_MANIFEST = Path("contracts/baselines/omarchy-installed-package-v4.0.4.json")
+
+
+def host_probe_expectations() -> tuple[tuple, dict[str, str]]:
+ raw = (ROOT / HOST_PROBE_MANIFEST).read_bytes()
+ manifest = json.loads(raw)
+ versions = {p["name"]: p["version"].rsplit("-", 1)[0]
+ for p in [*manifest["provenance"]["packages"], manifest["quickshellPackage"]]}
+ probes = (
+ ("omarchyPackage", ("pacman", "-Q", "omarchy"), f"omarchy {versions['omarchy']}-"),
+ ("omarchySettingsPackage", ("pacman", "-Q", "omarchy-settings"), f"omarchy-settings {versions['omarchy-settings']}-"),
+ ("quickshellPackage", ("pacman", "-Q", "quickshell"), f"quickshell {versions['quickshell']}-"),
+ ("quickshellVersion", ("quickshell", "--version"), f"Quickshell {versions['quickshell']}"),
+ )
+ return probes, {"path": str(HOST_PROBE_MANIFEST), "sha256": hashlib.sha256(raw).hexdigest()}
GATE_TIMEOUT_SECONDS = 1800
GATE_TERMINATION_GRACE_SECONDS = 10
@@ -918,6 +923,13 @@ def main() -> int:
):
print("--expected-commit must be a full lowercase commit id", file=sys.stderr)
return 2
+ try:
+ probe_expectations, probe_source = host_probe_expectations()
+ except (OSError, ValueError, KeyError, TypeError, AttributeError) as error:
+ print(json.dumps({"error": "invalid-host-probe-manifest",
+ "path": str(ROOT / HOST_PROBE_MANIFEST),
+ "reason": type(error).__name__}), file=sys.stderr)
+ return 2
try:
candidate_preflight = capture_git_checkout_identity(ROOT)
except (OSError, ValueError):
@@ -976,7 +988,7 @@ def main() -> int:
host_probes = [probe_omarchy_package()]
host_probes.extend(
probe_host_identity(name, command, prefix)
- for name, command, prefix in HOST_PROBES
+ for name, command, prefix in probe_expectations
if name != "omarchyPackage"
)
all_passed = release_host_probes_pass(host_probes)
@@ -1056,6 +1068,7 @@ def main() -> int:
},
"environment": {
"hostProbes": host_probes,
+ "hostProbeExpectationsSource": probe_source,
},
"baselines": manifests,
"baselinePreflights": baseline_preflight_evidence,
diff --git a/scripts/shared_runtime_contract.py b/scripts/shared_runtime_contract.py
index 0156acd3..4b83f8b6 100644
--- a/scripts/shared_runtime_contract.py
+++ b/scripts/shared_runtime_contract.py
@@ -13,7 +13,7 @@
from scripts.qml_import_lexer import ImportLexError, tokens
MODULE = Path("hancore.shibumi.state/runtime")
-MODULE_FILES = frozenset({"qmldir", "Runtime.qml", "Provider.qml", "HostShell.qml"})
+MODULE_FILES = frozenset({"qmldir", "Runtime.qml", "Provider.qml", "HostShell.qml", "HostContract.qml"})
IMPORTERS = frozenset({
"hancore.shibumi.ai/Service.qml",
"hancore.shibumi.audio/BarWidget.qml",
@@ -32,10 +32,12 @@
"hancore.shibumi.media/Service.qml",
"hancore.shibumi.memory/BarWidget.qml",
"hancore.shibumi.network/Service.qml",
+ "hancore.shibumi.network/NetworkPanelBridge.qml",
"hancore.shibumi.power-profile/BarWidget.qml",
"hancore.shibumi.power-state/Service.qml",
"hancore.shibumi.quick-access/Service.qml",
"hancore.shibumi.reactor/Service.qml",
+ "hancore.shibumi.reactor/BoundedTextSource.qml",
"hancore.shibumi.status/Service.qml",
"hancore.shibumi.storage/BarWidget.qml",
"hancore.shibumi.storage/Service.qml",
diff --git a/tests/audio-network-ipc-contract-regression.sh b/tests/audio-network-ipc-contract-regression.sh
index 39a48439..5e61de81 100755
--- a/tests/audio-network-ipc-contract-regression.sh
+++ b/tests/audio-network-ipc-contract-regression.sh
@@ -18,10 +18,10 @@ network_service="$repo_root/hancore.shibumi.network/Service.qml"
network_widget="$repo_root/hancore.shibumi.network/BarWidget.qml"
network_bridge="$repo_root/hancore.shibumi.network/NetworkPanelBridge.qml"
-[[ $(rg -l 'target: "omarchy\.audio"' \
+[[ $(rg -l 'target: ("omarchy\.audio"|SuiteRuntime\.HostContract\.ipcTargets\.audio)' \
"$audio_service" "$audio_widget" "$audio_bridge" | wc -l) -eq 1 ]] \
|| fail 'Audio does not expose exactly one process-wide compatibility target'
-rg -Fq 'target: "omarchy.audio"' "$audio_service" \
+rg -Fq 'target: SuiteRuntime.HostContract.ipcTargets.audio' "$audio_service" \
|| fail 'Audio compatibility target is not owned by the process-wide service'
for method in open close show hide toggle; do
rg -q "function ${method}\\(\\)" "$audio_service" \
@@ -42,10 +42,10 @@ rg -Fq 'candidate.open = false' "$audio_bridge" \
rg -Fq 'candidate.visible = false' "$audio_bridge" \
|| fail 'hidden Audio KeyboardPanel can flash before redirect'
-[[ $(rg -l 'target: "omarchy\.network"' \
+[[ $(rg -l 'target: ("omarchy\.network"|SuiteRuntime\.HostContract\.ipcTargets\.network)' \
"$network_service" "$network_widget" "$network_bridge" | wc -l) -eq 1 ]] \
|| fail 'Network does not expose exactly one compatibility target'
-rg -Fq 'target: "omarchy.network"' "$network_bridge" \
+rg -Fq 'target: SuiteRuntime.HostContract.ipcTargets.network' "$network_bridge" \
|| fail 'native Network bridge does not own the compatibility target'
if rg -q 'IpcHandler[[:space:]]*\{' \
"$network_service" "$network_widget"; then
diff --git a/tests/bar-host-registry-regression.sh b/tests/bar-host-registry-regression.sh
index a4c7fce5..3929c653 100755
--- a/tests/bar-host-registry-regression.sh
+++ b/tests/bar-host-registry-regression.sh
@@ -22,7 +22,7 @@ for endpoint in 'function prepareShutdown(): string' \
rg -Fq "$endpoint" "$repo_root/hancore.shibumi.bar/Bar.qml" \
|| fail "missing Shibumi Control Center IPC endpoint: $endpoint"
done
-[[ $(rg -Fc 'target: "omarchy.bar"' \
+[[ $(rg -Fc 'target: Local.HostContract.ipcTargets.bar' \
"$repo_root/hancore.shibumi.state/runtime/Runtime.qml") -eq 1 ]] \
|| fail 'shared runtime does not uniquely own the Omarchy visibility nudge'
rg -Fq 'enabled: runtime.visibilityIpcArmed' \
@@ -172,7 +172,7 @@ if source.count(old) != 1:
path.write_text(source.replace(old, new))
PY
mkdir -p "$lifecycle_root/hancore.shibumi.state"
-cp -a "$repo_root/hancore.shibumi.state/lib" \
+cp -a "$repo_root/hancore.shibumi.state/lib" "$repo_root/hancore.shibumi.state/runtime" \
"$lifecycle_root/hancore.shibumi.state/"
install -m 0644 "$widget_slot_source" \
"$lifecycle_root/staged/barcore/WidgetSlot.qml"
@@ -403,16 +403,16 @@ import sys
path = Path(sys.argv[1])
source = path.read_text()
watch = ''' FileView {
- path: root.hostReady ? root.home + "/.local/state/omarchy/toggles" : ""
+ path: root.hostReady ? SuiteRuntime.HostContract.path(root.home, "togglesDir") : ""
watchChanges: true
printErrors: false
onFileChanged: root.requestBarHiddenProbe()
}'''
quiet_watch = " Item {} // fixture: ordinary bar-off directory watcher disabled"
-command = ' command: ["bash", "-lc", "[[ -f $HOME/.local/state/omarchy/toggles/bar-off || -f $HOME/.local/state/omarchy/toggles/bar-hidden ]] && echo yes || echo no"]'
-delayed = (' command: ["bash", "-lc", "rm -f \\\"$HOME/probe-sampled\\\"; '
- 'if [[ -f $HOME/.local/state/omarchy/toggles/bar-off || -f $HOME/.local/state/omarchy/toggles/bar-hidden ]]; then result=yes; '
- 'else result=no; fi; : > \\\"$HOME/probe-sampled\\\"; sleep 0.15; echo \\\"$result\\\""]')
+command = ' command: ["bash", "-lc", "[[ -f " + SuiteRuntime.HostContract.path("$HOME", "barOffMarker") + " || -f " + SuiteRuntime.HostContract.path("$HOME", "barHiddenMarker") + " ]] && echo yes || echo no"]'
+delayed = command.replace('"[[ -f ', '"rm -f \\\"$HOME/probe-sampled\\\"; if [[ -f ').replace(
+ ' ]] && echo yes || echo no"]',
+ ' ]]; then result=yes; else result=no; fi; : > \\\"$HOME/probe-sampled\\\"; sleep 0.15; echo \\\"$result\\\""]')
if source.count(watch) != 1 or source.count(command) != 1:
raise SystemExit("visibility fixture calibration anchor drifted")
path.write_text(source.replace(watch, quiet_watch).replace(command, delayed))
diff --git a/tests/contract-regression.sh b/tests/contract-regression.sh
index fa2a0a24..e4a1fa3b 100755
--- a/tests/contract-regression.sh
+++ b/tests/contract-regression.sh
@@ -900,7 +900,7 @@ rg -q 'serviceFor\("hancore\.shibumi\.bluetooth"\)' "$bluetooth_widget" \
|| fail "Bluetooth view does not resolve the shipped service"
rg -q 'property var sessionOwners: \[\]' "$bluetooth_service" \
|| fail "Bluetooth service lacks multi-output session accounting"
-rg -q 'target: "omarchy\.bluetooth"' "$bluetooth_service" \
+rg -q 'target: SuiteRuntime\.HostContract\.ipcTargets\.bluetooth\b' "$bluetooth_service" \
|| fail "Bluetooth service does not own the single legacy IPC target"
rg -q 'bar\.hideBarWidget\("omarchy\.bluetooth"\)' "$bluetooth_service" \
|| fail "Bluetooth legacy close/hide is not routed to the local panel"
diff --git a/tests/network-ipc-routing-regression.sh b/tests/network-ipc-routing-regression.sh
index f12fec53..3899a69a 100755
--- a/tests/network-ipc-routing-regression.sh
+++ b/tests/network-ipc-routing-regression.sh
@@ -24,6 +24,8 @@ mkdir -p "$tmpdir/home" "$tmpdir/runtime" "$tmpdir/network"
chmod 700 "$tmpdir/runtime"
cp "$repo_root/tests/network-ipc-routing-smoke.qml" "$tmpdir/shell.qml"
cp "$repo_root/hancore.shibumi.network/NetworkPanelBridge.qml" "$tmpdir/network/"
+mkdir -p "$tmpdir/hancore.shibumi.state"
+cp -a "$repo_root/hancore.shibumi.state/runtime" "$tmpdir/hancore.shibumi.state/"
QT_QPA_PLATFORM=offscreen WAYLAND_DISPLAY='' \
HOME="$tmpdir/home" XDG_RUNTIME_DIR="$tmpdir/runtime" \
diff --git a/tests/network-plugin-regression.sh b/tests/network-plugin-regression.sh
index 0b132b65..acc2387c 100755
--- a/tests/network-plugin-regression.sh
+++ b/tests/network-plugin-regression.sh
@@ -1253,7 +1253,7 @@ if rg -q 'InlineSpeedTestRunner|omarchy-network-|\bnmcli\b' \
"$repo_root/hancore.shibumi.network"; then
fail "Network cutover retained a legacy Omarchy/nmcli backend"
fi
-[[ $(rg -c 'target: "omarchy\.network"' "$bridge") -eq 1 ]] \
+[[ $(rg -c 'target: SuiteRuntime\.HostContract\.ipcTargets\.network\b' "$bridge") -eq 1 ]] \
|| fail "Network compatibility IPC does not have exactly one owner"
if rg -q 'Loader|panelSource|panelComponent|omarchy\.wifiqr|omarchy\.speedtest' \
"$bridge"; then
diff --git a/tests/quattro-contract-regression.sh b/tests/quattro-contract-regression.sh
index 4865f7a8..ab694196 100755
--- a/tests/quattro-contract-regression.sh
+++ b/tests/quattro-contract-regression.sh
@@ -153,6 +153,32 @@ for needle in \
|| fail "KeyboardPanel compatibility surface drift: $needle"
done
+# Bind one constant-only compatibility layer to the already pinned host tree.
+python3 - "$repo_root" "$omarchy_path" <<'PY'
+import json, re, sys
+from pathlib import Path
+suite, native = map(Path, sys.argv[1:])
+contract = (suite / "hancore.shibumi.state/runtime/HostContract.qml").read_text()
+values = {}
+for key, expression in re.findall(r'readonly property string (\w+): ([^\n]+)', contract):
+ parts = [part.strip() for part in expression.split("+")]
+ values[key] = "".join(json.loads(p) if p.startswith('"') else values[p] for p in parts)
+services = json.loads(re.search(r'firstPartyServices:\s*(\[[^\]]+\])', contract)[1])
+targets = re.findall(r'"([^"]+)"', re.search(r'ipcTargets:\s*\(\{(.*?)\}\)', contract, re.S)[1])
+assert len(services) == 3 and len(targets) == 4, "incomplete HostContract identities"
+ids = {json.loads(p.read_text()).get("id") for p in (native / "shell/plugins").rglob("*manifest.json")}
+assert set(services) <= ids, f"host first-party IDs missing: {set(services) - ids}"
+qml = "\n".join(p.read_text() for p in sorted((native / "shell").rglob("*.qml")))
+for target in targets:
+ route = r'\b(?:target|ipcTarget)\s*:\s*"' + re.escape(target) + '"'
+ assert re.search(route, qml), f"host IPC identity missing: {target}"
+commands = "\n".join(p.read_text(errors="replace") for p in sorted((native / "bin").glob("omarchy-*")) if p.is_file())
+for key in ("stateRoot", "togglesDir", "themeDir", "themeColorsFile", "themeNameFile", "barOffMarker"):
+ assert re.search(r'(?:\$HOME/|~/|"/)' + re.escape(values[key]) + r'(?=["\s]|$)', commands + qml), f"host path missing: {key}={values[key]}"
+# barHiddenMarker is a suite-side legacy alias, not a claimed host path.
+print("HostContract source binding passed: first-party IDs, IPC routes, host marker/theme paths")
+PY
+
omarchy_version=unknown
for package_name in omarchy omarchy-dev; do
if package_version=$(pacman -Q "$package_name" 2>/dev/null | awk '{print $2}'); then
diff --git a/tests/quick-access-plugin-regression.sh b/tests/quick-access-plugin-regression.sh
index 5c7e5fc1..fb5159ec 100755
--- a/tests/quick-access-plugin-regression.sh
+++ b/tests/quick-access-plugin-regression.sh
@@ -270,7 +270,7 @@ for command in omarchy-theme-switcher omarchy-theme-bg-switcher; do
rg -Fq "\"$command\"" "$service" \
|| fail "official Omarchy image picker adapter is missing: $command"
done
-rg -Fq '["omarchy-shell", "image-selector", "cancel"]' "$service" \
+rg -Fq '[SuiteRuntime.HostContract.shellCommand, "image-selector", "cancel"]' "$service" \
|| fail "official Omarchy image picker cannot be cancelled"
rg -q 'usingOfficialPicker' "$service" \
|| fail "official image picker is not isolated from the Shibumi overlay"
diff --git a/tests/reactor-text-source-regression.py b/tests/reactor-text-source-regression.py
index 376dc819..73b877f1 100644
--- a/tests/reactor-text-source-regression.py
+++ b/tests/reactor-text-source-regression.py
@@ -11,7 +11,7 @@
from lib.isolated_files import read_regular
from lib.isolated_process import run_bounded
from lib.owned_cgroup import OwnedCgroup
-from lib.source_snapshot import materialize
+from lib.source_snapshot import materialize, snapshot
REPO = Path(__file__).resolve().parents[1]
PLUGIN = REPO / 'hancore.shibumi.reactor'
@@ -161,6 +161,8 @@ def controller_check(scenario, mutation=None):
'reactor/scripts/read-reactor-text.py': (helper.encode(), False),
'shell.qml': (read_regular(REPO / 'tests/reactor-text-source-smoke.qml'), False),
}
+ files.update({'hancore.shibumi.state/runtime/' + name: value
+ for name, value in snapshot(REPO / 'hancore.shibumi.state/runtime').items()})
with tempfile.TemporaryDirectory(prefix='reactor-source-') as temp:
root = Path(temp)
materialize(files, root)
diff --git a/tests/scoped-state-regression.py b/tests/scoped-state-regression.py
index b3920167..d9f1a719 100644
--- a/tests/scoped-state-regression.py
+++ b/tests/scoped-state-regression.py
@@ -123,7 +123,7 @@ def run_case(base, mode, api, state_source, probe, runtime_fixture=False, runtim
(directory / "host/PluginShellApi.qml").write_bytes(api)
(directory / "plugin/Service.qml").write_bytes(state_source)
(directory / "plugin/runtime").mkdir()
- for name in ("qmldir", "Runtime.qml", "Provider.qml", "HostShell.qml"):
+ for name in ("qmldir", "Runtime.qml", "Provider.qml", "HostShell.qml", "HostContract.qml"):
data = runtime_source if name == "Runtime.qml" and runtime_source is not None else bounded_read(
ROOT / "hancore.shibumi.state/runtime" / name)
(directory / "plugin/runtime" / name).write_bytes(data)
diff --git a/tests/shared-runtime-import-regression.py b/tests/shared-runtime-import-regression.py
index 5e541dbb..cac2e039 100644
--- a/tests/shared-runtime-import-regression.py
+++ b/tests/shared-runtime-import-regression.py
@@ -74,9 +74,9 @@ def test_complete_current_importer_roster_through_public_checker(self):
"control-center/BarWidget.qml", "control-center/PluginUpdateService.qml",
"cpu/BarWidget.qml", "cpu/Service.qml", "gpu/BarWidget.qml",
"media/BarWidget.qml", "media/Service.qml", "memory/BarWidget.qml",
- "network/Service.qml", "power-profile/BarWidget.qml",
+ "network/Service.qml", "network/NetworkPanelBridge.qml", "power-profile/BarWidget.qml",
"power-state/Service.qml", "quick-access/Service.qml",
- "reactor/Service.qml", "status/Service.qml", "storage/BarWidget.qml",
+ "reactor/Service.qml", "reactor/BoundedTextSource.qml", "status/Service.qml", "storage/BarWidget.qml",
"storage/Service.qml", "telemetry/Service.qml",
"temperature/BarWidget.qml", "update-center/Service.qml",
"workspaces/BarWidget.qml", "workspaces/WorkspaceService.qml")}
diff --git a/tests/state-storage-regression.py b/tests/state-storage-regression.py
index d37fc573..90c9f3a0 100644
--- a/tests/state-storage-regression.py
+++ b/tests/state-storage-regression.py
@@ -27,7 +27,7 @@ def main():
sources["state/" + name] = (read_regular(ROOT / "hancore.shibumi.state" / name, 65536), False)
if args.service:
for name in ("Service.qml", "ThemePalette.qml", "ThemePaletteModel.js", "runtime/qmldir",
- "runtime/Runtime.qml", "runtime/Provider.qml", "runtime/HostShell.qml"):
+ "runtime/Runtime.qml", "runtime/Provider.qml", "runtime/HostShell.qml", "runtime/HostContract.qml"):
sources["state/" + name] = (read_regular(ROOT / "hancore.shibumi.state" / name, 65536), False)
sources["state/.shibumi-managed.json"] = (json.dumps({
"suiteId": "hancore.shibumi", "suitePayloadDigest": "a" * 64}).encode(), False)
diff --git a/tests/test_package_release.py b/tests/test_package_release.py
index 09f41ae1..b86b8d1e 100644
--- a/tests/test_package_release.py
+++ b/tests/test_package_release.py
@@ -925,7 +925,7 @@ def test_release_evidence_host_probes_fail_closed_and_logs_are_redacted(self) ->
module = importlib.util.module_from_spec(spec)
loader.exec_module(module)
- for name, command, prefix in module.HOST_PROBES:
+ for name, command, prefix in module.host_probe_expectations()[0]:
with self.subTest(probe=name, failure="missing"):
with patch.object(
module.subprocess, "run", side_effect=FileNotFoundError("missing")
diff --git a/tests/third-party-integration-regression.sh b/tests/third-party-integration-regression.sh
index 2c9e6352..1cd4b986 100755
--- a/tests/third-party-integration-regression.sh
+++ b/tests/third-party-integration-regression.sh
@@ -26,6 +26,12 @@ cleanup() {
}
trap cleanup EXIT
+targets=$(rg -o '\b(bar|audio|bluetooth|network): "[^"]+"' \
+ "$repo_root/hancore.shibumi.state/runtime/HostContract.qml" | cut -d '"' -f2 | sort)
+reserved=$(jq -r '.dynamicResources[] | select(.kind == "ipc") | .resource' \
+ "$repo_root/contracts/plugin-integration-resources.json" | sort)
+[[ $targets == "$reserved" ]] || fail 'HostContract IPC reservations drifted'
+
[[ -x $validator ]] || fail 'authoritative Omarchy plugin validator is missing'
[[ -x $gate ]] || fail 'Shibumi integration gate is missing'
for fixture in conforming duplicate-ipc duplicate-service duplicate-network \
From c9facd58904cb7e69a3870ea7b2e783dd06e6720 Mon Sep 17 00:00:00 2001
From: HANCORE-linux <230438592+HANCORE-linux@users.noreply.github.com>
Date: Tue, 13 Oct 2026 23:53:57 +0200
Subject: [PATCH 10/18] test(harness): share host startup diagnostics and one
quickshell fixture runner
---
docs/development/testing.md | 24 ++++++++
tests/ai-plugin-regression.sh | 6 +-
tests/audio-media-plugins-regression.sh | 19 +++---
tests/bar-host-registry-regression.sh | 3 +-
tests/core-services-regression.sh | 3 +-
tests/drag-ghost-render-regression.sh | 4 +-
tests/host-registry-prime-regression.sh | 3 +-
tests/isolated-process-regression.py | 10 ++--
tests/lib/host-diagnostics.sh | 11 ++++
tests/lib/host-diagnostics.txt | 3 +
tests/lib/isolated_process.py | 50 +++++++++++++---
tests/lib/qs-runner.sh | 46 +++++++++++++++
tests/notification-adapter-regression.sh | 41 +++----------
tests/panel-window-geometry-regression.sh | 65 ++++++---------------
tests/power-plugins-regression.sh | 7 ++-
tests/quick-access-plugin-regression.sh | 14 +++--
tests/reactor-plugin-regression.sh | 5 +-
tests/scoped-loader-admission-regression.sh | 54 +++--------------
tests/status-plugin-regression.sh | 4 +-
tests/telemetry-plugins-regression.sh | 3 +-
tests/workspaces-plugin-regression.sh | 5 +-
21 files changed, 209 insertions(+), 171 deletions(-)
create mode 100644 tests/lib/host-diagnostics.sh
create mode 100644 tests/lib/host-diagnostics.txt
create mode 100644 tests/lib/qs-runner.sh
diff --git a/docs/development/testing.md b/docs/development/testing.md
index d094a994..f9c04a3f 100644
--- a/docs/development/testing.md
+++ b/docs/development/testing.md
@@ -238,6 +238,30 @@ without deriving owner readiness from its own allocated width.
It uses no desktop screenshot or physical layer surface; real Top/Bottom ghost
and mixed-scale acceptance remain separate.
+## Shared shell fixture helpers
+
+`tests/lib/host-diagnostics.txt` holds only the common missing-Hyprland-signature
+startup line and two byte-identical full Deny expressions. Shell callers source
+`host-diagnostics.sh`; substring versus whole-line matching, case sensitivity,
+additional diagnostics and exceptions remain local. Socket and module-path
+warnings are not universally accepted. Quick Access's intentional action and
+negative-control binding warnings never enter the common allowance.
+
+`qs_run_fixture [timeout]` in `tests/lib/qs-runner.sh`
+stages missing pinned Commons/Ui trees without replacing existing fixture stubs.
+Callers supply the whole fixture environment through the required `qs_fixture_env`
+array (`-i` clears the inherited one); the runner adds nothing.
+`qs_fixture_args` preserves CLI arguments. The synchronous helper
+uses `isolated_process.py` for bounded capture, deadline and owned-group cleanup;
+it preserves captured bytes in `qs-runner.log`, including timeout/error exits, and
+writes runner diagnostics to `qs-runner-error.log`. `qs-runner-survivors` records
+live descendants before the cleanup kill; Panel Geometry requires zero, and the
+shared cleanup verifies no live members remain before reaping its reserved leader.
+The combined output limit is 4 MiB; exceeding it exits 125 with the bounded prefix.
+A descendant holding stdout open keeps capture waiting until the deadline.
+The caller removes its workdir.
+QML, qmltestrunner, Python and nested-Wayland runners keep their existing policies.
+
## Asynchronous Control Center restoration
`tests/bar-host-registry-regression.sh` exercises the actual Bar restoration
diff --git a/tests/ai-plugin-regression.sh b/tests/ai-plugin-regression.sh
index 12f73663..470b897e 100755
--- a/tests/ai-plugin-regression.sh
+++ b/tests/ai-plugin-regression.sh
@@ -4,6 +4,7 @@ set -euo pipefail
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
source "$repo_root/tests/lib/baselines.sh"
+source "$repo_root/tests/lib/host-diagnostics.sh"
shibumi_load_omarchy_baseline
omarchy_path=$OMARCHY_PATH
quickshell_bin=${QUICKSHELL_BIN:-/usr/bin/quickshell}
@@ -81,9 +82,8 @@ printf '%s\n' "$output"
grep -F 'ai plugin smoke passed' <<<"$output" >/dev/null \
|| fail "success marker missing"
if grep -E ' WARN| ERROR|CRITICAL|TypeError|ReferenceError|Unable to assign' <<<"$output" \
- | grep -Fv \
- -e 'Unable to find hyprland socket. Cannot connect to hyprland.' \
- -e '$HYPRLAND_INSTANCE_SIGNATURE is unset. Cannot connect to hyprland.' >/dev/null; then
+ | host_filter_startup_diagnostics \
+ -e 'Unable to find hyprland socket. Cannot connect to hyprland.' >/dev/null; then
fail "component smoke emitted a QML/runtime warning or error"
fi
diff --git a/tests/audio-media-plugins-regression.sh b/tests/audio-media-plugins-regression.sh
index 556c18dd..6ea1d64d 100755
--- a/tests/audio-media-plugins-regression.sh
+++ b/tests/audio-media-plugins-regression.sh
@@ -4,6 +4,7 @@ set -euo pipefail
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
source "$repo_root/tests/lib/baselines.sh"
+source "$repo_root/tests/lib/qs-runner.sh"
shibumi_load_omarchy_baseline
omarchy_path=$OMARCHY_PATH
quickshell_bin=${QUICKSHELL_BIN:-/usr/bin/quickshell}
@@ -48,15 +49,16 @@ run_smoke() {
cp "$repo_root/tests/fixtures/ShibumiPanelTest.qml" "$smoke_root/media/ShibumiPanel.qml"
fi
- set +e
- local output rc
- output=$(timeout 8 env \
+ local -a qs_fixture_env=(
QT_QPA_PLATFORM=offscreen \
WAYLAND_DISPLAY= \
XDG_RUNTIME_DIR="$smoke_root/runtime" \
QML_IMPORT_PATH="$omarchy_path/shell${QML_IMPORT_PATH:+:$QML_IMPORT_PATH}" \
QML2_IMPORT_PATH="$omarchy_path/shell${QML2_IMPORT_PATH:+:$QML2_IMPORT_PATH}" \
- "$quickshell_bin" -p "$smoke_root" 2>&1)
+ )
+ set +e
+ local output rc
+ output=$(qs_run_fixture "$smoke_root/shell.qml" "$smoke_root" 8 2>&1)
rc=$?
set -e
@@ -92,9 +94,7 @@ run_spectrum_smoke() {
"$smoke_root/bin/cava"
fi
- set +e
- local output rc
- output=$(timeout 12 env \
+ local -a qs_fixture_env=(
PATH="$smoke_root/bin:/usr/bin:/bin" \
SHIBUMI_CAVA_TEST_MODE="$mode" \
QT_QPA_PLATFORM=offscreen \
@@ -102,7 +102,10 @@ run_spectrum_smoke() {
XDG_RUNTIME_DIR="$smoke_root/runtime" \
QML_IMPORT_PATH="$omarchy_path/shell${QML_IMPORT_PATH:+:$QML_IMPORT_PATH}" \
QML2_IMPORT_PATH="$omarchy_path/shell${QML2_IMPORT_PATH:+:$QML2_IMPORT_PATH}" \
- "$quickshell_bin" -p "$smoke_root" 2>&1)
+ )
+ set +e
+ local output rc
+ output=$(qs_run_fixture "$smoke_root/shell.qml" "$smoke_root" 12 2>&1)
rc=$?
set -e
diff --git a/tests/bar-host-registry-regression.sh b/tests/bar-host-registry-regression.sh
index 3929c653..7785b104 100755
--- a/tests/bar-host-registry-regression.sh
+++ b/tests/bar-host-registry-regression.sh
@@ -4,6 +4,7 @@ set -euo pipefail
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
source "$repo_root/tests/lib/baselines.sh"
+source "$repo_root/tests/lib/host-diagnostics.sh"
shibumi_load_omarchy_baseline
bar_root="$repo_root/hancore.shibumi.bar"
omarchy_path=$OMARCHY_PATH
@@ -326,7 +327,7 @@ printf '%s\n' "$nested_output"
[[ $nested_rc -eq 0 ]] || fail "nested hosted-panel smoke exited $nested_rc"
grep -q 'hosted panel loader smoke passed' <<<"$nested_output" \
|| fail 'nested hosted-panel smoke did not reach its success marker'
-if grep -Eq 'Binding loop|TypeError|ReferenceError|is not a type|failed to load' \
+if grep -Eq "$HOST_DIAGNOSTICS_COMPOSITION" \
<<<"$nested_output"; then
fail 'nested hosted-panel runtime log contains a composition error'
fi
diff --git a/tests/core-services-regression.sh b/tests/core-services-regression.sh
index 60244637..7d5964bf 100755
--- a/tests/core-services-regression.sh
+++ b/tests/core-services-regression.sh
@@ -3,6 +3,7 @@
set -euo pipefail
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
+source "$repo_root/tests/lib/host-diagnostics.sh"
fail() {
printf 'core services regression failed: %s\n' "$*" >&2
@@ -43,7 +44,7 @@ grep -q 'Configuration Loaded' <<<"$output" \
|| fail "configuration did not load"
grep -q 'core services smoke passed' <<<"$output" \
|| fail "runtime assertions did not complete"
-if grep -Eq 'TypeError|ReferenceError|Binding loop|Unable to assign|Internal error|Cannot assign' <<<"$output"; then
+if grep -Eq "$HOST_DIAGNOSTICS_BINDING" <<<"$output"; then
fail "QML runtime error"
fi
diff --git a/tests/drag-ghost-render-regression.sh b/tests/drag-ghost-render-regression.sh
index a9e28d7c..ac43ff8c 100755
--- a/tests/drag-ghost-render-regression.sh
+++ b/tests/drag-ghost-render-regression.sh
@@ -2,6 +2,7 @@
set -euo pipefail
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
source "$repo_root/tests/lib/baselines.sh"
+source "$repo_root/tests/lib/host-diagnostics.sh"
shibumi_load_omarchy_baseline
fixture=$(mktemp -d /tmp/sb-ghost.XXXXXXXX)
trap 'rm -rf -- "$fixture"' EXIT
@@ -29,8 +30,7 @@ for scale in 1 1.5; do
}
grep -q 'drag ghost render regression passed' "$fixture/runtime.log"
if grep -E 'Binding loop|TypeError|ReferenceError|WARN|ERROR' "$fixture/runtime.log" \
- | grep -Fv \
- -e '$HYPRLAND_INSTANCE_SIGNATURE is unset. Cannot connect to hyprland.' >/dev/null; then
+ | host_filter_startup_diagnostics >/dev/null; then
tail -60 "$fixture/runtime.log" >&2
exit 1
fi
diff --git a/tests/host-registry-prime-regression.sh b/tests/host-registry-prime-regression.sh
index ea6e635d..fa3acd36 100755
--- a/tests/host-registry-prime-regression.sh
+++ b/tests/host-registry-prime-regression.sh
@@ -3,6 +3,7 @@
set -euo pipefail
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
+source "$repo_root/tests/lib/host-diagnostics.sh"
tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/shibumi-host-registry-prime.XXXXXX")
cleanup() { rm -rf -- "$tmpdir"; }
trap cleanup EXIT
@@ -136,7 +137,7 @@ PY
if grep -q 'registry-prime-private-path-user-data' <<<"$output"; then
fail "$mode leaked private diagnostic data"
fi
- if grep -Eq 'Binding loop|TypeError|ReferenceError|is not a type|failed to load' \
+ if grep -Eq "$HOST_DIAGNOSTICS_COMPOSITION" \
<<<"$output"; then
fail "$mode runtime log contains a composition error"
fi
diff --git a/tests/isolated-process-regression.py b/tests/isolated-process-regression.py
index df179c8f..8d26b263 100644
--- a/tests/isolated-process-regression.py
+++ b/tests/isolated-process-regression.py
@@ -20,7 +20,7 @@ def test_output_boundary_and_return_code(self):
for length in (0, 63, 64):
result = self.run_python(f'import os; os.write(1, b"x"*{length})', maximum=64)
self.assertEqual((result.returncode, len(result.stdout)), (0, length))
- with self.assertRaisesRegex(RuntimeError, "output limit"):
+ with self.assertRaisesRegex(RuntimeError, "output limit") as failure:
self.run_python('import os; os.write(1,b"x"*65)', maximum=64)
with self.assertRaisesRegex(RuntimeError, "output limit"):
self.run_python('import os; os.write(1,b"x"*32); os.write(2,b"x"*33)', maximum=64)
@@ -36,8 +36,8 @@ def test_invalid_limits_refuse_before_spawn(self):
self.run_python('raise RuntimeError("must not execute")', maximum=value)
def test_closed_pipes_do_not_bypass_deadline(self):
- with self.assertRaisesRegex(RuntimeError, "deadline"):
- self.run_python('import os,time; os.close(1); os.close(2); time.sleep(20)', timeout=.15)
+ with self.assertRaisesRegex(RuntimeError, "deadline") as failure:
+ self.run_python('import os,time; os.write(1,b"partial"); os.close(1); os.close(2); time.sleep(20)', timeout=.15)
def test_exited_parent_does_not_leave_group_worker(self):
self.check_group_worker(False)
@@ -75,7 +75,9 @@ def check_group_worker(self, closed_pipes, lifecycle_api=False):
with self.assertRaisesRegex(RuntimeError, 'already reaped'):
finish_owned_group(process)
elif closed_pipes:
- self.assertEqual(self.run_python(code, timeout=.3).returncode, 0)
+ result = self.run_python(code, timeout=.3)
+ self.assertEqual(result.returncode, 0)
+ self.assertEqual(result.survivorsBeforeKill, 1)
else:
with self.assertRaisesRegex(RuntimeError, "deadline"):
self.run_python(code, timeout=.3)
diff --git a/tests/lib/host-diagnostics.sh b/tests/lib/host-diagnostics.sh
new file mode 100644
index 00000000..6a1e0e10
--- /dev/null
+++ b/tests/lib/host-diagnostics.sh
@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+# Only identical policies are shared. Additional diagnostics remain caller-local.
+while IFS=$'\t' read -r _host_name _host_pattern; do
+ printf -v "HOST_DIAGNOSTICS_$_host_name" '%s' "$_host_pattern"
+done < "$(dirname -- "${BASH_SOURCE[0]}")/host-diagnostics.txt"
+unset _host_name _host_pattern
+
+# Callers retain substring/whole-line matching and their own extra exceptions.
+host_filter_startup_diagnostics() {
+ grep -Fv -e "$HOST_DIAGNOSTICS_STARTUP_UNSET" "$@"
+}
diff --git a/tests/lib/host-diagnostics.txt b/tests/lib/host-diagnostics.txt
new file mode 100644
index 00000000..32a97b24
--- /dev/null
+++ b/tests/lib/host-diagnostics.txt
@@ -0,0 +1,3 @@
+STARTUP_UNSET $HYPRLAND_INSTANCE_SIGNATURE is unset. Cannot connect to hyprland.
+BINDING TypeError|ReferenceError|Binding loop|Unable to assign|Internal error|Cannot assign
+COMPOSITION Binding loop|TypeError|ReferenceError|is not a type|failed to load
diff --git a/tests/lib/isolated_process.py b/tests/lib/isolated_process.py
index 3e46763c..c7e24fbe 100644
--- a/tests/lib/isolated_process.py
+++ b/tests/lib/isolated_process.py
@@ -1,6 +1,7 @@
"""Bounded capture and cleanup of a subprocess owned by an isolated fixture."""
import math
import os
+from pathlib import Path
import selectors
import signal
import subprocess
@@ -32,11 +33,33 @@ def finish_owned_group(process):
os.waitid(os.P_PID, process.pid, os.WEXITED | os.WNOHANG | os.WNOWAIT)
if os.getpgid(process.pid) != process.pid:
raise RuntimeError("owned process is not its group leader")
+ def live_members():
+ members = []
+ for path in Path('/proc').glob('[0-9]*/stat'):
+ try:
+ fields = path.read_text().rsplit(')', 1)[1].split()
+ except (FileNotFoundError, ProcessLookupError):
+ continue
+ if int(path.parent.name) != process.pid and fields[0] != 'Z' and int(fields[2]) == process.pid:
+ members.append(int(path.parent.name))
+ return members
+
try:
- os.killpg(process.pid, signal.SIGKILL)
- except ProcessLookupError:
- pass
- process.wait(timeout=1)
+ survivors = len(live_members())
+ finally:
+ try:
+ os.killpg(process.pid, signal.SIGKILL)
+ except ProcessLookupError:
+ pass
+ deadline = time.monotonic() + 1
+ try:
+ while live_members():
+ if time.monotonic() >= deadline:
+ raise RuntimeError('owned process group survived cleanup')
+ time.sleep(.01)
+ finally:
+ process.wait(timeout=1)
+ return survivors
def run_bounded(command, *, env=None, cwd=None, timeout=4, maximum=65536, pass_fds=()):
@@ -50,6 +73,8 @@ def run_bounded(command, *, env=None, cwd=None, timeout=4, maximum=65536, pass_f
output = {"stdout": bytearray(), "stderr": bytearray()}
deadline = time.monotonic() + timeout
size = 0
+ failure = None
+ survivors = None
try:
selector.register(process.stdout, selectors.EVENT_READ, "stdout")
selector.register(process.stderr, selectors.EVENT_READ, "stderr")
@@ -62,17 +87,26 @@ def run_bounded(command, *, env=None, cwd=None, timeout=4, maximum=65536, pass_f
if not data:
selector.unregister(key.fileobj)
continue
+ output[key.data].extend(data[:maximum - size])
size += len(data)
if size > maximum:
raise RuntimeError("owned process output limit: " + str(command[0]))
- output[key.data].extend(data)
# Observe exit without reaping. Keeping the owned leader's PID reserved
# prevents process-group ID reuse before descendant cleanup below.
observe_owned_exit(process, deadline)
+ except RuntimeError as error:
+ failure = error
finally:
selector.close()
process.stdout.close()
process.stderr.close()
- finish_owned_group(process)
- return subprocess.CompletedProcess(command, process.returncode,
- bytes(output["stdout"]), bytes(output["stderr"]))
+ try:
+ survivors = finish_owned_group(process)
+ except RuntimeError as error:
+ failure = error
+ result = failure or subprocess.CompletedProcess(command, process.returncode)
+ result.stdout, result.stderr = bytes(output["stdout"]), bytes(output["stderr"])
+ result.survivorsBeforeKill = survivors
+ if failure is not None:
+ raise failure
+ return result
diff --git a/tests/lib/qs-runner.sh b/tests/lib/qs-runner.sh
new file mode 100644
index 00000000..87b82f6a
--- /dev/null
+++ b/tests/lib/qs-runner.sh
@@ -0,0 +1,46 @@
+#!/usr/bin/env bash
+# One synchronous launcher; ownership, bounded capture and cleanup stay in Python.
+qs_run_fixture() {
+ local fixture=$1 workdir=$2 deadline=${3:-8}
+ local libdir host=${OMARCHY_PATH:?pinned Omarchy baseline required}
+ libdir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
+ # Existing staged trees may contain deliberate platform stubs; never replace them.
+ local module
+ for module in Commons Ui; do
+ [[ -d ${fixture%/*}/$module ]] || cp -a -- "$host/shell/$module" "${fixture%/*}/$module"
+ done
+ # Caller-local arrays preserve legacy env/env -i and optional CLI arguments.
+ local -a environment=("${qs_fixture_env[@]:?explicit fixture environment required}")
+ /usr/bin/python3 - "$libdir" "$fixture" "$workdir" "$deadline" \
+ "${quickshell_bin:-${QUICKSHELL_BIN:-/usr/bin/quickshell}}" \
+ "${#environment[@]}" "${environment[@]}" "${qs_fixture_args[@]}" <<'PY'
+import os, signal, sys
+from pathlib import Path
+sys.path.insert(0, sys.argv[1])
+from isolated_process import run_bounded
+fixture, root = map(Path, sys.argv[2:4])
+count = int(sys.argv[6]); entries = sys.argv[7:7 + count]
+env = dict(os.environ)
+if entries and entries[0] == '-i':
+ env = {}; entries = entries[1:]
+env.update(entry.split('=', 1) for entry in entries)
+source = fixture.parent if fixture.name == 'shell.qml' else fixture
+command = [sys.argv[5], '-p', str(source), *sys.argv[7 + count:]]
+signal.signal(signal.SIGTERM, signal.default_int_handler)
+try:
+ # Redirection only, not a shell process manager: exec keeps the owned leader.
+ result = run_bounded(['/bin/sh', '-c', 'exec "$@" 2>&1', 'qs-fixture', *command],
+ env=env, timeout=float(sys.argv[4]), maximum=4 * 1024 * 1024)
+except RuntimeError as error:
+ result = error
+ (root / 'qs-runner-error.log').write_text(str(error) + '\n')
+ print(error, file=sys.stderr)
+ code = 124 if str(error).startswith('owned process deadline') else 125
+else:
+ code = result.returncode if result.returncode >= 0 else 128 - result.returncode
+(root / 'qs-runner.log').write_bytes(result.stdout + result.stderr)
+(root / 'qs-runner-survivors').write_text(str(result.survivorsBeforeKill) + '\n')
+sys.stdout.buffer.write(result.stdout + result.stderr)
+raise SystemExit(code)
+PY
+}
diff --git a/tests/notification-adapter-regression.sh b/tests/notification-adapter-regression.sh
index 0f9b08d4..d8675618 100755
--- a/tests/notification-adapter-regression.sh
+++ b/tests/notification-adapter-regression.sh
@@ -4,26 +4,13 @@ set -euo pipefail
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
source "$repo_root/tests/lib/baselines.sh"
+source "$repo_root/tests/lib/qs-runner.sh"
+source "$repo_root/tests/lib/host-diagnostics.sh"
shibumi_load_omarchy_baseline
omarchy_path=$OMARCHY_PATH
quickshell_bin=${QUICKSHELL_BIN:-/usr/bin/quickshell}
tmpdir=$(mktemp -d /tmp/shibumi-notification-adapter.XXXXXX)
-fixture_pid=
-
-stop_fixture() {
- [[ -z ${fixture_pid:-} ]] && return 0
- if kill -0 -- "-$fixture_pid" 2>/dev/null; then
- kill -TERM -- "-$fixture_pid" 2>/dev/null || true
- for _ in {1..20}; do
- kill -0 -- "-$fixture_pid" 2>/dev/null || break
- sleep 0.05
- done
- kill -KILL -- "-$fixture_pid" 2>/dev/null || true
- fi
- wait "$fixture_pid" 2>/dev/null || true
- fixture_pid=
-}
-trap 'stop_fixture; rm -rf -- "$tmpdir"' EXIT
+trap 'rm -rf -- "$tmpdir"' EXIT
fail() {
printf 'notification adapter regression failed: %s\n' "$*" >&2
@@ -33,7 +20,6 @@ fail() {
[[ -d $omarchy_path/shell/Commons ]] || fail "Omarchy Commons not found"
[[ -d $omarchy_path/shell/Ui ]] || fail "Omarchy Ui not found"
[[ -x $quickshell_bin ]] || fail "Quickshell not found"
-command -v setsid >/dev/null 2>&1 || fail "setsid not found"
mkdir -p "$tmpdir/status"
cp -a -- "$repo_root/hancore.shibumi.status/." "$tmpdir/status/"
@@ -108,7 +94,7 @@ EOF
printf '%s\0' "$root/omarchy fixture/bin/omarchy-hyprland-focus-app" "$proxy_app" >"$root/expected.argv"
fi
- setsid env -i \
+ local -a qs_fixture_env=(-i
HOME="$root/home" \
XDG_CONFIG_HOME="$root/config" \
XDG_CACHE_HOME="$root/cache" \
@@ -135,27 +121,16 @@ EOF
SHIBUMI_PROXY_APP="$proxy_app" \
QML_IMPORT_PATH="$omarchy_path/shell" \
QML2_IMPORT_PATH="$omarchy_path/shell" \
- "$quickshell_bin" -p "$tmpdir" --no-color >"$output" 2>&1 &
- fixture_pid=$!
- local ticks=0
- while kill -0 "$fixture_pid" 2>/dev/null && (( ticks < 160 )); do
- sleep 0.05
- ((ticks += 1))
- done
- if kill -0 "$fixture_pid" 2>/dev/null; then
- stop_fixture
- cat "$output"
- fail "$name timed out"
- fi
+ )
+ local -a qs_fixture_args=(--no-color)
set +e
- wait "$fixture_pid"
+ qs_run_fixture "$tmpdir/shell.qml" "$root" 8 >"$output" 2>&1
local rc=$?
set -e
- fixture_pid=
cat "$output"
[[ $rc -eq 0 ]] || fail "$name smoke exited $rc"
grep -Fq "$marker" "$output" || fail "$name success marker missing"
- if grep -Eq 'Binding loop|TypeError|ReferenceError|is not a type|failed to load' "$output"; then
+ if grep -Eq "$HOST_DIAGNOSTICS_COMPOSITION" "$output"; then
fail "$name runtime log contains a composition error"
fi
if [[ $history_mode == proxy ]]; then
diff --git a/tests/panel-window-geometry-regression.sh b/tests/panel-window-geometry-regression.sh
index d596a51f..56f817e9 100755
--- a/tests/panel-window-geometry-regression.sh
+++ b/tests/panel-window-geometry-regression.sh
@@ -4,6 +4,8 @@ set -euo pipefail
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
source "$repo_root/tests/lib/baselines.sh"
+source "$repo_root/tests/lib/qs-runner.sh"
+source "$repo_root/tests/lib/host-diagnostics.sh"
shibumi_load_omarchy_baseline
fail() {
@@ -113,25 +115,10 @@ rg -Fq 'implicitWidth: bar.vertical && validScreen ? bar.barSize : 0' \
|| fail 'BarPanel source is not bar-width'
tmpdir=$(mktemp -d /tmp/shibumi-panel-geometry.XXXXXX)
-active_pid=
-cleanup_group() {
- local pid=${1:-}
- [[ -n $pid ]] || return 0
- if kill -0 "$pid" 2>/dev/null; then
- kill -TERM -- "-$pid" 2>/dev/null || true
- for _ in $(seq 1 20); do
- kill -0 "$pid" 2>/dev/null || break
- sleep 0.05
- done
- kill -KILL -- "-$pid" 2>/dev/null || true
- fi
- wait "$pid" 2>/dev/null || true
-}
-cleanup() {
- cleanup_group "$active_pid"
- rm -rf -- "$tmpdir"
-}
-trap cleanup EXIT HUP INT TERM
+trap 'rm -rf -- "$tmpdir"' EXIT
+trap 'exit 129' HUP
+trap 'exit 130' INT
+trap 'exit 143' TERM
base="$tmpdir/base"
mkdir -p "$base/fixtures" "$base/shared" "$base/hancore.shibumi.bar/core"
@@ -256,8 +243,8 @@ path.write_text(text.replace(anchor, mutant + anchor), encoding="utf-8")
PY
fi
- local log="$root/output.log" rc=124
- setsid /usr/bin/env -i \
+ local log="$root/output.log" rc
+ local -a qs_fixture_env=(-i
HOME="$root/home" LANG=C.UTF-8 LC_ALL=C.UTF-8 \
XDG_CONFIG_HOME="$root/home/.config" \
XDG_STATE_HOME="$root/home/.local/state" \
@@ -271,34 +258,16 @@ PY
QT_QPA_PLATFORM=offscreen QT_QPA_PLATFORMTHEME= \
QT_QUICK_BACKEND=software QT_FORCE_STDERR_LOGGING=1 \
QML_DISABLE_DISK_CACHE=1 \
- /usr/bin/quickshell -p "$root" >"$log" 2>&1 &
- active_pid=$!
- for _ in $(seq 1 240); do
- if ! kill -0 "$active_pid" 2>/dev/null; then
- set +e
- wait "$active_pid"
- rc=$?
- set -e
- break
- fi
- sleep 0.05
- done
- if kill -0 "$active_pid" 2>/dev/null; then
- printf 'fixture deadline\n' >>"$log"
- cleanup_group "$active_pid"
- rc=124
- fi
- local pgid=$active_pid
- active_pid=
- local survivors
- survivors=$(ps -eo pgid=,pid= | awk -v pg="$pgid" '$1 == pg { print $2 }')
- if [[ -n $survivors ]]; then
- kill -KILL -- "-$pgid" 2>/dev/null || true
- fail "$name left owned process-group members: $survivors"
- fi
+ )
+ set +e
+ qs_run_fixture "$root/shell.qml" "$root" 12 >"$log" 2>&1
+ rc=$?
+ set -e
- printf '%s\n' "--- $name rc=$rc cleanup=owned-pgid-empty ---"
cat "$log"
+ [[ -f $root/qs-runner-survivors && $(<"$root/qs-runner-survivors") == 0 ]] \
+ || fail "$name left owned process-group members (or missing cleanup evidence)"
+ printf '%s\n' "--- $name rc=$rc cleanup=owned-pgid-empty ---"
if [[ $expected == pass ]]; then
[[ $rc -eq 0 ]] || fail "$name exited $rc"
grep -Fq "$diagnostic" "$log" || fail "$name missed success marker"
@@ -307,7 +276,7 @@ PY
if grep -q 'NATIVE_' "$log"; then
fail "$name incorrectly labels component evidence as native"
fi
- if grep -Eq 'Binding loop|TypeError|ReferenceError|is not a type|failed to load' "$log"; then
+ if grep -Eq "$HOST_DIAGNOSTICS_COMPOSITION" "$log"; then
fail "$name emitted a composition error"
fi
else
diff --git a/tests/power-plugins-regression.sh b/tests/power-plugins-regression.sh
index f63ec40d..e87ef3f0 100755
--- a/tests/power-plugins-regression.sh
+++ b/tests/power-plugins-regression.sh
@@ -4,6 +4,7 @@ set -euo pipefail
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
source "$repo_root/tests/lib/baselines.sh"
+source "$repo_root/tests/lib/host-diagnostics.sh"
shibumi_load_omarchy_baseline
omarchy_path=$OMARCHY_PATH
python3 "$repo_root/tests/power-deferred-regression.py"
@@ -52,7 +53,7 @@ printf '%s\n' "$output"
[[ $rc -eq 0 ]] || fail "component smoke exited $rc"
grep -F 'power plugins smoke passed' <<<"$output" >/dev/null \
|| fail "success marker missing"
-if grep -Eq 'TypeError|ReferenceError|Binding loop|Unable to assign|Internal error|Cannot assign' <<<"$output"; then
+if grep -Eq "$HOST_DIAGNOSTICS_BINDING" <<<"$output"; then
fail "component QML runtime error"
fi
@@ -78,7 +79,7 @@ printf '%s\n' "$service_output"
[[ $service_rc -eq 0 ]] || fail "power-state service smoke exited $service_rc"
grep -F 'power service runtime smoke passed' <<<"$service_output" >/dev/null \
|| fail "power-state service success marker missing"
-if grep -Eq 'TypeError|ReferenceError|Binding loop|Unable to assign|Internal error|Cannot assign' <<<"$service_output"; then
+if grep -Eq "$HOST_DIAGNOSTICS_BINDING" <<<"$service_output"; then
fail "service QML runtime error"
fi
[[ $(<"$tmpdir/power-state") == performance ]] \
@@ -105,7 +106,7 @@ for smoke in power-command power-runtime; do
printf '%s\n' "$smoke_output"
[[ $smoke_rc -eq 0 ]] || fail "$smoke exited $smoke_rc"
grep -F "${smoke//-/ } smoke passed" <<<"$smoke_output" >/dev/null || fail "$smoke marker missing"
- if grep -Eq 'TypeError|ReferenceError|Binding loop|Unable to assign|Internal error|Cannot assign' <<<"$smoke_output"; then
+ if grep -Eq "$HOST_DIAGNOSTICS_BINDING" <<<"$smoke_output"; then
fail "$smoke QML runtime error"
fi
done
diff --git a/tests/quick-access-plugin-regression.sh b/tests/quick-access-plugin-regression.sh
index fb5159ec..1638ff5c 100755
--- a/tests/quick-access-plugin-regression.sh
+++ b/tests/quick-access-plugin-regression.sh
@@ -4,6 +4,8 @@ set -euo pipefail
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
source "$repo_root/tests/lib/baselines.sh"
+source "$repo_root/tests/lib/qs-runner.sh"
+source "$repo_root/tests/lib/host-diagnostics.sh"
shibumi_load_omarchy_baseline
omarchy_path=$OMARCHY_PATH
quickshell_bin=${QUICKSHELL_BIN:-/usr/bin/quickshell}
@@ -112,8 +114,7 @@ PY
run_smoke() {
local fixture_root=$1
: >"$fixture_root/notify.log"
- set +e
- RUN_OUTPUT=$(timeout 8 env \
+ local -a qs_fixture_env=(
HOME="$fixture_root/home" \
XDG_CONFIG_HOME="$fixture_root/config" \
XDG_STATE_HOME="$fixture_root/state" \
@@ -127,15 +128,16 @@ run_smoke() {
XDG_RUNTIME_DIR="$fixture_root/runtime" \
QML_IMPORT_PATH="$omarchy_path/shell${QML_IMPORT_PATH:+:$QML_IMPORT_PATH}" \
QML2_IMPORT_PATH="$omarchy_path/shell${QML2_IMPORT_PATH:+:$QML2_IMPORT_PATH}" \
- "$quickshell_bin" -p "$fixture_root" 2>&1)
+ )
+ set +e
+ RUN_OUTPUT=$(qs_run_fixture "$fixture_root/shell.qml" "$fixture_root" 8 2>&1)
RUN_RC=$?
set -e
RUN_PLAIN_OUTPUT=$(sed $'s/\033\\[[0-9;]*m//g' <<<"$RUN_OUTPUT")
RUN_WARNINGS=$(sed -n -E \
's/^[[:space:]]*WARN([^:]*)?:[[:space:]]*//p' <<<"$RUN_PLAIN_OUTPUT" \
- | grep -Fvx \
- -e 'Unable to find hyprland socket. Cannot connect to hyprland.' \
- -e '$HYPRLAND_INSTANCE_SIGNATURE is unset. Cannot connect to hyprland.' || true)
+ | host_filter_startup_diagnostics -x \
+ -e 'Unable to find hyprland socket. Cannot connect to hyprland.' || true)
}
validate_common_completion() {
diff --git a/tests/reactor-plugin-regression.sh b/tests/reactor-plugin-regression.sh
index 1214b4e3..918e779c 100755
--- a/tests/reactor-plugin-regression.sh
+++ b/tests/reactor-plugin-regression.sh
@@ -4,6 +4,7 @@ set -euo pipefail
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
source "$repo_root/tests/lib/baselines.sh"
+source "$repo_root/tests/lib/host-diagnostics.sh"
shibumi_load_omarchy_baseline
omarchy_path=$OMARCHY_PATH
quickshell_bin=${QUICKSHELL_BIN:-/usr/bin/quickshell}
@@ -45,7 +46,7 @@ printf '%s\n' "$output"
[[ $rc -eq 0 ]] || fail "component smoke exited $rc"
grep -F 'reactor plugin smoke passed' <<<"$output" >/dev/null \
|| fail "success marker missing"
-if grep -Eq 'TypeError|ReferenceError|Binding loop|Unable to assign|Internal error|Cannot assign' <<<"$output"; then
+if grep -Eq "$HOST_DIAGNOSTICS_BINDING" <<<"$output"; then
fail "QML runtime error"
fi
@@ -62,7 +63,7 @@ set -e
printf '%s\n' "$scoped_output"
[[ $scoped_rc -eq 0 ]] || fail "scoped smoke exited $scoped_rc"
grep -Fq 'reactor runtime smoke passed' <<<"$scoped_output" || fail "scoped marker missing"
-if grep -Eq 'TypeError|ReferenceError|Binding loop|Unable to assign|Internal error|Cannot assign' <<<"$scoped_output"; then
+if grep -Eq "$HOST_DIAGNOSTICS_BINDING" <<<"$scoped_output"; then
fail "scoped QML runtime error"
fi
diff --git a/tests/scoped-loader-admission-regression.sh b/tests/scoped-loader-admission-regression.sh
index b187084e..11e4320f 100755
--- a/tests/scoped-loader-admission-regression.sh
+++ b/tests/scoped-loader-admission-regression.sh
@@ -4,6 +4,7 @@ set -euo pipefail
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
source "$repo_root/tests/lib/baselines.sh"
+source "$repo_root/tests/lib/qs-runner.sh"
shibumi_load_omarchy_baseline
fail() {
@@ -11,47 +12,13 @@ fail() {
exit 1
}
-[[ -x /usr/bin/quickshell ]] || fail 'quickshell is required'
-command -v setsid >/dev/null 2>&1 || fail 'setsid is required'
-command -v timeout >/dev/null 2>&1 || fail 'timeout is required'
+[[ -x ${QUICKSHELL_BIN:-/usr/bin/quickshell} ]] || fail 'quickshell is required'
command -v python3 >/dev/null 2>&1 || fail 'python3 is required'
[[ -n ${OMARCHY_PATH:-} && -d $OMARCHY_PATH/shell/Commons ]] \
|| fail 'OMARCHY_PATH must reference an admitted Quattro tree'
tmpdir=$(mktemp -d /tmp/shibumi-scoped-loader.XXXXXX)
-fixture_pid=
-fixture_pgid=
-
-process_group_alive() {
- [[ -n $fixture_pgid ]] && kill -0 -- "-$fixture_pgid" 2>/dev/null
-}
-
-terminate_fixture_group() {
- local attempt
- if process_group_alive; then
- kill -TERM -- "-$fixture_pgid" 2>/dev/null || true
- for ((attempt = 0; attempt < 20; attempt++)); do
- process_group_alive || break
- sleep 0.05
- done
- fi
- if process_group_alive; then
- kill -KILL -- "-$fixture_pgid" 2>/dev/null || true
- for ((attempt = 0; attempt < 20; attempt++)); do
- process_group_alive || break
- sleep 0.05
- done
- fi
- ! process_group_alive
-}
-
-cleanup() {
- trap - EXIT HUP INT TERM
- terminate_fixture_group || true
- [[ -z $fixture_pid ]] || wait "$fixture_pid" 2>/dev/null || true
- rm -rf -- "$tmpdir"
-}
-trap cleanup EXIT
+trap 'rm -rf -- "$tmpdir"' EXIT
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM
@@ -114,8 +81,7 @@ run_fixture() {
"$fixture_dir/hancore.shibumi.bar/core/WidgetSlot.qml"
fi
- set +e
- setsid timeout --foreground --kill-after=1 12 env \
+ local -a qs_fixture_env=(
HOME="$fixture_dir/home" \
XDG_CONFIG_HOME="$fixture_dir/home/.config" \
XDG_STATE_HOME="$fixture_dir/home/.local/state" \
@@ -129,16 +95,12 @@ run_fixture() {
QT_QPA_PLATFORM=offscreen QT_QPA_PLATFORMTHEME= \
QT_QUICK_BACKEND=software QT_FORCE_STDERR_LOGGING=1 \
QML_DISABLE_DISK_CACHE=1 \
- /usr/bin/quickshell -p "$fixture_dir" --no-color \
- >"$fixture_dir/output.log" 2>&1 &
- fixture_pid=$!
- fixture_pgid=$fixture_pid
- wait "$fixture_pid"
+ )
+ local -a qs_fixture_args=(--no-color)
+ set +e
+ qs_run_fixture "$fixture_dir/shell.qml" "$fixture_dir" 12 >"$fixture_dir/output.log" 2>&1
rc=$?
set -e
- terminate_fixture_group || fail "$variant process group did not terminate"
- fixture_pid=
- fixture_pgid=
output=$(<"$fixture_dir/output.log")
printf '%s\n' "$output"
diff --git a/tests/status-plugin-regression.sh b/tests/status-plugin-regression.sh
index 980d3ecf..41037beb 100755
--- a/tests/status-plugin-regression.sh
+++ b/tests/status-plugin-regression.sh
@@ -4,6 +4,7 @@ set -euo pipefail
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
source "$repo_root/tests/lib/baselines.sh"
+source "$repo_root/tests/lib/host-diagnostics.sh"
shibumi_load_omarchy_baseline
omarchy_path=$OMARCHY_PATH
quickshell_bin=${QUICKSHELL_BIN:-/usr/bin/quickshell}
@@ -75,9 +76,8 @@ grep -F 'status plugin smoke passed' <<<"$output" >/dev/null \
|| fail "success marker missing"
unexpected_diagnostics=$(grep -E \
'WARN|ERROR|CRITICAL|TypeError|ReferenceError|Binding loop|Unable to assign|Cannot assign|Internal error' \
- <<<"$output" | grep -Fv \
+ <<<"$output" | host_filter_startup_diagnostics \
-e 'Module path contains invalid characters for a module name: "/hancore.shibumi.bar/services"' \
- -e '$HYPRLAND_INSTANCE_SIGNATURE is unset. Cannot connect to hyprland.' \
|| true)
[[ -z $unexpected_diagnostics ]] \
|| fail "component smoke emitted an unexpected runtime diagnostic"
diff --git a/tests/telemetry-plugins-regression.sh b/tests/telemetry-plugins-regression.sh
index d0be5b1c..2dc0b077 100755
--- a/tests/telemetry-plugins-regression.sh
+++ b/tests/telemetry-plugins-regression.sh
@@ -4,6 +4,7 @@ set -euo pipefail
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
source "$repo_root/tests/lib/baselines.sh"
+source "$repo_root/tests/lib/host-diagnostics.sh"
shibumi_load_omarchy_baseline
omarchy_path=$OMARCHY_PATH
quickshell_bin=${QUICKSHELL_BIN:-/usr/bin/quickshell}
@@ -127,7 +128,7 @@ printf '%s\n' "$slice_output"
[[ $slice_rc -eq 0 ]] || fail "scoped telemetry smoke exited $slice_rc"
grep -F 'telemetry runtime smoke passed' <<<"$slice_output" >/dev/null \
|| fail "scoped telemetry success marker missing"
-if grep -Eq 'TypeError|ReferenceError|Binding loop|Unable to assign|Internal error|Cannot assign' <<<"$slice_output"; then
+if grep -Eq "$HOST_DIAGNOSTICS_BINDING" <<<"$slice_output"; then
fail "scoped telemetry QML runtime error detected"
fi
diff --git a/tests/workspaces-plugin-regression.sh b/tests/workspaces-plugin-regression.sh
index 5a53feff..8fadc156 100755
--- a/tests/workspaces-plugin-regression.sh
+++ b/tests/workspaces-plugin-regression.sh
@@ -4,6 +4,7 @@ set -euo pipefail
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
source "$repo_root/tests/lib/baselines.sh"
+source "$repo_root/tests/lib/host-diagnostics.sh"
shibumi_load_omarchy_baseline
omarchy_path=$OMARCHY_PATH
quickshell_bin=${QUICKSHELL_BIN:-/usr/bin/quickshell}
@@ -76,7 +77,7 @@ printf '%s\n' "$runtime_output"
[[ $runtime_rc -eq 0 ]] || fail "scoped workspace smoke exited $runtime_rc"
grep -F 'workspaces runtime smoke passed' <<<"$runtime_output" >/dev/null \
|| fail "scoped workspace marker missing"
-if grep -Eq 'TypeError|ReferenceError|Binding loop|Unable to assign|Internal error|Cannot assign' <<<"$runtime_output"; then
+if grep -Eq "$HOST_DIAGNOSTICS_BINDING" <<<"$runtime_output"; then
fail "scoped workspace QML error"
fi
@@ -100,7 +101,7 @@ printf '%s\n' "$geometry_output"
[[ $geometry_rc -eq 0 ]] || fail "workspace geometry exited $geometry_rc"
grep -F 'workspaces geometry regression passed' <<<"$geometry_output" >/dev/null \
|| fail "workspace geometry marker missing"
-if grep -Eq 'TypeError|ReferenceError|Binding loop|Unable to assign|Internal error|Cannot assign' <<<"$geometry_output"; then
+if grep -Eq "$HOST_DIAGNOSTICS_BINDING" <<<"$geometry_output"; then
fail "workspace geometry QML error"
fi
From 93b5e8dcfd32af86a289de5d073fc5bfde28a8f4 Mon Sep 17 00:00:00 2001
From: HANCORE-linux <230438592+HANCORE-linux@users.noreply.github.com>
Date: Tue, 13 Oct 2026 23:53:57 +0200
Subject: [PATCH 11/18] test(native): expect base-slot reuse in the B(b)
placement case
---
docs/development/testing.md | 4 ++
tests/fixtures/native-runtime/FixtureBar.qml | 2 +-
tests/fixtures/native-runtime/probe.py | 43 ++++----------------
3 files changed, 13 insertions(+), 36 deletions(-)
diff --git a/docs/development/testing.md b/docs/development/testing.md
index f9c04a3f..c7a8caf6 100644
--- a/docs/development/testing.md
+++ b/docs/development/testing.md
@@ -732,6 +732,10 @@ start. Fixed Shibumi groups use the State-only channel. A catalog-only Audio
provider covers serialized V1/V2 replacement, return to Shibumi, exact undo and
stale-postimage refusal without relying on a preloaded component. These are
isolated native-host flows, not rendered desktop or physical-output evidence.
+The B(b) fixture fills the five extras but leaves reusable base slots: following
+`ARCHITECTURE.md`'s allocation contract, it expects the native widget in `left[0]`,
+then swaps it to the extra when G1 returns. This is base-slot reuse and persistence
+coverage, not exhausted-capacity warning coverage.
`tests/state-storage-regression.py` isolates the actual controller with an explicit
own-entry writer and private configuration files. It exercises debounce, in-flight
diff --git a/tests/fixtures/native-runtime/FixtureBar.qml b/tests/fixtures/native-runtime/FixtureBar.qml
index 583391ae..1b5822b9 100644
--- a/tests/fixtures/native-runtime/FixtureBar.qml
+++ b/tests/fixtures/native-runtime/FixtureBar.qml
@@ -587,7 +587,7 @@ Bar {
const order = state ? V1Layout.copyOrder(state.config.order) : null
if (!state || state.writePending || probe.layoutTransitionBusy
|| probe.layoutController.v2Mode || !order
- || order.left[0] !== "" || order.left[7] !== "G1")
+ || order.left[0] !== "G:fixture.native-widget" || order.left[7] !== "G1")
return "not-ready"
return probe.layoutController.moveGroupToSlot("G1", "left", 0)
? "queued" : "refused"
diff --git a/tests/fixtures/native-runtime/probe.py b/tests/fixtures/native-runtime/probe.py
index 38515507..31a2b2f2 100644
--- a/tests/fixtures/native-runtime/probe.py
+++ b/tests/fixtures/native-runtime/probe.py
@@ -419,9 +419,8 @@ def catalog_probe():
and value["panelCatalog"]["byId"]["fixture.native-widget"]["enabled"] is False,
timeout=7)
- # Native B(b): fill all five V1 extension slots with fixed groups,
- # add the real catalog widget in V2, observe the full V1 background
- # reconcile, then free one slot and retry through V2 -> V1.
+ # Native B(b): reuse a base, swap to an extra, then verify V2 -> V1 persistence.
+ # Exhausted capacity: tests/layout-controller-regression.qml (mixedSpecs).
b_started = time.monotonic()
print("NATIVE_TIMING " + json.dumps({"endpoint": "b-start", "monotonic": b_started,
"probeElapsed": b_started - PROBE_STARTED}), flush=True)
@@ -491,26 +490,23 @@ def catalog_probe():
and native_added_entries[0].get("shibumiModule") is True,
"B(b) native file readback lost the exact marker")
+ full_order["left"][0] = "G:fixture.native-widget"
check(ipc("native-runtime-probe", "setShellStyle", "shibumi") == (0, "queued"),
"B(b) full V1 switch refused")
full_observation = wait_status("native-runtime-probe", lambda value:
not value["stateWritePending"] and not value["layoutBusy"]
and not value["v2Mode"] and value["config"]["order"] == full_order
- and all("G:fixture.native-widget" not in value["config"]["order"][region]
- for region in ("left", "center", "right"))
and any(isinstance(entry, dict)
and entry.get("id") == "fixture.native-widget"
and entry.get("shibumiModule") is True
for entry in value["injectedBar"]["layout"]["left"])
and ((not value["unplacedPluginIdsPropertyExists"]
and not value["capacityMessagePropertyExists"])
- or (value["unplacedPluginIds"] == ["fixture.native-widget"]
- and value["capacityMessage"] != "")))
+ or (value["unplacedPluginIds"] == []
+ and value["capacityMessage"] == "")))
persisted(lambda value:
state_settings(value)["presentation"]["shellStyle"] == "shibumi"
and state_settings(value)["order"] == full_order
- and all("G:fixture.native-widget" not in state_settings(value)["order"][region]
- for region in ("left", "center", "right"))
and any(isinstance(entry, dict)
and entry.get("id") == "fixture.native-widget"
and entry.get("shibumiModule") is True
@@ -531,23 +527,12 @@ def catalog_probe():
"pageFeedbackDetail": full_observation["pageFeedbackDetail"],
}
check(capacity_evidence["markerIsDict"]
- and not capacity_evidence["hasV1Position"],
- "B(b) full V1 observation did not retain the marker without a position")
+ and capacity_evidence["hasV1Position"],
+ "B(b) base-slot reuse did not retain the marker and position")
print("NATIVE_BB_FULL_V1_EVIDENCE "
+ json.dumps(capacity_evidence, sort_keys=True), flush=True)
- bb_capacity_warning = (
- capacity_evidence["unplacedPluginIds"] == ["fixture.native-widget"]
- and capacity_evidence["capacityMessage"] != "")
check(ipc("native-runtime-probe", "expirePageFeedback") == (0, "true"),
"explicit page feedback expiry failed")
- if bb_capacity_warning:
- warning_after_feedback = wait_status("native-runtime-probe", lambda value:
- not value["pageFeedbackVisible"]
- and value["unplacedPluginIds"] == ["fixture.native-widget"]
- and value["capacityMessage"] != "")
- check(warning_after_feedback["capacityMessage"]
- == capacity_evidence["capacityMessage"],
- "capacity warning disappeared when action feedback expired")
check(ipc("native-runtime-probe", "freeFullV1Slot") == (0, "queued"),
"B(b) fixed-group move-back refused")
@@ -560,19 +545,9 @@ def catalog_probe():
for group in freed["config"]["order"][region]
if group in fixed_ids]
check(sorted(freed_fixed) == sorted(fixed_ids) and len(freed_fixed) == 15
- and freed["config"]["order"]["left"][7] == "",
+ and freed["config"]["order"]["left"][7] == "G:fixture.native-widget",
"B(b) slot release did not preserve all fixed groups")
persisted(lambda value: state_settings(value)["order"] == freed_order)
- if bb_capacity_warning:
- check(freed["unplacedPluginIds"] == ["fixture.native-widget"]
- and not freed["pageFeedbackVisible"]
- and freed["capacityMessage"] == "Some widgets currently have no bar slot.",
- "free V1 slot with pending placement must show a cause-neutral page status")
- print("NATIVE_BB_FREE_SLOT_STATUS " + json.dumps({
- "slotEmpty": freed["config"]["order"]["left"][7] == "",
- "unplacedPluginIds": freed["unplacedPluginIds"],
- "capacityMessage": freed["capacityMessage"],
- }, sort_keys=True), flush=True)
check(ipc("native-runtime-probe", "setShellStyle", "full") == (0, "queued"),
"B(b) retry V2 switch refused")
@@ -994,8 +969,6 @@ def catalog_probe():
print("NATIVE_TIMING " + json.dumps({"endpoint": "probe-end", "monotonic": probe_ended, "probeElapsed": probe_ended - PROBE_STARTED, "endRemaining": probe_remaining}), flush=True)
check(probe_remaining >= 8,
"native B(b) probe exceeded the authorized 77 s action budget")
- check(bb_capacity_warning,
- "full V1 background reconcile did not publish the unplaced widget capacity warning")
finally:
finish_owned_group(process)
log.flush()
From a8069cbb4926e9fb2c67d3db7768d66d7f66ed10 Mon Sep 17 00:00:00 2001
From: HANCORE-linux <230438592+HANCORE-linux@users.noreply.github.com>
Date: Wed, 14 Oct 2026 11:04:59 +0200
Subject: [PATCH 12/18] test(contract): follow host-backed composites into the
shared embed helper
---
tests/contract-regression.sh | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/tests/contract-regression.sh b/tests/contract-regression.sh
index e4a1fa3b..c106e10e 100755
--- a/tests/contract-regression.sh
+++ b/tests/contract-regression.sh
@@ -246,9 +246,11 @@ if rg -q 'Services\.(SystemTelemetry|GpuTelemetry|PowerService|StatusService|Wea
fi
rg -q 'Qt\.createComponent\(url, Component\.PreferSynchronous\)' hancore.shibumi.bar/services/HostWidgetResolver.qml \
|| fail "host widget resolver does not load official manifest entry points"
-[[ $(rg -l 'bar\.registeredWidgetComponent' \
+[[ $(rg -l 'embed\.resolveComponent\(' \
hancore.shibumi.{audio,status,center}/BarWidget.qml | wc -l) -eq 3 ]] \
|| fail "host-backed composites bypass the stable widget resolver"
+rg -Fq 'bar.registeredWidgetComponent(id)' shared/runtime/HostWidgetEmbed.qml \
+ || fail "shared embed helper bypasses the stable widget resolver"
if rg -q 'registered(Component|Source)|registeredWidget(Component|Source)' \
hancore.shibumi.network/Service.qml; then
fail "native Network owner still resolves a host feature component"
From 6a5a40d648e5a1b26a8ba00d662c20f5cc305350 Mon Sep 17 00:00:00 2001
From: HANCORE-linux <230438592+HANCORE-linux@users.noreply.github.com>
Date: Wed, 14 Oct 2026 12:29:57 +0200
Subject: [PATCH 13/18] docs(install): restore the Pacman prerequisite line
---
README.md | 7 ++++---
docs/install.md | 13 ++++++++-----
2 files changed, 12 insertions(+), 8 deletions(-)
diff --git a/README.md b/README.md
index bc245857..070141a5 100644
--- a/README.md
+++ b/README.md
@@ -65,15 +65,16 @@ for the latest validated host versions.
> Install from the release tag, not `main`. Shibumi supports Omarchy Quattro only.
```bash
+sudo pacman -S --needed python jq curl networkmanager power-profiles-daemon upower xdg-utils libnotify wl-clipboard ttf-material-symbols-variable ttf-jetbrains-mono-nerd-basic noto-fonts-cjk adwaita-fonts
git clone https://github.com/HANCORE-linux/Shibumi-Shell.git
cd Shibumi-Shell
git checkout v0.1.1-beta.16.3
./scripts/shibumi-suite install --yes
```
-The installer asks for root privileges only while Pacman installs missing
-runtime commands and fonts; the Shibumi lifecycle itself runs as the desktop
-user and writes only user-scoped plugin state.
+Run the Pacman line yourself; it installs the runtime commands and fonts and
+is the only step that needs `sudo`. The Shibumi suite never calls Pacman: it
+runs as the desktop user and writes only user-scoped plugin state.
Update an admitted Beta.16.2 source installation to Beta.16.3.
Exact predecessor identities, not a version range, govern admission; see the
diff --git a/docs/install.md b/docs/install.md
index 4087a146..47eddcad 100644
--- a/docs/install.md
+++ b/docs/install.md
@@ -13,8 +13,9 @@ Do not install or update from `main`.
## Requirements
- Omarchy Quattro; other Omarchy generations are not supported
-- The source workflow installs required commands and fonts through Pacman and
- skips dependencies that are already installed or provided
+- Run the Pacman line below first; it installs the required commands and fonts
+ (Material Symbols icons come from `ttf-material-symbols-variable`) and skips
+ what is already installed. The suite itself never calls Pacman.
- The source workflow also needs Git, HTTPS access to the repository,
and a trusted local checkout
@@ -26,6 +27,7 @@ The exact accepted Omarchy and Quickshell packages are recorded in the
For an intentional non-interactive installation:
```bash
+sudo pacman -S --needed python jq curl networkmanager power-profiles-daemon upower xdg-utils libnotify wl-clipboard ttf-material-symbols-variable ttf-jetbrains-mono-nerd-basic noto-fonts-cjk adwaita-fonts
git clone https://github.com/HANCORE-linux/Shibumi-Shell.git
cd Shibumi-Shell
git checkout v0.1.1-beta.16.3
@@ -34,13 +36,14 @@ git checkout v0.1.1-beta.16.3
Do not install or update from `main`. If you previously installed from `main`, run `./scripts/shibumi-suite uninstall --keep-settings --yes` from that exact checkout before switching to a tag.
-The source workflow installs the package-managed runtime dependencies
-first. `sudo` applies only to Pacman; the suite transaction still runs as the
-desktop user and never lets a package hook modify the home directory.
+The Pacman line installs the package-managed runtime dependencies first and
+is the only step that needs `sudo`; the suite transaction runs as the desktop
+user and never lets a package hook modify the home directory.
To inspect the transaction before installing:
```bash
+sudo pacman -S --needed python jq curl networkmanager power-profiles-daemon upower xdg-utils libnotify wl-clipboard ttf-material-symbols-variable ttf-jetbrains-mono-nerd-basic noto-fonts-cjk adwaita-fonts
git clone https://github.com/HANCORE-linux/Shibumi-Shell.git
cd Shibumi-Shell
git checkout v0.1.1-beta.16.3
From 27f3c8b5254c29ab7e96f292500bf9ce68e16c9a Mon Sep 17 00:00:00 2001
From: HANCORE-linux <230438592+HANCORE-linux@users.noreply.github.com>
Date: Wed, 14 Oct 2026 13:08:25 +0200
Subject: [PATCH 14/18] fix(control-center): keep the V2 surface row readable
and offer Frost for Notch only
---
docs/configuration.md | 12 +++++++-----
docs/control-center-v4.md | 7 ++++---
docs/getting-started.md | 6 +++---
.../BarSurfaceSettings.qml | 9 +++++----
tests/control-center-regression.sh | 2 +-
tests/control-center-smoke.qml | 2 +-
6 files changed, 21 insertions(+), 17 deletions(-)
diff --git a/docs/configuration.md b/docs/configuration.md
index c62459e5..21cc22d3 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -24,8 +24,8 @@ unavailable rather than silently falling back. The settings include:
- per-widget enabled state and appearance;
- bar style and semantic accent;
- V1 border, shadow, and Radius 12/6 settings;
-- V2 bar-border and panel/tooltip-border settings;
-- Frost transparency for both V1 and V2;
+- V2 Bar border and Panels settings (Panels includes tooltip borders);
+- Frost transparency for V1 and V2 Notch;
- workspace mode and presentation;
- G1 launcher identity;
- image and media picker styles;
@@ -41,9 +41,11 @@ including nested settings, survive ordinary settings writes.
### Frost and optional compositor blur
-**Bars → Frost** makes the V1 or V2 shell translucent (alpha 0.68). It does
-not enable compositor blur. Full, Fit, and Dock retain their existing fixed
-shell shadows; Notch remains shadowless. Panel surfaces remain unchanged.
+**Bars → Frost** makes the V1 shell or V2 Notch translucent (alpha 0.68).
+It does not enable compositor blur. Full, Fit, and Dock retain their fixed
+shell shadows, which would mask the transparency, so they do not offer a
+Frost option. Notch remains shadowless. Saved Frost settings and panel
+surfaces remain unchanged.
For optional background blur on Hyprland 0.56, add the following to your own
Lua configuration after the defaults. This enables global blur and applies a
diff --git a/docs/control-center-v4.md b/docs/control-center-v4.md
index 3f653817..6e9fe82f 100644
--- a/docs/control-center-v4.md
+++ b/docs/control-center-v4.md
@@ -402,9 +402,10 @@ not secondary sections inside Icons:
shared Pickers route uses a content-matched compact panel height instead of
inheriting the taller generic Configure surface.
- **Bars** owns the active bar's supported surface and accent settings. V1
- exposes border, frost, shadow, and Radius 12/6. V2 exposes Bar Border,
- Panel + Tooltip, and Frost. Its fixed radii and shell shadows are not
- presented as editable settings. Both generations expose the eight palette
+ exposes border, frost, shadow, and Radius 12/6. V2 exposes Bar border and
+ Panels (including tooltip borders); only Notch also offers Frost. Full,
+ Fit, and Dock retain their fixed shell shadows, which would mask Frost
+ transparency. Fixed radii and shell shadows are not editable settings. Both generations expose the eight palette
roles supported by the accepted references: `color01` through `color07` and
foreground. The same tokens are consumed by both the V1 and V2 renderers.
- **Logo** owns launcher wordmark/icon format and visual choices. Its selection
diff --git a/docs/getting-started.md b/docs/getting-started.md
index 4b7fde6b..ccf124f5 100644
--- a/docs/getting-started.md
+++ b/docs/getting-started.md
@@ -41,9 +41,9 @@ The Control Center exposes the supported product settings:
- switch between the V1 and V2-derived shell styles;
- place the bar at the top or bottom and select its shell shape;
- choose a semantic accent from the active Omarchy theme;
-- configure V1 border, shadow, and Radius 12/6 or the V2 bar and
- panel/tooltip borders;
-- enable Frost transparency in V1 and V2;
+- configure V1 border, shadow, and Radius 12/6 or the V2 Bar border and
+ Panels (including tooltip borders);
+- enable Frost transparency in V1 and V2 Notch;
- choose workspace presentation and picker style;
- set per-widget presentation and enabled state; and
- rearrange widgets and control split or separator boundaries.
diff --git a/hancore.shibumi.control-center/BarSurfaceSettings.qml b/hancore.shibumi.control-center/BarSurfaceSettings.qml
index 35615113..619958e3 100644
--- a/hancore.shibumi.control-center/BarSurfaceSettings.qml
+++ b/hancore.shibumi.control-center/BarSurfaceSettings.qml
@@ -23,12 +23,13 @@ Column {
},
{
key: "panelBorder",
- label: "Panel + tooltip",
+ label: "Panels",
detail: "Outline connected panels and tooltips",
fallback: true
- },
- { key: "frost", label: "Frost", detail: "Translucent V2 shell", fallback: false }
- ]
+ }
+ ].concat(String(controller.barPresentation.shellStyle || "") === "notch"
+ ? [{ key: "frost", label: "Frost", detail: "Translucent V2 shell", fallback: false }]
+ : [])
: [
{
key: "border",
diff --git a/tests/control-center-regression.sh b/tests/control-center-regression.sh
index da235ec4..7bae6077 100755
--- a/tests/control-center-regression.sh
+++ b/tests/control-center-regression.sh
@@ -1213,7 +1213,7 @@ for return_contract in \
done
for label in Status Battery \
- 'Bar border' 'Panel + tooltip' Border Frost Shadow \
+ 'Bar border' 'Panels' Border Frost Shadow \
'Radius 12' 'Radius 6'; do
rg -Fq "label: \"$label\"" "$control_dir" \
--glob '*.qml' --glob '*.js' \
diff --git a/tests/control-center-smoke.qml b/tests/control-center-smoke.qml
index d5824784..9fed684c 100644
--- a/tests/control-center-smoke.qml
+++ b/tests/control-center-smoke.qml
@@ -1466,7 +1466,7 @@ ShellRoot {
if (!panel || !panel.settingsPageReady
|| panel.settingsPage !== "bars"
|| !panel.settingsPageItem
- || panel.settingsPageItem.surfaceEffectOptionCount !== 3
+ || panel.settingsPageItem.surfaceEffectOptionCount !== 2
|| panel.settingsPageItem.surfaceEffectPreviewCount !== 0
|| panel.settingsPageItem.splitActionPreviewCount !== 0
|| panel.settingsPageItem.surfaceRadiusOptionCount !== 0
From 9f1495ed26403ef2b540d108b08a6df2b49e4a6c Mon Sep 17 00:00:00 2001
From: HANCORE-linux <230438592+HANCORE-linux@users.noreply.github.com>
Date: Wed, 14 Oct 2026 13:49:27 +0200
Subject: [PATCH 15/18] fix(audio): follow the default sink when the
volume-sink resolver stalls
Closes #78.
---
hancore.shibumi.audio/AudioBackendAdapter.qml | 22 ++++++++++++++++---
1 file changed, 19 insertions(+), 3 deletions(-)
diff --git a/hancore.shibumi.audio/AudioBackendAdapter.qml b/hancore.shibumi.audio/AudioBackendAdapter.qml
index d2cb7a91..04e68617 100644
--- a/hancore.shibumi.audio/AudioBackendAdapter.qml
+++ b/hancore.shibumi.audio/AudioBackendAdapter.qml
@@ -32,6 +32,10 @@ Item {
property int volumeSinkProcessGeneration: 0
property bool volumeSinkProcessStarted: false
property bool volumeSinkProcessDiscarded: false
+ property int volumeSinkStallTicks: 0
+ readonly property bool volumeSinkShouldRun: root.active
+ && root.backendOverride === null && implementation.backendReady()
+ && !root.volumeSinkResolved
// Keep the last primitive value while the asynchronous volume-sink
// resolver is refreshing. Opening the panel acquires peak monitoring,
// which can transiently invalidate link-derived sink state.
@@ -700,22 +704,34 @@ Item {
running: root.active && root.backendOverride === null
onTriggered: {
if (!implementation.backendReady()) return
+ root.volumeSinkStallTicks = volumeSinkProcess.running
+ ? root.volumeSinkStallTicks + 1 : 0
+ if (root.volumeSinkStallTicks >= 2) {
+ // Recover the display even if termination never delivers onExited.
+ root.volumeSinkName = ""
+ root.volumeSinkResolutionFailed = true
+ root.volumeSinkStallTicks = 0
+ volumeSinkProcess.running = false
+ volumeSinkProcess.running = Qt.binding(function() { return root.volumeSinkShouldRun })
+ }
if (!sinkAvailabilityProcess.running)
root.invalidateSinkAvailability()
if (!volumeSinkProcess.running
&& (!root.volumeSinkResolved
|| root.volumeSinkResolutionFailed
- || !implementation.volumeSinkNameMapped()))
+ || !implementation.volumeSinkNameMapped())) {
root.invalidateVolumeSink()
+ volumeSinkProcess.running = Qt.binding(function() { return root.volumeSinkShouldRun })
+ }
}
}
Process {
id: volumeSinkProcess
command: ["omarchy-audio-output-sink"]
- running: root.active && root.backendOverride === null
- && implementation.backendReady() && !root.volumeSinkResolved
+ running: root.volumeSinkShouldRun
onStarted: {
+ root.volumeSinkStallTicks = 0
root.volumeSinkProcessStarted = true
root.volumeSinkProcessGeneration = root.volumeSinkRequestGeneration
}
From 4663ef6e9fae85120d0680912e279f61bbb1ee34 Mon Sep 17 00:00:00 2001
From: HANCORE-linux <230438592+HANCORE-linux@users.noreply.github.com>
Date: Wed, 14 Oct 2026 19:33:28 +0200
Subject: [PATCH 16/18] fix(embedding): clear embedded loaders without nulling
a live child's host facade
---
hancore.shibumi.audio/HostWidgetEmbed.qml | 1 -
hancore.shibumi.brightness/HostWidgetEmbed.qml | 1 -
hancore.shibumi.center/HostWidgetEmbed.qml | 1 -
hancore.shibumi.status/HostWidgetEmbed.qml | 1 -
shared/runtime/HostWidgetEmbed.qml | 1 -
5 files changed, 5 deletions(-)
diff --git a/hancore.shibumi.audio/HostWidgetEmbed.qml b/hancore.shibumi.audio/HostWidgetEmbed.qml
index 3013e386..89c899ba 100644
--- a/hancore.shibumi.audio/HostWidgetEmbed.qml
+++ b/hancore.shibumi.audio/HostWidgetEmbed.qml
@@ -46,7 +46,6 @@ QtObject {
}
function clearLoader(loader) {
- if (loader.item && "bar" in loader.item) loader.item.bar = null
loader.sourceComponent = null
loader.source = ""
}
diff --git a/hancore.shibumi.brightness/HostWidgetEmbed.qml b/hancore.shibumi.brightness/HostWidgetEmbed.qml
index 3013e386..89c899ba 100644
--- a/hancore.shibumi.brightness/HostWidgetEmbed.qml
+++ b/hancore.shibumi.brightness/HostWidgetEmbed.qml
@@ -46,7 +46,6 @@ QtObject {
}
function clearLoader(loader) {
- if (loader.item && "bar" in loader.item) loader.item.bar = null
loader.sourceComponent = null
loader.source = ""
}
diff --git a/hancore.shibumi.center/HostWidgetEmbed.qml b/hancore.shibumi.center/HostWidgetEmbed.qml
index 3013e386..89c899ba 100644
--- a/hancore.shibumi.center/HostWidgetEmbed.qml
+++ b/hancore.shibumi.center/HostWidgetEmbed.qml
@@ -46,7 +46,6 @@ QtObject {
}
function clearLoader(loader) {
- if (loader.item && "bar" in loader.item) loader.item.bar = null
loader.sourceComponent = null
loader.source = ""
}
diff --git a/hancore.shibumi.status/HostWidgetEmbed.qml b/hancore.shibumi.status/HostWidgetEmbed.qml
index 3013e386..89c899ba 100644
--- a/hancore.shibumi.status/HostWidgetEmbed.qml
+++ b/hancore.shibumi.status/HostWidgetEmbed.qml
@@ -46,7 +46,6 @@ QtObject {
}
function clearLoader(loader) {
- if (loader.item && "bar" in loader.item) loader.item.bar = null
loader.sourceComponent = null
loader.source = ""
}
diff --git a/shared/runtime/HostWidgetEmbed.qml b/shared/runtime/HostWidgetEmbed.qml
index 3013e386..89c899ba 100644
--- a/shared/runtime/HostWidgetEmbed.qml
+++ b/shared/runtime/HostWidgetEmbed.qml
@@ -46,7 +46,6 @@ QtObject {
}
function clearLoader(loader) {
- if (loader.item && "bar" in loader.item) loader.item.bar = null
loader.sourceComponent = null
loader.source = ""
}
From 02bec27bf773e925d446241f4c1f068cdb456b94 Mon Sep 17 00:00:00 2001
From: HANCORE-linux <230438592+HANCORE-linux@users.noreply.github.com>
Date: Wed, 14 Oct 2026 22:11:24 +0200
Subject: [PATCH 17/18] test(native): observe the catalog launcher until the
existing search budget ends
---
tests/fixtures/native-runtime/catalog-resource-probe.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/fixtures/native-runtime/catalog-resource-probe.py b/tests/fixtures/native-runtime/catalog-resource-probe.py
index c6fe2aa9..6ec1cb00 100644
--- a/tests/fixtures/native-runtime/catalog-resource-probe.py
+++ b/tests/fixtures/native-runtime/catalog-resource-probe.py
@@ -409,7 +409,7 @@ def stop_first_helper():
custodian_row = None
# The child waits for the launcher's start ACK. Step the launcher in
# short pidfd-controlled slices and hold both sides of the handshake.
- for _ in range(500):
+ while time.monotonic() < end:
continue_pidfd(launcher_fd)
time.sleep(.0002)
stop_pidfd(launcher_fd, pid, starttime)
From 64b9369559b0de2ffdc1faf1c563326c8173035c Mon Sep 17 00:00:00 2001
From: HANCORE-linux <230438592+HANCORE-linux@users.noreply.github.com>
Date: Wed, 14 Oct 2026 22:13:08 +0200
Subject: [PATCH 18/18] chore(release): prepare v0.1.1-beta.16.4
---
.github/release-notes/v0.1.1-beta.16.4.md | 52 +++++++++++++++
CHANGELOG.md | 24 +++++++
README.md | 12 ++--
VERSION | 2 +-
contracts/lifecycle-predecessors-v1.json | 63 ++++++++++++++++++
contracts/plugin-suite-v1.json | 2 +-
docs/development/packaging.md | 4 +-
docs/install.md | 18 ++---
docs/plugins/README.md | 2 +-
docs/release-readiness.md | 66 +++++++++----------
hancore.shibumi.ai/Service.qml | 2 +-
hancore.shibumi.ai/manifest.json | 2 +-
hancore.shibumi.audio/Service.qml | 2 +-
hancore.shibumi.audio/manifest.json | 2 +-
hancore.shibumi.bar/Bar.qml | 2 +-
hancore.shibumi.bar/manifest.json | 2 +-
hancore.shibumi.battery/manifest.json | 2 +-
hancore.shibumi.bluetooth/Service.qml | 2 +-
hancore.shibumi.bluetooth/manifest.json | 2 +-
hancore.shibumi.brightness/Service.qml | 2 +-
hancore.shibumi.brightness/manifest.json | 2 +-
hancore.shibumi.center/Service.qml | 2 +-
hancore.shibumi.center/manifest.json | 2 +-
.../PluginUpdateService.qml | 2 +-
hancore.shibumi.control-center/manifest.json | 2 +-
hancore.shibumi.cpu/Service.qml | 2 +-
hancore.shibumi.cpu/manifest.json | 2 +-
hancore.shibumi.gpu/manifest.json | 2 +-
hancore.shibumi.media/Service.qml | 2 +-
hancore.shibumi.media/manifest.json | 2 +-
hancore.shibumi.memory/manifest.json | 2 +-
hancore.shibumi.network/Service.qml | 2 +-
hancore.shibumi.network/manifest.json | 2 +-
hancore.shibumi.power-profile/manifest.json | 2 +-
hancore.shibumi.power-state/Service.qml | 2 +-
hancore.shibumi.power-state/manifest.json | 2 +-
hancore.shibumi.quick-access/Service.qml | 2 +-
hancore.shibumi.quick-access/manifest.json | 2 +-
hancore.shibumi.reactor/Service.qml | 2 +-
hancore.shibumi.reactor/manifest.json | 2 +-
hancore.shibumi.state/Service.qml | 2 +-
hancore.shibumi.state/manifest.json | 2 +-
hancore.shibumi.state/runtime/Runtime.qml | 2 +-
hancore.shibumi.status/Service.qml | 2 +-
hancore.shibumi.status/manifest.json | 2 +-
hancore.shibumi.storage/Service.qml | 2 +-
hancore.shibumi.storage/manifest.json | 2 +-
hancore.shibumi.telemetry/Service.qml | 2 +-
hancore.shibumi.telemetry/manifest.json | 2 +-
hancore.shibumi.temperature/manifest.json | 2 +-
hancore.shibumi.update-center/Service.qml | 2 +-
hancore.shibumi.update-center/manifest.json | 2 +-
.../WorkspaceService.qml | 2 +-
hancore.shibumi.workspaces/manifest.json | 2 +-
packaging/aur/.SRCINFO | 6 +-
packaging/aur/PKGBUILD | 4 +-
packaging/package-metadata.json | 2 +-
scripts/check-aur-package | 2 +-
.../audio-native-backend-seam-regression.qml | 2 +-
tests/audio-runtime-smoke.qml | 6 +-
tests/bar-catalog-consumer-smoke.qml | 14 ++--
tests/bar-visibility-ipc-smoke.qml | 2 +-
tests/catalog-service-smoke.qml | 2 +-
tests/core-services-smoke.qml | 6 +-
tests/fixtures/native-runtime/probe.py | 2 +-
tests/fixtures/release-predecessor.json | 14 ++--
tests/gpu-selection-state-smoke.qml | 4 +-
tests/host-registry-prime-smoke.qml | 8 +--
tests/media-spectrum-service-smoke.qml | 2 +-
tests/plugin-update-service-smoke.qml | 2 +-
tests/power-deferred-smoke.qml | 2 +-
tests/power-runtime-smoke.qml | 6 +-
tests/power-service-runtime-smoke.qml | 2 +-
tests/reactor-plugin-smoke.qml | 4 +-
tests/reactor-runtime-smoke.qml | 10 +--
tests/scoped-state-regression.qml | 2 +-
tests/shared-runtime-regression.qml | 16 ++---
tests/state-service-smoke.qml | 2 +-
tests/status-plugin-smoke.qml | 6 +-
tests/telemetry-plugins-smoke.qml | 4 +-
tests/telemetry-runtime-smoke.qml | 4 +-
tests/test_package_release.py | 12 +++-
tests/theme-palette-runtime-smoke.qml | 2 +-
tests/third-party-integration-smoke.qml | 4 +-
tests/widget-pipeline-diagnostics-smoke.qml | 6 +-
tests/workspaces-plugin-smoke.qml | 2 +-
tests/workspaces-runtime-smoke.qml | 6 +-
87 files changed, 325 insertions(+), 176 deletions(-)
create mode 100644 .github/release-notes/v0.1.1-beta.16.4.md
diff --git a/.github/release-notes/v0.1.1-beta.16.4.md b/.github/release-notes/v0.1.1-beta.16.4.md
new file mode 100644
index 00000000..bb1c03b4
--- /dev/null
+++ b/.github/release-notes/v0.1.1-beta.16.4.md
@@ -0,0 +1,52 @@
+# Shibumi v0.1.1-beta.16.4
+
+## Highlights
+
+- **Memory Sweep in the Memory panel.** Choose Page cache, Compact memory, or both, with one Polkit authorization. Available memory before and after the operation, cancellation, and errors appear in the panel. No swap operations or process killing; clearing caches does not guarantee a memory gain.
+- **Frost for the V2 Notch shell.** Notch gains the translucent Frost surface; Full, Fit, and Dock keep their fixed shell shadow. Frost controls transparency, not compositor blur; an optional Hyprland recipe is documented without changing your compositor settings. Connected panels keep their own opacity.
+- **Charging plasma from 10%.** A confined plasma core and five short sparks replace the battery ring's previous charging effect. The water animation is unchanged.
+- **Reliable Rings placement.** Rings starts on the correct workspace after activation or reload, including when the target arrives after layout. Other workspace styles no longer animate an invisible Rings frame.
+- **Power-profile selection stays open.** Mouse and keyboard selection update the active profile without closing or shifting the panel. Inactive labels remain readable with V2 and Catppuccin Latte.
+- **Small reliability fixes.** Unavailable AI usage displays an em dash, picker actions receive closed stdin, and deferred focus callbacks safely handle a destroyed panel. The audio bar follows the default sink again if the volume-sink resolver stalls after suspend/resume (#78). During recovery, DSP chains may temporarily fall back to the default sink.
+- **Smoother host-widget handoff.** Shared embedding keeps Status children loaded through bar handoff and preserves the official Audio panel instance. Existing Omarchy paths and IPC identifiers now share one contract layer, without adding backend owners or polling.
+
+The forward compatibility reference is updated to Omarchy `4c1b4cb3`. Source installation remains the supported route; OPR packaging is planned, not AUR publication.
+
+## Install and update
+
+For a new source installation, use the published release tag:
+
+```bash
+sudo pacman -S --needed python jq curl networkmanager power-profiles-daemon upower xdg-utils libnotify wl-clipboard ttf-material-symbols-variable ttf-jetbrains-mono-nerd-basic noto-fonts-cjk adwaita-fonts
+git clone https://github.com/HANCORE-linux/Shibumi-Shell.git
+cd Shibumi-Shell
+git checkout v0.1.1-beta.16.4
+./scripts/shibumi-suite install --yes
+```
+
+Update an admitted Beta.16.3 installation from its existing checkout:
+
+```bash
+git fetch --tags
+git checkout v0.1.1-beta.16.4
+./scripts/shibumi-suite update --dry-run
+./scripts/shibumi-suite update --yes
+./scripts/shibumi-suite status
+```
+
+The exact Beta.16.3 tag (`1a13d6b`) and documented source-installation integration
+merge (`494fa69`) are admitted predecessors. Older supported identities retain
+their exact revision and payload checks; an arbitrary checkout with the same
+version is not admitted. Do not install or update from `main`, or delete
+lifecycle state to bypass admission. See the [installation and recovery guide](https://github.com/HANCORE-linux/Shibumi-Shell/blob/v0.1.1-beta.16.4/docs/install.md).
+AUR publication remains deferred.
+
+## Validation
+
+In 600 private-renderer sessions against Beta 16.3, the median per-group CPU change was **−29.7% for focus**, **−11.3% for style changes**, and **+0.14% for bar changes**. Rings style changes reached stable pixels earlier in all eight measured contexts, with median reductions of 40–217 ms. Bar switching was broadly at Beta 16.3's level. These measurements describe the tested renderer scenarios, not a blanket desktop speedup.
+
+## Known points
+
+- Notch can still oscillate at DPR 1.6.
+- Notification focus remains limited when the sender does not provide a supported application identity.
+- If Memory Sweep loses its service owner, the authorization dialog may disappear without a result. An already authorized command can continue, and a replacement owner may permit another run; avoid restarting Sweep during an owner change.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2cd11f23..69fdaa74 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,29 @@
# Changelog
+## [0.1.1-beta.16.4] - 2026-10-14
+
+### Highlights
+
+- **Memory Sweep in the Memory panel.** Choose Page cache, Compact memory, or both, with one Polkit authorization. Available memory before and after the operation, cancellation, and errors appear in the panel. No swap operations or process killing; clearing caches does not guarantee a memory gain.
+- **Frost for the V2 Notch shell.** Notch gains the translucent Frost surface; Full, Fit, and Dock keep their fixed shell shadow. Frost controls transparency, not compositor blur; an optional Hyprland recipe is documented without changing your compositor settings. Connected panels keep their own opacity.
+- **Charging plasma from 10%.** A confined plasma core and five short sparks replace the battery ring's previous charging effect. The water animation is unchanged.
+- **Reliable Rings placement.** Rings starts on the correct workspace after activation or reload, including when the target arrives after layout. Other workspace styles no longer animate an invisible Rings frame.
+- **Power-profile selection stays open.** Mouse and keyboard selection update the active profile without closing or shifting the panel. Inactive labels remain readable with V2 and Catppuccin Latte.
+- **Small reliability fixes.** Unavailable AI usage displays an em dash, picker actions receive closed stdin, and deferred focus callbacks safely handle a destroyed panel. The audio bar follows the default sink again if the volume-sink resolver stalls after suspend/resume (#78). During recovery, DSP chains may temporarily fall back to the default sink.
+- **Smoother host-widget handoff.** Shared embedding keeps Status children loaded through bar handoff and preserves the official Audio panel instance. Existing Omarchy paths and IPC identifiers now share one contract layer, without adding backend owners or polling.
+
+The forward compatibility reference is updated to Omarchy `4c1b4cb3`. Source installation remains the supported route; OPR packaging is planned, not AUR publication.
+
+### Validation
+
+In 600 private-renderer sessions against Beta 16.3, the median per-group CPU change was **−29.7% for focus**, **−11.3% for style changes**, and **+0.14% for bar changes**. Rings style changes reached stable pixels earlier in all eight measured contexts, with median reductions of 40–217 ms. Bar switching was broadly at Beta 16.3's level. These measurements describe the tested renderer scenarios, not a blanket desktop speedup.
+
+### Known points
+
+- Notch can still oscillate at DPR 1.6.
+- Notification focus remains limited when the sender does not provide a supported application identity.
+- If Memory Sweep loses its service owner, the authorization dialog may disappear without a result. An already authorized command can continue, and a replacement owner may permit another run; avoid restarting Sweep during an owner change.
+
## [0.1.1-beta.16.3] - 2026-10-09
### Fixed
diff --git a/README.md b/README.md
index 070141a5..aeab5f82 100644
--- a/README.md
+++ b/README.md
@@ -16,9 +16,9 @@ process.
[Documentation](docs/README.md) ·
[Release status](docs/release-readiness.md)
-`0.1.1-beta.16.3` is the target GitHub prerelease. See the
-[release notes](.github/release-notes/v0.1.1-beta.16.3.md) for changes and the
-downgrade warning.
+`0.1.1-beta.16.4` is the target GitHub prerelease. See the
+[release notes](.github/release-notes/v0.1.1-beta.16.4.md) for changes. Before using an older version, read the
+[backup and downgrade warning](docs/install.md#backup-before-downgrade).
The current pinned host contracts use Omarchy and Omarchy Settings `4.0.4-1`
(source tag `v4.0.4`, revision `c668141e`) with Quickshell `0.3.1-1`; the
4.0.3 and 4.0.2 manifests remain optional compatibility baselines.
@@ -68,7 +68,7 @@ for the latest validated host versions.
sudo pacman -S --needed python jq curl networkmanager power-profiles-daemon upower xdg-utils libnotify wl-clipboard ttf-material-symbols-variable ttf-jetbrains-mono-nerd-basic noto-fonts-cjk adwaita-fonts
git clone https://github.com/HANCORE-linux/Shibumi-Shell.git
cd Shibumi-Shell
-git checkout v0.1.1-beta.16.3
+git checkout v0.1.1-beta.16.4
./scripts/shibumi-suite install --yes
```
@@ -76,14 +76,14 @@ Run the Pacman line yourself; it installs the runtime commands and fonts and
is the only step that needs `sudo`. The Shibumi suite never calls Pacman: it
runs as the desktop user and writes only user-scoped plugin state.
-Update an admitted Beta.16.2 source installation to Beta.16.3.
+Update an admitted Beta.16.3 source installation to Beta.16.4.
Exact predecessor identities, not a version range, govern admission; see the
[update guide](docs/install.md#update).
```sh
cd Shibumi-Shell
git fetch --tags
-git checkout v0.1.1-beta.16.3
+git checkout v0.1.1-beta.16.4
./scripts/shibumi-suite update --dry-run
./scripts/shibumi-suite update --yes
./scripts/shibumi-suite status
diff --git a/VERSION b/VERSION
index 301b5a8c..bdd0e34e 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.1.1-beta.16.3
+0.1.1-beta.16.4
diff --git a/contracts/lifecycle-predecessors-v1.json b/contracts/lifecycle-predecessors-v1.json
index 144740f4..fe37103b 100644
--- a/contracts/lifecycle-predecessors-v1.json
+++ b/contracts/lifecycle-predecessors-v1.json
@@ -940,9 +940,72 @@
],
"settingsStorageVersion": 1,
"sourceRevisions": [
+ "1a13d6be7d5b4bc9aa03590b77b6531aefcad497",
+ "494fa69b9e348fa595eb4d55d2e11b4aa2fd4470",
"package:0.1.1-beta.16.3"
],
"suiteVersion": "0.1.1-beta.16.3"
+ },
+ {
+ "id": "public-beta.16.4",
+ "payloadDigest": "bc60b476803f72d5604e592647db7f50c1f00723b986ae87e7730cba79779d34",
+ "pluginDigests": {
+ "hancore.shibumi.bar": "445913ac44c840e5f15eb78b5bb7e4b5072bbff26f319a0618f4b8c5ac849ec9",
+ "hancore.shibumi.state": "69a425a0b36d26547a02056daca6161ea70e99ae540d9cc0940fd17882717f18",
+ "hancore.shibumi.control-center": "226dc9fa32fd5e010eb00328fa8269fcf71f9667790f6114b5b16943d79535d8",
+ "hancore.shibumi.reactor": "4576d7d7c1a9ad408da2e670170d5dfd491a8871e605900f6603acdff30e65eb",
+ "hancore.shibumi.telemetry": "ae5d205047b86fcbbfc4d0b27fb38eac8e7dc344c55c9a9cb33327fea30966df",
+ "hancore.shibumi.power-state": "d68253b8d9151493e507277eafc3ad5a1f2d95c6b6a75c9b49f0aafe803cb8c5",
+ "hancore.shibumi.workspaces": "96ffc7698fcff00970eed638e26808661a4310b0620c46d56f8a6111aed4a123",
+ "hancore.shibumi.update-center": "15708aceabd7b23abbdb8ed2f642971915dcfa201d034684c7195dce39b7c4c3",
+ "hancore.shibumi.status": "99f0d8dfe18722f1f2de9599e049d9775ce603fb842b80bd797def4ff8616fed",
+ "hancore.shibumi.memory": "06e436ecb99c5b543757087ee980b66c322b22440b5a1e7c5c783474bee23c39",
+ "hancore.shibumi.cpu": "98901b062a6be7dd64c2f402eaf296b451a41e810d5c2cfd90195ab94f683c4e",
+ "hancore.shibumi.audio": "59a37a5195971472f2709c9ca5114f9ee946bce95dc696c69502060db115325e",
+ "hancore.shibumi.ai": "fdab6f260b73f7c6b37a1804ed76f8f245eecaceca3a5713f8cb16edc64f4221",
+ "hancore.shibumi.center": "c6b6b0b35cac9afd0cb4353a57fba47005c45497324f1ba1772287604ff742c8",
+ "hancore.shibumi.media": "80a48fe3a4975e4b9d61506155ca15ccf6c10c614caa1332f6afb3e259d01f5f",
+ "hancore.shibumi.quick-access": "405f271d5cb246d82086e7b9dbe86783c758b3244ee7e90a90602668c5b408c8",
+ "hancore.shibumi.network": "3f6da96d4d09b5aa2f1c1515a3e4855944d7126bb3237bf3ca5ce6a51f7f618d",
+ "hancore.shibumi.battery": "c443df274b222fdd8d6f65541a58d383f390f2102e13edbb0eba5f61e6b3f83e",
+ "hancore.shibumi.brightness": "ca6acef3dc3d9fd2c39807dee6308ef5bef2af8c52e51695b5d9b8702493fe1e",
+ "hancore.shibumi.power-profile": "d60e5922562f7771a25357d217b0555062b59783a92e3a69f59536e817cd0a1c",
+ "hancore.shibumi.bluetooth": "ffb2192a1614a54baf2319cc33574ac53f936631127834c09e9e957352c4a171",
+ "hancore.shibumi.temperature": "a544a359fe54186bf6b0df45bb5e17db263da4af25061f18ab885a090c4f6e3e",
+ "hancore.shibumi.gpu": "3bb129f9f242ebda2e7ad9f7515d7c06c2bd72f7e95effdc71f57ec5a38bc994",
+ "hancore.shibumi.storage": "e478677692ad9946b88bd3b79dec5176abdaeb6f63c72abc80ccd51bb513231e"
+ },
+ "pluginIds": [
+ "hancore.shibumi.bar",
+ "hancore.shibumi.state",
+ "hancore.shibumi.control-center",
+ "hancore.shibumi.reactor",
+ "hancore.shibumi.telemetry",
+ "hancore.shibumi.power-state",
+ "hancore.shibumi.workspaces",
+ "hancore.shibumi.update-center",
+ "hancore.shibumi.status",
+ "hancore.shibumi.memory",
+ "hancore.shibumi.cpu",
+ "hancore.shibumi.audio",
+ "hancore.shibumi.ai",
+ "hancore.shibumi.center",
+ "hancore.shibumi.media",
+ "hancore.shibumi.quick-access",
+ "hancore.shibumi.network",
+ "hancore.shibumi.battery",
+ "hancore.shibumi.brightness",
+ "hancore.shibumi.power-profile",
+ "hancore.shibumi.bluetooth",
+ "hancore.shibumi.temperature",
+ "hancore.shibumi.gpu",
+ "hancore.shibumi.storage"
+ ],
+ "settingsStorageVersion": 1,
+ "sourceRevisions": [
+ "package:0.1.1-beta.16.4"
+ ],
+ "suiteVersion": "0.1.1-beta.16.4"
}
]
}
diff --git a/contracts/plugin-suite-v1.json b/contracts/plugin-suite-v1.json
index 81c44b4e..e9cffb71 100644
--- a/contracts/plugin-suite-v1.json
+++ b/contracts/plugin-suite-v1.json
@@ -1,7 +1,7 @@
{
"schemaVersion": 1,
"suiteId": "hancore.shibumi",
- "suiteVersion": "0.1.1-beta.16.3",
+ "suiteVersion": "0.1.1-beta.16.4",
"settingsStorageVersion": 1,
"hostContract": "contracts/host-facade-v1.json",
"retiredPlugins": [
diff --git a/docs/development/packaging.md b/docs/development/packaging.md
index a741badc..72a97d2f 100644
--- a/docs/development/packaging.md
+++ b/docs/development/packaging.md
@@ -1,6 +1,6 @@
# Source installation and planned OPR packaging
-Status: install `0.1.1-beta.16.3` from source. OPR (omarchy-pkgs) packaging
+Status: install `0.1.1-beta.16.4` from source. OPR (omarchy-pkgs) packaging
is planned; AUR publication is no longer the target.
Shibumi ships one versioned suite containing 24 separately validated Omarchy
@@ -69,7 +69,7 @@ and lifecycle code before the user-level update runs, so Beta.12's storage guard
cannot reject that package afterward. There is no reverse migration to legacy
storage.
-The additional [Beta.16.3 downgrade warning and backup requirements](../install.md#backup-before-downgrade)
+The additional [Beta.16.4 downgrade warning and backup requirements](../install.md#backup-before-downgrade)
apply even to readers using canonical State storage: Beta.15.4 and older
cannot preserve a V1 layout containing `order.parked`. `--keep-settings` does
not prevent this. Restore the settings with a matching payload.
diff --git a/docs/install.md b/docs/install.md
index 47eddcad..0d8e045c 100644
--- a/docs/install.md
+++ b/docs/install.md
@@ -6,7 +6,7 @@ Shibumi installs 24 independent plugin roots into Omarchy's normal plugin
directory. The repository root is a suite source, not a single installable
Omarchy plugin.
-`0.1.1-beta.16.3` is the target GitHub prerelease. Install from the release
+`0.1.1-beta.16.4` is the target GitHub prerelease. Install from the release
tag using the source workflow below.
Do not install or update from `main`.
@@ -30,7 +30,7 @@ For an intentional non-interactive installation:
sudo pacman -S --needed python jq curl networkmanager power-profiles-daemon upower xdg-utils libnotify wl-clipboard ttf-material-symbols-variable ttf-jetbrains-mono-nerd-basic noto-fonts-cjk adwaita-fonts
git clone https://github.com/HANCORE-linux/Shibumi-Shell.git
cd Shibumi-Shell
-git checkout v0.1.1-beta.16.3
+git checkout v0.1.1-beta.16.4
./scripts/shibumi-suite install --yes
```
@@ -46,7 +46,7 @@ To inspect the transaction before installing:
sudo pacman -S --needed python jq curl networkmanager power-profiles-daemon upower xdg-utils libnotify wl-clipboard ttf-material-symbols-variable ttf-jetbrains-mono-nerd-basic noto-fonts-cjk adwaita-fonts
git clone https://github.com/HANCORE-linux/Shibumi-Shell.git
cd Shibumi-Shell
-git checkout v0.1.1-beta.16.3
+git checkout v0.1.1-beta.16.4
./scripts/shibumi-suite install --dry-run
./scripts/shibumi-suite install
```
@@ -126,14 +126,14 @@ Pacman updating `/usr/share/shibumi-shell` does not silently change a running
desktop. The explicit update validates, stages, reloads, and verifies all 24
plugins as one transaction.
-To update an admitted Beta.16.2 source installation to Beta.16.3, use its
-trusted checkout. The exact published Beta.16.2 tag and release merge are
-admitted predecessors:
+To update an admitted Beta.16.3 source installation to Beta.16.4, use its
+trusted checkout. The exact published Beta.16.3 tag and the documented
+source-installation integration merge are admitted predecessors:
```bash
cd Shibumi-Shell
git fetch --tags
-git checkout v0.1.1-beta.16.3
+git checkout v0.1.1-beta.16.4
./scripts/shibumi-suite update --dry-run
./scripts/shibumi-suite update --yes
./scripts/shibumi-suite status
@@ -186,7 +186,7 @@ authoritative origin. The old checkout is not deleted or modified.
### Roll back a package version
> [!WARNING]
-> **Beta.16.3 downgrade warning.**
+> **Beta.16.4 downgrade warning.**
> Beta.15.4 and older cannot read V1 layouts containing
> `order.parked`. They reset V1 order, roles, and splits to defaults; other
> settings and V2 remain. `uninstall --keep-settings` does not prevent this.
@@ -386,7 +386,7 @@ then install the published tag. For example, only if the recorded revision is
```bash
git checkout e92d8ee1ce9821d0b203a9d4d5dd9932d2e460fb
./scripts/shibumi-suite uninstall --keep-settings --yes
-git checkout v0.1.1-beta.16.3
+git checkout v0.1.1-beta.16.4
./scripts/shibumi-suite install --yes
```
diff --git a/docs/plugins/README.md b/docs/plugins/README.md
index f34c6ea8..fe81dbd3 100644
--- a/docs/plugins/README.md
+++ b/docs/plugins/README.md
@@ -2,7 +2,7 @@
Status: reference
-Shibumi `0.1.1-beta.16.3` contains 24 independently registered Omarchy plugins. The
+Shibumi `0.1.1-beta.16.4` contains 24 independently registered Omarchy plugins. The
default suite profile installs all of them as one verified transaction.
The authoritative inventory is
diff --git a/docs/release-readiness.md b/docs/release-readiness.md
index 031bca86..f390277e 100644
--- a/docs/release-readiness.md
+++ b/docs/release-readiness.md
@@ -1,4 +1,4 @@
-# Shibumi 0.1.1-beta.16.3 release readiness
+# Shibumi 0.1.1-beta.16.4 release readiness
> **Document status: Revision-bound validation record.** This page separates
> recorded product evidence from release, package, and physical acceptance.
@@ -6,30 +6,29 @@
## Candidate and published predecessor
-The Beta.16.3 candidate contains V1 network bar ink, the status pending-flag
-ordering fix, six-style hover colour, picker search/list fixes, inline helper
-execution in the plugin smokes, and the forward-baseline generator and repin. Pacman retains
-its exact 16.2 presentation and hover behaviour; compact and linked-theme
-Update Center changes are excluded. Its 24-plugin suite payload digest is
-`9d8cca435b465c52b0ff21d7a6780f90251a805dc1d1e72b78473ca2a8338a88`.
-This is the preparation snapshot included in the release-identity commit, not
-final clean-commit evidence. Source review, the complete contract, isolated
-Quattro lifecycle, package rehearsal, full 14-gate collector, and reproducible
-archive verification must record their exact final commit externally; no
-predecessor gate result transfers to this candidate.
-
-Beta.16.2 was published on 2026-10-08 as a GitHub prerelease from
-`bd1224ba92fbf279666abe362ff5cf7a165da096`. It is the frozen rolling
+The Beta.16.4 candidate adds Memory Sweep, Frost for V2 Notch, bounded charging
+plasma, Rings placement fixes, persistent power-profile selection, shared
+host-widget embedding and host identifiers, and Audio resolver recovery.
+Its 24-plugin suite payload digest is
+`bc60b476803f72d5604e592647db7f50c1f00723b986ae87e7730cba79779d34`.
+This is a release-preparation snapshot, not final clean-commit evidence. The
+complete contract, isolated Quattro lifecycle, package rehearsal, full 14-gate
+collector, and reproducible archive verification must record their exact final
+commit externally. No predecessor gate result transfers to this candidate.
+
+Beta.16.3 was published on 2026-10-10 as a GitHub prerelease from
+`1a13d6be7d5b4bc9aa03590b77b6531aefcad497`. It is the frozen rolling
predecessor in [`release-predecessor.json`](../tests/fixtures/release-predecessor.json).
-That exact tag checkout and release merge
-`e07853b54f018ab694572c7502297e0f17752220` are admitted by
+That exact tag checkout and the source-installation integration merge
+`494fa69b9e348fa595eb4d55d2e11b4aa2fd4470` (PR #77) are admitted by
[`lifecycle-predecessors-v1.json`](../contracts/lifecycle-predecessors-v1.json),
-with the unchanged Beta.16.2 payload digest
-`79c61d5e048c669c9eb27ebaad21987327892089daea501d0cf4185150a26f9e`.
-The exact local tag payload matches all 24 pinned plugin digests; the published
-release merge and tag have the same Git tree. Older exact identities remain admitted; a
-version string alone never authorizes recovery or replacement. AUR publication
-remains deferred.
+with the unchanged Beta.16.3 payload digest
+`9d8cca435b465c52b0ff21d7a6780f90251a805dc1d1e72b78473ca2a8338a88`.
+All 24 archived plugin digests match the published pins. The integration merge
+changes documentation and its regression, not plugin payloads; its complete Git
+tree is not identical to the release tag. Older exact identities remain admitted;
+a version string alone never authorizes recovery or replacement. Installation
+remains source-based; OPR packaging is planned, not AUR publication.
## Recorded evidence and outstanding gates
@@ -37,20 +36,21 @@ remains deferred.
| --- | --- |
| Complete source contract | Pending exact release-commit evidence at this preparation snapshot. |
| Isolated Quattro lifecycle | Pending exact release-commit evidence, retaining all 32 generations. |
-| Retained runtime paths | Historical Beta.13 → Beta.14.1 package arm (5), fresh checkout (6), Beta.15 (7), Beta.15.2 (7), and rolling frozen Beta.16.2 → candidate (7). No arm may be dropped. |
-| Stage-1 visual evidence | Round 379 records DPR 1/1.6 rest and shared-code captures, seven-theme picker search, previewless themes, sorting, and an unchanged Update Center. Round 380 separately rechecks Pacman against 16.2 and the six retained hover styles against 379. No physical-output claim. |
-| Stage-1 performance | Round 379: 670 sessions, 1,880 events, cold/warm N5; focus, style, bar and hover only, not media-scroll or Control Center events. Accepted by the maintainer, including the warm Kanji CPU group as variation. Focus R/B/changed-buffer counts are not identical in every group; 13/140 hover input-to-last-pixel measurements exceed 136.667 ms by at most 2.29 ms. No blanket speedup or scanout claim. Pacman has since been restored to 16.2; no new performance series is claimed. |
+| Retained runtime paths | Historical Beta.13 → Beta.14.1 package arm (5), fresh checkout (6), Beta.15 (7), Beta.15.2 (7), and rolling frozen Beta.16.3 → candidate (7). No arm may be dropped. |
+| Private renderer and lifetime evidence | All 37 bar-switch steps and 140 rest cases match the installed Beta.16.4 candidate `5732887`. Private Process evidence covers discarded and stalled Audio resolver recovery; existing fixtures cover embedded-child continuity and Audio panel identity. No physical-output or physical suspend/resume claim. |
+| Private renderer performance | 600 sessions against Beta.16.3, 1,600 events, 160 groups. Median per-group CPU change: focus −29.7%, style −11.3%, bar +0.14%. Rings style changes stabilize earlier in all eight contexts. Individual slower measurements remain; no blanket latency or desktop speedup claim. Accepted for release preparation. |
| Compatibility preparation | Forward manifest `4c1b4cb3` supersedes `e71a816e`; older manifests remain. Manifest, panel geometry and focused Quattro/style checks pass. Complete forward evidence after release-identity alignment remains required; the pin is not a complete compatibility pass. |
| Live installation and hardware | Keep-settings installation and reviewer live acceptance must be recorded separately. Fixtures do not establish physical multi-output, hardware, credential or real desktop input acceptance. |
| Release/package verification | Reproducible archive, checksum agreement, clean-commit gates and rehearsal, independent review, full collector and remote verification remain separate publication requirements. |
-The historical package arm is not a Beta.16.3 installed-package upgrade proof.
+The historical package arm is not a Beta.16.4 installed-package upgrade proof.
Private Wayland/Mesa captures are not live GPU, layer-shell, hotplug, or physical
multi-output acceptance. Earlier release passes do not transfer to this candidate.
-The release notes document the accepted Rings early step (raw DPR-1 reload N10:
-full 1/10, standard 5/10 sessions, not natural-frequency estimates) and the
-existing DPR-1.6 Notch/Numbers/Kanji centre oscillation. Both remain on the
-0.2.0 redesign list; the Rings animation-ordering cause is unproved.
+Rings placement is covered by private fixtures; release live acceptance remains
+separate. Notch can still oscillate at DPR 1.6. Notification focus depends on a
+supported sender identity. Memory Sweep can lose its service owner: a dialog may
+disappear, or an authorized command may continue while a replacement owner can
+permit another run. Real Polkit/root and owner-loss acceptance remain separate.
## Pinned host contracts
@@ -128,7 +128,7 @@ was performed. Each operation needs its own maintainer Go.
and [backup guide](install.md#backup-before-downgrade) identify the complete
settings and recovery evidence to preserve. Restore the settings with a
matching payload.
-- AUR and omarchy-pkgs publication are separate work, not authorized here.
+- OPR packaging is separate work, not authorized here; AUR publication is not the target.
## Immutable asset and publication gate
@@ -151,4 +151,4 @@ release checklist. Beta.16's published changes and limits remain in its
revision `30746f5` passed the complete contract and 32-generation runtime;
those fixtures retained known teardown warnings and did not establish blanket
hardware acceptance. Earlier readiness snapshots remain in Git history; none
-confers acceptance on Beta.16.3.
+confers acceptance on Beta.16.4.
diff --git a/hancore.shibumi.ai/Service.qml b/hancore.shibumi.ai/Service.qml
index e27c5797..16f0e104 100644
--- a/hancore.shibumi.ai/Service.qml
+++ b/hancore.shibumi.ai/Service.qml
@@ -19,7 +19,7 @@ Item {
SuiteRuntime.HostShell { id: suiteShell; host: root.shell }
SuiteRuntime.Provider {
pluginId: "hancore.shibumi.ai"
- implementationVersion: "0.1.1-beta.16.3"
+ implementationVersion: "0.1.1-beta.16.4"
owner: root
host: root.shell
manifest: root.manifest
diff --git a/hancore.shibumi.ai/manifest.json b/hancore.shibumi.ai/manifest.json
index f33a4f7c..e711d627 100644
--- a/hancore.shibumi.ai/manifest.json
+++ b/hancore.shibumi.ai/manifest.json
@@ -2,7 +2,7 @@
"schemaVersion": 1,
"id": "hancore.shibumi.ai",
"name": "Shibumi AI Usage",
- "version": "0.1.1-beta.16.3",
+ "version": "0.1.1-beta.16.4",
"author": "HANCORE",
"license": "MIT",
"description": "Shibumi Claude, Codex, and OpenCode usage presentation.",
diff --git a/hancore.shibumi.audio/Service.qml b/hancore.shibumi.audio/Service.qml
index dc075f78..fd7c099a 100644
--- a/hancore.shibumi.audio/Service.qml
+++ b/hancore.shibumi.audio/Service.qml
@@ -14,7 +14,7 @@ Item {
SuiteRuntime.Provider {
id: runtimeProvider
pluginId: "hancore.shibumi.audio"
- implementationVersion: "0.1.1-beta.16.3"
+ implementationVersion: "0.1.1-beta.16.4"
owner: root
host: root.shell
manifest: root.manifest
diff --git a/hancore.shibumi.audio/manifest.json b/hancore.shibumi.audio/manifest.json
index 30211a25..58ae658a 100644
--- a/hancore.shibumi.audio/manifest.json
+++ b/hancore.shibumi.audio/manifest.json
@@ -2,7 +2,7 @@
"schemaVersion": 1,
"id": "hancore.shibumi.audio",
"name": "Shibumi Audio",
- "version": "0.1.1-beta.16.3",
+ "version": "0.1.1-beta.16.4",
"author": "HANCORE",
"license": "MIT",
"description": "Shibumi audio presentation over Omarchy's PipeWire owner.",
diff --git a/hancore.shibumi.bar/Bar.qml b/hancore.shibumi.bar/Bar.qml
index d60f59d6..fc4cfab7 100644
--- a/hancore.shibumi.bar/Bar.qml
+++ b/hancore.shibumi.bar/Bar.qml
@@ -28,7 +28,7 @@ Item {
SuiteRuntime.Provider {
id: barRuntimeProvider
pluginId: "hancore.shibumi.bar"
- implementationVersion: "0.1.1-beta.16.3"
+ implementationVersion: "0.1.1-beta.16.4"
owner: root
host: suiteHostShell.host
manifest: root.manifest
diff --git a/hancore.shibumi.bar/manifest.json b/hancore.shibumi.bar/manifest.json
index f23f6979..7f1496a8 100644
--- a/hancore.shibumi.bar/manifest.json
+++ b/hancore.shibumi.bar/manifest.json
@@ -2,7 +2,7 @@
"schemaVersion": 1,
"id": "hancore.shibumi.bar",
"name": "Shibumi Bar",
- "version": "0.1.1-beta.16.3",
+ "version": "0.1.1-beta.16.4",
"author": "HANCORE",
"license": "MIT",
"description": "The Shibumi full-bar host for Omarchy Shell.",
diff --git a/hancore.shibumi.battery/manifest.json b/hancore.shibumi.battery/manifest.json
index c0fde0c6..770954fd 100644
--- a/hancore.shibumi.battery/manifest.json
+++ b/hancore.shibumi.battery/manifest.json
@@ -2,7 +2,7 @@
"schemaVersion": 1,
"id": "hancore.shibumi.battery",
"name": "Shibumi Battery",
- "version": "0.1.1-beta.16.3",
+ "version": "0.1.1-beta.16.4",
"author": "HANCORE",
"license": "MIT",
"description": "Shibumi battery status and detailed battery panel.",
diff --git a/hancore.shibumi.bluetooth/Service.qml b/hancore.shibumi.bluetooth/Service.qml
index 6c4f4930..47511fff 100644
--- a/hancore.shibumi.bluetooth/Service.qml
+++ b/hancore.shibumi.bluetooth/Service.qml
@@ -15,7 +15,7 @@ Item {
SuiteRuntime.HostShell { id: suiteShell; host: root.shell }
SuiteRuntime.Provider {
pluginId: "hancore.shibumi.bluetooth"
- implementationVersion: "0.1.1-beta.16.3"
+ implementationVersion: "0.1.1-beta.16.4"
owner: root
host: root.shell
manifest: root.manifest
diff --git a/hancore.shibumi.bluetooth/manifest.json b/hancore.shibumi.bluetooth/manifest.json
index a54cca43..2ff73106 100644
--- a/hancore.shibumi.bluetooth/manifest.json
+++ b/hancore.shibumi.bluetooth/manifest.json
@@ -2,7 +2,7 @@
"schemaVersion": 1,
"id": "hancore.shibumi.bluetooth",
"name": "Shibumi Bluetooth",
- "version": "0.1.1-beta.16.3",
+ "version": "0.1.1-beta.16.4",
"author": "HANCORE",
"license": "MIT",
"description": "Shibumi Bluetooth presentation with a native BlueZ and audio backend.",
diff --git a/hancore.shibumi.brightness/Service.qml b/hancore.shibumi.brightness/Service.qml
index 461e83d1..badafa18 100644
--- a/hancore.shibumi.brightness/Service.qml
+++ b/hancore.shibumi.brightness/Service.qml
@@ -13,7 +13,7 @@ Item {
property var manifest: null
SuiteRuntime.Provider {
pluginId: "hancore.shibumi.brightness"
- implementationVersion: "0.1.1-beta.16.3"
+ implementationVersion: "0.1.1-beta.16.4"
owner: root
host: root.shell
manifest: root.manifest
diff --git a/hancore.shibumi.brightness/manifest.json b/hancore.shibumi.brightness/manifest.json
index cb83e7d4..c0f0c77a 100644
--- a/hancore.shibumi.brightness/manifest.json
+++ b/hancore.shibumi.brightness/manifest.json
@@ -2,7 +2,7 @@
"schemaVersion": 1,
"id": "hancore.shibumi.brightness",
"name": "Shibumi Brightness",
- "version": "0.1.1-beta.16.3",
+ "version": "0.1.1-beta.16.4",
"author": "HANCORE",
"license": "MIT",
"description": "Shibumi brightness and display presentation over Omarchy's monitor owner.",
diff --git a/hancore.shibumi.center/Service.qml b/hancore.shibumi.center/Service.qml
index d639af80..d8f28fad 100644
--- a/hancore.shibumi.center/Service.qml
+++ b/hancore.shibumi.center/Service.qml
@@ -10,7 +10,7 @@ Item {
property var manifest: null
SuiteRuntime.Provider {
pluginId: "hancore.shibumi.center"
- implementationVersion: "0.1.1-beta.16.3"
+ implementationVersion: "0.1.1-beta.16.4"
owner: root
host: root.shell
manifest: root.manifest
diff --git a/hancore.shibumi.center/manifest.json b/hancore.shibumi.center/manifest.json
index 6a80dc77..a3c0c34d 100644
--- a/hancore.shibumi.center/manifest.json
+++ b/hancore.shibumi.center/manifest.json
@@ -2,7 +2,7 @@
"schemaVersion": 1,
"id": "hancore.shibumi.center",
"name": "Shibumi Center",
- "version": "0.1.1-beta.16.3",
+ "version": "0.1.1-beta.16.4",
"author": "HANCORE",
"license": "MIT",
"description": "Shibumi clock, calendar, weather, status, and update center.",
diff --git a/hancore.shibumi.control-center/PluginUpdateService.qml b/hancore.shibumi.control-center/PluginUpdateService.qml
index 0509d145..2fd51471 100644
--- a/hancore.shibumi.control-center/PluginUpdateService.qml
+++ b/hancore.shibumi.control-center/PluginUpdateService.qml
@@ -20,7 +20,7 @@ Item {
SuiteRuntime.Provider {
id: runtimeProvider
pluginId: "hancore.shibumi.control-center"
- implementationVersion: "0.1.1-beta.16.3"
+ implementationVersion: "0.1.1-beta.16.4"
owner: root
host: root.shell
manifest: root.manifest
diff --git a/hancore.shibumi.control-center/manifest.json b/hancore.shibumi.control-center/manifest.json
index dcc25960..3e5fa2f1 100644
--- a/hancore.shibumi.control-center/manifest.json
+++ b/hancore.shibumi.control-center/manifest.json
@@ -2,7 +2,7 @@
"schemaVersion": 1,
"id": "hancore.shibumi.control-center",
"name": "Shibumi Control Center",
- "version": "0.1.1-beta.16.3",
+ "version": "0.1.1-beta.16.4",
"author": "HANCORE",
"license": "MIT",
"description": "Shibumi G1 presentation and bar configuration panel.",
diff --git a/hancore.shibumi.cpu/Service.qml b/hancore.shibumi.cpu/Service.qml
index 7641c8f9..75fe2b95 100644
--- a/hancore.shibumi.cpu/Service.qml
+++ b/hancore.shibumi.cpu/Service.qml
@@ -13,7 +13,7 @@ Item {
SuiteRuntime.Provider {
id: runtimeProvider
pluginId: "hancore.shibumi.cpu"
- implementationVersion: "0.1.1-beta.16.3"
+ implementationVersion: "0.1.1-beta.16.4"
owner: root
host: root.shell
manifest: root.manifest
diff --git a/hancore.shibumi.cpu/manifest.json b/hancore.shibumi.cpu/manifest.json
index 4bbde093..5818c996 100644
--- a/hancore.shibumi.cpu/manifest.json
+++ b/hancore.shibumi.cpu/manifest.json
@@ -2,7 +2,7 @@
"schemaVersion": 1,
"id": "hancore.shibumi.cpu",
"name": "Shibumi CPU",
- "version": "0.1.1-beta.16.3",
+ "version": "0.1.1-beta.16.4",
"author": "HANCORE",
"license": "MIT",
"description": "Shibumi CPU widget, GPU sampler, and detailed usage panel.",
diff --git a/hancore.shibumi.gpu/manifest.json b/hancore.shibumi.gpu/manifest.json
index 3c6da808..82c8a876 100644
--- a/hancore.shibumi.gpu/manifest.json
+++ b/hancore.shibumi.gpu/manifest.json
@@ -2,7 +2,7 @@
"schemaVersion": 1,
"id": "hancore.shibumi.gpu",
"name": "Shibumi GPU",
- "version": "0.1.1-beta.16.3",
+ "version": "0.1.1-beta.16.4",
"author": "HANCORE",
"license": "MIT",
"description": "Lazy GPU load, hardware, driver, temperature, and VRAM widget.",
diff --git a/hancore.shibumi.media/Service.qml b/hancore.shibumi.media/Service.qml
index 46ffe3e8..af4773d7 100644
--- a/hancore.shibumi.media/Service.qml
+++ b/hancore.shibumi.media/Service.qml
@@ -18,7 +18,7 @@ Item {
SuiteRuntime.Provider {
id: runtimeProvider
pluginId: "hancore.shibumi.media"
- implementationVersion: "0.1.1-beta.16.3"
+ implementationVersion: "0.1.1-beta.16.4"
owner: root
host: root.shell
manifest: root.manifest
diff --git a/hancore.shibumi.media/manifest.json b/hancore.shibumi.media/manifest.json
index 26051c4a..a80e419c 100644
--- a/hancore.shibumi.media/manifest.json
+++ b/hancore.shibumi.media/manifest.json
@@ -2,7 +2,7 @@
"schemaVersion": 1,
"id": "hancore.shibumi.media",
"name": "Shibumi Media",
- "version": "0.1.1-beta.16.3",
+ "version": "0.1.1-beta.16.4",
"author": "HANCORE",
"license": "MIT",
"description": "Shibumi media controls over Omarchy's MPRIS service.",
diff --git a/hancore.shibumi.memory/manifest.json b/hancore.shibumi.memory/manifest.json
index b71b910d..6d5af893 100644
--- a/hancore.shibumi.memory/manifest.json
+++ b/hancore.shibumi.memory/manifest.json
@@ -2,7 +2,7 @@
"schemaVersion": 1,
"id": "hancore.shibumi.memory",
"name": "Shibumi Memory",
- "version": "0.1.1-beta.16.3",
+ "version": "0.1.1-beta.16.4",
"author": "HANCORE",
"license": "MIT",
"description": "Shibumi memory widget and detailed memory panel.",
diff --git a/hancore.shibumi.network/Service.qml b/hancore.shibumi.network/Service.qml
index 0f5e9823..a8c26999 100644
--- a/hancore.shibumi.network/Service.qml
+++ b/hancore.shibumi.network/Service.qml
@@ -15,7 +15,7 @@ Item {
property var manifest: null
SuiteRuntime.Provider {
pluginId: "hancore.shibumi.network"
- implementationVersion: "0.1.1-beta.16.3"
+ implementationVersion: "0.1.1-beta.16.4"
owner: root
host: root.shell
manifest: root.manifest
diff --git a/hancore.shibumi.network/manifest.json b/hancore.shibumi.network/manifest.json
index 569a93d3..74715b8f 100644
--- a/hancore.shibumi.network/manifest.json
+++ b/hancore.shibumi.network/manifest.json
@@ -2,7 +2,7 @@
"schemaVersion": 1,
"id": "hancore.shibumi.network",
"name": "Shibumi Network",
- "version": "0.1.1-beta.16.3",
+ "version": "0.1.1-beta.16.4",
"author": "HANCORE",
"license": "MIT",
"description": "Native Shibumi NetworkManager ownership, telemetry, actions, QR, and speed testing.",
diff --git a/hancore.shibumi.power-profile/manifest.json b/hancore.shibumi.power-profile/manifest.json
index e0118fb9..e65515b1 100644
--- a/hancore.shibumi.power-profile/manifest.json
+++ b/hancore.shibumi.power-profile/manifest.json
@@ -2,7 +2,7 @@
"schemaVersion": 1,
"id": "hancore.shibumi.power-profile",
"name": "Shibumi Power Profile",
- "version": "0.1.1-beta.16.3",
+ "version": "0.1.1-beta.16.4",
"author": "HANCORE",
"license": "MIT",
"description": "Shibumi power-profile status and profile selector.",
diff --git a/hancore.shibumi.power-state/Service.qml b/hancore.shibumi.power-state/Service.qml
index 0ae6d94c..2edfb2b6 100644
--- a/hancore.shibumi.power-state/Service.qml
+++ b/hancore.shibumi.power-state/Service.qml
@@ -23,7 +23,7 @@ Item {
SuiteRuntime.Provider {
id: runtimeProvider
pluginId: "hancore.shibumi.power-state"
- implementationVersion: "0.1.1-beta.16.3"
+ implementationVersion: "0.1.1-beta.16.4"
owner: root
host: root.shell
manifest: root.manifest
diff --git a/hancore.shibumi.power-state/manifest.json b/hancore.shibumi.power-state/manifest.json
index b4ea3847..190c633a 100644
--- a/hancore.shibumi.power-state/manifest.json
+++ b/hancore.shibumi.power-state/manifest.json
@@ -2,7 +2,7 @@
"schemaVersion": 1,
"id": "hancore.shibumi.power-state",
"name": "Shibumi Power State",
- "version": "0.1.1-beta.16.3",
+ "version": "0.1.1-beta.16.4",
"author": "HANCORE",
"license": "MIT",
"description": "Shared battery and power-profile state for Shibumi widgets.",
diff --git a/hancore.shibumi.quick-access/Service.qml b/hancore.shibumi.quick-access/Service.qml
index 1bbe5f2c..103eaa7b 100644
--- a/hancore.shibumi.quick-access/Service.qml
+++ b/hancore.shibumi.quick-access/Service.qml
@@ -18,7 +18,7 @@ Item {
SuiteRuntime.HostShell { id: suiteShell; host: root.shell }
SuiteRuntime.Provider {
pluginId: "hancore.shibumi.quick-access"
- implementationVersion: "0.1.1-beta.16.3"
+ implementationVersion: "0.1.1-beta.16.4"
owner: root
host: root.shell
manifest: root.manifest
diff --git a/hancore.shibumi.quick-access/manifest.json b/hancore.shibumi.quick-access/manifest.json
index f841529f..4fc2f87f 100644
--- a/hancore.shibumi.quick-access/manifest.json
+++ b/hancore.shibumi.quick-access/manifest.json
@@ -2,7 +2,7 @@
"schemaVersion": 1,
"id": "hancore.shibumi.quick-access",
"name": "Shibumi Quick Access",
- "version": "0.1.1-beta.16.3",
+ "version": "0.1.1-beta.16.4",
"author": "HANCORE",
"license": "MIT",
"description": "Shibumi idle inhibitor and theme, wallpaper, screenshot, and video pickers.",
diff --git a/hancore.shibumi.reactor/Service.qml b/hancore.shibumi.reactor/Service.qml
index 2ea5871c..7237a117 100644
--- a/hancore.shibumi.reactor/Service.qml
+++ b/hancore.shibumi.reactor/Service.qml
@@ -18,7 +18,7 @@ Item {
SuiteRuntime.Provider {
id: runtimeProvider
pluginId: "hancore.shibumi.reactor"
- implementationVersion: "0.1.1-beta.16.3"
+ implementationVersion: "0.1.1-beta.16.4"
owner: root
host: root.shell
manifest: root.manifest
diff --git a/hancore.shibumi.reactor/manifest.json b/hancore.shibumi.reactor/manifest.json
index 6f2642d0..d75ac520 100644
--- a/hancore.shibumi.reactor/manifest.json
+++ b/hancore.shibumi.reactor/manifest.json
@@ -2,7 +2,7 @@
"schemaVersion": 1,
"id": "hancore.shibumi.reactor",
"name": "Shibumi Reactor",
- "version": "0.1.1-beta.16.3",
+ "version": "0.1.1-beta.16.4",
"author": "HANCORE",
"license": "MIT",
"description": "Shibumi event and quote Reactor service for compatible bar styles.",
diff --git a/hancore.shibumi.state/Service.qml b/hancore.shibumi.state/Service.qml
index fae5bfff..e65ee947 100644
--- a/hancore.shibumi.state/Service.qml
+++ b/hancore.shibumi.state/Service.qml
@@ -16,7 +16,7 @@ Item {
SuiteRuntime.Provider {
id: runtimeProvider
pluginId: "hancore.shibumi.state"
- implementationVersion: "0.1.1-beta.16.3"
+ implementationVersion: "0.1.1-beta.16.4"
owner: root
host: root.shell
manifest: root.manifest
diff --git a/hancore.shibumi.state/manifest.json b/hancore.shibumi.state/manifest.json
index 39ea9e37..53cb3933 100644
--- a/hancore.shibumi.state/manifest.json
+++ b/hancore.shibumi.state/manifest.json
@@ -2,7 +2,7 @@
"schemaVersion": 1,
"id": "hancore.shibumi.state",
"name": "Shibumi State",
- "version": "0.1.1-beta.16.3",
+ "version": "0.1.1-beta.16.4",
"author": "HANCORE",
"license": "MIT",
"description": "Normalized Shibumi configuration and theme palette state.",
diff --git a/hancore.shibumi.state/runtime/Runtime.qml b/hancore.shibumi.state/runtime/Runtime.qml
index bd747689..ee223624 100644
--- a/hancore.shibumi.state/runtime/Runtime.qml
+++ b/hancore.shibumi.state/runtime/Runtime.qml
@@ -11,7 +11,7 @@ QtObject {
id: runtime
readonly property int contractVersion: 1
- readonly property string suiteVersion: "0.1.1-beta.16.3"
+ readonly property string suiteVersion: "0.1.1-beta.16.4"
readonly property var providerIds: [
"hancore.shibumi.state", "hancore.shibumi.control-center",
"hancore.shibumi.reactor", "hancore.shibumi.telemetry",
diff --git a/hancore.shibumi.status/Service.qml b/hancore.shibumi.status/Service.qml
index 0775cb92..a2965732 100644
--- a/hancore.shibumi.status/Service.qml
+++ b/hancore.shibumi.status/Service.qml
@@ -16,7 +16,7 @@ Item {
SuiteRuntime.HostShell { id: suiteShell; host: root.shell }
SuiteRuntime.Provider {
pluginId: "hancore.shibumi.status"
- implementationVersion: "0.1.1-beta.16.3"
+ implementationVersion: "0.1.1-beta.16.4"
owner: root
host: root.shell
manifest: root.manifest
diff --git a/hancore.shibumi.status/manifest.json b/hancore.shibumi.status/manifest.json
index 70b7c142..167cd201 100644
--- a/hancore.shibumi.status/manifest.json
+++ b/hancore.shibumi.status/manifest.json
@@ -2,7 +2,7 @@
"schemaVersion": 1,
"id": "hancore.shibumi.status",
"name": "Shibumi Status",
- "version": "0.1.1-beta.16.3",
+ "version": "0.1.1-beta.16.4",
"author": "HANCORE",
"license": "MIT",
"description": "Shibumi tray and notification composition with shared desktop status state.",
diff --git a/hancore.shibumi.storage/Service.qml b/hancore.shibumi.storage/Service.qml
index 90b769b4..e4bf333b 100644
--- a/hancore.shibumi.storage/Service.qml
+++ b/hancore.shibumi.storage/Service.qml
@@ -13,7 +13,7 @@ Item {
SuiteRuntime.Provider {
id: runtimeProvider
pluginId: "hancore.shibumi.storage"
- implementationVersion: "0.1.1-beta.16.3"
+ implementationVersion: "0.1.1-beta.16.4"
owner: root
host: root.shell
manifest: root.manifest
diff --git a/hancore.shibumi.storage/manifest.json b/hancore.shibumi.storage/manifest.json
index f825a1c4..5f875c06 100644
--- a/hancore.shibumi.storage/manifest.json
+++ b/hancore.shibumi.storage/manifest.json
@@ -2,7 +2,7 @@
"schemaVersion": 1,
"id": "hancore.shibumi.storage",
"name": "Shibumi Storage",
- "version": "0.1.1-beta.16.3",
+ "version": "0.1.1-beta.16.4",
"author": "HANCORE",
"license": "MIT",
"description": "Read-only root usage and physical drive inventory.",
diff --git a/hancore.shibumi.telemetry/Service.qml b/hancore.shibumi.telemetry/Service.qml
index e0172769..a30673c2 100644
--- a/hancore.shibumi.telemetry/Service.qml
+++ b/hancore.shibumi.telemetry/Service.qml
@@ -15,7 +15,7 @@ Item {
SuiteRuntime.Provider {
id: runtimeProvider
pluginId: "hancore.shibumi.telemetry"
- implementationVersion: "0.1.1-beta.16.3"
+ implementationVersion: "0.1.1-beta.16.4"
owner: root
host: root.shell
manifest: root.manifest
diff --git a/hancore.shibumi.telemetry/manifest.json b/hancore.shibumi.telemetry/manifest.json
index 2f76c604..f8a78949 100644
--- a/hancore.shibumi.telemetry/manifest.json
+++ b/hancore.shibumi.telemetry/manifest.json
@@ -2,7 +2,7 @@
"schemaVersion": 1,
"id": "hancore.shibumi.telemetry",
"name": "Shibumi Telemetry",
- "version": "0.1.1-beta.16.3",
+ "version": "0.1.1-beta.16.4",
"author": "HANCORE",
"license": "MIT",
"description": "Demand-driven CPU and memory telemetry shared by Shibumi widgets.",
diff --git a/hancore.shibumi.temperature/manifest.json b/hancore.shibumi.temperature/manifest.json
index 2e35b422..dea6c397 100644
--- a/hancore.shibumi.temperature/manifest.json
+++ b/hancore.shibumi.temperature/manifest.json
@@ -2,7 +2,7 @@
"schemaVersion": 1,
"id": "hancore.shibumi.temperature",
"name": "Shibumi Temperature",
- "version": "0.1.1-beta.16.3",
+ "version": "0.1.1-beta.16.4",
"author": "HANCORE",
"license": "MIT",
"description": "Demand-driven hardware temperature widget and sensor panel.",
diff --git a/hancore.shibumi.update-center/Service.qml b/hancore.shibumi.update-center/Service.qml
index 51be3674..991c750e 100644
--- a/hancore.shibumi.update-center/Service.qml
+++ b/hancore.shibumi.update-center/Service.qml
@@ -12,7 +12,7 @@ Item {
property var manifest: null
SuiteRuntime.Provider {
pluginId: "hancore.shibumi.update-center"
- implementationVersion: "0.1.1-beta.16.3"
+ implementationVersion: "0.1.1-beta.16.4"
owner: root
host: root.shell
manifest: root.manifest
diff --git a/hancore.shibumi.update-center/manifest.json b/hancore.shibumi.update-center/manifest.json
index 01aea569..5666746e 100644
--- a/hancore.shibumi.update-center/manifest.json
+++ b/hancore.shibumi.update-center/manifest.json
@@ -2,7 +2,7 @@
"schemaVersion": 1,
"id": "hancore.shibumi.update-center",
"name": "Shibumi Update Center",
- "version": "0.1.1-beta.16.3",
+ "version": "0.1.1-beta.16.4",
"author": "HANCORE",
"license": "MIT",
"description": "Safe repository package and user theme update status for Shibumi.",
diff --git a/hancore.shibumi.workspaces/WorkspaceService.qml b/hancore.shibumi.workspaces/WorkspaceService.qml
index 640bbb15..5bfc77d2 100644
--- a/hancore.shibumi.workspaces/WorkspaceService.qml
+++ b/hancore.shibumi.workspaces/WorkspaceService.qml
@@ -14,7 +14,7 @@ Item {
SuiteRuntime.Provider {
id: runtimeProvider
pluginId: "hancore.shibumi.workspaces"
- implementationVersion: "0.1.1-beta.16.3"
+ implementationVersion: "0.1.1-beta.16.4"
owner: root
host: root.shell
manifest: root.manifest
diff --git a/hancore.shibumi.workspaces/manifest.json b/hancore.shibumi.workspaces/manifest.json
index 01f268a1..db58ba7d 100644
--- a/hancore.shibumi.workspaces/manifest.json
+++ b/hancore.shibumi.workspaces/manifest.json
@@ -2,7 +2,7 @@
"schemaVersion": 1,
"id": "hancore.shibumi.workspaces",
"name": "Shibumi Workspaces",
- "version": "0.1.1-beta.16.3",
+ "version": "0.1.1-beta.16.4",
"author": "HANCORE",
"license": "MIT",
"description": "Shared Hyprland workspace state with the Shibumi workspace presentation.",
diff --git a/packaging/aur/.SRCINFO b/packaging/aur/.SRCINFO
index 988d44ad..b2dae4fb 100644
--- a/packaging/aur/.SRCINFO
+++ b/packaging/aur/.SRCINFO
@@ -1,6 +1,6 @@
pkgbase = shibumi-shell
pkgdesc = Transactional Shibumi desktop shell plugins for Omarchy Quattro
- pkgver = 0.1.1beta.16.3
+ pkgver = 0.1.1beta.16.4
pkgrel = 1
url = https://github.com/HANCORE-linux/Shibumi-Shell
arch = any
@@ -30,7 +30,7 @@ pkgbase = shibumi-shell
optdepends = btop: battery panel system monitor action
optdepends = nvidia-utils: NVIDIA GPU telemetry
optdepends = git: source-checkout diagnostics and migration
- source = shibumi-shell-0.1.1-beta.16.3.tar.gz::https://github.com/HANCORE-linux/Shibumi-Shell/releases/download/v0.1.1-beta.16.3/shibumi-shell-0.1.1-beta.16.3.tar.gz
- sha256sums = 5c94a288f417ec2aa5dacb97117520e31e8fdb70fe665372cec781ce7726a171
+ source = shibumi-shell-0.1.1-beta.16.4.tar.gz::https://github.com/HANCORE-linux/Shibumi-Shell/releases/download/v0.1.1-beta.16.4/shibumi-shell-0.1.1-beta.16.4.tar.gz
+ sha256sums = 8a3ab979a52f1fe3ecb9c67fbf8e82999675442a82a29b9bf4edf863b95652ad
pkgname = shibumi-shell
diff --git a/packaging/aur/PKGBUILD b/packaging/aur/PKGBUILD
index b952194f..a4e20769 100644
--- a/packaging/aur/PKGBUILD
+++ b/packaging/aur/PKGBUILD
@@ -1,7 +1,7 @@
# Maintainer: HANCORE Linux
pkgname=shibumi-shell
-_upstream_version=0.1.1-beta.16.3
-_source_sha256=5c94a288f417ec2aa5dacb97117520e31e8fdb70fe665372cec781ce7726a171
+_upstream_version=0.1.1-beta.16.4
+_source_sha256=8a3ab979a52f1fe3ecb9c67fbf8e82999675442a82a29b9bf4edf863b95652ad
pkgver=${_upstream_version//-/}
pkgrel=1
pkgdesc="Transactional Shibumi desktop shell plugins for Omarchy Quattro"
diff --git a/packaging/package-metadata.json b/packaging/package-metadata.json
index 57130d0e..3b62c5fb 100644
--- a/packaging/package-metadata.json
+++ b/packaging/package-metadata.json
@@ -1,5 +1,5 @@
{
"schemaVersion": 1,
"packageName": "shibumi-shell",
- "version": "0.1.1-beta.16.3"
+ "version": "0.1.1-beta.16.4"
}
diff --git a/scripts/check-aur-package b/scripts/check-aur-package
index 0d406e64..978ec43c 100755
--- a/scripts/check-aur-package
+++ b/scripts/check-aur-package
@@ -45,7 +45,7 @@ jq -e '.schemaVersion == 1
"public-beta.11", "step-5-tip", "public-beta.12", "public-beta.13",
"source-beta.14", "public-beta.14.1", "public-beta.15", "public-beta.15.1",
"public-beta.15.2", "public-beta.15.3", "public-beta.15.4", "public-beta.16",
- "public-beta.16.1", "public-beta.16.2", "public-beta.16.3"
+ "public-beta.16.1", "public-beta.16.2", "public-beta.16.3", "public-beta.16.4"
]' \
contracts/lifecycle-predecessors-v1.json >/dev/null
grep -Fq 'contracts/lifecycle-predecessors-v1.json' "$pkgbuild" || {
diff --git a/tests/audio-native-backend-seam-regression.qml b/tests/audio-native-backend-seam-regression.qml
index a71fcb99..6e1999c3 100644
--- a/tests/audio-native-backend-seam-regression.qml
+++ b/tests/audio-native-backend-seam-regression.qml
@@ -174,7 +174,7 @@ ShellRoot {
Audio.Service {
id: service
shell: serviceHost
- manifest: ({id: "hancore.shibumi.audio", version: "0.1.1-beta.16.3", kinds: ["service"]})
+ manifest: ({id: "hancore.shibumi.audio", version: "0.1.1-beta.16.4", kinds: ["service"]})
nativeBackendEnabled: true
nativeBackendOverride: fakeBackend
}
diff --git a/tests/audio-runtime-smoke.qml b/tests/audio-runtime-smoke.qml
index 9d93a24a..b6ab722c 100644
--- a/tests/audio-runtime-smoke.qml
+++ b/tests/audio-runtime-smoke.qml
@@ -11,7 +11,7 @@ ShellRoot {
property var savedPanel: null
property real originalRadius: 0
property var audioManifest: ({id: "hancore.shibumi.audio",
- version: "0.1.1-beta.16.3", kinds: ["service", "bar-widget"]})
+ version: "0.1.1-beta.16.4", kinds: ["service", "bar-widget"]})
function check(value, message) {
if (value) return
console.error("audio-runtime-smoke:", message)
@@ -48,9 +48,9 @@ ShellRoot {
SuiteRuntime.Provider {
id: stateProvider
pluginId: "hancore.shibumi.state"
- implementationVersion: "0.1.1-beta.16.3"
+ implementationVersion: "0.1.1-beta.16.4"
owner: tokenState
- manifest: ({id: "hancore.shibumi.state", version: "0.1.1-beta.16.3", kinds: ["service"]})
+ manifest: ({id: "hancore.shibumi.state", version: "0.1.1-beta.16.4", kinds: ["service"]})
}
Fixtures.AudioRuntimeBackend { id: backend }
Audio.Service { id: service; nativeBackendOverride: backend }
diff --git a/tests/bar-catalog-consumer-smoke.qml b/tests/bar-catalog-consumer-smoke.qml
index 2cc02c69..355c332d 100644
--- a/tests/bar-catalog-consumer-smoke.qml
+++ b/tests/bar-catalog-consumer-smoke.qml
@@ -67,10 +67,10 @@ ShellRoot {
SuiteRuntime.Provider {
id: stateProvider
pluginId: "hancore.shibumi.state"
- implementationVersion: "0.1.1-beta.16.3"
+ implementationVersion: "0.1.1-beta.16.4"
owner: stateService
host: stateHost
- manifest: ({ id: "hancore.shibumi.state", version: "0.1.1-beta.16.3",
+ manifest: ({ id: "hancore.shibumi.state", version: "0.1.1-beta.16.4",
kinds: ["service"] })
}
@@ -92,7 +92,7 @@ ShellRoot {
id: fakePluginRegistry
property string pluginId: "hancore.shibumi.bar"
property var manifest: ({ id: "hancore.shibumi.bar",
- version: "0.1.1-beta.16.3", kinds: ["bar"] })
+ version: "0.1.1-beta.16.4", kinds: ["bar"] })
property bool enabled: true
property var _entryPointUrl: null
readonly property var installedPlugins: {
@@ -138,11 +138,11 @@ ShellRoot {
property int catalogReadSerial: currentObservation ? currentObservation.serial : 0
property int catalogGeneration: currentObservation ? currentObservation.generation : 0
property var providerManifest: ({ id: "hancore.shibumi.control-center",
- version: "0.1.1-beta.16.3", kinds: ["service"] })
+ version: "0.1.1-beta.16.4", kinds: ["service"] })
QtObject { id: serviceHost; property string pluginId: "hancore.shibumi.control-center" }
SuiteRuntime.Provider {
pluginId: "hancore.shibumi.control-center"
- implementationVersion: "0.1.1-beta.16.3"
+ implementationVersion: "0.1.1-beta.16.4"
owner: service
host: serviceHost
manifest: service.providerManifest
@@ -192,7 +192,7 @@ ShellRoot {
root.bar = barFactory.createObject(root, {
omarchyPath: "/fixture/omarchy",
shell: scopedBarHost,
- manifest: ({ id: "hancore.shibumi.bar", version: "0.1.1-beta.16.3",
+ manifest: ({ id: "hancore.shibumi.bar", version: "0.1.1-beta.16.4",
kinds: ["bar"] }),
pluginRegistry: fakePluginRegistry,
barWidgetRegistry: fakeWidgetRegistry,
@@ -339,7 +339,7 @@ ShellRoot {
} else if (root.stage === 4) {
if (root.firstService.acquireCalls !== 2) return
root.firstService.providerManifest = ({ id: "invalid",
- version: "0.1.1-beta.16.3", kinds: ["service"] })
+ version: "0.1.1-beta.16.4", kinds: ["service"] })
root.stage = 5; root.waits = 0
} else if (root.stage === 5) {
if (root.firstService.releaseCalls !== 2) return
diff --git a/tests/bar-visibility-ipc-smoke.qml b/tests/bar-visibility-ipc-smoke.qml
index 0caca952..861607d6 100644
--- a/tests/bar-visibility-ipc-smoke.qml
+++ b/tests/bar-visibility-ipc-smoke.qml
@@ -60,7 +60,7 @@ ShellRoot {
shell: fakeShell
manifest: ({
id: "hancore.shibumi.bar",
- version: "0.1.1-beta.16.3",
+ version: "0.1.1-beta.16.4",
kinds: ["bar"]
})
pluginRegistry: fakePluginRegistry
diff --git a/tests/catalog-service-smoke.qml b/tests/catalog-service-smoke.qml
index b9c9372a..72ba591c 100644
--- a/tests/catalog-service-smoke.qml
+++ b/tests/catalog-service-smoke.qml
@@ -62,7 +62,7 @@ Scope {
}
}
function manifest(valid) {
- return {id: "hancore.shibumi.control-center", version: valid ? "0.1.1-beta.16.3" : "invalid",
+ return {id: "hancore.shibumi.control-center", version: valid ? "0.1.1-beta.16.4" : "invalid",
kinds: ["service", "bar-widget"]}
}
function changedCatalogOutput() {
diff --git a/tests/core-services-smoke.qml b/tests/core-services-smoke.qml
index d09cfe7f..76f294c8 100644
--- a/tests/core-services-smoke.qml
+++ b/tests/core-services-smoke.qml
@@ -20,14 +20,14 @@ ShellRoot {
id: telemetry
shell: fakeShell
thermalProbeEnabled: false
- manifest: ({id: "hancore.shibumi.telemetry", version: "0.1.1-beta.16.3", kinds: ["service"]})
+ manifest: ({id: "hancore.shibumi.telemetry", version: "0.1.1-beta.16.4", kinds: ["service"]})
}
Cpu.Service {
id: cpu
shell: fakeShell
gpuProbeEnabled: false
- manifest: ({id: "hancore.shibumi.cpu", version: "0.1.1-beta.16.3", kinds: ["service"]})
+ manifest: ({id: "hancore.shibumi.cpu", version: "0.1.1-beta.16.4", kinds: ["service"]})
}
QtObject {
@@ -40,7 +40,7 @@ ShellRoot {
PowerState.Service {
id: power
shell: fakeShell
- manifest: ({id: "hancore.shibumi.power-state", version: "0.1.1-beta.16.3", kinds: ["service"]})
+ manifest: ({id: "hancore.shibumi.power-state", version: "0.1.1-beta.16.4", kinds: ["service"]})
batterySnapshotOverride: ({ready: false})
commandOverrides: ({}) // This pure projection smoke may never launch helpers.
}
diff --git a/tests/fixtures/native-runtime/probe.py b/tests/fixtures/native-runtime/probe.py
index 31a2b2f2..8dfb5c8e 100644
--- a/tests/fixtures/native-runtime/probe.py
+++ b/tests/fixtures/native-runtime/probe.py
@@ -132,7 +132,7 @@ def catalog_probe():
"actual native catalog identity DTO mismatch")
control = ready["snapshot"]["byId"]["hancore.shibumi.control-center"]
check(control["description"] and control["author"] == "HANCORE"
- and control["version"] == "0.1.1-beta.16.3"
+ and control["version"] == "0.1.1-beta.16.4"
and control["barWidget"]["defaultSection"] == "left",
"package-bound catalog presentation metadata was lost")
check(host_config() == original, "read-only catalog changed native config")
diff --git a/tests/fixtures/release-predecessor.json b/tests/fixtures/release-predecessor.json
index 197544a7..6890f316 100644
--- a/tests/fixtures/release-predecessor.json
+++ b/tests/fixtures/release-predecessor.json
@@ -1,11 +1,11 @@
{
"schemaVersion": 1,
- "version": "0.1.1-beta.16.2",
- "tag": "v0.1.1-beta.16.2",
- "revision": "bd1224ba92fbf279666abe362ff5cf7a165da096",
- "payloadDigest": "79c61d5e048c669c9eb27ebaad21987327892089daea501d0cf4185150a26f9e",
- "releaseId": 407198695,
- "releaseUrl": "https://github.com/HANCORE-linux/Shibumi-Shell/releases/tag/v0.1.1-beta.16.2",
- "publishedAt": "2026-10-08T19:39:25Z",
+ "version": "0.1.1-beta.16.3",
+ "tag": "v0.1.1-beta.16.3",
+ "revision": "1a13d6be7d5b4bc9aa03590b77b6531aefcad497",
+ "payloadDigest": "9d8cca435b465c52b0ff21d7a6780f90251a805dc1d1e72b78473ca2a8338a88",
+ "releaseId": 409150061,
+ "releaseUrl": "https://github.com/HANCORE-linux/Shibumi-Shell/releases/tag/v0.1.1-beta.16.3",
+ "publishedAt": "2026-10-10T19:12:27Z",
"prerelease": true
}
diff --git a/tests/gpu-selection-state-smoke.qml b/tests/gpu-selection-state-smoke.qml
index adf20967..1aa33e9c 100644
--- a/tests/gpu-selection-state-smoke.qml
+++ b/tests/gpu-selection-state-smoke.qml
@@ -25,13 +25,13 @@ ShellRoot {
id: stateService
shell: fakeShell
omarchyPath: Quickshell.env("OMARCHY_PATH")
- manifest: ({id: "hancore.shibumi.state", version: "0.1.1-beta.16.3", kinds: ["service"]})
+ manifest: ({id: "hancore.shibumi.state", version: "0.1.1-beta.16.4", kinds: ["service"]})
}
Cpu.Service {
id: cpuService
shell: fakeShell
- manifest: ({id: "hancore.shibumi.cpu", version: "0.1.1-beta.16.3", kinds: ["service"]})
+ manifest: ({id: "hancore.shibumi.cpu", version: "0.1.1-beta.16.4", kinds: ["service"]})
gpuProbeEnabled: false
}
diff --git a/tests/host-registry-prime-smoke.qml b/tests/host-registry-prime-smoke.qml
index bed60b3e..9f9b9b07 100644
--- a/tests/host-registry-prime-smoke.qml
+++ b/tests/host-registry-prime-smoke.qml
@@ -50,11 +50,11 @@ ShellRoot {
Shared.Provider {
id: stateProvider
pluginId: "hancore.shibumi.state"
- implementationVersion: "0.1.1-beta.16.3"
+ implementationVersion: "0.1.1-beta.16.4"
owner: stateOwner
host: stateHost
manifest: ({ id: "hancore.shibumi.state",
- version: "0.1.1-beta.16.3", kinds: ["service"] })
+ version: "0.1.1-beta.16.4", kinds: ["service"] })
}
QtObject {
@@ -77,11 +77,11 @@ ShellRoot {
}
Shared.Provider {
pluginId: "hancore.shibumi.bar"
- implementationVersion: "0.1.1-beta.16.3"
+ implementationVersion: "0.1.1-beta.16.4"
owner: owner
host: owner.providerHost
manifest: ({ id: "hancore.shibumi.bar",
- version: "0.1.1-beta.16.3", kinds: ["bar"] })
+ version: "0.1.1-beta.16.4", kinds: ["bar"] })
}
}
}
diff --git a/tests/media-spectrum-service-smoke.qml b/tests/media-spectrum-service-smoke.qml
index e3503fd5..b7a00e6d 100644
--- a/tests/media-spectrum-service-smoke.qml
+++ b/tests/media-spectrum-service-smoke.qml
@@ -42,7 +42,7 @@ ShellRoot {
Media.Service {
id: spectrum
shell: fakeShell
- manifest: ({id: "hancore.shibumi.media", version: "0.1.1-beta.16.3", kinds: ["service", "bar-widget"]})
+ manifest: ({id: "hancore.shibumi.media", version: "0.1.1-beta.16.4", kinds: ["service", "bar-widget"]})
}
Timer {
diff --git a/tests/plugin-update-service-smoke.qml b/tests/plugin-update-service-smoke.qml
index 937e05eb..66225e6f 100644
--- a/tests/plugin-update-service-smoke.qml
+++ b/tests/plugin-update-service-smoke.qml
@@ -209,7 +209,7 @@ ShellRoot {
if (service.available || service.check(true))
return root.fail("scoped update worker started before manifest admission")
service.manifest = {id: "hancore.shibumi.control-center",
- version: "0.1.1-beta.16.3", kinds: ["service", "bar-widget"]}
+ version: "0.1.1-beta.16.4", kinds: ["service", "bar-widget"]}
root.phase++
root.ticks = 0
return
diff --git a/tests/power-deferred-smoke.qml b/tests/power-deferred-smoke.qml
index a7165828..36b9eabd 100644
--- a/tests/power-deferred-smoke.qml
+++ b/tests/power-deferred-smoke.qml
@@ -57,7 +57,7 @@ ShellRoot {
PowerState.Service {
id: power
shell: host
- manifest: root.allowed ? ({id: "hancore.shibumi.power-state", version: "0.1.1-beta.16.3", kinds: ["service"]}) : null
+ manifest: root.allowed ? ({id: "hancore.shibumi.power-state", version: "0.1.1-beta.16.4", kinds: ["service"]}) : null
batterySnapshotOverride: battery
commandOverrides: ({
profiles: ["/usr/bin/python3", "-I", root.helper, root.directory, "profiles"],
diff --git a/tests/power-runtime-smoke.qml b/tests/power-runtime-smoke.qml
index 6a65c81d..a20dbb7b 100644
--- a/tests/power-runtime-smoke.qml
+++ b/tests/power-runtime-smoke.qml
@@ -24,7 +24,7 @@ ShellRoot {
readonly property var power: serviceLoader.item
readonly property string fixturePath: Quickshell.env("SHIBUMI_POWER_SCOPE_DIR")
readonly property string helper: decodeURIComponent(String(Qt.resolvedUrl("fixtures/power-runtime-helper.py")).substring(7))
- readonly property var validManifest: ({ id: "hancore.shibumi.power-state", version: "0.1.1-beta.16.3", kinds: ["service"], entryPoints: { service: "Service.qml" } })
+ readonly property var validManifest: ({ id: "hancore.shibumi.power-state", version: "0.1.1-beta.16.4", kinds: ["service"], entryPoints: { service: "Service.qml" } })
readonly property var fakeCommands: ({
profiles: ["/usr/bin/python3", "-I", helper, fixturePath, "profiles"],
activeProfile: ["/usr/bin/python3", "-I", helper, fixturePath, "activeProfile"],
@@ -55,10 +55,10 @@ ShellRoot {
}
SuiteRuntime.Provider {
pluginId: "hancore.shibumi.state"
- implementationVersion: "0.1.1-beta.16.3"
+ implementationVersion: "0.1.1-beta.16.4"
owner: state
host: root.stateEnabled ? stateHost : null
- manifest: ({ id: "hancore.shibumi.state", version: "0.1.1-beta.16.3", kinds: ["service"] })
+ manifest: ({ id: "hancore.shibumi.state", version: "0.1.1-beta.16.4", kinds: ["service"] })
}
QtObject {
id: battery
diff --git a/tests/power-service-runtime-smoke.qml b/tests/power-service-runtime-smoke.qml
index 20cd22a9..d6e64b9d 100644
--- a/tests/power-service-runtime-smoke.qml
+++ b/tests/power-service-runtime-smoke.qml
@@ -14,7 +14,7 @@ ShellRoot {
PowerState.Service {
id: power
shell: scopedHost
- manifest: ({ id: "hancore.shibumi.power-state", version: "0.1.1-beta.16.3",
+ manifest: ({ id: "hancore.shibumi.power-state", version: "0.1.1-beta.16.4",
kinds: ["service"], entryPoints: { service: "Service.qml" } })
batterySnapshotOverride: ({ ready: false })
commandOverrides: ({
diff --git a/tests/reactor-plugin-smoke.qml b/tests/reactor-plugin-smoke.qml
index f153cbc0..24f39f12 100644
--- a/tests/reactor-plugin-smoke.qml
+++ b/tests/reactor-plugin-smoke.qml
@@ -76,7 +76,7 @@ ShellRoot {
Audio.Service {
id: fakeAudio
shell: fakeShell
- manifest: ({id: "hancore.shibumi.audio", version: "0.1.1-beta.16.3", kinds: ["service"]})
+ manifest: ({id: "hancore.shibumi.audio", version: "0.1.1-beta.16.4", kinds: ["service"]})
nativeBackendEnabled: false
}
@@ -115,7 +115,7 @@ ShellRoot {
Reactor.Service {
id: reactorService
- manifest: ({id: "hancore.shibumi.reactor", version: "0.1.1-beta.16.3", kinds: ["service"]})
+ manifest: ({id: "hancore.shibumi.reactor", version: "0.1.1-beta.16.4", kinds: ["service"]})
shell: fakeShell
runtimeProbesEnabled: false
}
diff --git a/tests/reactor-runtime-smoke.qml b/tests/reactor-runtime-smoke.qml
index e600fc7e..0cf9114e 100644
--- a/tests/reactor-runtime-smoke.qml
+++ b/tests/reactor-runtime-smoke.qml
@@ -16,7 +16,7 @@ ShellRoot {
property int beforeLoss: 0
property var cachedMode: null
property var cachedTest: null
- readonly property var manifest: ({id: "hancore.shibumi.reactor", version: "0.1.1-beta.16.3", kinds: ["service"]})
+ readonly property var manifest: ({id: "hancore.shibumi.reactor", version: "0.1.1-beta.16.4", kinds: ["service"]})
function check(value, message) {
if (value) return
console.error("reactor-runtime:", message)
@@ -44,14 +44,14 @@ ShellRoot {
property bool outputMuted: true
}
SuiteRuntime.Provider {
- pluginId: "hancore.shibumi.state"; implementationVersion: "0.1.1-beta.16.3"
+ pluginId: "hancore.shibumi.state"; implementationVersion: "0.1.1-beta.16.4"
owner: state; host: root.stateEnabled ? stateHost : null
- manifest: ({id: "hancore.shibumi.state", version: "0.1.1-beta.16.3", kinds: ["service"]})
+ manifest: ({id: "hancore.shibumi.state", version: "0.1.1-beta.16.4", kinds: ["service"]})
}
SuiteRuntime.Provider {
- pluginId: "hancore.shibumi.audio"; implementationVersion: "0.1.1-beta.16.3"
+ pluginId: "hancore.shibumi.audio"; implementationVersion: "0.1.1-beta.16.4"
owner: audio; host: root.audioEnabled ? audioHost : null
- manifest: ({id: "hancore.shibumi.audio", version: "0.1.1-beta.16.3", kinds: ["service"]})
+ manifest: ({id: "hancore.shibumi.audio", version: "0.1.1-beta.16.4", kinds: ["service"]})
}
Reactor.Service { id: service; runtimeProbesEnabled: false }
Reactor.Service { id: duplicate; runtimeProbesEnabled: false }
diff --git a/tests/scoped-state-regression.qml b/tests/scoped-state-regression.qml
index 8761e74f..90fb32b6 100644
--- a/tests/scoped-state-regression.qml
+++ b/tests/scoped-state-regression.qml
@@ -36,7 +36,7 @@ ShellRoot {
id: state
shell: host
omarchyPath: Quickshell.env("OMARCHY_PATH")
- manifest: ({ id: "hancore.shibumi.state", version: "0.1.1-beta.16.3",
+ manifest: ({ id: "hancore.shibumi.state", version: "0.1.1-beta.16.4",
kinds: ["service"], __sourceDir: Quickshell.env("DECOY_MARKER_DIR") })
}
BarProbe.Marker {
diff --git a/tests/shared-runtime-regression.qml b/tests/shared-runtime-regression.qml
index 8f3578c2..c5c6f92d 100644
--- a/tests/shared-runtime-regression.qml
+++ b/tests/shared-runtime-regression.qml
@@ -54,7 +54,7 @@ ShellRoot {
id: state
shell: stateApi
omarchyPath: Quickshell.env("OMARCHY_PATH")
- manifest: ({id: "hancore.shibumi.state", version: "0.1.1-beta.16.3", kinds: ["service"]})
+ manifest: ({id: "hancore.shibumi.state", version: "0.1.1-beta.16.4", kinds: ["service"]})
}
BarProbe.Marker { id: markerBar }
Item {
@@ -66,10 +66,10 @@ ShellRoot {
Shared.Provider {
id: barProvider
pluginId: "hancore.shibumi.bar"
- implementationVersion: "0.1.1-beta.16.3"
+ implementationVersion: "0.1.1-beta.16.4"
owner: barOwner
host: barOwner.injectedHost
- manifest: ({id: "hancore.shibumi.bar", version: "0.1.1-beta.16.3", kinds: ["bar"]})
+ manifest: ({id: "hancore.shibumi.bar", version: "0.1.1-beta.16.4", kinds: ["bar"]})
}
}
Shared.HostShell { id: client; host: stateApi }
@@ -82,7 +82,7 @@ ShellRoot {
property int value: 0
Shared.Provider {
pluginId: "hancore.shibumi.audio"
- implementationVersion: "0.1.1-beta.16.3"
+ implementationVersion: "0.1.1-beta.16.4"
owner: peer
host: peer.injectedHost
manifest: peer.injectedManifest
@@ -152,7 +152,7 @@ ShellRoot {
check(left.state === null && right.state === null && !state.ready && !state.writePending,
"version drift retained State authority or queued intent")
check(!state.setGroupSetting("G4", "compact", true), "revoked State accepted intent")
- state.manifest = {id: "hancore.shibumi.state", version: "0.1.1-beta.16.3", kinds: ["service"]}
+ state.manifest = {id: "hancore.shibumi.state", version: "0.1.1-beta.16.4", kinds: ["service"]}
check(left.state === state && Shared.Runtime._selected("hancore.shibumi.state") !== oldLease,
"State did not register a new lifetime")
Shared.Runtime.unregisterProvider(oldLease)
@@ -161,10 +161,10 @@ ShellRoot {
check(first !== null && client.serviceFor("hancore.shibumi.audio") === null,
"parentless service published before injection")
first.injectedHost = audioApi
- first.injectedManifest = {id: "hancore.shibumi.audio", version: "0.1.1-beta.16.3", kinds: ["service"]}
+ first.injectedManifest = {id: "hancore.shibumi.audio", version: "0.1.1-beta.16.4", kinds: ["service"]}
check(client.serviceFor("hancore.shibumi.audio") === first, "provider registration failed")
second = serviceFactory.createObject(null, {injectedHost: audioApi,
- injectedManifest: {id: "hancore.shibumi.audio", version: "0.1.1-beta.16.3", kinds: ["service"]}})
+ injectedManifest: {id: "hancore.shibumi.audio", version: "0.1.1-beta.16.4", kinds: ["service"]}})
check(client.serviceFor("hancore.shibumi.audio") === null,
"duplicate provider was last-writer-wins: second=" + JSON.stringify({
manifest: second.injectedManifest, kindsArray: Array.isArray(second.injectedManifest.kinds),
@@ -192,7 +192,7 @@ ShellRoot {
first.injectedHost = audioApi
check(client.serviceFor("hancore.shibumi.audio") === first, "scope recovery stayed unavailable")
let getterReached = false
- const malformed = {version: "0.1.1-beta.16.3", kinds: ["service"]}
+ const malformed = {version: "0.1.1-beta.16.4", kinds: ["service"]}
Object.defineProperty(malformed, "id", {get: function() {
getterReached = true
throw new Error("controlled malformed metadata getter")
diff --git a/tests/state-service-smoke.qml b/tests/state-service-smoke.qml
index a8420994..84bf53b7 100644
--- a/tests/state-service-smoke.qml
+++ b/tests/state-service-smoke.qml
@@ -55,7 +55,7 @@ ShellRoot {
id: state
omarchyPath: Quickshell.env("OMARCHY_PATH")
shell: fakeShell
- manifest: ({id: "hancore.shibumi.state", version: "0.1.1-beta.16.3", kinds: ["service"]})
+ manifest: ({id: "hancore.shibumi.state", version: "0.1.1-beta.16.4", kinds: ["service"]})
}
Component.onCompleted: steps = [
function() {
diff --git a/tests/status-plugin-smoke.qml b/tests/status-plugin-smoke.qml
index c652aab2..892625a4 100644
--- a/tests/status-plugin-smoke.qml
+++ b/tests/status-plugin-smoke.qml
@@ -204,11 +204,11 @@ ShellRoot {
SuiteRuntime.Provider {
id: leasedBarProvider
pluginId: "hancore.shibumi.bar"
- implementationVersion: "0.1.1-beta.16.3"
+ implementationVersion: "0.1.1-beta.16.4"
owner: leasedBarOwner
host: leasedBarOwner.providerHost
manifest: ({ id: "hancore.shibumi.bar",
- version: "0.1.1-beta.16.3", kinds: ["bar"] })
+ version: "0.1.1-beta.16.4", kinds: ["bar"] })
}
}
@@ -226,7 +226,7 @@ ShellRoot {
Status.Service {
shell: rawScopedStatusShell
manifest: ({ id: "hancore.shibumi.status",
- version: "0.1.1-beta.16.3", kinds: ["service"] })
+ version: "0.1.1-beta.16.4", kinds: ["service"] })
actionRunner: actionRecorder
runtimeProbesEnabled: false
}
diff --git a/tests/telemetry-plugins-smoke.qml b/tests/telemetry-plugins-smoke.qml
index 832ce779..23d2ac93 100644
--- a/tests/telemetry-plugins-smoke.qml
+++ b/tests/telemetry-plugins-smoke.qml
@@ -27,7 +27,7 @@ ShellRoot {
Telemetry.Service {
id: telemetryService
shell: fakeShell
- manifest: ({id: "hancore.shibumi.telemetry", version: "0.1.1-beta.16.3", kinds: ["service"]})
+ manifest: ({id: "hancore.shibumi.telemetry", version: "0.1.1-beta.16.4", kinds: ["service"]})
// Keep this fixture isolated from machine-specific hwmon inventory.
thermalProbeEnabled: false
}
@@ -35,7 +35,7 @@ ShellRoot {
Cpu.Service {
id: cpuService
shell: fakeShell
- manifest: ({id: "hancore.shibumi.cpu", version: "0.1.1-beta.16.3", kinds: ["service"]})
+ manifest: ({id: "hancore.shibumi.cpu", version: "0.1.1-beta.16.4", kinds: ["service"]})
gpuProbeEnabled: false
}
diff --git a/tests/telemetry-runtime-smoke.qml b/tests/telemetry-runtime-smoke.qml
index 1d12eaf4..dcbc4e69 100644
--- a/tests/telemetry-runtime-smoke.qml
+++ b/tests/telemetry-runtime-smoke.qml
@@ -22,7 +22,7 @@ ShellRoot {
Qt.exit(1)
throw new Error(message)
}
- function manifest(id) { return {id: id, version: "0.1.1-beta.16.3", kinds: ["service"]} }
+ function manifest(id) { return {id: id, version: "0.1.1-beta.16.4", kinds: ["service"]} }
component ScopedHost: QtObject {
property string pluginId: ""
function serviceFor(id) { root.check(false, "raw scoped lookup: " + id); return null }
@@ -43,7 +43,7 @@ ShellRoot {
}
SuiteRuntime.Provider {
pluginId: "hancore.shibumi.state"
- implementationVersion: "0.1.1-beta.16.3"
+ implementationVersion: "0.1.1-beta.16.4"
host: stateHost
owner: state
manifest: root.manifest("hancore.shibumi.state")
diff --git a/tests/test_package_release.py b/tests/test_package_release.py
index b86b8d1e..7b252bab 100644
--- a/tests/test_package_release.py
+++ b/tests/test_package_release.py
@@ -243,10 +243,20 @@ def test_lifecycle_contract_pins_exact_release_identities(self) -> None:
},
"public-beta.16.3": {
"suiteVersion": "0.1.1-beta.16.3",
- "sourceRevisions": ["package:0.1.1-beta.16.3"],
+ "sourceRevisions": [
+ "1a13d6be7d5b4bc9aa03590b77b6531aefcad497",
+ "494fa69b9e348fa595eb4d55d2e11b4aa2fd4470",
+ "package:0.1.1-beta.16.3",
+ ],
"settingsStorageVersion": 1,
"payloadDigest": "9d8cca435b465c52b0ff21d7a6780f90251a805dc1d1e72b78473ca2a8338a88",
},
+ "public-beta.16.4": {
+ "suiteVersion": "0.1.1-beta.16.4",
+ "sourceRevisions": ["package:0.1.1-beta.16.4"],
+ "settingsStorageVersion": 1,
+ "payloadDigest": "bc60b476803f72d5604e592647db7f50c1f00723b986ae87e7730cba79779d34",
+ },
}
self.assertEqual(set(states), set(expected))
for identity_id, pinned in expected.items():
diff --git a/tests/theme-palette-runtime-smoke.qml b/tests/theme-palette-runtime-smoke.qml
index 9a8037e8..9fbbf126 100644
--- a/tests/theme-palette-runtime-smoke.qml
+++ b/tests/theme-palette-runtime-smoke.qml
@@ -26,7 +26,7 @@ ShellRoot {
id: state
shell: fakeShell
omarchyPath: Quickshell.env("OMARCHY_PATH")
- manifest: ({id: "hancore.shibumi.state", version: "0.1.1-beta.16.3", kinds: ["service"]})
+ manifest: ({id: "hancore.shibumi.state", version: "0.1.1-beta.16.4", kinds: ["service"]})
}
function colorText(value) {
diff --git a/tests/third-party-integration-smoke.qml b/tests/third-party-integration-smoke.qml
index 8e3d081c..2ca9bdcd 100644
--- a/tests/third-party-integration-smoke.qml
+++ b/tests/third-party-integration-smoke.qml
@@ -38,14 +38,14 @@ ShellRoot {
AudioOwner.Service {
shell: fakeShell
nativeBackendEnabled: false // Controlled peak stub; this fixture tests IPC.
- manifest: ({ id: "hancore.shibumi.audio", version: "0.1.1-beta.16.3", kinds: ["service"] })
+ manifest: ({ id: "hancore.shibumi.audio", version: "0.1.1-beta.16.4", kinds: ["service"] })
}
StateOwner.Service {
shell: fakeShell
omarchyPath: ""
manifest: ({
- id: "hancore.shibumi.state", version: "0.1.1-beta.16.3", kinds: ["service"]
+ id: "hancore.shibumi.state", version: "0.1.1-beta.16.4", kinds: ["service"]
})
}
diff --git a/tests/widget-pipeline-diagnostics-smoke.qml b/tests/widget-pipeline-diagnostics-smoke.qml
index 9a19fcbb..faacf06d 100644
--- a/tests/widget-pipeline-diagnostics-smoke.qml
+++ b/tests/widget-pipeline-diagnostics-smoke.qml
@@ -237,11 +237,11 @@ ShellRoot {
Shared.Provider {
id: stateProvider
pluginId: "hancore.shibumi.state"
- implementationVersion: "0.1.1-beta.16.3"
+ implementationVersion: "0.1.1-beta.16.4"
owner: stateOwner
host: stateHost
manifest: ({ id: "hancore.shibumi.state",
- version: "0.1.1-beta.16.3", kinds: ["service"] })
+ version: "0.1.1-beta.16.4", kinds: ["service"] })
}
QtObject {
@@ -290,7 +290,7 @@ ShellRoot {
barWidgetRegistry: scopedWidgetRegistry
manifest: ({
id: "hancore.shibumi.bar",
- version: "0.1.1-beta.16.3",
+ version: "0.1.1-beta.16.4",
kinds: ["bar"]
})
barConfig: ({
diff --git a/tests/workspaces-plugin-smoke.qml b/tests/workspaces-plugin-smoke.qml
index 56e8b5e2..97d93901 100644
--- a/tests/workspaces-plugin-smoke.qml
+++ b/tests/workspaces-plugin-smoke.qml
@@ -125,7 +125,7 @@ ShellRoot {
Workspaces.WorkspaceService {
id: workspaceState
shell: fakeShell
- manifest: ({id: "hancore.shibumi.workspaces", version: "0.1.1-beta.16.3", kinds: ["service"]})
+ manifest: ({id: "hancore.shibumi.workspaces", version: "0.1.1-beta.16.4", kinds: ["service"]})
stateService: preferenceState
backendOverride: QtObject {
property var focusedWorkspace: ({ id: 8 })
diff --git a/tests/workspaces-runtime-smoke.qml b/tests/workspaces-runtime-smoke.qml
index 3fc992f2..dc7a2a79 100644
--- a/tests/workspaces-runtime-smoke.qml
+++ b/tests/workspaces-runtime-smoke.qml
@@ -15,7 +15,7 @@ ShellRoot {
property var cachedPreference: null
property int callsBeforeLoss: 0
property int writesBeforeLoss: 0
- readonly property var validManifest: ({id: "hancore.shibumi.workspaces", version: "0.1.1-beta.16.3", kinds: ["service"]})
+ readonly property var validManifest: ({id: "hancore.shibumi.workspaces", version: "0.1.1-beta.16.4", kinds: ["service"]})
function check(value, message) {
if (value) return
console.error("workspaces-runtime:", message)
@@ -44,10 +44,10 @@ ShellRoot {
}
SuiteRuntime.Provider {
pluginId: "hancore.shibumi.state"
- implementationVersion: "0.1.1-beta.16.3"
+ implementationVersion: "0.1.1-beta.16.4"
owner: state
host: root.stateEnabled ? stateHost : null
- manifest: ({id: "hancore.shibumi.state", version: "0.1.1-beta.16.3", kinds: ["service"]})
+ manifest: ({id: "hancore.shibumi.state", version: "0.1.1-beta.16.4", kinds: ["service"]})
}
QtObject {
id: backend