# Backlog — the prioritized task queue **This is the only executable work list for agents.** Product themes and v1.0 intent live in [`docs/VISION.md`](../docs/VISION.md); design history in [`docs/ROADMAP.md`](../docs/ROADMAP.md); map in [`docs/README.md`](../docs/README.md) and [`agent/README.md`](README.md). **Rules:** - Agents take the topmost actionable item (see LOOP.md). Finished items are **deleted** here — the journal + git log are the record; durable design notes get a ✓-entry in docs/ROADMAP.md (and/or a note in VISION) if worth keeping. - Item numbers are **stable IDs** — never renumbered or reused. A gap in the sequence means shipped (or dropped) work; new items take the next free number regardless of tier. - Tags: `[blocked:hw]` needs real hardware (see HARDWARE-QUEUE.md) · `[human]` needs Bernardo · `[stuck]` two failed attempts, needs help · `[big]` must be split before starting. - Agents may append to **PROPOSED** and **Decisions** freely (include `VISION § …` or `ROADMAP § …` when relevant); only Bernardo moves items *out* of PROPOSED into the tiers. --- ## NOW *(empty — NEXT's top item is the queue head)* ## NEXT ### 42. Default theme → boreal (`VISION § B` / v1.0 bar) Ship Boreal as the distro default instead of Tokyo Night: root `theme-state.json`, `templates/downstream/theme-state.json` (and any greeter/plymouth fallbacks that hardcode Tokyo Night when state is missing). Preset already exists under `themes/boreal/`. Capture `preview.png` if still missing so the picker looks first-class. Cost: copy/apply palette into seed state files + grep for tokyo-night defaults; V0 contrast, V1/V2 theme apply, V3 visual on hardware. ### 43. System › Firmware — fwupd product surface (`VISION § A`) `services.fwupd.enable` is already default-on and never auto-flashes, but users only learn about `fwupdmgr` from a README sentence. Add a self-gated menu entry (System submenu) and/or Control Center row that: refreshes metadata, lists updatable devices, confirms, runs `fwupdmgr update`, then prints a reboot note when needed. Never silent-flash (pillar 1). Optional: first-boot / MOTD one-liner when the daemon is active. Spec: docs/HARDWARE.md §4. Cost: menu script + optional MOTD; V2 menu, V3 on real LVFS hardware. ### 44. Doctor hardware section — read-only (`VISION § A`) Extend `nomarchy-doctor` with the same pass/fail + one-fix-command contract for: NetworkManager connectivity, default PipeWire sink, optional GPU smoke (`vainfo`/`glxinfo` if present), fprintd unit when fingerprint was enabled, fwupd active (+ warn if `get-updates` non-empty), laptop charge-threshold sysfs when a limit is set. Spec: docs/HARDWARE.md §10. Cost: doctor.sh sections; V1 in existing `checks.doctor` where fakes exist, rest V2/V3. ### 14. Automated lock bumps — confirm the first real run Slices b+c shipped 2026-07-05 (`.gitea/workflows/bump.yml`): weekly schedule (Mon 05:17 UTC) + `workflow_dispatch` as the security fast-lane; `nix flake update` → eval gate → pathspec-limited lock commit pushed to `main` on green (that push triggers check.yml as the second net). Update/commit/push logic simulated locally end-to-end in a scratch clone, incl. a real update; today's bumped lock evals green. Remaining (not confirmable from a session): the first scheduled or dispatched run must land — watch that the runner picks up the cron and that the Actions token can push. Then delete this item. Item 20's KVM runner later upgrades the gate from eval-only to the VM suite. ### 20. KVM runner → VM suite in CI `[human]` The remaining stretch of the CI item — checks-on-push is live and **green** (run #58; runner = gitea/act_runner docker, eval tier). Register a second runner on a host with `/dev/kvm` + nix (host-mode label `nix-kvm`), then an agent uncomments the workflow's `vm-checks` job: the `checks.*` VM suite + real toplevel/HM builds on every push that later upgrades item 14's bump gate from eval-only to the full suite. ### 33. rofi page-overflow hint (scrollbar) (Raised by Bernardo, 2026-07-07 — item 3.) When a list/grid overflows onto another page, nothing signals there's more below. Add a scrollbar (rofi's `scrollbar` element) or a "more ↓" hint, themed to the palette, on the launcher grid and the long text menus. Check rofi 2.0.0's scrollbar support/theming. _Scoped 2026-07-07:_ rofi only draws the scrollbar when the list actually overflows (`lines < rows`), so enabling it self-hides on short menus — the right primitive. But it's a **visual, ~10-file slice**: the generated palette theme (`rofi.nix`, `scrollbar = false`) **plus every authored `.rasi`** (boreal/nord/lumon/kanagawa/retro-82/neon-glass/executive-slate/ summer-day/summer-night) — all deliberately set `scrollbar { width:0 }` today, each needs a per-theme handle width/colour. Payoff is V3-only (rendering), so it wants a themed session, not a headless edit. ### 41. Floating-window audit — remaining (needs hardware class checks) (Raised by Bernardo, 2026-07-07 — item 11.) Broaden the `windowrule` float set beyond the conservative first cut. _Conservative cut shipped 2026-07-07 (iteration #63, on Bernardo's "take a conservative approach"):_ float + center the mixer (item 35), **blueman** (manager/adapters) and **system-config-printer** — the only shipped, unambiguous config dialogs whose classes are confident. **Remaining candidates, deliberately deferred because their window class can't be verified headlessly** (a guessed class = dead rule): the polkit auth prompt (hyprpolkitagent — no discoverable class in the package), GTK file-chooser portals (`xdg-desktop-portal-gtk`), and any pinentry dialog. Next slice: on hardware, run `hyprctl clients` while each is open, read the real `class`, then append rules. Also revisit whether blueman/s-c-p actually float once seen (regex tolerance for the `.…-wrapped` form). ## LATER - **Wallpapers artifact split** (ROADMAP § Faster switches — decided, deferred): pinned `Nomarchy-wallpapers` input so a state write stops re-copying 86 MB. Follow-on: pre-built theme variants if switches are still slow after. - **Installer round 2** (ROADMAP § Installer): multi-disk BTRFS RAID, impermanence, BIOS/legacy boot. - **Boot-from-snapshot**: a systemd-boot equivalent of grub-btrfs. - **Night-light geo mode**: lat/long auto sunset/sunrise (means wlsunset). - **Per-theme icon overrides** / more icon packs (ROADMAP § Icon themes). - **MIPI/IPU software-ISP camera** support (no-UVC machines). - **VPN exit-node richer display** (country/city) (optional). - **NixOS release bump → v2** `[human]`: deliberate, hand-edited, never automated; the previous attempt was discarded (2026-06-22) over a Hyprland OOM blocker — see MEMORY.md before retrying (NOW#3 should also soften that blocker class). ## PROPOSED (agent suggestions — await human triage) *Agents: append here with a one-paragraph pitch (what/why/cost). Do not implement. Bernardo moves accepted items into a tier.* ### v1.0 track — see `docs/VISION.md` Product themes and the release bar live in **VISION**. Promoted to **NEXT:** #42 boreal default · #43 System › Firmware · #44 doctor hardware. Remaining candidate promotions: fingerprint menu · first-boot tips · human rebuild errors · neon-glass quarantine. ### Pre-existing - **Battery charge-limit — make the toggle instant** `[blocked:hw]` (follow-up to the preset toggle shipped 2026-07-07, iteration #55). The control-center now has a proper toggle (Off / 80% / 90% / 60% / Custom…) writing `settings.power.batteryChargeLimit` — but that's a baked NixOS option, so it only lands on the next `sys-rebuild`. To make it instant (no rebuild): add a udev rule making `/sys/class/power_supply/BAT*/charge_control_end_threshold` group-writable (`MODE`/`GROUP`) so the menu can `echo N >` it live alongside persisting state (the sysfs oneshot in power.nix still owns boot + AC-replug re-apply), and/or surface a rofi-menu quick row or a Waybar action. `[blocked:hw]`: the sysfs write + no-rebuild effect can only be confirmed on a laptop exposing the threshold. Decide first whether the small privilege grant (a local user can set the charge cap) is acceptable. - **Portal/Flatpak camera picker still lists the internal IR sensor** (ROADMAP § Webcam follow-up). The shipped IR-hide is a *WirePlumber v4l2* rule, but Flatpak/portal apps consume cameras via the **libcamera** path, where both sensors remain visible — a Flatpak Zoom user can still pick the black IR "camera". Options, roughly ascending cost: (a) do nothing — document it (portal camera support is still rare in practice); (b) a WirePlumber *libcamera* monitor rule disabling GREY-only nodes — needs verifying that libcamera monitor rules can match early enough (the v4l2 investigation found only `device.api` binds pre-rule, which is why surgical scoping failed before — same wall likely applies); (c) a libcamera configuration/udev quirk hiding the IR sensor at the libcamera layer itself. Cost: (b)/(c) need a T14s-style RGB+IR machine to verify → pairs with a hardware-queue session. Recommend (a) now, (b) investigated when the T14s is next available. ### Bugs / broken behavior (codebase exam 2026-07-09) - **Waybar doctor indicator never shows an icon** `modules/home/waybar.nix` `nomarchy-doctor-status` prints `"text":""` on failure. Waybar custom modules with empty `text` self-hide, so the tripwire never becomes visible (tooltip-only if it rendered at all). Intent in comments is opposite: appear only when the sheet has a ✖. Fix: non-empty glyph (e.g. `󰒡` / `!`) + `class: bad` when unhealthy; exit 0 / no output when healthy. Cost: one-line script fix; V2 with existing `checks.doctor` if extended, else V1. - **neon-glass theme ships a broken Waybar whole-swap** `themes/neon-glass/waybar.css` uses `@text` / `@base` / `@accent` / etc. with **no** `@define-color` block. Whole-swap CSS replaces the entire stylesheet with no palette prepend (`waybar.nix`), so applying neon-glass yields undefined / broken bar colors (same class as the purged stub CSS, JOURNAL #45). Also missing `waybar.jsonc`, `preview.png`, `btop.theme`; CSS covers only a subset of modules. Fix: either finish (self-contained colors + layout parity + preview) **or** remove `waybar.css` until ready so apply falls back to the generated bar. Cost: medium polish or small quarantine delete; V2/V3 visual. - **Control Center appearance toggles are no-ops without a rebuild** `nomarchy-control-center.sh` `set_state` always uses `--no-switch`. Blur / gaps (and similar appearance keys) only take effect after a home switch, but the UI never runs one and never warns (unlike Updates/Bluetooth which print "requires rebuild"). User flips look identical until a later rebuild. Fix: run switch for appearance keys, or refuse / warn clearly. Cost: small UX fix in one script. - **Bluetooth menu row not self-gated** System submenu always offers Bluetooth and `exec blueman-manager`. If `nomarchy.system.bluetooth.enable = false`, blueman is not installed → silent/broken launch. Printers / colorpicker / audio gate correctly. Fix: gate on `command -v blueman-manager` (or the settings flag). Cost: tiny; V0 + manual. - **summer-day workspace scroll dispatches differently from every other bar** `themes/summer-day/waybar.jsonc` uses `workspace e+1` / `e-1` (next **empty** workspace); summer-night, executive-slate, boreal use `r+1` / `r-1` (relative). Likely leftover; behavior differs under scroll. Fix: align to `r±1`. Cost: two lines. - **Battery charge-limit / powerprofile only match `BAT*` sysfs names** `power.nix` oneshot and menu/waybar globs use `/sys/class/power_supply/BAT*`. `nomarchy-battery-notify` correctly treats any system battery (`type=Battery`, not `scope=Device`). Machines with `CMB0` (and similar) get notify but not charge-cap / inconsistent gating. Fix: name-agnostic scan aligned with battery-notify. Cost: small multi-file; `[blocked:hw]` to confirm on non-BAT* hardware. ### Docs / option-surface drift (codebase exam 2026-07-09) - **`option-docs` would fail: undocumented `nomarchy.hardware.i2c.*`** Live options `nomarchy.hardware.i2c.enable` and `.ddcci` in `modules/nixos/hardware.nix`; distro default turns ddcci **on** (`modules/nixos/default.nix` `mkDefault true`). No README table rows, no template comments. Simulated `checks.option-docs` finds exactly these two. Also: option default is false while module default is true — document the effective default. Cost: docs + optional template opt-in comments; would green option-docs. - **README / docs factual drift pack** (single docs pass or split): (1) theme count still says **21** — repo has **24** presets (`README.md`, `docs/TESTING.md`, `templates/downstream/README.md`); (2) waybar whole-swap list still names catppuccin/lumon/nord/ retro-82 — stubs purged; live CSS whole-swaps are summer-day, summer-night, executive-slate, boreal, neon-glass; (3) idle table still says "suspend **30**" — code is **15 min battery-only** (`idle.nix`; ROADMAP already correct); (4) Tailscale row says `sudo tailscale up` but module grants `--operator=` / template says no sudo; (5) `hosts/live.nix` header still says "No installer yet"; (6) CONVENTIONS parity list omits **boreal** (has full waybar.jsonc). Cost: pure docs; high trust payoff. - **Keybind cheatsheet: Network still labeled nmtui** `keybinds.nix` `desc = "Network (nmtui)"` but the module runs `networkmanager_dmenu`. Cheatsheet inherits the wrong label. Cost: one string. - **`nomarchy-menu` usage string incomplete** Usage omits implemented modules (`doctor`, `controlcenter`, `rollback`, …). Misleading for direct invocation / scripting. Cost: one string in `rofi.nix`. - **Template / seed state drift (residual after SoT work)** (1) `templates/downstream/theme-state.json` lacks `border` and `settings` keys present in repo root state (eval merges defaults); (2) no commented `nomarchy.system.snapper.enable` or `hardware.i2c.*` in template `system.nix` despite opt-in convention. Main home/system sync is **Installer consumes template as SoT** below — do that first, then close these residual gaps on the template itself. Cost: small template polish. - **Installer ↔ template SoT — follow-ups** (shipped 2026-07-09: copy + `patch-template.py`). Remaining: (1) optional CI that the install share ships the same template files; (2) V2 `test-install.sh` after ISO rebuild (HM pre-activate now pulls starter packages — larger closure); (3) residual theme-state / i2c comment polish on template (see residual seed drift item). - **Secondary docs polish** README layout tree omits many real modules and pkgs (doctor/control-center/battery-notify); `docs/MIGRATION.md` unlinked from README nav and has a placeholder flake-init URL; OVERRIDES.md example still pipes screenshots to **swappy** while shipped annotator is **satty**. Cost: docs-only. ### Theme polish / completeness (codebase exam 2026-07-09) - **Missing theme picker previews + hand btop for identity themes** `boreal`, `executive-slate`, and `neon-glass` lack `preview.png` (picker falls back to plain-name rows) and `btop.theme` (generated btop only). CSS/jsonc for boreal/executive-slate are otherwise solid. Capture previews after neon-glass is fixed/quarantined. Cost: asset capture + optional hand btop; V3 visual. - **Generated Waybar missing identity-bar affordances** Whole-swap bars ship `custom/powermenu` and a left logo → main menu; the generated `waybar.nix` bar has neither. Optional parity add for default-theme users. Cost: small waybar.nix + CSS; parity rule says whole-swaps must get the same modules if added. - **summer-* CSS under-styles status module states** summer-day/night include modules in selectors but largely lack `.on` / `.available` / `.recording` polish that boreal, executive-slate, and the generated style have. Functional OK; visual hierarchy weak. Cost: CSS pass; V3. - **Optional CI: whole-swap CSS self-containment** Contrast check does not require that a `waybar.css` defines every `@color` it references, nor that every preset has preview + backgrounds. A cheap gate would have caught neon-glass. Cost: small tool + flake check. ### Per-theme design audit (2026-07-09) *Method: `checks.theme-contrast` (all 24×7 pairings pass) + `tools/audit-theme-design.py` + per-theme agent review of JSON roles, ANSI, assets, whole-swaps, and fidelity to canonical sources. Gated WCAG floors are green; items below are fidelity, hierarchy, btop drift, identity semantics, and polish. Identity themes (white, vantablack, lumon, hackerman, matte-black, miasma, neon-glass) are flagged separately from fidelity bugs.* #### Broken / high-ROI fixes - **rose-pine btop is Main-on-Dawn (near-invisible hi_fg)** JSON is **Rosé Pine Dawn** (`mode: light`, base `#faf4ed`, text `#575279`) but `themes/rose-pine/btop.theme` mixes Dawn `main_bg`/`main_fg` with **Main** accents: `hi_fg=#e0def4` on `#faf4ed` ≈ invisible, `selected_bg=#524f67`, love `#eb6f92`, foam/iris Main hexes. **Fix:** rewrite btop entirely to Dawn roles (foam `#56949f`, iris `#907aa9`, love `#b4637a`, gold `#ea9d34`/`#d68a16`, pine `#286983`). Also rename display name to "Rosé Pine Dawn" (or ship a dark Main preset as a separate slug). Cost: one btop rewrite + optional rename; V1 + visual V2. - **everforest + summer-night btop: inactive_fg == main_bg** `themes/everforest/btop.theme` and `themes/summer-night/btop.theme` set `inactive_fg` to `#2d353b` (= `main_bg`) → inactive/disabled labels invisible. **Fix:** `inactive_fg` → grey1/muted range (`#859289` / `#7a8478` / JSON muted). Cost: two lines; V1. - **vantablack role inversion breaks selection / muted** `overlay` and `muted` are near-white `#fdfdfd` while base is `#0d0d0d`. Generated consumers use `overlay` as selected_bg; hand btop has `selected_bg=#fdfdfd` + `selected_fg=#ffffff` (white-on-white) and `inactive_fg=#fdfdfd` (inactive rows scream). Status greys are intentional monochrome identity. **Fix (keep void identity):** `surface #1a1a1a`, `overlay #2a2a2a`, `muted #666666`; btop `selected_bg #333` / `selected_fg #fff` / `inactive_fg` ≈ muted. Cost: JSON + btop; re-run contrast; V1–V2. - **osaka-jade: warn≈good and text/subtext inverted** `warn #459451` is green (same family as `good #549e6a`) → battery warning reads as "ok". `subtext #F6F5DD` is brighter than `text #C1C497` (secondary louder than primary). **Fix:** swap text↔subtext; set `warn #E5C736` (from bright yellow ANSI, already in palette). Keep jade accent + blossom accentAlt. Cost: small JSON retune; contrast re-check; V1. - **catppuccin-latte: ANSI black/white axis inverted + mantle sludge** Latte UI roles mostly match (base/text/accent/good/bad exact), but `ansi[0]=#bcc0cc` (light) and `ansi[7]/[15]` are darkish → terminal "black"/"white" slots misbehave; design-audit flags inverted slots. `mantle #cbcdd0` is import-darken of light base (should be Latte mantle `#e6e9ef`); `accentAlt #d260b5` is not Latte pink/mauve. **Fix:** ``` mantle #e6e9ef; accentAlt #ea76cb (pink); ansi[0] #5c5f77; ansi[7] #acb0be; ansi[8] #6c6f85; ansi[15] #bcc0cc ``` warn keep `#d6860a` if contrast requires, else `#df8e1d`. Cost: JSON only; btop already Latte-correct. V0 contrast + V1. - **catppuccin (Mocha) btop is Frappé-on-Mocha** JSON is exact Mocha; `btop.theme` uses Mocha `main_bg` but Frappé fgs/boxes (`main_fg #c6d0f5`, `hi_fg #8caaee`, mauve/green/maroon Frappé hexes). **Fix:** replace with official Mocha btop hexes (`#cdd6f4`, `#89b4fa`, …). Optional: rename to "Catppuccin Mocha". Cost: btop rewrite; V1. #### Whole-swap / identity themes - **neon-glass** — already queued above (broken waybar.css). JSON palette itself is fine (high-chroma cyber). Finish or quarantine. - **summer-day / summer-night pair polish** Structurally paired (inverted Everforest bar language) but: (1) waybar CSS hexes drift from JSON (day warn/accentAlt; night red/blue/yellow/purple vs JSON); bar follows legacy EF source, Stylix/swaync follow JSON — dual sources of truth; (2) night JSON collapses `subtext`=`muted`=`overlay` `#868d80` (no secondary ladder for generated surfaces); (3) day font 16px vs night 13px; day battery thresholds 30/15 vs night 25/10; day missing `#custom-vpn` in pill selectors; (4) day scroll `e±1` already queued. **Fix:** pick JSON as source of truth and map waybar tokens to it (or document intentional EF split); split night muted darker / subtext lighter; align font + battery thresholds + VPN pill. Cost: CSS/JSON polish pass; V3. - **boreal / executive-slate** — whole-swaps self-define colors and match JSON; best-in-class. Only gaps: `preview.png` + optional `btop.theme` (already queued). - **white (monochrome light)** — identity-ok. Status good/warn/bad are greys by design (audit hue/CVD noise expected). Optional: widen L steps (`good #555`, `warn #777`, `bad #222`), raise `surface #f0f0f0`, split `subtext #404040` while staying hueless. Do not inject traffic-light hues. Cost: optional taste retune. - **lumon (Severance blue mono-status)** — identity-ok. good/warn/bad all blue family by design; CVD collapse expected — rely on glyph/shape (item 28). Optional: dim `subtext` one step (`#9bb0c0`); increase L separation only among blues. Cost: optional; document as identity exemption in audit tooling. - **hackerman** — matrix identity: warn cyan, bad green, ANSI reds are greens. CVD collapse expected. **Keep identity** or optional cyber-semantic UI only: `warn #e8d44f`, `bad #ff5a7a` while leaving ANSI matrix. Raise `surface #1a1c2e` (flat base=surface). Cost: product call + small JSON. - **matte-black** — identity: `good=#FFC107` amber, `warn=#b91c1c` red (inverted traffic-light). Matches Omarchy matte-black. Minimal fix: `subtext #9a9a9d` (text==subtext today). Full semantic remap only if product wants conventional battery colors on this theme. Cost: one-line hierarchy or deliberate status retune. - **miasma** — earthy `bad #685742` (brown, contrast ~2.3 audit-only) is JOURNAL-exempt identity. Optional more readable brown-red `#8b5a4a` / `#a65d4e` without leaving the forest. Cost: optional. #### Fidelity / hierarchy polish (popular ports) - **gruvbox is Material medium, not classic — name + btop split** JSON hexes are Gruvbox **Material** (`text #d4be98`, material green/yellow/red) while name says "Gruvbox" and `btop.theme` is **classic** (`#ebdbb2`, `#d79921`, …). `surface`=`overlay`, `text`=`subtext`. **Fix:** rename to "Gruvbox Material" **or** retune JSON to classic; sync btop to the chosen lineage; `overlay #504945`, `subtext #a89984`. Cost: naming + assets. - **nord** — clean Polar Night. Only nit: `subtext #e5e9f0` is **brighter** than `text #d8dee9` (hierarchy inverted). **Fix:** subtext → nord4-dim or bump text to nord6 `#eceff4`. Cost: one hex. - **tokyo-night** — Night (not Storm), JSON solid. btop `main_fg`/`title` use warm `#cfc9c2` instead of Night `#c0caf5`/`#a9b1d6`. Optional: `text #c0caf5`, `accentAlt #bb9af7` (modern purple). Cost: btop + optional JSON. - **kanagawa** — Wave, clean. No action required; optional rename "Kanagawa Wave"; optional brighter good `springGreen #98bb6c`. - **ethereal** — vibe solid; `surface`=`base`, `muted`=`overlay`. **Fix:** `surface #1a2340` (or Omarchy color0 `#3C486D`); split muted darker than overlay; optional richer good. Cost: small JSON. - **retro-82** — strong synthwave + excellent rofi whole-swap. `surface #00172e` darker than `base #05182e` (inverted raise); `good #028391` teal (reads info not ok). **Fix:** `surface #0a2844`; optional `good #3f8f8a` or phosphor green. Cost: small JSON; keep rofi. - **ristretto** — Monokai Pro Ristretto, ship-quality. Only `subtext`=`text`; btop `main_bg` 1-step drift (`#2c2421` vs `#2c2525`). **Fix:** `subtext #b5a9aa`; align btop bg. Cost: tiny. - **flexoki-light** — best of the Flexoki/import set post item-28. Optional: distinct ANSI bright-black `#575653`; recapture preview if terminal chrome still looks dark-on-paper. Cost: polish only. #### Systemic theme tooling - **Import pipeline collapses hierarchy when ANSI 0==8** `tools/import-palettes.py` maps `surface←color0`, `overlay←color8`, `good/warn/bad←color2/3/1`, `mantle←darken(base)`. When color0==8 (gruvbox, everforest) surface=overlay; light themes get sludge mantle. Long-term: allow explicit role overrides independent of ANSI for identity themes; don't re-import without a hierarchy pass. Cost: tool + convention doc. - **audit-theme-design identity exemptions** Document expected noise for white/vantablack/lumon/hackerman/ matte-black/miasma (hue-family + CVD) so future audits don't "fix" identity into traffic lights. Optional: special-case in the report script. Cost: docs or small script tweak. ### Tooling / CI / installer hardening (codebase exam 2026-07-09) - **CI `py_compile` only covers theme-sync** `.gitea/workflows/check.yml` compiles only `pkgs/nomarchy-theme-sync/nomarchy-theme-sync.py`. Misses `compose-lock.py`, `tools/*.py`, `tools/vm/*.py` — syntax errors can ship. Expand to all tracked `*.py` (or at least installer + tools). Cost: one workflow line; TESTING.md §1 should match. - **No automated check for installer pure contracts** `compose-lock.py`, disko `swapSize`/`withLuks` arg contract, and hardware-db module names vs `nixos-hardware.nixosModules` have no `checks.*`. Lock bumps of nixos-hardware can rename modules and break profiled installs only on matching hardware. Cost: medium one-time check scaffolding; high long-term install safety. - **Installer soft gaps** (1) fingerprint path prefers `lsusb` but package PATH has `pciutils` not `usbutils` (sysfs fallback usually OK); (2) `chown -R 1000:100` on the flake dir assumes first-user UID/GID; (3) flake `packages` exports only theme-sync + install, not doctor / control-center / battery-notify (runtime via overlay is fine — discoverability only). Cost: small each. - **Control Center `/tmp/nomarchy-gens` for rollback list** Predictable world-writable path for generation listing — multi-user race / trash. Prefer `mktemp`. Cost: tiny. ### Distro improvement ideas (codebase exam 2026-07-09) - **Fail-closed / friendlier theme-state errors in `mkFlake`** Missing `theme-state.json` fails later at `readFile`; invalid JSON fails at `fromJSON` before theme.nix's field-level validator. A friendly "add theme-state.json" / schema pointer would help downstream authors. Related: `settings.displayProfile*` not in defaults block (consumers use `or`); unknown border role falls through to raw string. Cost: medium lib/theme.nix UX. - **Look & Feel submenu (ROADMAP optional)** Night-light, wallpaper cycle, blur/gaps (once CC appearance is fixed) grouped with Theme once the root stays six entries. Cost: menu structure only; product call. - **Export all overlay tools as flake packages** `nix build .#nomarchy-doctor` etc. for maintainers and CI convenience without going through modules. Cost: three lines in flake.nix. - **Unattended-install test matrix** `test-install.sh` always LUKS+swap; add (or document) no-swap and no-LUKS paths once those contracts are fixed, so swap/LUKS-class bugs cannot recur. Cost: script flags + time on KVM host. - **Document always-on home pieces** easyeffects, udiskie, cliphist always on with no toggle (by design) — optional README note so users know what ships and how to override. Cost: docs only. ### Hardware product (investigation 2026-07-09) *Full write-up: [`docs/HARDWARE.md`](../docs/HARDWARE.md). Install-time autodetect is strong; day-2 lifecycle (firmware, biometrics, re-detect, NVIDIA depth) is still mostly CLI. Items below are product work on top of that doc — design notes live there (§4–§10).* - **`nomarchy-detect-hw` CLI (post-install re-probe)** The installer already has pure `nomarchy_detect_hw` stdout protocol (MODULE / NOMARCHY / DETAIL). Ship it as a package on PATH that prints suggested `hardwareProfile = [ … ]` and `system.nix` snippets without reinstalling — closes the template/migration gap and “I swapped the Wi‑Fi card.” Does not rewrite the flake unless a later `--apply` is explicitly designed. Spec: docs/HARDWARE.md §8. Cost: extract script + package + man/README pointer; V0–V1. - **Fingerprint menu: enroll / list (+ optional PAM toggle)** Installer enables fprintd on known USB VIDs but enroll is CLI-only (`fprintd-enroll`) and PAM is a commented rebuild. Self-gated System row when `fprintd` is present: enroll, list, delete; optional “use for login” that writes `settings`/system intent + rebuild note (Control Center Bluetooth pattern). Spec: docs/HARDWARE.md §5. Cost: menu module; V3 enroll on real reader (`[blocked:hw]` for full path). - **Installer: NVIDIA commented system.nix guidance** Detection already adds `common-gpu-nvidia`; there is no `nomarchy.hardware.nvidia.*` and hybrid/PRIME is wiki territory. When NVIDIA is detected, emit a **commented** block in generated `system.nix` (same style as ROCm/NPU) pointing at PRIME / powerManagement / open-module and docs/HARDWARE.md §6 — not a full second stack. Cost: install script strings; V0 + doc. - **CI: hardware-db module names ∈ nixos-hardware** Every `HARDWARE_DB` third field must exist in the pinned `nixos-hardware.nixosModules` or installs break only on matching DMI. Add a pure `checks.*` (or option-docs-style runCommand). Pairs with existing “installer pure contracts” proposal. Cost: small check; V0. - **Installer: `usbutils` on PATH for fingerprint probe** Fingerprint path prefers `lsusb` but package wrap has `pciutils` only; sysfs VID walk is the fallback. Add `usbutils` to `nomarchy-install` PATH for reliable detection. Cost: one package in wrap; V0. - **Document + README table: `nomarchy.hardware.i2c.*`** `i2c.ddcci` is **mkDefault true** (external monitor brightness) but missing from README option tables → `checks.option-docs` red (also under docs-drift pack). Document in README + HARDWARE.md already mentions ddcci; close option-docs. Cost: docs; V0 with option-docs. - **Post-install hardware hints (MOTD / first session)** When relevant services exist, surface one line each: firmware (`fwupdmgr get-updates`), fingerprint (`fprintd-enroll`), optional “run nomarchy-doctor”. Avoid nagging — once or until dismissed. Spec: HARDWARE.md §4/§5. Cost: small greeter/MOTD or notify; V2/V3. - **NVIDIA first-class options (larger, optional)** Only if a maintainer commits to hybrid testing: thin `nomarchy.hardware.nvidia.*` wrapping common PRIME/power patterns. Prefer the commented-guidance item first; promote only with hardware-queue coverage. Cost: high; `[big]` + `[blocked:hw]`. ### Installer process (audit 2026-07-09) *Full-path review of live ISO → `nomarchy-install` → disko → config generation → offline/online `nixos-install` → HM pre-activate → first boot. Philosophy applied: **opinionated, stability-first** — prefer safer defaults and fewer install questions over option sprawl. Day-2 firmware / fingerprint / re-detect stay in Hardware product above, not in the installer questionnaire.* #### Already strong (do not “fix” with more choices) - Single whole-disk GPT, UEFI + systemd-boot only; multi-disk RAID / dual-boot / BIOS deferred (ROADMAP LATER). - LUKS2 **default** + greeter auto-login (one gate); type-to-confirm wipe; live medium excluded from disk list; careful pre-wipe. - BTRFS subvols + snapper on by default; hibernation swap default = RAM; hardware autodetection with **safe defaults on, heavy bits commented**. - Offline-capable install (compose-lock, ISO store pin, daemon substituter); HM pre-activate so first boot is themed. - No Secure Boot theater; no install-time app-suite *wizard* / custom partitioner / pbkdf knobs. (Starter apps come from the **template** `home.packages` once the installer consumes the template as SoT — see above; not a separate install questionnaire.) - Live: no idle suspend (avoids mid-install sleep); WiFi via normal desktop NM path. #### Bugs (stability contracts — fix, don’t optionalize) - **Installer swap=0 + unattended LUKS fail-closed** — ✓ shipped (workflow test run 2026-07-09): pass bare `"0"`; disko accepts `"0"`/`"0G"`; unattended needs passphrase or `NOMARCHY_NO_LUKS=1`. - **Offline mode must not silently fall back to network lock** When the network probe fails (or install is offline), `compose-lock.py` failure currently **warns** and runs `nix flake lock` (needs network). That can turn a promised offline install into a hung/partial one. **Fix:** if offline path is active and compose-lock fails → **fail the install** with a clear message (dirty ISO / missing narHash). Network `flake lock` only when the probe said online. Cost: small branch in `nomarchy-install.sh`; V2 offline install test. #### Safety / clarity (one line each — no new install menus) - **Review panel: warn when target already has Windows / BitLocker / NTFS / crypto_LUKS** Typed disk-name confirm is solid, but multi-OS machines still risk “wrong disk.” At selection or review, if `lsblk -f`/`blkid` shows BitLocker, NTFS, Microsoft, or existing LUKS on the **chosen** disk, add one explicit line: data will be destroyed. Still single-disk, whole-disk only — **no** dual-boot path. Cost: few lines of probe + review string; V0 + V2 optional. - **Review panel: offline vs online source line** Review always implies “no network needed” even when the online path (skip full store copy, use substituters) is active. Make the Source line conditional on the same `cache.nixos.org` probe so expectations match. Cost: tiny; V0. - **User password minimum length = LUKS (8 chars)** LUKS requires ≥8; account password only requires non-empty. Align to 8 for one consistent rule — not a strength meter. Cost: one check; V0. #### Live ISO discoverability (one durable cue) - **Live: one always-visible “Install Nomarchy” action** Install is only in: 3s notify-send toast, getty helpLine, and tribal knowledge of `nomarchy-install`. No desktop entry / menu row. **One** durable surface on the live host only: e.g. `xdg.desktopEntries` (Terminal=true) **or** a single Tools/System menu item that opens a terminal into `nomarchy-install`. Prefer that over more install-time questions or a second installer UI. Also fix stale `hosts/live.nix` header (“No installer yet” — already in docs drift pack). Cost: live.nix only; V1 ISO rebuild + V2 smoke. #### Generated config / post-install polish - **HM pre-activate failure → one durable recovery hint on target** Installer already prints the `home-manager switch` one-liner and logs to `/var/log/nomarchy-hm-preactivate.log`. If pre-activate fails, also drop a small flag file or MOTD snippet on the target so first login still shows the fix after the live session is gone. Avoid permanent first-boot checklists (fwupd/fprintd belong in menu/doctor). Cost: small install-script write; V2. - **MIGRATION.md: installer snapshot layout vs `@home-snapshots`** Installer creates disko `@snapshots` → `/.snapshots` and a first-boot oneshot for nested `/home/.snapshots`. MIGRATION still speaks of top-level `@home-snapshots` (TuringMachine vocabulary). One paragraph clarifying installer layout vs migration machines prevents wrong expectations. Cost: docs only. #### Test / pure contracts (after P0 bugs) - **Installer pure checks + expanded unattended matrix** — overlaps existing *Tooling* items (disko `swapSize` contract, compose-lock py_compile, hardware-db ∈ nixos-hardware). After swap/LUKS fixes: document or script unattended no-swap and explicit no-LUKS paths in `test-install.sh` (env flags only — no interactive options). Do **not** grow the interactive installer questionnaire. #### Explicit non-goals for the installer (defer) Do **not** add install-time: dual-boot / preserve partitions, multi-disk RAID, Secure Boot/lanzaboote, recovery-key *wizard* (optional silent recovery print is the only recovery-key idea worth considering later), custom partitioner, online/offline user toggle, app suite selection, pbkdf/TRIM knobs, or a second installer frontend. Stability and the single golden path win. ## Decisions `[human]` Open calls only Bernardo can make; agents add options/evidence but never decide. - **Formatter adoption:** repo deliberately has none; `nixfmt-rfc-style` would flatten the aligned hand-formatting of ~33 files. Adopt or declare never? - **Docs site vs Markdown-in-repo** (from the docs-review item). - **zram swap:** faster under pressure and pairs with NOW#3, but it interacts with the hibernation-swapfile story (resume device/priority ordering) — adopt, adopt-with-hibernation-guard, or skip? - **Default browser:** the template comments Firefox out. The shipped mime defaults (item 8, done) point `text/html`/http(s) at `firefox.desktop` as *inert* entries — they activate the moment Firefox is installed and are skipped otherwise, so the remaining call is only: ship a browser active in the suite, or stay browserless-by-default? - **Default power backend — PPD vs TLP:** (raised by Bernardo 2026-07-08: would TLP be more power-efficient, and should it be the default?) TLP does get more *idle* battery life, but only from device-level knobs PPD deliberately omits — PCIe ASPM, disk/NVMe link PM, USB autosuspend, runtime PM — which are the same knobs behind NVMe/USB flakiness that pillar 1 (rock-stable, never fight your machine) guards against. **Evidence (2026-07-08):** no credible hard-watt benchmarks exist — TLP's own maintainer declines to quote any ("measure on your hardware") and says PPD's power-saver gives *similar* savings under load; TLP's edge is idle-only (linrunner.de/tlp/faq/ppd.html — PPD covers a *subset* of TLP, "no settings to reduce consumption when the CPU is idle"). Our sibling distro **Omarchy** ran this exact experiment — a "replace Power Profiles with TLP for battery" guide — and the author **reverted to PPD** ("more headaches and weird issues"); Omarchy shipped PPD auto-switching instead (basecamp/omarchy#3907). PPD 3.4.0 now does AC/battery auto-switching, closing part of TLP's UX gap. **Cost of TLP-default:** TLP has no live D-Bus profile API, so the `powermgmt` menu + Waybar profile indicator + low-battery auto power-saver (all `powerprofilesctl`) would need a rework — it's not a one-line backend swap. **Agent rec (evidence, not a decision): keep PPD default;** chase battery via targeted low-risk tweaks (PCIe ASPM policy, battery-side EPP, the charge *start* threshold, PPD auto-switching) and keep TLP the documented one-line opt-in it already is (`nomarchy.system.power.backend = "tlp"`). Options: (i) status quo + targeted tweaks [rec]; (ii) TLP default (reworks the profile UX); (iii) nothing.