feat(menu): group keybinds cheatsheet Window/Workspace/Menu/Media (#108)
All checks were successful
Check / eval (push) Successful in 3m39s

Every bind in keybinds.nix carries a group; SUPER+? renders section
headers. checks.keybinds-cheatsheet asserts all groups are valid and
every description appears in the sheet (same source as Hyprland binds).

Verified: V0 flake check; built cheatsheet shows ── Window ── / etc.
This commit is contained in:
2026-07-15 10:04:33 +01:00
parent e6be5a5770
commit be2d0d200b
6 changed files with 131 additions and 61 deletions

View File

@@ -261,13 +261,6 @@ scoped implementation/verification tasks before work starts. Pass = every
current route has one deliberate home, navigation remains shallow, and no current route has one deliberate home, navigation remains shallow, and no
root-level entry is added or lost. root-level entry is added or lost.
### 108. Keybindings menu presentation and completeness audit
Group the menu as Window, Workspace, Menu, and Media, then prove every live
Hyprland binding appears from the canonical source. Do not repeat the obsolete
claim that float/move bindings are absent. Pass = presentation is scannable and
an automated comparison detects omissions or stale displayed bindings.
### 110. `[big]` Retire Control Center safely ### 110. `[big]` Retire Control Center safely
Split this into two phases before implementation: first build a migration/drop Split this into two phases before implementation: first build a migration/drop

View File

@@ -19,6 +19,16 @@ Template:
--- ---
## 2026-07-15 — #108 keybinds cheatsheet groups + completeness check
- **Task:** BACKLOG #108 — group SUPER+? as Window/Workspace/Menu/Media;
automated completeness.
- **Did:** every bind/extra in keybinds.nix gets `group`; cheatsheet renders
section headers; `checks.keybinds-cheatsheet` asserts groups + every desc
present (proved via assert).
- **Verified:** V0 flake check; cheatsheet file shows ── Window ── etc.
- **Pending:** V3 eyeball SUPER+? on a real session (optional).
- **Next suggestion:** #94 residual or #107 state.json rename.
## 2026-07-15 — Fix batch #112 disk picker, #106 menu Back, #113 offline theme ## 2026-07-15 — Fix batch #112 disk picker, #106 menu Back, #113 offline theme
- **Task:** Second fix-only batch from NEXT/NOW residuals. - **Task:** Second fix-only batch from NEXT/NOW residuals.
- **Did:** (112) `list_installable_disks` excludes fd/loop/sr/zram/tiny - **Did:** (112) `list_installable_disks` excludes fd/loop/sr/zram/tiny

View File

@@ -404,6 +404,10 @@ Design/decision records and a running log of shipped work (items marked
decision rather than a drive-by. Proved to fail: dropping snapshot makes it decision rather than a drive-by. Proved to fail: dropping snapshot makes it
name the missing entry. **V3 pending** — that the apps *launch* needs real name the missing entry. **V3 pending** — that the apps *launch* needs real
hardware (HARDWARE-QUEUE, Acer M5-481T). hardware (HARDWARE-QUEUE, Acer M5-481T).
- ✓ **Keybinds cheatsheet groups (#108):** SUPER+? sections Window /
Workspace / Menu / Media from keybinds.nix `group`;
`checks.keybinds-cheatsheet` fails if a bind lacks a group or a desc is
missing from the sheet.
- ✓ **Fix batch #112 / #106 / #113:** installer disk picker drops fd0/loop/ - ✓ **Fix batch #112 / #106 / #113:** installer disk picker drops fd0/loop/
optical/<8GiB and lists largest-first (OVMF no longer defaults to floppy); optical/<8GiB and lists largest-first (OVMF no longer defaults to floppy);
`checks.menu-back` guards every internal rofi list emits ↩ Back; offline `checks.menu-back` guards every internal rofi list emits ↩ Back; offline

View File

@@ -477,6 +477,48 @@
touch $out touch $out
''; '';
# Keybinds cheatsheet completeness + grouping (#108): every bind
# from keybinds.nix has a group, and every desc appears in the
# rendered cheatsheet (same source hyprland.nix binds from).
keybinds-cheatsheet =
let
inherit (nixpkgs.lib) assertMsg concatLists concatMapStringsSep
concatStringsSep elem filter hasInfix;
kb = import ./modules/home/keybinds.nix;
groups = [ "Window" "Workspace" "Menu" "Media" ];
allBinds = kb.binds ++ kb.multiLayoutBinds;
badGroup = filter (b: !(elem (b.group or null) groups)) allBinds;
extraBad = filter (e: !(elem (e.group or null) groups)) kb.extra;
descs = map (b: b.desc) allBinds ++ map (e: e.desc) kb.extra;
linesFor = g:
let rows = filter (b: b.group == g) allBinds
++ filter (e: e.group == g) kb.extra;
in if rows == [ ] then [ ]
else [ (" " + g + " ") ] ++ map (r: r.desc) rows;
sheet = concatStringsSep "\n" (concatLists (map linesFor groups));
missing = filter (d: !(hasInfix d sheet)) descs;
ok = badGroup == [ ] && extraBad == [ ] && missing == [ ]
&& hasInfix " Window " sheet
&& hasInfix " Workspace " sheet
&& hasInfix " Menu " sheet
&& hasInfix " Media " sheet;
msg =
(if badGroup != [ ] then
"binds missing/invalid group: "
+ concatMapStringsSep ", " (b: b.desc) badGroup + "\n"
else "")
+ (if extraBad != [ ] then
"extra missing/invalid group: "
+ concatMapStringsSep ", " (e: e.desc) extraBad + "\n"
else "")
+ (if missing != [ ] then
"descs missing from grouped sheet: "
+ concatStringsSep ", " missing + "\n"
else "");
in
assert assertMsg ok ("keybinds-cheatsheet: " + msg);
pkgs.runCommand "nomarchy-keybinds-cheatsheet" { } "touch $out";
# Installer keyboard no-variant regression (item 91): gum's `(none)` # Installer keyboard no-variant regression (item 91): gum's `(none)`
# row is display-only. Exercise it through the real template # row is display-only. Exercise it through the real template
# patcher, then build the exact us + empty-variant console keymap # patcher, then build the exact us + empty-variant console keymap

View File

@@ -7,85 +7,86 @@
# key — the key name # key — the key name
# action — the Hyprland dispatcher (+args) that the bind runs # action — the Hyprland dispatcher (+args) that the bind runs
# desc — human label, shown in the cheatsheet # desc — human label, shown in the cheatsheet
# group — cheatsheet section: Window | Workspace | Menu | Media (#108)
# #
# `extra` holds cheatsheet-only rows for binds generated elsewhere # `extra` holds cheatsheet-only rows for binds generated elsewhere
# (per-workspace numbers, mouse drags) so they still show up under SUPER+?. # (per-workspace numbers, mouse drags) so they still show up under SUPER+?.
{ {
binds = [ binds = [
{ mods = "$mod"; key = "Return"; action = "exec, $terminal"; desc = "Open terminal"; } { mods = "$mod"; key = "Return"; action = "exec, $terminal"; desc = "Open terminal"; group = "Window"; }
{ mods = "$mod"; key = "Space"; action = "exec, rofi -show drun -theme launcher"; desc = "App launcher"; } { mods = "$mod"; key = "Space"; action = "exec, rofi -show drun -theme launcher"; desc = "App launcher"; group = "Menu"; }
{ mods = "$mod"; key = "M"; action = "exec, nomarchy-menu"; desc = "Main menu"; } { mods = "$mod"; key = "M"; action = "exec, nomarchy-menu"; desc = "Main menu"; group = "Menu"; }
{ mods = "$mod"; key = "E"; action = "exec, $terminal -e yazi"; desc = "File manager (yazi)"; } { mods = "$mod"; key = "E"; action = "exec, $terminal -e yazi"; desc = "File manager (yazi)"; group = "Window"; }
{ mods = "$mod"; key = "Q"; action = "killactive"; desc = "Close window"; } { mods = "$mod"; key = "Q"; action = "killactive"; desc = "Close window"; group = "Window"; }
{ mods = "$mod"; key = "F"; action = "fullscreen"; desc = "Fullscreen"; } { mods = "$mod"; key = "F"; action = "fullscreen"; desc = "Fullscreen"; group = "Window"; }
{ mods = "$mod"; key = "V"; action = "togglefloating"; desc = "Toggle floating"; } { mods = "$mod"; key = "V"; action = "togglefloating"; desc = "Toggle floating"; group = "Window"; }
{ mods = "$mod SHIFT"; key = "E"; action = "exit"; desc = "Exit Hyprland"; } { mods = "$mod SHIFT"; key = "E"; action = "exit"; desc = "Exit Hyprland"; group = "Window"; }
# Theme picker (menu dispatcher): apply writes the state and runs # Theme picker (menu dispatcher): apply writes the state and runs
# home-manager switch (progress via notify-send). # home-manager switch (progress via notify-send).
{ mods = "$mod"; key = "T"; action = "exec, nomarchy-menu theme"; desc = "Theme picker"; } { mods = "$mod"; key = "T"; action = "exec, nomarchy-menu theme"; desc = "Theme picker"; group = "Menu"; }
# Cycle the current theme's wallpapers (instant, no rebuild). # Cycle the current theme's wallpapers (instant, no rebuild).
{ mods = "$mod SHIFT"; key = "T"; action = "exec, nomarchy-theme-sync bg next"; desc = "Next wallpaper"; } { mods = "$mod SHIFT"; key = "T"; action = "exec, nomarchy-theme-sync bg next"; desc = "Next wallpaper"; group = "Menu"; }
# Power menu via the dispatcher. Not Escape: Super+Escape gets # Power menu via the dispatcher. Not Escape: Super+Escape gets
# swallowed before reaching the dispatcher on some setups. # swallowed before reaching the dispatcher on some setups.
{ mods = "$mod"; key = "X"; action = "exec, nomarchy-menu power"; desc = "Power menu"; } { mods = "$mod"; key = "X"; action = "exec, nomarchy-menu power"; desc = "Power menu"; group = "Menu"; }
{ mods = "$mod"; key = "N"; action = "exec, swaync-client -t"; desc = "Notification centre"; } { mods = "$mod"; key = "N"; action = "exec, swaync-client -t"; desc = "Notification centre"; group = "Media"; }
# SUPER+? — ? is Shift+/. SHIFT stays in the modmask (you hold it), but # SUPER+? — ? is Shift+/. SHIFT stays in the modmask (you hold it), but
# the keysym must be the BASE key `slash`, not `question`: Hyprland # the keysym must be the BASE key `slash`, not `question`: Hyprland
# resolves the sym with Shift consumed, so `question` never matches while # resolves the sym with Shift consumed, so `question` never matches while
# Shift is down — same as the `$mod SHIFT, 1` workspace binds. (item 26 # Shift is down — same as the `$mod SHIFT, 1` workspace binds. (item 26
# fixed the modmask but kept the shifted keysym → still dead; item 32.) # fixed the modmask but kept the shifted keysym → still dead; item 32.)
# The cheatsheet still renders this row as SUPER + ? (rofi.nix). # The cheatsheet still renders this row as SUPER + ? (rofi.nix).
{ mods = "$mod SHIFT"; key = "slash"; action = "exec, nomarchy-menu keybinds"; desc = "Keybindings cheatsheet"; } { mods = "$mod SHIFT"; key = "slash"; action = "exec, nomarchy-menu keybinds"; desc = "Keybindings cheatsheet"; group = "Menu"; }
# Menu functions — SUPER+CTRL+<mnemonic> jumps straight to a # Menu functions — SUPER+CTRL+<mnemonic> jumps straight to a
# nomarchy-menu module (all also reachable from the SUPER+M picker). # nomarchy-menu module (all also reachable from the SUPER+M picker).
{ mods = "$mod CTRL"; key = "V"; action = "exec, nomarchy-menu clipboard"; desc = "Clipboard history"; } { mods = "$mod CTRL"; key = "V"; action = "exec, nomarchy-menu clipboard"; desc = "Clipboard history"; group = "Menu"; }
{ mods = "$mod CTRL"; key = "C"; action = "exec, nomarchy-menu calc"; desc = "Calculator"; } { mods = "$mod CTRL"; key = "C"; action = "exec, nomarchy-menu calc"; desc = "Calculator"; group = "Menu"; }
{ mods = "$mod CTRL"; key = "W"; action = "exec, nomarchy-menu web"; desc = "Web search"; } { mods = "$mod CTRL"; key = "W"; action = "exec, nomarchy-menu web"; desc = "Web search"; group = "Menu"; }
{ mods = "$mod CTRL"; key = "F"; action = "exec, nomarchy-menu files"; desc = "File search"; } { mods = "$mod CTRL"; key = "F"; action = "exec, nomarchy-menu files"; desc = "File search"; group = "Menu"; }
{ mods = "$mod CTRL"; key = "E"; action = "exec, nomarchy-menu emoji"; desc = "Emoji picker"; } { mods = "$mod CTRL"; key = "E"; action = "exec, nomarchy-menu emoji"; desc = "Emoji picker"; group = "Menu"; }
{ mods = "$mod CTRL"; key = "N"; action = "exec, nomarchy-menu network"; desc = "Network (networkmanager_dmenu)"; } { mods = "$mod CTRL"; key = "N"; action = "exec, nomarchy-menu network"; desc = "Network (networkmanager_dmenu)"; group = "Menu"; }
{ mods = "$mod CTRL"; key = "B"; action = "exec, nomarchy-menu bluetooth"; desc = "Bluetooth"; } { mods = "$mod CTRL"; key = "B"; action = "exec, nomarchy-menu bluetooth"; desc = "Bluetooth"; group = "Menu"; }
{ mods = "$mod CTRL"; key = "K"; action = "exec, nomarchy-menu keyboard"; desc = "Keyboard layout"; } { mods = "$mod CTRL"; key = "K"; action = "exec, nomarchy-menu keyboard"; desc = "Keyboard layout"; group = "Menu"; }
{ mods = "$mod CTRL"; key = "S"; action = "exec, nomarchy-menu capture"; desc = "Screenshot / capture"; } { mods = "$mod CTRL"; key = "S"; action = "exec, nomarchy-menu capture"; desc = "Screenshot / capture"; group = "Media"; }
{ mods = "$mod CTRL"; key = "P"; action = "exec, nomarchy-menu colorpicker"; desc = "Color picker ( clipboard)"; } { mods = "$mod CTRL"; key = "P"; action = "exec, nomarchy-menu colorpicker"; desc = "Color picker ( clipboard)"; group = "Menu"; }
{ mods = "$mod CTRL"; key = "A"; action = "exec, nomarchy-menu ask"; desc = "Ask Claude"; } { mods = "$mod CTRL"; key = "A"; action = "exec, nomarchy-menu ask"; desc = "Ask Claude"; group = "Menu"; }
{ mods = "$mod CTRL"; key = "D"; action = "exec, nomarchy-menu dnd"; desc = "Do Not Disturb toggle"; } { mods = "$mod CTRL"; key = "D"; action = "exec, nomarchy-menu dnd"; desc = "Do Not Disturb toggle"; group = "Media"; }
# I as in "settings" muscle memory (Super+I elsewhere); T for Tools. # I as in "settings" muscle memory (Super+I elsewhere); T for Tools.
{ mods = "$mod CTRL"; key = "I"; action = "exec, nomarchy-menu system"; desc = "System menu"; } { mods = "$mod CTRL"; key = "I"; action = "exec, nomarchy-menu system"; desc = "System menu"; group = "Menu"; }
{ mods = "$mod CTRL"; key = "T"; action = "exec, nomarchy-menu tools"; desc = "Tools menu"; } { mods = "$mod CTRL"; key = "T"; action = "exec, nomarchy-menu tools"; desc = "Tools menu"; group = "Menu"; }
# The one SUPER+CTRL row that is not a menu module: lock earns the # The one SUPER+CTRL row that is not a menu module: lock earns the
# family slot because bare SUPER+L is a common app bind and Escape is # family slot because bare SUPER+L is a common app bind and Escape is
# unusable (see the power-menu note above). Same dispatcher the power # unusable (see the power-menu note above). Same dispatcher the power
# menu's Lock row uses (rofi.nix) — logind, not `hyprlock` directly, so # menu's Lock row uses (rofi.nix) — logind, not `hyprlock` directly, so
# the session is marked locked and hypridle's lock_cmd stays the one # the session is marked locked and hypridle's lock_cmd stays the one
# place that decides how a lock actually looks. # place that decides how a lock actually looks.
{ mods = "$mod CTRL"; key = "L"; action = "exec, loginctl lock-session"; desc = "Lock screen"; } { mods = "$mod CTRL"; key = "L"; action = "exec, loginctl lock-session"; desc = "Lock screen"; group = "Menu"; }
{ mods = "$mod SHIFT"; key = "C"; action = "exec, hyprpicker -a"; desc = "Color picker ( clipboard)"; } { mods = "$mod SHIFT"; key = "C"; action = "exec, hyprpicker -a"; desc = "Color picker ( clipboard)"; group = "Menu"; }
# Focus — SUPER + arrow keys. # Focus — SUPER + arrow keys.
{ mods = "$mod"; key = "left"; action = "movefocus, l"; desc = "Focus left"; } { mods = "$mod"; key = "left"; action = "movefocus, l"; desc = "Focus left"; group = "Window"; }
{ mods = "$mod"; key = "right"; action = "movefocus, r"; desc = "Focus right"; } { mods = "$mod"; key = "right"; action = "movefocus, r"; desc = "Focus right"; group = "Window"; }
{ mods = "$mod"; key = "up"; action = "movefocus, u"; desc = "Focus up"; } { mods = "$mod"; key = "up"; action = "movefocus, u"; desc = "Focus up"; group = "Window"; }
{ mods = "$mod"; key = "down"; action = "movefocus, d"; desc = "Focus down"; } { mods = "$mod"; key = "down"; action = "movefocus, d"; desc = "Focus down"; group = "Window"; }
# Multi-monitor workspace movement — SUPER + ALT + arrow keys. # Multi-monitor workspace movement — SUPER + ALT + arrow keys.
{ mods = "$mod ALT"; key = "left"; action = "movecurrentworkspacetomonitor, l"; desc = "Move workspace to left monitor"; } { mods = "$mod ALT"; key = "left"; action = "movecurrentworkspacetomonitor, l"; desc = "Move workspace to left monitor"; group = "Workspace"; }
{ mods = "$mod ALT"; key = "right"; action = "movecurrentworkspacetomonitor, r"; desc = "Move workspace to right monitor"; } { mods = "$mod ALT"; key = "right"; action = "movecurrentworkspacetomonitor, r"; desc = "Move workspace to right monitor"; group = "Workspace"; }
{ mods = "$mod ALT"; key = "up"; action = "movecurrentworkspacetomonitor, u"; desc = "Move workspace to upper monitor"; } { mods = "$mod ALT"; key = "up"; action = "movecurrentworkspacetomonitor, u"; desc = "Move workspace to upper monitor"; group = "Workspace"; }
{ mods = "$mod ALT"; key = "down"; action = "movecurrentworkspacetomonitor, d"; desc = "Move workspace to lower monitor"; } { mods = "$mod ALT"; key = "down"; action = "movecurrentworkspacetomonitor, d"; desc = "Move workspace to lower monitor"; group = "Workspace"; }
# Screenshots (the menu's Capture module has the rest: OCR, recording). # Screenshots (the menu's Capture module has the rest: OCR, recording).
# Bare Print → region to clipboard; the two → file binds save a # Bare Print → region to clipboard; the two → file binds save a
# timestamped PNG under ~/Pictures/Screenshots and toast the path, the # timestamped PNG under ~/Pictures/Screenshots and toast the path, the
# same plumbing the Capture menu's "→ file" rows use. # same plumbing the Capture menu's "→ file" rows use.
{ mods = ""; key = "Print"; action = "exec, grim -g \"$(slurp)\" - | wl-copy && notify-send Screenshot \"Region copied to clipboard.\""; desc = "Screenshot region clipboard"; } { mods = ""; key = "Print"; action = "exec, grim -g \"$(slurp)\" - | wl-copy && notify-send Screenshot \"Region copied to clipboard.\""; desc = "Screenshot region clipboard"; group = "Media"; }
{ mods = "SHIFT"; key = "Print"; action = "exec, f=$HOME/Pictures/Screenshots/$(date +%Y%m%d-%H%M%S).png; mkdir -p $HOME/Pictures/Screenshots && grim -g \"$(slurp)\" \"$f\" && notify-send \"Screenshot saved\" \"$f\""; desc = "Screenshot region file"; } { mods = "SHIFT"; key = "Print"; action = "exec, f=$HOME/Pictures/Screenshots/$(date +%Y%m%d-%H%M%S).png; mkdir -p $HOME/Pictures/Screenshots && grim -g \"$(slurp)\" \"$f\" && notify-send \"Screenshot saved\" \"$f\""; desc = "Screenshot region file"; group = "Media"; }
{ mods = "CTRL"; key = "Print"; action = "exec, f=$HOME/Pictures/Screenshots/$(date +%Y%m%d-%H%M%S).png; mkdir -p $HOME/Pictures/Screenshots && grim \"$f\" && notify-send \"Screenshot saved\" \"$f\""; desc = "Screenshot screen file"; } { mods = "CTRL"; key = "Print"; action = "exec, f=$HOME/Pictures/Screenshots/$(date +%Y%m%d-%H%M%S).png; mkdir -p $HOME/Pictures/Screenshots && grim \"$f\" && notify-send \"Screenshot saved\" \"$f\""; desc = "Screenshot screen file"; group = "Media"; }
{ mods = "$mod SHIFT"; key = "Print"; action = "exec, f=$HOME/Pictures/Screenshots/$(date +%Y%m%d-%H%M%S).png; mkdir -p $HOME/Pictures/Screenshots && grim -g \"$(slurp)\" - | satty --filename - --fullscreen --output-filename \"$f\""; desc = "Annotate region"; } { mods = "$mod SHIFT"; key = "Print"; action = "exec, f=$HOME/Pictures/Screenshots/$(date +%Y%m%d-%H%M%S).png; mkdir -p $HOME/Pictures/Screenshots && grim -g \"$(slurp)\" - | satty --filename - --fullscreen --output-filename \"$f\""; desc = "Annotate region"; group = "Media"; }
]; ];
# Rendered only when the session has >1 layout (a comma in # Rendered only when the session has >1 layout (a comma in
@@ -94,14 +95,14 @@
# step. `current` targets the focused keyboard, so a board with its # step. `current` targets the focused keyboard, so a board with its
# own per-device layout (a single one) is a no-op, never a leak. # own per-device layout (a single one) is a no-op, never a leak.
multiLayoutBinds = [ multiLayoutBinds = [
{ mods = "$mod SHIFT"; key = "K"; action = "exec, hyprctl switchxkblayout current next"; desc = "Cycle keyboard layout"; } { mods = "$mod SHIFT"; key = "K"; action = "exec, hyprctl switchxkblayout current next"; desc = "Cycle keyboard layout"; group = "Menu"; }
]; ];
extra = [ extra = [
{ keys = "SUPER + 1-9, 0"; desc = "Switch to workspace 1-10 (0 = 10)"; } { keys = "SUPER + 1-9, 0"; desc = "Switch to workspace 1-10 (0 = 10)"; group = "Workspace"; }
{ keys = "SUPER + SHIFT + 1-9, 0"; desc = "Move window to workspace 1-10 (0 = 10)"; } { keys = "SUPER + SHIFT + 1-9, 0"; desc = "Move window to workspace 1-10 (0 = 10)"; group = "Workspace"; }
{ keys = "SUPER + drag"; desc = "Move (LMB) / resize (RMB) window"; } { keys = "SUPER + drag"; desc = "Move (LMB) / resize (RMB) window"; group = "Window"; }
{ keys = "Volume / Brightness"; desc = "Hardware keys, shown via the OSD"; } { keys = "Volume / Brightness"; desc = "Hardware keys, shown via the OSD"; group = "Media"; }
{ keys = "Bar: 󰾪 click"; desc = "Caffeine hold the screen awake (idle inhibitor)"; } { keys = "Bar: 󰾪 click"; desc = "Caffeine hold the screen awake (idle inhibitor)"; group = "Media"; }
]; ];
} }

View File

@@ -133,17 +133,37 @@ let
# renders "SHIFT + /". `?` IS Shift+/, so collapse that back to the # renders "SHIFT + /". `?` IS Shift+/, so collapse that back to the
# single documented glyph — the cheatsheet reads "SUPER + ?". # single documented glyph — the cheatsheet reads "SUPER + ?".
in lib.replaceStrings [ "SHIFT + /" ] [ "?" ] (prefix + key); in lib.replaceStrings [ "SHIFT + /" ] [ "?" ] (prefix + key);
cheatRows = # Grouped cheatsheet (#108): Window → Workspace → Menu → Media.
map (b: padRight 22 (prettyKeys b) + b.desc) (keybinds.binds # Section headers are non-selectable chrome (rofi still shows them as rows;
# Enter is a no-op either way). Order matches the group enum.
cheatGroupOrder = [ "Window" "Workspace" "Menu" "Media" ];
cheatBindRows =
map (b: {
inherit (b) group desc;
keys = prettyKeys b;
}) (keybinds.binds
++ lib.optionals (lib.hasInfix "," cfg.keyboard.layout) ++ lib.optionals (lib.hasInfix "," cfg.keyboard.layout)
keybinds.multiLayoutBinds) keybinds.multiLayoutBinds)
# Launch-or-focus binds (nomarchy.launchOrFocus, generated in # Launch-or-focus binds (nomarchy.launchOrFocus, generated in
# hyprland.nix) — same renderer, so they land in the cheatsheet too. # hyprland.nix) — same renderer, so they land in the cheatsheet too.
++ map (e: padRight 22 (prettyKeys e) ++ map (e: {
+ (if e.desc != "" then e.desc group = "Menu";
else "Focus or launch ${if e.command == "" then lib.toLower e.class else e.command}")) keys = prettyKeys e;
cfg.launchOrFocus desc = if e.desc != "" then e.desc
++ map (e: padRight 22 e.keys + e.desc) keybinds.extra; else "Focus or launch ${if e.command == "" then lib.toLower e.class else e.command}";
}) cfg.launchOrFocus
++ map (e: {
group = e.group or "Menu";
keys = e.keys;
inherit (e) desc;
}) keybinds.extra;
cheatRowsFor = g:
let rows = lib.filter (r: r.group == g) cheatBindRows;
in lib.optionals (rows != [ ]) (
[ (" " + g + " ") ]
++ map (r: padRight 22 r.keys + r.desc) rows
);
cheatRows = lib.concatLists (map cheatRowsFor cheatGroupOrder);
cheatsheetFile = pkgs.writeText "nomarchy-keybinds.txt" cheatsheetFile = pkgs.writeText "nomarchy-keybinds.txt"
(lib.concatStringsSep "\n" cheatRows + "\n"); (lib.concatStringsSep "\n" cheatRows + "\n");