Commit Graph
10 Commits
Author SHA1 Message Date
Gabriel Brown 8be3fc2fdd Right-click a bar widget to open its settings
Four places in the entire shell could reach Settings. The bar, where a
person looks first, was not one of them -- and Pill has routed
right-click to a secondaryActivated signal all along, which nothing
connected, so the gesture did nothing on every widget in the bar.

Each widget now opens the page that owns its settings: the clock and the
calendar reminder open Date & Time, weather opens Home, the vitals
readout opens Appearance, the status glyphs open Network & Devices, the
media readout opens Sound, and the privacy indicator opens Privacy &
Security. Left-click behaviour is untouched.

Two routing bugs found while picking those destinations, both of the
same kind and both invisible from the code, since each page reads
perfectly well on its own:

  weather routed to Appearance while every weather control lives on
  Home, so searching "temperature unit" opened a page without it.

  vitals routed to Appearance, but the refresh interval sat on Home
  while the toggles it governs sat on Appearance -- one concept split
  across two pages, which is exactly what the ownership rule forbids.
  The interval now sits beside the toggles and Home's stub card is gone.

The jump contract guards the failure mode these share. openSettings()
falls back to Home for an unknown page, sensibly and completely
silently, so a typo or a later rename turns a right-click into "opens
the wrong page" with nothing logged. It also fails a Pill-based bar
widget that leaves right-click unconnected, since that is how the
gesture came to be inert everywhere in the first place.

A third instance of the routing bug is still open: followMouse and
pointerSensitivity sit in the input group, which routes to Keyboard,
while both render on Mouse. Fixing it is a two-line group change in
PreferenceSchema.qml, which codex currently owns, so the contract that
catches all three lands with that fix rather than red.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 14:46:04 -04:00
Gabriel Brown df1dcdfad5 Add effective desktop style controls 2026-08-18 12:57:54 -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 6042c0b1c0 Merge codex's System Health and recovery work
Brings in panama-doctor (a 25-check diagnostic with fixture-backed
contracts), a Health service, a System Health page replacing Startup &
Services, and a bar indicator that stays absent until something is
actually degraded. All seven of its contracts pass on the merge.

Three things needed resolving rather than accepting:

The branch predates the debranding, so its user-visible strings still
named the product -- "Panama desktop is healthy", "Restart Panama",
"Panama tools". Rewritten to say the same thing without the name, which
is what the rest of the app now does.

Its Fedora hand-off card was a single button calling openGnomePanel
("network") under a subtitle naming five subjects. Main had already
replaced that with a row per subject, each opening the panel that owns
it, so those rows are ported into HealthPage instead. Printers and
online accounts stay on Network & Devices with the rest of the network
hardware.

That broke its own assertion, which matched the literal
openGnomePanel("network") string. Rewritten rather than reverted: it now
checks the boundary card exists and that every panel named in HealthPage
is one openGnomePanel actually allows, since a name outside the
allow-list opens nothing at all. Verified it catches a plausible-looking
wrong name.

SettingsShell and SettingsSidebar conflicted because both sides added
pages; resolved as the union, keeping its System Health page and live
footer alongside main's Mouse & Touchpad, Privacy & Security, Region &
Language and Online Accounts.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 11:20:06 -04:00
Gabriel Brown 3238623934 Build the System Health settings page 2026-08-18 09:50:15 -04:00
Gabriel Brown b1f9891849 Add multitasking controls, and a light theme for the launcher
Two things, both closing gaps in work that was already reported done.

GNOME's Multitasking panel, in Hyprland's terms: tiling layout, split
behaviour, floating-window snapping, workspace wrap-around and
back-and-forth, whether applications may take focus, and whether the
pointer changes the active display. Hyprland creates and destroys
workspaces as you use them, so there is no fixed count to expose, and
the page says so rather than leaving a conspicuous absence.

The Desktop page described the first two of these as read-only facts --
"Layout: Tiling", "Workspace movement: Dynamic" -- which was never true.
Both are ordinary Hyprland options that happened to have no controls,
and TextRow's own documentation says a setting the user could reasonably
change does not belong in it. Schema defaults are Panama's shipped
values from looks.lua rather than Hyprland's own, so restoring defaults
returns the desktop to how it ships. 43 mapped options now, from 35.

