diff --git a/docs/superpowers/specs/2026-08-20-panama-fresh-install-design.md b/docs/superpowers/specs/2026-08-20-panama-fresh-install-design.md new file mode 100644 index 0000000..48d51ed --- /dev/null +++ b/docs/superpowers/specs/2026-08-20-panama-fresh-install-design.md @@ -0,0 +1,345 @@ +# Panama: fresh-install correctness and the sunhat deprecation + +Panama installs a desktop it does not fully provision. The Quickshell shell names +fonts nothing installs, a cursor theme nothing installs, and a wallpaper that does +not exist; four settings pages shell out to packages no list declares. None of this +shows up on this machine, because this machine accumulated those pieces under +sunhat and never lost them. It shows up on the next machine. + +This spec closes that gap, moves every question the installer needs to the front of +the run, and ports the parts of sunhat that still matter — so sunhat can be deleted. + +## Why now + +Recent work built a large Quickshell desktop: a bar, dock, overview, lock screen, +notification server, and a Settings application with thirty-odd pages. Almost all +of it was verified against a running system. Almost none of it was verified against +a system where Panama had just been installed for the first time. + +The existing `tests/quickshell/declared-dependencies-contract.sh` was written for +exactly this class of bug and reports PASS, because it checks only commands that +scripts invoke. Fonts, cursor themes, wallpapers, and packages consumed by GUI +handoffs are invisible to it. A dependency checker with a blind spot is worse than +none, because it reports PASS — that argument is already in that file's own header +comment, and it applies to the checker itself. + +## What is broken + +Verified against the repository at `3b01f1e`. + +| Gap | Evidence | +|---|---| +| No fonts installed | `setup/scripts/change-settings:32` sets `VictorMono Nerd Font 10`. `kitty`, `ghostty`, `hyprlock.conf.template`, `Theme.qml` and `Fonts.qml` all name Nerd Font families. No stage installs a font. `Fonts.qml` documents that a mono family without Nerd glyphs replaces every icon in the shell with tofu. | +| No cursor theme installed | `oreo_blue_cursors` is named in `change-settings:33`, both GTK `settings.ini.template` files, `config/dot/uwsm/env:23`, `config/dot/hypr/env.lua:52`, and `PreferenceSchema.qml:985`. It exists in `~/.local/share/icons` on this machine only. | +| No wallpaper | `PreferenceSchema.qml:826` defaults `wallpaperPath` to `""`. No image ships. | +| `gnome-control-center` undeclared | `SystemSettings.qml:599` allow-lists 23 GNOME panels; 15+ rows across `OnlineAccountsPage`, `RegionPage`, `AccessibilityPage`, `ConnectivityPage`, `ShortcutsPage`, `SoundPage`, `PrivacyPage` and `HealthPage` call `openGnomePanel`. No list declares the package. | +| `snapper` undeclared | `panama-snapshots` drives the entire Snapshots page through `snapper`. | +| `espanso`, `ghostty` undeclared | Both have configs symlinked by `link-dotfiles`; neither is in a package list. | +| `.bashrc` breaks on a fresh box | `config/bash/.bashrc` ends with an unguarded `. "$HOME/.cargo/env"`, which does not exist until `rustup-init` has run, so every shell start emits an error. The file also carries a committed `# >>> Codex installer >>>` block. | +| README describes a desktop that is not installed | The Desktops section advertises GNOME with Forge, Dash-to-Dock, Openbar and Vitals. Nothing in `setup/` installs or configures any of it. | + +## Decisions + +These were settled during design and are recorded so the plan does not relitigate +them. + +**The GNOME session is cut; `gnome-control-center` stays.** These are different +things and only the first is free. The session — Forge, Dash-to-Dock, Openbar, +Vitals, the README's parity claims — is dead weight and goes. The control center is +load-bearing: it is where Panama's own Settings sends you for Online Accounts, +Region, Accessibility, Color, Wellbeing and Wacom, and commit `0c364f3` shows the +handoff list was deliberately narrowed to what Panama does not own. It becomes a +declared dependency rather than an accident of this machine's history. + +**The installer asks everything up front, then runs untouched.** sunhat's failure +mode was a prompt or a failure twenty minutes in, with a person needed at the +keyboard to get past it. Panama's answer is not "no prompts" — some of what the +installer needs genuinely changes between machines and between years, and a stale +hardcoded answer is worse than a question. The answer is that every question is +asked before any package work begins, and nothing asks again afterwards. + +**Answers are not persisted.** Each run asks fresh. The only default shown is the +current hostname, which today's installer already shows. This is a deliberate +simplicity choice: no answers file to keep in sync, nothing personal on disk, and +re-answering six questions costs less than maintaining state that can go stale. + +**Hardware is detected, then confirmed.** Gib's machines span AMD, Intel and NVIDIA +in both laptops and desktops. The installer probes — `lspci` for GPUs, +`mokutil --sb-state` for secure boot, DMI for chassis — and asks a question that +names what it found: *"Found NVIDIA RTX 4070 — install akmod-nvidia and enroll a +secure boot key? [Y/n]"*. Detection alone would remove the ability to decline a +proprietary driver on a machine that has the card; asking alone would make a person +recite their own hardware. + +**Nothing personal is committed.** Panama should be cloneable by someone else and +yield a working desktop. Identity, hostnames and machine layout come from prompts +and from gitignored local state — `~/.config/panama/settings.json` is already +exactly this — never from files in the repository. + +**Applications are admitted one at a time, and dnf or flatpak only.** A short +always-installed core, plus categories chosen from a `gum` checklist during the +interview, so a work laptop does not acquire emulators and a desktop does not skip +Steam. But the menu starts nearly empty and grows by explicit decision, one +application per decision — it is not seeded by porting sunhat's list. + +The rule is that every application Panama installs must come from dnf or from +Flathub, with flatpak preferred where both exist. Anything requiring a pinned RPM +URL, a `curl | bash`, or a source build is admitted only case by case, with a stated +reason why no packaged form exists. + +sunhat is the argument for this rule, not a counterexample to it. Its +`sources/dotenv.sh` hardcodes `upscayl-2.11.5`, `lact-0.5.4-0.x86_64.fedora-40.rpm` +and `jdk-22` — URLs that were stale within a release cycle and that turn a re-run +into a 404. Every application Panama declares is one it has to keep installable, +and the cost of that promise is only bearable if a package manager is keeping it. + +Panama already carries three exceptions: the `oh-my-posh` and `bun` install scripts +in `install-packages`, and `helium-browser-bin` from the `lionheartp/Hyprland` COPR. +They stay — each is idempotent and guarded — but they are the ceiling, not a +precedent. New exceptions need the same standard. + +**Bash scripts carry no `.sh` extension.** A shebang and the executable bit select +the interpreter; the extension is redundant. This applies to the 159 existing files +as well as new ones. + +## Design + +### The installer + +`./install` becomes four movements: + +``` +ascii → interview → stages (uninterrupted) → panama-doctor summary +``` + +The interview is a new script, `setup/scripts/interview`, that runs before any +other stage and asks, in order: + +- Hostname — defaulting to the current one, as today +- Git identity: name, email, editor +- Run `gh auth login`? — skipped without asking when `gh auth status` already passes +- Generate an SSH key? — skipped without asking when one already exists +- Hardware confirmations, each naming what was detected +- Remove Fedora's preinstalled bloat? +- Update firmware with `fwupdmgr`? +- Which application extras to install — a `gum` checklist + +Answers reach the stages as `PANAMA_*` environment variables, but not by the +obvious route. Stages run as child processes of `install`, and a child cannot export +into its parent or its siblings — so the interview cannot simply set variables and +expect the next stage to see them. Instead it writes `KEY=value` lines to a +`mktemp` file whose path `install` chose; `install` sources that file, exports the +names, and deletes it on the existing `trap ... EXIT INT TERM` that already restores +the idle settings. The answers exist for the length of one run and never touch a +durable path, which is what keeps "no persisted answers" honest. + +The interview is therefore listed in `STAGES` like any other stage, but `install` +treats it specially: it is the only stage whose output the installer reads back. + +A stage that needs an answer reads its variable and treats absent as "no". This +matters for re-running an individual stage by hand, which is a debugging path that +must keep working, and it means the interview is never a prerequisite for repairing +one piece of a machine. + +The run ends with `panama-doctor`, printing its summary. Today the only signal that +an install worked is a failed-stage count; a failure that produced no non-zero exit +— a font that did not land, a service that did not start — scrolls past unnoticed. +`docs/UPSTREAM-INSPIRATION.md` already lists a first-run health check as a +candidate; the health check now exists, so this is wiring rather than building. + +### Stages + +The `STAGES` array in `install` is explicit rather than glob-ordered, and stays that +way. The new order: + +| Stage | Status | Responsibility | +|---|---|---| +| `interview` | new | Every prompt, before any work | +| `install-packages` | edited | Repos, package lists, extras, debloat | +| `install-fonts` | new | Nerd Fonts and MS core fonts | +| `install-cursors` | new | Cursor themes into `~/.local/share/icons` | +| `install-hardware` | new | NVIDIA, akmods, secure boot, firmware — all conditional | +| `link-dotfiles` | edited | Adds the wallpaper; drops `forge` | +| `change-settings` | edited | Unchanged in substance | +| `link-vicinae-scripts` | unchanged | | +| `setup-identity` | new | git config, `gh auth login`, SSH key | + +Ordering constraints that are not obvious and must not be broken: + +- `install-fonts` and `install-cursors` precede `change-settings`, because + `change-settings` sets gsettings values naming a font family and a cursor theme. + Setting them before they exist is not an error, but it does mean the first + session renders with fallbacks. +- `setup-identity` follows `install-packages`, because `gh` is installed there. +- `install-hardware` runs late; MOK enrollment sets a password consumed at the next + boot, and firmware updates may prompt for a reboot. Neither should sit in front of + twenty minutes of package work. + +Every stage stays independently re-runnable and independently failable, which is +the existing contract and the reason Panama exists. + +### Fonts + +`install-fonts` installs the families the desktop actually names, checking +`fc-list` before downloading so a re-run costs nothing. VictorMono Nerd Font is +required — it is the shell's icon font. The remaining sunhat families (CascadiaMono, +FiraMono, JetBrainsMono, Meslo, iA Writer Mono) are carried across because Panama's +own font picker exists to choose among them, and a picker with one option is not a +picker. MS core fonts come from the msttcore installer, as in sunhat. + +### Cursors + +`install-cursors` places `oreo_blue_cursors` — and the purple and white variants, +since `PreferenceSchema.qml` exposes `cursorTheme` as a choice — into +`~/.local/share/icons`. Skipped per-theme when already present. + +### Wallpaper + +`config/wallpapers/faroe_islands.jpg` (4000x2444, 644K) is committed. `link-dotfiles` +copies it to `~/Pictures/Wallpapers/` when absent, and seeds `wallpaperPath` in +`settings.json` **only when that key is still empty**. An existing choice is never +overwritten — the same rule every other seeding step in `link-dotfiles` already +follows for kitty, tmux, GTK and hyprlock. + +### The assets contract + +A new `tests/quickshell/declared-assets-contract` covers what the dependency +contract structurally cannot. It fails when: + +- A font family named in `config/` or in a `gsettings set` call is not installed by + `install-fonts` +- A cursor theme named in `config/` is not installed by `install-cursors` +- The default `wallpaperPath` names a file the repository does not ship +- A package invoked by a settings-page helper or a GUI handoff is undeclared +- A directory under `config/dot/` is neither in `link-dotfiles`'s `dirs` array nor + given explicit handling + +All four of today's gaps fail this test. That is the acceptance criterion for the +test itself: it must be written against the broken tree and observed to fail before +the fixes land. + +It is named without a `.sh` extension from the start, while its siblings still carry +one until phase 6. That inconsistency is deliberate and temporary — new files follow +the convention immediately rather than being written wrong and renamed later. + +### GNOME excision + +`config/dot/forge` is deleted and `forge` removed from `link-dotfiles`'s `dirs`. +The README's Desktops section is rewritten to describe one desktop. The comment in +`link-dotfiles` explaining why GNOME and Hyprland coexist goes with it. + +`gnome-control-center` is added to `desktop-packages` with a comment naming what +depends on it, so a future reader does not mistake it for GNOME-session residue and +remove it. `gnome-settings-hyprland.desktop` stays. + +### The `panama` command + +Three subcommands are added to the existing dispatcher, which was written to grow +this way: + +- `panama doctor` — front `panama-doctor` from a terminal, not only from Settings +- `panama test` — run every contract under `tests/`, with a pass/fail summary. The + suite is 119 files with no entry point and no mention in the README; it is the + main safety net and it is currently invisible. +- `panama upgrade` — re-run `./install` from anywhere + +### Dropping the `.sh` extension + +159 files: 110 under `tests/quickshell`, 47 under +`config/local/share/vicinae/scripts`, 2 under `tests/hypr`. All are already +executable and all already carry a bash shebang, so the rename is `git mv` plus +three comment references and two lines in `panama-settings-commands`, which +generates the Vicinae command files and globs for `settings-*.sh`. + +The one real risk is Vicinae's script discovery. Its documentation states a script +command needs a plain text file, the three `@vicinae.*` directives, a shebang, and +the executable bit — no extension requirement. That is close enough to a guarantee +to act on, but not close enough to skip verifying: after the rename, reload with +`vicinae cmd launch core:reload-scripts` and confirm all 47 commands still resolve +before the change is considered done. If discovery does key off the extension, the +Vicinae directory is exempted and the reason recorded here. + +### Machine-specific configuration + +Monitor layout, keybinds and input differences stay in gitignored local state — +`~/.config/panama/settings.json`, written by the Settings application. Panama ships +defaults; a machine deviates locally. No per-hostname directories enter the +repository, because that is what "cloneable by someone else" costs. + +This is a decision to *not* build something. sunhat branched its Hyprland config on +hostname and it was one of the things that made sunhat unmaintainable. + +## Ported from sunhat + +Everything below blocks sunhat's deletion. + +- **NVIDIA and secure boot** — `akmod-nvidia`, `xorg-x11-drv-nvidia-cuda`, MOK + enrollment via `mokutil --import`, and the `rebuild_nvidia_kmod` helper. sunhat's + version opened an editor to hand-edit grub; the replacement does not, because a + step that requires a human mid-run is the failure this repository exists to avoid. +- **Fedora debloat** — removing `gnome-contacts`, `gnome-tour`, `gnome-maps`, + `totem` and the LibreOffice components, behind an interview question. +- **Firmware** — `fwupdmgr refresh` and `update`, behind an interview question. + Panama's Updates page covers this after installation; the installer covers the + first run. +- **Applications** — the extras *mechanism*, and only the applications explicitly + agreed. sunhat's ~70 installers are a list of candidates to review, not a manifest + to port; most of them install by methods that no longer work. Reviewing them is + its own task, done one application at a time against the dnf/flatpak rule above, + and it does not block anything else in this spec. +- **Firefox** — the package itself, which Panama does not currently install, plus + sunhat's `themes/firefox` userChrome. Helium is the daily driver; Firefox stays + because web development means checking a second engine. If Helium supports + userChrome, it gets the equivalent treatment. + +Deliberately not ported: sunhat's `configs/docker/compose_files`. Those are server +workloads — caddy, gitea, homeassistant, nextcloud_aio, pihole, vaultwarden, +wireguard, searxng — and belong to whatever runs them, not to a workstation +installer. They remain recoverable from sunhat's git history. + +## Repository hygiene + +- Prune the worktrees under `.worktrees/` whose branches are already in `main`. +- Audit the seven branches that are not: `feat/panama-displays`, + `feat/panama-settings-pages`, `feat/roadmap-completion` (5 commits), + `feat/settings-home-completion` (3), `feat/settings-sound`, + `fix/settings-backup-complete` (2), `fix/settings-sidebar-scroll`. Report what is + in each before anything is merged or deleted. +- Guard the `.cargo/env` source in `config/bash/.bashrc` and remove the Codex block. +- Rewrite the README: one desktop, the real install stages, and the test suite. +- Audit `docs/settings.md` (21K) for claims that no longer hold. + +## Phases + +Each phase is independently shippable and independently useful. + +1. **Install correctness** — fonts, cursors, wallpaper, the five undeclared + packages, the `.bashrc` fixes, and the assets contract. Closes every gap that + would visibly break a fresh machine. +2. **The interview** — up-front prompts, hardware detection, `panama-doctor` at the + end of the run. +3. **Hardware flows** — NVIDIA, secure boot, debloat, firmware. +4. **Applications and theming** — the extras menu mechanism, Firefox and its theme. + The menu ships with the categories agreed at the time and nothing more; filling + it is ongoing work, one application per decision, not part of this phase. +5. **GNOME excision and documentation** — Forge removal, README rewrite, + `docs/settings.md` audit. +6. **Hygiene** — the `.sh` rename, worktree pruning, the branch audit, and the three + new `panama` subcommands. + +## Verification + +Panama is not going to be tested by reinstalling Fedora. Confidence comes from +three places instead: + +- **The assets contract**, which encodes each gap found here as a test that fails on + the broken tree. +- **`panama doctor` at the end of every install**, which reports what is actually + running rather than what was attempted. +- **`panama test`**, which makes the existing 119 contracts runnable in one command + for the first time. + +The open risk this leaves is anything that breaks only on a genuinely clean system +and that none of the three notices — a repository that fails to enable, a package +renamed between Fedora releases. That risk is accepted; a VM run can settle it later +if the fixes here prove insufficient.