# Settings The control centre for everything Panama owns. Anything the system owns — hardware, accounts, printers — is delegated to GNOME Settings and labelled as such rather than half-reimplemented. ## Adding a setting One schema entry. That is the whole job. ```qml // config/PreferenceSchema.qml { key: "blurSize", type: "int", def: 8, min: 1, max: 20, step: 1, unit: "px", group: "effects", label: "Blur radius", detail: "Larger is softer and costs more frame time", hypr: { path: ["decoration", "blur", "size"], option: "decoration:blur:size", readAs: "int" } } ``` ```qml // the page SliderRow { setting: "blurSize" } ``` Persistence, validation, clamping, reset, search indexing, and — with a `hypr` block — live application to the compositor and the startup replay all derive from that entry. There is nothing else to register. If it is compositor-backed, add the matching `prefs.get("blurSize", 8)` in `hypr/looks.lua` so the Hyprland config still stands alone with no settings file. ## The rows | Component | For | |---|---| | `SettingsPage` | The page scaffold: title, lede, optional pinned `header` | | `ToggleRow { setting }` | A boolean | | `SliderRow { setting }` | A number; `zeroLabel` renders 0 as "Never"/"Instant"/"None" | | `ChoiceRow { setting }` | An enum, as a segmented control | | `ActionRow` | A button: opens a GNOME panel, runs a one-shot | | `TextRow` | A genuinely read-only fact | `TextRow` is for facts, not for settings that were merely expensive to wire. Before Stage 3 more than half of all rows were static text standing in for controls; that is the failure this vocabulary exists to prevent. Rows write through `SystemSettings.commitPreference(key, value)`, which routes compositor-backed keys through apply-and-verify and local keys straight to the store. A row never needs to know which kind it holds. ## Things that will bite you **`readAs` describes the answer, not the setting.** `hyprctl getoption` returns the value in a different JSON field per type — `int`, `bool`, `float`, `str`, and `css` for gaps (a four-value box). Declaring the wrong one does not fail loudly: it makes every write to that key look *rejected*, and the user sees an error for a change that worked. `tests/quickshell/schema-hypr-shape-contract.sh` asks the compositor for the real shape of every mapped option. **Never trust an exit code from `hyprctl`.** `keyword` refuses to work on a Lua-configured Hyprland, prints the refusal to stdout, and exits 0. `eval` exits 0 on syntax and runtime errors too. The only trustworthy signal that a write landed is reading the value back. **The Settings window is tiled.** `implicitWidth` is a hint; the layout decides, and it ranges from a half-screen split to the full display. `SliderRow` stacks its control under the label below 520px. Test narrow. **Binding an anchor to `undefined` does not reliably release it.** Switching layouts that way left a slider anchored to both edges with the label squeezed into what was left. Position explicitly instead. **Inside a `SettingsCard`, `parent` is the card's internal Column.** So `parent.modelData` in a nested `Repeater` is undefined and the rows silently never appear — you get a card with a heading and nothing under it. Address the outer model through an explicit `id`. **A `TapHandler` declared as a child of `SettingRow` lands in the trailing slot**, because that is the row's default property, so only the right-hand edge becomes clickable. Use `activatable: true` with `onActivated` for a whole-row target. **A copy of the Quickshell config shares the live shell's ID.** Quickshell derives the Shell ID from config *content*, not path, so `cp -a config/dot/quickshell $tmp && qs -p $tmp kill` kills the running desktop, and `qs -p $tmp ipc call …` can drive it. Harnesses that point at a single distinct `.qml` file are safe; copying the whole directory is not. ## Where state lives | File | Holds | |---|---| | `~/.config/panama/settings.json` | Everything in the schema. Read by the shell *and* by `hypr/prefs.lua` | | `$XDG_STATE_HOME/panama/panama-home.json` | Home accessory favourites and aliases | | `$XDG_STATE_HOME/panama/backups/` | Settings snapshots | | `$XDG_STATE_HOME/panama/hypridle.conf` | Generated idle config | `SystemSettings.restoreDefaults()` spans all of them. A reset that silently skipped one would be worse than having no reset, because nothing would say so. ## Not stored by Panama Timezone and network time are read from and written to `timedatectl` directly. They belong to the machine and are shared with sessions that never see Panama's file; storing a copy would create a second answer to a question the system already answers.