Commit Graph
19 Commits
Author SHA1 Message Date
Gabriel Brown 06c53d6c21 Shortcuts you invent, rules you write, gestures you own - all still just data
Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
2026-08-25 01:51:59 -04:00
Gabriel Brown 8b1205e4b8 Tier 0: render what the services already decided, honestly
Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
2026-08-25 00:24:30 -04:00
Gabriel Brown be0e55214b Finish the wonderland: System told truthfully, in eight tabs instead of ten
Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
2026-08-24 23:31:52 -04:00
Gabriel Brown 50077a0c31 Fix the second dead settings button, and pin the class shut
A sweep for controls wired to targets the backend does not handle -- the shape
of the dictation bug -- found one more: "Start Orca" on the Accessibility page
called openApplication("orca") with no "orca" in the command map, so it set an
error and launched nothing. orca ships in hyprland-packages; it now runs.

That is the whole count. Every service-method call across 84 settings pages
resolves, every helper subcommand a service invokes is implemented, every
openGnomePanel handoff is allow-listed. Two dead buttons existed in the entire
settings app -- the dictation Download and this -- and both are fixed.

settings-buttons-contract pins the class: every openApplication id and
openGnomePanel panel a QML button passes must be present in SystemSettings'
dispatch maps, both of which return false silently on an unknown name.
Verified it catches the orca button when the fix is reverted.

And a dependency-contract exception the ffmpeg fix needed: ffmpeg is provided
by a swap (ffmpeg-free -> ffmpeg), never a list entry, because listing it is
the conflict that fix removed -- so panama-transcode's use of it is satisfied
without a package name to point at.

Claude-Session: https://claude.ai/code/session_01Epx9ZC1gwm81K3jm9x9CKh
2026-08-23 14:11:15 -04:00
Gabriel Brown 2fcaada7e8 Snapshot automatically before restoring defaults
Restoring defaults clears every preference and the Home accessory store,
and nothing in the app can undo it. Snapshots existed but were entirely
manual, so the one irreversible action Panama offers was also the one
with no safety net.

It now snapshots first. Not fatal if that fails: someone who asked to
reset gets their reset, and a snapshot that could not be written is
reported rather than allowed to block what they asked for.

The wiring is inverted deliberately. SettingsBackup already references
SystemSettings, so referencing it back would make two singletons depend
on each other, which is an initialisation-order problem waiting to
happen. Instead SystemSettings exposes a seam defaulting to a no-op and
SettingsBackup installs itself into it at startup -- the same shape as
the seams the reset path already uses for test isolation.

The contract asserts the snapshot is FIRST in the call sequence, not
merely present. A snapshot taken after the stores were cleared would
faithfully record the wiped state as the user's own, which is worse than
no snapshot: it looks like a safety net and is a copy of the damage.
Verified against both mutations -- removing the snapshot, and moving it
after the wipe.

One trap, hit for the third time today: QML allows only one
Component.onCompleted per object, and SettingsBackup already had one.
Adding a second does not fail locally -- it poisons the entire services
module, so every singleton reports "Type X unavailable" and the real
error is the last line of a forty-line cascade.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-19 08:37:43 -04:00
Gabriel Brown d96863b687 Convert British spellings to American across the repo
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
2026-08-19 08:07:55 -04:00
Gabriel Brown d2898d8806 Integrate complete display layouts 2026-08-18 15:00:18 -04:00
Gabriel Brown 3e98cc2216 Integrate managed lock screen recovery 2026-08-18 15:00:18 -04:00
Gabriel Brown ccf46c40ef Expose 19 more compositor options that only looks.lua could reach
Measured the gap first: of the 38 real Hyprland options Panama's own Lua
sets, only 16 were editable in Settings. Everything else required a text
editor, which is the thing this app exists to stop. This closes most of
that: 66 mapped options now, from 47.

Window shape and shadows on Appearance: corner shape (rounding_power),
focused and fullscreen opacity, shadow falloff and hard-edged shadows.
Window edges, master layout and Hyprland's own notices on Desktop & Dock.

Three of these are corrections rather than additions.

Master layout options existed nowhere, while Settings has offered "Master
and stack" as a choice since this morning -- a layout you can select and
cannot configure is barely a choice. Its card is hidden unless that
layout is actually selected, since settings that do nothing under the
layout you are running are worse than not offering the layout at all.

The four Hyprland notices -- logo, splash, update news, donation nag --
are all turned off by looks.lua on the user's behalf. Defensible as a
default, but not a decision anyone could reverse. They are stored
positively ("show this") and written as Hyprland's `disable_*` through a
new `invert` flag, because a switch labelled "Disable splash text" that
must be ON to hide something is a small cruelty. The Lua does the same
inversion so both sides agree.

