Files
Nomarchy/agent/BACKLOG.md
Bernardo Magri 208b8d4444
All checks were successful
Check / eval (push) Successful in 3m4s
fix(install): swap=0 and unattended LUKS fail-closed
Pass bare swapSize "0" when the user wants no swap (was "0G", which
still created @swap). Accept "0"/"0G" in disko-config. Unattended
installs require NOMARCHY_LUKS_PASSPHRASE or explicit NOMARCHY_NO_LUKS=1
instead of silently installing cleartext.

Verified: bash -n; nix-instantiate subvolume sets for 0/0G/16G.
VISION § v1.0 install golden path (workflow test iteration).
2026-07-09 09:40:07 +01:00

39 KiB
Raw Blame History

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; design history in docs/ROADMAP.md; map in docs/README.md and agent/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

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. Many slices already have pitches below (Hardware product, Installer bugs, Default theme → boreal). When adding new product work, reference VISION § <theme> and prefer the north-star laptop user. Suggested next promotions (human picks order): install P0 contracts · boreal default · System Firmware · doctor hardware · fingerprint menu · first-boot tips · human rebuild errors · neon-glass quarantine.

Pre-existing

  • 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.

  • 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; V1V2.

  • 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.

  • 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. 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).

  • System Firmware (fwupd product surface) 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.

  • Doctor hardware section (read-only) 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.

  • 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 WiFi 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; V0V1.

  • 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, dont 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.