# Settings redesign — deferred test runs

No contract is executed while the settings redesign is in flight: many are live
harnesses that open windows, drive overlays, and run display transactions on
the real desktop. Everything below runs **once, at the end of the redesign,
with Gabriel's go-ahead**, and failures get fixed then.

## The run

- `panama test` — the full suite (**166** contracts as of the phase 4 Shell
  wave; the top-level README's count line is set to match and is itself
  checked by `setup/readme-contract`).

## Known items to verify or investigate at the end

- `quickshell/welcome-contract` — failed once ("the welcome screen did not
  open") during a full-suite run storming the live session; passed in
  isolation minutes earlier after the Welcome.qml comment edit. Suspected
  contention flake, not a regression. Confirm.
- `setup/readme-contract` — counts contracts; keep the README number in sync
  as later phases add contracts (phase 2 added `phone-page-contract`).
- Phase 2 additions already written and passing when last run:
  `my-home-settings-contract` (renamed from `home-phone-settings-contract`),
  `phone-page-contract`, extended `home_assistant_bridge_test.py` and
  `kdeconnect_bridge_test.py`.
- Each later phase (Appearance, …) appends its new/changed contracts here
  instead of running them.

## Phase 3 (Appearance) — append below

The contracts wave is done. Everything below is written and **still to be
RUN**. Nothing in this list has been executed against a live harness: the
static halves were checked against the tree, but every harness run and the
full-suite pass are deferred to the end-of-redesign sweep.

### New contracts (3)

| Contract | What it pins |
|---|---|
| `quickshell/theme-catalog-contract` | `config/themes.json` shape (10 themes, 6 dark / 4 light), every palette and ansi block accepted by the model's own validators, moon and day byte-identical to the shell's pre-theme literals, `ThemeCatalog.qml`'s embedded fallback carrying both, and `Theme.qml` holding no palette ternary. |
| `quickshell/video-wallpaper-contract` | Three independent pause reasons (`FocusModes.gameRunning`, `Battery.acOnline` plus the preference, `manuallyPaused`) and pausing over mpv's JSON IPC; the mpvpaper argv (`hwdec=vaapi`, `no-audio`, `loop-file=inf`, `input-ipc-server=`); the hyprpaper stop/start choreography and `Wallpaper.refreshActive()`; `Wallpaper.qml`'s video routing branches; the helper's 60-file cap, depth-2 scan and mp4/mkv/webm filter; the still frame for the lock screen; `WallpaperIndicator` in `Bar.qml`'s right-hand row with its `visible` binding, spoken name and no animation; the two schema keys; the doctor check id. |
| `quickshell/settings-titlebar-contract` | No minimize or maximize anywhere in the settings chrome (comments may say the words, code may not); `height: shown ? 48 : 0` so hiding collapses rather than leaving a hole; `buttonsLeft` side-awareness with exactly one anchor released per element; the close button's `activeFocusOnTab`, `Accessible.role`/`name`, Return/Space handlers and focus treatment, and that it is the only button; Escape declared on the shell rather than inside the bar; the `panamaTitlebar` schema entry and its Appearance row. |

### Updated contracts (6)

| Contract | What it now pins |
|---|---|
| `quickshell/accent-controls-contract` | Rewritten around the new editor: the nine surviving components and their qmldir lines, `AccentPicker`/`ThemeProfilePicker` staying deleted *and* unregistered, the four wells and their single `apply()` path, one hyprpicker invocation aimed by a remembered target, `ColorWell`'s hex validation committing on Enter and focus-out but never per keystroke, the six HSV labels, the debounce (the write lives in `commitTimer`, `changeChannel` writes nothing, `releaseTimer` hands the sliders back), `commitActive` recomputing `accentName` through `nearestCuratedName` with `selectProfile`/`updateActive` both routing into it, `Theme.qml` reading `ThemeProfiles.activePalette`, the Themes and Theme editor tabs, four search labels, and the no-animation ban extended over all nine components. Live half rewritten against the rebuilt harness. |
| `quickshell/theme-profiles-contract` | Node half rebuilt for the optional-field model: `shippedProfiles()` with no argument still returns the three built-in fallbacks (back-compat), the ten catalog records survive with palettes and ansi and no effects, palette/ansi/effects survive a stored record with per-key effect clamping, an invalid palette drops the FIELD not the profile, the caller's shipped list owns the id and name space, `editProfile` passes untouched fields through and a shipped fork carries the whole palette, `nearestCuratedName` mappings (mauve→orchid, gruvbox-yellow→amber, grey→slate, per scheme), and `derivePalette`/`resaturatePalette`/`deriveAnsi`/`mixHex` producing palettes the validators accept. Live half now pins the per-mode selection flow: a light/dark flip returns to the theme chosen on that side, never a forced default. |
| `quickshell/desktop-style-contract` | `titlebarMaximizeButton` and `titlebarDoubleClick` asserted **gone** from the schema and from `DesktopStyle`, along with `action-double-click-titlebar`; `panamaTitlebar` added (bool, def true, group titlebar); the button layout pinned close-only on both sides with `minimize`/`maximize` banned from the function body; the Appearance greps moved to Fonts/Sizes/Rendering with exactly five `FontPicker` rows and their five role labels, plus the honest-titlebar subtitle. |
| `quickshell/settings-ownership-contract` | The ColorScheme block was pinning `inactiveBorderDark`/`inactiveBorderLight`, which no longer exist. It now pins the inactive border as the active theme's `gutter`, bans a regrown literal, pins the accent border roles through the new `hyprColor(value, alpha)` signature, requires both roles to be restated on a theme change, and cross-checks Hyprland's startup literals against moon's and day's gutters in `themes.json`. |
| `quickshell/lock-screen-settings-contract` | The "between Background and Shell typography" ordering check named a card that no longer exists. It now pins Background → Video playback → Lock screen order, and that all three sit on the Background tab. |
| `quickshell/settings-search-contract` | Seven fixed cases added for the new surface: themes, theme editor, dark mode, Catppuccin, Gruvbox, video wallpaper, titlebar. The schema-label sweep already covers the new keys automatically. |

### Verified against the new tree, no edit needed

- `quickshell/settings-hardcoded-values-contract` — its scope is `Settings.qml`,
  which the theme work did not touch; `Theme.qml`'s literals are covered by
  `theme-catalog-contract` instead.
- `quickshell/wallpaper-service-contract` — already carries the video routing
  greps.
- `quickshell/wallpaper-settings-contract`, `wallpaper-policy-contract`,
  `settings-nav-contract`, `settings-pages-contract`, `manual-contract`,
  `settings-docs-contract`, `qmldir-registration-contract`,
  `settings-sync-contract`, `settings-backup-contract` — checked, nothing stale.
- `quickshell/panama-doctor-contract` — already lists `input.video-wallpaper`.

### Docs updated in the same wave

- `modules/settings/README.md` — new **Appearance** section (Themes, Theme
  editor, backgrounds still and moving, the honest titlebar); the border
  ownership paragraph corrected to the theme's gutter.
- `manual/05-making-it-yours.md` — rewritten Appearance chapter: the two
  galleries, per-mode memory, the four-well editor, video wallpapers, and why
  there is no minimize.
- Top-level `README.md` — contract count 162 → 165, confirmed by
  `setup/readme-contract`.

### Still open before the run

- The parallel pipeline wave's files (`gtk-theme-contract`,
  `lock-screen-theme-contract`, `palette-contract`, the two bridge tests) are
  not counted above. If that wave adds contracts, the README count line and the
  count in this file both need bumping again before the suite runs.