Everything new also reads from prefs in looks.lua. Without that these
would apply live and silently revert on the next compositor reload,
which is the failure this codebase keeps designing against.

Two shapes the write path had never seen. Border colours are gradients
and shadow offsets are vec2, and the verifier understood neither -- it
returned false for anything outside int/bool/float/str/css, so both
would have reported every write as rejected. Gradients also need real
care: the stubs declare them as `string|{colors,angle}`, and the string
form carries only ONE stop, so writing "rgba(a) rgba(b) 45deg" as a
string is accepted and keeps the previous value. Verified that directly.
They are also written in one notation and read back in another
(`{colors={"rgba(3b426199)"},angle=45}` becomes `993b4261 45deg`), so
comparison normalises both sides.

Border COLOUR is deliberately not exposed yet. col.inactive_border is
written by ColorScheme on every scheme change, so a user's choice would
be silently overwritten, and col.active_border is the Prism gradient,
which needs a colour control this app does not have. Shadow offset is
left out for the same reason -- the vec2 support is in place for
whenever the widget exists.

Verified each new option applies and reverts against the live
compositor, and that the schema, enum-map, nav, write and commit/reset
contracts all pass.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 12:27:34 -04:00
Gabriel Brown 6026bf308b Add Region & Language, and answer "what am I running on" in About
More GNOME Settings parity.

Region & Language is new. The locale is localectl's, and Panama stores no
copy of it -- there is exactly one system locale, so a preference here
would be a second source of truth that drifts the moment anything else
changes it. Codes are resolved against iso-codes into "Portuguese
(Brazil)" the way GNOME does, with the code kept visible because it is
what actually gets written and someone choosing between two Spanish
variants needs to see it. Changing it is privileged and only applies to
programs started afterwards, so the page says a sign-out is needed
rather than claiming the new language is in use.

The service is called SystemLocale, not Locale: QML has a built-in
Locale value type that silently shadows a singleton of that name, and
every binding then reads properties off the wrong thing. The page
rendered empty with nothing but "cannot read property of undefined" to
explain it.

About now answers what GNOME's About answers -- model, processor,
memory, disk, OS, kernel, windowing system -- where before it listed
only Panama's own component versions. Graphics is joined from
GraphicsDevices rather than read again, because two readouts of the same
hardware are two things that can disagree. Placeholder DMI strings
("To Be Filled By O.E.M.") are filtered out, and unreadable facts are
omitted rather than shown as "Unknown".

The Fedora hand-off card was one row listing five subjects that opened
the network panel regardless. Naming a panel and then not opening it
reads as a broken button rather than a deliberate hand-off. Each subject
now opens the panel that owns it, and openGnomePanel takes an optional
subpage so "Users" reaches System's users page the way GNOME's own
desktop entry does, instead of dropping the user on System's front page.
Printers and online accounts are not repeated here; they stay with the
network hardware on Network & Devices.

One bug this surfaced, caught by the hyprland write contract: the Lua
config key and the hyprctl option name genuinely differ for tap to
click. hl.config wants input.touchpad.tap_to_click; getoption answers to
input:touchpad:tap-to-click. Either spelling used for both fails -- a
hyphen is not a Lua identifier, and the underscored name is not a known
option -- which is what the schema's two separate fields are for.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 08:30:21 -04:00
Gabriel Brown 7b8270fdd6 Keep Settings recovery isolated and display-safe 2026-08-18 03:19:45 -04:00
Gabriel Brown 6d3f888784 Protect live state during restore and reset 2026-08-18 03:10:31 -04:00
Gabriel Brown 787d2b121a Use the durable Home reset boundary 2026-08-18 01:39:34 -04:00
Gabriel Brown 2bc12e6022 Add wallpaper, power, date, accessibility, and real search
Continues the settings expansion toward replacing GNOME Settings for
everything Panama actually owns.

Wallpaper. A thumbnail grid rather than a path field: the value of this
setting is the picture, so typing a path to something you cannot see is
the worst version of it. Two things about hyprpaper 0.8 shaped this. Its
IPC is much smaller than older documentation suggests -- preload,
listloaded, unload, and reload all answer "invalid hyprpaper request",
so setting is a single call with no preload. And the "<empty>,<path>"
form that used to mean every output is silently ignored, so a wallpaper
set that way appears to succeed and never changes; outputs are walked
explicitly instead. hyprpaper.conf lives in the repo through the
~/.config/hypr symlink and so cannot hold machine state, which is why
the choice lives in the shared settings store and is re-applied at
startup.

