feat(menu): Recovery scopes (#111), System IA (#105), drop Control Center (#110)
Some checks failed
Check / eval (push) Has been cancelled

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.
This commit is contained in:
2026-07-15 11:49:57 +01:00
parent 0ba9633728
commit 856a9d1a49
14 changed files with 246 additions and 420 deletions

View File

@@ -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+<mnemonic>); 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
'';