- `AppearancePage.qml`'s `tab` property defaults to `"background"` while its
  own comment says Themes leads. Decide which is intended before the run;
  nothing currently pins it either way.

## Phase 4 (Shell category) — append below

Spec: `2026-08-24-shell-category-redesign.md`. Desktop & Dock became **Shell**
(Bar · Dock · Control Center · Tiling · Workspaces), System gained **Sync &
Backup**, and the dock got its feature wave.

Unlike phases 2 and 3, the static and stubbed contracts in this wave **were
run** as they were written, and every one of them passed in isolation against
the tree. Two things are still deferred. The **live-harness halves** were not
run — the session was locked, and `dock-position-contract` opens a probe shell
while `settings-pages-contract` starts a settings harness, so those ran static-
only (`PANAMA_SETTINGS_STATIC_ONLY=1`) or not at all. And the **full-suite
pass**, the only thing that catches contention between harnesses, happens next
with Gabriel driving.

### New contracts (1)

| Contract | What it pins |
|---|---|
| `quickshell/bar-visibility-contract` | The bar's own neutral family: `barFg`/`barFgDim`/`barFgMuted` exist, each forced tone is anchored on one literal with both dims mixed off it, and the `theme` branch returns `root.fg`/`fgDim`/`fgMuted` **by identity** rather than a copied colour. All thirteen bar-text files bind to those tokens and none paints neutral text with the `fg` family (`Theme.alpha(Theme.fg, …)` hover and separator fills are allowed; semantic tones were never in scope). `Bar.qml`'s scrim reads `visible: Settings.barBackdrop`, its shadow reads `layer.enabled: Settings.barTextShadow` over exactly one `MultiEffect` layer, and neither may be a literal `true` — both shipped hardcoded during the build, which is the regression this exists for. Plus the four widget gates ANDed with their state conditions, `VitalsWidget`'s whole-pill `visible`, `exclusiveZone: Theme.barHeight` still literal, no animation anywhere in `Bar.qml`, and the two right-click jumps landing on `bar`. |

Mutation-checked while writing: hardcoding the shadow, returning a literal from
the `theme` branch, putting one widget back on `Theme.fg`, deleting the vitals
pill's own `visible`, and adding a `Behavior` to `Bar.qml` each fail it with a
message naming the actual problem.

### Updated contracts (5)

