colour -> color, behaviour -> behavior, centre -> center, favourite -> favorite, and about twenty other pairs, applied consistently across comments, docs, error/UI copy, and a handful of QML identifiers that used the British spelling as their actual name: SystemSettings' serialiseValue/serialiseTable/normaliseGradient, Displays' normaliseModes, Wallpaper's normalisePolicy, SettingsBackup's serialiseHomeState, DateTime's ntpSynchronised property, Clipboard's _normalise helper, and ShortcutCapture's cancelled signal (with its onCancelled handler in ShortcutsPage.qml). Every call site and the two tests that assert on the literal source text (settings-ownership and settings-backup-live contracts) were updated in lockstep. Left untouched: config/dot/espanso/match/packages/misspell-en/ is a vendored third-party autocorrect dictionary -- its entries are typo corrections, not our prose, and rewriting them would fight the package's own purpose (and any future re-sync from upstream). The already-American `favorites` property (Home page pinned accessories) was never actually misspelled -- only nearby comments and error strings said "favourites" -- so no data migration was needed there. Claude-Session: https://claude.ai/code/session_01E6TJUAh41HaP25MVHWkhRZ
129 lines
6.5 KiB
Bash
Executable File
129 lines
6.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
# config/dot/hypr/prefs.lua is read at config time by looks.lua, input.lua, and
|
|
# monitors.lua. It is therefore the one piece of Panama that can cost the user a
|
|
# working compositor rather than merely a working feature.
|
|
#
|
|
# This contract pins the only behavior that matters: whatever is in the
|
|
# settings file -- including nothing, garbage, or values of the wrong type --
|
|
# the config still parses, and every setting either takes the stored value or
|
|
# falls back to the value shipped in the Lua.
|
|
|
|
set -euo pipefail
|
|
|
|
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
hypr_dir="$repo_dir/config/dot/hypr"
|
|
work="$(mktemp -d /tmp/panama-prefs-contract.XXXXXX)"
|
|
|
|
fail() {
|
|
printf 'prefs fallback contract: %s\n' "$1" >&2
|
|
exit 1
|
|
}
|
|
|
|
cleanup() {
|
|
rm -rf "$work"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
# Exercise prefs.lua directly rather than through a full compositor launch: the
|
|
# question is what the module returns, and Hyprland's own parse is covered by
|
|
# the --verify-config case at the end.
|
|
probe() {
|
|
local settings="$1"
|
|
mkdir -p "$work/config/panama"
|
|
if [[ "$settings" == "__absent__" ]]; then
|
|
rm -f "$work/config/panama/settings.json"
|
|
else
|
|
printf '%s' "$settings" >"$work/config/panama/settings.json"
|
|
fi
|
|
|
|
XDG_CONFIG_HOME="$work/config" lua -e "
|
|
package.path = '$hypr_dir/?.lua;' .. package.path
|
|
local ok, prefs = pcall(require, 'prefs')
|
|
if not ok then
|
|
print('LOAD_ERROR ' .. tostring(prefs))
|
|
os.exit(0)
|
|
end
|
|
print(string.format(
|
|
'gapsOut=%s rounding=%s blur=%s layout=%s hdr=%s',
|
|
tostring(prefs.get('gapsOut', 10)),
|
|
tostring(prefs.get('windowRounding', 18)),
|
|
tostring(prefs.get('blurEnabled', true)),
|
|
tostring(prefs.get('keyboardLayout', 'us')),
|
|
tostring(prefs.getInt('autoHdr', 1))))
|
|
" 2>&1
|
|
}
|
|
|
|
shipped='gapsOut=10 rounding=18 blur=true layout=us hdr=1'
|
|
|
|
# ── No file at all: the normal first run ─────────────────────────────────────
|
|
result="$(probe '__absent__')"
|
|
[[ "$result" == "$shipped" ]] || fail "an absent settings file did not yield shipped defaults: $result"
|
|
|
|
# ── Empty file ───────────────────────────────────────────────────────────────
|
|
result="$(probe '')"
|
|
[[ "$result" == "$shipped" ]] || fail "an empty settings file did not yield shipped defaults: $result"
|
|
|
|
# ── Whitespace only ──────────────────────────────────────────────────────────
|
|
result="$(probe '
|
|
')"
|
|
[[ "$result" == "$shipped" ]] || fail "a whitespace-only file did not yield shipped defaults: $result"
|
|
|
|
# ── Truncated mid-object, the shape a crashed write leaves behind ────────────
|
|
result="$(probe '{ "gapsOut": 24, "windowRounding":')"
|
|
[[ "$result" == "$shipped" ]] || fail "a truncated file did not yield shipped defaults: $result"
|
|
|
|
# ── Not JSON at all ──────────────────────────────────────────────────────────
|
|
result="$(probe 'gaps_out = 24')"
|
|
[[ "$result" == "$shipped" ]] || fail "a non-JSON file did not yield shipped defaults: $result"
|
|
|
|
# ── A JSON array rather than an object ───────────────────────────────────────
|
|
result="$(probe '[1, 2, 3]')"
|
|
[[ "$result" == "$shipped" ]] || fail "a top-level array did not yield shipped defaults: $result"
|
|
|
|
# ── Wrong types: each bad value falls back on its own ────────────────────────
|
|
result="$(probe '{"gapsOut": "wide", "windowRounding": 24, "blurEnabled": 3, "keyboardLayout": 7}')"
|
|
[[ "$result" == 'gapsOut=10 rounding=24 blur=true layout=us hdr=1' ]] \
|
|
|| fail "wrong-typed values did not fall back per key: $result"
|
|
|
|
# ── Good values are actually used ────────────────────────────────────────────
|
|
result="$(probe '{"gapsOut": 24, "windowRounding": 6, "blurEnabled": false, "keyboardLayout": "us,de", "autoHdr": false}')"
|
|
[[ "$result" == 'gapsOut=24 rounding=6 blur=false layout=us,de hdr=0' ]] \
|
|
|| fail "stored values were not applied: $result"
|
|
|
|
# ── A boolean maps onto an integer option, matching SystemSettings.hyprValue ─
|
|
result="$(probe '{"autoHdr": true}')"
|
|
[[ "$result" == 'gapsOut=10 rounding=18 blur=true layout=us hdr=1' ]] \
|
|
|| fail "getInt did not convert a boolean: $result"
|
|
|
|
# ── Escapes and nesting do not break the reader ──────────────────────────────
|
|
result="$(probe '{"note": "a \"quoted\" value\nwith escapes", "nested": {"a": [1, 2, {"b": null}]}, "gapsOut": 12}')"
|
|
[[ "$result" == 'gapsOut=12 rounding=18 blur=true layout=us hdr=1' ]] \
|
|
|| fail "a file with escapes and nesting was not parsed: $result"
|
|
|
|
# ── The real config parses in every one of those states ─────────────────────
|
|
# This is the case that actually protects the desktop: prefs.lua returning
|
|
# defaults is only useful if Hyprland still accepts the config around it.
|
|
#
|
|
# Hyprland resolves its own config from $HOME/.config/hypr regardless of
|
|
# XDG_CONFIG_HOME, while prefs.lua honors XDG_CONFIG_HOME. That asymmetry is
|
|
# what makes this loop useful rather than vacuous: the *real* looks.lua and
|
|
# input.lua are parsed against a *fixture* settings file. The probe cases above
|
|
# already establish that prefs.lua reads the fixture and not the live file.
|
|
for settings in '__absent__' '' '{ "gapsOut": 24, "windowRounding":' 'gaps_out = 24' '{"gapsOut": "wide"}' \
|
|
'{"gapsOut": 24, "windowRounding": 6, "blurEnabled": false, "borderSize": 0, "keyboardLayout": "us,de"}'; do
|
|
if [[ "$settings" == "__absent__" ]]; then
|
|
rm -f "$work/config/panama/settings.json"
|
|
else
|
|
mkdir -p "$work/config/panama"
|
|
printf '%s' "$settings" >"$work/config/panama/settings.json"
|
|
fi
|
|
output="$(XDG_CONFIG_HOME="$work/config" Hyprland --verify-config 2>&1 || true)"
|
|
grep -q 'config ok' <<<"$output" \
|
|
|| fail "Hyprland rejected the config with settings=<$settings>: $(tail -5 <<<"$output")"
|
|
done
|
|
|
|
trap - EXIT
|
|
cleanup
|
|
printf 'prefs fallback contract: PASS\n'
|