The launcher had no light theme. vicinae.json already selected a theme
per system appearance, but both entries pointed at Moon, so choosing
light mode left the most frequently opened window on the desktop dark --
a hole in the light/dark work, not a missing feature. Day is authored
from the same palette as the kitty Day theme so the two cannot drift,
and link-dotfiles now installs every authored theme rather than only the
dark one, which is why the gap survived being noticed.

Its placeholder colour is not Tokyo Night Day's own: that measures
2.54:1 against the background, below the 3:1 floor for secondary text.
This is 3.25:1, the same value used for neovim's light comments.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 08:34:05 -04:00
Gabriel Brown 9ce7040b91 Add a Mouse & Touchpad page and make keyboard layout editable
Working towards parity with GNOME Settings, which splits pointing
devices into their own panel. Panama had pointer speed and focus-follows
buried under a page called "Input & Shortcuts", and had nothing at all
for scroll direction, acceleration profile, scroll speed, left-handed
buttons, or any touchpad setting -- all of which could only be changed
by editing hypr/input.lua by hand, which is the thing this app exists to
stop.

Every new mapping was read back off the running compositor rather than
assumed, and two were not what they look like: touchpad drag lock is an
int with three states, not a switch, and scroll factors are floats even
at their default of exactly 1. Getting either wrong makes every write to
that setting look rejected. The shape contract now covers 35 mapped
options, up from 23.

The touchpad card renders only when a touchpad is attached, which is
what InputDevices is for. On a desktop it would be worse than useless:
every switch on it would appear to work, because the preference is
stored and Hyprland accepts an option for a device class it has no
member of, so the settings would silently affect nothing.

Keyboard layout was read-only text, justified by a note saying changes
needed a compositor reload. That is not true in 0.56.2 -- setting
input:kb_variant through hl.config re-keymaps attached keyboards
immediately, verified by watching active_keymap on a real keyboard
change to "English (US, intl., with dead keys)" and back. So layout,
variant, and options are now real controls, joined by a TextEntryRow
that commits on Enter or focus loss rather than per keystroke, since
half a layout name is a valid string meaning something else.

Rejected input is shown as rejected rather than sanitised: these strings
are serialised into an hl.config payload, where stripping an unexpected
character would turn a typo into a different working setting.

Verified each new pointer option applies and reverts against the live
compositor. Schema, search, commit/reset, and system contracts pass.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 08:16:10 -04:00
Gabriel Brown 94353aa0bd Close the gaps the cross-UI audit found
Audited bar, dock, quick settings, date menu and Settings for three
things: a setting reachable in one UI but not another, a setting that
exists but is unreachable anywhere, and UI that states something false.

Night Light was fully exposed in Quick Settings and had no control
anywhere in Settings. It now has a card on Displays, where GNOME also
puts it, with on/off, schedule, times and temperature.

Adding those controls would have shipped the exact defect this audit
exists to find. NightLight declared enabled, temperature and automatic
as bindings on the store, but toggle() assigns to them, and an
assignment destroys a QML binding permanently -- so the service wrote to
the store and never read from it again. The Settings controls would have
written values the service ignored, while Quick Settings kept working.
It now follows the store. Every other service was swept for the same
pattern; this was the only one.

The night light schedule was two hardcoded literals, so the hours could
not be changed. They are schema keys now, with a row that renders 17.5
as "5:30 PM" and honours the 24-hour preference rather than showing a
decimal nobody reads as a time.

keyboardLayout was in the schema and read by input.lua but had no
control anywhere: configurable in principle, unreachable in practice. It
is surfaced on Input & Shortcuts as read-only, with the reason, because
it needs a compositor reload and a control implying instant apply would
be a smaller lie but still a lie.

Caffeine was a Quick Settings toggle mentioned only in a subtitle in
Settings. It has a real control now.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 06:25:15 -04:00
Gabriel Brown 6377bfb8fd Route the Applications page and add its settings
Wires the schema entries and routing the codex agent needs for default
applications, weather, vitals refresh, notification timing, and capture,
so its pages can be written against keys that already exist.

Capture directories and encoder arguments are enums rather than free
text. Both are handed to a recorder process, and a settings page has no
reason to expose an arbitrary string there.

ApplicationsPage.qml is a placeholder so the page id can be routed,
registered, and searchable before the real page lands. It is owned by
the other agent and expected to be replaced wholesale.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 00:51:10 -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