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
236 lines
9.3 KiB
Bash
Executable File
236 lines
9.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
# Phone is the continuity tab of the Home category -- the half of the old Home
|
|
# & Phone page that did not go to My Home. Three things carried over or arrived
|
|
# with it, and each has already been got wrong once:
|
|
#
|
|
# the vitals strip reads plugin data that only exists while a phone is paired
|
|
# and nearby. A missing battery or signal reading is the ordinary case, not an
|
|
# error, so it renders as an em-dash
|
|
#
|
|
# the Reach it buttons mirror Control Center exactly: nearby, not mid-transfer,
|
|
# and the plugin present. Dropping any one of those three offers an action
|
|
# that silently does nothing
|
|
#
|
|
# Messages is not a KDE Connect action. It needs BlueBubbles installed and
|
|
# nothing else, and it must stay that way -- coupling it to phone reachability
|
|
# is the regression phone-messages-contract was written for, and this page
|
|
# inherited the same row
|
|
#
|
|
# The live half boots an isolated shell with a fake Flatpak to prove the
|
|
# installed-state probe runs and that nothing here launches the app.
|
|
|
|
set -euo pipefail
|
|
|
|
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
settings_dir="$repo_dir/config/dot/quickshell/modules/settings"
|
|
phone_page="$settings_dir/PhonePage.qml"
|
|
settings_qmldir="$settings_dir/qmldir"
|
|
system_settings="$repo_dir/config/dot/quickshell/services/SystemSettings.qml"
|
|
|
|
fail() {
|
|
printf 'phone page contract: %s\n' "$1" >&2
|
|
exit 1
|
|
}
|
|
|
|
assert_contains() {
|
|
local needle="$1"
|
|
local file="$2"
|
|
rg -Fq "$needle" "$file" || fail "$file is missing: $needle"
|
|
}
|
|
|
|
[[ -f "$phone_page" ]] || fail 'PhonePage.qml is missing'
|
|
|
|
assert_contains 'SettingsPage {' "$phone_page"
|
|
assert_contains 'objectName: "phone-page"' "$phone_page"
|
|
assert_contains 'title: "Phone"' "$phone_page"
|
|
assert_contains 'PhonePage 1.0 PhonePage.qml' "$settings_qmldir"
|
|
if rg -q '^\s*Flickable \{' "$phone_page"; then
|
|
fail 'PhonePage.qml still owns a copied Flickable scaffold'
|
|
fi
|
|
|
|
# ── Vitals ───────────────────────────────────────────────────────────────────
|
|
# KdeConnect.phoneBattery and phoneSignal are null whenever the plugin objects
|
|
# are absent, which is most of the time on an unpaired or away phone.
|
|
|
|
vitals_block="$(sed -n '/id: vitals/,/── Reach it/p' "$phone_page")"
|
|
[[ -n "$vitals_block" ]] || fail 'PhonePage.qml has no vitals strip'
|
|
rg -Fq 'KdeConnect.phoneBattery' <<<"$vitals_block" \
|
|
|| fail 'the vitals strip does not read the phone battery'
|
|
rg -Fq 'KdeConnect.phoneSignal' <<<"$vitals_block" \
|
|
|| fail 'the vitals strip does not read the phone signal'
|
|
[[ "$(rg -Fc -- '"—"' <<<"$vitals_block")" -ge 2 ]] \
|
|
|| fail 'a null battery or signal does not render as an em-dash'
|
|
|
|
# ── Reach it ─────────────────────────────────────────────────────────────────
|
|
|
|
assert_contains 'readonly property bool actionsReady: KdeConnect.phoneReachable && !KdeConnect.transferActive' \
|
|
"$phone_page"
|
|
for action in ring clipboard share; do
|
|
rg -Fq "enabled: root.actionsReady && KdeConnect.supports(\"$action\")" "$phone_page" \
|
|
|| fail "the $action action does not gate on nearby, idle, and plugin support together"
|
|
done
|
|
assert_contains 'KdeConnect.ring()' "$phone_page"
|
|
assert_contains 'KdeConnect.sendClipboard()' "$phone_page"
|
|
assert_contains 'KdeConnect.sendFile(path)' "$phone_page"
|
|
assert_contains 'KdeConnect.cancelTransfer()' "$phone_page"
|
|
|
|
# ── Messages ─────────────────────────────────────────────────────────────────
|
|
|
|
assert_contains 'Opens BlueBubbles' "$phone_page"
|
|
assert_contains 'SystemSettings.openApplication("bluebubbles")' "$phone_page"
|
|
messages_card="$(sed -n '/title: "Messages"/,/title: "Device"/p' "$phone_page")"
|
|
[[ -n "$messages_card" ]] || fail 'PhonePage.qml has no Messages card'
|
|
rg -Fq 'SystemSettings.bluebubblesAvailable' <<<"$messages_card" \
|
|
|| fail 'Messages enablement does not read BlueBubbles availability'
|
|
if rg -Fq 'KdeConnect.' <<<"$messages_card"; then
|
|
fail 'Messages is coupled to KDE Connect'
|
|
fi
|
|
assert_contains 'readonly property bool bluebubblesAvailable: root.bluebubblesDetected' "$system_settings"
|
|
assert_contains 'command: ["flatpak", "info", "app.bluebubbles.BlueBubbles"]' "$system_settings"
|
|
assert_contains '"bluebubbles": ["flatpak", "run", "app.bluebubbles.BlueBubbles"]' "$system_settings"
|
|
|
|
# ── The live half ────────────────────────────────────────────────────────────
|
|
|
|
state_home="$(mktemp -d /tmp/panama-phone-page-state.XXXXXX)"
|
|
config_path="$state_home/quickshell"
|
|
test_bin="$state_home/bin"
|
|
shell_log="$state_home/quickshell.log"
|
|
flatpak_log="$state_home/flatpak.log"
|
|
|
|
cleanup_bootstrap() {
|
|
rm -rf "$state_home"
|
|
}
|
|
trap cleanup_bootstrap EXIT
|
|
|
|
mkdir -p "$test_bin"
|
|
cp -a "$repo_dir/config/dot/quickshell" "$config_path"
|
|
: >"$flatpak_log"
|
|
|
|
cat >"$config_path/scripts/panama-home-assistant" <<'EOF'
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
case "${1:-}" in
|
|
catalog|areas)
|
|
printf '%s\n' '{"ok":false,"error":"test-helper"}'
|
|
;;
|
|
esac
|
|
EOF
|
|
chmod +x "$config_path/scripts/panama-home-assistant"
|
|
|
|
cat >"$test_bin/hyprctl" <<'EOF'
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
if [[ "${1:-}" == "-j" && "${2:-}" == "monitors" ]]; then
|
|
printf '%s\n' '[{"focused":true,"name":"TEST-1","description":"Phone contract","width":1920,"height":1080,"refreshRate":60,"scale":1,"currentFormat":"XRGB8888","colorManagementPreset":"srgb","vrr":false}]'
|
|
exit 0
|
|
fi
|
|
if [[ "${1:-}" == "keyword" ]]; then
|
|
exit 0
|
|
fi
|
|
exec /usr/sbin/hyprctl "$@"
|
|
EOF
|
|
chmod +x "$test_bin/hyprctl"
|
|
|
|
cat >"$test_bin/flatpak" <<'EOF'
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
printf '%s\n' "$*" >>"$PANAMA_FLATPAK_LOG"
|
|
if [[ "${1:-}" == "info" && "${2:-}" == "app.bluebubbles.BlueBubbles" ]]; then
|
|
exit 0
|
|
fi
|
|
exit 97
|
|
EOF
|
|
chmod +x "$test_bin/flatpak"
|
|
|
|
qs_for_test() {
|
|
PATH="$test_bin:$PATH" QS_CONFIG_PATH="$config_path" XDG_STATE_HOME="$state_home" \
|
|
PANAMA_FLATPAK_LOG="$flatpak_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 settings close >/dev/null 2>&1 || true
|
|
if stop_test_shell; then
|
|
rm -rf "$state_home"
|
|
else
|
|
printf 'phone page 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 settings-system$' >/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,240p' "$shell_log" >&2
|
|
fail 'isolated branch shell did not start'
|
|
}
|
|
|
|
start_test_shell
|
|
qs_for_test ipc call settings page phone >/dev/null
|
|
for _ in $(seq 1 40); do
|
|
[[ "$(qs_for_test ipc call settings status | jq -r .page)" == "phone" ]] && break
|
|
sleep 0.1
|
|
done
|
|
status="$(qs_for_test ipc call settings status | jq -c .)"
|
|
jq -e '.open == true and .page == "phone"' <<<"$status" >/dev/null \
|
|
|| fail "the Phone tab did not render: $status"
|
|
|
|
system_status="$(qs_for_test ipc call settings-system status | jq -c .)"
|
|
jq -e '.bluebubblesAvailable == true' <<<"$system_status" >/dev/null \
|
|
|| fail "BlueBubbles availability was not exposed: $system_status"
|
|
|
|
shell_pid="$(qs_for_test list | awk '/Process ID:/ { print $3; exit }')"
|
|
[[ "$shell_pid" =~ ^[0-9]+$ ]] || fail 'could not identify the branch shell process'
|
|
for _ in $(seq 1 40); do
|
|
if /usr/sbin/hyprctl -j clients | jq -e --argjson pid "$shell_pid" \
|
|
'[.[] | select(.pid == $pid and .title == "Settings" and .floating == false)] | length == 1' >/dev/null; then
|
|
break
|
|
fi
|
|
sleep 0.1
|
|
done
|
|
/usr/sbin/hyprctl -j clients | jq -e --argjson pid "$shell_pid" \
|
|
'[.[] | select(.pid == $pid and .title == "Settings" and .floating == false)] | length == 1' >/dev/null \
|
|
|| fail 'the branch shell did not own exactly one tiled Panama Settings client'
|
|
|
|
if [[ -n "${PANAMA_TEST_SCREENSHOT_PATH:-}" ]]; then
|
|
geometry="$(/usr/sbin/hyprctl -j clients | jq -r --argjson pid "$shell_pid" \
|
|
'.[] | select(.pid == $pid and .title == "Settings") | "\(.at[0]),\(.at[1]) \(.size[0])x\(.size[1])"')"
|
|
[[ -n "$geometry" ]] || fail 'could not resolve the Settings client geometry'
|
|
grim -g "$geometry" "$PANAMA_TEST_SCREENSHOT_PATH"
|
|
fi
|
|
|
|
rg -Fxq 'info app.bluebubbles.BlueBubbles' "$flatpak_log" \
|
|
|| fail 'the fixed BlueBubbles availability probe did not run'
|
|
if rg -q '^run ' "$flatpak_log"; then
|
|
fail 'the contract started BlueBubbles'
|
|
fi
|
|
|
|
trap - EXIT
|
|
cleanup
|
|
[[ ! -e "$state_home" ]] || fail 'temporary Phone state was not removed after shell exit'
|
|
printf 'phone page contract: PASS\n'
|