5 Commits
Author SHA1 Message Date
Gabriel Brown e1faaf7a76 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
2026-08-20 21:55:55 -04:00
Gabriel Brown 79b3d5cb85 Close the sweep's last blind spot, and stop shortcuts silently colliding
gapsIn and gapsOut were the only two compositor settings the write sweep had
never verified: Hyprland answers for them in CSS shorthand, "5 5 5 5", and the
sweep had no way to compare that. The preference behind each is a single int
that Hyprland expands to four sides, so a uniform reading compares exactly. A
non-uniform one is not something the preference can express, and is skipped
rather than collapsed to a number it never wrote. 63 of 63 verified live now,
none skipped.

Wallpaper thumbnails are cached. The report that five of them sat at "Loading…"
was a screenshot taken 1.1 seconds after the page opened -- decoding one of
these at tile size takes between 1.2 and 2.6 seconds and about ten start at
once, which the code already said. Measuring it did turn up something real
though: without a cache, scrolling back up pays that decode again for every
tile. The tradeoff is a wallpaper replaced in place showing a stale thumbnail
until restart, which is worth it for a directory of files that are added rather
than edited.

A chord already in use is now named rather than taken: "Super+Q is already
Terminal". Two actions on one chord means whichever Hyprland reads last wins,
which is not a thing to find out later by pressing it. Rebinding a shortcut to
the chord it already holds is correctly not a conflict.

Also: Open Appearance lands on the Windows tab now that the page has tabs,
Storage points at reclaimable container space, and a dock row shows its desktop
id only when two pinned applications share a name -- it is developer text, and
repeating it under fifteen recognisable names made the list harder to scan.

Written down because it cost the shell: QML has no default parameter values, and
`function openSettings(page: string, section: string = "")` fails the entire
configuration rather than the one function -- so the bar and dock went with it,
and 43 contracts failed at once pointing at the same line. qmllint --bare passes
that, which is why the usual check before touching the running shell did not
catch it. openSettingsSection exists as a separate function for that reason.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-20 09:56:02 -04:00
Gabriel Brown fd99569666 Add a Firewall page, led by what is actually reachable
Listing zones and services is what firewall-cmd already does. The
question it does not answer needs both halves at once: a port is
reachable only when something is LISTENING on a network address AND the
firewall permits it.

On this machine that crossing is the whole story. The rules look
unremarkable -- one zone, three services, a port range -- and what they
mean is that PostgreSQL and Redis, published by rootless containers on
every interface, are reachable by anyone on the network. Neither half
says that alone, which is exactly how a tidy rules list coexists with an
open database. Nothing was misconfigured: Fedora's default zone met
podman's default publish behaviour.

Ephemeral client sockets are excluded. A browser's outbound UDP port is
indistinguishable from a service in ss, and listing twenty of them
buried the two rows that mattered.

Closing the port range names what it would cut off, by service, before
doing it, and removing ssh says so when someone is connected over it.
Rich rules are shown and never edited: a syntax is not a setting, but
hiding it would misrepresent the configuration.

The contract needed a recorded firewall, and the reason is worth
keeping. The rule this page exists for cannot be tested against this
machine -- its zone permits everything above 1024, so "listening" and
"listening and permitted" give identical answers, and a blocked listener
needs a port below 1024, which needs root. With the crossing deleted,
the contract passed. It now runs against a fixture where two listeners
are blocked, and catches it.

Also here: polkit response files are written 0600 rather than at the
default mask, the agent sweeps requests left by an instance that did not
exit cleanly, and the write sweep waits for its harness to be ready
instead of reporting the startup race as settings that failed.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-19 19:46:16 -04:00
Gabriel Brown e0c0e53ae0 Say what the sweep helper is, when run on its own
A suite runner walking the tests directory executes it with no arguments
and gets an IndexError, which reads as a failing test rather than a
helper being used wrongly.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-19 12:22:38 -04:00
Gabriel Brown 180308135a Verify every setting actually does something
The audit that followed "some things in the settings app don't work".

Sixty-five settings reach the compositor and five of them were checked
against it. The rest were covered only by tests that read source text,
which is exactly where a dead setting hides: the write no-ops, nothing
fails, nothing logs, and the row simply does nothing.

The sweep drives each setting through SystemSettings.commitPreference --
the entry point a settings row uses -- flips it to a value it does not
hold, reads it back from the live compositor, and puts it straight back
before touching the next one. Settings Panama stores itself get the same
treatment against the store, since a value that fails to persist is the
same dead row from the outside.

Result: 61 of 63 compositor settings verified against the running
compositor, and 51 stored settings round-tripped. No failures. The
breakage was confined to the Applications page, which is fixed.

Proven able to fail before being trusted: with commitPreference stubbed
to return true without applying, 61 settings are reported; with the
store stubbed to return nothing, 51 are. A one-second settle window
keeps a slow read from being reported as a dead write, which it briefly
was.

Also here: control-center-contract asserted the literal margin
expression that made the panel hang 38 pixels below the bar, so the
contract and the code agreed and the bug was invisible to both. And
settings-page-registry-contract is deleted -- settings-nav-contract
already checked those files and more. It would have caught the Storage
page omission if I had run the suite instead of a hand-picked subset.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-19 12:17:27 -04:00