# 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 ### Live ISO / install hardware findings — Acer Aspire M5-481T + Dell XPS 9350 Bernardo, real installs 2026-07-13–14 (photos of the install end screens and post-boot sessions). Preserve separation: the installer bake failure and the flake pin are different root causes even when they show up on the same machine. (Terminal / Ghostty-on-Acer → shipped as Kitty-only, #95.) ### 98. Boreal: button text renders black and is difficult to read Audit GTK button foreground/background contrast under Boreal, including the installer surface where it was observed. Follow THEME-DESIGN's two-theme visual protocol. Pass = normal, hover, focused, and disabled button labels remain legible and palette-consistent. **Progress 2026-07-13:** scripted checks pass all 24 themes — not palette-level. Stylix gtk.css for Boreal audited: fg roles are light (#d3dae0); the near-black values are `accent/warning/error_fg_color` (#21272f) on their pastel bgs (deliberate, ~4.4:1). Candidate surfaces: adw-gtk3 (light base) + dark recolor edge cases, or gum's TUI confirm buttons in the installer (VM gum screens under Boreal look legible — artifacts in `/tmp/nomarchy-v2-swap-93/`). Needs Bernardo's screenshot or an Acer repro to pin the actual widget before fixing. ### 127. `[blocked:hw]` Docked idle: black screens, no wake, undock does not restore panel Bernardo 2026-07-15 (AMD dev box, docked): left the laptop docked; after a few minutes of idle it appeared to "sleep". Then: 1. External keyboard/mouse did **not** wake any display. 2. Unplugging the external monitor did **not** turn the laptop panel back on (dock mode has eDP disabled — undock *should* re-enable it). 3. The machine was still alive: Caps Lock LED toggled on the keyboard. This also killed a long agent session (looked like a "crash" from the outside). Separate from awake undock recovery (HARDWARE-QUEUE round 8): the failure is **idle while docked**, then no path back to a usable panel. **Likely shape (code-side, unconfirmed on the incident):** on AC, hypridle does **not** call `systemctl suspend` (`onAc || suspend` — only battery suspends at 15 min). Idle path is lock @5 min + `dpms off` @10 min (`modules/home/idle.nix`). Caps Lock working fits **DPMS/lock blackout** better than deep S3. Dock mode has already disabled eDP; recovery then needs either (a) input → hypridle `on-resume` / `dpms on` on the external, or (b) undock → `nomarchy-display-transition undock` re-enabling the panel. Both failed. `after_sleep_cmd` only does `hyprctl dispatch dpms on` and never re-enables a disabled internal. **Pass:** docked on AC, idle past lock + DPMS; external KB/mouse restores the external; if the external is unplugged while black, eDP comes back without reboot; journal shows display-watch undock `result=ok` when the cable leaves. Capture steps in HARDWARE-QUEUE. ## NEXT ### 120. A netinstall ISO, next to the fat offline one Bernardo 2026-07-14, after seeing the measured size: **keep the current ISO exactly as it is** — the guaranteed offline install is the feature it buys — and ship a **much lighter netinstall variant alongside it**. Two products, one distro: "works on a plane" and "8 GiB is absurd to download" are both true, and a second target settles them without compromising either. **Measured facts (2026-07-14), so this starts from numbers, not vibes.** *(These stand as measured: #121 would have cut ~0.67 GiB of duplicate chromium from them, but it was **reverted** — decided against, ROADMAP § one chromium, not two. If a netinstall ships, revisit it: the duplicate is worth ~195 MiB of **download**, which is this item's whole currency, even though it is worth almost nothing on disk or on the ISO.)* > **Read this before using the numbers below.** They are **closure arithmetic**, > and #121 proved the hard way that closure size is neither disk size nor image > size: removing a 687 MiB path shrank the ISO by **8 KiB**, because > **mksquashfs dedupes duplicate files** and **`auto-optimise-store` hardlinks** > them on disk. So a change that looks like it sheds gigabytes of closure can > shed nothing off the actual image. **Measure the artifact — build the ISO and > `stat` it.** The corollary cuts the other way and is the good news for this > item: what dedupe cannot help is the **wire**, so a netinstall's download is > the one figure closure/NAR size predicts honestly (`nix path-info --store > https://cache.nixos.org --json` gives the real `downloadSize`). - Current ISO **8.078 GiB** compressed; **18.03 GiB** of store uncompressed (`zstd -19`, 2.23:1 — compression is already near-max, not the lever). - The offline pin (`system.extraDependencies`, 60 roots: a representative installed system + the template HM closure + all flake inputs) is **4.02 GiB uncompressed of that — only ~22%**. Dropping it entirely still leaves a **~13.3 GiB** desktop → roughly **6 GiB** compressed at the same ratio. **So "no pin" alone is NOT the lighter ISO** — this is the trap to avoid. - The desktop's own top weights: libreoffice 1457 MiB, initrd 1369, linux-firmware 770, chromium 1391 (two builds — #121, left in), llvm-lib 540, bibata-cursors 322, mesa 264, mbrola-voices 259, nerd-fonts ~420 combined. Note what that list implies: no single lever gets a desktop ISO under ~4 GB — which is the case for (b) below. **So the real decision is what a netinstall ISO IS**, and it should be settled first (`[human]`): (a) the full try-before-install desktop minus the pin (~6.3 GiB — barely lighter, probably not worth a second target); (b) a **TUI installer only, no desktop** (~1 GiB, the actual "netinstall" in the Debian sense) which drops "try before install" from that medium — the fat ISO still offers it; (c) a middle desktop (no libreoffice/chromium — but note #103 just put those there deliberately, and a *demo* desktop that can't browse is the bug #103 fixed). **The gotcha that decides feasibility:** without the pin, a netinstall target fetches from `cache.nixos.org` for stock nixpkgs paths — but **Nomarchy's own derivations are in no binary cache**, so they would build *from source on the user's machine* during install. That is the same failure `tools/vm/gap-analysis.py` exists to diagnose (and #113 is a live instance of). So this item probably depends on a public binary cache (cachix) for the flake's own outputs, or it trades an 8 GiB download for a 40-minute install. Establish that before building the target. Pass = a second, documented ISO target that is *substantially* smaller (state the measured number, both ISOs built from one tree), installs successfully with a network in a QEMU run, says clearly at boot that it needs one, and leaves the offline ISO's behaviour untouched (`checks.*` for the offline path stay green). ### 115. Suspend-then-hibernate, with a way to set it up Bernardo, 2026-07-14: a suspended laptop should be able to fall through to hibernate after a while, so a bag-carried machine stops draining, and setting that up must be easy — not a systemd-sleep man-page trip. The mechanism is `systemd`'s `suspend-then-hibernate` plus `HibernateDelaySec`; the work is the surfacing, and the constraints are already in the tree. Hibernate needs the resume offset + swap the installer detects (`resumeOffset`/`rootUuid` in patch-template.py) — on a machine without them this must self-gate, not fail at suspend time, the way Battery limit gates on a battery. `modules/nixos/default.nix` already declines to hyprlock before an encrypted hibernate (the LUKS resume gates it); check that reasoning still holds when the suspend leads to hibernate. Menu placement: it belongs with the power settings, not the root (§ menu placement convention). Settle first (`[human]`): whether the delay is a preset list (30 min / 1 h / 2 h / never) or a free-form entry, and whether it applies on battery only or always — the same on-AC question `modules/home/idle.nix`'s suspend listener already answers with `${onAc} ||`. In-flake state like the rest (`settings.*`), menu-writable, and read via `state-read.nix` on the NixOS side — NOT `config.nomarchy.settings`, which does not exist there (ROADMAP § *NixOS-side state bridges (#116)*; copy the `bluetooth.enable` shape in `modules/nixos/default.nix` and add a case to `checks.state-bridges`). Pass = the toggle survives a rebuild, a suspended machine hibernates after the configured delay and resumes, and a machine without hibernate support hides the row instead of offering a suspend that never wakes. ### 105. `[big]` System-menu information architecture Keep exactly six root menu entries while reorganizing System into Connectivity, Devices, Recovery, and Preferences. This item must be split into scoped implementation/verification tasks before work starts. Pass = every current route has one deliberate home, navigation remains shallow, and no root-level entry is added or lost. ### 110. `[big]` Retire Control Center safely Split this into two phases before implementation: first build a migration/drop matrix for every unique setting, then remove the package and all references. Keyboard and terminal replacements must reject unsafe free-text values. Pass = no supported setting silently disappears and the second phase leaves no stale launcher, menu, package, documentation, or generated-artifact reference. **Facts for the matrix, from #116 (2026-07-14):** its Bluetooth and Printing rows write `settings.{bluetooth,printing}.enable`, and those keys became live bridges to the NixOS config in a9f3a64 — until then they wrote JSON nothing read, so *these two toggles have never actually worked for any user*. That cuts both ways: "preserve existing behavior" is not a reason to keep them (there is no behavior to preserve), but the state keys and their bridges are real now and outlive the TUI — dropping the rows must not drop the keys, or a machine whose state.json already says `bluetooth.enable = false` will silently flip back on at the next rebuild. `checks.state-bridges` covers those two keys and will fail loudly if the bridges go with the tool; keep it green, and if a key is deliberately retired, remove its case in the same commit. Whatever inherits the rows should rebuild on toggle rather than print "requires rebuild" (#117). Updates (`settings.updates.enable`) is HM-side, where `nomarchy.settings` genuinely exists, and needs none of this. ### 111. Scope-first Recovery menu Replace confusing Rollback/Snapshots duplication with explicit Desktop generation, System boot generation, and Files/root BTRFS scopes. Pass = labels state what is restored and from where before action, with destructive or reboot effects made clear and each existing recovery path represented once. ## 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. - **MIPI/IPU software-ISP camera** support (no-UVC machines). - **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). ## FUTURE (decided deferred — not the agent queue head) Work we **intend** someday but explicitly **not** NEXT. Agents do not pick these unless Bernardo promotes one into NEXT/NOW. ### 20. KVM runner → VM suite in CI `[human]` **Status (2026-07-10):** keep **eval-only** CI on the current Gitea stack (act_runner in docker-compose on the 4c/4 GB IONOS VPS). Nested KVM + RAM headroom on that host are a poor fit next to Gitea; full `checks.*` VMs stay local / promotion-time until a **separate** KVM-capable machine exists. **When ready:** register a second runner (host-mode nix + `/dev/kvm`, label `nix-kvm` — not the existing docker eval runner), then uncomment the `vm-checks` job in `.gitea/workflows/check.yml` (`runs-on: nix-kvm`, `nix flake check` + toplevel/HM builds). Do not enable the job until that label is online (Gitea queues forever otherwise). ### Formatter — adopt later `[human]` **Intent:** add a Nix formatter (likely `nixfmt-rfc-style`) in a dedicated pass: reformat the tree once, document in CONVENTIONS, optional CI check. **Not** the queue head — no drive-by reformats until that pass. ## 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.* *Open work only. Shipped exam/A–C items (#47–#63, #14, #52 theme high-ROI, etc.) live in the journal + ROADMAP — not here.* ### Product / day-2 ### 117. Control Center says "requires rebuild" and leaves the user to do it **Filed as an input to #110 (Retire Control Center safely), not as standalone work** — Bernardo 2026-07-14: the Control Center is going away, so fixing its toggles would be building on a condemned surface. Recorded because the observation outlives the tool: it is a fact the #110 migration matrix needs, and whatever menu inherits these settings must not repeat it. Noticed while fixing #116 (2026-07-14). `nomarchy-control-center.sh`'s Bluetooth, Printing and Updates toggles set the state and print "…​ (requires rebuild)", so the setting only lands whenever the user next thinks to run `nomarchy-rebuild`. Every menu toggle written since does the rebuild itself and toasts the outcome (`nomarchy-autologin`, `nomarchy-fingerprint`, `nomarchy-autotimezone`) — the state-write-then-rebuild shape. A user who toggles Bluetooth off and sees Bluetooth still running cannot tell "needs a rebuild" from "the toggle is broken again", which is exactly the symptom #116 removed. Migrating these rows into the rofi menu resolves it for free, since that shape is the convention there. ### 114. Greeter ignores per-device keyboard layouts Found by Bernardo 2026-07-14: logging out while docked lands on tuigreet, where his external keyboard (remembered as `us` via `settings.keyboard.devices`) types the session layout `gb` — so the password prompt fights him. Not a regression and not docking-related: per-device layouts are applied with `hyprctl keyword device[]:kb_layout`, which only exists inside a running Hyprland session, while tuigreet draws on a kernel VT whose single keymap comes from `console.useXkbConfig` ← `services.xserver.xkb.layout`. A VT structurally cannot do per-device layouts, so this is a design gap, not a bug to patch. Options, cheapest first: (a) document it and stop there; (b) a greeter layout-cycle key (tuigreet has no such feature — would need the keymap swapped under it); (c) host tuigreet inside a small Wayland compositor (cage/labwc), which *does* get per-device XKB and would let the greeter honour the same in-flake state the session uses — real work, and it changes the greeter's whole rendering path (`modules/nixos/greeter.nix` themes tuigreet through the 16 ANSI console slots, so (c) is not a drop-in). Worth deciding whether the greeter is meant to be layout-aware at all before costing it. - **NVIDIA first-class options** — **deferred past v1** (Bernardo 2026-07-10). Keep #59 commented install guidance; no `nomarchy.hardware.nvidia.*` until a hybrid maintainer + queue. - **Post-install hardware hints** (`VISION § B`) — After the general “you're set” card (#81), optionally fire **one** additional self-gated notify when the machine actually has the hardware: (a) `fwupdmgr` on PATH → “System › Firmware to check LVFS updates”; (b) `fprintd-list` on PATH → “System › Fingerprint to enroll”. One-shot markers in `settings.*` (same in-checkout discipline as `firstBootShown`); never a permanent MOTD nag. Cost: small — extend `nomarchy-first-boot` or a sibling oneshot + `checks.first-boot` fixture. Control-center / MOTD already mention these; the gap is the silent first *graphical* session for people who never open those. _(#80–#83 + #85–#88 shipped 2026-07-11. Theme A day-2 + neon-glass finish shipped — VISION ✓. Dock/hibernate V3 → HARDWARE-QUEUE. Parallel fingerprint-or-password shipped 2026-07-12 (Bernardo promoted it live; `fingerprint.parallel`, pam-fprint-grosshack) — reader V3 → HARDWARE-QUEUE.)_ ### v1.0 pointer See **VISION**. Open PROPOSED: post-install hardware hints; NVIDIA deferred past v1; IR portal (b)/(c) need T14s (HARDWARE-QUEUE § T14s). Standing calls: browser = Chromium; power = PPD. ## Decisions `[human]` Open calls only Bernardo can make; agents add options/evidence but never decide. **Resolved** entries stay for history; agents treat them as closed. ### Resolved (2026-07-10) - **Docs site vs Markdown-in-repo** — **markdown in-repo for now** (`docs/`, README). A rendered docs site is FUTURE if wanted. - **Default browser** — **ship Chromium** in `templates/downstream/home.nix`; mime → `chromium-browser.desktop`. Opt out: delete the line / override mime. - **Default power backend** — **keep PPD** (`nomarchy.system.power.backend` default). TLP remains the one-line opt-in. Rationale: stability + live profile API for menu/Waybar; Omarchy’s TLP experiment reverted. ### Resolved (2026-07-10, more) - **Formatter adoption** — **yes, but not now.** Tracked as FUTURE (below). Nix-source style only (`nixfmt-rfc-style` or similar); one bulk reformat + CI/check when promoted. Until then: hand-aligned style per CONVENTIONS. - **Hibernation** — **want by default** (product intent). Needs a disk-backed swap (file or partition) sized for resume; not zram alone. **Shipped as #76**; V3 power-cycle PASSED on TuringMachine 2026-07-12 (ROADMAP § Hibernation + zram by default). ### Resolved (2026-07-10, #76 design) - **Swap sizing** — **exactly RAM** (installer default, unchanged). Hibernate image ≤ RAM; zram takes day-to-day paging. **`swapSize=0`** stays no-swap. - **Migration** — **docs runbook** (`docs/MIGRATION.md`), not a tool. - **No-swap Hibernate** — keep the menu row; **notify on failure**.