Home is now Overview | My Home | Phone. Overview leads with quick-action tiles (focus, Do Not Disturb, health, snapshots, storage), keeps the findings card — updates fold in, the reclaim-space prompt is gone on purpose — and adds glance cards, the next calendar event, and weather. My Home groups every light by Home Assistant area: the helper gained an `areas` command (one REST template render, no websocket), and the rooms degrade to a flat list on setups without areas. The favorites editor and connection card moved intact. Phone gains a vitals strip — battery and cell signal read from KDE Connect's plugin D-Bus objects, where absence is data, not an error — beside ring, clipboard, send-a-file, and the BlueBubbles handoff. The retired home-phone id resolves to my-home forever via a new alias map in SettingsRoutes (with a hasOwnProperty guard so prototype names cannot leak into settingsPage). Storage no longer claims 0 B free — the old page read a field the disks helper never emitted. Contracts updated alongside; per the new workflow, the full suite runs once at the end of the redesign (see the test backlog note). Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
251 lines
12 KiB
Bash
Executable File
251 lines
12 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
set -euo pipefail
|
|
|
|
project_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
source_config_path="$project_root/config/dot/quickshell"
|
|
quicksettings_path="$source_config_path/modules/quicksettings"
|
|
state_home="$(mktemp -d /tmp/panama-control-center-ui.XXXXXX)"
|
|
config_path="$state_home/quickshell"
|
|
helper_log="$state_home/home-helper.log"
|
|
shell_log="$state_home/quickshell.log"
|
|
|
|
fail() {
|
|
printf 'Control Center contract: %s\n' "$1" >&2
|
|
exit 1
|
|
}
|
|
|
|
qs_for_test() {
|
|
QS_CONFIG_PATH="$config_path" XDG_STATE_HOME="$state_home" \
|
|
QS_DISABLE_CRASH_HANDLER=1 \
|
|
PANAMA_HOME_HELPER_LOG="$helper_log" \
|
|
qs -p "$config_path" "$@"
|
|
}
|
|
|
|
stop_test_shell() {
|
|
qs_for_test kill >/dev/null 2>&1 || true
|
|
for _ in $(seq 1 80); do
|
|
if ! qs_for_test list 2>/dev/null | rg '^Instance ' >/dev/null \
|
|
&& ! qs_for_test ipc show >/dev/null 2>&1; then
|
|
return 0
|
|
fi
|
|
sleep 0.1
|
|
done
|
|
return 1
|
|
}
|
|
|
|
cleanup() {
|
|
qs_for_test ipc call quicksettings close >/dev/null 2>&1 || true
|
|
qs_for_test ipc call kdeconnect reset >/dev/null 2>&1 || true
|
|
qs_for_test ipc call home-assistant reset >/dev/null 2>&1 || true
|
|
if stop_test_shell; then
|
|
rm -rf "$state_home"
|
|
else
|
|
printf 'Control Center contract: branch shell did not stop; retained %s\n' \
|
|
"$state_home" >&2
|
|
fi
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
start_test_shell() {
|
|
stop_test_shell || fail 'pre-existing branch shell did not stop cleanly'
|
|
for _attempt in 1 2; do
|
|
qs_for_test --daemonize >"$shell_log" 2>&1
|
|
for _ in $(seq 1 80); do
|
|
if qs_for_test ipc show 2>/dev/null | rg '^target quicksettings$' >/dev/null; then
|
|
return
|
|
fi
|
|
sleep 0.1
|
|
done
|
|
stop_test_shell || fail 'failed branch-shell attempt did not stop cleanly'
|
|
done
|
|
sed -n '1,200p' "$shell_log" >&2
|
|
fail 'isolated branch shell did not start'
|
|
}
|
|
|
|
rg -Fq 'readonly property int controlCenterWidth: 430' \
|
|
"$source_config_path/config/Theme.qml" \
|
|
|| fail 'approved Control Center width is missing'
|
|
|
|
# The margin is the GAP alone. This used to assert the bar height plus the gap,
|
|
# which is what the code said and what made the panel open 38 pixels below a bar
|
|
# it was written to sit 2 pixels under: an exclusiveZone of 0 already places the
|
|
# surface below the bar's reserved space, so naming the bar height again counted
|
|
# it twice. The contract agreed with the code and so the bug was invisible to
|
|
# both. layer-margin-contract now holds that rule for every surface.
|
|
# Asserted as a RULE rather than a literal, and derived from two files so that
|
|
# drift in either direction fails. The Control Center used to carry its own gap
|
|
# constant set to 2 while every other popover used barGap * 2, leaving the widest
|
|
# surface in the shell hanging ten pixels higher than the date menu beside it.
|
|
# Pinning the new number here would repeat the mistake this file already records
|
|
# below: a contract that agrees with the code cannot see the code being wrong.
|
|
control_center_gap="$(rg -o 'margins\.top: (.+)$' -r '$1' "$quicksettings_path/QuickSettings.qml" | head -1)"
|
|
date_menu_gap="$(rg -o 'margins\.top: (.+)$' -r '$1' "$source_config_path/modules/datemenu/DateMenu.qml" | head -1)"
|
|
[[ -n "$control_center_gap" ]] || fail 'Control Center sets no top margin at all'
|
|
[[ "$control_center_gap" == "$date_menu_gap" ]] \
|
|
|| fail "Control Center hangs at '$control_center_gap' while the date menu uses '$date_menu_gap'"
|
|
|
|
rg -Fq 'implicitWidth: Theme.controlCenterWidth' \
|
|
"$quicksettings_path/QuickSettings.qml" \
|
|
|| fail 'Control Center window does not use its geometry token'
|
|
rg -Fq 'HomeControls' \
|
|
"$quicksettings_path/QuickSettingsPanel.qml" \
|
|
|| fail 'Home controls are not mounted'
|
|
rg -Fq 'PhoneControls' \
|
|
"$quicksettings_path/QuickSettingsPanel.qml" \
|
|
|| fail 'Phone controls are not mounted'
|
|
rg -Fq 'visible: KdeConnect.phoneReachable' \
|
|
"$source_config_path/modules/bar/StatusCluster.qml" \
|
|
|| fail 'reachable phone state is absent from the bar'
|
|
|
|
for component in ControlSectionHeader HomeBrightnessSlider HomeControls HomeTile PhoneActions PhoneControls RecentExchange; do
|
|
[[ -f "$quicksettings_path/$component.qml" ]] \
|
|
|| fail "$component is missing"
|
|
done
|
|
|
|
[[ "$(rg -c 'id: "(share|clipboard|ring|messages)"' "$quicksettings_path/PhoneActions.qml")" -eq 4 ]] \
|
|
|| fail 'Phone controls do not expose four stable action models'
|
|
rg -Fq 'columns: 4' "$quicksettings_path/PhoneControls.qml" \
|
|
|| fail 'Phone actions are not arranged in four equal columns'
|
|
if rg -Fq '.filter(' "$quicksettings_path/PhoneActions.qml"; then
|
|
fail 'Phone action columns change when a KDE capability is unavailable'
|
|
fi
|
|
rg -Fq 'SystemSettings.bluebubblesAvailable' "$quicksettings_path/PhoneControls.qml" \
|
|
|| fail 'Messages action is not independently enabled by BlueBubbles'
|
|
rg -Fq 'KdeConnect.phoneReachable' "$quicksettings_path/PhoneControls.qml" \
|
|
|| fail 'KDE action reachability behavior is missing'
|
|
|
|
[[ -f "$quicksettings_path/qmldir" ]] \
|
|
|| fail 'quick-settings module manifest is missing'
|
|
rg -Fq 'HomeBrightnessSlider 1.0 HomeBrightnessSlider.qml' \
|
|
"$quicksettings_path/qmldir" \
|
|
|| fail 'brightness slider is not registered in the quick-settings module'
|
|
rg -Fq 'PhoneActions 1.0 PhoneActions.qml' \
|
|
"$quicksettings_path/qmldir" \
|
|
|| fail 'Phone actions model is not registered in the quick-settings module'
|
|
|
|
[[ "$(rg -c '^[[:space:]]*columns: 2$' "$quicksettings_path/HomeControls.qml")" -ge 2 ]] \
|
|
|| fail 'resting and expanded Home shelves are not both two-column grids'
|
|
rg -Fq 'model: HomeAssistant.visibleEntities' "$quicksettings_path/HomeControls.qml" \
|
|
|| fail 'resting Home shelf does not use the first four selected lights'
|
|
rg -Fq 'model: HomeAssistant.selectedEntities' "$quicksettings_path/HomeControls.qml" \
|
|
|| fail 'expanded Home shelf does not use every selected light'
|
|
rg -Fq 'HomeAssistant.pendingFor(' "$quicksettings_path/HomeControls.qml" \
|
|
|| fail 'Home tiles do not receive per-light pending brightness'
|
|
rg -Fq 'HomeAssistant.setBrightness(' "$quicksettings_path/HomeControls.qml" \
|
|
|| fail 'Home brightness commits are not wired to the service'
|
|
rg -Fq 'ShellState.openSettings("my-home")' "$quicksettings_path/HomeControls.qml" \
|
|
|| fail 'Manage in Settings does not open My Home'
|
|
|
|
rg -Fq 'signal previewChanged(int value)' "$quicksettings_path/HomeBrightnessSlider.qml" \
|
|
|| fail 'brightness slider has no preview contract'
|
|
rg -Fq 'signal committed(int value)' "$quicksettings_path/HomeBrightnessSlider.qml" \
|
|
|| fail 'brightness slider has no release-commit contract'
|
|
rg -Fq 'onReleased: event =>' "$quicksettings_path/HomeBrightnessSlider.qml" \
|
|
|| fail 'brightness slider does not route pointer release through its interaction state'
|
|
rg -Fq 'root.releasePointerInteraction();' "$quicksettings_path/HomeBrightnessSlider.qml" \
|
|
|| fail 'brightness slider release is not wired to one-shot commit semantics'
|
|
rg -Fq 'onCanceled: root.cancelPointerInteraction()' "$quicksettings_path/HomeBrightnessSlider.qml" \
|
|
|| fail 'brightness slider does not restore its external value when a Flickable steals the pointer'
|
|
if rg -Fq 'preventStealing: true' "$quicksettings_path/HomeBrightnessSlider.qml"; then
|
|
fail 'brightness slider blocks the expanded shelf from stealing vertical drags'
|
|
fi
|
|
rg -Fq 'onWheel:' "$quicksettings_path/HomeBrightnessSlider.qml" \
|
|
|| fail 'brightness slider has no wheel commit path'
|
|
rg -Fq 'onCommitted: value => root.brightnessRequested(value)' "$quicksettings_path/HomeTile.qml" \
|
|
|| fail 'Home tile does not forward the slider release commit'
|
|
rg -Fq 'accessibleName: root.entity.name + " brightness"' "$quicksettings_path/HomeTile.qml" \
|
|
|| fail 'Home tile does not give its dimmer an accessory-specific accessible name'
|
|
|
|
# Controls added after the original contract remain service-backed rather than
|
|
# becoming optimistic local toggles. Keep these checks in the static section so
|
|
# they can run without mapping the Control Center on a daily-driver desktop.
|
|
rg -Fq 'visible: PowerProfiles.available' "$quicksettings_path/QuickSettingsPanel.qml" \
|
|
|| fail 'power profile control does not follow daemon availability'
|
|
rg -Fq 'PowerProfiles.refresh();' "$quicksettings_path/QuickSettings.qml" \
|
|
|| fail 'opening Control Center does not refresh the external power profile'
|
|
rg -Fq 'expanded: root.expandedSection === "power"' "$quicksettings_path/QuickSettingsPanel.qml" \
|
|
|| fail 'power profile choices have no detail section'
|
|
rg -Fq 'PowerProfileList {' "$quicksettings_path/QuickSettingsPanel.qml" \
|
|
|| fail 'power profile choices are not mounted'
|
|
|
|
rg -Fq 'active: NightLight.active' "$quicksettings_path/QuickSettingsPanel.qml" \
|
|
|| fail 'Night Light control does not reflect service state'
|
|
rg -Fq 'onToggled: NightLight.toggle()' "$quicksettings_path/QuickSettingsPanel.qml" \
|
|
|| fail 'Night Light primary action is not wired'
|
|
rg -Fq 'onExpanded: NightLight.automatic = !NightLight.automatic' "$quicksettings_path/QuickSettingsPanel.qml" \
|
|
|| fail 'Night Light schedule action is not wired'
|
|
|
|
rg -Fq 'sublabel: ColorScheme.dark ? "Dark" : "Light"' "$quicksettings_path/QuickSettingsPanel.qml" \
|
|
|| fail 'colour scheme control does not expose its current state'
|
|
rg -Fq 'SystemSettings.commitPreference("colorScheme",' "$quicksettings_path/QuickSettingsPanel.qml" \
|
|
|| fail 'colour scheme control bypasses the preference commit path'
|
|
|
|
if [[ "${1:-}" == "--static-only" ]]; then
|
|
trap - EXIT
|
|
cleanup
|
|
printf 'Control Center contract (static): PASS\n'
|
|
exit 0
|
|
fi
|
|
|
|
cp -a "$source_config_path" "$config_path"
|
|
: >"$helper_log"
|
|
cat >"$config_path/scripts/panama-home-assistant" <<'EOF'
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
printf '%s\n' "$*" >>"$PANAMA_HOME_HELPER_LOG"
|
|
case "${1:-}" in
|
|
catalog)
|
|
printf '%s\n' '{"ok":false,"configured":false,"entities":[],"legacyEntityIds":[],"error":"test-helper"}'
|
|
;;
|
|
toggle|brightness)
|
|
printf '%s\n' '{"ok":false,"error":"contract-action-forbidden"}'
|
|
exit 73
|
|
;;
|
|
esac
|
|
EOF
|
|
chmod +x "$config_path/scripts/panama-home-assistant"
|
|
|
|
start_test_shell
|
|
|
|
qs_for_test ipc call home-assistant fixture ready >/dev/null
|
|
home_ready="$(qs_for_test ipc call home-assistant status)"
|
|
jq -e '.fixture == true and .visibleCount == 4 and .configuredCount == 7' \
|
|
<<<"$home_ready" >/dev/null \
|
|
|| fail 'Home fixture does not expose four resting and seven expanded accessories'
|
|
|
|
qs_for_test ipc call kdeconnect fixture reachable >/dev/null
|
|
qs_for_test ipc call quicksettings open >/dev/null
|
|
|
|
hyprctl layers | rg -q 'namespace: qs-popover-quicksettings' \
|
|
|| fail 'Control Center layer did not map'
|
|
jq -e '.open == true and .expandedSection == ""' \
|
|
<<<"$(qs_for_test ipc call quicksettings status)" >/dev/null \
|
|
|| fail 'Control Center did not open in its resting state'
|
|
|
|
qs_for_test ipc call quicksettings section home >/dev/null
|
|
jq -e '.open == true and .expandedSection == "home"' \
|
|
<<<"$(qs_for_test ipc call quicksettings status)" >/dev/null \
|
|
|| fail 'Home section did not expand'
|
|
|
|
qs_for_test ipc call quicksettings section phone >/dev/null
|
|
jq -e '.open == true and .expandedSection == "phone"' \
|
|
<<<"$(qs_for_test ipc call quicksettings status)" >/dev/null \
|
|
|| fail 'Phone did not replace Home as the expanded section'
|
|
|
|
qs_for_test ipc call quicksettings section phone >/dev/null
|
|
jq -e '.open == true and .expandedSection == ""' \
|
|
<<<"$(qs_for_test ipc call quicksettings status)" >/dev/null \
|
|
|| fail 'expanded Phone section did not collapse'
|
|
|
|
if rg '^(toggle|brightness)( |$)' "$helper_log" >&2; then
|
|
fail 'Control Center contract attempted a real Home action path'
|
|
fi
|
|
|
|
trap - EXIT
|
|
cleanup
|
|
[[ ! -e "$state_home" ]] \
|
|
|| fail 'temporary Control Center state was not removed after shell exit'
|
|
printf 'Control Center contract: PASS\n'
|