Power & Lock. hypridle has no IPC for reconfiguration and its config is
hyprlang rather than the shared JSON, so scripts/panama-idle generates a
config from the settings store and restarts the daemon. The generated
file lives under XDG_STATE_HOME for the same symlink reason, with a
systemd drop-in pointing hypridle at it. Management is a real state and
the page says which one you are in rather than showing sliders that
quietly do nothing. Zero means never for all three timers, which a naive
template would render as "immediately".

Date & Time. Deliberately not stored in Panama's settings: the timezone
and network time belong to the machine and are shared with sessions that
never see this file. Storing a copy would create a second answer to a
question the system already answers. Reads and writes timedatectl
directly; a cancelled polkit prompt surfaces as an error rather than as
a value that appears to have been accepted.

Accessibility. Pointer size and text scale have to agree across three
consumers with no shared configuration -- the compositor, GTK, and the
shell -- so the store is the source of truth and the values are pushed
outward to gsettings and hyprctl setcursor.

Search now indexes the schema instead of the twelve page labels. "gaps",
"wallpaper", and "screenshot" previously found nothing on an app that
has all three, which is the clearest way a settings app feels smaller
than it is. Shortcuts are indexed by what they do. A contract asserts
every non-internal schema label is reachable, so a new setting cannot be
added in an undiscoverable state.

The GNOME delegation allow-list was widened to the panel names
gnome-control-center actually reports; the previous list contained
"users", which is not one of them and so opened nothing.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 00:42:13 -04:00
Gabriel Brown fe7c85e471 Build the settings vocabulary and generate the keymap
Stage 3 and 4 of docs/superpowers/plans/2026-08-17-panama-cohesion.md.

Add SettingsPage plus ToggleRow, SliderRow, ChoiceRow, ActionRow, and
TextRow. A row names a schema key and needs nothing else: label, detail,
range, and unit come from PreferenceSchema, and writes go through
SystemSettings.commitPreference, which routes compositor-backed keys
through apply-and-verify and local keys straight to the store. The page
scaffold that was copy-pasted eleven times is now one component.

Rebuild Appearance around a live preview of the real desktop, scaled by
the ratio between the preview and the actual monitor so a 10px gap on a
4500px display looks as small as it is. Rebuild Desktop & Dock and Input
& Shortcuts on the shared rows, replacing the read-only text that stood
in for controls that were merely expensive to add.

Generate the shortcut list from hyprctl binds. The page held a
hand-typed nineteen entries against a real keymap of a hundred and
thirteen; it could not show the rest and went stale whenever a bind
changed. Every bind now carries its own description -- backfilled for
the twenty-nine that lacked one -- and keybinds-contract.sh fails if any
bind lacks one, since undescribed binds are dropped from the page.

Make Restore defaults span every store Panama owns. Resetting only the
schema store left the Home accessory arrangement customised while
claiming to restore defaults, which is worse than no reset because it is
silent. Done through HomePreferences' existing public aliases rather
than a new API.

Four defects found while building:

cursor:inactive_timeout is answered by getoption as float, not int. A
wrong readAs does not fail loudly; it makes every write to that key look
rejected, and the user saw an error for a change that worked.
schema-hypr-shape-contract.sh now checks all 23 mapped options against
the running compositor.

The Settings window is tiled, so implicitWidth is only a hint and rows
must survive roughly 400px. SliderRow stacks its control under the label
below 520px.

Binding an anchor to undefined to switch layouts does not reliably
release it. Both row layouts are positioned explicitly.

Concurrent compositor writes are queued and merged rather than refused.
The startup replay of every compositor-backed preference routinely
overlaps a UI change, and refusing left the store and the compositor
disagreeing.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 00:24:51 -04:00
Gabriel Brown 5347954a88 Merge branch 'feat/home-accessories-customization'
# Conflicts:
#	config/dot/quickshell/config/qmldir
#	config/dot/quickshell/services/SystemSettings.qml
#	tests/quickshell/settings-pages-contract.sh
2026-08-17 23:31:33 -04:00
Gabriel Brown 00a81edadd Make Panama settings one shared source of truth
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
2026-08-17 23:26:56 -04:00
Gabriel Brown 65966200fe Add Home and Phone settings 2026-08-17 17:48:05 -04:00
Gabriel Brown 5248883e4b Build the Panama Hyprland desktop 2026-08-17 10:32:55 -04:00