Build the System Health settings page

This commit is contained in:
Gabriel Brown
2026-08-18 09:50:15 -04:00
parent 21aa9223df
commit 3238623934
11 changed files with 939 additions and 143 deletions
+222
View File
@@ -0,0 +1,222 @@
#!/usr/bin/env bash
# The approved Diagnostic Ledger is exercised in an isolated Quickshell
# harness. It never maps or reloads the user's production shell.
set -euo pipefail
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
settings_dir="$repo_dir/config/dot/quickshell/modules/settings"
fail() {
printf 'health UI contract: %s\n' "$1" >&2
exit 1
}
for file in HealthPage.qml HealthSummary.qml HealthCheckRow.qml; do
[[ -f "$settings_dir/$file" ]] || fail "$file is missing"
done
[[ ! -e "$settings_dir/ServicesPage.qml" ]] || fail 'ServicesPage.qml still exists'
rg -Fq 'HealthPage 1.0 HealthPage.qml' "$settings_dir/qmldir" \
|| fail 'HealthPage is not registered in the Settings QML module'
rg -Fq 'HealthSummary 1.0 HealthSummary.qml' "$settings_dir/qmldir" \
|| fail 'HealthSummary is not registered in the Settings QML module'
rg -Fq 'HealthCheckRow 1.0 HealthCheckRow.qml' "$settings_dir/qmldir" \
|| fail 'HealthCheckRow is not registered in the Settings QML module'
! rg -Fq 'ServicesPage 1.0 ServicesPage.qml' "$settings_dir/qmldir" \
|| fail 'retired ServicesPage remains registered in the Settings QML module'
rg -Fq 'label: "System Health"' "$settings_dir/SettingsSidebar.qml" \
|| fail 'sidebar does not label the stable services route System Health'
rg -Fq 'onTapped: root.pageRequested("services")' "$settings_dir/SettingsSidebar.qml" \
|| fail 'health footer does not open the stable services route'
rg -Fq 'height: 54' "$settings_dir/SettingsSidebar.qml" \
|| fail 'health footer lost its 54px target'
rg -Fq 'onClicked: Health.copyReport()' "$settings_dir/HealthSummary.qml" \
|| fail 'Copy Report does not use the redacted Health report path'
rg -Fq 'text: "Checking…"' "$settings_dir/HealthSummary.qml" \
|| fail 'refresh state is not expressed in text'
rg -Fq 'implicitHeight: 126' "$settings_dir/HealthSummary.qml" \
|| fail 'summary hero is not the approved stable 126px height'
rg -Fq 'implicitHeight: 62' "$settings_dir/HealthCheckRow.qml" \
|| fail 'health rows are below the approved 62px target'
rg -Fq 'Health.refresh()' "$settings_dir/HealthPage.qml" \
|| fail 'opening System Health does not request a fresh scan'
rg -Fq 'SystemSettings.openGnomePanel("network")' "$settings_dir/HealthPage.qml" \
|| fail 'Fedora ownership boundary does not open GNOME Settings'
rg -Fq 'Health.repair(check.id, false)' "$settings_dir/HealthPage.qml" \
|| fail 'Settings repair does not stay inline/non-external'
rg -Fq 'ShellState.openSettings(check.action.target)' "$settings_dir/HealthPage.qml" \
|| fail 'authored Settings targets are not routed directly'
! rg -n '#[0-9a-fA-F]{3,8}' \
"$settings_dir/HealthPage.qml" \
"$settings_dir/HealthSummary.qml" \
"$settings_dir/HealthCheckRow.qml" >/dev/null \
|| fail 'health UI introduced colors outside Theme'
python3 - "$settings_dir/HealthPage.qml" "$settings_dir/HealthSummary.qml" \
"$settings_dir/HealthCheckRow.qml" <<'PY' || fail 'approved health structure or accessibility contract is missing'
import sys
page, summary, row = [open(path, encoding="utf-8").read() for path in sys.argv[1:]]
labels = (
'if (status === "ok") return "Healthy";',
'if (status === "warning") return "Needs attention";',
'if (status === "error") return "Action required";',
'return "Not set up";',
)
assert all(label in page for label in labels)
assert 'group: "desktop-foundation"' in page
assert 'group: "input-media"' in page
assert 'group: "integrations"' in page
assert 'group: "panama-tools"' in page
assert 'SettingsCard {' in page and 'SettingsCard {' in summary
assert 'activeFocusOnTab: enabled' in summary
assert 'Keys.onReturnPressed' in summary and 'Keys.onSpacePressed' in summary
assert 'activeFocusOnTab: enabled' in row
assert 'Keys.onReturnPressed' in row and 'Keys.onSpacePressed' in row
assert 'border.width: activeFocus ? 2 : 1' in summary
assert 'border.width: activeFocus ? 2 : 1' in row
assert 'Health.diagnosticUnavailable ? "Retry"' in summary
assert 'Health.lastCopyResult' in summary
assert 'pendingConfirmation' in page
assert 'ddc-permissions' in page
PY
fixture='{"schemaVersion":1,"generatedAt":"2026-08-18T12:00:00Z","summary":{"status":"error","healthy":2,"warnings":2,"errors":1,"unconfigured":1},"context":{"session":"hyprland","versions":[{"id":"quickshell","version":"0.3.0"}]},"checks":[{"id":"desktop.vicinae","group":"desktop-foundation","title":"Vicinae","status":"warning","detail":"The launcher service is stopped.","action":{"kind":"repair","label":"Restart Vicinae","confirm":false}},{"id":"desktop.quickshell","group":"desktop-foundation","title":"Quickshell","status":"error","detail":"Panama shell needs to restart.","action":{"kind":"repair","label":"Restart Panama","confirm":true}},{"id":"input.pipewire","group":"input-media","title":"PipeWire","status":"ok","detail":"Audio graph is responding."},{"id":"integration.bluebubbles","group":"integrations","title":"BlueBubbles","status":"unconfigured","detail":"Messaging integration has not been enabled."},{"id":"integration.calendar","group":"integrations","title":"Calendar","status":"warning","detail":"Calendar probe timed out.","action":{"kind":"open","label":"Open Date & Time","confirm":false,"target":"datetime"}},{"id":"panama.caffeine","group":"panama-tools","title":"Caffeine","status":"ok","detail":"No duplicate sleep inhibitors."}]}'
state_home="$(mktemp -d /tmp/panama-health-ui.XXXXXX)"
config_path="$state_home/quickshell"
harness="$config_path/health-ui-harness.qml"
helper="$state_home/panama-doctor"
shell_log="$state_home/quickshell.log"
fixture_home="$state_home/home"
mkdir -p "$fixture_home"
cp -a "$repo_dir/config/dot/quickshell" "$config_path"
cat >"$helper" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
sleep 1.5
printf '%s\n' "$PANAMA_HEALTH_FIXTURE"
EOF
chmod +x "$helper"
cat >"$harness" <<'QML'
import Quickshell
import Quickshell.Io
import QtQuick
import qs.modules.settings
import qs.services
ShellRoot {
id: root
Component.onCompleted: {
Health.startupScanEnabled = false;
ShellState.settingsPage = "services";
Health.consumeSnapshot(Quickshell.env("PANAMA_HEALTH_FIXTURE"), 100);
}
Item {
width: 980
height: 820
SettingsShell {
id: settingsShell
anchors.fill: parent
}
}
IpcHandler {
target: "health-ui-test"
function state(): string {
return JSON.stringify(settingsShell.healthDiagnostics);
}
function request(id: string): bool {
return settingsShell.requestHealthAction(id);
}
}
}
QML
run() {
PANAMA_HEALTH_FIXTURE="$fixture" PANAMA_HEALTH_HELPER="$helper" \
HOME="$fixture_home" XDG_STATE_HOME="$state_home" \
qs -p "$harness" "$@"
}
harness_pid=""
cleanup() {
if [[ -n "$harness_pid" ]]; then
kill "$harness_pid" >/dev/null 2>&1 || true
for _ in $(seq 1 40); do
kill -0 "$harness_pid" >/dev/null 2>&1 || break
sleep 0.1
done
else
run kill >/dev/null 2>&1 || true
fi
rm -rf "$state_home"
}
trap cleanup EXIT
PANAMA_HEALTH_FIXTURE="$fixture" PANAMA_HEALTH_HELPER="$helper" \
HOME="$fixture_home" XDG_STATE_HOME="$state_home" \
qs -p "$harness" --daemonize >"$shell_log" 2>&1
for _ in $(seq 1 40); do
harness_pid="$(qs list --all 2>/dev/null | awk -v path="$harness" '
/Process ID:/ { pid = $3 }
index($0, "Config path: " path) { print pid; exit }
')"
[[ "$harness_pid" =~ ^[0-9]+$ ]] && break
sleep 0.1
done
for _ in $(seq 1 60); do
run ipc show 2>/dev/null | rg -q '^target health-ui-test$' && break
sleep 0.1
done
run ipc show 2>/dev/null | rg -q '^target health-ui-test$' \
|| { sed -n '1,200p' "$shell_log" >&2; fail 'isolated fixture did not start'; }
checking_state="$(run ipc call health-ui-test state)"
jq -e '
.issueIds == ["desktop.vicinae", "desktop.quickshell", "integration.calendar"]
and .desktopIds == ["desktop.vicinae", "desktop.quickshell"]
and .integrationIds == ["integration.bluebubbles", "integration.calendar"]
and .statusLabels == ["Needs attention", "Action required", "Healthy", "Not set up", "Needs attention", "Healthy"]
and .summaryHeight == 126
and (.rowHeights | length) == 9
and (.rowHeights | all(. >= 62))
and .checking == true
and .checkingText == "Checking…"
' >/dev/null <<<"$checking_state" || fail "checking fixture did not render the approved state: $checking_state"
checking_heights="$(jq -c .rowHeights <<<"$checking_state")"
for _ in $(seq 1 40); do
settled_state="$(run ipc call health-ui-test state)"
[[ "$(jq -r .checking <<<"$settled_state")" == "false" ]] && break
sleep 0.1
done
[[ "$(jq -c .rowHeights <<<"$settled_state")" == "$checking_heights" ]] \
|| fail 'row geometry changed after refresh settled'
jq -e '.copyFocusable == true and .refreshFocusable == true and .rowActionFocusable == true' \
>/dev/null <<<"$settled_state" || fail 'keyboard focus does not reach hero and row actions'
[[ "$(run ipc call health-ui-test request desktop.quickshell)" == "true" ]] \
|| fail 'restart confirmation fixture could not be requested'
confirmation_state="$(run ipc call health-ui-test state)"
jq -e '.confirmationVisible == true and .confirmationId == "desktop.quickshell"' \
>/dev/null <<<"$confirmation_state" || fail 'Quickshell restart did not open confirmation sheet'
if rg -i 'QQml|ReferenceError|TypeError|binding loop|failed to load component' "$shell_log"; then
fail 'isolated fixture emitted QML errors or warnings'
fi
trap - EXIT
cleanup
printf 'health UI contract: PASS\n'
+1 -1
View File
@@ -9,7 +9,7 @@ fail() {
exit 1
}
pages=(Home Displays Connectivity Sound Notifications ScreenIntelligence Services About)
pages=(Home Displays Connectivity Sound Notifications ScreenIntelligence Health About)
for page in "${pages[@]}"; do
page_file="$repo_dir/config/dot/quickshell/modules/settings/${page}Page.qml"
[[ -f "$page_file" ]] || fail "${page}Page.qml is missing"
@@ -58,8 +58,13 @@ wallpaper|Wallpaper|appearance
blur|Blur|appearance
timezone|Timezone|datetime
repeat delay|Repeat delay|shortcuts
system health|System Health|services
doctor|Copy health report|services
CASES
! rg -Fq 'Startup & Services' "$repo_dir/config/dot/quickshell/services/SettingsSearch.qml" \
|| fail 'search index still uses the retired Startup & Services name'
# ── Shortcuts are searchable by what they do ─────────────────────────────────
[[ "$(find_top screenshot | jq -r .topPage)" == "shortcuts" ]] \
|| fail 'searching a shortcut description did not route to the shortcuts page'