It is the system settings app for this desktop, so it is named the way one is. The window is "Settings", the titlebar is "Settings", the wordmark above the search field is gone, the sidebar's last row is "About", and the status line reads "Desktop is healthy". Prose followed the same rule. Forty-odd strings explained what "Panama" does -- "Panama never animates while idle", "Restore Panama defaults", "Panama looks in ~/Pictures/Wallpapers" -- which is how a product describes itself, not how a settings panel describes a setting. They now say what happens. No user-visible "Panama" remains anywhere in the app. Two consequences worth naming: The SUPER+I shortcut's description is user-visible, because the Shortcuts page is generated from it, so that is renamed too. Rebindings are keyed by shipped chord rather than description, so no existing override is orphaned by this. Three contracts matched the window by title and one matched that shortcut by description; all four are updated. There are no Hyprland window rules keyed on the title, so nothing about the window's placement changes. The desktop entry is now Name=Settings, but the FILE keeps its panama-settings name, as does the icon: the dock pins applications by desktop id, and renaming the file would silently unpin it. dock-pins-contract covers exactly that. The dated design docs under docs/superpowers keep the old name. They are a record of what was decided when, and editing them to agree with the present would make them lie about the past. Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
320 lines
12 KiB
Bash
Executable File
320 lines
12 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
set -euo pipefail
|
|
|
|
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
state_home="$(mktemp -d /tmp/panama-home-phone-settings-state.XXXXXX)"
|
|
source_config_path="$repo_dir/config/dot/quickshell"
|
|
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 "$source_config_path" "$config_path"
|
|
: >"$flatpak_log"
|
|
|
|
cat >"$config_path/scripts/panama-home-assistant" <<'EOF'
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
case "${1:-}" in
|
|
catalog)
|
|
printf '%s\n' '{"ok":false,"error":"test-helper"}'
|
|
;;
|
|
toggle|brightness)
|
|
printf '%s\n' '{"ok":true}'
|
|
;;
|
|
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":"Home and 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"
|
|
|
|
fail() {
|
|
printf 'home phone settings contract: %s\n' "$1" >&2
|
|
exit 1
|
|
}
|
|
|
|
assert_contains() {
|
|
local needle="$1"
|
|
local file="$2"
|
|
rg -Fq "$needle" "$file" || fail "$file is missing: $needle"
|
|
}
|
|
|
|
home_page="$repo_dir/config/dot/quickshell/modules/settings/HomePhonePage.qml"
|
|
favorite_card="$repo_dir/config/dot/quickshell/modules/settings/HomeFavoriteCard.qml"
|
|
available_row="$repo_dir/config/dot/quickshell/modules/settings/AvailableLightRow.qml"
|
|
system_settings="$repo_dir/config/dot/quickshell/services/SystemSettings.qml"
|
|
|
|
[[ -f "$home_page" ]] || fail 'HomePhonePage.qml is missing'
|
|
[[ -f "$favorite_card" ]] || fail 'HomeFavoriteCard.qml is missing'
|
|
[[ -f "$available_row" ]] || fail 'AvailableLightRow.qml is missing'
|
|
assert_contains 'SettingsPage {' "$home_page"
|
|
assert_contains 'title: "Home & Phone"' "$home_page"
|
|
assert_contains 'lede: "Choose what appears in Control Center and keep phone continuity close at hand."' "$home_page"
|
|
if rg -q '^\s*Flickable \{' "$home_page"; then
|
|
fail 'HomePhonePage.qml still owns a copied Flickable scaffold'
|
|
fi
|
|
assert_contains 'Connected · ' "$home_page"
|
|
assert_contains 'Last update unavailable · showing saved controls' "$home_page"
|
|
assert_contains 'Authentication required' "$home_page"
|
|
assert_contains 'Home Assistant is not configured' "$home_page"
|
|
assert_contains 'HomePreferences.setAlias' "$home_page"
|
|
assert_contains 'HomePreferences.move' "$home_page"
|
|
assert_contains 'HomePreferences.remove' "$home_page"
|
|
assert_contains 'HomePreferences.add' "$home_page"
|
|
assert_contains 'HomePreferences.retrySave' "$home_page"
|
|
assert_contains 'Choose lights below to build your Control Center shelf.' "$home_page"
|
|
assert_contains 'All discovered lights are already selected' "$home_page"
|
|
assert_contains 'No lights discovered' "$home_page"
|
|
assert_contains 'No lights match that search' "$home_page"
|
|
assert_contains 'Opens BlueBubbles' "$home_page"
|
|
[[ "$(rg -Fc 'required property var modelData' "$home_page")" -ge 2 ]] \
|
|
|| fail 'HomePhonePage.qml does not bind both reusable delegates to modelData'
|
|
if rg -Fq 'index: model.index' "$home_page"; then
|
|
fail 'HomePhonePage.qml reads an undefined model.index instead of the delegate index'
|
|
fi
|
|
if rg -qi 'bearer|api/states' "$home_page"; then
|
|
fail 'HomePhonePage.qml crosses the Home Assistant REST boundary'
|
|
fi
|
|
assert_contains 'signal aliasCommitted(string id, string alias)' "$favorite_card"
|
|
assert_contains 'signal removeRequested(string id)' "$favorite_card"
|
|
assert_contains 'signal moveRequested(string id, int targetIndex)' "$favorite_card"
|
|
assert_contains 'text: "↑"' "$favorite_card"
|
|
assert_contains 'text: "↓"' "$favorite_card"
|
|
assert_contains 'enabled: root.canMoveEarlier' "$favorite_card"
|
|
assert_contains 'enabled: root.canMoveLater' "$favorite_card"
|
|
if rg -Fq 'DragHandler {' "$favorite_card"; then
|
|
fail 'Home light cards still expose the broken drag affordance'
|
|
fi
|
|
assert_contains 'canMoveEarlier: index > 0' "$home_page"
|
|
assert_contains 'canMoveLater: index < favoritesGrid.count - 1' "$home_page"
|
|
assert_contains 'onEditingFinished:' "$favorite_card"
|
|
assert_contains 'text: "Control Center"' "$favorite_card"
|
|
assert_contains 'activeFocusOnTab: true' "$favorite_card"
|
|
assert_contains 'signal addRequested(string id)' "$available_row"
|
|
assert_contains 'activeFocusOnTab: true' "$available_row"
|
|
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"
|
|
|
|
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 'home phone settings 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 home-assistant fixture ready >/dev/null
|
|
qs_for_test ipc call settings page home-phone >/dev/null
|
|
|
|
status='{}'
|
|
for _ in $(seq 1 40); do
|
|
status="$(qs_for_test ipc call settings status | jq -c .)"
|
|
if jq -e '.page == "home-phone" and .discoveredCount == 7 and .selectedCount == 7' \
|
|
<<<"$status" >/dev/null; then
|
|
break
|
|
fi
|
|
sleep 0.1
|
|
done
|
|
jq -e '
|
|
.open == true and
|
|
.page == "home-phone" and
|
|
.discoveredCount == 7 and
|
|
.selectedCount == 7 and
|
|
.homePhone.availableLightIds == [] and
|
|
.homePhone.availableEmptyText == "All discovered lights are already selected"
|
|
' \
|
|
<<<"$status" >/dev/null || fail "Home & Phone diagnostics are incomplete: $status"
|
|
|
|
qs_for_test ipc call home-assistant fixture available-extra >/dev/null
|
|
available_status='{}'
|
|
for _ in $(seq 1 40); do
|
|
available_status="$(qs_for_test ipc call settings status | jq -c .)"
|
|
if jq -e '
|
|
.discoveredCount == 8 and
|
|
.selectedCount == 7 and
|
|
.homePhone.availableLightIds == ["light.fixture_guest"]
|
|
' <<<"$available_status" >/dev/null; then
|
|
break
|
|
fi
|
|
sleep 0.1
|
|
done
|
|
jq -e '
|
|
.discoveredCount == 8 and
|
|
.selectedCount == 7 and
|
|
.homePhone.availableLightIds == ["light.fixture_guest"]
|
|
' <<<"$available_status" >/dev/null \
|
|
|| fail "an unselected fixture light was not the only available row: $available_status"
|
|
|
|
qs_for_test ipc call home-assistant fixture stale-authentication >/dev/null
|
|
authentication_status='{}'
|
|
for _ in $(seq 1 40); do
|
|
authentication_status="$(qs_for_test ipc call settings status | jq -c .)"
|
|
if jq -e '
|
|
.discoveredCount == 7 and
|
|
.selectedCount == 7 and
|
|
.homePhone.homeStatus == "Authentication required"
|
|
' <<<"$authentication_status" >/dev/null; then
|
|
break
|
|
fi
|
|
sleep 0.1
|
|
done
|
|
jq -e '
|
|
.discoveredCount == 7 and
|
|
.selectedCount == 7 and
|
|
.homePhone.homeStatus == "Authentication required"
|
|
' <<<"$authentication_status" >/dev/null \
|
|
|| fail "stale authentication did not retain actionable copy: $authentication_status"
|
|
|
|
qs_for_test ipc call home-assistant fixture stale-not-configured >/dev/null
|
|
not_configured_status='{}'
|
|
for _ in $(seq 1 40); do
|
|
not_configured_status="$(qs_for_test ipc call settings status | jq -c .)"
|
|
if jq -e '
|
|
.discoveredCount == 7 and
|
|
.selectedCount == 7 and
|
|
.homePhone.homeStatus == "Home Assistant is not configured"
|
|
' <<<"$not_configured_status" >/dev/null; then
|
|
break
|
|
fi
|
|
sleep 0.1
|
|
done
|
|
jq -e '
|
|
.discoveredCount == 7 and
|
|
.selectedCount == 7 and
|
|
.homePhone.homeStatus == "Home Assistant is not configured"
|
|
' <<<"$not_configured_status" >/dev/null \
|
|
|| fail "stale not-configured state did not retain actionable copy: $not_configured_status"
|
|
|
|
qs_for_test ipc call home-assistant fixture unavailable >/dev/null
|
|
empty_status='{}'
|
|
for _ in $(seq 1 40); do
|
|
empty_status="$(qs_for_test ipc call settings status | jq -c .)"
|
|
if jq -e '
|
|
.discoveredCount == 0 and
|
|
.selectedCount == 0 and
|
|
.homePhone.homeStatus == "Home Assistant is not configured" and
|
|
.homePhone.availableLightIds == [] and
|
|
.homePhone.availableEmptyText == "No lights discovered"
|
|
' <<<"$empty_status" >/dev/null; then
|
|
break
|
|
fi
|
|
sleep 0.1
|
|
done
|
|
jq -e '
|
|
.discoveredCount == 0 and
|
|
.selectedCount == 0 and
|
|
.homePhone.homeStatus == "Home Assistant is not configured" and
|
|
.homePhone.availableLightIds == [] and
|
|
.homePhone.availableEmptyText == "No lights discovered"
|
|
' <<<"$empty_status" >/dev/null \
|
|
|| fail "an empty catalog did not render its distinct copy: $empty_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
|
|
if find "$state_home" -name panama-home.json -print -quit | rg -q .; then
|
|
fail 'the read-only fixture route wrote Home preferences'
|
|
fi
|
|
|
|
trap - EXIT
|
|
cleanup
|
|
[[ ! -e "$state_home" ]] || fail 'temporary Home & Phone state was not removed after shell exit'
|
|
printf 'home phone settings contract: PASS\n'
|