Settings had a search index and the launcher had script commands, and neither knew about the other: finding a setting meant opening Settings first and searching there. One command per page is now generated from the same sources the in-app search reads -- the sidebar page list, the group routing, and the schema labels -- so a setting searchable inside Settings is searchable from the launcher without a second list to maintain. Generating it surfaced a gap in the in-app search as well. Six pages had no vocabulary at all, because their contents come from the system rather than our schema, so searching "volume" found nothing in Settings either. They now carry entries, which improves both surfaces. The product prefix is dropped from every hand-written command title. The generated ones are qualified "Settings: <page>" instead: a bare page label collides with the feature of the same name, and two commands sharing a title are indistinguishable in a launcher. Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
182 lines
8.6 KiB
Markdown
182 lines
8.6 KiB
Markdown
# Settings
|
|
|
|
The control center for everything Panama owns. Anything the system owns —
|
|
hardware, accounts, printers — is delegated to GNOME Settings and labeled as
|
|
such rather than half-reimplemented.
|
|
|
|
## System Health
|
|
|
|
The stable internal `services` route renders **System Health**. It is reachable
|
|
from the Settings sidebar and its live 54px footer, the degraded-only bar
|
|
indicator, and Vicinae's **Check System Health** command. Healthy scans
|
|
reserve no bar space and produce no notification.
|
|
|
|
`services/Health.qml` owns the last accepted redacted snapshot. It invokes
|
|
`scripts/panama-doctor` for scans and bounded repairs, `wl-copy` only for an
|
|
explicit **Copy Report**, and bounded `notify-send` only when an external repair
|
|
fails. For a concise terminal view, run:
|
|
|
|
```bash
|
|
~/.config/quickshell/scripts/panama-doctor --summary
|
|
```
|
|
|
|
The helper diagnoses Panama-owned desktop services, dependencies, links, and
|
|
configured integrations. It does not read secret values, clipboard or
|
|
notification contents, calendar events, SSIDs, addresses, or arbitrary command
|
|
output. Its repair interface is an authored allow-list: it never installs a
|
|
package, runs `sudo`, deletes user data, or repairs a service Panama does not
|
|
own. A repair remains degraded until a fresh scan observes recovery.
|
|
|
|
The final card is the ownership boundary. Network configuration and the exact
|
|
Users, Sharing, Color profiles, and Digital wellbeing handoffs open GNOME
|
|
Settings because Fedora's system services own those areas.
|
|
|
|
## 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.
|
|
|
|
## Setting ownership
|
|
|
|
Every preference has **one primary page**, derived from its schema `group` and
|
|
the route in `services/SettingsSearch.qml`. Search results always open that
|
|
owner. A control may appear on a second page only when the same adaptation is
|
|
part of another established mental model; otherwise use a labeled handoff to
|
|
the owner instead of duplicating it.
|
|
|
|
### Intentional mirrors
|
|
|
|
| Setting | Primary page | Mirror | Why the mirror earns its place |
|
|
|---|---|---|---|
|
|
| `animationsEnabled` | Appearance | Accessibility | Reduced motion belongs both to visual polish and motion accessibility. |
|
|
| `cursorInactiveTimeout` | Mouse | Accessibility | Pointer visibility is configured with pointer behavior but affects motor and visual access. |
|
|
| `cursorSize` | Accessibility | Mouse | Large cursors are an accessibility adaptation that users also look for beside pointer controls. |
|
|
| `inactiveOpacity` | Appearance | Accessibility | Window translucency is an appearance choice with a direct readability impact. |
|
|
| `lockMinutes` | Power | Privacy | Idle timing owns the mechanism; privacy owns the expectation that the unattended desktop locks. |
|
|
| `lockOnSleep` | Power | Privacy | Suspend owns the transition; privacy owns whether waking requires authentication. |
|
|
|
|
Lock-screen visuals belong only to **Appearance**: background source, blur,
|
|
clock, date, user name, and password-field presentation. **Power** owns when
|
|
the session locks, while **Privacy** keeps only the established timing mirrors
|
|
above. Visual controls must not be copied onto either page.
|
|
|
|
**Displays** is the sole owner of mode, scale, rotation, arrangement, and primary role.
|
|
Those values form one safety transaction: every connected output
|
|
is applied, verified, confirmed, or restored together. Other pages may link to
|
|
Displays, but must never expose a second geometry control or persist a partial
|
|
layout.
|
|
|
|
Mirrors must remain the same schema-backed control, never a second preference
|
|
or a copied default. Additions to this table require a concrete discoverability
|
|
reason and an update to `tests/quickshell/settings-ownership-contract.sh`.
|
|
|
|
Window border color follows the same ownership rule. The inactive border is a
|
|
**scheme-relative role** owned by `ColorScheme.qml`: it changes only to retain
|
|
neutral contrast in light and dark modes. The focused Prism border is the
|
|
**accent role**, driven by the chosen `accentName` and also owned by
|
|
`ColorScheme.qml`: each accent carries a separate pair for light and dark, so
|
|
`ColorScheme.qml` restates the focused border alongside the inactive one on
|
|
every scheme change, rather than leaving a scheme flip to erase a
|
|
user-selected accent.
|
|
|
|
## 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 content-identical Quickshell entry can share the live shell's ID.**
|
|
Quickshell derives the Shell ID from config *content*, not path. Runtime
|
|
harnesses therefore create a distinct semantic entry file, address that exact
|
|
file with `qs -p`, and discover its PID from the exact Config path in
|
|
`qs list --all`. They terminate only that recorded PID with `kill`; never use
|
|
`qs kill` from a copied configuration.
|
|
|
|
## 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 favorites and aliases |
|
|
| `$XDG_STATE_HOME/panama/backups/` | Settings snapshots |
|
|
| `$XDG_STATE_HOME/panama/hypridle.conf` | Generated idle config |
|
|
| `$XDG_STATE_HOME/panama/hyprlock.conf` | Generated lock-screen appearance |
|
|
|
|
`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.
|