Merge codex's System Health and recovery work
Brings in panama-doctor (a 25-check diagnostic with fixture-backed
contracts), a Health service, a System Health page replacing Startup &
Services, and a bar indicator that stays absent until something is
actually degraded. All seven of its contracts pass on the merge.
Three things needed resolving rather than accepting:
The branch predates the debranding, so its user-visible strings still
named the product -- "Panama desktop is healthy", "Restart Panama",
"Panama tools". Rewritten to say the same thing without the name, which
is what the rest of the app now does.
Its Fedora hand-off card was a single button calling openGnomePanel
("network") under a subtitle naming five subjects. Main had already
replaced that with a row per subject, each opening the panel that owns
it, so those rows are ported into HealthPage instead. Printers and
online accounts stay on Network & Devices with the rest of the network
hardware.
That broke its own assertion, which matched the literal
openGnomePanel("network") string. Rewritten rather than reverted: it now
checks the boundary card exists and that every panel named in HealthPage
is one openGnomePanel actually allows, since a name outside the
allow-list opens nothing at all. Verified it catches a plausible-looking
wrong name.
SettingsShell and SettingsSidebar conflicted because both sides added
pages; resolved as the union, keeping its System Health page and live
footer alongside main's Mouse & Touchpad, Privacy & Security, Region &
Language and Online Accounts.
Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
This commit is contained in:
Executable
+15
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [[ "${PANAMA_DOCTOR_FIXTURE_BUS:-ready}" == "missing" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
if [[ "${PANAMA_DOCTOR_FIXTURE_BUS:-ready}" == "invalid-utf8" ]]; then
|
||||
printf '\377\n'
|
||||
exit 0
|
||||
fi
|
||||
printf '%s\n' \
|
||||
'org.freedesktop.portal.Desktop 1000 portal' \
|
||||
'org.kde.kdeconnect 1000 kdeconnect' \
|
||||
'fixture clipboard body AA:BB:CC:DD:EE:FF'
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
name="${!#}"
|
||||
case ",${PANAMA_DOCTOR_FIXTURE_PROCESSES:-}," in
|
||||
*",$name:duplicate,"*) printf '4101\n4102\n' ;;
|
||||
*",$name:malformed,"*) printf 'not-a-pid\n' ;;
|
||||
*",$name:missing,"*) exit 1 ;;
|
||||
*) printf '4101\n' ;;
|
||||
esac
|
||||
Executable
+15
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
case "${1:-}" in
|
||||
--version) printf '%s\n' "${PANAMA_DOCTOR_FIXTURE_QS_VERSION:-Quickshell 0.2.0}" ;;
|
||||
ipc)
|
||||
if [[ "${PANAMA_DOCTOR_FIXTURE_QS:-ready}" == "malformed" ]]; then
|
||||
printf 'fixture-secret-token AA:BB:CC:DD:EE:FF\n'
|
||||
else
|
||||
printf '%s\n' 'target notifications' 'target clipboard' 'target wallpaper' 'target capture'
|
||||
fi
|
||||
;;
|
||||
*) exit 2 ;;
|
||||
esac
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [[ -n ${PANAMA_DOCTOR_FIXTURE_PROBE_SECRET+x} ]]; then
|
||||
exit 97
|
||||
fi
|
||||
|
||||
service="${4:-}"
|
||||
case ",${PANAMA_DOCTOR_FIXTURE_STOPPED:-}," in
|
||||
*",$service,"*) exit 3 ;;
|
||||
esac
|
||||
printf 'fixture-secret-token\n'
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
uid="$(/usr/bin/id -u)"
|
||||
printf 'Panama %s fixture-user 4101 systemd-inhibit sleep:idle Caffeine block\n' "$uid"
|
||||
if [[ "${PANAMA_DOCTOR_FIXTURE_CAFFEINE:-single}" == "duplicate" ]]; then
|
||||
printf 'Panama %s fixture-user 4102 systemd-inhibit sleep:idle Caffeine block\n' "$uid"
|
||||
fi
|
||||
if [[ "${PANAMA_DOCTOR_FIXTURE_CAFFEINE:-single}" == "malformed" ]]; then
|
||||
printf 'Panama %s fixture-user invalid Caffeine\n' "$uid"
|
||||
fi
|
||||
printf 'Other %s fixture-secret-token AA:BB:CC:DD:EE:FF fixture clipboard body ignore ignore\n' "$uid"
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
case "${1:-}" in
|
||||
--version) printf 'Vicinae 0.26.0 fixture-secret-token\n' ;;
|
||||
ping) exit 0 ;;
|
||||
*) exit 2 ;;
|
||||
esac
|
||||
Executable
+265
@@ -0,0 +1,265 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Health owns the accepted diagnostic snapshot. A newer unreadable response
|
||||
# must degrade diagnostics without discarding the last report that Settings
|
||||
# and future health surfaces will render.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
harness="$repo_dir/config/dot/quickshell/health-harness.qml"
|
||||
service="$repo_dir/config/dot/quickshell/services/Health.qml"
|
||||
shell="$repo_dir/config/dot/quickshell/shell.qml"
|
||||
warning_snapshot='{"schemaVersion":1,"generatedAt":"2026-08-18T00:00:00Z","summary":{"status":"warning","healthy":0,"warnings":2,"errors":0,"unconfigured":0},"context":{"session":"hyprland","versions":[{"id":"quickshell","version":"0.3.0"}]},"checks":[{"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":"warning","detail":"Duplicate inhibitors are active.","action":{"kind":"repair","label":"Release duplicate inhibitors","confirm":false}}]}'
|
||||
projection_snapshot="$(jq -c '
|
||||
.fixtureSecret = "fixture-secret"
|
||||
| .summary.fixtureSecret = "fixture-secret"
|
||||
| .context.fixtureSecret = "fixture-secret"
|
||||
| .context.versions[0].fixtureSecret = "fixture-secret"
|
||||
| .checks[0].fixtureSecret = "fixture-secret"
|
||||
' <<<"$warning_snapshot")"
|
||||
adversarial_snapshot="$(jq -c '
|
||||
.fixtureSecret = "fixture-secret"
|
||||
| .summary.fixtureSecret = "fixture-secret"
|
||||
| .context.fixtureSecret = "fixture-secret"
|
||||
| .context.versions[0].fixtureSecret = "fixture-secret"
|
||||
| .checks[0].fixtureSecret = "fixture-secret"
|
||||
| .checks[0].action.fixtureSecret = "fixture-secret"
|
||||
' <<<"$warning_snapshot")"
|
||||
|
||||
fail() {
|
||||
printf 'health service contract: %s\n' "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
[[ -f "$service" ]] || fail 'Health.qml is missing'
|
||||
[[ -f "$harness" ]] || fail 'health harness is missing'
|
||||
[[ -f "$shell" ]] || fail 'shell.qml is missing'
|
||||
|
||||
# shell.qml is not started here: it is the active desktop shell. Keep this
|
||||
# contract static while pinning the typed, redacted IPC boundary it exports.
|
||||
python3 - "$shell" <<'PY' || fail 'health IPC contract is missing or exposes unsafe state'
|
||||
import re
|
||||
import sys
|
||||
|
||||
text = open(sys.argv[1], encoding="utf-8").read()
|
||||
match = re.search(r'IpcHandler \{\s*target: "health"(?P<body>.*?)\n \}', text, re.S)
|
||||
if not match:
|
||||
raise SystemExit(1)
|
||||
body = match.group("body")
|
||||
required = (
|
||||
'function refresh(): bool { return Health.refresh(); }',
|
||||
'function status(): string {',
|
||||
'summary: Health.summary,',
|
||||
'busy: Health.busy,',
|
||||
'generation: Health.generation,',
|
||||
'acceptedGeneration: Health.acceptedGeneration,',
|
||||
'checks: Health.checks.map(check => ({ id: check.id, status: check.status }))',
|
||||
'ShellState.openSettings("services");',
|
||||
'Health.refresh();',
|
||||
'function repair(id: string): bool { return Health.repair(id, true); }',
|
||||
)
|
||||
if any(entry not in body for entry in required):
|
||||
raise SystemExit(1)
|
||||
if 'Health.snapshot' in body or 'Health.diagnostics' in body:
|
||||
raise SystemExit(1)
|
||||
status = re.search(r'function status\(\): string \{\s*return JSON\.stringify\(\{(?P<fields>.*?)\n \}\);', body, re.S)
|
||||
if not status:
|
||||
raise SystemExit(1)
|
||||
keys = re.findall(r'^\s*([A-Za-z][A-Za-z0-9]*):', status.group("fields"), re.M)
|
||||
if keys != ["summary", "busy", "generation", "acceptedGeneration", "checks"]:
|
||||
raise SystemExit(1)
|
||||
PY
|
||||
|
||||
fixture_dir="$(mktemp -d /tmp/panama-health.XXXXXX)"
|
||||
helper="$fixture_dir/panama-doctor"
|
||||
copy_bin="$fixture_dir/bin"
|
||||
copy_file="$fixture_dir/copied-report.json"
|
||||
repair_mode_file="$fixture_dir/repair-mode"
|
||||
repair_log="$fixture_dir/repair.log"
|
||||
notification_log="$fixture_dir/notifications.log"
|
||||
printf 'success\n' >"$repair_mode_file"
|
||||
printf '%s\n' \
|
||||
'#!/usr/bin/env bash' \
|
||||
'printf "%s\n" "$*" >>"$PANAMA_HEALTH_REPAIR_LOG"' \
|
||||
'if [[ "$1" == "--json" ]]; then' \
|
||||
' sleep 0.2' \
|
||||
" printf '%s\\n' '$warning_snapshot'" \
|
||||
' exit 0' \
|
||||
'fi' \
|
||||
'if [[ "$1" == "--repair" && "$2" == "panama.caffeine" && "$3" == "--json" ]]; then' \
|
||||
' sleep 0.25' \
|
||||
' case "$(cat "$PANAMA_HEALTH_REPAIR_MODE_FILE")" in' \
|
||||
' success) printf "{\"schemaVersion\":1,\"checkId\":\"panama.caffeine\",\"accepted\":true,\"exitCode\":0,\"message\":\"Duplicate inhibitors were released.\"}\\n"; exit 0 ;;' \
|
||||
' failed) printf "{\"schemaVersion\":1,\"checkId\":\"panama.caffeine\",\"accepted\":true,\"exitCode\":7,\"message\":\"Duplicate inhibitors could not be released.\"}\\n"; exit 7 ;;' \
|
||||
' mismatch) printf "{\"schemaVersion\":1,\"checkId\":\"desktop.vicinae\",\"accepted\":true,\"exitCode\":0,\"message\":\"Wrong row.\"}\\n"; exit 0 ;;' \
|
||||
' *) printf "not-json\\n"; exit 0 ;;' \
|
||||
' esac' \
|
||||
'fi' \
|
||||
'exit 2' >"$helper"
|
||||
chmod +x "$helper"
|
||||
mkdir -p "$copy_bin"
|
||||
printf '%s\n' \
|
||||
'#!/usr/bin/env bash' \
|
||||
'/usr/bin/cat > "$PANAMA_HEALTH_COPY_FILE"' >"$copy_bin/wl-copy"
|
||||
printf '%s\n' \
|
||||
'#!/usr/bin/env bash' \
|
||||
'printf "%s\n" "$*" >>"$PANAMA_HEALTH_NOTIFICATION_LOG"' >"$copy_bin/notify-send"
|
||||
chmod +x "$copy_bin/wl-copy" "$copy_bin/notify-send"
|
||||
|
||||
run() {
|
||||
PATH="$copy_bin:$PATH" PANAMA_HEALTH_HELPER="$helper" PANAMA_HEALTH_COPY_FILE="$copy_file" \
|
||||
PANAMA_HEALTH_REPAIR_MODE_FILE="$repair_mode_file" PANAMA_HEALTH_REPAIR_LOG="$repair_log" \
|
||||
PANAMA_HEALTH_NOTIFICATION_LOG="$notification_log" qs -p "$harness" "$@"
|
||||
}
|
||||
harness_pid=""
|
||||
|
||||
cleanup() {
|
||||
[[ -n "$harness_pid" ]] && kill "$harness_pid" >/dev/null 2>&1 || true
|
||||
rm -rf "$fixture_dir"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
PATH="$copy_bin:$PATH" PANAMA_HEALTH_HELPER="$helper" PANAMA_HEALTH_COPY_FILE="$copy_file" \
|
||||
PANAMA_HEALTH_REPAIR_MODE_FILE="$repair_mode_file" PANAMA_HEALTH_REPAIR_LOG="$repair_log" \
|
||||
PANAMA_HEALTH_NOTIFICATION_LOG="$notification_log" \
|
||||
qs -p "$harness" --daemonize >/dev/null
|
||||
for _ in $(seq 1 40); do
|
||||
run ipc show 2>/dev/null | rg -q '^target health-test$' && break
|
||||
sleep 0.1
|
||||
done
|
||||
run ipc show 2>/dev/null | rg -q '^target health-test$' || fail 'test IPC target did not start'
|
||||
harness_pid="$(run list | awk '/Process ID:/ { print $3; exit }')"
|
||||
|
||||
[[ "$(run ipc call health-test accept "$warning_snapshot" 0)" == "true" ]] \
|
||||
|| fail 'valid warning snapshot was rejected'
|
||||
state="$(run ipc call health-test status)"
|
||||
jq -e '.status == "warning" and .acceptedGeneration == 0 and .checks == ["integration.calendar", "panama.caffeine"] and .diagnosticUnavailable == false' \
|
||||
>/dev/null <<<"$state" || fail "valid warning snapshot was not accepted intact: $state"
|
||||
|
||||
[[ "$(run ipc call health-test accept "$projection_snapshot" 0)" == "true" ]] \
|
||||
|| fail 'snapshot with unknown non-action fields was rejected instead of safely projected'
|
||||
stored_report="$(run ipc call health-test report)"
|
||||
! grep -Fq 'fixture-secret' <<<"$stored_report" \
|
||||
|| fail "accepted snapshot retained an unknown secret field: $stored_report"
|
||||
[[ "$(run ipc call health-test copy)" == "true" ]] \
|
||||
|| fail 'copy report was refused'
|
||||
for _ in $(seq 1 40); do
|
||||
[[ -f "$copy_file" ]] && break
|
||||
sleep 0.1
|
||||
done
|
||||
[[ -f "$copy_file" ]] || fail 'copy report did not reach wl-copy'
|
||||
! grep -Fq 'fixture-secret' "$copy_file" \
|
||||
|| fail 'copied report retained an unknown secret field'
|
||||
|
||||
adversarial_result="$(run ipc call health-test accept "$adversarial_snapshot" 1)"
|
||||
[[ "$adversarial_result" == "true" || "$adversarial_result" == "false" ]] \
|
||||
|| fail "adversarial action fixture did not return a Boolean: $adversarial_result"
|
||||
stored_report="$(run ipc call health-test report)"
|
||||
! grep -Fq 'fixture-secret' <<<"$stored_report" \
|
||||
|| fail "adversarial snapshot leaked an unknown secret field: $stored_report"
|
||||
|
||||
[[ "$(run ipc call health-test accept "$warning_snapshot" -1)" == "false" ]] \
|
||||
|| fail 'older generation replaced the current snapshot'
|
||||
state="$(run ipc call health-test status)"
|
||||
jq -e '.acceptedGeneration == 0 and .checks == ["integration.calendar", "panama.caffeine"]' \
|
||||
>/dev/null <<<"$state" || fail "older generation altered accepted state: $state"
|
||||
|
||||
[[ "$(run ipc call health-test accept '{not json' 1)" == "false" ]] \
|
||||
|| fail 'malformed snapshot was accepted'
|
||||
state="$(run ipc call health-test status)"
|
||||
jq -e '.diagnosticUnavailable == true and .checks == ["integration.calendar", "panama.caffeine"]' \
|
||||
>/dev/null <<<"$state" || fail "malformed snapshot discarded the last valid checks: $state"
|
||||
|
||||
before_generation="$(jq -r .generation <<<"$state")"
|
||||
run ipc call health-test queue >/dev/null
|
||||
state="$(run ipc call health-test status)"
|
||||
jq -e '.queuedRefresh == true and .generation == ($before + 1)' --argjson before "$before_generation" \
|
||||
>/dev/null <<<"$state" || fail "two refreshes did not retain exactly one follow-up: $state"
|
||||
for _ in $(seq 1 120); do
|
||||
state="$(run ipc call health-test status)"
|
||||
jq -e '.busy == false and .generation == ($before + 2) and .queuedRefresh == false' --argjson before "$before_generation" \
|
||||
>/dev/null <<<"$state" && break
|
||||
sleep 0.1
|
||||
done
|
||||
jq -e '.busy == false and .generation == ($before + 2) and .queuedRefresh == false' --argjson before "$before_generation" \
|
||||
>/dev/null <<<"$state" || fail "queued refresh did not run exactly once: $state"
|
||||
|
||||
printf 'success\n' >"$repair_mode_file"
|
||||
repair_generation="$(jq -r .generation <<<"$state")"
|
||||
[[ "$(run ipc call health-test repair panama.caffeine)" == "true" ]] \
|
||||
|| fail 'repairable check was refused'
|
||||
run ipc call health-test queue >/dev/null
|
||||
working_state="$(run ipc call health-test status)"
|
||||
jq -e '.repairingId == "panama.caffeine" and .queuedRefresh == true
|
||||
and (.checkStates[] | select(.id == "panama.caffeine") | .status) == "warning"' \
|
||||
>/dev/null <<<"$working_state" || fail "repair did not retain the degraded row while working: $working_state"
|
||||
for _ in $(seq 1 120); do
|
||||
state="$(run ipc call health-test status)"
|
||||
jq -e '.busy == false and .generation == ($before + 1) and .queuedRefresh == false' --argjson before "$repair_generation" \
|
||||
>/dev/null <<<"$state" && break
|
||||
sleep 0.1
|
||||
done
|
||||
jq -e '.busy == false and .generation == ($before + 1) and .queuedRefresh == false
|
||||
and .lastRepair == {schemaVersion:1, checkId:"panama.caffeine", accepted:true, exitCode:0, message:"Duplicate inhibitors were released."}
|
||||
and (.checkStates[] | select(.id == "panama.caffeine") | .status) == "warning"' \
|
||||
--argjson before "$repair_generation" >/dev/null <<<"$state" \
|
||||
|| fail "accepted repair was trusted before exactly one observed rescan: $state"
|
||||
|
||||
# A syntactically valid command failure remains inline for Settings and still
|
||||
# receives exactly one observed rescan.
|
||||
printf 'failed\n' >"$repair_mode_file"
|
||||
failure_generation="$(jq -r .generation <<<"$state")"
|
||||
[[ "$(run ipc call health-test repair panama.caffeine)" == "true" ]] \
|
||||
|| fail 'second repairable check was refused'
|
||||
for _ in $(seq 1 120); do
|
||||
state="$(run ipc call health-test status)"
|
||||
jq -e '.busy == false and .generation == ($before + 1)' --argjson before "$failure_generation" \
|
||||
>/dev/null <<<"$state" && break
|
||||
sleep 0.1
|
||||
done
|
||||
jq -e '.lastRepair.checkId == "panama.caffeine"
|
||||
and .lastRepair.accepted == true and .lastRepair.exitCode == 7
|
||||
and .lastRepair.message == "Duplicate inhibitors could not be released."
|
||||
and .generation == ($before + 1)' --argjson before "$failure_generation" \
|
||||
>/dev/null <<<"$state" || fail "known repair failure was not retained inline: $state"
|
||||
[[ ! -e "$notification_log" || ! -s "$notification_log" ]] \
|
||||
|| fail 'Settings-originated repair emitted an external notification'
|
||||
|
||||
# A malformed or mismatched helper response is contained and cannot masquerade
|
||||
# as recovery; it also schedules only one scan.
|
||||
printf 'mismatch\n' >"$repair_mode_file"
|
||||
mismatch_generation="$(jq -r .generation <<<"$state")"
|
||||
[[ "$(run ipc call health-test repair panama.caffeine)" == "true" ]] \
|
||||
|| fail 'mismatch repair fixture was refused'
|
||||
for _ in $(seq 1 120); do
|
||||
state="$(run ipc call health-test status)"
|
||||
jq -e '.busy == false and .generation == ($before + 1)' --argjson before "$mismatch_generation" \
|
||||
>/dev/null <<<"$state" && break
|
||||
sleep 0.1
|
||||
done
|
||||
jq -e '.lastRepair.checkId == "panama.caffeine" and .lastRepair.accepted == false
|
||||
and .lastRepair.exitCode == 0 and .generation == ($before + 1)' \
|
||||
--argjson before "$mismatch_generation" >/dev/null <<<"$state" \
|
||||
|| fail "mismatched repair JSON escaped containment: $state"
|
||||
|
||||
[[ "$(run ipc call health-test repair unknown.check)" == "false" ]] \
|
||||
|| fail 'unknown check started a repair'
|
||||
[[ "$(run ipc call health-test repair integration.calendar)" == "false" ]] \
|
||||
|| fail 'non-repairable check started a repair'
|
||||
state="$(run ipc call health-test status)"
|
||||
jq -e '.repairingId == "" and .generation == ($before + 1)' --argjson before "$mismatch_generation" \
|
||||
>/dev/null <<<"$state" || fail "rejected repair altered process state: $state"
|
||||
|
||||
python3 - "$service" <<'PY' || fail 'external repair failure notification is not bounded'
|
||||
import sys
|
||||
|
||||
source = open(sys.argv[1], encoding="utf-8").read()
|
||||
assert 'if (failed && external && !failureNotification.running)' in source
|
||||
assert '"notify-send", "-a", "Panama", "-i", "dialog-error-symbolic"' in source
|
||||
assert '"Panama action failed", "The requested health repair could not be completed."' in source
|
||||
PY
|
||||
|
||||
trap - EXIT
|
||||
cleanup
|
||||
printf 'health service contract: PASS\n'
|
||||
Executable
+415
@@ -0,0 +1,415 @@
|
||||
#!/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"
|
||||
bar_dir="$repo_dir/config/dot/quickshell/modules/bar"
|
||||
|
||||
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
|
||||
[[ -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'
|
||||
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'
|
||||
# The Fedora hand-off is a row per subject, not one button that opened the
|
||||
# network panel whatever it was labelled. What matters is that each row reaches
|
||||
# the panel that owns it, so this checks the boundary still exists and that
|
||||
# every panel it names is one openGnomePanel accepts -- a name outside that
|
||||
# allow-list opens nothing and reports an error, i.e. a dead button.
|
||||
rg -Fq 'title: "Fedora system settings"' "$settings_dir/HealthPage.qml" \
|
||||
|| fail 'the Fedora ownership boundary card is gone'
|
||||
|
||||
allowed="$(rg -o '"[a-z-]+"' "$repo_dir/config/dot/quickshell/services/SystemSettings.qml" \
|
||||
| sed -n '/"\(applications\|background\|bluetooth\|color\|display\|keyboard\|mouse\|multitasking\|network\|notifications\|online-accounts\|power\|printers\|privacy\|search\|sharing\|sound\|system\|universal-access\|wacom\|wellbeing\|wifi\|wwan\)"/p' \
|
||||
| tr -d '"' | sort -u)"
|
||||
|
||||
while read -r panel; do
|
||||
[[ -n "$panel" ]] || continue
|
||||
grep -qx "$panel" <<<"$allowed" \
|
||||
|| fail "the Fedora card opens \"$panel\", which openGnomePanel does not allow -- that button does nothing"
|
||||
done < <(rg -o 'openGnomePanel\("([a-z-]+)"' -r '$1' "$settings_dir/HealthPage.qml" | sort -u)
|
||||
|
||||
rg -q 'openGnomePanel\(' "$settings_dir/HealthPage.qml" \
|
||||
|| fail 'the Fedora ownership boundary does not open GNOME Settings at all'
|
||||
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
|
||||
if [[ "${1:-}" == "--repair" ]]; then
|
||||
sleep 0.35
|
||||
printf '{"schemaVersion":1,"checkId":"%s","accepted":true,"exitCode":7,"message":"The authored repair failed."}\n' "$2"
|
||||
exit 7
|
||||
fi
|
||||
sleep 1.5
|
||||
printf '%s\n' "$PANAMA_HEALTH_FIXTURE"
|
||||
EOF
|
||||
chmod +x "$helper"
|
||||
|
||||
cat >"$harness" <<'QML'
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import QtQuick
|
||||
|
||||
import "modules/bar" as BarModule
|
||||
import qs.config
|
||||
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);
|
||||
}
|
||||
|
||||
FloatingWindow {
|
||||
title: "Panama Health UI Contract"
|
||||
visible: true
|
||||
implicitWidth: 980
|
||||
implicitHeight: 820
|
||||
|
||||
SettingsShell {
|
||||
id: settingsShell
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
BarModule.HealthIndicator {
|
||||
id: healthIndicator
|
||||
objectName: "health-indicator"
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "health-ui-test"
|
||||
|
||||
function state(): string {
|
||||
return JSON.stringify(settingsShell.healthDiagnostics);
|
||||
}
|
||||
|
||||
function request(id: string): bool {
|
||||
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
|
||||
|
||||
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 '
|
||||
.renderedRows == [
|
||||
{objectName:"health-check-row:issue:desktop.vicinae", id:"desktop.vicinae", section:"issue", statusText:"Needs attention"},
|
||||
{objectName:"health-check-row:issue:desktop.quickshell", id:"desktop.quickshell", section:"issue", statusText:"Action required"},
|
||||
{objectName:"health-check-row:issue:integration.calendar", id:"integration.calendar", section:"issue", statusText:"Needs attention"},
|
||||
{objectName:"health-check-row:quiet:input.pipewire", id:"input.pipewire", section:"quiet", statusText:"Healthy"},
|
||||
{objectName:"health-check-row:quiet:integration.bluebubbles", id:"integration.bluebubbles", section:"quiet", statusText:"Not set up"},
|
||||
{objectName:"health-check-row:quiet:panama.caffeine", id:"panama.caffeine", section:"quiet", statusText:"Healthy"}
|
||||
]
|
||||
and (.renderedRows | map(.id) | length) == 6
|
||||
and (.renderedRows | map(.id) | unique | length) == 6
|
||||
and .emptyQuietGroups == ["desktop-foundation"]
|
||||
and .summaryHeight == 126
|
||||
and (.rowHeights | length) == 6
|
||||
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
|
||||
sleep 0.1
|
||||
settled_state="$(run ipc call health-ui-test state)"
|
||||
[[ "$(jq -c .rowHeights <<<"$settled_state")" == "$checking_heights" ]] \
|
||||
|| fail 'row geometry changed after refresh settled'
|
||||
jq -e '
|
||||
(.focusChain | index("health-copy-report-button")) != null
|
||||
and (.focusChain | index("health-refresh-button")) != null
|
||||
and (.focusChain | any(startswith("health-row-action:")))
|
||||
' >/dev/null <<<"$settled_state" || fail "actual focus-chain traversal does not reach hero and row actions: $settled_state"
|
||||
|
||||
settled_heights="$(jq -c .rowHeights <<<"$settled_state")"
|
||||
[[ "$(run ipc call health-ui-test request desktop.vicinae)" == "true" ]] \
|
||||
|| fail 'inline repair fixture could not be requested'
|
||||
working_repair_state="$(run ipc call health-ui-test state)"
|
||||
jq -e '.renderedRows[] | select(.id == "desktop.vicinae") | .statusText == "Working…"' \
|
||||
>/dev/null <<<"$working_repair_state" || fail "repair row did not show Working state: $working_repair_state"
|
||||
[[ "$(jq -c .rowHeights <<<"$working_repair_state")" == "$settled_heights" ]] \
|
||||
|| fail 'repair Working state changed row geometry'
|
||||
for _ in $(seq 1 40); do
|
||||
failed_repair_state="$(run ipc call health-ui-test state)"
|
||||
jq -e '.renderedRows[] | select(.id == "desktop.vicinae") | .statusText == "Repair failed"' \
|
||||
>/dev/null <<<"$failed_repair_state" && break
|
||||
sleep 0.1
|
||||
done
|
||||
jq -e '.renderedRows[] | select(.id == "desktop.vicinae") | .statusText == "Repair failed"' \
|
||||
>/dev/null <<<"$failed_repair_state" || fail "repair failure was not shown inline: $failed_repair_state"
|
||||
[[ "$(jq -c .rowHeights <<<"$failed_repair_state")" == "$settled_heights" ]] \
|
||||
|| fail 'repair failure changed row geometry'
|
||||
[[ "$(jq -r '.renderedRows | map(.id) | unique | length' <<<"$failed_repair_state")" == 6 ]] \
|
||||
|| fail 'repair state duplicated a health action row'
|
||||
for _ in $(seq 1 40); do
|
||||
failed_repair_state="$(run ipc call health-ui-test state)"
|
||||
[[ "$(jq -r .checking <<<"$failed_repair_state")" == "false" ]] && break
|
||||
sleep 0.1
|
||||
done
|
||||
[[ "$(jq -r .checking <<<"$failed_repair_state")" == "false" ]] \
|
||||
|| fail 'post-repair scan did not settle before the next action'
|
||||
|
||||
[[ "$(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"
|
||||
and (.activatedRows | index("health-check-row:issue:desktop.quickshell")) != null
|
||||
and (.activatedRows | map(select(endswith(":desktop.quickshell"))) | length) == 1
|
||||
' \
|
||||
>/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
|
||||
fail 'isolated fixture emitted QML errors or warnings'
|
||||
fi
|
||||
|
||||
trap - EXIT
|
||||
cleanup
|
||||
printf 'health UI contract: PASS\n'
|
||||
@@ -148,6 +148,9 @@ assert_commands 'qs <ipc> <call> <overview> <open>'
|
||||
run_action settings
|
||||
assert_commands 'qs <ipc> <call> <settings> <open>'
|
||||
|
||||
run_action health
|
||||
assert_commands 'qs <ipc> <call> <health> <open>'
|
||||
|
||||
run_action dnd
|
||||
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
|
||||
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" \
|
||||
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'
|
||||
fi
|
||||
rg -q '\bhealth\b' "$usage_output" || fail 'health is missing from dispatcher usage'
|
||||
|
||||
: >"$command_log"
|
||||
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-mission-control.sh]=overview
|
||||
[open-settings.sh]=settings
|
||||
[check-system-health.sh]=health
|
||||
[toggle-dnd.sh]=dnd
|
||||
[toggle-caffeine.sh]=caffeine
|
||||
[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" \
|
||||
|| 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"
|
||||
HOME="$work/home" PANAMA_COMMAND_TEST_LOG="$dispatch_log" "$script"
|
||||
dispatched="$(cat "$dispatch_log")"
|
||||
|
||||
Executable
+431
@@ -0,0 +1,431 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# The doctor is deliberately exercised through its command boundary. The
|
||||
# fixture commands include sensitive-looking output so this test proves the
|
||||
# report only retains explicitly parsed, non-sensitive observations.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
doctor="$repo_dir/config/dot/quickshell/scripts/panama-doctor"
|
||||
fixture_root="$repo_dir/tests/quickshell/fixtures/doctor"
|
||||
|
||||
fail() {
|
||||
printf 'panama doctor contract: %s\n' "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
fixture="$(mktemp -d /tmp/panama-doctor.XXXXXX)"
|
||||
trap 'rm -rf "$fixture"' EXIT
|
||||
|
||||
home="$fixture/home"
|
||||
config_home="$home/.config"
|
||||
state_home="$home/.local/state"
|
||||
runtime_dir="$fixture/runtime"
|
||||
bin_dir="$fixture/bin"
|
||||
data_home="$home/.local/share"
|
||||
|
||||
mkdir -p "$config_home" "$state_home" "$runtime_dir" "$bin_dir" "$data_home/vicinae"
|
||||
cp "$fixture_root/bin/"* "$bin_dir/"
|
||||
chmod +x "$bin_dir"/*
|
||||
|
||||
# These are intentionally tiny stand-ins for authored executable probes. The
|
||||
# named fixture scripts above cover probes whose output needs branch coverage.
|
||||
for tool in hyprctl wl-paste grim tesseract kitty nextcloud rustdesk kdeconnect-cli; do
|
||||
cat >"$bin_dir/$tool" <<'EOF'
|
||||
#!/usr/bin/bash
|
||||
case "${0##*/}" in
|
||||
hyprctl) printf 'Hyprland 0.50.0\n' ;;
|
||||
esac
|
||||
EOF
|
||||
chmod +x "$bin_dir/$tool"
|
||||
done
|
||||
|
||||
cat >"$bin_dir/flatpak" <<'EOF'
|
||||
#!/usr/bin/bash
|
||||
if [[ "${1:-}" == "info" && "${2:-}" == "app.bluebubbles.BlueBubbles" \
|
||||
&& "${PANAMA_DOCTOR_FIXTURE_BLUEBUBBLES:-installed}" == "installed" ]]; then
|
||||
printf 'BlueBubbles fixture-secret-token\n'
|
||||
exit 0
|
||||
fi
|
||||
exit 1
|
||||
EOF
|
||||
chmod +x "$bin_dir/flatpak"
|
||||
|
||||
cat >"$bin_dir/calendar-agenda" <<'EOF'
|
||||
#!/usr/bin/bash
|
||||
if [[ "${1:-}" != "probe" ]]; then
|
||||
exit 2
|
||||
fi
|
||||
case "${PANAMA_DOCTOR_FIXTURE_CALENDAR:-ready}" in
|
||||
ready) printf '{"eds":true,"sourceRegistry":true,"enabledSources":2,"event":"fixture clipboard body"}\n' ;;
|
||||
malformed) printf 'calendar AA:BB:CC:DD:EE:FF\n' ;;
|
||||
timeout) /usr/bin/sleep 2; printf '{"enabledSources":2}\n' ;;
|
||||
*) printf '{"eds":true,"sourceRegistry":true,"enabledSources":0}\n' ;;
|
||||
esac
|
||||
EOF
|
||||
chmod +x "$bin_dir/calendar-agenda"
|
||||
|
||||
cat >"$bin_dir/panama-brightness" <<'EOF'
|
||||
#!/usr/bin/bash
|
||||
case "${PANAMA_DOCTOR_FIXTURE_BRIGHTNESS:-ready}" in
|
||||
ready) printf '{"displays":[{"connector":"AA:BB:CC:DD:EE:FF"}],"error":""}\n' ;;
|
||||
denied) printf '{"displays":[],"error":"fixture-secret-token"}\n' ;;
|
||||
malformed) printf 'fixture clipboard body\n' ;;
|
||||
esac
|
||||
EOF
|
||||
chmod +x "$bin_dir/panama-brightness"
|
||||
|
||||
mkdir -p "$config_home/autostart"
|
||||
touch "$config_home/autostart/nextcloud.desktop"
|
||||
for name in hypr quickshell uwsm vicinae; do
|
||||
ln -s "$repo_dir/config/dot/$name" "$config_home/$name"
|
||||
done
|
||||
ln -s "$repo_dir/config/local/share/vicinae/scripts" "$data_home/vicinae/scripts"
|
||||
|
||||
run_doctor() {
|
||||
HOME="$home" \
|
||||
PATH="$bin_dir" \
|
||||
XDG_CURRENT_DESKTOP=Hyprland \
|
||||
PANAMA_HOME_ASSISTANT_URL='https://fixture.invalid' \
|
||||
PANAMA_HOME_ASSISTANT_TOKEN='fixture-secret-token' \
|
||||
PANAMA_DOCTOR_ROOT="$repo_dir" \
|
||||
PANAMA_DOCTOR_HOME="$home" \
|
||||
PANAMA_DOCTOR_CONFIG_HOME="$config_home" \
|
||||
PANAMA_DOCTOR_STATE_HOME="$state_home" \
|
||||
PANAMA_DOCTOR_RUNTIME_DIR="$runtime_dir" \
|
||||
PANAMA_DOCTOR_PATH="$bin_dir" \
|
||||
PANAMA_DOCTOR_TIMEOUT="${PANAMA_DOCTOR_TIMEOUT:-0.2}" \
|
||||
/usr/bin/python3 "$doctor" "$@"
|
||||
}
|
||||
|
||||
expected_order=$'desktop.hyprland\ndesktop.quickshell\ndesktop.notifications\ndesktop.portals\ndesktop.hyprpaper\ndesktop.hypridle\ndesktop.vicinae\ninput.pipewire\ninput.clipboard\ninput.wallpaper\ninput.capture\ninput.ocr\ninput.brightness\nintegration.nextcloud\nintegration.rustdesk\nintegration.kdeconnect\nintegration.bluebubbles\nintegration.home-assistant\nintegration.calendar\npanama.runtime-links\npanama.vicinae-commands\npanama.selected-terminal\npanama.selected-launcher\npanama.processes\npanama.caffeine'
|
||||
|
||||
assert_schema_and_redaction() {
|
||||
local snapshot="$1"
|
||||
jq -e '.schemaVersion == 1
|
||||
and (.generatedAt | type == "string")
|
||||
and (.summary.status | IN("healthy", "warning", "error"))
|
||||
and (.context.session | IN("hyprland", "other"))
|
||||
and (.context.versions | type == "array")
|
||||
and ([.checks[].id] | length == 25)
|
||||
and ([.checks[].id] | unique | length == 25)
|
||||
and ([.checks[].status] | all(IN("ok", "warning", "error", "unconfigured")))' \
|
||||
>/dev/null <<<"$snapshot" || fail "invalid schema: $snapshot"
|
||||
[[ "$(jq -r '.checks[].id' <<<"$snapshot")" == "$expected_order" ]] \
|
||||
|| fail "checks are not in the authored order"
|
||||
! grep -Fq 'fixture-secret-token' <<<"$snapshot" \
|
||||
|| fail 'report exposed a fixture secret'
|
||||
! grep -Fq 'fixture clipboard body' <<<"$snapshot" \
|
||||
|| fail 'report exposed clipboard or calendar content'
|
||||
! grep -Fq 'AA:BB:CC:DD:EE:FF' <<<"$snapshot" \
|
||||
|| fail 'report exposed a device address'
|
||||
}
|
||||
|
||||
check_status() {
|
||||
local snapshot="$1" id="$2" expected="$3"
|
||||
[[ "$(jq -r --arg id "$id" '.checks[] | select(.id == $id) | .status' <<<"$snapshot")" == "$expected" ]] \
|
||||
|| fail "$id did not report $expected: $snapshot"
|
||||
}
|
||||
|
||||
snapshot="$(run_doctor --json)"
|
||||
assert_schema_and_redaction "$snapshot"
|
||||
|
||||
# A healthy systemd-backed service stays healthy.
|
||||
check_status "$snapshot" desktop.hyprpaper ok
|
||||
|
||||
# Arbitrary parent environment values are not propagated into probes.
|
||||
sealed_environment="$(PANAMA_DOCTOR_FIXTURE_PROBE_SECRET=fixture-secret-token run_doctor --json)"
|
||||
assert_schema_and_redaction "$sealed_environment"
|
||||
check_status "$sealed_environment" desktop.hyprpaper ok
|
||||
|
||||
# An OS-level launch failure is contained as a check result, never a failed
|
||||
# doctor invocation or a partial snapshot.
|
||||
chmod 0644 "$bin_dir/systemctl"
|
||||
if ! launch_failure="$(run_doctor --json)"; then
|
||||
chmod +x "$bin_dir/systemctl"
|
||||
fail 'launch failure prevented the doctor from emitting JSON'
|
||||
fi
|
||||
chmod +x "$bin_dir/systemctl"
|
||||
assert_schema_and_redaction "$launch_failure"
|
||||
check_status "$launch_failure" desktop.hyprpaper error
|
||||
|
||||
# A missing required executable is an error rather than a crash.
|
||||
mv "$bin_dir/qs" "$bin_dir/qs.off"
|
||||
missing_qs="$(run_doctor --json)"
|
||||
check_status "$missing_qs" desktop.quickshell error
|
||||
mv "$bin_dir/qs.off" "$bin_dir/qs"
|
||||
|
||||
# Optional integrations stay neutral until the user configures them.
|
||||
rm "$config_home/autostart/nextcloud.desktop"
|
||||
unconfigured_nextcloud="$(run_doctor --json)"
|
||||
check_status "$unconfigured_nextcloud" integration.nextcloud unconfigured
|
||||
touch "$config_home/autostart/nextcloud.desktop"
|
||||
|
||||
# A configured integration that stopped is actionable with an authored label,
|
||||
# never an application name or command derived from probe output.
|
||||
stopped_nextcloud="$(PANAMA_DOCTOR_FIXTURE_STOPPED=nextcloud.service run_doctor --json)"
|
||||
check_status "$stopped_nextcloud" integration.nextcloud warning
|
||||
jq -e '.checks[] | select(.id == "integration.nextcloud")
|
||||
| .action == {kind:"open", label:"Open Nextcloud", confirm:false}' \
|
||||
>/dev/null <<<"$stopped_nextcloud" || fail 'Nextcloud action was not authored'
|
||||
|
||||
# DDC errors are classified without retaining connectors or bus addresses.
|
||||
denied_brightness="$(PANAMA_DOCTOR_FIXTURE_BRIGHTNESS=denied run_doctor --json)"
|
||||
check_status "$denied_brightness" input.brightness warning
|
||||
jq -e '.checks[] | select(.id == "input.brightness")
|
||||
| .action == {kind:"instructions", label:"View setup instructions", confirm:false, target:"ddc-permissions"}' \
|
||||
>/dev/null <<<"$denied_brightness" || fail 'DDC instructions were not authored'
|
||||
|
||||
# A bounded probe timeout becomes a result, never a helper failure.
|
||||
timed_calendar="$(PANAMA_DOCTOR_FIXTURE_CALENDAR=timeout PANAMA_DOCTOR_TIMEOUT=0.05 run_doctor --json)"
|
||||
check_status "$timed_calendar" integration.calendar warning
|
||||
jq -e '.checks[] | select(.id == "integration.calendar")
|
||||
| .action == {kind:"open", label:"Open Date & Time", confirm:false, target:"datetime"}' \
|
||||
>/dev/null <<<"$timed_calendar" || fail 'calendar action was not authored'
|
||||
|
||||
# Exact Panama/Caffeine inhibitor rows detect duplicates without exposing PIDs.
|
||||
duplicated_caffeine="$(PANAMA_DOCTOR_FIXTURE_CAFFEINE=duplicate run_doctor --json)"
|
||||
check_status "$duplicated_caffeine" panama.caffeine warning
|
||||
jq -e '.checks[] | select(.id == "panama.caffeine")
|
||||
| .action == {kind:"repair", label:"Release duplicate inhibitors", confirm:false}' \
|
||||
>/dev/null <<<"$duplicated_caffeine" || fail 'Caffeine repair action was not authored'
|
||||
! jq -r '.checks[] | select(.id == "panama.caffeine") | .detail' <<<"$duplicated_caffeine" | grep -Eq '[0-9]{3,}' \
|
||||
|| fail 'Caffeine detail exposed inhibitor PIDs'
|
||||
|
||||
# Process counts use only exact authored names and never expose command lines or PIDs.
|
||||
duplicated_processes="$(PANAMA_DOCTOR_FIXTURE_PROCESSES=quickshell:duplicate run_doctor --json)"
|
||||
check_status "$duplicated_processes" panama.processes warning
|
||||
! jq -r '.checks[] | select(.id == "panama.processes") | .detail' <<<"$duplicated_processes" | grep -Eq '[0-9]{3,}' \
|
||||
|| fail 'process detail exposed a PID'
|
||||
|
||||
# Invalid output for a non-Quickshell authored process is not a normal zero
|
||||
# count that can be hidden by the running Quickshell process.
|
||||
malformed_processes="$(PANAMA_DOCTOR_FIXTURE_PROCESSES=hyprpaper:malformed run_doctor --json)"
|
||||
check_status "$malformed_processes" panama.processes warning
|
||||
|
||||
# Panama/Caffeine-shaped rows that do not satisfy the fixed inhibitor schema
|
||||
# are unavailable rather than reported as a healthy no-inhibitor state.
|
||||
malformed_caffeine="$(PANAMA_DOCTOR_FIXTURE_CAFFEINE=malformed run_doctor --json)"
|
||||
check_status "$malformed_caffeine" panama.caffeine warning
|
||||
|
||||
# A decoding error raised inside a concurrent probe is converted to a complete
|
||||
# snapshot rather than escaping from Future.result().
|
||||
if ! invalid_probe="$(PANAMA_DOCTOR_FIXTURE_BUS=invalid-utf8 run_doctor --json)"; then
|
||||
fail 'unexpected probe exception prevented the doctor from emitting JSON'
|
||||
fi
|
||||
assert_schema_and_redaction "$invalid_probe"
|
||||
check_status "$invalid_probe" desktop.portals warning
|
||||
|
||||
# Configured Home Assistant failures route to the exact authored Settings page.
|
||||
rm "$config_home/quickshell"
|
||||
mkdir -p "$config_home/quickshell/scripts"
|
||||
home_assistant_failure="$(run_doctor --json)"
|
||||
check_status "$home_assistant_failure" integration.home-assistant warning
|
||||
jq -e '.checks[] | select(.id == "integration.home-assistant")
|
||||
| .action == {kind:"open", label:"Open Home settings", confirm:false, target:"home-phone"}' \
|
||||
>/dev/null <<<"$home_assistant_failure" || fail 'Home Assistant action was not routed to home-phone'
|
||||
|
||||
# Invalid probe text is contained in its own check and never copied to JSON.
|
||||
malformed_calendar="$(PANAMA_DOCTOR_FIXTURE_CALENDAR=malformed run_doctor --json)"
|
||||
check_status "$malformed_calendar" integration.calendar warning
|
||||
assert_schema_and_redaction "$malformed_calendar"
|
||||
|
||||
summary="$(run_doctor --summary)"
|
||||
[[ "$summary" =~ ^Panama\ system\ health:\ (healthy|warning|error)\ \([0-9]+\ ok,\ [0-9]+\ warnings,\ [0-9]+\ errors,\ [0-9]+\ unconfigured\)$ ]] \
|
||||
|| fail "summary is not concise: $summary"
|
||||
|
||||
# Repairs run against a second, disposable Panama root. Every process boundary
|
||||
# records its argv, and every filesystem assertion is confined to this fixture.
|
||||
repair_root="$fixture/repair-root"
|
||||
repair_log="$runtime_dir/repair.log"
|
||||
mkdir -p "$repair_root/config/dot" "$repair_root/setup/scripts"
|
||||
for name in hypr quickshell uwsm vicinae; do
|
||||
mkdir -p "$repair_root/config/dot/$name"
|
||||
done
|
||||
|
||||
mv "$bin_dir/systemctl" "$bin_dir/systemctl-probe"
|
||||
cat >"$bin_dir/systemctl" <<'EOF'
|
||||
#!/usr/bin/bash
|
||||
set -euo pipefail
|
||||
if [[ "${1:-}" == "--user" && "${2:-}" == "restart" ]]; then
|
||||
printf 'systemctl' >>"$XDG_RUNTIME_DIR/repair.log"
|
||||
printf '|%s' "$@" >>"$XDG_RUNTIME_DIR/repair.log"
|
||||
printf '\n' >>"$XDG_RUNTIME_DIR/repair.log"
|
||||
[[ ! -e "$XDG_RUNTIME_DIR/fail-repair" ]] || exit 5
|
||||
exit 0
|
||||
fi
|
||||
exec "${0%/*}/systemctl-probe" "$@"
|
||||
EOF
|
||||
|
||||
cat >"$bin_dir/panama-action" <<'EOF'
|
||||
#!/usr/bin/bash
|
||||
set -euo pipefail
|
||||
printf 'panama-action' >>"$XDG_RUNTIME_DIR/repair.log"
|
||||
printf '|%s' "$@" >>"$XDG_RUNTIME_DIR/repair.log"
|
||||
printf '\n' >>"$XDG_RUNTIME_DIR/repair.log"
|
||||
EOF
|
||||
|
||||
cat >"$bin_dir/kill" <<'EOF'
|
||||
#!/usr/bin/bash
|
||||
set -euo pipefail
|
||||
printf 'kill' >>"$XDG_RUNTIME_DIR/repair.log"
|
||||
printf '|%s' "$@" >>"$XDG_RUNTIME_DIR/repair.log"
|
||||
printf '\n' >>"$XDG_RUNTIME_DIR/repair.log"
|
||||
EOF
|
||||
|
||||
cat >"$bin_dir/systemd-inhibit" <<'EOF'
|
||||
#!/usr/bin/bash
|
||||
set -euo pipefail
|
||||
printf 'systemd-inhibit' >>"$XDG_RUNTIME_DIR/repair.log"
|
||||
printf '|%s' "$@" >>"$XDG_RUNTIME_DIR/repair.log"
|
||||
printf '\n' >>"$XDG_RUNTIME_DIR/repair.log"
|
||||
uid="$(/usr/bin/id -u)"
|
||||
printf 'Panama %s fixture-user 4101 systemd-inhibit sleep:idle Caffeine block\n' "$uid"
|
||||
printf 'Panama %s fixture-user 4102 systemd-inhibit sleep:idle Caffeine block\n' "$uid"
|
||||
printf 'Other %s fixture-user 4999 systemd-inhibit sleep:idle Caffeine block\n' "$uid"
|
||||
printf 'Panama 99999 fixture-user 4998 systemd-inhibit sleep:idle Caffeine block\n'
|
||||
printf 'Panama %s fixture-user 4997 systemd-inhibit sleep:idle Other block\n' "$uid"
|
||||
printf 'Panama %s fixture-user 4996 systemd-inhibit sleep:idle Caffeine delay\n' "$uid"
|
||||
EOF
|
||||
|
||||
cat >"$repair_root/setup/scripts/link-vicinae-scripts" <<'EOF'
|
||||
#!/usr/bin/bash
|
||||
set -euo pipefail
|
||||
printf 'link-vicinae-scripts|%s' "$0" >>"$XDG_RUNTIME_DIR/repair.log"
|
||||
if (( $# > 0 )); then
|
||||
printf '|%s' "$@" >>"$XDG_RUNTIME_DIR/repair.log"
|
||||
fi
|
||||
printf '\n' >>"$XDG_RUNTIME_DIR/repair.log"
|
||||
EOF
|
||||
chmod +x "$bin_dir/systemctl" "$bin_dir/panama-action" "$bin_dir/kill" \
|
||||
"$bin_dir/systemd-inhibit" "$repair_root/setup/scripts/link-vicinae-scripts"
|
||||
|
||||
run_repair() {
|
||||
HOME="$home" \
|
||||
PATH="$bin_dir" \
|
||||
XDG_CURRENT_DESKTOP=Hyprland \
|
||||
PANAMA_DOCTOR_ROOT="$repair_root" \
|
||||
PANAMA_DOCTOR_HOME="$home" \
|
||||
PANAMA_DOCTOR_CONFIG_HOME="$config_home" \
|
||||
PANAMA_DOCTOR_STATE_HOME="$state_home" \
|
||||
PANAMA_DOCTOR_RUNTIME_DIR="$runtime_dir" \
|
||||
PANAMA_DOCTOR_PATH="$bin_dir" \
|
||||
PANAMA_DOCTOR_TIMEOUT=0.2 \
|
||||
/usr/bin/python3 "$doctor" "$@"
|
||||
}
|
||||
|
||||
repair_output=""
|
||||
repair_status=0
|
||||
invoke_repair() {
|
||||
set +e
|
||||
repair_output="$(run_repair --repair "$1" --json)"
|
||||
repair_status=$?
|
||||
set -e
|
||||
}
|
||||
|
||||
assert_repair_result() {
|
||||
local id="$1" accepted="$2" exit_code="$3"
|
||||
jq -e --arg id "$id" --argjson accepted "$accepted" --argjson exitCode "$exit_code" '
|
||||
(keys | sort) == ["accepted", "checkId", "exitCode", "message", "schemaVersion"]
|
||||
and .schemaVersion == 1
|
||||
and .checkId == $id
|
||||
and .accepted == $accepted
|
||||
and .exitCode == $exitCode
|
||||
and (.message | type == "string" and length > 0)
|
||||
' >/dev/null <<<"$repair_output" || fail "invalid repair result for $id: $repair_output"
|
||||
}
|
||||
|
||||
for repair_case in \
|
||||
'desktop.hyprpaper|systemctl|--user|restart|hyprpaper.service' \
|
||||
'desktop.hypridle|systemctl|--user|restart|hypridle.service' \
|
||||
'desktop.vicinae|systemctl|--user|restart|vicinae.service' \
|
||||
'desktop.quickshell|panama-action|restart-shell'; do
|
||||
IFS='|' read -r repair_id executable arg1 arg2 arg3 <<<"$repair_case"
|
||||
: >"$repair_log"
|
||||
invoke_repair "$repair_id"
|
||||
[[ "$repair_status" == 0 ]] || fail "$repair_id returned $repair_status"
|
||||
assert_repair_result "$repair_id" true 0
|
||||
expected="$executable|$arg1"
|
||||
[[ -z "$arg2" ]] || expected+="|$arg2"
|
||||
[[ -z "$arg3" ]] || expected+="|$arg3"
|
||||
[[ "$(<"$repair_log")" == "$expected" ]] \
|
||||
|| fail "$repair_id argv was not exact: $(<"$repair_log")"
|
||||
done
|
||||
|
||||
# Known process failures still return complete JSON and preserve the command's
|
||||
# exit status for the QML state machine.
|
||||
touch "$runtime_dir/fail-repair"
|
||||
: >"$repair_log"
|
||||
invoke_repair desktop.vicinae
|
||||
rm "$runtime_dir/fail-repair"
|
||||
[[ "$repair_status" == 5 ]] || fail "failed repair returned $repair_status instead of 5"
|
||||
assert_repair_result desktop.vicinae true 5
|
||||
[[ "$(<"$repair_log")" == 'systemctl|--user|restart|vicinae.service' ]] \
|
||||
|| fail 'failed repair changed the authored argv'
|
||||
|
||||
# The Vicinae repair executes only the authored setup helper with no arguments.
|
||||
: >"$repair_log"
|
||||
invoke_repair panama.vicinae-commands
|
||||
[[ "$repair_status" == 0 ]] || fail "Vicinae command repair returned $repair_status"
|
||||
assert_repair_result panama.vicinae-commands true 0
|
||||
[[ "$(<"$repair_log")" == "link-vicinae-scripts|$repair_root/setup/scripts/link-vicinae-scripts" ]] \
|
||||
|| fail "Vicinae command repair argv was not exact: $(<"$repair_log")"
|
||||
|
||||
# Runtime-link repair may replace only the four authored symlink names. Broken
|
||||
# or absent links are recreated toward authored tracked destinations; regular
|
||||
# files and directories remain untouched and make the result incomplete.
|
||||
for name in hypr quickshell uwsm vicinae; do
|
||||
path="$config_home/$name"
|
||||
if [[ -e "$path" || -L "$path" ]]; then
|
||||
mv "$path" "$fixture/pre-repair-$name"
|
||||
fi
|
||||
done
|
||||
ln -s "$fixture/missing-hypr" "$config_home/hypr"
|
||||
ln -s "$fixture/missing-quickshell" "$config_home/quickshell"
|
||||
printf 'user-owned file\n' >"$config_home/uwsm"
|
||||
mkdir "$config_home/vicinae"
|
||||
ln -s "$fixture/untouched" "$config_home/not-panama"
|
||||
: >"$repair_log"
|
||||
invoke_repair panama.runtime-links
|
||||
[[ "$repair_status" == 1 ]] || fail "blocked runtime-link repair returned $repair_status"
|
||||
assert_repair_result panama.runtime-links true 1
|
||||
[[ -L "$config_home/hypr" && "$(readlink "$config_home/hypr")" == "$repair_root/config/dot/hypr" ]] \
|
||||
|| fail 'hypr link was not recreated toward its authored destination'
|
||||
[[ -L "$config_home/quickshell" && "$(readlink "$config_home/quickshell")" == "$repair_root/config/dot/quickshell" ]] \
|
||||
|| fail 'quickshell link was not recreated toward its authored destination'
|
||||
[[ -f "$config_home/uwsm" && "$(<"$config_home/uwsm")" == 'user-owned file' ]] \
|
||||
|| fail 'runtime-link repair replaced a regular file'
|
||||
[[ -d "$config_home/vicinae" && ! -L "$config_home/vicinae" ]] \
|
||||
|| fail 'runtime-link repair replaced a user-owned directory'
|
||||
[[ -L "$config_home/not-panama" && "$(readlink "$config_home/not-panama")" == "$fixture/untouched" ]] \
|
||||
|| fail 'runtime-link repair touched an unauthored link name'
|
||||
[[ ! -s "$repair_log" ]] || fail 'runtime-link repair launched a process'
|
||||
|
||||
# Caffeine repair parses exact authored metadata, keeps the first valid lock,
|
||||
# and releases only later exact matches.
|
||||
: >"$repair_log"
|
||||
invoke_repair panama.caffeine
|
||||
[[ "$repair_status" == 0 ]] || fail "Caffeine repair returned $repair_status"
|
||||
assert_repair_result panama.caffeine true 0
|
||||
expected_caffeine=$'systemd-inhibit|--list|--no-pager|--no-legend\nkill|--|4102'
|
||||
[[ "$(<"$repair_log")" == "$expected_caffeine" ]] \
|
||||
|| fail "Caffeine repair did not preserve/filter exact inhibitors: $(<"$repair_log")"
|
||||
|
||||
# Rejected IDs are complete JSON, exit 2, and cause neither a process launch
|
||||
# nor a filesystem mutation.
|
||||
fixture_state() {
|
||||
find "$config_home" -mindepth 1 -printf '%P|%y|%l\n' | sort | sha256sum | awk '{print $1}'
|
||||
}
|
||||
for rejected_id in unknown.check integration.home-assistant input.brightness \
|
||||
desktop.notifications ../../escape 'desktop.vicinae;touch injected'; do
|
||||
: >"$repair_log"
|
||||
before_state="$(fixture_state)"
|
||||
invoke_repair "$rejected_id"
|
||||
[[ "$repair_status" == 2 ]] || fail "$rejected_id returned $repair_status instead of 2"
|
||||
assert_repair_result "$rejected_id" false 2
|
||||
[[ ! -s "$repair_log" ]] || fail "$rejected_id launched a process"
|
||||
[[ "$(fixture_state)" == "$before_state" ]] || fail "$rejected_id mutated the filesystem"
|
||||
done
|
||||
|
||||
printf 'panama doctor contract: PASS\n'
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user