Drop the extension, and give the test suite a front door
Phase 6, the last of the fresh-install spec. 159 scripts lose their .sh: 110 contracts, 47 Vicinae commands, 2 compositor contracts. A shebang and the executable bit already select the interpreter. The extension only ever added something that had to stay in sync, and the rename proved the point twice over in the space of an hour. The spec's stated risk was Vicinae's script discovery. One script was renamed and reloaded on its own before the other 46 followed; it came back as scripts:panama.capture and all 47 resolve. What the probe turned up instead is that the extension was never only a filename: Vicinae's command IDs embed it, so every ID changed. Nothing in this repository refers to them, so nothing breaks. The only trace is Vicinae's metadata.json, whose visited map had two Panama entries that are now orphaned -- two commands lost their usage ranking and will earn it back. Worth knowing before anyone renames these again on a machine that has a keybind pointing at one. Rewriting the references by exact filename missed two things it structurally could not see: a name built from a variable, settings-$page.sh, and a glob, -name '*.sh'. Both were in the contract that counts the generated commands, which promptly reported 47 expected and 0 found. The mechanical part of a rename is the part that looks finished. The three subcommands. panama doctor fronts a health check that already existed and already ran at the end of every install but could not be reached from a terminal. panama upgrade re-runs the installer from anywhere. panama test runs the suite, which had no entry point at all -- 121 files that were the main safety net in this repository and were invisible in it. Writing that runner found three tests nothing was running. calendar_agenda_bridge_test, home_assistant_bridge_test and kdeconnect_bridge_test are unittest suites without the executable bit, so no contract invoked them and the first draft of the runner skipped them silently. All three pass, and have passed unobserved for weeks. The runner collects *_test.py as well now, because a runner with a blind spot is worse than no runner for the same reason a dependency checker with one is: it reports PASS. Six worktrees pruned. Each was re-checked rather than trusted to the spec's list, and two needed it: panama-commands is not on feat/panama-commands but on feat/gnome-tweaks-parity, and fix/panama-displays-review reads [ahead 3] -- ahead of its remote, not of main, with every commit patch-equivalent to landed work. roadmap-completion stays; it has five commits that are genuinely unlanded. The branches are left alone: pruning a worktree costs nothing, deleting a branch is a decision. 121 contracts pass. Claude-Session: https://claude.ai/code/session_01NvgBuSWB5sE43yWmg21ozj
This commit is contained in:
+187
@@ -0,0 +1,187 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Two behaviors the settings rows depend on:
|
||||
#
|
||||
# commitPreference(key, value)
|
||||
# One entry point for every row. A compositor-backed key must reach Hyprland
|
||||
# and be confirmed before it is stored; a local key is written directly.
|
||||
# Rows bind a schema key and call this, so they never need to know which
|
||||
# kind they hold -- and a row must not be able to store a value the
|
||||
# compositor rejected.
|
||||
#
|
||||
# restoreDefaults()
|
||||
# Panama keeps user state in more than one file. Resetting only the schema
|
||||
# store would leave a customized Home accessory arrangement in place while
|
||||
# claiming to have restored Panama's defaults. That is worse than having no
|
||||
# reset at all, because it is silent.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
harness="$repo_dir/config/dot/quickshell/settings-system-harness.qml"
|
||||
system_settings="$repo_dir/config/dot/quickshell/services/SystemSettings.qml"
|
||||
wallpaper_service="$repo_dir/config/dot/quickshell/services/Wallpaper.qml"
|
||||
|
||||
# Preferences are committed to $XDG_CONFIG_HOME, and the Home store lives under
|
||||
# $XDG_STATE_HOME. Both are isolated so this contract cannot touch the real
|
||||
# desktop's settings. The harness replaces the compositor write seam as well,
|
||||
# so interruption cannot leave the daily desktop modified.
|
||||
config_home="$(mktemp -d /tmp/panama-commit-config.XXXXXX)"
|
||||
state_home="$(mktemp -d /tmp/panama-commit-state.XXXXXX)"
|
||||
|
||||
fail() {
|
||||
printf 'settings commit/reset contract: %s\n' "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
rg -Fq 'HomePreferences.resetHomeDefaults();' "$system_settings" \
|
||||
|| fail 'restoreDefaults does not use the durable Home reset boundary'
|
||||
if rg -q 'HomePreferences\.(favorites|initialized)\s*=' "$system_settings"; then
|
||||
fail 'restoreDefaults mutates Home aliases instead of using resetHomeDefaults'
|
||||
fi
|
||||
rg -Fq 'DesktopPreferences.resetDesktopDefaults();' "$system_settings" \
|
||||
|| fail 'restoreDefaults does not clear confirmed display layout fields'
|
||||
rg -Fq 'Keybinds.applyReload();' "$system_settings" \
|
||||
|| fail 'restoreDefaults does not replay shipped keybindings'
|
||||
rg -Fq 'root.applyWallpaper(String(DesktopPreferences.get("wallpaperPath") ?? ""));' "$system_settings" \
|
||||
|| fail 'restoreDefaults does not visibly reapply the shipped wallpaper'
|
||||
rg -Fq 'LockScreen.regenerate();' "$system_settings" \
|
||||
|| fail 'restoreDefaults does not regenerate the shipped lock screen'
|
||||
rg -Fq 'const effectivePath = path === "" ? root.shippedPath : path;' "$wallpaper_service" \
|
||||
|| fail 'clearing wallpaper preference leaves the old image visible'
|
||||
rg -Fq 'storedPath: rawGlobal === root.shippedPath ? "" : rawGlobal' "$wallpaper_service" \
|
||||
|| fail 'the shipped wallpaper cannot remain represented by the default empty preference'
|
||||
|
||||
qs_for_harness() {
|
||||
XDG_CONFIG_HOME="$config_home" XDG_STATE_HOME="$state_home" \
|
||||
PANAMA_SETTINGS_TEST_ISOLATE_COMPOSITOR=1 qs -p "$harness" "$@"
|
||||
}
|
||||
|
||||
restore() {
|
||||
qs_for_harness kill >/dev/null 2>&1 || true
|
||||
rm -rf "$config_home" "$state_home"
|
||||
}
|
||||
trap restore EXIT
|
||||
|
||||
# This contract shares its harness file with settings-hyprland-write-contract,
|
||||
# and Quickshell identifies an instance by config path -- so if that run's
|
||||
# instance is still alive, the IPC wait below is satisfied by ITS target. That
|
||||
# direction is the dangerous one: this contract believes the compositor seam is
|
||||
# stubbed, so it would happily drive the DAILY DESKTOP's real compositor while
|
||||
# reporting isolation. Refuse to start rather than find out.
|
||||
harness_instances() {
|
||||
# rg -c prints nothing when there are no matches, so an unguarded
|
||||
# substitution yields "" rather than "0".
|
||||
local count
|
||||
count="$(qs list 2>/dev/null | rg -c "^ Config path: $harness\$" || true)"
|
||||
printf '%s' "${count:-0}"
|
||||
}
|
||||
for _ in $(seq 1 50); do
|
||||
[[ "$(harness_instances)" == "0" ]] && break
|
||||
sleep 0.1
|
||||
done
|
||||
[[ "$(harness_instances)" == "0" ]] \
|
||||
|| fail 'another instance of the settings harness is still running -- this contract would drive it instead of its own isolated one, and that instance may be writing to the real compositor'
|
||||
|
||||
XDG_CONFIG_HOME="$config_home" XDG_STATE_HOME="$state_home" \
|
||||
PANAMA_SETTINGS_TEST_ISOLATE_COMPOSITOR=1 qs -p "$harness" --daemonize >/dev/null
|
||||
for _ in $(seq 1 40); do
|
||||
qs_for_harness ipc show 2>/dev/null | rg -q '^target settings-system-test$' && break
|
||||
sleep 0.1
|
||||
done
|
||||
qs_for_harness ipc show 2>/dev/null | rg -q '^target settings-system-test$' || fail 'test IPC target did not start'
|
||||
|
||||
# ── A local key is stored directly ───────────────────────────────────────────
|
||||
[[ "$(qs_for_harness ipc call settings-system-test commit showSeconds false)" == "true" ]] \
|
||||
|| fail 'commitPreference refused a local key'
|
||||
[[ "$(qs_for_harness ipc call settings-system-test stored showSeconds)" == "false" ]] \
|
||||
|| fail 'a local key was not stored'
|
||||
|
||||
# ── A compositor key reaches the verified apply boundary, then is stored ────
|
||||
target_rounding=11
|
||||
[[ "$(qs_for_harness ipc call settings-system-test commit windowRounding "$target_rounding")" == "true" ]] \
|
||||
|| fail 'commitPreference refused a compositor key'
|
||||
apply_state="$(qs_for_harness ipc call settings-system-test applyState)"
|
||||
jq -e '.[-1].windowRounding == 11' <<<"$apply_state" >/dev/null \
|
||||
|| fail "a compositor-backed commit did not reach the apply boundary: $apply_state"
|
||||
[[ "$(qs_for_harness ipc call settings-system-test stored windowRounding)" == "$target_rounding" ]] \
|
||||
|| fail 'a verified compositor commit was not stored'
|
||||
|
||||
# ── A value the schema rejects is never stored ───────────────────────────────
|
||||
before="$(qs_for_harness ipc call settings-system-test stored windowRounding)"
|
||||
[[ "$(qs_for_harness ipc call settings-system-test commit windowRounding 9999)" == "true" ]] \
|
||||
|| fail 'an out-of-range value should be clamped by the schema, not refused outright'
|
||||
[[ "$(qs_for_harness ipc call settings-system-test stored windowRounding)" != "9999" ]] \
|
||||
|| fail 'an out-of-range value was stored unclamped'
|
||||
|
||||
[[ "$(qs_for_harness ipc call settings-system-test commit __not_a_setting__ 1)" == "false" ]] \
|
||||
|| fail 'commitPreference accepted a key outside the schema'
|
||||
|
||||
# ── Reset spans every store, not just the schema one ─────────────────────────
|
||||
qs_for_harness ipc call settings-system-test seedHome >/dev/null
|
||||
qs_for_harness ipc call settings-system-test commit dockHideDelayMs 900 >/dev/null
|
||||
display_fixture='{"DP-2":{"mode":"4500x3000@60","scale":1.5,"transform":0,"x":0,"y":0,"primary":true},"HDMI-A-1":{"mode":"2560x1440@60","scale":1,"transform":0,"x":3000,"y":0,"primary":false}}'
|
||||
[[ "$(qs_for_harness ipc call settings-system-test commit displays "$display_fixture")" == "true" ]] \
|
||||
|| fail 'the protected display fixture did not apply'
|
||||
sleep 0.4
|
||||
|
||||
home_before="$(qs_for_harness ipc call settings-system-test homeState)"
|
||||
jq -e '.count == 1 and .initialized == true' <<<"$home_before" >/dev/null \
|
||||
|| fail "the Home fixture did not apply: $home_before"
|
||||
[[ "$(qs_for_harness ipc call settings-system-test stored dockHideDelayMs)" == "900" ]] \
|
||||
|| fail 'the dock fixture did not apply'
|
||||
|
||||
[[ "$(qs_for_harness ipc call settings-system-test restoreDefaults)" == "true" ]] \
|
||||
|| fail 'restoreDefaults refused a safe reset'
|
||||
sleep 0.6
|
||||
|
||||
reset_state="$(qs_for_harness ipc call settings-system-test resetState)"
|
||||
# The snapshot must come FIRST. Restoring defaults is the only irreversible
|
||||
# action Panama offers, and a snapshot taken after the stores were cleared would
|
||||
# faithfully record the wiped state as if it were the user's.
|
||||
jq -e '.calls[0] == "snapshot"' <<<"$reset_state" >/dev/null \
|
||||
|| fail "reset did not snapshot before wiping the stores: $reset_state"
|
||||
|
||||
jq -e '.calls == [
|
||||
"snapshot",
|
||||
"display.block:true",
|
||||
"keybinds.reload",
|
||||
"wallpaper.set:",
|
||||
"lock.regenerate",
|
||||
"display.block:false"
|
||||
] and .displayBlocked == false' <<<"$reset_state" >/dev/null \
|
||||
|| fail "reset did not safely replay non-reactive state: $reset_state"
|
||||
|
||||
[[ "$(qs_for_harness ipc call settings-system-test stored dockHideDelayMs)" == "250" ]] \
|
||||
|| fail 'reset did not restore a schema default'
|
||||
for default_case in \
|
||||
'wallpaperMode|"single"' \
|
||||
'wallpaperSlideshowPaths|[]' \
|
||||
'wallpaperIntervalMinutes|30' \
|
||||
'wallpaperShuffle|true' \
|
||||
'wallpaperPerMonitor|{}' \
|
||||
'lockBackgroundMode|"screenshot"' \
|
||||
'lockBlurLevel|3' \
|
||||
'lockShowClock|true' \
|
||||
'lockShowDate|true' \
|
||||
'lockShowUser|true' \
|
||||
'lockFadeOnEmpty|false'; do
|
||||
IFS='|' read -r key expected <<<"$default_case"
|
||||
[[ "$(qs_for_harness ipc call settings-system-test stored "$key")" == "$expected" ]] \
|
||||
|| fail "reset did not restore $key to $expected"
|
||||
done
|
||||
[[ "$(qs_for_harness ipc call settings-system-test stored displays | jq -cS .)" == '{}' ]] \
|
||||
|| fail 'reset retained confirmed arrangement fields instead of returning startup to shipped placement'
|
||||
|
||||
home_after="$(qs_for_harness ipc call settings-system-test homeState)"
|
||||
jq -e '.count == 0 and .initialized == false' <<<"$home_after" >/dev/null \
|
||||
|| fail "reset left the Home accessory store customized: $home_after"
|
||||
|
||||
# Resetting a stored value does not itself apply compositor policy, so the last
|
||||
# isolated batch must contain the shipped default.
|
||||
jq -e '.appliedBatches[-1].windowRounding == 18' <<<"$reset_state" >/dev/null \
|
||||
|| fail "reset did not re-apply the compositor default: $reset_state"
|
||||
|
||||
trap - EXIT
|
||||
restore
|
||||
printf 'settings commit/reset contract: PASS\n'
|
||||
Reference in New Issue
Block a user