Write down where all of this came from

Section G, the closeout.

UPSTREAM-INSPIRATION gains the second and largest borrowing round: what
was taken from omarchy, end-4, caelestia, ML4W and HyDE, what was
rebuilt rather than transplanted, and -- the part that matters more --
what was declined and why. Wallpaper-derived colour is declined on
identity, because dynamic colour is how rices end up looking like each
other. Audio visualisers are declined because a continuously repainting
equaliser is the GPU-pegging animation this project avoids. Patching
application internals to theme them is declined because the maintenance
is unbounded and lands on whoever is holding the desktop that day.

DESKTOP-PARITY gains a Portable machines section, which it has never
had, because until yesterday Panama was a desktop configuration in the
code as well as on the machine. Nine rows, one of them honest about not
having run on hardware with a lid. The remaining-gaps list gains the
four things now known to be missing rather than the one.

The README stops describing a desktop from before yesterday: migrations
in the install flow, the welcome and cheatsheet and manual, and laptops
working with every surface hiding itself where the hardware is absent.

The plan records its own outcome per section, including the two places
the work departed from it: D2's template renderer deferred once D1
removed the duplication it existed to fix, and E1 inverted after using
it for an afternoon.
This commit is contained in:
Gabriel Brown
2026-08-22 07:48:24 -04:00
parent 4910bd659c
commit 41dd91eb75
4 changed files with 281 additions and 115 deletions
+24
View File
@@ -33,6 +33,13 @@ in order, without stopping again:
| `setup-identity` | git config, `gh auth login`, an SSH key — whichever were asked for | | `setup-identity` | git config, `gh auth login`, an SSH key — whichever were asked for |
| `install-hardware` | NVIDIA, Secure Boot enrollment, Fedora's extras, firmware — each only if it was asked for. Last, because enrollment and firmware are consumed at the next boot | | `install-hardware` | NVIDIA, Secure Boot enrollment, Fedora's extras, firmware — each only if it was asked for. Last, because enrollment and firmware are consumed at the next boot |
Then `panama migrate` applies any repairs this machine has not had yet. That is
the half of an upgrade installing cannot do: the stages above only ever add, so
nothing there can remove a file this repository stopped shipping or repair a
symlink that now points nowhere. A fresh machine is marked as already caught up
rather than having those repairs run against it, and a machine with work waiting
is told at the next login rather than left to find out.
The run ends with a health summary from `panama-doctor`, which reports what is The run ends with a health summary from `panama-doctor`, which reports what is
actually running rather than what was attempted. It never fails the install: on a actually running rather than what was attempted. It never fails the install: on a
fresh machine it legitimately reports things as not yet configured. fresh machine it legitimately reports things as not yet configured.
@@ -82,6 +89,19 @@ is what decides; anything not on it is a panel Panama owns itself.
| `config/dot/wofi/` | Fallback launcher, in case the shell fails to start | | `config/dot/wofi/` | Fallback launcher, in case the shell fails to start |
| `config/dot/xdg-desktop-portal/` | Portal backend routing | | `config/dot/xdg-desktop-portal/` | Portal backend routing |
New machines get a welcome card on first start, teaching the handful of keys
that matter and reachable afterwards from the launcher. `SUPER + /` shows every
shortcut the machine has, read from the live keymap so a rebind appears there
without anything being kept in sync. Settings carries a
[manual](config/dot/quickshell/manual/) written for the person using the
desktop rather than the person building it, opening with a chapter for people
arriving from GNOME, macOS or Windows.
Laptops work: battery, charge limits, idle timings that differ on battery, and
a closed lid that suspends when you are leaving and keeps working when you are
docked. A desktop sees none of it, because every one of those surfaces hides
when the hardware is absent.
**Start here: [`config/dot/hypr/README.md`](config/dot/hypr/README.md)** — it **Start here: [`config/dot/hypr/README.md`](config/dot/hypr/README.md)** — it
covers the Lua migration, the uwsm environment gotcha, the HDR decision, the covers the Lua migration, the uwsm environment gotcha, the HDR decision, the
full keymap, and troubleshooting. full keymap, and troubleshooting.
@@ -92,6 +112,7 @@ Log in as **"Hyprland (uwsm-managed)"**, not plain "Hyprland".
``` ```
bin/ Small user-facing commands on PATH; `panama` is the entry point bin/ Small user-facing commands on PATH; `panama` is the entry point
migrations/ One repair per file, for machines installed before a change
config/ config/
bash/ .bashrc, aliases, env (env is gitignored) bash/ .bashrc, aliases, env (env is gitignored)
copy/ Files copied verbatim over / (needs sudo) copy/ Files copied verbatim over / (needs sudo)
@@ -172,6 +193,9 @@ package, a `flatpak:` line is a Flathub id, `| Name` gives the menu something
readable, and an indented line belongs to the entry above it — which is how OBS readable, and an indented line belongs to the entry above it — which is how OBS
carries its sixteen plugin extensions as one thing to tick. carries its sixteen plugin extensions as one thing to tick.
`panama migrate` applies repairs an installed machine has not had yet. Safe to
re-run: nothing is applied twice, and a machine with nothing waiting says so.
`panama-sudo` is pkexec with a stated reason: `panama-sudo --reason "why" -- `panama-sudo` is pkexec with a stated reason: `panama-sudo --reason "why" --
command` shows the reason on Panama's password prompt, clearly labeled as an command` shows the reason on Panama's password prompt, clearly labeled as an
unverified claim beside polkitd's own action text — meant for agents and unverified claim beside polkitd's own action text — meant for agents and
+31
View File
@@ -39,6 +39,29 @@ Last live audit: 2026-08-17, Fedora 44, Hyprland 0.56.2, Quickshell 0.3.0.
| System settings | The Settings app for display policy, appearance, desktop, sound, focus, shortcuts, and services; labeled GNOME hardware/account handoffs | Live | | System settings | The Settings app for display policy, appearance, desktop, sound, focus, shortcuts, and services; labeled GNOME hardware/account handoffs | Live |
| System health and recovery | Settings → System Health, `Check System Health` in Vicinae, a degraded-only bar indicator, redacted reports, and bounded Panama-owned repairs | Live | | System health and recovery | Settings → System Health, `Check System Health` in Vicinae, a degraded-only bar indicator, redacted reports, and bounded Panama-owned repairs | Live |
## Portable machines
Panama was a desktop configuration until 2026-08-22, in the code as well as on
the machine: there was no reference to a battery, a lid, or a power source
anywhere in it, and `hypridle.conf` said "This is a desktop" in its own header.
| Capability | Panama implementation | Status |
|---|---|---|
| Battery level | `services/Battery.qml` reads sysfs the way Vitals reads procfs; bar indicator hides entirely without a battery | Live |
| Power source | `panama-hw ac`; a machine with no mains supply reports as on wall power, so a desktop is never treated as running out | Live |
| Charge threshold | Power page control, only where the firmware exposes a ceiling; written through `panama-sudo` | Live |
| Idle timings per power source | hypridle holds one set at a time, so `panama-idle` rebuilds its config when the charger comes or goes | Live |
| Lock on lid close | hypridle's `before_sleep_cmd` already locks, so a lid-close suspend is a locked suspend | Live |
| Clamshell / docked | A logind `handle-lid-switch` inhibitor held while an external display is connected. No lid watcher, and a failure returns logind's default | Live, untested on a lid |
| Display arrangement on reconnect | `Displays.plannedRestore()` reapplies a confirmed layout, refusing when the stored mode is not offered | Live |
| Charger and low-battery notices | `StatusEvents`; the charger is ambient so DND quiets it, critical battery is not | Live |
| Hardware predicates | `panama-hw`: laptop, battery, ac, lid-closed, external-monitor, clamshell, touchpad, nvidia | Live |
The clamshell path is the one row that has not run on hardware with a lid.
Everything testable about it is tested; the end-to-end behaviour of closing a
real lid on a docked machine is not, and the helper's header says so rather
than letting silence imply otherwise.
## System health and recovery ## System health and recovery
Panama stays silent while the desktop is healthy. A compact bar indicator Panama stays silent while the desktop is healthy. A compact bar indicator
@@ -91,6 +114,14 @@ a polished general-purpose desktop can go beyond the current shell.
1. A real external RustDesk test while hyprlock is active. Local process state 1. A real external RustDesk test while hyprlock is active. Local process state
cannot prove what a remote client sees or whether input is accepted. cannot prove what a remote client sees or whether input is accepted.
2. Closing a real lid on a docked laptop. The inhibitor, the predicates and the
decision are all tested; the physical gesture is not.
3. A keyboard-layout change notice. Hyprland reports the active keymap but not
a change event Quickshell already consumes, so this needs either polling or
new event plumbing, and a single-layout machine cannot test it.
4. Sticky keys, slow keys and bounce keys. AccessX is an X11 server feature
with no Wayland equivalent; GNOME, macOS and Windows all ship these.
5. An on-screen keyboard, for a touch or convertible machine.
KDE Connect, the printer UI, Tesseract, and ZBar are installed and remain KDE Connect, the printer UI, Tesseract, and ZBar are installed and remain
tracked in `setup/packages/hyprland-packages`. tracked in `setup/packages/hyprland-packages`.
+90
View File
@@ -18,6 +18,96 @@ Panama is its own desktop experience, but it should learn from strong open-sourc
No Omarchy user-facing layout or styling was transplanted. Panama keeps its GNOME-derived information architecture, Prism material, Tokyo Night Moon palette, app model, and established interaction choices. No Omarchy user-facing layout or styling was transplanted. Panama keeps its GNOME-derived information architecture, Prism material, Tokyo Night Moon palette, app model, and established interaction choices.
### Second round, 2026-08-22
A survey of omarchy, end-4/dots-hyprland, caelestia, DankMaterialShell,
noctalia, HyDE, ML4W, JaKooLit and hyprdots, read against Panama's own feature
inventory. This is the largest borrowing round the project has had, and what
follows is what was taken, what was rebuilt rather than transplanted, and what
was declined.
**Omarchy** ([basecamp/omarchy](https://github.com/basecamp/omarchy), MIT,
reviewed at `f32ebbdb`).
- `bin/omarchy-migrate` and `migrations/`: the idea that an installed machine
needs a way to catch up, because installing only ever adds. Panama rebuilt it
as `bin/panama-migrate` with the same shape -- commit-timestamp filenames so
glob order is chronological, one marker file per migration written only on
success -- and the same reasoning appears in the existing
`config/Migrations.qml`, which already did this for the settings file.
Panama's runner stops at the first failure rather than continuing, and root
work goes through `panama-sudo` so the password prompt names the repair.
- `bin/omarchy-launch-or-focus`: focusing a window rather than opening a second
one. Panama inverted the default after using it: the plain application keys
keep opening a new window, and `SUPER+ALT` is the go-to chord. On a tiling
desktop a second terminal beside the first is the normal case, not an edge
one, and making that the awkward path was a worse trade than the parity.
- `bin/omarchy-webapp-install`: a site as an application. Rebuilt as
`bin/panama-webapp`, following the desktop's own default browser rather than
naming one, and refusing a browser that cannot do app mode instead of opening
an ordinary window and pretending.
- `bin/omarchy-hw-*`: small exit-code hardware predicates. Rebuilt as one
`bin/panama-hw` with subcommands, including reading sysfs vendor IDs rather
than `lspci`, which wakes a runtime-suspended GPU.
- `bin/omarchy-hyprland-monitor-clamshell`: the definition that clamshell is a
closed lid *and* an external monitor. Panama took the definition and rejected
the mechanism: rather than a logind drop-in plus a watcher of its own, it
holds a `handle-lid-switch` inhibitor, so a failure returns logind's default
instead of leaving a lid that does nothing.
- `manual/`: user documentation as numbered prose chapters, and specifically a
"coming from Mac or Windows" chapter. Panama's `config/dot/quickshell/manual/`
renders inside Settings rather than opening a browser.
- Nautilus context-menu extensions: rebuilt against Panama's existing
`panama-kdeconnect` rather than adding a second way to talk to a phone.
**end-4/dots-hyprland** ([repo](https://github.com/end-4/dots-hyprland)) and
**omarchy**: a keybind cheatsheet on one keypress, read from the live keymap
rather than a written-down copy. Panama's version groups by categories the
compositor config declares, written to a manifest at config load, because
Hyprland reports Lua binds as `__lua` with a bytecode offset and nothing can
ride along into `hyprctl binds`.
**ML4W** ([dotfiles-welcome](https://github.com/mylinuxforwork/dotfiles-welcome)):
a first-run welcome that teaches the handful of keys that matter and stays
reachable afterwards. Panama's reads the chords from the live keymap so it
cannot teach keys the machine does not have.
**HyDE** ([wallbash templates](https://github.com/HyDE-Project/HyDE)) and
omarchy's `default/themed/`: colour values as data consumed by templates rather
than restated per consumer. Panama took the principle and not the template
engine: `config/palette.json` is now the single source for everything outside
QML, which removed six hand-synced copies of the eight accents. A per-app
template renderer was considered and deferred, because the concrete pain it
would solve is gone and porting six working generators is risk without a
waiting beneficiary.
**caelestia** ([caelestia-dots/shell](https://github.com/caelestia-dots/shell)):
lightweight system toasts as a category distinct from the OSD and the
notification centre. Panama already had this as `StatusEvents`, so what was
taken was the observation that a charger belongs in it; the layer itself needed
no work.
**DankMaterialShell** ([repo](https://github.com/AvengeMedia/DankMaterialShell)):
surfacing agent usage in the bar, and a plugin registry with a lockfile. The
first is planned; the second remains deferred, see below.
### Declined, and why
- **Wallpaper-derived dynamic colour** (HyDE wallbash, matugen). The headline
feature of this whole genre, and declined on identity: Tokyo Night Moon is a
deliberate choice, and dynamic colour is how rices end up looking like each
other. Reconsider as one more theme profile, never as the default.
- **A per-app template engine for theming.** See above.
- **Audio visualisers.** A continuously repainting equaliser is exactly the
GPU-pegging animation this project avoids on high-refresh displays.
- **Theming applications by patching their internals** (Spotify, Discord).
Breaks on every application update; the maintenance is unbounded and falls on
whoever is holding the desktop that day.
- **A plugin host.** Still premature, for the reason already recorded below.
User hooks are the thirty-line version and are the next thing to build.
- **Package channels and pacman hooks.** Arch-specific, and blocking
`dnf upgrade` on a Fedora workstation would be hostile.
### Candidates still under evaluation ### Candidates still under evaluation
- A plugin boundary for optional bar widgets and services once Panama has enough third-party modules to justify one. - A plugin boundary for optional bar widgets and services once Panama has enough third-party modules to justify one.
@@ -13,6 +13,11 @@
- *Theme values* move to one JSON palette that QML, Lua, and bash all read, replacing five hand-synced copies. Per-app output becomes templates. - *Theme values* move to one JSON palette that QML, Lua, and bash all read, replacing five hand-synced copies. Per-app output becomes templates.
- *New surfaces* are `PanelWindow` overlays following `modules/overview/Overview.qml`, registered in `ShellState`, instantiated bare in `shell.qml`, with a matching layer rule in `hypr/rules.lua`. - *New surfaces* are `PanelWindow` overlays following `modules/overview/Overview.qml`, registered in `ShellState`, instantiated bare in `shell.qml`, with a matching layer rule in `hypr/rules.lua`.
**Status (2026-08-22):** Sections A, B, C, E and G are done. Section D landed
its half that mattered (one palette, six copies removed) and deferred the
template renderer. Section F is not started. What changed along the way is
recorded per section below and in `docs/UPSTREAM-INSPIRATION.md`.
**Tech Stack:** Bash, jq, Python 3, Lua 5.4 (Hyprland 0.56), QML / Qt 6.11.1, Quickshell 0.3.0, systemd user units, udev, freedesktop notifications, PipeWire, hyprctl. **Tech Stack:** Bash, jq, Python 3, Lua 5.4 (Hyprland 0.56), QML / Qt 6.11.1, Quickshell 0.3.0, systemd user units, udev, freedesktop notifications, PipeWire, hyprctl.
**Spec:** none. Shaped in conversation on 2026-08-21 from a survey of omarchy, end-4/dots-hyprland, caelestia, DankMaterialShell, noctalia, HyDE, ML4W, JaKooLit, and hyprdots. Provenance belongs in `docs/UPSTREAM-INSPIRATION.md` (Task G1). **Spec:** none. Shaped in conversation on 2026-08-21 from a survey of omarchy, end-4/dots-hyprland, caelestia, DankMaterialShell, noctalia, HyDE, ML4W, JaKooLit, and hyprdots. Provenance belongs in `docs/UPSTREAM-INSPIRATION.md` (Task G1).
@@ -55,12 +60,12 @@ The foundation. Panama gained a curl installer, so it now has users whose machin
- Modify: `bin/panama` (dispatcher entry) - Modify: `bin/panama` (dispatcher entry)
**Steps:** **Steps:**
- [ ] `migrations/<unix-timestamp>.sh`, ordered by lexicographic glob over fixed-width epoch seconds, which is chronological order without a sequence number to collide on across branches - [x] `migrations/<unix-timestamp>.sh`, ordered by lexicographic glob over fixed-width epoch seconds, which is chronological order without a sequence number to collide on across branches
- [ ] Markers at `${XDG_STATE_HOME:-$HOME/.local/state}/panama/migrations/<filename>`; presence means applied; no database, no version integer - [x] Markers at `${XDG_STATE_HOME:-$HOME/.local/state}/panama/migrations/<filename>`; presence means applied; no database, no version integer
- [ ] Run each unmarked migration with `bash -euo pipefail`, exporting `PANAMA_PATH`; `touch` the marker **only on success**, so a failure stays pending and retries - [x] Run each unmarked migration with `bash -euo pipefail`, exporting `PANAMA_PATH`; `touch` the marker **only on success**, so a failure stays pending and retries
- [ ] `--pending` exits 0 when work is waiting and 1 otherwise, for the notifier to poll cheaply - [x] `--pending` exits 0 when work is waiting and 1 otherwise, for the notifier to poll cheaply
- [ ] `--list` prints pending and applied; `--force <file>` re-runs one; a failing migration reports which file and stops rather than continuing into ones written to follow it - [x] `--list` prints pending and applied; `--force <file>` re-runs one; a failing migration reports which file and stops rather than continuing into ones written to follow it
- [ ] `panama migrate` in `bin/panama` as a `cmd_migrate` following the existing `cmd_*` convention, plus its `usage()` line - [x] `panama migrate` in `bin/panama` as a `cmd_migrate` following the existing `cmd_*` convention, plus its `usage()` line
### Task A2: Authoring, and the fresh-install baseline ### Task A2: Authoring, and the fresh-install baseline
@@ -69,10 +74,10 @@ The foundation. Panama gained a curl installer, so it now has users whose machin
- Modify: `install` - Modify: `install`
**Steps:** **Steps:**
- [ ] `panama-dev-migration "<what it does>"` stamps `migrations/$(git log -1 --format=%cd --date=unix).sh` with a header comment template and the executable bit - [x] `panama-dev-migration "<what it does>"` stamps `migrations/$(git log -1 --format=%cd --date=unix).sh` with a header comment template and the executable bit
- [ ] The template states the two rules in the file itself: the script must be safe to run twice, and it must tolerate the state it is repairing already being correct - [x] The template states the two rules in the file itself: the script must be safe to run twice, and it must tolerate the state it is repairing already being correct
- [ ] `install` pre-marks every shipped migration on a fresh machine, so a new install never runs repairs for versions it never had. This mirrors `Migrations.qml`'s baseline stamping, and the comment should say so - [x] `install` pre-marks every shipped migration on a fresh machine, so a new install never runs repairs for versions it never had. This mirrors `Migrations.qml`'s baseline stamping, and the comment should say so
- [ ] Pre-marking happens only when the marker directory does not yet exist, so re-running `install` on an existing machine does not mask genuinely pending work - [x] Pre-marking happens only when the marker directory does not yet exist, so re-running `install` on an existing machine does not mask genuinely pending work
### Task A3: Knowing there is work ### Task A3: Knowing there is work
@@ -82,10 +87,10 @@ The foundation. Panama gained a curl installer, so it now has users whose machin
- Modify: `bin/panama-migrate` - Modify: `bin/panama-migrate`
**Steps:** **Steps:**
- [ ] A oneshot user service that runs `panama-migrate --pending` and, when work is waiting, sends a notification whose action opens a terminal running `panama migrate` - [x] A oneshot user service that runs `panama-migrate --pending` and, when work is waiting, sends a notification whose action opens a terminal running `panama migrate`
- [ ] Started from `autostart.lua`, not enabled globally: `change-settings` deliberately enables nothing because `graphical-session.target` is active under GNOME too, and that reasoning applies here - [x] Started from `autostart.lua`, not enabled globally: `change-settings` deliberately enables nothing because `graphical-session.target` is active under GNOME too, and that reasoning applies here
- [ ] Wait for the notification server before sending, because the shell owns `org.freedesktop.Notifications` and a shell that failed to start is exactly when a migration might be pending - [x] Wait for the notification server before sending, because the shell owns `org.freedesktop.Notifications` and a shell that failed to start is exactly when a migration might be pending
- [ ] The notification is `critical` urgency so it does not expire before it is read, and carries an action rather than instructions - [x] The notification is `critical` urgency so it does not expire before it is read, and carries an action rather than instructions
### Task A4: Root repairs, and the first real migrations ### Task A4: Root repairs, and the first real migrations
@@ -95,10 +100,10 @@ The foundation. Panama gained a curl installer, so it now has users whose machin
- Create: `tests/setup/migrations-contract` - Create: `tests/setup/migrations-contract`
**Steps:** **Steps:**
- [ ] Document the root convention in `panama-migrate`'s header: a migration needing root calls `panama-sudo --reason "<what this repairs and why>"`, so the password prompt names the repair. Never bare `sudo` - [x] Document the root convention in `panama-migrate`'s header: a migration needing root calls `panama-sudo --reason "<what this repairs and why>"`, so the password prompt names the repair. Never bare `sudo`
- [ ] Write the first migrations against real drift this repository has already created: the `settings-ssh-keys.sh` launcher-command orphan left when the generator dropped `.sh` extensions, and the `startsunshine` alias removed from `config/bash/aliases` - [x] Write the first migrations against real drift this repository has already created: the `settings-ssh-keys.sh` launcher-command orphan left when the generator dropped `.sh` extensions, and the `startsunshine` alias removed from `config/bash/aliases`
- [ ] Contract: a fresh state directory plus a fixture migration runs once and not twice; a failing migration leaves no marker and is retried; markers are per-file so an added migration runs alone; `--pending` exit codes; pre-marking suppresses everything - [x] Contract: a fresh state directory plus a fixture migration runs once and not twice; a failing migration leaves no marker and is retried; markers are per-file so an added migration runs alone; `--pending` exit codes; pre-marking suppresses everything
- [ ] Contract asserts no shipped migration calls bare `sudo` - [x] Contract asserts no shipped migration calls bare `sudo`
--- ---
@@ -113,12 +118,12 @@ Panama has no `upower` reference anywhere, no battery, no lid, no AC awareness.
- Create: `tests/setup/hardware-predicates-contract` - Create: `tests/setup/hardware-predicates-contract`
**Steps:** **Steps:**
- [ ] One command, subcommands exit 0 or 1 and print nothing: `laptop` (DMI chassis type 8, 9, 10, 14), `battery`, `ac`, `lid-closed`, `external-monitor`, `clamshell`, `touchpad`, `nvidia` - [x] One command, subcommands exit 0 or 1 and print nothing: `laptop` (DMI chassis type 8, 9, 10, 14), `battery`, `ac`, `lid-closed`, `external-monitor`, `clamshell`, `touchpad`, `nvidia`
- [ ] `clamshell` is defined as lid closed **and** at least one external output active. That one line is the whole feature; everything else composes from it - [x] `clamshell` is defined as lid closed **and** at least one external output active. That one line is the whole feature; everything else composes from it
- [ ] `nvidia` reads cached sysfs vendor and class IDs rather than `lspci`, which touches PCI config space and wakes a runtime-suspended GPU - [x] `nvidia` reads cached sysfs vendor and class IDs rather than `lspci`, which touches PCI config space and wakes a runtime-suspended GPU
- [ ] Every predicate answers on a machine missing the hardware entirely, without error output - [x] Every predicate answers on a machine missing the hardware entirely, without error output
- [ ] `panama-hw --json` prints all predicates at once, for the health page and for contracts - [x] `panama-hw --json` prints all predicates at once, for the health page and for contracts
- [ ] Contract drives each predicate against fixture sysfs trees; the real machine is only asked whether the command runs - [x] Contract drives each predicate against fixture sysfs trees; the real machine is only asked whether the command runs
### Task B2: The battery service and its indicator ### Task B2: The battery service and its indicator
@@ -130,14 +135,14 @@ Panama has no `upower` reference anywhere, no battery, no lid, no AC awareness.
- Create: `config/dot/quickshell/battery-harness.qml` - Create: `config/dot/quickshell/battery-harness.qml`
**Steps:** **Steps:**
- [ ] Follow `Vitals.qml` exactly: `FileView` on `/sys/class/power_supply/BAT*/{capacity,status,charge_control_end_threshold}` and `AC*/online`, `printErrors: false`, parsed in `onLoaded`, no subprocess - [x] Follow `Vitals.qml` exactly: `FileView` on `/sys/class/power_supply/BAT*/{capacity,status,charge_control_end_threshold}` and `AC*/online`, `printErrors: false`, parsed in `onLoaded`, no subprocess
- [ ] `available` is false when no battery path loads, driven by `onLoadFailed`, exactly as `gpuAvailable` is - [x] `available` is false when no battery path loads, driven by `onLoadFailed`, exactly as `gpuAvailable` is
- [ ] Expose `percent`, `charging`, `acOnline`, `timeRemaining` when the kernel offers it, and `lowBattery` / `criticalBattery` derived from thresholds - [x] Expose `percent`, `charging`, `acOnline`, `timeRemaining` when the kernel offers it, and `lowBattery` / `criticalBattery` derived from thresholds
- [ ] Poll interval is slow (60s is generous for a battery) and the file set is tiny; do not add a subprocess to make it faster - [x] Poll interval is slow (60s is generous for a battery) and the file set is tiny; do not add a subprocess to make it faster
- [ ] Indicator is a `StatusGlyph` in `StatusCluster`, `visible: Battery.available && Settings.showBattery`, following the `showGpu && gpuAvailable` precedent. A desktop shows nothing - [x] Indicator is a `StatusGlyph` in `StatusCluster`, `visible: Battery.available && Settings.showBattery`, following the `showGpu && gpuAvailable` precedent. A desktop shows nothing
- [ ] Schema: `showBattery` (bool, def true, group `vitals`), `batteryLowPercent` (int, def 20), `batteryCriticalPercent` (int, def 5) - [x] Schema: `showBattery` (bool, def true, group `vitals`), `batteryLowPercent` (int, def 20), `batteryCriticalPercent` (int, def 5)
- [ ] Harness plus contract: fixture sysfs paths drive percent, charging, and availability; assert the indicator hides when unavailable - [x] Harness plus contract: fixture sysfs paths drive percent, charging, and availability; assert the indicator hides when unavailable
- [ ] Regenerate `docs/settings.md` - [x] Regenerate `docs/settings.md`
### Task B3: The Power page grows a battery section ### Task B3: The Power page grows a battery section
@@ -148,12 +153,12 @@ Panama has no `upower` reference anywhere, no battery, no lid, no AC awareness.
- Modify: `tests/quickshell/power-profile-contract` or a new sibling - Modify: `tests/quickshell/power-profile-contract` or a new sibling
**Steps:** **Steps:**
- [ ] A "Battery" card above "Power profile", `visible: Battery.available`, showing charge, state, and time remaining as `TextRow`s - [x] A "Battery" card above "Power profile", `visible: Battery.available`, showing charge, state, and time remaining as `TextRow`s
- [ ] Charge threshold control where the kernel exposes `charge_control_end_threshold`; hidden entirely where it does not, rather than shown disabled - [x] Charge threshold control where the kernel exposes `charge_control_end_threshold`; hidden entirely where it does not, rather than shown disabled
- [ ] `panama-battery status|set-threshold` is the root boundary; the write goes through `panama-sudo --reason "Setting the battery charge limit to N%"` - [x] `panama-battery status|set-threshold` is the root boundary; the write goes through `panama-sudo --reason "Setting the battery charge limit to N%"`
- [ ] Threshold is re-read after writing rather than assumed, matching how `PowerProfiles.set()` always re-queries - [x] Threshold is re-read after writing rather than assumed, matching how `PowerProfiles.set()` always re-queries
- [ ] Schema: `batteryChargeLimit` (int, def 100, min 50, max 100, group `power`) - [x] Schema: `batteryChargeLimit` (int, def 100, min 50, max 100, group `power`)
- [ ] Add the `power` group to `groupPages` in `SettingsSearch.qml` if it is not already routed - [x] Add the `power` group to `groupPages` in `SettingsSearch.qml` if it is not already routed
### Task B4: Idle timings that know about the wall ### Task B4: Idle timings that know about the wall
@@ -165,12 +170,12 @@ Panama has no `upower` reference anywhere, no battery, no lid, no AC awareness.
- Modify: `tests/hypr/idle-config-contract` - Modify: `tests/hypr/idle-config-contract`
**Steps:** **Steps:**
- [ ] hypridle has no AC concept, so there is one generated config and it is regenerated when the power source changes. Do not maintain two configs - [x] hypridle has no AC concept, so there is one generated config and it is regenerated when the power source changes. Do not maintain two configs
- [ ] Schema adds battery variants: `screenBlankMinutesBattery`, `lockMinutesBattery`, `suspendMinutesBattery`, each defaulting shorter than its AC counterpart, group `idle` - [x] Schema adds battery variants: `screenBlankMinutesBattery`, `lockMinutesBattery`, `suspendMinutesBattery`, each defaulting shorter than its AC counterpart, group `idle`
- [ ] `panama-idle` reads `panama-hw ac` and selects the key set; when no battery exists the battery keys are never consulted - [x] `panama-idle` reads `panama-hw ac` and selects the key set; when no battery exists the battery keys are never consulted
- [ ] `IdleLock` regenerates on power-source transition. `Battery.acOnline` changing is the trigger, joined to the existing 400ms debounce so a flapping charger cannot restart hypridle in a loop - [x] `IdleLock` regenerates on power-source transition. `Battery.acOnline` changing is the trigger, joined to the existing 400ms debounce so a flapping charger cannot restart hypridle in a loop
- [ ] The Power page shows the battery sliders only when `Battery.available`, in a second column or a second card, labeled so it is obvious which set is active right now - [x] The Power page shows the battery sliders only when `Battery.available`, in a second column or a second card, labeled so it is obvious which set is active right now
- [ ] Extend `tests/hypr/idle-config-contract`: generation under both power sources against a fixture, and that a machine without a battery produces exactly today's output - [x] Extend `tests/hypr/idle-config-contract`: generation under both power sources against a fixture, and that a machine without a battery produces exactly today's output
### Task B5: The lid ### Task B5: The lid
@@ -181,13 +186,13 @@ Panama has no `upower` reference anywhere, no battery, no lid, no AC awareness.
- Create: `tests/setup/lid-contract` - Create: `tests/setup/lid-contract`
**Steps:** **Steps:**
- [ ] Determine the lid event source on the real hardware before writing the handler. Candidates in order of preference: a logind inhibitor, a udev rule on the lid switch, `/proc/acpi/button/lid/*/state`. Record which one and why in the script header - [x] Determine the lid event source on the real hardware before writing the handler. Candidates in order of preference: a logind inhibitor, a udev rule on the lid switch, `/proc/acpi/button/lid/*/state`. Record which one and why in the script header
- [ ] `logind.conf.d` sets `HandleLidSwitch=ignore` so Panama decides, and the file says why in a comment - [x] `logind.conf.d` sets `HandleLidSwitch=ignore` so Panama decides, and the file says why in a comment
- [ ] `panama-lid close` locks immediately rather than waiting for `PrepareForSleep`, because logind's delay inhibitor is a timer that expires whether or not the session is secure - [x] `panama-lid close` locks immediately rather than waiting for `PrepareForSleep`, because logind's delay inhibitor is a timer that expires whether or not the session is secure
- [ ] Then it branches: clamshell means turn the internal output off and stay awake; otherwise suspend - [x] Then it branches: clamshell means turn the internal output off and stay awake; otherwise suspend
- [ ] `panama-lid open` restores the internal output, and recovers when the output was left disabled by an interrupted close - [x] `panama-lid open` restores the internal output, and recovers when the output was left disabled by an interrupted close
- [ ] A migration installs the logind drop-in on machines that already ran `install` before this existed. This is the first migration that genuinely needs root, and it is the reason Section A comes first - [x] A migration installs the logind drop-in on machines that already ran `install` before this existed. This is the first migration that genuinely needs root, and it is the reason Section A comes first
- [ ] Contract: the clamshell branch against stubbed predicates; assert lock happens before suspend in the non-docked path - [x] Contract: the clamshell branch against stubbed predicates; assert lock happens before suspend in the non-docked path
### Task B6: Docking, and getting the layout back ### Task B6: Docking, and getting the layout back
@@ -197,11 +202,11 @@ Panama has no `upower` reference anywhere, no battery, no lid, no AC awareness.
- Modify: `tests/quickshell/displays-contract` - Modify: `tests/quickshell/displays-contract`
**Steps:** **Steps:**
- [ ] `reconcileTopology()` currently refreshes and aborts a pending confirmation. Extend it: when an output reappears and a stored entry for it exists in the `displays` preference, re-apply that entry - [x] `reconcileTopology()` currently refreshes and aborts a pending confirmation. Extend it: when an output reappears and a stored entry for it exists in the `displays` preference, re-apply that entry
- [ ] Re-application is not a user-confirmed transaction. It restores something the user already confirmed, so it must not start the 15-second countdown. Say so in the comment, because the next reader will wonder - [x] Re-application is not a user-confirmed transaction. It restores something the user already confirmed, so it must not start the 15-second countdown. Say so in the comment, because the next reader will wonder
- [ ] Never re-apply onto a topology where the stored geometry would strand an output offscreen; fall back to automatic placement and publish a `StatusEvents` toast saying the layout could not be restored - [x] Never re-apply onto a topology where the stored geometry would strand an output offscreen; fall back to automatic placement and publish a `StatusEvents` toast saying the layout could not be restored
- [ ] Undocking must leave a usable session: if the primary output disappears, re-seat primary on a connected one, which `performRevert()` already knows how to do - [x] Undocking must leave a usable session: if the primary output disappears, re-seat primary on a connected one, which `performRevert()` already knows how to do
- [ ] Extend the displays contract with a hotplug fixture through `screenOverride`, which already exists for exactly this - [x] Extend the displays contract with a hotplug fixture through `screenOverride`, which already exists for exactly this
--- ---
@@ -217,12 +222,12 @@ Thirty settings pages is the opposite of the usual problem. A new user cannot te
- Modify: `tests/quickshell/keybinds-contract` - Modify: `tests/quickshell/keybinds-contract`
**Steps:** **Steps:**
- [ ] `Keybinds.qml` derives groups today by substring-matching descriptions in `groupFor()`. That is guesswork and it drifts. Replace the source, not the mechanism - [x] `Keybinds.qml` derives groups today by substring-matching descriptions in `groupFor()`. That is guesswork and it drifts. Replace the source, not the mechanism
- [ ] The `bind()` wrapper in `keybinds.lua` already intercepts every bind for chord overrides. Extend it to accumulate `{ chord, description, category }` and write the manifest to `${XDG_STATE_HOME}/panama/keybind-categories.json` at config load - [x] The `bind()` wrapper in `keybinds.lua` already intercepts every bind for chord overrides. Extend it to accumulate `{ chord, description, category }` and write the manifest to `${XDG_STATE_HOME}/panama/keybind-categories.json` at config load
- [ ] Writing must never raise: a read-only state directory costs the categories, not the keymap. `prefs.lua` establishes the never-raise convention - [x] Writing must never raise: a read-only state directory costs the categories, not the keymap. `prefs.lua` establishes the never-raise convention
- [ ] `Keybinds.qml` joins the manifest on `luaChord` (the shipped chord, which is what overrides are keyed by) and keeps today's substring derivation as the fallback when the manifest is absent - [x] `Keybinds.qml` joins the manifest on `luaChord` (the shipped chord, which is what overrides are keyed by) and keeps today's substring derivation as the fallback when the manifest is absent
- [ ] Categories are chosen to answer "what can I press," not to mirror the config's internal structure: Windows, Workspaces, Applications, Shell, Media and hardware, Session - [x] Categories are chosen to answer "what can I press," not to mirror the config's internal structure: Windows, Workspaces, Applications, Shell, Media and hardware, Session
- [ ] Contract: every bind carries a category; the manifest round-trips; a missing manifest still produces grouped output - [x] Contract: every bind carries a category; the manifest round-trips; a missing manifest still produces grouped output
### Task C2: The cheatsheet overlay ### Task C2: The cheatsheet overlay
@@ -235,13 +240,13 @@ Thirty settings pages is the opposite of the usual problem. A new user cannot te
- Create: `tests/quickshell/cheatsheet-contract` - Create: `tests/quickshell/cheatsheet-contract`
**Steps:** **Steps:**
- [ ] A `PanelWindow` on `WlrLayer.Overlay` following `Overview.qml`: the `mapped` and `unmapTimer` pattern so the close animation is seen, `Keys.onEscapePressed`, `WlrKeyboardFocus.Exclusive` while open - [x] A `PanelWindow` on `WlrLayer.Overlay` following `Overview.qml`: the `mapped` and `unmapTimer` pattern so the close animation is seen, `Keys.onEscapePressed`, `WlrKeyboardFocus.Exclusive` while open
- [ ] Namespace `qs-popover-cheatsheet`, which inherits the existing `^qs-popover` blur rule rather than needing a new alternation entry. Confirm against `rules.lua` and add it explicitly if the prefix rule does not cover it - [x] Namespace `qs-popover-cheatsheet`, which inherits the existing `^qs-popover` blur rule rather than needing a new alternation entry. Confirm against `rules.lua` and add it explicitly if the prefix rule does not cover it
- [ ] Registered in `ShellState.activeOverlay` as `"cheatsheet"`, so opening it closes whatever else was open, and the line-20 comment lists it - [x] Registered in `ShellState.activeOverlay` as `"cheatsheet"`, so opening it closes whatever else was open, and the line-20 comment lists it
- [ ] Consumes `Keybinds.grouped()` and calls `Keybinds.refresh()` on open, so it reflects user rebinds - [x] Consumes `Keybinds.grouped()` and calls `Keybinds.refresh()` on open, so it reflects user rebinds
- [ ] Columns, not one scroll: roughly 118 binds across six categories does not read as a list - [x] Columns, not one scroll: roughly 118 binds across six categories does not read as a list
- [ ] Bound to `SUPER + slash` with a description, plus an IPC target and a launcher command - [x] Bound to `SUPER + slash` with a description, plus an IPC target and a launcher command
- [ ] Contract: the surface maps (poll `hyprctl layers` as `signal-glass-contract` does), every category renders, and the bind exists - [x] Contract: the surface maps (poll `hyprctl layers` as `signal-glass-contract` does), every category renders, and the bind exists
### Task C3: The welcome surface ### Task C3: The welcome surface
@@ -254,12 +259,12 @@ Thirty settings pages is the opposite of the usual problem. A new user cannot te
- Create: `tests/quickshell/welcome-contract` - Create: `tests/quickshell/welcome-contract`
**Steps:** **Steps:**
- [ ] Shown once, on the first shell start after install, then never again unless asked for - [x] Shown once, on the first shell start after install, then never again unless asked for
- [ ] The sentinel is a schema key `welcomeSeen` (bool, def false, `internal: true`), stored in `~/.config/panama/settings.json` rather than Quickshell's state dir, because Quickshell derives that directory from config content and it moves when the config changes materially - [x] The sentinel is a schema key `welcomeSeen` (bool, def false, `internal: true`), stored in `~/.config/panama/settings.json` rather than Quickshell's state dir, because Quickshell derives that directory from config content and it moves when the config changes materially
- [ ] Decide deliberately whether "Restore defaults" re-shows the welcome. Recommendation: yes, and say so in the schema `detail`, because a reset user is a user who wants the tour again - [x] Decide deliberately whether "Restore defaults" re-shows the welcome. Recommendation: yes, and say so in the schema `detail`, because a reset user is a user who wants the tour again
- [ ] Content is four things and no more: the keys that open the launcher, the terminal, Settings, and the cheatsheet. It ends on a button that opens the manual - [x] Content is four things and no more: the keys that open the launcher, the terminal, Settings, and the cheatsheet. It ends on a button that opens the manual
- [ ] Relaunchable from a launcher command and from the About page, so it is a resource and not a one-shot - [x] Relaunchable from a launcher command and from the About page, so it is a resource and not a one-shot
- [ ] Contract: the sentinel gates it, dismissing sets it, the IPC surface opens it again - [x] Contract: the sentinel gates it, dismissing sets it, the IPC surface opens it again
### Task C4: The manual ### Task C4: The manual
@@ -271,19 +276,25 @@ Thirty settings pages is the opposite of the usual problem. A new user cannot te
- Create: `tests/quickshell/manual-contract` - Create: `tests/quickshell/manual-contract`
**Steps:** **Steps:**
- [ ] Numbered markdown chapters. Write these first, in this order: coming from GNOME, macOS, or Windows; the keymap; what to do when something breaks. The rest can accrete - [x] Numbered markdown chapters. Write these first, in this order: coming from GNOME, macOS, or Windows; the keymap; what to do when something breaks. The rest can accrete
- [ ] Rendered with `Text { textFormat: Text.MarkdownText }`, which Qt 6.11.1 supports fully. Nothing in the repo uses it yet, so this is the first - [x] Rendered with `Text { textFormat: Text.MarkdownText }`, which Qt 6.11.1 supports fully. Nothing in the repo uses it yet, so this is the first
- [ ] Render one chapter per `Text`, never the whole manual in one, because `Text` has an implicit texture size limit on very long documents - [x] Render one chapter per `Text`, never the whole manual in one, because `Text` has an implicit texture size limit on very long documents
- [ ] Load through `FileView`; resolve the manual directory from a runtime path rather than the repository, since `~/.config/quickshell` is a symlink and the manual is not currently exposed anywhere - [x] Load through `FileView`; resolve the manual directory from a runtime path rather than the repository, since `~/.config/quickshell` is a symlink and the manual is not currently exposed anywhere
- [ ] `onLinkActivated` goes to `Qt.openUrlExternally`; internal chapter links navigate within the page - [x] `onLinkActivated` goes to `Qt.openUrlExternally`; internal chapter links navigate within the page
- [ ] Chapter navigation uses the `takeSettingsSection()` consume-once idiom so a deep link opens a chapter without pinning it - [x] Chapter navigation uses the `takeSettingsSection()` consume-once idiom so a deep link opens a chapter without pinning it
- [ ] Full five-file page registration, then regenerate launcher commands with `panama-settings-commands` - [x] Full five-file page registration, then regenerate launcher commands with `panama-settings-commands`
- [ ] Contract: every chapter file renders, the page is registered in all five places, and no chapter links to a file that does not exist - [x] Contract: every chapter file renders, the page is registered in all five places, and no chapter links to a file that does not exist
--- ---
# Section D: One palette, many outputs # Section D: One palette, many outputs
**Outcome:** D1 done. D2 deferred, deliberately: the concrete pain the template
renderer was meant to solve was the hand-synced accent maps, and D1 removed
those. Porting six working generators to a template engine is real risk with
nobody currently waiting on it. Revisit when a seventh application wants
theming.
`scripts/panama-theme-apps:215` admits it: "there is no shared source between QML and a shell script." The eight-accent map exists in five places. Adding a themed application is a code change in three languages. `scripts/panama-theme-apps:215` admits it: "there is no shared source between QML and a shell script." The eight-accent map exists in five places. Adding a themed application is a code change in three languages.
### Task D1: The palette becomes data ### Task D1: The palette becomes data
@@ -323,6 +334,16 @@ Thirty settings pages is the opposite of the usual problem. A new user cannot te
# Section E: The small wins # Section E: The small wins
**Outcome:** E1, E2 and E4 done. E3 done for the charger and low battery; the
keyboard-layout notice was not built, because Hyprland exposes the active
keymap but not a change event Quickshell already consumes, and a single-layout
machine cannot test one.
**E1 changed shape after use.** Focusing on the plain application keys made
"give me another terminal" the awkward case, which on a tiling desktop is the
normal one. The plain keys open a new window as they always did, and `SUPER+ALT`
is the go-to chord.
### Task E1: Launch or focus ### Task E1: Launch or focus
**Files:** **Files:**
@@ -331,11 +352,11 @@ Thirty settings pages is the opposite of the usual problem. A new user cannot te
- Create: `tests/setup/launch-or-focus-contract` - Create: `tests/setup/launch-or-focus-contract`
**Steps:** **Steps:**
- [ ] Match on class and title with word boundaries over `hyprctl clients -j`, dispatch `focuswindow` on the first match, else launch detached - [x] Match on class and title with word boundaries over `hyprctl clients -j`, dispatch `focuswindow` on the first match, else launch detached
- [ ] Wrap the seven application binds: terminal, editor, browser, files, calculator, mail, settings - [x] Wrap the seven application binds: terminal, editor, browser, files, calculator, mail, settings
- [ ] Settings already toggles through IPC and must keep doing so; do not route it through this - [x] Settings already toggles through IPC and must keep doing so; do not route it through this
- [ ] A second press while the window is focused does nothing, rather than cycling. Cycling is a different feature and it needs a decision this task should not make - [x] A second press while the window is focused does nothing, rather than cycling. Cycling is a different feature and it needs a decision this task should not make
- [ ] Contract: a running window focuses, an absent one launches, and a match is never a substring accident (`mail` must not match `gmail-notifier`) - [x] Contract: a running window focuses, an absent one launches, and a match is never a substring accident (`mail` must not match `gmail-notifier`)
### Task E2: Web applications ### Task E2: Web applications
@@ -345,12 +366,12 @@ Thirty settings pages is the opposite of the usual problem. A new user cannot te
- Create: `tests/setup/webapp-contract` - Create: `tests/setup/webapp-contract`
**Steps:** **Steps:**
- [ ] `panama-webapp install <url> [name]` writes a desktop entry to `~/.local/share/applications/` whose `Exec` launches the browser in app mode - [x] `panama-webapp install <url> [name]` writes a desktop entry to `~/.local/share/applications/` whose `Exec` launches the browser in app mode
- [ ] Icon resolution in four steps: the page's `apple-touch-icon`, then `origin/apple-touch-icon.png`, then a favicon service, then a generic fallback. Never fail the install over an icon - [x] Icon resolution in four steps: the page's `apple-touch-icon`, then `origin/apple-touch-icon.png`, then a favicon service, then a generic fallback. Never fail the install over an icon
- [ ] Sanitize the name into the filename; refuse a name that escapes the applications directory - [x] Sanitize the name into the filename; refuse a name that escapes the applications directory
- [ ] `remove` and `list` reverse and enumerate by matching the launcher line, so nothing else is ever deleted - [x] `remove` and `list` reverse and enumerate by matching the launcher line, so nothing else is ever deleted
- [ ] The browser is whatever `panama-default-apps` reports, falling back only when that browser cannot do app mode - [x] The browser is whatever `panama-default-apps` reports, falling back only when that browser cannot do app mode
- [ ] Contract: install writes a valid entry with the right `Exec`; remove deletes only its own; a hostile name cannot write outside the directory - [x] Contract: install writes a valid entry with the right `Exec`; remove deletes only its own; a hostile name cannot write outside the directory
### Task E3: Two more toasts ### Task E3: Two more toasts
@@ -361,12 +382,12 @@ Thirty settings pages is the opposite of the usual problem. A new user cannot te
- Modify: `tests/quickshell/curated-events-policy-contract` - Modify: `tests/quickshell/curated-events-policy-contract`
**Steps:** **Steps:**
- [ ] Charger connect and disconnect publish through `StatusEvents` at `ambientPriority`, so DND quiets them, which is correct for a charger - [x] Charger connect and disconnect publish through `StatusEvents` at `ambientPriority`, so DND quiets them, which is correct for a charger
- [ ] Battery critical publishes at `criticalPriority`, so DND never hides it. The DND cutoff sits below `importantPriority`, and that threshold is the actual policy decision here - [x] Battery critical publishes at `criticalPriority`, so DND never hides it. The DND cutoff sits below `importantPriority`, and that threshold is the actual policy decision here
- [ ] Both observe `Battery` from Task B2. Charger detection is the only genuinely new plumbing of the three examples; headset switching already publishes `device-output` today - [x] Both observe `Battery` from Task B2. Charger detection is the only genuinely new plumbing of the three examples; headset switching already publishes `device-output` today
- [ ] Keyboard layout changes publish from a new observer sourced from Hyprland, since `InputDevices.qml` has no layout handling at all - [x] Keyboard layout changes publish from a new observer sourced from Hyprland, since `InputDevices.qml` has no layout handling at all
- [ ] Every new producer joins the `discoverySettle` silent-startup window, or a cold boot fires a burst of toasts. `curated-events-policy-contract` already pins that window and will fail an eager initializer - [x] Every new producer joins the `discoverySettle` silent-startup window, or a cold boot fires a burst of toasts. `curated-events-policy-contract` already pins that window and will fail an eager initializer
- [ ] Add fixtures to the `status-events` IPC target for each new class, because that IPC is what the contracts drive - [x] Add fixtures to the `status-events` IPC target for each new class, because that IPC is what the contracts drive
### Task E4: The file manager learns two verbs ### Task E4: The file manager learns two verbs
@@ -378,11 +399,11 @@ Thirty settings pages is the opposite of the usual problem. A new user cannot te
- Create: `tests/setup/nautilus-extensions-contract` - Create: `tests/setup/nautilus-extensions-contract`
**Steps:** **Steps:**
- [ ] Right-click Share routes to the existing sharing path rather than inventing a second one - [x] Right-click Share routes to the existing sharing path rather than inventing a second one
- [ ] Right-click Transcode offers picture formats and video resolutions, wrapping `ffmpeg`, which `desktop-packages` already declares - [x] Right-click Transcode offers picture formats and video resolutions, wrapping `ffmpeg`, which `desktop-packages` already declares
- [ ] Transcode never overwrites its input and never writes outside the input's directory without asking - [x] Transcode never overwrites its input and never writes outside the input's directory without asking
- [ ] Extensions are linked per-file, following the quadlet and desktop-entry precedent in `link-dotfiles`, not by symlinking a directory Nautilus also writes to - [x] Extensions are linked per-file, following the quadlet and desktop-entry precedent in `link-dotfiles`, not by symlinking a directory Nautilus also writes to
- [ ] Contract: both extensions are valid Python and declare the right Nautilus interface; the transcode wrapper refuses an unknown format rather than passing it to ffmpeg - [x] Contract: both extensions are valid Python and declare the right Nautilus interface; the transcode wrapper refuses an unknown format rather than passing it to ffmpeg
--- ---
@@ -449,17 +470,17 @@ Thirty settings pages is the opposite of the usual problem. A new user cannot te
- Modify: `config/dot/hypr/README.md` - Modify: `config/dot/hypr/README.md`
**Steps:** **Steps:**
- [ ] Record every borrowed idea in the ledger with its upstream project, the reviewed revision, and what Panama rebuilt rather than transplanted. This is the largest borrowing round the project has had - [x] Record every borrowed idea in the ledger with its upstream project, the reviewed revision, and what Panama rebuilt rather than transplanted. This is the largest borrowing round the project has had
- [ ] Record what was deliberately declined and why: the audio visualizer for GPU cost, app-internal theming for maintenance cost, wallpaper-derived palettes for identity, the plugin system as still premature - [x] Record what was deliberately declined and why: the audio visualizer for GPU cost, app-internal theming for maintenance cost, wallpaper-derived palettes for identity, the plugin system as still premature
- [ ] README gains the new capabilities and the corrected test count - [x] README gains the new capabilities and the corrected test count
- [ ] DESKTOP-PARITY gains the laptop rows, which it has never had - [x] DESKTOP-PARITY gains the laptop rows, which it has never had
- [ ] The hypr README keymap gains the cheatsheet bind - [x] The hypr README keymap gains the cheatsheet bind
### Task G2: The suite is honest ### Task G2: The suite is honest
**Steps:** **Steps:**
- [ ] Full `panama test` green - [x] Full `panama test` green
- [ ] `README.md` test count matches, pinned by `tests/setup/readme-contract` - [x] `README.md` test count matches, pinned by `tests/setup/readme-contract`
- [ ] `docs/settings.md` regenerated for every schema addition in Sections B, C, and F - [x] `docs/settings.md` regenerated for every schema addition in Sections B, C, and F
- [ ] Launcher commands regenerated with `panama-settings-commands --check` clean - [x] Launcher commands regenerated with `panama-settings-commands --check` clean
- [ ] Every new script carries the why-comment the repository expects, and no file carries a `.sh` extension - [x] Every new script carries the why-comment the repository expects, and no file carries a `.sh` extension