| Contract | What it now pins |
|---|---|
| `quickshell/dock-position-contract` | `DockPinsEditor` → `DockPinsStrip`: `preventStealing` moved to the strip, the retired ↑/↓ buttons replaced by the strip's own keyboard path (Left/Right move, Delete unpins), and a new section for the **live** dock's drag-to-reorder — commits once on release, and measures a slot from a real icon rather than a constant, because a `DockItem` is taller than it is wide and a constant is wrong on one orientation. |
| `quickshell/settings-jump-contract` | `DockContextMenu`'s "Dock settings" now opens `dock`, not the retired `desktop`. |
| `quickshell/settings-pages-contract` | Six new pages added to the page sweep (Bar, Dock, ControlCenter, Tiling, Workspaces, Sync); `vitalsIntervalMs` and the graphics ChoiceGrid now required on `BarPage` rather than `AppearancePage`, since the vitals are bar content and not surface appearance. |
| `setup/projects-contract` | The saved-projects list moved from the deleted `DesktopPage.qml` to `WorkspacesPage.qml`. |
| `quickshell/panama-commands-contract` | The launcher's new `dock-add-app` → `panama-action dock-pin` command. |

### Verified against the new tree, no edit needed

Every one of these was **run** and passed after the phase-4 changes landed:

- `quickshell/settings-search-contract` — the new `bar`/`controlCenter`/
  `datetime` groups and the re-pointed `dock`/`multitasking`/`edges`/`master`/
  `notices`/`focus` routes are covered by the schema-label sweep already.
- `quickshell/settings-ownership-contract` — the six intentional mirrors and the
  eleven README literals survived the rewrite; no new mirror was introduced.
- `quickshell/settings-nav-contract` — 14 categories, 38 leaves, 2 retired ids
  (`home-phone`, `desktop`).
- `quickshell/manual-contract`, `gnome-handoff-contract` (15 handoffs against
  38 pages), `control-center-contract`, `welcome-contract`,
  `accent-controls-contract`, `theme-catalog-contract`,
  `desktop-style-contract` — checked, nothing stale.

`welcome-contract` passed in isolation again here, which does not settle the
phase-3 flake above: that failure only appeared under a storming full-suite run.

### Docs updated in the same wave

- `docs/settings.md` and the 37 `settings-*` launcher commands — regenerated by
  `panama-settings-docs` and `panama-settings-commands`; both `--check` modes
  clean and both generators verified idempotent. `settings-desktop` is deleted,
  `settings-bar`/`-dock`/`-control-center`/`-tiling`/`-workspaces`/`-sync` are
  new, and `dock-add-app` joins them.
- `modules/settings/README.md` — new **Shell** section (the five tabs, the bar
  token design and why it exists, the pins strip and the live dock's gestures,
  the Control Center rule that only real sections get toggles) and a new
  **System › Sync & Backup** section. Appearance corrected from six tabs to
  five, saying where the Shell tab went.
- `manual/05-making-it-yours.md` — new **Shell** chapter section (bar
  legibility, widget switches, why `use24Hour` is not there, the pins strip,
  the dock's drag/right-click/scroll/preview gestures, **Add App to Dock**,
  Control Center sections) plus **Carrying settings between machines**.
  `manual/03-windows-and-workspaces.md` — the multi-display workspace switch
  now points at Displays, which is where it actually is, instead of the deleted
  Desktop & Dock page.
- Top-level `README.md` — contract count 165 → 166, recounted the way
  `panama test` collects (executable, or `*_test.py`, excluding fixtures and
  `__pycache__`).

### Still open before the run

- Four contracts in the working tree changed for reasons **outside** this
  phase and are not accounted for above: `declared-assets-contract` (`pkill`,
  from the video wallpaper work), `declared-dependencies-contract` (`cmp` →
  diffutils), `panama-doctor-contract` (29 → 30 check ids), and
  `lock-screen-helper-contract` / `video-wallpaper-contract` (theme-derived
  literals and the mpv IPC key quoting). Confirm each belongs to a wave that
  intended it before the suite runs.
- `tests/setup/update-command-contract` is untracked and belongs to the
  separate `panama update` design, not to this redesign. It **is** inside the
  166.
- `quickshell/dock-position-contract` has never had its **live** half run since
  the strip landed: it opens a probe shell, and this wave ran under a locked
  session. Its static half is what was verified. Run it first in the sweep.
- `quickshell/settings-pages-contract` passed static-only for the same reason.
  Its compositor-integration half — the settings harness, its IPC, and the
  page-open checks — is unverified against the six new pages.
- `Bar.qml` has one stray indentation glitch at its first `Row` (line 105).
  Cosmetic, untouched here because the file is not this wave's to reformat.
- 2026-08-24 full-suite run: 166 contracts, all green except `displays-contract`
  and `switcher-contract`, which are live interactive tests that cannot run
  behind hyprlock (both passed in the same day's unlocked run; neither
  subsystem changed in phase 4). Re-verify after unlock.
