Add quiet System Health entry points

This commit is contained in:
Gabriel Brown
2026-08-18 10:19:03 -04:00
parent 8a0a09dbb4
commit d855a26bd9
7 changed files with 245 additions and 2 deletions
@@ -108,6 +108,10 @@ PanelWindow {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
HealthIndicator {
anchors.verticalCenter: parent.verticalCenter
}
ActivityIndicator { ActivityIndicator {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
@@ -0,0 +1,89 @@
// A deliberately absent-until-needed health affordance. Healthy and merely
// unconfigured systems leave no ornament or layout residue in the bar.
import QtQuick
import QtQuick.Controls
import qs.config
import qs.services
Rectangle {
id: root
signal activated
readonly property int issueCount: Health.summary.warnings + Health.summary.errors
readonly property color tone: Health.status === "error"
? Theme.danger
: (Health.status === "warning" ? Theme.warn : "transparent")
readonly property string statusText: root.issueCount === 1
? "1 system health issue"
: root.issueCount + " system health issues"
readonly property string accessibleLabel: Health.status === "error"
? "System Health: " + root.issueCount + (root.issueCount === 1 ? " issue requires action" : " issues require action")
: "System Health: " + root.issueCount + (root.issueCount === 1 ? " issue needs attention" : " issues need attention")
readonly property string tooltipText: root.accessibleLabel
visible: Health.actionable
implicitWidth: visible ? content.implicitWidth + 16 : 0
implicitHeight: visible ? 24 : 0
width: implicitWidth
height: implicitHeight
radius: 9
color: visible ? Theme.alpha(root.tone, 0.09) : "transparent"
border.width: activeFocus ? 2 : 1
border.color: visible ? Theme.alpha(root.tone, activeFocus ? 0.72 : 0.20) : "transparent"
activeFocusOnTab: visible
Accessible.role: Accessible.Button
Accessible.name: root.accessibleLabel
Accessible.description: "Open System Health"
Accessible.onPressAction: root.activated()
onActivated: {
ShellState.openSettings("services");
Health.refresh();
}
Keys.onReturnPressed: root.activated()
Keys.onEnterPressed: root.activated()
Keys.onSpacePressed: root.activated()
Row {
id: content
anchors.centerIn: parent
spacing: 6
Text {
anchors.verticalCenter: parent.verticalCenter
text: "\u{F0ECD}" // md-shield-alert-outline
color: root.tone
font.family: Theme.fontMono
font.pixelSize: 14
}
Text {
anchors.verticalCenter: parent.verticalCenter
text: String(root.issueCount)
color: root.tone
font.family: Theme.fontFamily
font.features: Theme.tabularFigures
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.DemiBold
}
}
MouseArea {
id: pointer
anchors.fill: parent
enabled: root.visible
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: root.activated()
}
ToolTip.visible: pointer.containsMouse && root.visible
ToolTip.delay: 500
ToolTip.text: root.tooltipText
}
+2 -1
View File
@@ -109,6 +109,7 @@ case "$action" in
clipboard) qs ipc call clipboard open ;; clipboard) qs ipc call clipboard open ;;
overview) qs ipc call overview open ;; overview) qs ipc call overview open ;;
settings) qs ipc call settings open ;; settings) qs ipc call settings open ;;
health) qs ipc call health open ;;
dnd) dnd)
state="$(qs ipc call notifications dnd)" state="$(qs ipc call notifications dnd)"
@@ -148,7 +149,7 @@ case "$action" in
;; ;;
*) *)
printf 'Usage: panama-action {%s}\n' \ printf 'Usage: panama-action {%s}\n' \
'control-center|notifications|calendar|clipboard|overview|settings|dnd|caffeine|night-light|focus-start|focus-end|capture|intelligence|screenshot|microphone|gallery|restart-shell' >&2 'control-center|notifications|calendar|clipboard|overview|settings|health|dnd|caffeine|night-light|focus-start|focus-end|capture|intelligence|screenshot|microphone|gallery|restart-shell' >&2
exit 2 exit 2
;; ;;
esac esac
@@ -0,0 +1,9 @@
#!/usr/bin/env bash
# @vicinae.schemaVersion 1
# @vicinae.title Panama: Check System Health
# @vicinae.mode silent
# @vicinae.icon ../../icons/hicolor/scalable/apps/panama-settings.svg
# @vicinae.description Review Panama services, integrations, and recovery actions.
# @vicinae.keywords ["health", "doctor", "repair", "services"]
exec "$HOME/.config/quickshell/scripts/panama-action" health
+123
View File
@@ -7,6 +7,7 @@ set -euo pipefail
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
settings_dir="$repo_dir/config/dot/quickshell/modules/settings" settings_dir="$repo_dir/config/dot/quickshell/modules/settings"
bar_dir="$repo_dir/config/dot/quickshell/modules/bar"
fail() { fail() {
printf 'health UI contract: %s\n' "$1" >&2 printf 'health UI contract: %s\n' "$1" >&2
@@ -16,6 +17,21 @@ fail() {
for file in HealthPage.qml HealthSummary.qml HealthCheckRow.qml; do for file in HealthPage.qml HealthSummary.qml HealthCheckRow.qml; do
[[ -f "$settings_dir/$file" ]] || fail "$file is missing" [[ -f "$settings_dir/$file" ]] || fail "$file is missing"
done done
[[ -f "$bar_dir/HealthIndicator.qml" ]] || fail 'HealthIndicator.qml is missing'
python3 - "$bar_dir/Bar.qml" <<'PY' || fail 'health indicator is not immediately before activity in the bar'
import re
import sys
source = open(sys.argv[1], encoding="utf-8").read()
match = re.search(r'HealthIndicator\s*\{.*?\}\s*ActivityIndicator\s*\{', source, re.S)
assert match is not None
PY
! rg -n '#[0-9a-fA-F]{3,8}' "$bar_dir/HealthIndicator.qml" >/dev/null \
|| fail 'health indicator introduced colors outside Theme'
! rg -n 'Behavior|Animation|Transition|pulse|shimmer' "$bar_dir/HealthIndicator.qml" >/dev/null \
|| fail 'health indicator introduced motion'
[[ ! -e "$settings_dir/ServicesPage.qml" ]] || fail 'ServicesPage.qml still exists' [[ ! -e "$settings_dir/ServicesPage.qml" ]] || fail 'ServicesPage.qml still exists'
rg -Fq 'HealthPage 1.0 HealthPage.qml' "$settings_dir/qmldir" \ rg -Fq 'HealthPage 1.0 HealthPage.qml' "$settings_dir/qmldir" \
|| fail 'HealthPage is not registered in the Settings QML module' || fail 'HealthPage is not registered in the Settings QML module'
@@ -108,6 +124,8 @@ import Quickshell
import Quickshell.Io import Quickshell.Io
import QtQuick import QtQuick
import "modules/bar" as BarModule
import qs.config
import qs.modules.settings import qs.modules.settings
import qs.services import qs.services
@@ -130,6 +148,11 @@ ShellRoot {
id: settingsShell id: settingsShell
anchors.fill: parent anchors.fill: parent
} }
BarModule.HealthIndicator {
id: healthIndicator
objectName: "health-indicator"
}
} }
IpcHandler { IpcHandler {
@@ -142,6 +165,58 @@ ShellRoot {
function request(id: string): bool { function request(id: string): bool {
return settingsShell.requestHealthAction(id); return settingsShell.requestHealthAction(id);
} }
function indicatorMode(mode: string): bool {
const checkStatus = mode === "healthy" ? "ok" : (mode === "unconfigured" ? "unconfigured" : mode);
const overallStatus = mode === "warning" || mode === "error" ? mode : "healthy";
const snapshot = {
schemaVersion: 1,
generatedAt: "2026-08-18T12:00:00Z",
summary: {
status: overallStatus,
healthy: mode === "healthy" ? 1 : 0,
warnings: mode === "warning" ? 1 : 0,
errors: mode === "error" ? 1 : 0,
unconfigured: mode === "unconfigured" ? 1 : 0
},
context: { session: "hyprland", versions: [] },
checks: [{
id: "desktop.vicinae",
group: "desktop-foundation",
title: "Vicinae",
status: checkStatus,
detail: "Fixture observation."
}]
};
return Health.consumeSnapshot(JSON.stringify(snapshot), Health.acceptedGeneration + 1);
}
function indicatorState(): string {
return JSON.stringify({
visible: healthIndicator.visible,
width: healthIndicator.width,
implicitWidth: healthIndicator.implicitWidth,
issueCount: healthIndicator.issueCount,
statusText: healthIndicator.statusText,
accessibleLabel: healthIndicator.accessibleLabel,
tooltipText: healthIndicator.tooltipText,
warningTone: healthIndicator.tone === Theme.warn,
errorTone: healthIndicator.tone === Theme.danger,
activeFocusOnTab: healthIndicator.activeFocusOnTab
});
}
function activateIndicator(): string {
ShellState.settingsPage = "home";
ShellState.settingsOpen = false;
const generation = Health.generation;
healthIndicator.activated();
return JSON.stringify({
page: ShellState.settingsPage,
settingsOpen: ShellState.settingsOpen,
refreshRequested: Health.generation > generation || Health.queuedRefresh
});
}
} }
} }
QML QML
@@ -231,6 +306,54 @@ jq -e '
' \ ' \
>/dev/null <<<"$confirmation_state" || fail 'Quickshell restart did not open confirmation sheet' >/dev/null <<<"$confirmation_state" || fail 'Quickshell restart did not open confirmation sheet'
for hidden_mode in healthy unconfigured; do
[[ "$(run ipc call health-ui-test indicatorMode "$hidden_mode")" == "true" ]] \
|| fail "$hidden_mode indicator fixture was rejected"
hidden_state="$(run ipc call health-ui-test indicatorState)"
jq -e '
.visible == false
and .width == 0
and .implicitWidth == 0
and .issueCount == 0
' >/dev/null <<<"$hidden_state" \
|| fail "$hidden_mode state reserved bar space: $hidden_state"
done
[[ "$(run ipc call health-ui-test indicatorMode warning)" == "true" ]] \
|| fail 'warning indicator fixture was rejected'
warning_state="$(run ipc call health-ui-test indicatorState)"
jq -e '
.visible == true
and .width > 0
and .implicitWidth > 0
and .issueCount == 1
and .statusText == "1 system health issue"
and .accessibleLabel == "System Health: 1 issue needs attention"
and .tooltipText == "System Health: 1 issue needs attention"
and .warningTone == true
and .errorTone == false
and .activeFocusOnTab == true
' >/dev/null <<<"$warning_state" || fail "warning indicator is not the approved amber accessible capsule: $warning_state"
[[ "$(run ipc call health-ui-test indicatorMode error)" == "true" ]] \
|| fail 'error indicator fixture was rejected'
error_state="$(run ipc call health-ui-test indicatorState)"
jq -e '
.visible == true
and .issueCount == 1
and .accessibleLabel == "System Health: 1 issue requires action"
and .tooltipText == "System Health: 1 issue requires action"
and .warningTone == false
and .errorTone == true
' >/dev/null <<<"$error_state" || fail "error indicator is not the approved red accessible capsule: $error_state"
activation_state="$(run ipc call health-ui-test activateIndicator)"
jq -e '
.page == "services"
and .settingsOpen == true
and .refreshRequested == true
' >/dev/null <<<"$activation_state" || fail "indicator activation did not open and refresh System Health: $activation_state"
if rg -i 'QQml|ReferenceError|TypeError|binding loop|failed to load component' "$shell_log"; then if rg -i 'QQml|ReferenceError|TypeError|binding loop|failed to load component' "$shell_log"; then
fail 'isolated fixture emitted QML errors or warnings' fail 'isolated fixture emitted QML errors or warnings'
fi fi
+6 -1
View File
@@ -148,6 +148,9 @@ assert_commands 'qs <ipc> <call> <overview> <open>'
run_action settings run_action settings
assert_commands 'qs <ipc> <call> <settings> <open>' assert_commands 'qs <ipc> <call> <settings> <open>'
run_action health
assert_commands 'qs <ipc> <call> <health> <open>'
run_action dnd run_action dnd
assert_commands $'qs <ipc> <call> <notifications> <dnd>\npanama-osd <message> <notifications-disabled-symbolic> <Do Not Disturb On>' assert_commands $'qs <ipc> <call> <notifications> <dnd>\npanama-osd <message> <notifications-disabled-symbolic> <Do Not Disturb On>'
@@ -191,11 +194,13 @@ assert_commands $'qs <kill>\nquickshell <--daemonize>\nqs <ipc> <show>\npanama-o
PANAMA_ACTION_TEST_FAIL_KILL=true run_action restart-shell PANAMA_ACTION_TEST_FAIL_KILL=true run_action restart-shell
assert_commands $'qs <kill>\nquickshell <--daemonize>\nqs <ipc> <show>\npanama-osd <message> <view-refresh-symbolic> <Panama Restarted>' assert_commands $'qs <kill>\nquickshell <--daemonize>\nqs <ipc> <show>\npanama-osd <message> <view-refresh-symbolic> <Panama Restarted>'
usage_output="$work/usage.txt"
if HOME="$work/home" PATH="$fake_bin:$PATH" PANAMA_ACTION_HELPER_DIR="$fake_bin" \ if HOME="$work/home" PATH="$fake_bin:$PATH" PANAMA_ACTION_HELPER_DIR="$fake_bin" \
PANAMA_ACTION_CONFIRM_ATTEMPTS=1 PANAMA_ACTION_CONFIRM_DELAY=0 PANAMA_ACTION_TEST_LOG="$command_log" \ PANAMA_ACTION_CONFIRM_ATTEMPTS=1 PANAMA_ACTION_CONFIRM_DELAY=0 PANAMA_ACTION_TEST_LOG="$command_log" \
"$dispatcher" definitely-not-an-action >/dev/null 2>&1; then "$dispatcher" definitely-not-an-action > /dev/null 2>"$usage_output"; then
fail 'unknown action was accepted' fail 'unknown action was accepted'
fi fi
rg -q '\bhealth\b' "$usage_output" || fail 'health is missing from dispatcher usage'
: >"$command_log" : >"$command_log"
if PANAMA_ACTION_TEST_FAIL_QS=true HOME="$work/home" PATH="$fake_bin:$PATH" \ if PANAMA_ACTION_TEST_FAIL_QS=true HOME="$work/home" PATH="$fake_bin:$PATH" \
@@ -24,6 +24,7 @@ declare -A expected=(
[open-clipboard.sh]=clipboard [open-clipboard.sh]=clipboard
[open-mission-control.sh]=overview [open-mission-control.sh]=overview
[open-settings.sh]=settings [open-settings.sh]=settings
[check-system-health.sh]=health
[toggle-dnd.sh]=dnd [toggle-dnd.sh]=dnd
[toggle-caffeine.sh]=caffeine [toggle-caffeine.sh]=caffeine
[toggle-night-light.sh]=night-light [toggle-night-light.sh]=night-light
@@ -72,6 +73,17 @@ for script_name in "${!expected[@]}"; do
grep -Fxq '# @vicinae.icon ../../icons/hicolor/scalable/apps/panama-settings.svg' "$script" \ grep -Fxq '# @vicinae.icon ../../icons/hicolor/scalable/apps/panama-settings.svg' "$script" \
|| fail "$script_name does not use the Panama application identity" || fail "$script_name does not use the Panama application identity"
if [[ $script_name == check-system-health.sh ]]; then
[[ $title == 'Panama: Check System Health' ]] \
|| fail "health command has the wrong title: $title"
grep -Fxq '# @vicinae.schemaVersion 1' "$script" \
|| fail 'health command does not use schema version 1'
grep -Fxq '# @vicinae.keywords ["health", "doctor", "repair", "services"]' "$script" \
|| fail 'health command has the wrong search vocabulary'
grep -Fxq 'exec "$HOME/.config/quickshell/scripts/panama-action" health' "$script" \
|| fail 'health command bypasses the stable dispatcher path'
fi
: >"$dispatch_log" : >"$dispatch_log"
HOME="$work/home" PANAMA_COMMAND_TEST_LOG="$dispatch_log" "$script" HOME="$work/home" PANAMA_COMMAND_TEST_LOG="$dispatch_log" "$script"
dispatched="$(cat "$dispatch_log")" dispatched="$(cat "$dispatch_log")"