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
Two things stood between this repository and a machine that could
install it.
The installer aborted on its own first question. The hostname prompt
defaults to N, and the N branch ran `exit` -- so pressing Enter, the
obvious answer when you do not want to rename your machine, skipped the
entire installation and said nothing about it. Declining now just
declines. The installer is also safe to re-run, which is the upgrade
path too: it reports which stages failed instead of scrolling the
failure past twenty minutes ago, and restores the idle settings on every
exit path rather than only on success.
The package lists had drifted badly from what the configs and helpers
actually use. jq alone has thirty-one call sites across the helpers and
the contracts; kitty has a full shipped config and a dock pin; tmux and
btop have shipped themes the colour scheme switches; ddcutil, qrencode
and orca back features added today. None were declared. Neither were
fontconfig, pciutils, libselinux-utils, libnotify, wireplumber, fwupd or
python3-dnf, all of which shipped scripts invoke by name. A fresh
machine following this repository's own instructions would have got a
desktop whose features quietly were not there -- the helpers report "not
installed" rather than crashing, which is good behaviour and completely
silent.
So the lists are corrected and a contract now checks that every external
command Panama's scripts invoke is installed by Panama's packages.
Writing it was instructive about its own blind spots. The first version
reported `then`, `esac` and `done` as missing packages, burying the real
findings. The second passed while jq was undeclared, because the pattern
required three characters and jq is two -- a dependency checker with a
blind spot for short names is worse than none, since it reports PASS.
The third missed ddcutil, which is only ever invoked as `timeout 10
ddcutil` and so never appears statement-initial. It now also reads
`command -v X`, which is how these helpers probe for a tool and
therefore the clearest statement of a dependency there is. Verified it
catches jq, ddcutil and qrencode individually.
Also replaced a fixed 0.3s sleep in the write contract with a bounded
wait. It was failing about one run in three with "a rejected value did
not surface an error" when the error had simply not arrived yet, which
reads as a missing guard rather than a slow one.
Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
This contract failed intermittently with "a typed batch did not reach
the compositor", reporting Panama's shipped defaults, and passed on
retry. The earlier guard against a lingering harness was not the cause --
no instance was alive.
Adding the writer's own error to the failure message settled it: the
writer reports NO error while the compositor holds defaults. A rejected
write leaves an error behind; a write that succeeded and was then
overwritten does not. Both this contract and the LIVE shell write to the
same compositor, and the running Panama re-applies its own preferences
on any store change -- landing exactly the values that were being
mistaken for "the write never happened".
So the apply is re-issued periodically while waiting, which makes the
test survive being overwritten without weakening what it asserts, and
the failure message now distinguishes the two cases instead of
describing both as a write that never arrived.
The wait is also 10 seconds rather than 4. The write path verifies every
option by reading it back and retries a refused batch, so a loaded
machine legitimately takes longer -- and this runs in a suite alongside
other tests driving the same compositor.
Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
settings-hyprland-write-contract failed in full suite runs and passed on
its own, reporting "a typed batch did not reach the compositor" with
every value at its default.
Both it and settings-commit-reset-contract drive the same harness file,
and Quickshell identifies an instance by its config path -- not by the
environment it was launched with. So when one run's instance has not
fully exited, the other's wait for `ipc show` is satisfied by that
instance's target, and the whole contract then talks to a shell it did
not start.
The two directions fail differently, and the second is the alarming one:
The write contract lands on the isolated instance, whose compositor
write seam is deliberately stubbed. Its writes go nowhere, which is
exactly the symptom above.
The commit/reset contract lands on the non-isolated instance and
drives the DAILY DESKTOP's real compositor while believing it is
isolated.
Both now refuse to start while another instance of that harness is
alive, and say which hazard they are avoiding rather than failing on an
assertion much later.
One trap worth naming, since it bit me writing this: `rg -c` prints
nothing at all when there are no matches, so an unguarded command
substitution yields "" and not "0" -- the first version of the guard
fired on a perfectly clean machine.
Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
Panama had grown into three configuration surfaces that only agreed because
they had been typed to agree: looks.lua hardcoded values, DesktopPreferences
independently defaulted the same values, and SystemSettings replayed them at
startup. Nothing kept them in sync, and the Lua side read no shared state at
all. This lands the first three stages of docs/superpowers/plans/2026-08-17-panama-cohesion.md.
Fix silently failing Hyprland writes. On a Lua-configured Hyprland, hyprctl
keyword refuses the write, prints the refusal to stdout, and still exits 0, so
the HDR, VRR, and direct-scanout toggles persisted their value and reported
success while the compositor never changed. Writes now go through hyprctl eval,
which has the same hazard on syntax and runtime errors, so success is defined
as reading the value back and finding it equal. The existing contract passed
throughout the outage because it re-applied the values already in place; the
new one flips each value to something it does not hold.
Derive preferences from a schema. Every setting used to be restated four times
-- a property alias, a JSON adapter property, a change handler, and a line in
reset -- where omitting any one failed silently. PreferenceSchema.qml is now
the single source, and persistence, validation, reset, and the Hyprland mapping
all derive from it. Unknown keys on disk survive a write so a rollback does not
discard a newer build's settings, and a corrupt file falls back to shipped
defaults. The store moved to ~/.config/panama/settings.json, migrating from the
old state directory without deleting it.
Share that file with Hyprland. prefs.lua reads it at config time with every
shipped literal kept as the fallback, so the config still stands alone. The Lua
is the default, the JSON is the truth, and Settings is the editor. The
compositor-adjustable surface goes from 3 keys to 23.
Also fixes two test-hygiene bugs found by running the suite end to end for the
first time: settings-pages-contract could see the window settings-window-contract
leaves behind, and the new write contract was persisting its deliberately-wrong
values into the user's real store.
Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L