Panama absorbed Users, Sharing, Printers and Online Accounts one page at a time. Each time, the row pointing at GNOME's equivalent stayed exactly where it was -- so an app whose stated purpose is to make GNOME Settings unnecessary shipped four doors back to it, two of them inside a card headed "these areas remain owned by Fedora and GNOME's mature system panels". Nothing failed. Every row worked as written. They were simply no longer true, and no test could notice, because none of them knew what Panama had come to own in the meantime. gnome-handoff-contract reads the sidebar for the pages that exist and the pages for the panels they hand off, and fails on any overlap -- derived from both sides rather than a hand-kept list, so absorbing the next page cannot leave a stale door behind. Adding an online account is allow-listed with its reason: it genuinely requires GOA's own dialog. health-ui-contract asserted those handoffs were present, which is how they survived. The assertion is inverted rather than deleted, so reintroducing one fails loudly. The Home Assistant "Light entities" box is gone. It was a multi-line list of comma-separated Zigbee entity IDs, and the light catalog does not come from it -- the helper discovers that live. It is a one-time migration seed for the Control Center selection, so saving now passes the stored value back untouched: setting a URL or a token cannot disturb it. Deleting the control naively would have written an empty list over it. Sharing showed two "Port" rows for RDP, same label and value, one read-only and one editable, separated by a switch. The read-only leftover is gone. The SSH port stays read-only because sshd's port is not ours to write. About reported "488G free of 1.9T" where Storage said "523 GB free of 2.0 TB" -- the same drive, binary against decimal. About uses decimal now, matching how drives are sold. Memory and swap stay in GiB, which is how RAM is sold. Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
431 lines
20 KiB
Bash
Executable File
431 lines
20 KiB
Bash
Executable File
#!/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'
|
|
rg -Fq 'SystemSettings.openGnomePanel("network")' "$settings_dir/HealthPage.qml" \
|
|
|| fail 'Fedora ownership boundary does not open GNOME Settings'
|
|
# Users and Sharing are Panama pages now. A handoff here would send someone to
|
|
# GNOME for a panel this app owns, which is the opposite of the point -- so the
|
|
# assertion is inverted rather than deleted.
|
|
rg -Fq 'SystemSettings.openGnomePanel("system", "users")' "$settings_dir/HealthPage.qml" \
|
|
&& fail 'System Health still hands Users to GNOME, but Panama owns that page'
|
|
rg -Fq 'SystemSettings.openGnomePanel("sharing")' "$settings_dir/HealthPage.qml" \
|
|
&& fail 'System Health still hands Sharing to GNOME, but Panama owns that page'
|
|
rg -Fq 'SystemSettings.openGnomePanel("color")' "$settings_dir/HealthPage.qml" \
|
|
|| fail 'Fedora ownership boundary lost the Color profiles handoff'
|
|
rg -Fq 'SystemSettings.openGnomePanel("wellbeing")' "$settings_dir/HealthPage.qml" \
|
|
|| fail 'Fedora ownership boundary lost the Digital wellbeing handoff'
|
|
# Exact authored handoffs are asserted above. Also prove every panel named by
|
|
# this boundary is accepted by SystemSettings, so a typo cannot ship a dead
|
|
# button even if its copy still looks correct.
|
|
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 .fedoraHandoffs == [
|
|
{id:"color", label:"Color profiles", action:"Open color"},
|
|
{id:"wellbeing", label:"Digital wellbeing", action:"Open wellbeing"}
|
|
]
|
|
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'
|