27af4fd4434bb584e4dce0d546eb5cc119405372
17
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
52d896b054 |
Add an Online Accounts page
GNOME Online Accounts is a daemon plus a D-Bus API, and the daemon already runs in this session -- gvfs activates it, and all four accounts on this machine work without gnome-shell involved anywhere. Only the PANEL was GNOME's. The accounts themselves are ordinary D-Bus objects that anything may read and modify. So everything except the initial sign-in is now native: the account list, per-service toggles for mail, calendar, contacts, files, photos, music and chat, and removal. That is the whole Online Accounts panel apart from one OAuth handshake. Signing in is the exception, and only for OAuth providers. The daemon's AddAccount takes credentials as an argument -- it stores them, it does not obtain them -- and the code that runs Google's OAuth exchange lives in libgoa-backend, which Fedora ships without a GIR binding, so it is reachable from C only. Reimplementing it would mean our own Google client credentials. That step is handed to GNOME's panel and the page says so, because a hand-off the user does not expect reads as a bug. Password-based providers (Nextcloud, IMAP, WebDAV) could be added natively later; their credential keys are known now. Accounts needing re-authentication are surfaced first, which turned up something immediately: both Google accounts on this machine report attention_needed, meaning their tokens have expired and they have stopped syncing. GOA has known that all along and nothing outside its own panel ever said so. Every write re-reads the account list rather than assuming it landed. GOA can refuse, and a toggle that springs back is the honest outcome. Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L |
||
|
|
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
|
||
|
|
14529c011f |
Add a Privacy & Security page
Continuing towards GNOME Settings parity. GNOME's Privacy panel covers screen lock, camera and microphone access, file history, trash, and device security; Panama had no equivalent page at all, despite already tracking camera and microphone use for the bar indicator. Device security is a new read-only readout: Secure Boot, TPM, disk encryption, SELinux mode, and the firewall. None of these is a preference -- they are set in firmware, at install time, or by system policy, and a switch offering to change them would either fail or do something far-reaching from a control that looks like every other control. What it answers is "is this machine set up the way I think it is", which otherwise takes five commands and root. Facts that cannot be determined report Unknown rather than guessing, because a security readout that quietly says "fine" when it failed to look is worse than no readout. File history and trash retention are deliberately NOT offered as switches. They are GNOME preferences enforced by gsd-housekeeping, which does not run in a Hyprland session -- verified, it is not running here. Toggling them would store a preference, change nothing, and give no sign of it. They are delegated to GNOME Settings by name instead. Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L |
||
|
|
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 |
||
|
|
d1b2cd2083 |
Make typography choosable instead of hardcoded
Theme.qml was the largest remaining thing in this desktop that could only be changed by editing a file, and typeface is the first thing someone changes when they want a desktop to feel like theirs. Interface font, icon font, and the base text size are settings now. The two font choices are deliberately separate lists. Theme.fontMono is used only to draw glyphs -- workspace pills, the status cluster, search icons -- so a plain monospace family there replaces every icon in the shell with tofu. The picker offers only Nerd Fonts for that slot and says why. Candidates are rendered in the family they name. A list of font names set in the current font tells you nothing about what you are choosing. The four type sizes derive from the base rather than being stored separately, so the relationship between body, caption, heading and title survives a change instead of four numbers drifting apart. hypr/looks.lua reads the same key. Following the preference only on the QML side would leave the compositor and the shell disagreeing about the interface font, which nobody notices until a tooltip renders in a different typeface. Only a family this machine reports is accepted: the value reaches hl.config as a string, and a settings file moved between machines will name fonts that are not installed. A missing family is reported rather than silently substituted by fontconfig. Also collapses the wallpaper grid to two rows. Sixty tiles is two screens of pictures on a page that also holds typography, window geometry and effects -- everything below it was unreachable without scrolling past all of them. Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L |
||
|
|
d18fe51553 |
Make the weather location and graphics device choosable
The last two values that could only be changed by editing a file. Weather was pinned to hardcoded coordinates, so the card could not be pointed anywhere else. It is a location search now, not latitude and longitude fields: nobody knows their own coordinates, and a control that demands them is one nobody uses. Open-Meteo's geocoding endpoint needs no key, the same reason the forecast already uses them. Only the search term leaves the machine -- the stored place name is a label -- and coordinates are rounded to four decimals, far finer than a weather reading resolves and coarse enough to keep a precise home location out of the settings file. The graphics readout was hardcoded to card1. This machine has two amdgpu cards, discrete and integrated, so that was right only by luck, and the path is meaningless on any other machine. GPUs are enumerated with a readable name from lspci, since sysfs exposes only numeric ids, and the picker appears only when there is more than one to choose between. A stored path the machine does not have is refused and reported rather than silently measuring nothing. Also merges the per-application notification rules UI. Its three commits were believed integrated but the page half was not actually in the tree: main had the service side in Notifs.qml and zero references to setAppRule in NotificationsPage. Ancestry is not content. Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L |
||
|
|
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 |
||
|
|
81096e2d95 |
Declare the Sound and notification-rule manifest entries
Registers the components and schema key the codex agent needs for the Sound page and per-application notification rules, so its branches compile against a manifest that already holds them rather than each carrying a conflicting edit to the same file. An absent notification rule is permissive rather than denying: a newly installed application must be able to notify without an entry being written for it first. Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L |
||
|
|
3c521cf5fa |
Handle Wi-Fi and Bluetooth in Settings
Network & Devices was 92 lines and two buttons that opened GNOME. It now scans, joins, and pairs directly through Quickshell.Networking and Quickshell.Bluetooth -- NetworkManager and BlueZ over DBus, no shelling out to nmcli or bluetoothctl. That was the founding requirement for this desktop: never having to drop to a terminal to join a network. Scanning follows the page being visible. Wi-Fi scanning and especially Bluetooth discovery hold the radio, and running either for a list nobody is looking at spends airtime on nothing. Joining a secured network gets a real password field, not the clipboard popover's search box with different placeholder text: a Wi-Fi key typed into a field that echoes it is readable by anyone behind you, and a search glyph in front of a password prompt is simply wrong. Two bugs found by looking at the rendered page, both silent: The device lookups used enum names that do not exist -- NetworkDeviceType.Wifi rather than DeviceType.Wifi -- so both returned null and the page reported "No Wi-Fi adapter" on a machine whose Wi-Fi was connected. Nothing was logged; QML resolves an unknown enum member to undefined and compares happily. signalStrength is 0.0-1.0, not a percentage, so thresholds written for 0-100 put every network including the connected one in the bottom bucket. The labels now use the same buckets as the icons in quicksettings/WifiList.qml so the two cannot disagree. The contract compares what the service resolves against what nmcli reports, rather than only checking that nothing crashed. Also makes the Home Assistant bridge hermetic: resolve_config read the user's private env file even when a caller supplied an explicit environment, so adding a real PANAMA_HOME_ASSISTANT_ENTITIES to that file silently overrode a fixture asserting the legacy fallback. An explicit environment is now the whole environment; production still reads the file. Its live contract skips when no token is configured -- an absent credential is not a defect, and a suite expected to be red stops being read -- while a configured-but-broken bridge still fails. Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L |
||
|
|
5671324eb2 |
Make displays configurable, with a revert countdown
Resolution, refresh rate, scale, and rotation, applied through
hl.monitor{} and stored per output.
This is the only setting in Panama where a wrong value can leave the
user unable to SEE the screen well enough to undo it: a mode the panel
cannot show, or a scale that makes everything unreadable, is not
recoverable through the UI that caused it. So a change is never applied
irreversibly. It is applied, then reverted automatically after fifteen
seconds unless confirmed, and confirming is what writes it to the
settings store -- letting the countdown run leaves nothing behind.
The contract tests that property specifically: it applies a scale, waits
out the countdown, and asserts the display came back and that nothing
was stored. A regression there is not a broken feature, it is a user
staring at a blank monitor.
Modes are grouped by resolution with refresh rates beside them. The
panel reports 35, many differing only in refresh-rate rounding -- 60.00
and 59.94 -- which as a flat list of buttons is noise rather than
choice; equal rounded pairs collapse, leaving 21.
Only mode, scale, and transform are configurable. Colour management and
bit depth stay in monitors.lua because they carry a documented screencopy
tradeoff that a settings page cannot explain at the moment you would be
changing it.
Also replaces the display policy rows with the schema-bound ones, so the
page no longer restates labels that PreferenceSchema already holds.
Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
|
||
|
|
634a9ebe07 |
Let shortcuts be rebound from Settings
Every bind in keybinds.lua now goes through a small wrapper that substitutes the chord from a stored override. Only the chord is taken from settings; the action is always the Lua value written in that file, so an override can move a shortcut but can never make one do something else. That is the property that makes reading them from a file the user can edit safe, and it is why the alternative -- storing dispatchers -- was not considered. Overrides are keyed by the shipped chord rather than the description. Keying by description moved every bind that shared one: rebinding SUPER+C also moved the XF86Calculator hardware key onto the same chord, silently costing it. Chords are unique; descriptions are not. Applying needs hyprctl reload rather than a live hl.bind. Hyprland reports Lua-defined binds with dispatcher "__lua" and a bytecode offset, so the action cannot be reconstructed from outside to re-bind it; reload re-runs the config, which re-reads the settings file. The capture control ignores modifier-only presses, because every chord passes through them and holding Super would otherwise be captured the moment the modifier went down. It refuses a bare letter, which would swallow ordinary typing, and refuses a key with no keysym name rather than storing something that would fail to bind. Rebinding onto a chord already in use is refused rather than shadowing the existing shortcut. The refactor was verified by snapshotting all 113 binds before and after: the keymap is byte-identical, and identical again after applying an override and resetting it. Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L |
||
|
|
150f3cdb09 |
Make the Dock editable and add settings snapshots
The Dock's pinned applications were a sixteen-entry literal in Settings.qml, so changing what sits in the Dock meant editing QML. They are now an ordered list in the shared store, with move up, move down, unpin, and a filtered picker for adding installed applications. Keeping them in the shared store rather than a file of their own means they are covered by Restore defaults like everything else. This needed a "json" schema type for values the schema stores and resets but does not validate field by field. It exists so structured settings can live in the one file rather than growing a fourth preference store; the owning service validates the contents. Snapshots make the settings app safe to experiment with. The whole configuration is one file, so a backup is a copy and a restore is an overwrite, and restoring snapshots what it replaces so it is itself undoable. A snapshot is validated as JSON before it can be restored over a working configuration, and a name that is not a plain snapshot filename from the backup directory is refused. Snapshot names carry milliseconds. At one-second resolution a save followed promptly by a restore produced the same filename twice, and the restore's own safety snapshot overwrote the file it was about to read -- found by the contract, which restores immediately after saving. Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
65966200fe | Add Home and Phone settings | ||
|
|
5248883e4b | Build the Panama Hyprland desktop |