From 856a9d1a497187494ba15d05e04d169b6e6437ea Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Wed, 15 Jul 2026 11:49:57 +0100 Subject: [PATCH] feat(menu): Recovery scopes (#111), System IA (#105), drop Control Center (#110) System is Connectivity / Devices / Recovery / Preferences under the same six-entry root. Recovery lists desktop (HM), system boot, and BTRFS files once each with cost labels. Control Center is gone: blur/gaps live in Look & Feel; update checks and Bluetooth/Printing package toggles in Preferences (with rebuilds). docs/RECOVERY paths updated. --- README.md | 1 - agent/BACKLOG.md | 61 +--- agent/JOURNAL.md | 12 + docs/HARDWARE.md | 7 +- docs/RECOVERY.md | 10 +- docs/ROADMAP.md | 7 + docs/VISION.md | 2 +- flake.nix | 2 - modules/home/rofi.nix | 302 ++++++++++++------ modules/nixos/default.nix | 3 +- modules/nixos/options.nix | 2 +- modules/nixos/services.nix | 4 +- pkgs/nomarchy-control-center/default.nix | 7 - .../nomarchy-control-center.sh | 246 -------------- 14 files changed, 246 insertions(+), 420 deletions(-) delete mode 100644 pkgs/nomarchy-control-center/default.nix delete mode 100644 pkgs/nomarchy-control-center/nomarchy-control-center.sh diff --git a/README.md b/README.md index 0840d2f..e1e0a35 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,6 @@ Flat on purpose. Two module trees, one options file each, no hidden layers. │ ├── nomarchy-state-sync/ # state writer + rebuild dispatcher (Python) │ ├── nomarchy-install/ # live-ISO installer (gum + disko + mkFlake) │ ├── nomarchy-doctor/ # read-only health sheet, one command per failure -│ ├── nomarchy-control-center/ # TUI settings front-end over nomarchy-state-sync (gum) │ └── nomarchy-battery-notify/ # low-battery toast watcher backing battery-notify.nix ├── templates/downstream/ # machine flake SoT (`flake init` + installer copy/patch) ├── docs/ # human docs — map: docs/README.md diff --git a/agent/BACKLOG.md b/agent/BACKLOG.md index fe81aaf..6e7acdb 100644 --- a/agent/BACKLOG.md +++ b/agent/BACKLOG.md @@ -169,44 +169,6 @@ 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, @@ -219,8 +181,7 @@ effects made clear and each existing recovery path represented once. - **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). + Hyprland OOM blocker — see MEMORY.md before retrying. ## FUTURE (decided deferred — not the agent queue head) @@ -255,25 +216,6 @@ 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, @@ -321,7 +263,6 @@ 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 diff --git a/agent/JOURNAL.md b/agent/JOURNAL.md index a2d8ad8..f8d01a9 100644 --- a/agent/JOURNAL.md +++ b/agent/JOURNAL.md @@ -19,6 +19,18 @@ Template: --- +## 2026-07-15 — #111 Recovery + #105 System IA + #110 retire Control Center +- **Task:** Scope-first Recovery, System submenu IA, retire Control Center. +- **Did:** System → Connectivity / Devices / Recovery / Preferences. Recovery: + Desktop gen (HM list+activate), System boot (toast + RECOVERY.md), Files + BTRFS, What changed. Migrated CC: blur/gaps presets in Look & Feel; update + checks + Bluetooth/Printing package toggles in Preferences (rebuild in + terminal). Removed `pkgs/nomarchy-control-center` + menu entry. Docs + RECOVERY/HARDWARE/VISION updated. Closed #111 #105 #110 #117. +- **Verified:** V0 menu-back; HM gen builds; flake check --no-build. +- **Pending:** V3 eyeball the new menu tree on a real session. +- **Next suggestion:** #115/#120 human decisions, or hardware queue. + ## 2026-07-15 — V2 permanent checks for #104 / #122 - **Task:** V2 everything VM-checkable from the #104/#122 batch. - **Did:** Extracted `pkgs/nomarchy-airplane` (overlay). Added diff --git a/docs/HARDWARE.md b/docs/HARDWARE.md index 2462fd0..8139ddc 100644 --- a/docs/HARDWARE.md +++ b/docs/HARDWARE.md @@ -136,10 +136,9 @@ confirm → `fwupdmgr update`. It never auto-flashes — `fwupdmgr update` confirms each device and prompts for the reboot a capsule needs; pillar 1 (rock-stable) forbids silent BIOS writes. -**Hints (#43 / #73):** MOTD cheat-sheet line + `nomarchy-control-center ---first-boot` tip when `fwupdmgr` is on PATH. Fingerprint / doctor tips -follow the same pattern (fingerprint MOTD only when -`nomarchy.hardware.fingerprint.enable`). +**Hints (#43 / #73):** MOTD cheat-sheet line when `fwupdmgr` is on PATH. +Fingerprint / doctor tips follow the same pattern (fingerprint MOTD only +when `nomarchy.hardware.fingerprint.enable`). **Still queued:** a Doctor “updates available” check, and Waybar/updates-panel integration. diff --git a/docs/RECOVERY.md b/docs/RECOVERY.md index 38556e4..51f26a5 100644 --- a/docs/RECOVERY.md +++ b/docs/RECOVERY.md @@ -21,8 +21,10 @@ home-manager generations # newest first, one per theme/HM change /nix/store/…-home-manager-generation/activate # run the one you want ``` -The same picker lives in the menu: **System › Rollback** lists the -recent desktop generations — pick one and it activates. +The same picker lives in the menu: **System › Recovery › Desktop +generation** lists the recent Home Manager generations — pick one and +it activates. System-level undo is **System › Recovery › System boot +generation** (boot menu) or **Files (BTRFS)** (snapper). Or simply apply a theme you know is good: `nomarchy-state-sync apply boreal` (or any preset). If a switch failed halfway, the state file is written @@ -69,8 +71,8 @@ With `nomarchy.system.snapper.enable` (the installer's default on BTRFS), the root filesystem has hourly/daily history, and `nixos-rebuild-snap` leaves a snapshot right before a rebuild: -- **GUI:** menu › System › Snapshots (btrfs-assistant; expects a polkit - password prompt). +- **GUI:** menu › System › Recovery › Files (BTRFS) (btrfs-assistant; + expects a polkit password prompt). - **Terminal/SSH:** `sudo nomarchy-snapshots` — browse a snapshot's diff, **restore changed files** (snapper `undochange`), or **roll the whole root back** to a snapshot and reboot. Both destructive actions diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 96ae3db..e162a59 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -427,6 +427,13 @@ Design/decision records and a running log of shipped work (items marked default `$TERMINAL` / SUPER+Return, doctor + calendar + what-changed use `kitty --class=com.nomarchy.*`. Ghostty module and install-time GL probe removed. V3: Acer SUPER+Return + doctor float. +- ✓ **Recovery + System IA + Control Center retired (#111 / #105 / #110):** + System menu is Connectivity / Devices / Recovery / Preferences (root still + six entries). Recovery is scope-first: Desktop generation (HM), System boot + generation (how), Files BTRFS, What changed. Control Center package removed; + blur/gaps (presets) in Look & Feel; update checks + Bluetooth/Printing + package toggles in Preferences (system toggles run `nomarchy-rebuild`). + #117 absorbed (rebuild no longer left to the user for those rows). - ✓ **Airplane mode (#104):** System › Airplane mode + SUPER+CTRL+R + Waybar plane glyph (self-hides when off). Runtime `nomarchy-airplane` softs Wi-Fi (nmcli) + Bluetooth (rfkill/bluetoothctl), remembers prior diff --git a/docs/VISION.md b/docs/VISION.md index 08ffa88..bcee40b 100644 --- a/docs/VISION.md +++ b/docs/VISION.md @@ -74,7 +74,7 @@ Reference: [HARDWARE.md](HARDWARE.md) §4–§5, §11 (doctor). | **Silent first-week card** ✓ | One dismissible “you’re set” (menu, themes, keys, network) — not a wizard | ✓ shipped #81 — `settings.firstBootShown` + notify; live ISO keeps its own toast | | **Boreal as default** ✓ | Identity on first boot | ✓ seed `state.json` + install path use Boreal (2026-07-09) | | **Generation readability** ✓ | “What changed last rebuild” in plain language | ✓ shipped #82 — nvd toast + System › What changed? | -| **Post-install hints** | One-shot MOTD/notify for fwupd / fprintd when relevant | Partial: MOTD + control-center tips; still no self-gated *first-session* toast when hardware is present (see BACKLOG PROPOSED) | +| **Post-install hints** | One-shot MOTD/notify for fwupd / fprintd when relevant | Partial: MOTD tips; first-session toast is #81; still refine self-gated hardware hints if needed | --- diff --git a/flake.nix b/flake.nix index 7a3c1c2..6b4e293 100644 --- a/flake.nix +++ b/flake.nix @@ -85,7 +85,6 @@ themesDir = ./themes; }; nomarchy-doctor = final.callPackage ./pkgs/nomarchy-doctor { }; - nomarchy-control-center = final.callPackage ./pkgs/nomarchy-control-center { }; nomarchy-battery-notify = final.callPackage ./pkgs/nomarchy-battery-notify { }; nomarchy-first-boot = final.callPackage ./pkgs/nomarchy-first-boot { }; nomarchy-what-changed = final.callPackage ./pkgs/nomarchy-what-changed { }; @@ -136,7 +135,6 @@ # (maintainer/CI convenience — modules install them via the overlay). nomarchy-doctor = pkgs.nomarchy-doctor; nomarchy-lifecycle = pkgs.nomarchy-lifecycle; - nomarchy-control-center = pkgs.nomarchy-control-center; nomarchy-battery-notify = pkgs.nomarchy-battery-notify; nomarchy-first-boot = pkgs.nomarchy-first-boot; nomarchy-what-changed = pkgs.nomarchy-what-changed; diff --git a/modules/home/rofi.nix b/modules/home/rofi.nix index c20396a..c1a314f 100644 --- a/modules/home/rofi.nix +++ b/modules/home/rofi.nix @@ -341,7 +341,7 @@ let done back } | rofi_menu -show-icons -p "profile (now: $cur)") || exit 0 - [ "$choice" = "$BACK" ] && exec "$0" system + [ "$choice" = "$BACK" ] && exec "$0" system-preferences [ -n "$choice" ] && powerprofilesctl set "$choice" ;; powermgmt) @@ -548,7 +548,7 @@ ${themeRows} back } | rofi_menu -show-icons -p Audio) || exit 0 case "$choice" in - "$BACK") exec "$0" system ;; + "$BACK") exec "$0" system-devices ;; "Send output"*) nomarchy-dock-audio select menu ;; *Output*) exec rofi-pulse-select sink ;; @@ -567,7 +567,7 @@ ${themeRows} done back } | rofi_menu -p "Keyboard") || exit 0 - [ "$picked" = "$BACK" ] && exec "$0" system + [ "$picked" = "$BACK" ] && exec "$0" system-devices device=''${picked%% ·*} saved=$(nomarchy-keyboard-layout saved "$device") layout=$( { @@ -627,7 +627,7 @@ ${themeRows} printf '%s' "$mons" | jq -r '.[] | "\(.name) · \(.width)x\(.height)@\(.refreshRate|round)Hz"' printf '%s\n' "$BACK" } | rofi_menu -p Display ) || exit 0 - [ "$name" = "$BACK" ] && exec "$0" system + [ "$name" = "$BACK" ] && exec "$0" system-devices [ "$name" = "Profiles ›" ] && exec "$0" display-profile case "$name" in "Dock mode"*) @@ -754,16 +754,13 @@ ${themeRows} esac ;; lookfeel) - # Look & Feel — appearance: Theme, wallpapers, night light. - # Root stays six entries (Apps / Look & Feel / Tools / System / - # Power / Keybindings). SUPER+T / SUPER+SHIFT+T still hit leaves. + # Look & Feel — appearance: Theme, wallpapers, night light, blur/gaps + # (#110 moved blur/gaps out of Control Center — presets only, no + # free-text). Root stays six entries. choice=$( { row "Theme${menuHint "theme"}" preferences-desktop-theme row "Next wallpaper${hintForAction "exec, nomarchy-state-sync bg next"}" preferences-desktop-wallpaper row "Reset wallpaper (auto)" preferences-desktop-wallpaper - # Use the toggle script (knows hyprsunset vs wlsunset); a hard-coded - # hyprsunset-only check always showed (off) in geo mode and when - # the unit was still the empty HM mask from a disabled package unit. if [ "$(nomarchy-nightlight is-active 2>/dev/null)" = on ] then row "Night light (on)" weather-clear-night else row "Night light (off)" weather-clear-night @@ -772,6 +769,12 @@ ${themeRows} then row "Auto theme (on)" preferences-desktop-theme else row "Auto theme (off)" preferences-desktop-theme fi + case "$(nomarchy-state-sync get ui.blur 2>/dev/null)" in + true|True) row "Blur (on)" preferences-desktop-effects ;; + *) row "Blur (off)" preferences-desktop-effects ;; + esac + gaps=$(nomarchy-state-sync get ui.gapsOut 2>/dev/null || echo 12) + row "Gaps (now $gaps px)" preferences-desktop-display back } | rofi_menu -show-icons -markup-rows -p "Look & Feel") || exit 0 case "$choice" in @@ -781,8 +784,43 @@ ${themeRows} *"Next wallpaper"*) exec nomarchy-state-sync bg next ;; *"Reset wallpaper"*) exec nomarchy-state-sync bg auto ;; *"Night light"*) exec "$0" nightlight ;; + *Blur*) exec "$0" blur ;; + *Gaps*) exec "$0" gaps ;; esac ;; + blur) + # ui.blur → Hyprland decoration; needs a home switch to bake. + case "$(nomarchy-state-sync get ui.blur 2>/dev/null)" in + true|True) + nomarchy-state-sync --quiet set ui.blur false + notify-send "Blur" "Off — desktop rebuilt." ;; + *) + nomarchy-state-sync --quiet set ui.blur true + notify-send "Blur" "On — desktop rebuilt." ;; + esac + exit 0 ;; + + gaps) + # Preset-only (no free-text) — #110 keyboard/gaps free-text was unsafe. + cur=$(nomarchy-state-sync get ui.gapsOut 2>/dev/null || echo 12) + choice=$( { + for g in 0 4 8 12 16 24; do + if [ "$g" = "$cur" ]; then row "$g px (current)" preferences-desktop-display + else row "$g px" preferences-desktop-display + fi + done + back + } | rofi_menu -show-icons -p "Outer gaps (now $cur px)") || exit 0 + case "$choice" in + "$BACK") exec "$0" lookfeel ;; + *) + g=''${choice%% *} + case "$g" in *[!0-9]*) exit 0 ;; esac + nomarchy-state-sync --quiet set ui.gapsOut "$g" + notify-send "Gaps" "Outer gaps $g px — desktop rebuilt." ;; + esac + exit 0 ;; + display-profile) # Named multi-output layouts (nomarchy.displayProfiles). Applying is # instant (hyprctl per entry, via nomarchy-display-profile) and @@ -1039,7 +1077,7 @@ ${themeRows} back } | rofi_menu -show-icons -p Fingerprint) || exit 0 case "$choice" in - "$BACK") exec "$0" system ;; + "$BACK") exec "$0" system-devices ;; *Enroll*) exec ${cfg.terminal} -e sh -c ' echo "== Enroll fingerprint for $USER ==" @@ -1076,10 +1114,6 @@ ${themeRows} exec ${cfg.terminal} -e nomarchy-fingerprint toggle ;; esac ;; - controlcenter) - # Nomarchy TUI Control Center. - exec ${cfg.terminal} -e nomarchy-control-center ;; - whatchanged) # Plain-language last rebuild (system + desktop nvd). Toast the # one-liners; open a floating terminal for the full report when @@ -1094,46 +1128,66 @@ ${themeRows} -e sh -c "nomarchy-what-changed printf '\nEnter to close.'; read -r _" ;; - rollback) - # Undo, one menu away (informative + rock-stable). Desktop = - # Home Manager generations: theme/config history is already one - # generation per change, and running an older generation's - # activate script is the supported HM rollback — reversible by - # activating a newer one again, so no typed-yes gate here. - # System-level undo deliberately LINKS OUT instead of putting - # destructive flows in rofi: boot-menu generations and snapper - # rollback have their own homes and gates (docs/RECOVERY.md, - # nomarchy-snapshots). + # #111 Scope-first Recovery — one menu for every undo path. + # Desktop = Home Manager generations (themes/home config). + # System boot = NixOS generations in the firmware boot menu. + # Files = BTRFS snapper (files on disk / whole-root roll). + # Each path is listed once; labels say what is restored and the cost + # (instant / reboot / confirm) before the action. + recovery) + choice=$( { + row "Desktop generation — themes & home config (instant, reversible)" document-open-recent + row "System boot generation — older NixOS (reboot, then pick in boot menu)" system-reboot + command -v nomarchy-snapshots >/dev/null 2>&1 \ + && row "Files (BTRFS) — restore files or roll root (password + confirm)" timeshift + command -v nomarchy-what-changed >/dev/null 2>&1 \ + && row "What changed? — last system & desktop rebuild" view-refresh + back + } | rofi_menu -show-icons -markup-rows -p Recovery) || exit 0 + case "$choice" in + "$BACK") exec "$0" system ;; + *Desktop*) exec "$0" recovery-desktop ;; + *System\ boot*) + notify-send "System boot generation" \ + "Reboot and pick an older NixOS generation in the boot menu (last 10 kept). That boot is temporary — make it stick: revert in ~/.nomarchy, then nomarchy-rebuild. Details: docs/RECOVERY.md §3." ;; + *Files*) exec "$0" snapshot ;; + *"What changed"*) exec "$0" whatchanged ;; + esac ;; + + recovery-desktop) + # Home Manager generations only — desktop half of RECOVERY.md §1. gens=$(home-manager generations 2>/dev/null | head -10) choice=$( { if [ -n "$gens" ]; then printf '%s\n' "$gens" | awk '{ - printf "Desktop gen %s — %s %s%s\n", $5, $1, $2, (NR==1 ? " (current)" : "") + printf "Gen %s — %s %s%s\n", $5, $1, $2, (NR==1 ? " (current)" : "") }' | while IFS= read -r line; do row "$line" document-open-recent; done + else + row "(no Home Manager generations found)" dialog-information fi - command -v nomarchy-snapshots >/dev/null 2>&1 \ - && row "System files → Snapshots" timeshift - row "System config → boot an older generation (how)" help-about back - } | rofi_menu -show-icons -p Rollback) || exit 0 + } | rofi_menu -show-icons -p "Desktop generation") || exit 0 case "$choice" in - "$BACK") exec "$0" system ;; - "Desktop gen "*) - num=''${choice#Desktop gen }; num=''${num%% *} + "$BACK"|"(no "*) exec "$0" recovery ;; + "Gen "*) + num=''${choice#Gen }; num=''${num%% *} path=$(printf '%s\n' "$gens" | awk -v n="$num" '$5 == n { print $7 }') [ -x "$path/activate" ] \ - || { notify-send "Rollback" "Generation $num not found on disk."; exit 1; } + || { notify-send "Desktop generation" "Generation $num not found on disk."; exit 1; } exec ${cfg.terminal} -e sh -c " - echo \"Activating Home Manager generation $num…\"; echo + echo \"Activating Home Manager generation $num (desktop only)…\" + echo \"This does not change the NixOS system or BTRFS files.\" + echo \"$path/activate\" - echo; echo \"Desktop rolled back to generation $num.\" - echo \"(Roll forward the same way — newer generations stay listed.)\" + echo + echo \"Desktop is now generation $num.\" + echo \"Roll forward the same way — newer generations stay listed.\" printf 'Enter to close.'; read -r _" ;; - *Snapshots*) exec "$0" snapshot ;; - *"older generation"*) - notify-send "System rollback" "Reboot and pick an older NixOS generation in the boot menu (the last 10 are kept). Make it stick: revert the change in ~/.nomarchy, then nomarchy-rebuild. Details: docs/RECOVERY.md §3." ;; esac ;; + # Compat aliases — old menu ids and muscle memory. + rollback) exec "$0" recovery ;; + tools) # Tools submenu — utilities you invoke. Each leaf is also reachable # directly (SUPER+CTRL+); this just keeps the root short. @@ -1170,37 +1224,79 @@ ${themeRows} esac ;; system) - # System submenu — connectivity and machine state. Snapshots and the - # power-profile picker self-gate (BTRFS+snapper / laptop on ppd), so - # the submenu shrinks to match the hardware, like the Waybar modules. + # #105 System IA — four groups under the same System entry so the + # root menu stays at six items (Apps / Look & Feel / Tools / System / + # Power / Keybindings). Leaves keep SUPER+CTRL mnemonics via their + # own modules; this only organizes the picker. choice=$( { - row "Network${menuHint "network"}" network-wireless - row "VPN" network-vpn - # Airplane mode (#104): runtime Wi-Fi+BT kill-switch. Label - # tracks the session flag; tool is always on PATH via airplane.nix. + row "Connectivity" network-wireless + row "Devices" preferences-desktop-peripherals + row "Recovery${menuHint "recovery"}" edit-undo + row "Preferences" preferences-system + back + } | rofi_menu -show-icons -markup-rows -p System) || exit 0 + case "$choice" in + "$BACK") exec "$0" ;; + *Connectivity*) exec "$0" system-connectivity ;; + *Devices*) exec "$0" system-devices ;; + *Recovery*) exec "$0" recovery ;; + *Preferences*) exec "$0" system-preferences ;; + esac ;; + + system-connectivity) + choice=$( { + row "Network${menuHint "network"}" network-wireless + row "VPN" network-vpn if [ "$(nomarchy-airplane is-active 2>/dev/null)" = on ] then row "Airplane mode (on)${menuHint "airplane"}" network-wireless-offline else row "Airplane mode (off)${menuHint "airplane"}" network-wireless-offline fi command -v blueman-manager >/dev/null 2>&1 \ - && row "Bluetooth${menuHint "bluetooth"}" bluetooth + && row "Bluetooth${menuHint "bluetooth"}" bluetooth + back + } | rofi_menu -show-icons -markup-rows -p Connectivity) || exit 0 + case "$choice" in + "$BACK") exec "$0" system ;; + *Network*) exec "$0" network ;; + *VPN*) exec "$0" vpn ;; + *"Airplane mode"*) exec "$0" airplane ;; + *Bluetooth*) exec "$0" bluetooth ;; + esac ;; + + system-devices) + choice=$( { [ -e "''${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/pulse/native" ] \ && row "Audio" audio-volume-high row "Display" video-display command -v nomarchy-keyboard-layout >/dev/null 2>&1 \ && row "Keyboard${menuHint "keyboard"}" preferences-desktop-keyboard command -v system-config-printer >/dev/null 2>&1 && row "Printers" printer + command -v fprintd-list >/dev/null 2>&1 \ + && row "Fingerprint" preferences-desktop-user-password + command -v fwupdmgr >/dev/null 2>&1 && row "Firmware" firmware-manager + back + } | rofi_menu -show-icons -markup-rows -p Devices) || exit 0 + case "$choice" in + "$BACK") exec "$0" system ;; + *Audio*) exec "$0" audio ;; + *Display*) exec "$0" display ;; + *Keyboard*) exec "$0" keyboard ;; + *Printers*) exec "$0" printers ;; + *Fingerprint*) exec "$0" fingerprint ;; + *Firmware*) exec "$0" firmware ;; + esac ;; + + system-preferences) + # Preferences + former Control Center system toggles (#110). + # Bluetooth/Printing flip NixOS bridges → rebuild in a terminal. + # Updates is HM-side (state-sync rebuilds home). + choice=$( { row "Do Not Disturb${menuHint "dnd"}" notification-disabled if [ "$(nomarchy-state-sync get settings.autoTimezone 2>/dev/null)" = true ] then row "Auto timezone (on)" preferences-system-time else row "Auto timezone (off)" preferences-system-time fi - # Auto-login — self-gated on the tool (greeter.enable). Off = the - # greeter asks; that's the prompt the Fingerprint switch feeds. if command -v nomarchy-autologin >/dev/null 2>&1; then - # Unset (get exits 1, empty) and null both read as off. "None" is - # what an older state-sync printed for null — accepted so a menu - # from a newer generation can't misreport against an older tool. case "$(nomarchy-state-sync get settings.greeter.autoLogin 2>/dev/null)" in null|""|None) row "Auto-login (off)" system-users ;; *) row "Auto-login (on)" system-users ;; @@ -1212,57 +1308,83 @@ ${themeRows} else row "Auto-commit (off)" git fi fi - command -v nomarchy-snapshots >/dev/null 2>&1 && row "Snapshots" timeshift - row "Rollback" edit-undo - # Plain-language last-rebuild summary (nvd; BACKLOG #82). - command -v nomarchy-what-changed >/dev/null 2>&1 \ - && row "What changed?" view-refresh - command -v nomarchy-doctor >/dev/null 2>&1 && row "Doctor" utilities-system-monitor - # Firmware/LVFS updates — self-gated on fwupd (default-on, but off - # on VMs/headless via services.fwupd.enable=false), like Printers. - command -v fwupdmgr >/dev/null 2>&1 && row "Firmware" firmware-manager - # Fingerprint — self-gated on fprintd CLI (services.fprintd; BACKLOG #55). - command -v fprintd-list >/dev/null 2>&1 && row "Fingerprint" preferences-desktop-user-password - command -v nomarchy-control-center >/dev/null 2>&1 && row "Control Center" preferences-desktop + case "$(nomarchy-state-sync get settings.updates.enable 2>/dev/null)" in + true|True) row "Update checks (on)" system-software-update ;; + *) row "Update checks (off)" system-software-update ;; + esac + case "$(nomarchy-state-sync get settings.bluetooth.enable 2>/dev/null)" in + false|False) row "Bluetooth package (off)" bluetooth ;; + *) row "Bluetooth package (on)" bluetooth ;; + esac + case "$(nomarchy-state-sync get settings.printing.enable 2>/dev/null)" in + true|True) row "Printing (on)" printer ;; + *) row "Printing (off)" printer ;; + esac if has_system_battery && command -v powerprofilesctl >/dev/null 2>&1; then row "Power profile" preferences-system-power fi - # Gated on a battery (like Power profile), NOT on the - # charge-threshold sysfs node: firmware without the control must - # get the leaf's explanation, not a silently missing row that - # reads as a broken feature (BACKLOG #96, Acer M5-481T). has_system_battery && row "Battery limit" battery-080 + command -v nomarchy-doctor >/dev/null 2>&1 && row "Doctor" utilities-system-monitor back - } | rofi_menu -show-icons -markup-rows -p System) || exit 0 + } | rofi_menu -show-icons -markup-rows -p Preferences) || exit 0 case "$choice" in - "$BACK") exec "$0" ;; - *Network*) exec "$0" network ;; - *VPN*) exec "$0" vpn ;; - *"Airplane mode"*) exec "$0" airplane ;; - *Bluetooth*) exec "$0" bluetooth ;; - *Audio*) exec "$0" audio ;; - *Display*) exec "$0" display ;; - *Keyboard*) exec "$0" keyboard ;; - *Printers*) exec "$0" printers ;; + "$BACK") exec "$0" system ;; *"Do Not Disturb"*) exec "$0" dnd ;; *"Auto timezone"*) exec "$0" autotimezone ;; *"Auto-login"*) exec "$0" autologin ;; *"Auto-commit"*) exec "$0" autocommit ;; - *Snapshots*) exec "$0" snapshot ;; - *Rollback*) exec "$0" rollback ;; - *"What changed"*) exec "$0" whatchanged ;; - *Doctor*) exec "$0" doctor ;; - *Firmware*) exec "$0" firmware ;; - *Fingerprint*) exec "$0" fingerprint ;; - *"Control Center"*) exec "$0" controlcenter ;; + *"Update checks"*) exec "$0" updates-toggle ;; + *"Bluetooth package"*) exec "$0" bluetooth-package ;; + *Printing*) exec "$0" printing-toggle ;; *"Power profile"*) exec "$0" power-profile ;; *"Battery limit"*) exec "$0" batterylimit ;; + *Doctor*) exec "$0" doctor ;; esac ;; + updates-toggle) + # settings.updates.enable → HM nomarchy.updates (Waybar checker). + case "$(nomarchy-state-sync get settings.updates.enable 2>/dev/null)" in + true|True) + nomarchy-state-sync --quiet set settings.updates.enable false + notify-send "Update checks" "Off — desktop rebuilt." ;; + *) + nomarchy-state-sync --quiet set settings.updates.enable true + notify-send "Update checks" "On — desktop rebuilt." ;; + esac + exit 0 ;; + + bluetooth-package) + # settings.bluetooth.enable → NixOS hardware.bluetooth (state bridge). + # Needs a system rebuild; run it in a terminal with a clear story. + case "$(nomarchy-state-sync get settings.bluetooth.enable 2>/dev/null)" in + false|False) new=true; label=On ;; + *) new=false; label=Off ;; + esac + exec ${cfg.terminal} -e sh -c " + nomarchy-state-sync --quiet set settings.bluetooth.enable $new --no-switch + echo \"Bluetooth package → $label (system rebuild)…\" + echo + nomarchy-rebuild + echo + printf 'Enter to close.'; read -r _" ;; + + printing-toggle) + case "$(nomarchy-state-sync get settings.printing.enable 2>/dev/null)" in + true|True) new=false; label=Off ;; + *) new=true; label=On ;; + esac + exec ${cfg.terminal} -e sh -c " + nomarchy-state-sync --quiet set settings.printing.enable $new --no-switch + echo \"Printing → $label (system rebuild)…\" + echo + nomarchy-rebuild + echo + printf 'Enter to close.'; read -r _" ;; + batterylimit) # Battery charge-limit preset picker — the rofi-System counterpart of - # the control-center's toggle (Bernardo: he wanted this in the menu, - # not buried in the gum TUI). Writes the baked NixOS option + # (Bernardo: charge limit belongs in the menu, not a raw TUI). Writes + # the baked NixOS option # Persist + live sysfs apply (same as powermgmt). Boot/AC re-apply # stays with the oneshot in power.nix. has_charge_threshold \ @@ -1322,7 +1444,7 @@ ${themeRows} fi } case "$sel" in - "$BACK") exec "$0" system ;; + "$BACK") exec "$0" system-preferences ;; # Order: Off (100%) before 90/80/60 so "100%" does not hit *0%* patterns. *Off*|*100%*) set_limit null "Off — charges to 100%" ;; *90%*) set_limit 90 "Set to 90%" ;; @@ -1363,7 +1485,7 @@ ${themeRows} esac ;; *) - echo "usage: nomarchy-menu [lookfeel|tools|system|power|power-profile|powermgmt|batterylimit|theme|clipboard|calc|files|emoji|web|network|bluetooth|airplane|audio|display|display-profile|keyboard|printers|capture|colorpicker|keybinds|ask|dnd|nightlight|autotimezone|autologin|autocommit|vpn|snapshot|doctor|firmware|fingerprint|controlcenter|rollback|whatchanged]" >&2 + echo "usage: nomarchy-menu [lookfeel|tools|system|system-connectivity|system-devices|system-preferences|recovery|power|power-profile|powermgmt|batterylimit|theme|clipboard|calc|files|emoji|web|network|bluetooth|airplane|audio|display|display-profile|keyboard|printers|capture|colorpicker|keybinds|ask|dnd|nightlight|autotimezone|autologin|autocommit|vpn|snapshot|doctor|firmware|fingerprint|rollback|whatchanged|blur|gaps|updates-toggle|bluetooth-package|printing-toggle]" >&2 exit 64 ;; esac ''; diff --git a/modules/nixos/default.nix b/modules/nixos/default.nix index 9e4d224..b4c99a3 100644 --- a/modules/nixos/default.nix +++ b/modules/nixos/default.nix @@ -21,7 +21,7 @@ let install -Dm444 ${./branding/Nomarchy.ttf} $out/share/fonts/truetype/Nomarchy.ttf ''; - # Control Center's Bluetooth toggle writes settings.bluetooth.enable; read + # Menu Preferences › Bluetooth package writes settings.bluetooth.enable; read # it from the state file, the only place it exists on the NixOS side (the # hardware.nix/timezone.nix bridge). Missing/invalid JSON fails closed via # state-read.nix rather than a raw stack. null = key absent, which @@ -375,7 +375,6 @@ in environment.systemPackages = with pkgs; [ nomarchy-state-sync # provided by overlays.default nomarchy-doctor # read-only health check (System › Doctor) - nomarchy-control-center # TUI control center nomarchy-detect-hw # post-install hardware re-probe (HARDWARE.md §8) # Day-to-day lifecycle (README §3): nomarchy-pull / -rebuild / -home diff --git a/modules/nixos/options.nix b/modules/nixos/options.nix index 3951186..534a30a 100644 --- a/modules/nixos/options.nix +++ b/modules/nixos/options.nix @@ -52,7 +52,7 @@ audio.enable = lib.mkEnableOption "the Pipewire audio stack" // { default = true; }; # default stays a plain `true` here; ./default.nix mkDefaults it from - # settings.bluetooth.enable (Control Center's Bluetooth toggle). Reading + # settings.bluetooth.enable (menu Preferences › Bluetooth package). Reading # the state in the option default is the trap ROADMAP § "NixOS-side state # bridges (#116)" documents: `config.nomarchy.settings` does not exist on # the NixOS side, and `or true` silently swallowed that for years. diff --git a/modules/nixos/services.nix b/modules/nixos/services.nix index fee2430..52fc940 100644 --- a/modules/nixos/services.nix +++ b/modules/nixos/services.nix @@ -8,7 +8,7 @@ let cfg = config.nomarchy.services; - # Control Center's Printing toggle writes settings.printing.enable; read it + # Menu Preferences › Printing writes settings.printing.enable; read it # from the state file, the only place it exists on the NixOS side (the # hardware.nix/timezone.nix bridge). Missing/invalid JSON fails closed via # state-read.nix rather than a raw stack. null = key absent, which @@ -88,7 +88,7 @@ in `services.ollama.acceleration` natively for GPU offload''; # default stays a plain `false` here; the state bridge is a mkDefault - # below, from settings.printing.enable (Control Center's Printing + # below, from settings.printing.enable (menu Preferences › Printing # toggle) — see ROADMAP § state bridges (#116) for why the old read of # `config.nomarchy.settings` never worked. printing.enable = lib.mkEnableOption '' diff --git a/pkgs/nomarchy-control-center/default.nix b/pkgs/nomarchy-control-center/default.nix deleted file mode 100644 index 9e5352c..0000000 --- a/pkgs/nomarchy-control-center/default.nix +++ /dev/null @@ -1,7 +0,0 @@ -{ writeShellApplication, coreutils, gawk, jq, gum }: - -writeShellApplication { - name = "nomarchy-control-center"; - runtimeInputs = [ coreutils gawk jq gum ]; - text = builtins.readFile ./nomarchy-control-center.sh; -} diff --git a/pkgs/nomarchy-control-center/nomarchy-control-center.sh b/pkgs/nomarchy-control-center/nomarchy-control-center.sh deleted file mode 100644 index cbdf885..0000000 --- a/pkgs/nomarchy-control-center/nomarchy-control-center.sh +++ /dev/null @@ -1,246 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Nomarchy Control Center — TUI frontend over nomarchy-state-sync and tools -# Uses charmbracelet/gum for rendering. - -trap 'exit 0' SIGINT - -function get_state() { - nomarchy-state-sync get "$1" 2>/dev/null || echo "" -} - -function set_state() { - nomarchy-state-sync --quiet set "$1" "$2" --no-switch -} - -function first_boot() { - gum style --border normal --margin "1" --padding "1 2" --border-foreground 212 "Welcome to Nomarchy!" - - echo "Pick a Theme Preset:" - themes=$(nomarchy-state-sync list) - chosen_theme=$(printf "%s" "$themes" | gum choose) - if [ -n "$chosen_theme" ]; then - echo "Applying theme $chosen_theme..." - nomarchy-state-sync apply "$chosen_theme" - fi - - if gum confirm "Enable auto-commit for your config? (settings changes + a sweep of pending edits before each rebuild)"; then - set_state "settings.autoCommit" "true" - else - set_state "settings.autoCommit" "false" - fi - - if command -v nomarchy-autotimezone >/dev/null 2>&1; then - if gum confirm "Enable automatic timezone detection?"; then - set_state "settings.autoTimezone" "true" - else - set_state "settings.autoTimezone" "false" - fi - fi - - # Post-install hardware/day-2 tips (#43 Firmware; #73 fingerprint + - # doctor). Self-gated on the same CLI presence the System menu uses; - # first-boot is once, so no dismiss state file is needed. - if command -v fwupdmgr >/dev/null 2>&1; then - gum style --foreground 245 \ - "Tip: SUPER+M → System › Firmware checks LVFS updates (never auto-flashes)." - fi - if command -v fprintd-list >/dev/null 2>&1; then - gum style --foreground 245 \ - "Tip: SUPER+M → System › Fingerprint to enroll a finger (or fprintd-enroll)." - fi - if command -v nomarchy-doctor >/dev/null 2>&1; then - gum style --foreground 245 \ - "Tip: run nomarchy-doctor (or SUPER+M → System › Doctor) for a read-only health check." - fi - - gum style --foreground 212 "First boot configuration complete!" - echo "You can always change these later in the Control Center." - read -r -n 1 -s -p "Press any key to exit..." - echo -} - -function main_menu() { - while true; do - choice=$(gum choose "Appearance" "System Toggles" "Health (Doctor)" "Rollback" "Exit") - case "$choice" in - "Appearance") appearance_menu ;; - "System Toggles") toggles_menu ;; - "Health (Doctor)") - if command -v nomarchy-doctor >/dev/null 2>&1; then - nomarchy-doctor || true - else - echo "nomarchy-doctor not found." - fi - echo - read -r -n 1 -s -p "Press any key to return..." - echo - ;; - "Rollback") - gens=$(home-manager generations 2>/dev/null | head -10) - if [ -z "$gens" ]; then - echo "No Home Manager generations found." - else - tmp=$(mktemp) - printf "%s\n" "$gens" | awk '{ printf "Desktop gen %s — %s %s%s\n", $5, $1, $2, (NR==1 ? " (current)" : "") }' > "$tmp" - sel=$(gum choose < "$tmp" || true) - rm -f "$tmp" - if [ -n "$sel" ]; then - num=${sel#Desktop gen } - num=${num%% *} - path=$(printf "%s\n" "$gens" | awk -v n="$num" '$5 == n { print $7 }') - if [ -x "$path/activate" ]; then - echo "Activating Home Manager generation $num..." - "$path/activate" - echo "Desktop rolled back to generation $num." - else - echo "Generation $num not found." - fi - fi - fi - echo - read -r -n 1 -s -p "Press any key to return..." - echo - ;; - "Exit"|*) break ;; - esac - done -} - -function appearance_menu() { - while true; do - choice=$(gum choose "Pick Theme" "Toggle Blur" "Set Gaps" "Back") - case "$choice" in - "Pick Theme") - themes=$(nomarchy-state-sync list) - chosen=$(printf "%s" "$themes" | gum choose) - if [ -n "$chosen" ]; then - nomarchy-state-sync apply "$chosen" - fi - ;; - "Toggle Blur") - blur=$(get_state "ui.blur") - if [ "$blur" = "true" ]; then - set_state "ui.blur" "false" - else - set_state "ui.blur" "true" - fi - echo "Blur setting saved (requires rebuild)." - sleep 1 - ;; - "Set Gaps") - gaps=$(gum input --prompt "Enter gaps in pixels: " --placeholder "$(get_state ui.gapsOut)") - if [ -n "$gaps" ]; then - set_state "ui.gapsOut" "$gaps" - echo "Gaps set to $gaps (requires rebuild)." - sleep 1 - fi - ;; - "Back"|*) break ;; - esac - done -} - -function toggles_menu() { - while true; do - choice=$(gum choose "Auto-commit" "Auto-timezone" "Night Light" "Updates" "Bluetooth" "Printing" "Terminal" "Keyboard Layout" "Auto-Login" "Back") - case "$choice" in - "Auto-commit") - cur=$(get_state "settings.autoCommit") - if [ "$cur" = "true" ]; then - set_state "settings.autoCommit" "false" - echo "Auto-commit disabled." - else - set_state "settings.autoCommit" "true" - echo "Auto-commit enabled." - fi - sleep 1 - ;; - "Auto-timezone") - cur=$(get_state "settings.autoTimezone") - if [ "$cur" = "true" ]; then - set_state "settings.autoTimezone" "false" - echo "Auto-timezone disabled." - else - set_state "settings.autoTimezone" "true" - echo "Auto-timezone enabled." - fi - sleep 1 - ;; - "Night Light") - if command -v nomarchy-nightlight >/dev/null 2>&1; then - nomarchy-nightlight toggle - sleep 1 - else - echo "nomarchy-nightlight not found." - sleep 1 - fi - ;; - "Updates") - cur=$(get_state "settings.updates.enable") - if [ "$cur" = "true" ]; then - set_state "settings.updates.enable" "false" - echo "Updates checker disabled (requires rebuild)." - else - set_state "settings.updates.enable" "true" - echo "Updates checker enabled (requires rebuild)." - fi - sleep 1 - ;; - "Bluetooth") - cur=$(get_state "settings.bluetooth.enable") - if [ "$cur" = "true" ]; then - set_state "settings.bluetooth.enable" "false" - echo "Bluetooth disabled (requires rebuild)." - else - set_state "settings.bluetooth.enable" "true" - echo "Bluetooth enabled (requires rebuild)." - fi - sleep 1 - ;; - "Printing") - cur=$(get_state "settings.printing.enable") - if [ "$cur" = "true" ]; then - set_state "settings.printing.enable" "false" - echo "Printing services disabled (requires rebuild)." - else - set_state "settings.printing.enable" "true" - echo "Printing services enabled (requires rebuild)." - fi - sleep 1 - ;; - "Terminal") - echo "Kitty is Nomarchy's terminal (themed from theme-state)." - echo "Override nomarchy.terminal in home.nix only if you install another yourself." - sleep 2 - ;; - "Keyboard Layout") - layout=$(gum input --prompt "Enter keyboard layout (e.g. us, de, es): " --placeholder "$(get_state settings.keyboard.layout | tr -d '"')") - if [ -n "$layout" ]; then - set_state "settings.keyboard.layout" "\"$layout\"" - echo "Keyboard layout set to $layout (requires rebuild)." - fi - sleep 1 - ;; - "Auto-Login") - user=$(gum input --prompt "Enter auto-login username (or empty to disable): " --placeholder "$(get_state settings.greeter.autoLogin | tr -d '"')") - if [ -n "$user" ]; then - set_state "settings.greeter.autoLogin" "\"$user\"" - echo "Auto-login set to $user (requires rebuild)." - else - set_state "settings.greeter.autoLogin" "null" - echo "Auto-login disabled (requires rebuild)." - fi - sleep 1 - ;; - "Back"|*) break ;; - esac - done -} - -if [ "${1:-}" = "--first-boot" ]; then - first_boot -else - main_menu -fi