Finish the wonderland: System told truthfully, in eight tabs instead of ten

Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
This commit is contained in:
Gabriel Brown
2026-08-24 23:31:52 -04:00
parent 9ffaf45a4d
commit be0e55214b
57 changed files with 5040 additions and 925 deletions
@@ -1672,3 +1672,235 @@ settings generator writes 38 of them).
`search-routing-contract`, `ipc-targets-contract`, `settings-jump-contract`,
`settings-docs-contract`, `readme-contract`, all eight of `tests/hypr/`),
then the sandboxed-harness ones, then the live settings window last.
## Phase 15 (System) — the last one
Spec: `2026-08-24-system-redesign.md`. System went from ten tabs to eight:
Region & Language merged into **Date, Time & Region** — a date format and the
clock that shows it are one subject, and splitting them meant changing how a
date is written on a page that never showed one — and the Manual became a
routable leaf with no tab, opened from About's Manual card, a deep link, or the
search box rather than found by scanning a strip. With that, the settings
redesign is complete: fourteen categories, thirty-eight leaves, every one of
them rebuilt.
The bug list this phase carried was longer than the layout change. The
hardcoded Quickshell version, the auto-download switch nested inside the
Firmware card, `CHECK_TITLES` missing `panama.updates`, two Health actions with
no target, RegionPage's stale "Open appearance" handoff, `panama-updates`'
stale dnf-automatic comment, the backup colour gap, Restore without
confirmation, and four pieces of dead code.
Three agents edited the tree concurrently; everything below was reconciled
against the landed files rather than against the spec's pinned shapes, and
every contract named here was **run**.
### No new contracts (175 → 175)
Every pin fitted a file that already existed, so the suite did not grow. That
was the judgement call this phase offered and it went the honest way: a
`system-pages-contract` would have been a new file holding assertions that
belong beside the ones they are variations of — the nav counts beside the nav
taxonomy, the repair command beside the repair, the single-check verb beside
the scan. `setup/readme-contract` — RUN, PASS ("175 contracts, as documented").
### What each contract gained, and why
- **`quickshell/settings-nav-contract` — RUN, PASS** (14 categories, 38 leaves
of which 1 hidden, 3 retired ids). It learned the third leaf shape:
`SettingsRoutes.hiddenLeaves`, which is how the manual stays addressable
without a tab. A hidden leaf that is also a tab, or also a category, now
fails — that overlap is the one the existing checks could not see, because
the hidden half draws no row anywhere. Then: the System strip is pinned at
exactly eight tabs in order (the number IS the point — it is the horizontal
space one row has, and an eleventh subject needs a decision, not another
entry), `region` is pinned to resolve specifically to `datetime` rather than
merely to *a* leaf, `RegionPage.qml` must be gone, and the manual must be a
leaf and must not be a tab.
It also grew the check that would have caught the retired search entries:
every page id in `SettingsSearch` — extra entries and group routes alike —
must be a live leaf, with a distinct failure message when it is a retired id,
since that is the case that still opens a window and lands somewhere else.
Plus a table of the fifteen subjects the consolidation moved, each asserted
to be findable by its own name on the tab that now owns it.
- **`quickshell/settings-pages-contract` — RUN, PASS** (production PIDs
preserved). `Updates`, `DateTime`, `Containers` and `Manual` joined the
page-scaffold sweep; Containers is the one that was waiting for it, since its
root was a bare `Item` and every convention the scaffold carries was
hand-rolled there. The live routing list gained `updates`, `datetime` and
`containers`, and the retired `region` id is now driven through the real IPC
and asserted to land on `datetime`.
- **`quickshell/updates-contract` — RUN, PASS** (2 dnf, 3 flatpak, 0 firmware).
Three additions. **Download sizes add up or are not offered**: a total is
present only when every pending item was priced, because a partial figure
shown as the download understates it, and understating it is the direction
that costs somebody money on a metered connection. **A changelog is a read**,
proved by construction rather than by inspection: `dnf5`, `flatpak`,
`fwupdmgr`, `pkexec` and `systemctl` are replaced with stubs that record
their argv and answer nothing, and the recorded argv is then checked for
mutating tokens — which also exercises the honest-absence path, the common
case on a machine with third-party repositories. Names outside
`NAME_PATTERN` must be refused before anything is launched. **One
application, by name**: the per-app argv is asserted exactly
(`flatpak update -y --noninteractive <id>`) against a stub, and an ID the
last scan did not list must be refused without running flatpak at all.
- **`quickshell/panama-doctor-contract` — RUN, PASS.** The `CHECK_TITLES`
KeyError is pinned twice: as the invariant (every id in `CHECK_ORDER` has a
title, asserted by importing the module) and as the path (a probe forced to
raise must come back as a titled warning row rather than taking the report
with it). Worth both — the bug needed the updates probe to fail, which it
almost never does, and the containment that was supposed to catch it *was*
the KeyError, raised inside the `except` handling the original failure.
Then: the updates action's `target: "updates"`, driven by a fixture cache
with a security count; `repairCommand` asserted against `REPAIR_COMMANDS`
itself rather than restated, so an argv change cannot leave the row
describing the old one; and the `check CHECK_ID` verb — full snapshot
envelope, exactly one check, summary arithmetic covering only that check,
refusal of unauthored ids with no report and no filesystem mutation, and no
second output mode.
The fixture also gained `XDG_CACHE_HOME`, which was leaking: `check_updates`
reads the Updates cache from the environment rather than through
`DoctorConfig`, so the contract had been reading the real machine's pending
updates.
- **`quickshell/health-service-contract` — RUN, PASS.** The both-sides pin is
derived from both files: every `open`/`instructions` target the doctor emits
must appear in Health's `settingsTargets`/`instructionTargets`. This is the
one where half a change does not degrade a row, it blanks the page —
`validAction` false makes `validCheck` false makes `consumeSnapshot` reject
the whole report. Live fixtures either side of it: a check targeting
`updates` is accepted intact, and one targeting `storage` — a real page the
list deliberately omits — is rejected, which is what makes the allow-list an
allow-list. Also `repairCommand` surviving the projection (a new known key is
dropped silently unless it is added, and the failure is invisible: the repair
still works, only the sentence saying what it runs is gone), a check without
one still accepted, `refreshCheck` asking for `check <id>` and never `--json`
while leaving the other rows and recomputing the headline counts, an unknown
id starting no process, and `saveReport` writing the same redacted projection
to a named file.
- **`quickshell/health-ui-contract` — RUN, PASS.** The Colour profiles handoff
assertion is inverted rather than deleted (Displays owns colour profile, bit
depth, SDR brightness and saturation per output, which is more than GNOME's
panel can say in a session it does not manage); Digital wellbeing stays. The
repair command must be shown, and must not be gated on `lastRepair`,
`repairingId` or a working state — after the fact is not the same answer,
because by then the decision is made. A per-row re-check and a save-report
row must exist. The fixture grew a `panama.updates` check whose action is
followed for real through `requestHealthAction`, landing on `updates`: the
third piece of the chain the doctor and the service each pin one end of.
- **`quickshell/settings-backup-contract` — RUN, PASS.** Named snapshots
(fifteen rows reading `2026-08-24 11:03:07` are fifteen rows nobody can
choose between), with the name reaching a filename — which is the oldest way
a helper gets talked into writing outside its own directory. Seven hostile
names, each of which must either fail or produce a file inside the backup
directory. Delete gets the same confinement as restore in the other
direction, and a worse failure mode: restore reading the wrong file
overwrites settings, delete resolving the wrong name destroys something that
is not a backup. Traversal, absolute paths, an escaping symlink, an
already-deleted name and an empty name all refused. `bytes` per row, checked
against `stat`.
- **`quickshell/settings-backup-live-contract` — RUN, PASS.** The colour gap,
both halves. `layoutFromStoredDisplays` must carry `vrrMode`,
`colorProfile`, `bitdepth`, `sdrBrightness`, `sdrSaturation` and `mirrorOf`
through, and must still accept a record written before they existed.
`layoutsEqual` must detect a difference in each of them — **this is the
assertion the shipped behaviour fails**, and the reason the restore silently
did nothing: the seven-field comparison called a snapshot with different
colour settings equal, so the restore took the early return and reported
success while leaving HDR off. Plus the inverse for the two float fields: a
value that came back one ulp different is the same value, or every restore
reapplies the layout it already has.
Two harness seams were added for this (`layoutFor`, `layoutsMatch`), because
the colour half is invisible from `handleRestoreOutput` — geometry restores
perfectly and success is reported either way.
- **`quickshell/settings-sync-contract` — RUN, PASS.** The preview's `changes`
must be `{key, from, to}` with both sides already text, a `changeCount`
agreeing with what would be applied, a rendered list no longer than that
count, and no side over 200 characters — with a bundle carrying a 4000-
character `weatherLocation` proving the cap is real rather than incidental.
Stringifying in the helper rather than in QML is what makes the cap
enforceable at all. Observed on this machine: 115 changes, 40 rendered,
absent values rendered as "not set".
- **`quickshell/manual-contract` — RUN, PASS** (5 chapters). Titles now come
from the files: the chapter list moved to `ManualChapters.qml`, which both
the reader and About's card instantiate, and the contract fails on any
`label:` beside a filename — the drift it is guarding against had already
happened, under a comment claiming it could not. It also fails if either page
names chapter files itself. In-app links: the handler must route
`panama://settings/<page>` through `ShellState.openSettings` while still
sending everything else out of the desktop, asserted by following the call
(inline block or named function, brace-matched) rather than by reading one
line, and every such link written in a chapter must name a real leaf. Plus
the taxonomy check inverted: the manual must be a hidden leaf and must NOT be
a System tab.
- **`quickshell/gnome-handoff-contract` — RUN, PASS** (6 handoffs checked
against 37 pages). Two panels joined `OWNED`: `system region`, because Date,
Time & Region now offers a language picker, four per-category format
dropdowns backed by the installed locales, and a live preview of what each
renders; and `color`, because Displays has offered a colour profile per
output for some time. Both are derived rather than hand-asserted, so the door
cannot come back under a name nobody thought to list. Plus two inverses:
`RegionPage.qml` must stay gone, and `DateTimePage` must not open a GNOME
panel for something it now does itself.
- **`quickshell/search-routing-contract` — RUN, PASS** (146 routed settings).
Unchanged; run because `SettingsSearch` moved.
### Search entries
Two moved: Language and Regional formats, from `region` to `datetime`. Fifteen
added: the seven About facts nobody could search for (Hostname, Kernel version,
Device model, Installed memory, Uptime, Serial number, BIOS version — none of
them the label of a preference, all of them things people arrive looking for by
name, and "Installed memory" rather than "Memory" because that one IS already a
schema label on Bar); the two Sync verbs (Export settings, Import settings —
"Carry settings to another machine" was the only entry, and nobody searches for
a sentence); and six format subjects on the merged tab (Date format, Number
format, Currency, Measurement units, Paper size, First day of the week) plus
"Set the clock by hand".
### Schema docs and launcher commands regenerated
`quickshell/scripts/panama-settings-docs` and `panama-settings-commands` both
run without `--check` and their output committed. `docs/settings.md` stays at
**174 settings across 36 groups** — no preference was added this phase — but
the `datetime` group is now "Found on **System Date, Time & Region**", which
is the whole point of a generated document: the tab was renamed in one place.
The launcher went 38 generated commands to **37**: `settings-region` was
removed, and `settings-manual` survives because the generator learned about
`hiddenLeaves` — a leaf with no tab is the one leaf that cannot be found by
scanning, so it is the one that most needs a launcher entry. Keywords picked up
the new search entries on their own: `settings-about` gained hostname, kernel
version, device model, installed memory, uptime, serial number and BIOS
version; `settings-datetime` gained language, regional formats and the six
format subjects; `settings-sync` gained export and import.
`quickshell/settings-docs-contract` — RUN, PASS (174 settings documented).
`quickshell/panama-commands-contract` — RUN, PASS (76 commands), after teaching
its own leaf derivation about `hiddenLeaves` too: it counted 75 and found 76,
which is the generator and the contract disagreeing about what a leaf is
rather than a stale command. `quickshell/qmldir-registration-contract` — RUN,
PASS (189 components), covering the two new ones, `ManualChapters` and
`FieldActionRow`.
### Deferred, and why
- **`quickshell/settings-search-contract` — NOT RUN.** It daemonizes a
Quickshell instance against the user's real XDG directories rather than a
scratch set, and constructing `SettingsSearch` calls
`DesktopStyle.ensureStarted()`, which replays application preferences to the
live GTK configuration. Every case it pins was checked statically instead:
none of the fifteen new labels equals or prefixes any of the twenty-one
pinned queries, so none can outrank one, and every schema label is still
reachable because the schema half of the index did not change. The new static
page-id sweep in `settings-nav-contract` covers the failure this phase could
actually have introduced.
- **Nobody has typed a locale into the four format dropdowns.** The category
round-trip is A's, through `panama-locale`; no `localectl` write was made
from any contract here, and none should be.
- **The manual's in-app links have not been clicked.** The handler is pinned
structurally and every link a chapter writes is checked against the taxonomy,
but no chapter currently writes one, so the routing has been proven correct
and never exercised.
- **No backup has been restored against the real store**, and no update has
been applied. Both are pinned through stubs and scratch directories, which is
the sanctioned path and the only one that should ever run here.