Files
Nomarchy/docs/ROADMAP.md
Bernardo Magri e1cf190dd2 feat(menu): visual theme picker — rofi preview grid
Replace the plain-text `nomarchy-menu theme` list with a grid of real
desktop previews. The grid, the Name→slug map and the active `✓` mark
are generated at eval time from the preset JSONs (`readDir` + `fromJSON`
in rofi.nix); "active" is just `t.slug`, since every switch rebuilds the
menu. Grouped dark-first then light in one scrollable grid (the previews
make the mode obvious — no light/dark submenu split), `flow: horizontal`
so Down scrolls row-by-row.

Previews are committed as themes/<slug>/preview.png at 480×270 (~2.4 MB
for all 21). rofi's element-icon `size` is single-value → a *square*
cell that *contains* the icon, so a 16:9 preview would letterbox; a
build-time imagemagick step centre-crops each to a square so it fills
the cell edge-to-edge (source stays 16:9, crop is reversible). The
window width is derived from `themeGridIconW` so a column is exactly the
icon side — no slack margins. Themes without a preview degrade to a
plain-name row.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 17:26:39 +01:00

41 KiB
Raw Blame History

Roadmap & changelog

Forward-looking plans, plus a running log of shipped fixes (the "Known issues & follow-ups" section). Split out of the README so that stays a focused entry point — what Nomarchy is, how to install it, and how to override it. Items marked ✓ are shipped.

