22 KiB
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 bysetup/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 addedphone-page-contract).- Phase 2 additions already written and passing when last run:
my-home-settings-contract(renamed fromhome-phone-settings-contract),phone-page-contract, extendedhome_assistant_bridge_test.pyandkdeconnect_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 isSettings.qml, which the theme work did not touch;Theme.qml's literals are covered bytheme-catalog-contractinstead.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 listsinput.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 bysetup/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'stabproperty 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 newbar/controlCenter/datetimegroups and the re-pointeddock/multitasking/edges/master/notices/focusroutes 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.mdand the 37settings-*launcher commands — regenerated bypanama-settings-docsandpanama-settings-commands; both--checkmodes clean and both generators verified idempotent.settings-desktopis deleted,settings-bar/-dock/-control-center/-tiling/-workspaces/-syncare new, anddock-add-appjoins 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, whyuse24Houris 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 waypanama testcollects (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), andlock-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-contractis untracked and belongs to the separatepanama updatedesign, not to this redesign. It is inside the 166.quickshell/dock-position-contracthas 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-contractpassed 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.qmlhas one stray indentation glitch at its firstRow(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-contractandswitcher-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. Still pending — anddisplays-contracthas since changed (phase 5), so this run is now the first one that exercises the new material as well.
Phase 5 (Displays) — append below
Spec: 2026-08-24-displays-redesign.md. The Displays page became canvas-first,
the per-monitor record grew VRR / colour profile / bit depth / SDR trim /
mirroring, and pushLayout stopped clobbering monitors.lua's colour values.
Nothing in this wave was run. The test window was closed while it was
written: these are live harnesses that drive the real compositor through
display transactions, and three agents were editing the tree concurrently. What
was verified is listed as static below — evaluated directly against the
implementation files without a harness, by loading monitors.lua under a stub
prefs and by evaluating DisplayLayout.js in node. Everything else is
deferred to the sweep.
Contracts changed (4)
| Contract | What it now pins | Verified |
|---|---|---|
quickshell/display-transaction-contract |
The extended record end to end: applyRecord merging one field at a time; vrr omitted when vrrMode === -1 and emitted when it is not; neutral sdrsaturation omitted rather than written; a mirrored rule asking for position = "auto"; the mirror x/y carve-out in matchesLayout, and that it does not leak to an unmirrored record; a framebuffer format with no 8/10 mapping skipping the bit-depth assertion instead of blocking Keep; confirm() persisting the whole record; old-shape stored blobs still validating and an impossible vrrMode not; and seven refusals (self-mirror, primary mirroring, absent target, out-of-range vrr/profile/depth/SDR). |
Bash and Python syntax; every new static grep checked against the landed Displays.qml (including the vrrMode >= 0 omission branch in monitorRule) and against the extended harness; the fake compositor's rule parser dry-run on a real monitorRule payload, covering the position = "auto" and mirror = branches and the mirrorOf: "none" readback spelling. Every IPC assertion is deferred. |
quickshell/display-arrangement-contract |
Flipped: the canvas is no longer hidden below two displays. The visible: … monitors.length gate is now asserted absent from the DisplayArrangement element (by AST-free block scan, so the selector chips and Workspaces card may keep theirs), with a solo hint string, a draggable flag, and an enabled: binding on the DragHandler. Plus the mirror badge: Mirrors in the component, mirrorOf read off the rects, and two new harness fixtures — solo renders one rect with draggable == false, mirrored stacks rect 1 on rect 0 and flags it. |
Statically verified against the rebuilt DisplayArrangement.qml: every grep hits, the DragHandler's enabled: is found by the brace-matching helper, and the flip check was mutation-tested both ways — it fails on the pre-redesign page (where the gate sat on the enclosing card, not on the canvas) and passes once the gate is gone. The two new harness fixtures are deferred. |
quickshell/display-layout-contract |
Mirror geometry in DisplayLayout.js: a valid mirror validates; the mirrored record keeps its stored coordinates through normalize (the primary's anchor does not apply to a position nothing reads back); it contributes nothing to bounds; canvasRects stacks its rect on its target's and carries mirrorOf/mirrored. Five refusals: self, absent target, mirroring primary, a two-hop chain, and a non-string. |
Statically verified in node against the real DisplayLayout.js — every expected value in the two new jq filters came from that run, including the 3140/80 the mirrored record keeps. Harness plumbing deferred. |
quickshell/displays-contract |
The Lua consumer half: color_profile / bitdepth_value / vrr_value / sdr_value / mirror_value present, cm/bitdepth/sdrbrightness/vrr/mirror emitted under Hyprland's own key names, a mirrored entry's position forced to auto, neutral SDR saturation and vrrMode = -1 written as absence rather than as a value, and an entry with every new field impossible surviving with its geometry while each bad field drops. Plus the extended-record greps on Displays.qml. |
Statically verified: the whole LUA block was run against config/dot/hypr/monitors.lua with a stub prefs and passes. The Displays.qml greps were checked by hand and all hit. The live compositor half is deferred. |
Cross-agent shapes these contracts now pin
Written from the spec's pinned API while agents A and B worked in parallel, and re-checked against their files as those landed:
DisplayArrangement.canvasSnapshot()exposesdraggablealongsiderects/scale, and passescanvasRects'mirrorOf/mirroredthrough — it returnscanvasData.rects, not the solo-shrunktiles, which is what the new fixtures assert against. Confirmed in the landed component.- The solo hint is pinned as the prefix
One display connectedrather than the full sentence, so the em dash cannot break the grep. - The mirror badge is pinned as
MirrorsinDisplayArrangement.qml. soloFixture/mirrorFixturemutatefixtureService.monitorsand callresetDraft(), which is what the component's ownonMonitorsChangeddoes. Whether that ordering settles beforecanvasSnapshot()reads back is the one thing only a run can answer.
Still open before the run
— resolved: the detail now names color, VRR override, and mirroring, and the matching grep inPreferenceSchema.qml's staledisplaysdetail stringdisplays-contractwas updated in the same commit.displays-contractalso grepsDisplaysPage.qmlforselectedOutput,scalesForMode(,primaryFirstMonitors.map(andenabled: Displays.canConfirm. All four still hit, but the page was still the pre-redesign one when this was written — re-check them once the rebuilt page lands, particularlyprimaryFirstMonitors.map(, since the spec replaces the "Connected display" picker card with selector chips.- No contract file was added or removed, so the README count line stays at
166 and
setup/readme-contractneeds nothing. - Run order for the sweep:
display-layout-contractfirst (pure geometry, no compositor), thendisplay-transaction-contract(fake compositor onPATH), thendisplay-arrangement-contract, anddisplays-contractlast — it is the only one that drives the physical display, and it refuses to start from a scale that does not match whatmonitors.luaships.