Roadmap

  • Menu system (apps launcher + theme switching + system actions), built on rofi 2.0 (native Wayland on 26.05) — its .rasi theme is baked from theme-state.json like every other app, with rich per-element styling:

    • ✓ shipped: modules/home/rofi.nix (per-element theme generated from the palette, themes/<slug>/rofi.rasi whole-swap) and the nomarchy-menu dispatcher: root picker (no args) · power (lock/logout/suspend/hibernate/reboot/shutdown, SUPER+X) · theme (SUPER+T) · clipboard (cliphist, SUPER+CTRL+V) · calc (rofi-calc, live, SUPER+CTRL+C) · files (fd → xdg-open, SUPER+CTRL+F) · emoji (rofi-emoji, SUPER+CTRL+E) · web (Google). SUPER+D is rofi -show drun.
    • ✓ shipped modules: network (nmtui in $TERMINAL) · bluetooth (blueman-manager) · capture (grim/slurp submenu: region/full → clipboard/file, saved to ~/Pictures/Screenshots) · keybinds (the cheatsheet, see below) · ask (free-text → claude CLI in a terminal; auths via OAuth, no API key; pulled fresh via npx @anthropic-ai/claude-code@latest rather than the nixpkgs package, which lags model releases — nodejs is bundled for npx; REPL stays open)
    • ✓ keybindings cheatsheet: modules/home/keybinds.nix is the single source for both the Hyprland binds and the SUPER+? rofi list, so they can't drift; nomarchy-menu keybinds renders the padded two-column sheet (generated/mouse binds carried in its extra rows)
    • ✓ shipped binds: SUPER+Spacerofi -show drun (quick launch) · SUPER+Mnomarchy-menu (main menu) · SUPER+? → the cheatsheet; SUPER+D stays -show drun
    • launcher icons: ✓ show-icons on, drawing from the theme's icon set (Papirus, via the icon-themes work below)
    • decision record: resolves the old Walker/Lua question — no GTK4 launcher, no second theming pipeline; the dispatcher owns the menu structure, so the renderer stays swappable (we moved fuzzel → rofi 2.0 once mainline gained native Wayland, for its richer theming)
    • Organize the picker into category submenus: the root picker had grown into a long flat list of 16 entries. It's now six: Apps · Theme · Tools · System · Power · Keybindings. Tools (Calculator, Clipboard, Emoji, Files, Web, Capture, Ask) and System (Network, Bluetooth, DND, and the self-gated Snapshots / Power-profile) are submenus the dispatcher routes via nomarchy-menu tools|system, each ending in a ← Back entry that re-opens the root. The direct SUPER+CTRL+<mnemonic> binds still hit the leaves straight (bypassing the menu), and the self-gated entries still hide when unavailable. Remaining (optional): a Look & Feel category once there are more appearance toggles to group with Theme (night-light, wallpaper). General pattern (ongoing): the grouping is a standing convention, not a one-off — any new feature that earns a menu entry must be placed in the right submenu (don't let the root creep back to a flat list), with its direct SUPER+CTRL+<mnemonic> bind and self-gating as applicable.
    • Back everywhere: every list menu now ends with a ↩ Back entry that returns one level up (power/theme/power-profile/clipboard/files/capture, not just Tools/System), so you never have to Esc out and reopen. A back helper + a shared BACK label keep it uniform; matched exactly so it can't collide with clipboard/filename content. Esc still quits instantly.
  • Menu modules from rofi plugins: the old calc flow committed the expression blind (result only in the next menu's -mesg) and qalc -t misparsed common phrasings (15% of 200rem(15, 1 B), the natural- language "of" tripping the CLI). Resolved by adopting purpose-built rofi modi (programs.rofi.plugins), all themed through the same .rasi:

    • calcrofi-calc: live results as you type via libqalculate directly (dodging the qalc-CLI "of" bug), Enter copies, menu persists to chain calculations.
    • emojirofi-emoji (new module, SUPER+CTRL+E): glyph picker, copies via the plugin's Wayland clipboard adapter.
    • files stays the hand-rolled fdrofi -dmenu → xdg-open fuzzy search: rofi-file-browser-extended was tried and dropped — its navigate-a-tree model felt worse than flat fuzzy-find for a quick launcher, and yazi (SUPER+E) already covers real browsing.
  • More menu modules from rofi tools: the script-based counterparts (run via rofi -dmenu, like the hand-rolled modules), each a deliberate replacement of an existing flow: rofi-network-manager (a keyboard wifi/VPN picker vs today's nmtui-in-terminal network), rofi-rbw / rofi-pass (a secrets module — Bitwarden via rbw, or pass — pairs with keys.nix), and rofi-pulse-select (an audio sink/source switcher). Decide per-module whether it earns replacing the current path.

  • Theme parity with legacy: summer-day/night now carry their legacy bar layouts as waybar.jsonc whole-swaps (adapted: dead legacy script modules dropped, Nerd-Fonts-v2 codepoints remapped to FontAwesome/v3, logo button opens nomarchy-menu); the other four identity themes are palette recolors and already match. Remaining: a visual pass over all six on the live ISO

  • Per-theme rofi identity: the themes/<slug>/rofi.rasi whole-swap ships, and summer-day/night carry their legacy designs (inverted window, green inputbar, yellow bottom-border). Remaining: author .rasi identities for the other four ported themes if/when they want one (the generated palette theme is the default and looks fine)

  • Visual theme picker (preview thumbnails): nomarchy-menu theme is now a rofi icon grid of real desktop previews instead of a plain-text list — each theme a screenshot of its themed desktop (waybar + floating terminal), pretty name beneath, grouped dark-first then light in one scrollable grid (the previews make the mode obvious, so no light/dark submenu split), the active theme marked , ending in ↩ Back. The grid, the Name→slug map and the active mark are all generated at eval time from the preset JSONs in rofi.nix (builtins.readDir + fromJSON); "active" is just t.slug, since every switch rebuilds the menu. Grid layout via a per-invocation -theme-str (listview { columns: 3; flow: horizontal; } so Down scrolls row-by-row, + vertical element cards, name centred below). Sizing gotcha (learned the hard way): rofi's element-icon size is a single value → a square cell (a two-value WxH is silently collapsed), and the icon is contained in that square. So a 16:9 preview letterboxes (theme-coloured bands top/ bottom), and a cell can't be shorter-than-square without the bands returning. The fix: a build-time imagemagick step centre-crops each preview to a square so it fills the cell edge-to-edge; the window width is derived so a column is exactly the icon side (no slack margins). One knob, themeGridIconW (240px), drives both icon and window. Workflow: Bernardo captures previews on real hardware and commits them as themes/<slug>/preview.png, already downscaled to 480×270 (~2.4 MB total for all 21, vs ~28 MB full-res) — the source stays 16:9 and untouched; only the displayed thumb is squared at build, so the crop is reversible. Graceful fallback when a theme has no preview.png: a plain-name row, so it degrades cleanly. (A headless VM-render route — runNixOSTest + software-GL Hyprland (LIBGL_ALWAYS_SOFTWARE on virtio-gpu) + machine.screenshot() QMP framebuffer dump — was prototyped 2026-06-19 and works (themed waybar rendered + captured); real-hardware capture was chosen for fidelity + simplicity, with the VM route as a documented fallback if hand-capturing all themes gets tedious.)

  • Faster switches: move backgrounds/ out of the flake source. Diagnosis (confirmed): themes/ is 86 MB and backgrounds/ is all of it — the palette JSONs + per-theme overrides are only ~208 KB, and the wallpapers are never read at Nix eval (only the Python swww path uses them). But theme-state.json is git-tracked, so every apply rewrites it → the flake tree changes → Nix re-copies the whole 86 MB source before home-manager switch can evaluate. You pay an 86 MB copy to change a 1 KB file. Decided approach (deferred — don't want the extra moving part yet): Option 1, a separate pinned wallpapers artifact — a Nomarchy-wallpapers repo or release tarball, pulled once via a flake input / fetchurl (pinned by hash → content-addressed, never re-copied on a state write), with nomarchy-theme-sync reading wallpapers from that stable store path (the NOMARCHY_DEFAULT_THEMES env hook already anticipates external theme assets). Keeps eval pure; the live ISO still bakes them in (fetched at build). A state write then re-copies only ~208 KB. Rejected alternative: moving theme-state.json out to ~/.config + --impure eval (one repo, no second artifact, but trades away the in-tree-pinned-state reproducibility). Follow-on if home-manager switch itself is still the bottleneck after the copy is gone: pre-built theme variants (build each theme's generation ahead of time so a switch just activates a cached one).

  • Greeter (tuigreet/SDDM) theming from the same JSON (Plymouth ships since v1: nomarchy.system.plymouth.*, background tinted from the state file)

  • Installer round 2: multi-disk BTRFS RAID, impermanence, BIOS/legacy boot (v1 nomarchy-install is single-disk UEFI — see pkgs/nomarchy-install)

  • launch-or-focus UX scripts (swayosd volume/brightness OSD ships since v1: nomarchy.osd.*, media keys drive swayosd-client, themed from the JSON)

  • Distro branding, round 2: distroName = "Nomarchy" ships (os-release PRETTY_NAME, systemd-boot entries, ISO menu label). ✓ tuigreet greeting (Welcome to <distroName>) and a branded users.motd (doubling as a helper cheat sheet), both keyed off distroName. ✓ isoImage.splashImage — the vendored vector logo (modules/nixos/branding/logo.svg, from legacy) recolored to the palette accent on the theme base, built at ISO-build time (hosts/live.nix). Remaining: isoImage.grubTheme so UEFI boot matches the isolinux splash (needs a full grub theme dir), and the distroId question (it changes DEFAULT_HOSTNAME and upstream isNixos checks — needs a test pass; nixos-* CLI names stay regardless)

  • fastfetch branding: modules/home/fastfetch.nix (nomarchy.fastfetch.enable) — the vendored vector logo, recolored to the palette accent and rendered to compact block-art via chafa at build time (tracks the theme), fronting a curated module list. Replaces the oversized legacy ASCII with a themed, sized logo.

  • Nomarchy logo font in Waybar: vendored Nomarchy.ttf (modules/nixos/branding/), installed via fonts.packages, and the summer-day/night menu buttons now use its U+F000 glyph with font-family: Nomarchy pinned in their CSS (Nerd Fonts also occupy U+F000, so the pin is required). The other themes have no logo button.

  • Quality-of-life command aliases: a curated set ships on by default in modules/home/shell.nix — navigation (../.../....), a git block (g, gst, ga/gaa, gc/gcm, gco/gsw, gb, gd/gds, gl/glg, gp/gpl, gf), and a nix block (ns, nr, nfu, nfc, nsearch, ngc), plus path and reload. Scope decision: short where it helps but never shadow a real binary (same rule as rg/fd — the git block deliberately avoids gs/ghostscript); system/home rebuilds keep their full sys-update/home-update names rather than getting cryptic aliases. The set is documented in README §5 (Day-to-day), and alias lists them in-shell.

  • Theme-switch feedback: ✓ the "rebuilding…" notification is now persistent (timeout 0) and replaced in place by "applied ✓" / failure via a synchronous tag, so a multi-minute switch never reads as a failed selection. (An honest indeterminate indicator — HM gives no % — a literal progress widget would be cosmetic; revisit only if wanted.)

  • Icon themes: ✓ ships papirus-icon-theme; the resolved name lives on nomarchy.theme.iconTheme (the JSON's optional icons field, else Papirus-Dark/Light by mode) and feeds both Stylix's gtk.iconTheme (Thunar/GTK apps) and rofi's show-icons. Remaining (optional): per-theme icons overrides for the presets, or shipping more icon packs.

  • Nicer shell out of the box: ✓ zsh is the default login shell, with a starship prompt themed from the JSON, autosuggestions + syntax highlighting, and modern-CLI ergonomics — cat→bat (theme "ansi", so it tracks the palette), ls→eza, cd→zoxide, plus lt/tree. nomarchy.shell.enable. (Deliberately did NOT alias grep→rg / find→fd: their flags differ enough to surprise; rg/fd ship as themselves.)

  • Default application suite: a starter complete-workstation set — libreoffice-fresh, vscode, gimp, inkscape — ships active in the downstream templates/downstream/home.nix home.packages, with the heavier opt-ins (texliveFull — multi-GB; texliveMedium is lighter — plus browser/email) commented just below. Decision: no nomarchy.apps.* option surface. Unlike nomarchy.services.* (real config behind each toggle — systemd units, subuid ranges, the Flathub oneshot), a bare package install has nothing behind the toggle: mkIf x { environment.systemPackages = [p]; } just reinholds what a package list already gives, and the honest opt-out for a package is deleting the line. So the suite is the user's own curated list (extending the existing # firefox convention), not a distro-module feature — which also keeps the apps out of the live ISO automatically (it never starts from the template) and respects that editor/office/graphics are personal-taste choices, not defaults to impose. Unfree vscode evaluates fine — mkFlake and the system both set allowUnfree = true.

  • Plymouth logo contrast: the shipped art was a fixed navy that vanished on dark bases. modules/nixos/plymouth.nix now recolors every element from the palette at build time (flat fill, alpha kept): logo/lock/bullet → text, entry/progress-box → surface, progress-bar → accent. Reads on light and dark; follows the theme as of the last system rebuild (like the background tint).

  • Hibernate double-unlock: on resume from hibernate the LUKS passphrase already gates the machine, but locking hyprlock before sleep meant a second password. Fixed by not locking before an encrypted hibernate: a nomarchy-lock-before-sleep systemd unit (modules/nixos/default.nix) takes over from hypridle's before_sleep_cmd and locks on the RAM-resume sleeps (suspend / hybrid-sleep / suspend-then- hibernate) always, but skips hibernate.target when the disk is LUKS- encrypted. (A first attempt that dismissed hyprlock after resume was wrong — killing a Wayland session-lock client trips its "go to a tty" crash failsafe instead of unlocking, which is the error screen it caused.)

  • Key agents & pinentry: ships modules/home/keys.nix (nomarchy.keys.enable): one agent — services.gpg-agent with enableSshSupport fronts SSH, so a single pinentry-qt (native-Wayland, Stylix-themed Qt) handles GPG and SSH passphrases alike. SSH_AUTH_SOCK comes from the agent's zsh integration; cache TTLs (30 min / 2 h) govern re-prompting. gnome-keyring stays the Secret Service (modern versions run no SSH agent, so no socket contention); screen lock doesn't flush the cache. Remaining (optional): a session-level SSH_AUTH_SOCK export so GUI clients launched outside a shell also see the agent.

  • Sanitize & organize the repo: a housekeeping pass for consistency and clarity.

    • ✓ pruned now-redundant config: the installer no longer writes console.useXkbConfig / boot.initrd.systemd.enable into system.nix (distro-wide defaults since the LUKS-keymap fix), nor the offline-pin test fixture in flake.nix.
    • ✓ reconciled the README option tables with the live nomarchy.* surface (added keys/fastfetch/snapper/greeter.autoLogin).
    • ✓ untracked .claude/settings.local.json (machine-local) + gitignored.
    • branch/release model: main is the development default; v1 is the release pointer downstreams pin (?ref=v1), advanced to main once a batch is tested — rolling, no tags. Brought main current (it was a stale legacy-merge), and deleted the dead refactor / wave/nixos-26.05 branches (kept legacy for history). See the comment in flake.nix.
    • ✓ re-checked that every file earns its place under the modules/hosts/themes/pkgs/tools rule of thumb: no orphaned modules (keybinds.nix is a data module import-ed by hyprland/rofi, so correctly absent from default.nix), no dead-code markers, no editor/backup cruft, each themes/<slug>.json palette pairs with its optional <slug>/ overrides dir, and hosts/{default,live} are the documented reference + ISO hosts. Structure is clean as-is.
    • Open decision (deferred): the repo declares no formatter (no flake formatter, no .editorconfig); .nix files use deliberate aligned hand-formatting, so nixfmt-rfc-style --check flags ~33 files. Adopting a formatter would be a one-time repo-wide reformat that flattens that alignment — a maintainer call, not a silent cleanup, so left untouched.
  • Full docs review & restructure: a dedicated pass over all the prose, not just the code-adjacent cleanup the repo-sanitize item covers. The README has grown to ~540 lines with a ~200-line roadmap inline — the biggest single move is likely splitting the roadmap out (e.g. ROADMAP.md / docs/) so the README stays a focused "what it is / how to install / how to override" entry point. Also: reconcile every option table against the live nomarchy.* surface (snapper and others are missing); a pass over docs/OVERRIDES.md + docs/TESTING.md and the templates/downstream/README.md for drift; check the install/first-run story reads cleanly end to end; and decide whether anything wants a real docs site vs. staying Markdown-in-repo. Pairs with the first-boot welcome and control-center items (shared "how do I…" surface).

  • Laptop power / battery management: a real power story behind a nomarchy.system.power.* surface (modules/nixos/power.nix), replacing the old "only services.upower for Waybar reporting" baseline:

    • profiles: power-profiles-daemon ships by default (the upstream- aligned choice — power-saver/balanced/performance via powerprofilesctl, switched through polkit so no root prompt). A power-profile menu module (in the SUPER+M picker) and the first Waybar custom/* indicator (click to cycle) both self-gate on a battery being present + PPD running, so they hide on desktops and under TLP — no system→home wiring. TLP is the opt-in (backend = "tlp") for deeper battery tuning; the two are mutually exclusive (asserted).
    • thermal: thermald behind power.thermal.enable; the installer turns it on for a GenuineIntel CPU.
    • longevity: power.batteryChargeLimit (e.g. 80) — a backend- independent sysfs oneshot writes charge_control_end_threshold. Off by default; the installer scaffolds it commented-out on laptops.
    • installer: writes power.laptop = true (battery probe) and power.thermal.enable (Intel) into the generated system.nix.
    • idle cohesion: modules/home/idle.nix now suspends only on battery (and sooner — 15 min vs the old fixed 30), gating the hypridle suspend listener on a nomarchy-on-ac probe so a plugged-in machine stays up mid-idle; lock/screen-off are unchanged on both sources. logind's lid handling is left at its defaults (suspend on lid close, ignore when docked) — an explicit "I'm done" that coheres with the idle behaviour.
    • Remaining: a boot-only→event-driven charge-limit re-apply (udev) if a firmware resets the threshold on unplug.
    • Waybar parity: the custom/powerprofile indicator now shows in the summer-day/night whole-swap themes too. powerProfileStatus/Cycle are named writeShellScriptBins on PATH (waybar.nix home.packages), so the generated config and the static waybar.jsonc both exec them by bare name (the same parity the DND bell has). General pattern: any new generated-config module needs adding to the whole-swap themes too.
  • Full hardware enablement (beyond nixos-hardware): nixos-hardware profiles cover basic per-model functionality (quirks, drivers), but the advanced features of a machine — keyed more on the CPU/GPU generation than the model — are left for the user to wire up by hand. Close that gap without making it finnicky: extend the installer's existing autodetection (DMI → nixos-hardware profile today; add CPU vendor/arch, GPU, NPU, fingerprint reader, SSD) to switch on the safe, broadly-beneficial features automatically, and expose a small nomarchy.hardware.* surface (per-vendor where it helps, e.g. nomarchy.hardware.amd.*) for the heavy or experimental bits. Goal: full utilisation out of the box — opt-out for the safe defaults, opt-in for the multi-GB / experimental extras. Complements nixos-hardware (model quirks) rather than replacing it; keep the detection in the installer's hardware-db so an installed machine bakes the right defaults, all overridable through nomarchy.hardware.*.

    • Mechanism + broad seed shipped (modules/nixos/hardware.nix): a vendor-keyed nomarchy.hardware.* surface (intel, amd, fingerprint, npu), hardware-db.sh extended to detect Intel/AMD, a fingerprint reader (libfprint USB vendor IDs) and an NPU (Intel VPU / AMD XDNA PCI IDs), and the installer bakes the matching toggles into system.nix — safe defaults active (GuC/HuC, amd-pstate, AMD VA-API env, fprintd), heavy/experimental opt-ins commented (Intel compute-runtime, ROCm, fingerprint PAM, NPU driver). Audited against the nixos-hardware commons so it only fills the gap above them — microcode / the media-driver VA-API stack / weekly fstrim already come from common-cpu-* / common-gpu-* / common-pc-ssd. Exercised live on the dev machine's detection (an AMD Ryzen-AI laptop: AMD
      • fingerprint + NPU all detected) and a toggles-on build (amd_pstate=active
      • i915.enable_guc=3 in kernel-params, fprintd.service present). Remaining: on-hardware verification of the runtime bits (AMD/NPU/Intel GPU-compute) — none are testable in CI or on the Intel Latitudes for the AMD paths. SSD TRIM is intentionally left to common-pc-ssd.
    • Hardening (driver-gen + kernel awareness): intel.guc emits the i915 param, so the installer turns it off when the GPU is on the newer xe driver (Lunar Lake / Battlemage / Panther Lake — GuC is default-on there); the NPU detector matches the PCI accelerator class (not just a device-ID list) so new gens are caught without a code change; and a nomarchy.hardware.latestKernel escape hatch (+ a build-time warning when npu.enable predates the shipped kernel) covers very-new hardware whose drivers only just landed. A config-assertion VM test guards the wiring (checks.hardware-toggles: kernel cmdline + fprintd + PAM, booted in a VM). Worked example — ThinkPad T14s (Ryzen 7 PRO 7840U + Radeon 780M):
    • GPU compute — ROCm: the 780M is an RDNA3 iGPU (gfx1103) ROCm doesn't officially list, so it needs HSA_OVERRIDE_GFX_VERSION=11.0.0. Multi-GB closure → opt-in (nomarchy.hardware.amd.rocm.enable); unlocks GPU PyTorch / Ollama.
    • Ryzen AI NPU (XDNA): the amdxdna kernel driver (mainline 6.14+) + the XRT/xdna userspace runtime + firmware. Niche/experimental → opt-in.
    • AMD P-State EPP (Zen 4 power): amd_pstate=active for the EPP governor, which power-profiles-daemon (already shipped, power.nix) drives per profile. Broadly beneficial on Zen 2+ → default on when the installer sees an AMD CPU.
    • Hardware video acceleration (VA-API): mesa's radeonsi exposes VA-API on AMD (LIBVA_DRIVER_NAME=radeonsi, libva + drivers in hardware.graphics.extraPackages); Intel uses intel-media-driver. Broadly beneficial → default on, vendor-detected.
    • LVFS firmware & biometrics: fwupd already ships (✓, services.fwupd). Add fingerprint: services.fprintd when a reader is detected, with opt-in PAM integration for login/sudo (password-only stays the default for the cautious).
    • SSD periodic TRIM: services.fstrim.enable (weekly) — safe and beneficial, so default on (trivial enough it may warrant being distro-wide regardless). Sub-items here can graduate into their own roadmap entries as they're scoped; the unifying work is the detection + nomarchy.hardware.* surface.
  • Opt-in services & integrations: the counterpart to the opt-out application suite above — heavier or more personal integrations shipped off by default, each a nomarchy.services.<name>.enable toggle a downstream flips on in one line. Keeps the base lean while making common additions trivial. ✓ Seventeen shipped in modules/nixos/services.nix (system-side), each with a commented example in the downstream system.nix template: tailscale, syncthing (runs as the login user, GUI on 127.0.0.1:8384), podman (rootless, docker aliased, user subuid/subgid), flatpak (+ Flathub remote added by a oneshot), pika (Pika Backup GUI), steam (programs.steam — the 32-bit stack, controller udev, Remote-Play ports a bare package can't), libvirt (libvirtd + virt-manager, user added to the libvirtd group), obs (OBS Studio + a v4l2loopback virtual camera usable as a webcam in Zoom/Teams), docker (rootful; asserts against the podman docker-compat), kdeconnect, gamemode, adb (android-tools; systemd handles the udev rules), wireshark (Qt GUI + the wireshark group), ollama (local LLM API on 127.0.0.1:11434), printing (CUPS + Avahi mDNS), openrgb (RGB lighting daemon), and restic (scheduled daily backup — a small option surface: repository/passwordFile/paths, 7/4/6 retention). More candidates by area:

    • cloud/sync: Syncthing (services.syncthing); Nextcloud client is a bare tray app (in the app-suite menu, not a service)
    • local AI: Ollama (nomarchy.services.ollama, optional GPU accel) ships; LM Studio is a (commented) app-suite package (a bare GUI, no service) — pairs with the menu's Ask-Claude philosophy
    • containers/VMs: Podman (virtualisation.podman); libvirt + virt-manager (nomarchy.services.libvirt); Docker (nomarchy.services.docker)
    • networking: Tailscale (services.tailscale); WireGuard needs no toggle — NetworkManager (on by default) imports .conf tunnels natively; the wireguard-tools CLI is in the app-suite menu
    • gaming/media: Steam (nomarchy.services.steam); OBS Studio (nomarchy.services.obs, with the v4l2loopback virtual camera); GameMode (nomarchy.services.gamemode)
    • devices: printing (CUPS + Avahi, nomarchy.services.printing); KDE Connect (nomarchy.services.kdeconnect); OpenRGB (nomarchy.services.openrgb). Plus dev tooling — Android adb and wireshark toggles
    • backup: Pika Backup (pika-backup, GUI over Borg); scheduled headless restic (nomarchy.services.restic)
    • escape hatch: Flatpak (services.flatpak) + Flathub, for apps outside nixpkgs

    Surface decided: nomarchy.services.*, system-side for system services (home-side ones can extend the same prefix later). Remaining: curate the rest (Ollama, containers, Steam/OBS, printing, backups, Flatpak …). The opt-out application suite is deliberately not a parallel nomarchy.apps.* surface — it's a curated home.packages list in the downstream template (see "Default application suite" above), since a bare package install has no config to put behind a toggle. Unfree entries are already covered (allowUnfree = true).

  • Display / monitor management: declarative nomarchy.monitors (a per-output schema — name/resolution/position/scale/transform/mirror/ bitdepth/vrr/disable) generates Hyprland monitor rules, keeping the ,preferred,auto,1 wildcard as the fallback; Hyprland applies them on hotplug, so a declared external/dock output arranges itself on connect (no kanshi — it fights Hyprland's own output management). nwg-displays ships behind nomarchy.displays.enable as an interactive arranger (a helper to find values; the declarative config stays the source of truth — its output file isn't sourced). Remaining: true docked/undocked profile switching of the same outputs (Hyprland's per-output rules cover the common "external connects → arrange" case, not multi-layout toggles), and optionally workspace-to-monitor binding.

  • Night light / blue-light filter: nomarchy.nightlight (opt-in) — a scheduled colour-temperature shift via hyprsunset (Hyprland-native), warm at night, identity (no shift) by day. modules/home/nightlight.nix drives the HM services.hyprsunset with two time-based profiles (sunrise → identity, sunsettemperature), so hyprsunset handles the schedule and the on-login state. Active-profile-at-session-start was confirmed on hardware (2026-06-18). ✓ Menu + Waybar toggle: nomarchy-nightlight (in nightlight.nix, on PATH) starts/stops the hyprsunset servicesystemctl --user is-active is the single source of truth, the screen restores to true colours when stopped, and it forces the filter off for colour-sensitive work and back on without touching config. A nomarchy-menu nightlight entry (System submenu, self-gated on the unit existing) and a self-gating Waybar custom/nightlight indicator (moon = on, sun = off; in the generated bar and both summer whole-swaps) drive it. The toggle is session-scoped by design: stop halts the running unit but it's WantedBy=graphical-session.target, so the next login auto-starts it and night-light returns to its schedule (off never persists). Remaining (optional): (1) persistent off — a state marker (~/.local/state/nomarchy/nightlight-off) the unit honours via ConditionPathExists=!%h/…, so a manual off survives logout/reboot until toggled back; (2) geo (lat/long) auto sunset/sunrise (would mean wlsunset, which schedules by location). Pending an on-machine check that stopping hyprsunset cleanly restores the gamma.

  • Keyboard layouts (per-device + switching):

    • Per-device declarative layout: nomarchy.keyboard.devices ({ "<hyprctl-device-name>" = { layout; variant; }; }) generates Hyprland device blocks that override the session nomarchy.keyboard.layout for a named keyboard — e.g. an external board that's physically a different layout than the laptop's. Hyprland applies it whenever that device connects (the "external keyboard remembers its layout" case, done the declarative way). A Waybar hyprland/language indicator shows the active layout, gated on more than one layout being in play (comma in the session layout, or any per-device override) so single-layout bars stay clean.
    • Interactive new-keyboard prompt (runtime + persist): set nomarchy.keyboard.layouts (candidate layouts) and a nomarchy-keyboard- watch daemon runs (exec-once): it polls hyprctl devices, and when a keyboard connects after login that isn't in keyboard.devices and hasn't been chosen before, it pops a rofi layout picker, applies the choice as a per-device hyprctl keyword device[<name>]:kb_layout (the runtime twin of the declarative device{} blocks — so it isolates that one keyboard and never touches the built-in board), and remembers it per-device in ~/.local/state/nomarchy/keyboard-layouts — re-applied silently on later reconnects. The boot-time set (incl. the built-in keyboard) is never prompted. The runtime-remember complement to the declarative keyboard.devices; a stateful runtime piece by design. Picker verified on hardware (2026-06-18); the first cut applied the choice with switchxkblayout (a global layout-index flip) and merged the candidate pool into input.kb_layout, so a selection leaked onto the laptop keyboard and stuck after unplug — fixed by the per-device keyword + keeping the pool out of the session layout. Remaining (bonus): offer to write a choice into keyboard.devices so it graduates to the reproducible config; re-verify the per-device isolation on hardware.
    • Remaining: a multi-layout cycle bind (hyprctl switchxkblayout / xkb grp: options) for switching layouts on one keyboard; add the hyprland/language module to the summer whole-swap themes for parity (the gating doesn't translate to their static JSON). Keep the system (console/initrd) and session layouts in sync (the LUKS-keymap work).
  • Do-Not-Disturb: swaync DND toggle wired into the menu (nomarchy-menu dnd, in the picker, SUPER+CTRL+D) and a Waybar bell indicator (custom/notification via swaync-client -swb: shows the notification count + DND state, left-click toggles the panel, right-click toggles DND). Turning DND off confirms with a toast; turning it on is silent (notifications are suppressed — the bell-off glyph is the cue).

  • Snapshot browse/restore UX: nomarchy-menu snapshot (in the SUPER+M picker, self-gated on snapper) launches btrfs-assistant — the desktop browse/diff/restore/rollback UI, elevating via polkit (the safe choice for a destructive, root-only op vs a fat-fingerable rofi rollback). Shipped system-side gated on nomarchy.system.snapper. Also extended snapper to snapshot /home by default (hourly 5 / daily 7 / weekly 4) when it's its own BTRFS subvolume; a boot oneshot creates the required /home/.snapshots subvolume if missing (covers existing installs, no disko change). The backend is VM-verified (a full BTRFS+LUKS install: both root+home configs, a /home timeline snapshot taken, /home/.snapshots a real subvolume, the oneshot Result=success).

    • Known bug — btrfs-assistant 2.2 segfaults on launch in nixpkgs 26.05: it crashes inside libbtrfsutil.so.1.4.0 (a library ABI mismatch, confirmed in the VM regardless of GL/Qt platform — so it's not a VM/GL artifact and will crash on hardware too). The menu wiring is correct and the app is installed, but the GUI doesn't open. The snapshot backend (the actual snapshots) is unaffected. Fix path: a nixpkgs override aligning its libbtrfsutil, or wait for an upstream bump. ✓ Fallback shipped: the menu now launches nomarchy-snapshots (system-side, gated on nomarchy.system.snapper) instead — a keyboard-driven snapper browser/restore with no btrfs-assistant dependency. snapper is root-only here (no ALLOW_USERS) and rofi can't run as root under Wayland, so it runs in a terminal via sudo (one password prompt) and uses fzf to pick: browse/diff (read-only), restore files (undochange), or roll back (root config only) — each behind a typed-yes confirmation, which is safer than the fat-fingerable one-click rofi rollback this entry originally avoided. btrfs-assistant stays installed for when nixpkgs fixes it. Pending an on-machine check of the restore/rollback paths.
    • Remaining (optional): boot-from-snapshot needs a systemd-boot equivalent of grub-btrfs.
  • Update awareness: nomarchy.updates.enable (opt-in, modules/home/ updates.nix) — a nomarchy-updates checker on a systemd user timer (.interval, default daily) compares each direct branch-tracking flake input (nixpkgs, the Nomarchy input, home-manager, stylix … via git ls-remote vs the locked rev; offline → skipped, no false alarm) and, when the flatpak CLI is present (.flatpak), counts Flatpak updates. A self-gating Waybar custom/updates indicator (hidden until something's available; accent 󰚰 N, signal-refreshed) + a notification that fires only when the count grows (so a daily timer never nags). Click → the upgrade flow in a terminal (sys-update / flatpak update, each confirmed). Purely passive — it never changes anything, augmenting the explicit rebuild flow. Indicator is in the generated bar and both summer whole-swaps. Pending an on-machine check (the ls-remote/notify/timer path needs a live session).

  • "nomarchy" control center: a single TUI/GUI front-end over the common toggles — theme, power profile, opt-in services, display, DND — built on the same nomarchy-theme-sync / nomarchy.* surface the menu already uses. Plus a first-boot welcome that lands new installs in a guided "pick your theme / essentials" flow (ties into the branding work).

Known issues & follow-ups

  • Yazi TOML parse error on startup: yazi 26.x made fetchers' group field required, so [[plugin.prepend_fetchers]] failed to parse and yazi fell back to presets. Fixed by adding group = "git" to both git fetcher entries in modules/home/yazi.nix (the git plugin's setup() only renders the linemode; the fetcher still needs registering).
  • Starship prompt styling: dropped the powerline look (the [] separator glyph + bg: fills) for a flat prompt — bold-accent directory, warn git branch, subtext status/duration, character, all plain colored text (modules/home/shell.nix). Also wired in cmd_duration, which was configured but never referenced in format.
  • xfce deprecation warnings: moved xfce.exopkgs.xfce4-exo and the three Thunar plugins to their new top-level names in modules/nixos/file-manager.nix; the eval warnings are gone.
  • Rofi function keybindings: direct SUPER+CTRL+<mnemonic> binds jump straight to each nomarchy-menu module — V clipboard · C calc · W web · F files · N network · B bluetooth · S capture · A ask — added to modules/home/keybinds.nix, so they also show in the SUPER+? cheatsheet.
  • Enable nix-ld by default: programs.nix-ld.enable is on distro-wide (modules/nixos/default.nix), so prebuilt/foreign dynamically-linked binaries run out of the box.
  • Hyprland border colors off for some themes: the cause was v1 forcing an accent→accentAlt gradient on every theme; legacy used a solid border (accent for nord/retro-82/lumon, the text tone for kanagawa/summer-day/summer-night). Added a palette-resolved border field ({active, inactive}) to the theme schema (theme.nix), consumed solid in hyprland.nix, and declared it in every preset so a switch always replaces it. All six identity themes now match legacy exactly.
  • Waybar shows non-existent workspaces: the v1 summer port "fixed" legacy's deprecated persistent_workspaces (underscore — silently ignored by current Waybar, so legacy only ever showed existing workspaces) into the modern persistent-workspaces (hyphen), which Waybar honours → all 10 rendered as phantoms. Dropped the block from themes/summer-{day,night}/waybar.jsonc; the other themes use the generated waybar.nix, which never had it, so they already match.
  • GTK/Qt ignore the theme's light/dark mode: Stylix set polarity but not the org.freedesktop.appearance color-scheme that GTK4/libadwaita and Qt6 read via the portal, so a light theme could still render apps dark. stylix.nix now sets dconf org/gnome/desktop/interface color-scheme = prefer-light/prefer-dark from t.mode (xdg-desktop-portal-gtk already ships, and programs.dconf is on system-side), so libadwaita/Qt follow the palette. Confirmed in a live session (headless QEMU): the portal's org.freedesktop.appearance color-scheme reads 1 on a dark theme and flips to 2 after switching to a light one (catppuccin-latte) — so xdg-desktop-portal-gtk does relay the dconf value GTK4/Qt apps read.