Compare commits

..

4 Commits

Author SHA1 Message Date
Bernardo Magri
28a0e28f2d docs(roadmap): log uwsm-in-virtualization module placement (Later)
core/system/virtualization.nix wires `programs.uwsm` + the Hyprland
session config at the top of the file — loaded unconditionally on every
install, with no actual relationship to libvirt/docker. Cosmetic
mislocation, not a behavior bug; logged as a Later row so it can be
fixed in a dedicated session module without growing this audit PR.

Found during Pillar 8 audit of core/system modules.
2026-05-19 17:50:44 +01:00
Bernardo Magri
0e93639702 fix(hibernate): mkDefault on HandlePowerKey / IdleAction / IdleActionSec
These three settings.Login fields were set at default priority, so a
downstream system.nix that wrote (e.g.) `services.logind.settings.Login.HandlePowerKey = "poweroff"`
would collide with Nomarchy's value instead of overriding it. Same
mkDefault treatment as the other lid-switch settings in this block.

Found during Pillar 8 audit of core/system modules.
2026-05-19 17:50:37 +01:00
Bernardo Magri
0656f33611 chore(schema): drop orphan features.makima
`lib/state-schema.nix` declared `system.features.makima = false` but
the field was never wired anywhere: no matching option in
core/system/options.nix, no consumer in core/system/state.nix, no
references in the wider tree. Schema-only ghost — removed.

Found during Pillar 8 audit of core/system modules.
2026-05-19 17:50:32 +01:00
Bernardo Magri
749f970fb0 fix(impermanence): user must match created account, not hardcoded "nomarchy"
The persistence block at core/system/impermanence.nix:75 read
`users.nomarchy = { directories = [...]; }` — the username was a
literal, not a reference. For any user not literally named "nomarchy"
the block was silently inert and ~/.ssh, ~/.gnupg, ~/.local/share/keyrings,
Documents, Downloads, Pictures, Videos, Projects were wiped on every boot.

Adds `nomarchy.system.impermanence.user` (str, default "nomarchy") and
uses it via `users.${cfg.user}`. The installer now writes the chosen
username alongside `enable` and `mainLuksName` so impermanence installs
with non-default usernames are correct out of the box.

docs/OPTIONS.md: fixes the wrong path on the impermanence row
(documented `impermanence.enable`, real option is
`nomarchy.system.impermanence.enable`) and adds entries for
`mainLuksName` and `user`.

Found during Pillar 8 audit of core/system modules.
2026-05-19 17:50:27 +01:00
834 changed files with 23249 additions and 38273 deletions

View File

@@ -1,28 +0,0 @@
---
name: nomarchy-runner
description: Mechanical execution of the Nomarchy headless test harness. Use PROACTIVELY when a build or VM run just needs to be executed and its artifacts collected — nix build, tools/test-live-iso.sh, tools/test-install.sh, screenshot capture via tools/vm/vncshot.py and tools/vm/qmp.py, and the scripted checks (check-theme-contrast.py, audit-theme-design.py, check-option-docs.py). Never use for interpreting ambiguous failures, judging visual quality, or deciding what to test.
tools: Read, Bash, Glob, Grep
model: haiku
---
You are a deterministic test executor for the Nomarchy repository. You run
exactly the commands you are asked to run, headlessly, and hand back the
evidence. You do not decide what to test, do not interpret ambiguous
results, and do not judge whether something "looks fine".
Rules:
- Run everything headless and unattended. Never launch a graphical VM
window; never wait on human input. Wrap long-running commands in
timeouts as instructed by the caller, and treat a timeout as a recorded
failure, not something to silently retry.
- Collect artifacts to the working directory the caller specifies: full
command lines, exit codes, the tail of stdout/stderr (plus the complete
logs as files), serial console output, and screenshot file paths.
- Report format: for each command — command, exit code, wall time,
artifact paths, and verbatim error lines if the exit code was nonzero.
Nothing else. No summaries of what the results "mean".
- Never mark anything as passed or verified. You return evidence; the
caller makes the verification claim.
- If a command fails in a way that prevents collecting artifacts, report
exactly what happened and stop — do not improvise recovery steps beyond
what the caller authorized.

View File

@@ -1,20 +0,0 @@
---
name: nomarchy-scout
description: Read-only reconnaissance inside the Nomarchy repo. Use PROACTIVELY for any task that is pure information gathering — locating where an option/module/theme is defined, mapping which files touch a subsystem, scanning build logs or serial-console output for errors, checking docs against code for drift, summarizing a directory. Never use for anything requiring judgment about design, correctness, or visual quality.
tools: Read, Grep, Glob
model: haiku
---
You are a fast, cheap scout for the Nomarchy NixOS distribution repository.
Your only job is to find things and report facts. You never edit, never
judge design quality, and never draw conclusions beyond what the files
literally say.
Rules:
- Report file paths and line numbers, quote the minimum relevant snippet.
- If asked to scan logs, extract the error/warning lines verbatim with
enough surrounding context to locate them, and note timestamps.
- If you cannot find something, say so plainly — never guess or infer that
something "probably" exists.
- Keep output terse and structured: the caller pays for every token you
produce. Facts first, no prose padding, no recommendations.

View File

@@ -1,88 +0,0 @@
{
"permissions": {
"allow": [
"Read",
"Edit",
"Write",
"Glob",
"Grep",
"Bash(ls *)",
"Bash(cat *)",
"Bash(head *)",
"Bash(tail *)",
"Bash(grep *)",
"Bash(rg *)",
"Bash(find *)",
"Bash(tree *)",
"Bash(wc *)",
"Bash(file *)",
"Bash(stat *)",
"Bash(du *)",
"Bash(df *)",
"Bash(which *)",
"Bash(diff *)",
"Bash(jq *)",
"Bash(sha256sum *)",
"Bash(mkdir *)",
"Bash(cp *)",
"Bash(mv *)",
"Bash(touch *)",
"Bash(ln -s *)",
"Bash(tar *)",
"Bash(nix *)",
"Bash(nix-store *)",
"Bash(nix-instantiate *)",
"Bash(./tools/test-live-iso.sh *)",
"Bash(./tools/test-install.sh *)",
"Bash(./tools/audit-theme-design.py *)",
"Bash(./tools/check-option-docs.py *)",
"Bash(./tools/check-theme-contrast.py *)",
"Bash(./tools/import-palettes.py *)",
"Bash(./tools/vm/qmp.py *)",
"Bash(./tools/vm/vncshot.py *)",
"Bash(./tools/vm/gap-analysis.py *)",
"Bash(python3 tools/*)",
"Bash(bash tools/*)",
"Bash(qemu-system-x86_64 *)",
"Bash(qemu-img *)",
"Bash(git status *)",
"Bash(git diff *)",
"Bash(git log *)",
"Bash(git show *)",
"Bash(git add *)",
"Bash(git commit *)",
"Bash(git branch *)",
"Bash(git switch *)",
"Bash(git checkout *)",
"Bash(git restore *)",
"Bash(git stash *)",
"Bash(git worktree *)",
"Bash(systemctl --user status *)",
"Bash(journalctl --user *)",
"Bash(pgrep *)",
"Bash(pkill -f qemu*)"
],
"ask": [
"Bash(git push *)",
"Bash(rm *)",
"Bash(git reset --hard *)",
"Bash(git clean *)",
"Bash(nix-collect-garbage *)",
"Bash(curl *)",
"Bash(wget *)"
],
"deny": [
"Bash(sudo *)",
"Bash(git push --force *)",
"Bash(git push -f *)"
],
"defaultMode": "default"
}
}

View File

@@ -0,0 +1,102 @@
# Nomarchy CI — eval + lint.
#
# Catches the regressions that hurt today:
# 1. Flake stops evaluating (broken option ref, missing import, etc.).
# 2. A `nomarchy-*` shell script has a syntax error or a shellcheck
# error-severity issue.
# 3. `docs/SCRIPTS.md` drifts from the repo state because somebody
# added / removed / renamed a script and didn't run the generator
# (the pre-commit hook handles this, but only when enabled per-clone).
#
# Doesn't build ISOs — that needs a binary cache. Add a separate job
# once Cachix/Attic is in place.
name: Check
on:
push:
branches: [main]
pull_request:
jobs:
eval-and-lint:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@main
with:
# Match the runner's effective channel. Nomarchy itself tracks
# nixos-25.11 via flake.nix; the installer-action default is fine.
extra-conf: |
experimental-features = nix-command flakes
- name: nix flake check --no-build
run: nix flake check --no-build
- name: Lint nomarchy-* scripts (bash -n + shellcheck)
run: |
# Mirror what .githooks/pre-commit runs locally, but across the
# whole tree instead of just changed files. Pre-commit gates
# individual commits; CI gates branches (including --no-verify
# bypasses).
set -e
fail=0
while IFS= read -r script; do
[[ -f "$script" ]] || continue
# Python helpers ship under the same nomarchy- prefix
# (e.g. nomarchy-haptic-touchpad). Skip non-bash.
head -1 "$script" | grep -qE '^#!.*\bbash\b' || continue
if ! bash -n "$script"; then
echo "::error file=$script::bash syntax error"
fail=1
fi
if ! nix shell nixpkgs#shellcheck --command shellcheck \
--severity=error --shell=bash "$script"; then
echo "::error file=$script::shellcheck error-severity issue"
fail=1
fi
done < <(find features/scripts/utils core/system/scripts \
themes/engine/scripts \
-maxdepth 1 -type f -name 'nomarchy-*')
exit "$fail"
- name: docs/SCRIPTS.md is up to date
run: |
# Regenerate to a temp file and compare. If different, the
# contributor forgot to run the generator (or skipped the
# pre-commit hook). Fail loudly and tell them the fix.
./bin/utils/nomarchy-docs-scripts --out /tmp/SCRIPTS.regen.md
if ! diff -q docs/SCRIPTS.md /tmp/SCRIPTS.regen.md >/dev/null; then
echo "::error::docs/SCRIPTS.md is stale."
echo "Run: ./bin/utils/nomarchy-docs-scripts --out docs/SCRIPTS.md"
echo "Then commit the regenerated file."
echo "--- diff ---"
diff -u docs/SCRIPTS.md /tmp/SCRIPTS.regen.md || true
exit 1
fi
- name: installer/hardware-db.sh references real nixos-hardware modules
run: |
# Every 4th-pipe-field in HARDWARE_DB is a nixos-hardware module
# name. Half the DB used to point at modules that don't exist
# (e.g. microsoft-surface-pro-8 — there's only -pro-intel and
# -pro-9), which made the install fail at eval time with
# cryptic "attribute not found" errors on real laptops. This
# step catches that regression class.
awk -F'|' '/^ "/ { gsub(/"/,"",$4); gsub(/^[[:space:]]+|[[:space:]]+$/,"",$4); if ($4) print $4 }' \
installer/hardware-db.sh | sort -u > /tmp/db-refs.txt
nix eval --impure --json --expr '
let
nh = (builtins.getFlake (toString ./.)).inputs.nixos-hardware.nixosModules;
in builtins.attrNames nh' \
| nix shell nixpkgs#jq --command jq -r '.[]' | sort -u > /tmp/db-real.txt
missing=$(comm -23 /tmp/db-refs.txt /tmp/db-real.txt)
if [[ -n "$missing" ]]; then
echo "::error::hardware-db.sh references nixos-hardware modules that don't exist:"
printf ' - %s\n' $missing
echo "Either fix the name (check the actual attr in nixos-hardware) or drop the row."
exit 1
fi

View File

@@ -1,103 +0,0 @@
# Nomarchy scheduled lock bump — the automated half of "rock-stable
# without rotting": once a day, update flake.lock (inputs track pinned
# release branches, so this never jumps a NixOS release — that's a
# deliberate hand-edited v2, see agent/GOALS.md), gate it, and land it
# on main only on green. `v1` promotion stays human, always.
#
# Fast lane: workflow_dispatch. For a security fix upstream, run this
# workflow manually from the Actions tab instead of waiting for the
# next daily run.
#
# Gate scope: `nix flake check --no-build` (eval tier) followed by a V1 build
# (home-manager activation package + nixos toplevel) to catch compilation errors.
# (this runner has no KVM; see item 20 for the VM-suite
# upgrade path). The py_compile/bash -n steps are skipped on purpose:
# they don't read flake.lock, so a lock bump cannot break them. The
# bump commit's own push then triggers check.yml as a second net.
# A green bump therefore guards evaluation and compilation, not VM behaviour —
# the checks.* suite still runs locally / at promotion time.
#
# Failure mode: a red gate fails this run visibly and pushes nothing;
# next schedule retries. A push race (someone landed on main mid-run)
# also just fails the final push — rerun or wait a day.
#
# Container recipe (nixbld users, sandbox=false, pinned Nix, plain-shell
# install) inherited from check.yml — the gotchas are documented there.
name: Lock bump
on:
schedule:
- cron: '17 5 * * *' # daily 05:17 UTC
workflow_dispatch:
jobs:
bump:
runs-on: ubuntu-latest
timeout-minutes: 90
env:
NIX_CONFIG: |
max-jobs = 1
cores = 2
experimental-features = nix-command flakes
sandbox = false
NIX_SSL_CERT_FILE: /etc/ssl/certs/ca-certificates.crt
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Nix
run: |
NIX_VERSION=2.31.5
groupadd -r nixbld 2>/dev/null || true
for i in $(seq 1 10); do
useradd -r -g nixbld -G nixbld -d /var/empty \
-s /usr/sbin/nologin -c "Nix build user $i" "nixbld$i" 2>/dev/null || true
done
curl -L "https://releases.nixos.org/nix/nix-${NIX_VERSION}/install" | sh -s -- --no-daemon
echo "$HOME/.nix-profile/bin" >> "$GITHUB_PATH"
- name: Update flake.lock
# Inputs track release branches (nixos-26.05, release-26.05), so
# the update stays within them by construction. The human-readable
# change list goes into the commit body below.
run: |
nix flake update 2>&1 | tee /tmp/bump-log
if git diff --quiet flake.lock; then
echo "Lock already up to date — nothing to do."
echo "changed=0" >> "$GITHUB_OUTPUT"
else
echo "changed=1" >> "$GITHUB_OUTPUT"
fi
id: update
- name: Gate — evaluate every output (eval tier, memory-bounded)
# Per-output processes, not one big `nix flake check --no-build`:
# the single-process walk peaks ~6 GB RSS and OOMs inside the
# runner's 2 GB container cap (details in tools/ci-eval.sh).
if: steps.update.outputs.changed == '1'
run: bash tools/ci-eval.sh
- name: Gate — V1 build (toplevel + home-manager)
if: steps.update.outputs.changed == '1'
run: |
nix build .#homeConfigurations.nomarchy.activationPackage --no-link
nix build .#nixosConfigurations.nomarchy.config.system.build.toplevel --no-link
- name: Commit + push on green
if: steps.update.outputs.changed == '1'
# The checkout step persists the Actions token, so this push
# authenticates as the workflow; its push triggers check.yml as
# the second net. Only flake.lock is committed — pathspec-limited,
# nothing else can ride along.
run: |
{
echo "chore(lock): scheduled upstream bump"
echo
grep -E '^(• | )' /tmp/bump-log || true
echo
echo "Gate: nix flake check --no-build (eval tier — see bump.yml header)."
} > /tmp/bump-msg
git -c user.name="nomarchy-bump" -c user.email="actions@git.bemagri.xyz" \
commit --only flake.lock -F /tmp/bump-msg
git push origin HEAD:main

View File

@@ -1,108 +0,0 @@
# Nomarchy CI — the always-on net under direct-to-main pushes.
#
# Scope (deliberate): the EVAL tier only. The runner behind this Gitea
# instance is act_runner + docker containers (the legacy repo's check.yml
# ran 57 times on it) — no systemd, no /dev/kvm — so the checks.* VM
# tests and full toplevel builds can't run here. `nix flake check
# --no-build` still catches most breakage (type errors, missing options,
# bad merges, template/mkFlake drift — see docs/TESTING.md §1); the VM
# suite stays a local/promotion gate until a KVM-capable NixOS runner
# exists (see the commented vm-checks job at the bottom).
#
# Inherited-from-legacy gotchas (learned over 57 runs, kept verbatim):
# - Single-user Nix (--no-daemon): no systemd in the container. The
# installer runs as root and honours build-users-group=nixbld from
# its bundled nix.conf, aborting unless the group exists AND has
# members — create nixbld + users first.
# - sandbox=false: Stylix/base16.nix do import-from-derivation; the
# single-user Nix in this container can't set up the build sandbox
# (no user namespaces), which otherwise surfaces as
# "path '…-source' is not valid".
# - Pin the Nix version: 2.34's lazy-trees git cache doesn't
# materialise flake-input `-source` paths into the store, breaking
# the same IFD reads. 2.31.5 matches what wrote flake.lock.
# - Plain-shell Nix install, not a JS action: act_runner's bundled act
# tops out at node20; a `run:` step has no node-runtime coupling.
name: Check
on:
push:
branches: [main, v1]
workflow_dispatch:
jobs:
eval:
runs-on: ubuntu-latest
timeout-minutes: 60
env:
NIX_CONFIG: |
max-jobs = 1
cores = 1
experimental-features = nix-command flakes
sandbox = false
NIX_SSL_CERT_FILE: /etc/ssl/certs/ca-certificates.crt
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Nix
run: |
NIX_VERSION=2.31.5
groupadd -r nixbld 2>/dev/null || true
for i in $(seq 1 10); do
useradd -r -g nixbld -G nixbld -d /var/empty \
-s /usr/sbin/nologin -c "Nix build user $i" "nixbld$i" 2>/dev/null || true
done
curl -L "https://releases.nixos.org/nix/nix-${NIX_VERSION}/install" | sh -s -- --no-daemon
echo "$HOME/.nix-profile/bin" >> "$GITHUB_PATH"
- name: Evaluate every output (eval tier, memory-bounded)
# Same coverage as `nix flake check --no-build` — both nixos
# configs, the home config, the checks.* derivations (instantiated,
# not run) and the downstream template through lib.mkFlake — but
# ONE output per nix process. A single process walking everything
# peaks at ~6.0 GB RSS (measured 2026-07-12): it OOM'd the 4 GB
# VPS itself, and can never fit this runner's 2 GB container cap.
# Per-output processes cap at the largest single output (~1 GB).
run: bash tools/ci-eval.sh
- name: Python syntax (all tracked scripts)
# Every tracked *.py — theme-sync, the installer composers
# (compose-lock, patch-template) and the maintainer tools
# (tools/, tools/vm/). Via nix shell so the step doesn't depend on
# the runner image preinstalling python3.
run: |
nix shell nixpkgs#python3 --command \
bash -c 'git ls-files "*.py" | xargs -r python3 -m py_compile'
- name: Shell syntax (tracked scripts)
# The distro's user-facing scripts are generated by Nix (their
# syntax is exercised by the eval + local builds); this covers the
# hand-written .sh files: installer helpers and maintainer tools.
run: |
set -e
fail=0
while IFS= read -r script; do
head -1 "$script" | grep -qE '^#!.*\b(bash|sh)\b' || continue
if ! bash -n "$script"; then
echo "::error file=$script::bash syntax error"
fail=1
fi
done < <(git ls-files '*.sh')
exit "$fail"
# ── vm-checks (DISABLED — FUTURE #20, not NEXT) ─────────────────────
# Eval-only CI is the standing decision: Gitea act_runner stays docker
# compose (no /dev/kvm). Full checks.* VMs need a *separate* host with
# nix + KVM (label nix-kvm); see agent/BACKLOG.md FUTURE #20. Do NOT
# uncomment until that label is online — Gitea queues forever otherwise.
#
# vm-checks:
# runs-on: nix-kvm
# timeout-minutes: 120
# steps:
# - uses: actions/checkout@v4
# - run: nix flake check # builds + runs the VM tests
# - run: nix build .#nixosConfigurations.nomarchy.config.system.build.toplevel --no-link
# - run: nix build .#homeConfigurations.nomarchy.activationPackage --no-link

54
.githooks/pre-commit Executable file
View File

@@ -0,0 +1,54 @@
#!/usr/bin/env bash
# Nomarchy pre-commit hook.
#
# Enable per-clone with:
# git config core.hooksPath .githooks
#
# Two responsibilities:
# 1. Lint changed nomarchy-* scripts (bash -n + shellcheck if available)
# so syntax errors and unquoted-var bugs don't ship.
# 2. Regenerate docs/SCRIPTS.md when any nomarchy-* script under the three
# script directories is added, modified, or deleted in this commit, and
# stage the refreshed file so it lands with the change.
set -e
repo_root="$(git rev-parse --show-toplevel)"
cd "$repo_root"
script_dirs_re='^(features/scripts/utils|core/system/scripts|themes/engine/scripts)/nomarchy-'
# 1. Lint changed scripts. bash -n catches syntax errors (always fatal).
# shellcheck catches unquoted-var, use-before-define, missing-shebang, etc.
# We only fail on severity=error so the long tail of pre-existing warnings
# (info / style / warning) doesn't block commits — those can be cleaned up
# incrementally without a flag day.
changed_scripts=$(git diff --cached --name-only --diff-filter=ACMR \
| grep -E "$script_dirs_re" || true)
if [[ -n "$changed_scripts" ]]; then
while IFS= read -r script; do
[[ -f "$script" ]] || continue
# Only lint scripts with a bash shebang. nomarchy-* is a name
# convention, not a language guarantee — at least one Python helper
# ships under the same prefix (nomarchy-haptic-touchpad).
head -1 "$script" | grep -qE '^#!.*\bbash\b' || continue
if ! bash -n "$script"; then
echo "pre-commit: bash syntax error in $script — aborting commit." >&2
exit 1
fi
if command -v shellcheck >/dev/null 2>&1; then
if ! shellcheck --severity=error --shell=bash "$script"; then
echo "pre-commit: shellcheck found error-level issues in $script — aborting commit." >&2
echo "pre-commit: fix the reported issues, or rerun with --no-verify after a deliberate decision to ship." >&2
exit 1
fi
fi
done <<< "$changed_scripts"
fi
# 2. Regenerate the script audit doc.
if git diff --cached --name-only --diff-filter=ACMRD | grep -qE "$script_dirs_re"; then
echo "pre-commit: regenerating docs/SCRIPTS.md (script change detected)…"
./bin/utils/nomarchy-docs-scripts --out docs/SCRIPTS.md
git add docs/SCRIPTS.md
fi

18
.gitignore vendored
View File

@@ -1,13 +1,15 @@
# Nix build artifacts
# ---> Nix
# Ignore build outputs from performing a nix-build or `nix build` command
result
result-*
__pycache__/
.DS_Store
# Ignore automatically generated direnv output
.direnv
# Ad-hoc hardware evidence photos dropped in the tree (not repo content)
WhatsApp Image*.jpeg
# Local IDE settings
.claude/
# Claude Code machine-local settings (permissions etc.)
.claude/settings.local.json
.claude/settings.local.json.tmp.*
# VM and ISO artifacts
*.qcow2
*.qcow
*.iso

View File

@@ -1,65 +0,0 @@
# Nomarchy — agent entry point
Nomarchy is a NixOS-based distro: rock-stable, fully reproducible, themed
from one JSON, configured through a menu that writes into the user's own
flake checkout. Read the README for the architecture.
This file is the entry point for **any** AI coding agent, whatever the
vendor or harness. Everything agents need lives in vendor-neutral,
git-tracked markdown (`agent/`, `docs/`); harness-specific config is a
thin *adapter* (see bottom) and never holds shared content.
## Where things live (read this first)
| Path | Role |
|------|------|
| **`agent/`** | Agent instructions + loop state — **only executable queue is `BACKLOG.md`** |
| **`agent/README.md`** | Map of the agent files |
| **`docs/VISION.md`** | Product themes toward **v1.0** (not a queue) |
| **`docs/ROADMAP.md`** | Design history + shipped log |
| **`docs/README.md`** | Full documentation map |
| **`.claude/`** | Claude Code adapter only (permissions, subagent defs) — not the backlog |
## If you're here to work autonomously (the loop)
Follow **`agent/LOOP.md`** — one iteration: orient → pick one BACKLOG
item → work → verify → commit+push on `main` → record. All loop state is
git-tracked in `agent/`.
## Rules that apply to every session, loop or not
- **Before your first change, read `agent/VERIFICATION.md`** and follow
it — every change, however small, climbs its V0V3 ladder. For theme
or visual work, also read `agent/THEME-DESIGN.md`; before any VM test,
`docs/TESTING.md`.
- **Honesty rule** (`agent/VERIFICATION.md`, `docs/TESTING.md`): for
visual/interactive changes, evaluation is not rendering — state the
tier you reached. Cheap first: `nix flake check --no-build`.
- **Conventions** (`agent/CONVENTIONS.md`): in-flake state, menu
placement, Waybar parity with whole-swaps, toggle-vs-package discipline,
template as SoT for opt-in comments (`templates/downstream`).
- **Sync sweep on completion** (`agent/LOOP.md` §5): when a task ships,
update or delete every cross-reference it made stale — BACKLOG
pitches/pointers, HARDWARE-QUEUE entries, docs — in the same commit.
No stale items, no unsynced information.
- **Git:** direct commits on `main`, pushed; **`v1` is human-only** —
never touch it. Never `nix flake update` unless the task is a lock bump.
No formatter — match aligned hand-formatting.
- Layout: `hosts/` machine · `modules/` distro · `themes/` data ·
`pkgs/` code · `tools/` maintainer · `agent/` loop · `docs/` human docs.
## Delegation & escalation
Push mechanical work down to cheaper models; keep judgment on the
strongest model available. Capability tiers, standing roles (scout /
runner), and per-harness model mappings: **`agent/DELEGATION.md`**.
Work above your tier? Return it — don't burn tokens.
## Harness adapters
One directory per harness, holding only what that harness *requires* in
its own format (permissions, subagent/skill registration). Adapters point
into `agent/`; they never carry policy, queue items, or vision text.
| Harness | Adapter |
|---------|---------|
| Claude Code | `.claude/``CLAUDE.md` is a symlink to this file |
| others | add a sibling dir + a row here; keep it a thin shim |

View File

@@ -1 +0,0 @@
AGENTS.md

21
LICENSE
View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2026 Bernardo Magri
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

617
README.md
View File

@@ -1,549 +1,118 @@
# Nomarchy
# 👑 Nomarchy
**The rock-solid reproducibility of NixOS 26.05. The out-of-the-box polish of
Omarchy/Omakub.** One JSON file rules the look of the entire desktop; every
theme change is a Home Manager generation — atomic, rollbackable, never
partial.
**Nomarchy** is a professional-grade NixOS distribution that ships a highly curated Hyprland desktop on a strictly declarative, flake-based foundation. It provides a highly polished, "it just works" experience for power users who want a beautiful Wayland environment without sacrificing the reliability of NixOS.
## ✨ Key Features
- **Purely Declarative:** Your entire desktop—themes, fonts, wallpapers, and toolchains—is defined in code.
- **Modular Merging Architecture:** Distro code and user code are strictly separated. You can update the distro core without ever touching or breaking your personal configurations.
- **Interactive "Smart" Installer:** Detects hardware, sets up networking, localizes timezones/keymaps, and offers multi-select software profiles.
- **Erase Your Darlings (Optional):** Optional BTRFS root-wipe on boot ensures your system stays pristine and 100% declarative.
- **Dynamic Theming Engine:** 20+ built-in themes with instant UI feedback via IPC (swww, waybar, and stylix).
- **Portable State (`nomarchy-sync`):** Easily backup and sync your declarative config and dynamic state (theme/wallpaper choices) to a private Git repo.
---
## 📂 Component-Based Architecture
Nomarchy uses a **Feature-Centric Directory Structure**. For a comprehensive breakdown of the system architecture, folder roles, and module logic, see the [Detailed Architecture Documentation](docs/STRUCTURE.md).
Configuration, modules, and utilities are strictly organized to maintain sanity as the system grows.
- **`core/`**: Foundational OS & User defaults (Bootloader, Audio, Bluetooth, core system features).
- **`features/`**: Isolated modules containing Nix logic and raw dotfiles.
- **`features/apps/`**: App-specific configs (e.g., `features/apps/btop/`, `features/apps/kitty/`), each containing their own `default.nix` and standalone `config/` directory mapped via Home Manager.
- **`features/desktop/`**: Desktop environment components (e.g., Hyprland, Waybar).
- **`features/scripts/utils/`**: Consolidated repository for all custom Nomarchy bash scripts, centrally packaged and injected into the user's `PATH` with correct dependencies.
- **`themes/`**: The global theming engine. It holds pure color data and logic. *Theme-specific app layouts* (like a custom Waybar layout) are stored directly inside the app's feature folder, solving the matrix problem of theming.
---
## 📥 Installation
### 1. Try it in a VM (Recommended)
Verify the experience without touching your hardware:
```bash
./bin/utils/nomarchy-test-installer
```
┌──────────────────────────────────────────────────────────────────────────┐
│ state.json (single source of truth) │
│ lives INSIDE your flake checkout, git-tracked │
└───────────────────────────────────┬──────────────────────────────────────┘
nomarchy-state-sync apply gruvbox
1. merges the preset into the JSON (atomic write)
2. runs `home-manager switch` (no sudo, no system rebuild)
▼ pure read (nomarchy.stateFile)
┌──────────────────────────────────────────────────────────────────────────┐
│ Home Manager bakes EVERYTHING into one read-only generation: │
│ Hyprland (colors/gaps/borders) Waybar (palette or whole-swap) │
│ Kitty (full ANSI palette) btop (asset or generated) │
│ Stylix → GTK, Qt, cursors, fonts │
└───────────────────────────────────┬──────────────────────────────────────┘
wallpaper via awww (the one runtime piece:
applied post-switch + at session start, `bg next` cycles)
This builds a full graphical VM of the installer environment. Once inside, click the **Install Nomarchy** icon or run `nomarchy-install`.
### 2. Build the Installer ISO
To install on physical hardware, generate your own bootable image:
```bash
./features/scripts/utils/nomarchy-build-iso # Minimal TTY installer
./features/scripts/utils/nomarchy-build-live-iso # Graphical try-before-install
```
The ISO will be located at `./result/iso/nixos-*.iso`. Flash it to a USB drive and boot.
## 1. Layout
Flat on purpose. Two module trees, one options file each, no hidden layers.
```
.
├── flake.nix # inputs + the downstream API (exports below)
├── lib.nix # nomarchy.lib.mkFlake — one-call downstream wrapper
├── state.json # ★ THE single source of truth (git-tracked!)
├── themes/ # 28 presets: <slug>.json + optional <slug>/ assets
│ ├── nord.json # palette (required, works alone)
│ └── nord/ # assets (optional, fixed filenames)
│ ├── backgrounds/ # wallpapers (pinned nomarchy-wallpapers input, not this repo)
│ ├── btop.theme # hand-made config drop (else generated)
│ └── waybar.css # whole-swap: replaces the generated bar style
├── modules/
│ ├── nixos/ # the distro, system side
│ │ ├── default.nix # Hyprland session, Pipewire, greetd, fonts
│ │ ├── options.nix # nomarchy.system.* toggles
│ │ ├── plymouth.nix # boot splash, tinted from the JSON
│ │ ├── file-manager.nix # Thunar GUI + gvfs/tumbler/udisks2
│ │ └── power.nix # power-profiles-daemon/TLP, thermald, charge limit
│ └── home/ # the distro, user side
│ ├── default.nix # entry point
│ ├── options.nix # nomarchy.* option surface
│ ├── theme.nix # JSON ingestion + wallpaper hook
│ ├── stylix.nix # GTK/Qt/cursors/fonts from the same JSON
│ ├── hyprland.nix # all JSON-driven
│ ├── waybar.nix
│ ├── kitty.nix
│ ├── btop.nix
│ ├── rofi.nix # launcher + nomarchy-menu (calc, emoji, clip…)
│ ├── keybinds.nix # single source: Hyprland binds + SUPER+? sheet
│ ├── swaync.nix # notifications, same JSON
│ ├── idle.nix # hyprlock + hypridle, same JSON
│ ├── battery-notify.nix # low-battery toasts at the bar's thresholds
│ ├── yazi.nix # flagship TUI file manager + plugins
│ ├── osd.nix # swayosd volume/brightness OSD
│ ├── shell.nix # zsh + starship + bat/eza/zoxide
│ ├── keys.nix # gpg-agent fronting SSH, pinentry-qt
│ └── fastfetch.nix # themed Nomarchy logo + system info
├── hosts/
│ ├── default/ # reference machine (thin: boot, user, hostname)
│ └── live.nix # bootable live ISO (try the distro, no install)
├── pkgs/
│ ├── 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-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
│ ├── VISION.md # product themes toward v1.0 (not a queue)
│ ├── ROADMAP.md # design/decision records + shipped log
│ ├── HARDWARE.md # firmware, profiles, drivers
│ ├── TESTING.md · RECOVERY.md · OVERRIDES.md · MIGRATION.md
├── agent/ # agent instructions + loop state — map: agent/README.md
│ # BACKLOG (executable queue), LOOP, VERIFICATION, …
├── AGENTS.md # agent entry, any vendor (CLAUDE.md symlinks here)
├── .claude/ # Claude Code adapter: permissions + subagent defs
└── tools/ # maintainer-only
├── import-palettes.py # converts old-distro themes → JSON + assets
├── test-live-iso.sh # build the ISO + boot it in QEMU
├── test-install.sh # full offline-install regression in QEMU
└── vm/ # headless-VM helpers (QMP keys, VNC shots,
# offline-pin gap analysis)
### 3. Run the Installer
Once booted into the Live environment, launch the installer:
```bash
nomarchy-install
```
The wizard will guide you through:
- **Networking:** An interactive wizard to connect to Wi-Fi if needed.
- **Hardware:** Optimized profiles for Dell XPS, Framework, Apple T2, and more.
- **Storage:** Choice between Standard Ext4 or Encrypted BTRFS with optional **Impermanence**.
- **Localization:** Searchable timezones and keyboard layout selection.
**Rule of thumb:** `modules/` is the distro (reusable, no machine specifics),
`hosts/` is a machine, `themes/` is data, `pkgs/` is code, `tools/` is
maintainer-only. If a new file doesn't obviously belong to one of those, it
probably shouldn't exist.
### Already on NixOS?
Layer Nomarchy onto an existing 25.11 install without reformatting — see the [Migration Guide](docs/MIGRATION.md).
## 2. Try it first (live ISO)
---
**Before you install:** check **[docs/REQUIREMENTS.md](docs/REQUIREMENTS.md)**
(UEFI x86_64, disk planning for the Nix store + BTRFS snapshots — 128GiB is
the comfortable floor).
## 🛠️ Configuration & Usage
Boot the full desktop from a USB stick or VM without installing anything:
Nomarchy uses a "Downstream" model. After installation, your configuration lives in `/etc/nixos/` and is split into three main parts:
```sh
nix build .#nixosConfigurations.nomarchy-live.config.system.build.isoImage
# → result/iso/nomarchy-live-….iso — dd to a stick, or boot it in QEMU:
tools/test-live-iso.sh
```
The live session auto-logs-in, seeds the flake at `~/.nomarchy`, and pins
the locked inputs into the ISO store — so theme switching (including the
`home-manager switch` it triggers) works **offline**, exactly like on an
installed system. Verification checklist: [docs/TESTING.md](docs/TESTING.md).
It's a working desktop, not a demo shell: Chromium, LibreOffice, Text Editor,
Amberol and Snapshot are in the launcher alongside the terminal tooling — so
you can read these docs in a browser while you try it, or boot the stick to
rescue a machine that won't start and get a document off it. Installed
machines get their app list from the template's `home.packages` instead, which
is yours to edit.
Like what you see? **`nomarchy-install`** (in a terminal) walks you through
installing to disk: pick a disk, LUKS2 full-disk encryption **by default**
(in exchange the desktop logs in passwordless — the passphrase already
gates the machine), user + hostname + timezone, hardware autodetection
(DMI → nixos-hardware profile — see **[docs/HARDWARE.md](docs/HARDWARE.md)**),
a hibernation-ready swapfile sized to RAM,
then disko partitions (GPT + ESP + BTRFS subvolumes incl. `@snapshots`
snapper timeline snapshots are on) and `nixos-install` runs — **without a
network** when the ISO was built from a clean tree (the target's
`flake.lock` is composed from the rev the ISO carries). The installed
machine gets the standard downstream layout: the flake at `~/.nomarchy`
(`/etc/nixos` symlinks to it), one `mkFlake` call, your
`system.nix`/`home.nix`. First boot lands in the fully themed desktop —
the installer pre-activates the Home Manager generation. UEFI only for now.
## 3. Using Nomarchy on your machine (downstream)
Nomarchy is consumed as a flake input — you never fork or edit this repo:
```sh
mkdir my-machine && cd my-machine
nix flake init -t "git+https://git.bemagri.xyz/bernardo/nomarchy.git?ref=v1"
```
The template's own README then walks the first-run steps: a real
`hardware-configuration.nix`, `git init` (flakes only see tracked
files), the two first rebuilds, and landing the checkout at
`~/.nomarchy` so the theme CLI finds it. (The installer does all of
this for you.)
Already running NixOS on this machine? No reinstall needed — adopt
Nomarchy in place, reusing your existing `hardware-configuration.nix`:
**[docs/MIGRATION.md](docs/MIGRATION.md)**.
You own two files day-to-day: `system.nix` and `home.nix` (plus
`state.json`, written by the CLI). Your `flake.nix` is set up once —
later by the installer — and never hand-edited; it's a single call:
### 1. Distro Core (Upstream)
Managed via the public Git repository. This is the engine. You should generally not modify files here.
### 2. Your System (`system.nix`)
Add system-wide packages, services, and hardware tweaks here.
*Example: Adding a persistent directory for Docker if using Impermanence:*
```nix
outputs = { nomarchy, ... }:
nomarchy.lib.mkFlake {
src = ./.;
username = "me";
hardwareProfile = "framework-13-7040-amd"; # optional, nixos-hardware name
};
environment.persistence."/persist" = {
directories = [ "/var/lib/docker" ];
};
```
| `mkFlake` arg | Default | Purpose |
|---|---|---|
| `src` | — (required) | Your flake directory (`./.`) |
| `username` | — (required) | Login name; flows into `system.nix` and names the HM config |
| `hardwareProfile` | `null` | One [nixos-hardware](https://github.com/NixOS/nixos-hardware) module name, or a list of them (pinned + tested by Nomarchy; unknown names fail with suggestions) |
| `system` | `"x86_64-linux"` | Platform |
(Power users can skip `mkFlake` and wire `nixosModules.nomarchy` /
`homeModules.nomarchy` / `overlays.default` by hand — the wrapper is sugar.)
Two deliberately separate rebuild paths:
```sh
sudo nixos-rebuild switch --flake .#default # system: rare
home-manager switch --flake .#me # desktop: every theme change, no sudo
### 3. Your User Environment (`home.nix`)
Add user-level packages, aliases, and dotfiles here.
*Example: Overriding the default terminal:*
```nix
nomarchy.home.terminal = "kitty";
```
Day-to-day you'll use the shipped shortcuts instead:
For the full list of `nomarchy.*` options you can set in `system.nix` and `home.nix`, see the [Options Reference](docs/OPTIONS.md). Hit a rebuild error? Check [Troubleshooting](docs/TROUBLESHOOTING.md). For where the project is heading next, see the [Roadmap](docs/ROADMAP.md).
```sh
nomarchy-pull # nix flake update — refresh inputs (nomarchy, nixpkgs, …);
# optional git pull only if ~/.nomarchy tracks a remote
nomarchy-rebuild # system rebuild against the CURRENT lock (sudo inside;
# BTRFS snapshot first when snapper is on; nvd diff after)
nomarchy-home # home-manager switch — desktop only, no sudo, no lock bump
# (prints nvd + a "What changed" toast when the generation moved)
nomarchy-what-changed # plain-language last rebuild (system + desktop; --summary for one-liners)
### Applying Changes
After editing your files, apply them instantly. **IMPORTANT:** Nomarchy requires the `--impure` flag for evaluation. You **MUST** use the following aliases rather than standard NixOS commands:
```bash
sys-update # Rebuilds the NixOS system (Runs: sudo nixos-rebuild switch --flake .#default --impure)
env-update # Reloads your Home Manager environment (Runs: home-manager switch --flake .#default --impure)
```
`~/.nomarchy` is **your machine flake** (`system.nix` / `home.nix`). Newer
Nomarchy code arrives when `flake.lock` updates the `nomarchy` input
(`nomarchy-pull`), not by `git pull` of that directory (many installs have
no remote there — only local commits from auto-commit). With auto-commit
on (menu: **System Auto-commit**), all three commands above also sweep
any pending hand edits into a commit before they switch, so
`git -C ~/.nomarchy log` mirrors your generations.
---
| When | Run |
|---|---|
| Pull newer Nomarchy / nixpkgs / inputs | `nomarchy-pull` |
| You changed `system.nix` (or after pull) | `nomarchy-rebuild` |
| You changed `home.nix` / theme / desktop | `nomarchy-home` |
| Full upgrade (inputs + both layers) | `nomarchy-pull && nomarchy-rebuild && nomarchy-home` |
## 🚀 Commands & Keybindings
**Order matters for distro updates.** `nomarchy-home` rebuilds against the
**current** `flake.lock` — it never updates inputs. A new Nomarchy revision
lands only when the lock is updated (`nomarchy-pull`). So for an upstream
desktop change: **pull → rebuild → home**. Home before pull rebuilds against
the old inputs and silently skips new home-side changes. After a home-side
keybind/config change, also `hyprctl reload` (or relogin) so the session
re-reads it.
The full list lives in [`docs/KEYBINDINGS.md`](docs/KEYBINDINGS.md) (auto-generated from the Hyprland configs). A few highlights:
Legacy aliases still work: `sys-update` → pull+rebuild, `sys-rebuild`
`nomarchy-rebuild`, `home-update``nomarchy-home`.
| Keybinding | Action |
| :--- | :--- |
| `Super + Space` | **App Launcher** (Walker) |
| `Super + Shift + Space` | **Nomarchy Menu** (Walker) |
| `Super + Alt + Space` | **Toggle Top Bar** (Waybar) |
| `Super + Return` | Open Terminal |
| `Super + Q` | Close Window |
Override anything via the `nomarchy.*` surface or plain NixOS/HM options:
appearance (gaps/colors/fonts) changes through `nomarchy-state-sync`,
behaviour (input/misc/monitor/chrome) is `mkDefault` so a plain `home.nix`
assignment wins, and bind/exec-once lists concatenate. Full guide with
examples: **[docs/OVERRIDES.md](docs/OVERRIDES.md)**.
### Utility Scripts
Nomarchy includes dozens of productivity scripts available in your PATH. Some highlights:
- `nomarchy-sync push <repo>`: Backup your setup to Git.
- `nomarchy-theme-bg-next`: Cycle to the next wallpaper in the current theme.
- `nomarchy-menu`: The central hub for all utilities and pickers.
**Where each option goes.** `nomarchy.system.*`, `nomarchy.hardware.*`, and
`nomarchy.services.*` are NixOS options — set them in `system.nix`. Everything
else under `nomarchy.*` is a Home Manager option — set it in `home.nix`. The
two tables below are split along exactly that line.
**`home.nix`** (Home Manager — the desktop):
| Option | Default | Purpose |
|---|---|---|
| `nomarchy.stateFile` | — (required) | Path to your state.json |
| `nomarchy.terminal` | `"kitty"` | Terminal for keybinds and `$TERMINAL` (Kitty is the only shipped/themed emulator) |
| `nomarchy.kitty.enable` | `true` | Nomarchy's Kitty config (palette/font/opacity from theme-state) |
| `nomarchy.keyboard.layout` | `"us"` | XKB layout for the Hyprland session (installer writes the matching `services.xserver.xkb` in system.nix; the console + LUKS prompt follow via the distro default) |
| `nomarchy.keyboard.variant` | `""` | XKB variant for the session |
| `nomarchy.keyboard.devices` | `{}` | Per-device layout overrides (Hyprland `device` blocks keyed by `hyprctl devices` name) — e.g. an external keyboard with its own layout/variant |
| `nomarchy.keyboard.layouts` | `[]` | Optional layouts to put first in the new-keyboard picker; the watcher runs by default, offers every installed XKB layout, and remembers the choice per-device. Pick/change one anytime at System Keyboard |
| `nomarchy.nightlight.enable` | `false` | Scheduled blue-light filter (hyprsunset) — warm at night (`.temperature`, default 4000K) between `.sunset`/`.sunrise`, no shift by day; set BOTH `.latitude`/`.longitude` for **geo mode** (wlsunset computes sunrise/sunset from your location; the fixed times are ignored); off by default — enable it from the menu (System Night light; the first enable rebuilds), then toggle on/off instantly from the menu or the Waybar moon indicator (writes `settings.nightlight.on` in your flake, no rebuild) and it survives reboot, so it stays reproducible |
| `nomarchy.updates.enable` | `false` | Passive update awareness: a background check (`.interval`, default daily) that flags when flake inputs (nixpkgs, the Nomarchy input, …) are behind upstream — and, with Flatpak on, when apps have updates (`.flatpak`) — via a Waybar indicator + notification. Never changes anything; click the indicator to run the upgrade flow |
| `nomarchy.hyprland.enable` | `true` | Nomarchy's Hyprland config |
| `nomarchy.waybar.enable` | `true` | Nomarchy's Waybar |
| `nomarchy.rofi.enable` | `true` | Themed rofi launcher + `nomarchy-menu` dispatcher |
| `nomarchy.swaync.enable` | `true` | swaync notifications, themed |
| `nomarchy.batteryNotify.enable` | `true` | Low-battery toasts at the bar's thresholds — 25% low, 10% critical (stays up until dismissed); silent no-op on machines without a battery |
| `nomarchy.dockAudio.enable` | `true` | On a fresh external-monitor plug, PipeWire/WirePlumber are reprobed after hardware settles and the highest-priority available HDMI/DP/USB sink becomes default, with a toast and journal result. A later manual speaker choice sticks until the next physical plug; unplug falls back to built-in. Manual route: System Audio |
| `nomarchy.firstBootWelcome.enable` | `true` | One dismissible “you're set” toast on the first session (SUPER+M / SUPER+T / SUPER+? + network pointer); marker is `settings.firstBootShown` in the flake checkout. Also fires at most one follow-up “Hardware tips” toast pointing at System Firmware / Fingerprint when `fwupdmgr` / `fprintd-list` are on PATH (`settings.hardwareHintsShown`) |
| `nomarchy.idle.enable` | `true` | hyprlock + hypridle (lock 5 min; display off 10 min in every dock/lid state — input wakes it compositor-side, #127; suspend 15 min battery-only via `nomarchy-suspend`) |
| `nomarchy.idle.fingerprint` | `false` | Unlock the lock screen with a fingerprint as well as the password, and say so on the input field. Set it alongside `nomarchy.hardware.fingerprint.pam` in system.nix: hyprlock is configured from Home Manager, which cannot read the NixOS option — and hyprlock does **not** take a fingerprint through PAM at all (its PAM stack runs only on submit), so it uses its own fprintd backend that this switch turns on |
| `nomarchy.yazi.enable` | `true` | yazi TUI file manager, themed + curated plugins |
| `nomarchy.osd.enable` | `true` | swayosd on-screen display for volume/brightness/mute |
| `nomarchy.shell.enable` | `true` | zsh + starship prompt + bat/eza/zoxide (zsh is the default login shell) |
| `nomarchy.btop.enable` | `true` | btop with per-theme colors |
| `nomarchy.stylix.enable` | `true` | GTK/Qt/cursor theming |
| `nomarchy.fastfetch.enable` | `true` | fastfetch fronted by the themed Nomarchy logo |
| `nomarchy.keys.enable` | `true` | gpg-agent fronting SSH + pinentry-qt |
| `nomarchy.displays.enable` | `true` | nwg-displays interactive monitor arranger (helper for `nomarchy.monitors`) |
| `nomarchy.viewers.enable` | `true` | Document/image viewers: zathura (Stylix-themed PDF) + imv |
| `nomarchy.mime.enable` | `true` | Default "open with" associations (PDF/image/video/text/browser/directory); entries for apps you removed are skipped, so it degrades with the suite |
| `nomarchy.monitors` | `[]` | Declarative per-output layout → Hyprland `monitor` rules (applied on hotplug); `,preferred,auto,1` wildcard kept as fallback |
| `nomarchy.displayProfiles` | `{}` | Named layouts for the same outputs (docked/undocked/…), switched from System Display Profiles: instant via hyprctl, persisted in-flake (`settings.displayProfile`), baked over `nomarchy.monitors` at the next rebuild. Profiles that disable the laptop panel use the same safe ordering as Dock mode (external on → workspace handoff → internal off). The menu's Auto-switch row (`settings.displayProfileAuto`) applies the matching profile from Hyprland hotplug events. Workspace pins (`workspaces = { "1" = "DP-3"; }`) are moved instantly and baked as rules |
| `nomarchy.launchOrFocus` | `[]` | Launch-or-focus binds: `SUPER+<key>` focuses the app's window (case-insensitive class match) or launches it; entries land in the SUPER+? cheatsheet, and a bind whose app was removed notifies instead of failing silently |
| `nomarchy.themesDir` | Nomarchy's `themes/` | Where per-theme app overrides are probed |
| `nomarchy.package` | overlay's `nomarchy-state-sync` | The theme/state tool package, overridable if you fork it |
**Greeter keyboard layout:** tuigreet runs on a kernel VT, which has exactly
one keymap — `console.useXkbConfig` follows `services.xserver.xkb.layout`
(`nomarchy.keyboard.layout`), not Hyprland's per-device `kb_layout` binding.
Per-device layouts (`nomarchy.keyboard.devices`) only take effect once the
Hyprland session starts, so at the login prompt an external keyboard types
the **system** layout, not its remembered one — if your password comes out
wrong there, type it as if on the system layout, or use the laptop's own
keyboard. A VT constraint, not a bug.
**Always-on, no toggle by design:** `services.cliphist`, `services.udiskie`
(automount + safe-removal toasts) and `services.easyeffects` (mic noise
cancellation) ship unconditionally — small, low-risk pieces with no
Nomarchy-specific config behind them (the toggle-vs-package rule in
`agent/CONVENTIONS.md`). Disable one from `home.nix` with the plain HM
option, e.g. `services.easyeffects.enable = lib.mkForce false;` — see
**[docs/OVERRIDES.md](docs/OVERRIDES.md)**.
**`system.nix`** (NixOS — the machine):
| Option | Default | Purpose |
|---|---|---|
| `nomarchy.system.plymouth.enable` | `true` | Branded boot splash, background from the theme JSON (recolors on system rebuilds) |
| `nomarchy.system.stateFile` | `null` | state.json for the system-side consumers (the Plymouth tint); `lib.mkFlake` wires it for you — set it only when composing the modules by hand |
| `nomarchy.system.fileManager.enable` | `true` | Thunar GUI + gvfs/tumbler/udisks2 (the "open folder" handler) |
| `nomarchy.system.greeter.enable` | `true` | greetd/tuigreet |
| `nomarchy.system.greeter.autoLogin` | state | Auto-login this user into Hyprland. Owned by System Auto-login (`settings.greeter.autoLogin`; installer seeds it on LUKS machines) — set it here only to pin it against the menu |
| `nomarchy.system.audio.enable` | `true` | PipeWire stack; WirePlumber priority rules provide HDMI/USB preference/fallback, while the Home Manager dock watcher performs the fresh-display reprobe and explicit default selection |
| `nomarchy.system.bluetooth.enable` | `true` | Bluetooth + blueman |
| `nomarchy.system.autoTimezone.enable` | `false` | Automatic timezone (geoclue + automatic-timezoned) — the clock follows your location; toggle from System Auto timezone (a menu enable rebuilds: it has to unset the static `time.timeZone`) |
| `nomarchy.system.snapper.enable` | `false` | Hourly/daily BTRFS timeline snapshots + `nixos-rebuild-snap` (installer enables it; no-op unless root is BTRFS) |
| `nomarchy.system.power.enable` | `true` | Active power management (see below) |
| `nomarchy.system.power.backend` | `"ppd"` | `"ppd"` (power-profiles-daemon + menu/Waybar switcher) or `"tlp"` (deeper battery tuning, no switcher) — mutually exclusive |
| `nomarchy.system.power.laptop` | `false` | Marks a laptop, gating battery-only features; the installer sets it when a battery is present. Clamshell uses logind's `HandleLidSwitchDocked=ignore`; the desktop additionally holds a low-level lid-switch inhibitor across dock/undock, releasing only after the internal panel is restored and the lid opens. Normal future undocked lid-close suspend remains enabled |
| `nomarchy.system.power.thermal.enable` | `false` | thermald (Intel-only); the installer enables it on a GenuineIntel CPU |
| `nomarchy.system.power.batteryChargeLimit` | `null` | Stop charging at this % (e.g. `80`) where the hardware supports it; needs `power.laptop` |
| `nomarchy.system.power.suspendThenHibernate` | `true` (state) | On battery, suspend falls through to hibernate after **1 hour** (`suspend-then-hibernate` + `HibernateDelaySec=1h`; never while on AC). Needs a hibernate resume path (`boot.resumeDevice`). Toggle: System Preferences **Suspend then hibernate** (`settings.power.suspendThenHibernate`; system rebuild for logind lid policy). Idle and the Power menu use `nomarchy-suspend` (live state). |
| `nomarchy.hardware.intel.enable` | `false` | Intel enablement above nixos-hardware (GuC/HuC firmware via `i915.enable_guc=3``.guc` toggles just that; the installer unsets it on `xe`-driver GPUs); the installer sets it on an Intel CPU/GPU |
| `nomarchy.hardware.intel.computeRuntime` | `false` | Opt-in: Intel GPU compute — OpenCL/Level-Zero (`intel-compute-runtime`) + oneVPL (`vpl-gpu-rt`) |
| `nomarchy.hardware.amd.enable` | `false` | AMD enablement above nixos-hardware (amd-pstate EPP + radeonsi VA-API, each toggleable via `.pstate` / `.vaapi`); installer-set on an AMD CPU/GPU |
| `nomarchy.hardware.amd.rocm.enable` | `false` | Opt-in: ROCm HIP/OpenCL GPU compute (multi-GB); pair with `.gfxOverride` (e.g. `"11.0.0"`) for an unlisted iGPU |
| `nomarchy.hardware.fingerprint.enable` | `false` | fprintd for a detected fingerprint reader (installer-set); enroll with `fprintd-enroll` |
| `nomarchy.hardware.fingerprint.pam` | `false` | Opt-in: use the fingerprint for login + sudo (PAM) |
| `nomarchy.hardware.fingerprint.parallel` | `true` | With PAM on: password *or* fingerprint at the same prompt (whichever comes first); `false` = stock sequential pam_fprintd. Password alone always stays sufficient |
| `nomarchy.hardware.npu.enable` | `false` | Opt-in/experimental: load the on-die NPU driver (`amdxdna`/`intel_vpu`); userspace runtime is BYO |
| `nomarchy.hardware.latestKernel` | `false` | Opt-in: ship `linuxPackages_latest` instead of the default kernel — for very new hardware whose drivers landed recently |
| `nomarchy.hardware.camera.hideIrSensor` | `false` | Hide a dual-sensor webcam's IR node from PipeWire's **v4l2** path so apps only ever see the colour camera (the "second, dark Integrated Camera"); installer-set on a paired RGB+IR webcam. `/dev/video*` stays open, so Howdy-style face unlock still works; `.irMatch` overrides the IR-name regex. Does **not** hide IR from libcamera / portal / Flatpak pickers — see [HARDWARE.md §7](docs/HARDWARE.md) |
| `nomarchy.hardware.i2c.enable` | `false` | I2C devices support — access to `/dev/i2c-*` (RGB controllers, sensors, DDC/CI monitor control) |
| `nomarchy.hardware.i2c.ddcci` | `false` (distro default: **`true`**) | the ddcci-driver kernel module, exposing external monitors as standard backlight devices via DDC/CI so brightness keys and swayosd natively control them |
| `nomarchy.services.tailscale.enable` | `false` | Opt-in: Tailscale mesh VPN — the login user is made the operator, so `tailscale up/down/set` and the System VPN menu work without sudo |
| `nomarchy.services.syncthing.enable` | `false` | Opt-in: Syncthing file sync as the login user (GUI at `127.0.0.1:8384`) |
| `nomarchy.services.podman.enable` | `false` | Opt-in: rootless Podman (`docker` aliased to it) |
| `nomarchy.services.flatpak.enable` | `false` | Opt-in: Flatpak + the Flathub remote |
| `nomarchy.services.pika.enable` | `false` | Opt-in: Pika Backup (GUI Borg backups) |
| `nomarchy.services.steam.enable` | `false` | Opt-in: Steam via `programs.steam` (32-bit libs, controller udev, Remote-Play ports) |
| `nomarchy.services.libvirt.enable` | `false` | Opt-in: libvirt/KVM + virt-manager GUI (login user added to `libvirtd`) |
| `nomarchy.services.obs.enable` | `false` | Opt-in: OBS Studio + a v4l2loopback virtual camera (selectable as a webcam in Zoom/Teams) |
| `nomarchy.services.docker.enable` | `false` | Opt-in: rootful Docker (user in `docker` group); don't enable alongside podman |
| `nomarchy.services.kdeconnect.enable` | `false` | Opt-in: KDE Connect phone integration (opens its firewall ports) |
| `nomarchy.services.gamemode.enable` | `false` | Opt-in: Feral GameMode performance daemon (`gamemoderun`) |
| `nomarchy.services.adb.enable` | `false` | Opt-in: Android adb/fastboot tools (systemd handles device udev rules) |
| `nomarchy.services.wireshark.enable` | `false` | Opt-in: Wireshark Qt GUI (user in `wireshark` group, capture without root) |
| `nomarchy.services.ollama.enable` | `false` | Opt-in: Ollama local LLM runtime on `127.0.0.1:11434` (CPU; GPU via `services.ollama.acceleration`) |
| `nomarchy.services.printing.enable` | `false` | Opt-in: CUPS + Avahi/mDNS network printer discovery |
| `nomarchy.services.openrgb.enable` | `false` | Opt-in: OpenRGB daemon for RGB peripheral/motherboard lighting |
| `nomarchy.services.restic.enable` | `false` | Opt-in: scheduled daily restic backup (set `.repository` + `.passwordFile`; backs up `.paths`, default `/home`; 7/4/6 retention; list/restore via the `restic-nomarchy` wrapper) |
Beyond the `nomarchy.*` surface, the system layer turns on the usual
desktop services with `lib.mkDefault` (override natively) and enforces
baseline security/stability defaults. A few worth calling out:
**`security.apparmor.enable`** is on by default for mandatory access
control confinement. **Kernel panics** are configured to auto-reboot the
machine after 10 seconds (`panic=10 oops=panic`) rather than hanging
indefinitely. **`services.fwupd.enable`** is on by default for firmware
updates via LVFS — it only refreshes metadata, never flashes on its own,
so run `fwupdmgr update` to apply; disable with `services.fwupd.enable =
false;` on machines without real firmware (VMs/headless). Full hardware
story (profiles, fingerprint, NVIDIA, unsupported machines, contributing
a DMI line): **[docs/HARDWARE.md](docs/HARDWARE.md)**. And
**`services.earlyoom`** is on by default so running out of memory kills
the offending process (with a desktop notification) instead of freezing
the desktop — process-level on purpose, since a Hyprland session is one
cgroup and systemd-oomd would kill all of it (oomd is disabled
accordingly). Opt out with `services.earlyoom.enable = false;`.
**`zramSwap`** is also on by default (zstd, 50% of RAM, priority 100):
day-to-day memory pressure pages into compressed RAM instead of the
disk, keeping the disk swapfile free for **hibernation** — the installer
creates a RAM-sized encrypted swapfile and wires `resume`, so
Power Hibernate works out of the box (swap size 0 at install = no
swap). Opt out with `zramSwap.enable = false;`; to add hibernation to a
machine installed before it existed, follow
**[docs/MIGRATION.md](docs/MIGRATION.md)** § Enabling hibernation.
## 4. How theming works
### Pure JSON ingestion
The trap with "read a mutable file from Nix" is pure evaluation: flakes
cannot read arbitrary `$HOME` paths without `--impure` (the old prototype
required it — never again). Nomarchy's convention: **the state file lives
inside the consuming flake** and is wired via
`nomarchy.stateFile = ./state.json;`. Reading it is pure — it's flake
source. It must be git-tracked (`nomarchy-state-sync` runs
`git add --intent-to-add` after every write as a safety net).
### One change = one generation
`nomarchy-state-sync apply <theme>` merges the preset into the JSON and runs
`home-manager switch` (override the command with `$NOMARCHY_REBUILD`, or pass
`--no-switch` to only write). Everything is baked: Hyprland, Waybar, Kitty,
btop, and — via Stylix, mapped onto base16 roles — GTK, Qt, cursors and
fonts. No runtime patching means no partial states, and `home-manager
generations` is also your theme history. Waybar even restyles in place: it
re-reads `style.css` when the symlink flips.
**Cleanup is automatic.** A weekly timer (`nomarchy-gen-prune`) removes
**system** and **Home Manager** generations that are older than **14 days**
*and* beyond the **three most recent past** gens — so you always keep the
current generation plus at least three rollbacks, even on a rarely rebuilt
machine. Manual: `sudo nomarchy-gen-prune --dry-run`. Full story:
[docs/RECOVERY.md §4](docs/RECOVERY.md#4-how-generations-are-kept-and-cleaned-up).
The **wallpaper** is the one runtime piece (awww — nixpkgs' swww — is
imperative; nothing in Nix consumes the path): applied at session start,
after every switch via a tiny activation hook, and again when a monitor is
hotplugged; cycled instantly with `bg next`.
### Config the menu writes (not just themes)
The in-flake-state model isn't only for appearance. **Feature toggles you flip
from the menu are written into a `settings.*` section of the *same* state file**
— git-tracked, reproducible, never stashed in `~/.local/state`. The menu is just
an ergonomic writer for your flake, so version-controlling your downstream
reproduces the machine, settings and all. Where a toggle can take effect without
a rebuild it does: the menu writes the key (`--no-switch`) and flips the running
service, which reads the *live* flake state at session start, so the choice is
both instant and survives reboot.
**Night light** is the first to use this — enable it from the menu (System
Night light; the first enable rebuilds to install hyprsunset), then on/off is
instant. Internally it's two keys: `settings.nightlight.installed` (sticky —
gates the unit, the first enable rebuilds) and `settings.nightlight.on` (the
instant on/off). Expect more `nomarchy.*` toggles to migrate to this pattern.
**Auto-commit (opt-in):** System Auto-commit makes every `apply`/`set`
mutation also `git commit` state.json in your flake — *only* that
file, so unrelated dirty work is never swept up — turning your settings
history into `git log`. Off by default; the toggle is instant (nothing in
Nix consumes the flag), the off-write is itself committed so history stays
consistent, and wallpaper cycling (`bg next`) is deliberately excluded —
the current wallpaper rides along with the next real commit.
### Per-theme app assets (`themes/<slug>/`)
Recoloring covers 95% of theming; the rest is one optional assets directory
per theme — a single place to look, unlike the old distro's split:
| Asset | Mechanism |
|---|---|
| `backgrounds/` | wallpapers; empty `wallpaper` in the state means "first one"; `bg next` cycles. Pinned in a separate `nomarchy-wallpapers` flake input, not this repo (ROADMAP § "Faster switches") — merged in at build time |
| `btop.theme` | baked into the generation (generated from the palette when absent) |
| `waybar.css` | **whole-swap**: replaces the generated bar style entirely (probed at eval time, self-contained) |
| `waybar.jsonc` | whole-swap for the bar *layout* (must be plain JSON) |
| `rofi.rasi` | **whole-swap**: replaces the generated launcher/menu theme entirely |
Eight themes ship a `waybar.css` identity: the three Nomarchy-exclusive
day/night pairs — boreal ↔ boreal-dawn, executive-slate ↔ executive-ivory,
kiln ↔ kiln-clay — plus summer-night ↔ summer-day. Custom user themes can live in
`$NOMARCHY_PATH/themes/` (preset lookup) and `nomarchy.themesDir` (eval-time
asset probe) — including their own `backgrounds/`, which wins over the
pinned `nomarchy-wallpapers` input (checked first); an explicit `wallpaper`
path in `state.json` works too.
## 5. Day-to-day
```sh
nomarchy-state-sync list # 28 presets (nord, gruvbox, rose-pine, …)
nomarchy-state-sync apply kanagawa # whole desktop, one generation (~a switch)
nomarchy-state-sync set ui.gapsOut 16 # tweak one knob (also a switch)
nomarchy-state-sync bg next # cycle wallpapers — instant, no rebuild
nomarchy-state-sync bg auto # back to the theme's default wallpaper
nomarchy-state-sync get colors.accent
nomarchy-pull # flake input update (no rebuild)
nomarchy-rebuild # rebuild the system, current lock
nomarchy-home # rebuild just the desktop layer
nomarchy-doctor # read-only health sheet (also: menu System Doctor)
```
Something broke anyway? Every rebuild is a generation and (on BTRFS)
every hour is a snapshot — the undo story, from a bad theme to a
machine that won't boot, is **[docs/RECOVERY.md](docs/RECOVERY.md)**.
Keybinds: `SUPER+Return` terminal · `SUPER+Space` launcher · `SUPER+T` theme
picker · `SUPER+SHIFT+T` next wallpaper · `SUPER+X` power menu ·
`SUPER+E` file manager (yazi) · `SUPER+N` notifications · `SUPER+CTRL+V`
clipboard history · `SUPER+SHIFT+C` color picker (hyprpicker) · `SUPER+Q`
close · `SUPER+1..9` workspaces · `SUPER+ALT+arrow` move workspace to monitor · `Print` region screenshot.
Shell aliases (zsh, gated on `nomarchy.shell.enable`) — `alias` lists them
all; the curated set:
```sh
# navigation
.. ... .... # cd up 1 / 2 / 3 levels
# git
g gst # git · git status -sb
ga gaa # git add · git add -A
gc gcm # git commit · git commit -m
gco gsw gb # checkout · switch · branch
gd gds # diff · diff --staged
gl glg # log graph (last 20 · all branches)
gp gpl gf # push · pull · fetch --all --prune
# nix (lifecycle: nomarchy-pull / nomarchy-rebuild / nomarchy-home — full names)
ns nr # nix shell · nix run (e.g. ns nixpkgs#ripgrep)
nfu nfc # nix flake update · check (prefer nomarchy-pull for day-to-day)
nsearch ngc # nix search · nix-collect-garbage -d (prefer: sudo nomarchy-gen-prune)
# misc
path # print $PATH, one entry per line
reload # exec zsh (reload the shell)
```
## 6. Extending
- **New theme:** drop a JSON into `themes/` (schema = any existing preset),
plus an optional `themes/<slug>/` assets directory.
- **New themed value:** add the key to `state.json` and consume it in
the Nix modules. One place — there is no second renderer to keep in sync.
- **Importing more old-distro palettes:**
`tools/import-palettes.py <palettes-dir> themes/` (roles are first-class:
when ANSI color0==color8 the tool derives an overlay step; light themes
do not use dark ANSI black as surface — hand-tune after import; do not
bulk-reimport shipped JSON without a hierarchy pass).
- **New opt-in feature (convention):** when a feature is off by default and
needs the user to set `nomarchy.*` options (e.g. night light, per-device
keyboard layouts, monitor layout, power management), ship a **commented**
example of it in `templates/downstream/home.nix` or `system.nix`. New
installs then enable it by uncommenting + tweaking, rather than hunting the
docs for the option name.
- **Applications:** the starter complete-workstation set (LibreOffice, VS
Code, GIMP, Inkscape) lives in the `home.packages` list in your
`home.nix` — it's a plain package list, so curate it directly: delete a
line to slim the machine, uncomment an extra (a browser, email, full TeX
Live), or add your own. No `nomarchy.apps.*` toggles — a package list is
already its own opt-out, so the distro doesn't impose these or wrap them.
- **A newer individual app (unstable channel, #134):** Nomarchy carries a
second, newer nixpkgs channel and exposes it as `unstable` through its
overlay, so any package can be taken from it by prefixing `unstable.` in
`home.packages` — e.g. `unstable.lmstudio` — with no second flake input of
your own to manage. Three costs, stated plainly: (a) **you own this
combination** — nothing upstream tests a pinned Nomarchy plus an unstable
app together; (b) an unstable package brings its own second toolchain into
your closure (measured: `unstable.lmstudio` shares only ~690 of ~4k store
paths with the pinned set); (c) it moves whenever the flake lock moves
(`nomarchy-pull`), not on its own schedule. Home packages only — that's
the supported surface, not `system.nix` or the module set.
## Roadmap & known issues
| Doc | Role |
|-----|------|
| **[agent/BACKLOG.md](agent/BACKLOG.md)** | What to do next (agent queue) |
| **[docs/VISION.md](docs/VISION.md)** | Product themes toward **v1.0** |
| **[docs/ROADMAP.md](docs/ROADMAP.md)** | Design history + shipped log |
| **[docs/README.md](docs/README.md)** · **[agent/README.md](agent/README.md)** | Maps |
| **[agent/LOOP.md](agent/LOOP.md)** | Autonomous iteration protocol |
Kept out of the README body so this stays a focused entry point.
License: [MIT](LICENSE).
---
*Built with ❤️ using NixOS, Hyprland, Stylix, and the Nomarchy Community.*

View File

@@ -1,353 +0,0 @@
# Backlog — the prioritized task queue
**This is the only executable work list for agents.** Product themes and
v1.0 intent live in [`docs/VISION.md`](../docs/VISION.md); design history
in [`docs/ROADMAP.md`](../docs/ROADMAP.md); map in
[`docs/README.md`](../docs/README.md) and [`agent/README.md`](README.md).
**Rules:**
- Agents take the topmost actionable item (see LOOP.md). Finished items
are **deleted** here — the journal + git log are the record; durable
design notes get a ✓-entry in docs/ROADMAP.md (and/or a note in VISION)
if worth keeping.
- Item numbers are **stable IDs** — never renumbered or reused. A gap in
the sequence means shipped (or dropped) work; new items take the next
free number regardless of tier.
- Tags: `[blocked:hw]` needs real hardware (see HARDWARE-QUEUE.md) ·
`[human]` needs Bernardo · `[stuck]` two failed attempts, needs help ·
`[big]` must be split before starting.
- Agents may append to **PROPOSED** and **Decisions** freely (include
`VISION § …` or `ROADMAP § …` when relevant); only Bernardo moves items
*out* of PROPOSED into the tiers.
---
## NOW
### Live ISO / install hardware findings — Acer Aspire M5-481T + Dell XPS 9350
Bernardo, real installs 2026-07-1314 (photos of the install end screens and
post-boot sessions). Preserve separation: the installer bake failure and the
flake pin are different root causes even when they show up on the same machine.
(Terminal / Ghostty-on-Acer → shipped as Kitty-only, #95.)
## NEXT
### 151. `[human]` v1 launch plan — devise with Bernardo (includes the GitHub move)
Bernardo, 2026-07-17: before tagging v1, sit down and devise the launch plan.
Known ingredients, so that session starts concrete:
- **The repo moves to GitHub.** Everything baking the Gitea URL follows: the
downstream template's `inputs.nomarchy.url`, the `nomarchy-wallpapers`
input URL (baked 2026-07-17 — sweep it in the same move), README/docs
links, and CI (eval CI runs on Gitea act_runner — decide GitHub Actions
vs keeping Gitea CI on a mirror).
- The move is the natural moment for the **history-rewrite decision**, and
it is now measured (2026-07-17): the 107 MiB pack is 94.2 MB wallpaper
blobs + 9.9 MB preview generations; all 848 commits of *text* pack to
~1520 MiB. So the history is not long-heavy, it is image-heavy.
Recommendation on the table: **filter, don't reset**
`git filter-repo` stripping only `themes/*/backgrounds/` keeps every
commit's diffs/blame/`log -S` (load-bearing: it diagnosed #147) at a
~1520 MiB clone. Cost: all SHAs change, and ROADMAP/journal cite them —
mitigate by freezing the Gitea repo as a read-only archive (old SHAs
resolve there; stash filter-repo's commit-map in it) + one ROADMAP note.
Argue against a full mirror (ships 94 MB of deleted images to every
clone forever) and against fresh-start (burns the archaeology to save
~15 MiB).
- The existing v1 bar (VISION § v1.0): HARDWARE-QUEUE burn-down, install P0
re-verify before the `v1` fast-forward, visual ritual. LICENSE shipped
(MIT, 2026-07-17).
- Adjacent calls that may fold in: cachix/public binary cache (#120's
prerequisite), the netinstall decision itself, release notes/announce.
`v1` stays human-only throughout — agents prepare evidence and checklists,
never the tag or branch.
## LATER
- **Pre-built theme variants** (ROADMAP § Faster switches follow-on): if
`home-manager switch` itself is still the theme-switch bottleneck now
that the wallpapers split shipped (2026-07-17), pre-build each theme's
generation so a switch just activates a cached one. Measure first.
- **Installer round 2** (ROADMAP § Installer): multi-disk BTRFS RAID,
impermanence, BIOS/legacy boot.
- **Boot-from-snapshot**: a systemd-boot equivalent of grub-btrfs.
- **MIPI/IPU software-ISP camera** support (no-UVC machines).
- **NixOS release bump → v2** `[human]`: deliberate, hand-edited, never
automated; the previous attempt was discarded (2026-06-22) over a
Hyprland OOM blocker — see MEMORY.md before retrying.
## FUTURE (decided deferred — not the agent queue head)
Work we **intend** someday but explicitly **not** NEXT. Agents do not
pick these unless Bernardo promotes one into NEXT/NOW.
### 20. KVM runner → VM suite in CI `[human]`
**Status (2026-07-10):** keep **eval-only** CI on the current Gitea
stack (act_runner in docker-compose on the 4c/4GB IONOS VPS). Nested
KVM + RAM headroom on that host are a poor fit next to Gitea; full
`checks.*` VMs stay local / promotion-time until a **separate**
KVM-capable machine exists.
**When ready:** register a second runner (host-mode nix + `/dev/kvm`,
label `nix-kvm` — not the existing docker eval runner), then uncomment
the `vm-checks` job in `.gitea/workflows/check.yml` (`runs-on: nix-kvm`,
`nix flake check` + toplevel/HM builds). Do not enable the job until
that label is online (Gitea queues forever otherwise).
### Formatter — adopt later `[human]`
**Intent:** add a Nix formatter (likely `nixfmt-rfc-style`) in a dedicated
pass: reformat the tree once, document in CONVENTIONS, optional CI
check. **Not** the queue head — no drive-by reformats until that pass.
## PROPOSED (agent suggestions — await human triage)
*Agents: append here with a one-paragraph pitch (what/why/cost). Do not
implement. Bernardo moves accepted items into a tier.*
*Open work only. Shipped exam/AC items (#47#63, #14, #52 theme
high-ROI, etc.) live in the journal + ROADMAP — not here.*
### Product / day-2
### 153. Desktop widgets (clock / system stats) as an opt-in per-theme surface
Pitch (filed while shipping the three exclusive theme pairs, 2026-07-18):
themed desktop widgets — a clock, calendar, or system-stats panel living on
the wallpaper, conky-style — would deepen the identity themes (imagine
kiln's copper stats panel or executive-ivory's letterhead clock). No such
surface exists today, so it is a NEW theming surface: per CONVENTIONS it
must consume the palette from the state JSON (no side pipeline) and ship
as an opt-in `nomarchy.*` toggle with a commented template example. Engine
choice is the real decision — conky is X11-era; a Wayland layer-shell
widget system (eww or a small QML/GTK layer-shell tool) fits the stack
better. Cost: moderate (engine spike + palette bridge + 23 widget
layouts; start with the exclusive pairs only, not all 28 themes). Value:
v1+ polish, not a v1 blocker.
### 146. `[watch]` hypridle hangs instead of exiting when its compositor vanishes
**Re-measured 2026-07-20 (dev box; user journal is persistent back to 2026-05-01,
so this is the full window, not a fragment).** The linger fix landed between 17:25
and 20:13 on 2026-07-16: the last zombie-shaped disconnect is the known one
(17:25:39, PID 1698 — logout→greeter with **no** following `Stopped hypridle`), and
**every** `Disconnected from pollfd id 1` since is a clean teardown bracketed by both
a logind `Session N logged out` and a systemd `Stopped hypridle` within ≤11s
(07-16 20:13:04, 07-17 17:04:38, 07-18 13:35:25, 07-19 16:46:49). **Genuine zombies
post-fix: 0.** The live daemon is healthy too — PID 1707: `active`, `NRestarts=0`,
`ExecMainPID`==pid, `ss` Recv-Q 0 on its D-Bus socket (reading, not the 48 KB-unread
wedge), `WCHAN futex_do_wait`, logged activity <10 min before inspection. **Why this
stays open a little longer, not closed today:** all four post-fix disconnects were
*deliberate* logouts — no Hyprland *crash* (the zombie's actual trigger; the nearby
coredumps were xdg-desktop-portal/swww/swayosd during teardown, not the compositor)
has occurred since the fix, so the crash path is confirmed by argument (a crash ends
the logind session too → no-linger manager teardown reaps hypridle) but not yet by an
observed crash, and ~4 days is under the "few weeks" bar. **Close condition:** if
genuine zombies stay 0 by ~2026-08-10 (≈3 weeks post-fix), close unbuilt; one wedged
process (`ps` alive + `ss` Recv-Q high + `NRestarts=0` while `active`) reopens it as
real work. Everything below is prior context, unchanged.
**Rewritten 2026-07-16, hours after filing: the premise was disproven and almost
nothing survives.** As filed it said hypridle "dies silently ~2×/week — 19 times
in 11 days", with Jul 14 as the clean example ("`CRITICAL` at 13:08:17, then
eleven hours of silence"). Every number there was an artifact of the
measurement. **18 of the 19** `Disconnected from pollfd id 1` lines are
hypridle's noisy goodbye while systemd *deliberately stops it* — each bracketed
by `Stopped hypridle` / `Started hypridle` with a healthy replacement seconds
later. The "eleven hours of silence" came from grepping **one PID**: systemd
logged `Stopped hypridle` 13:08:17, `Started hypridle` 13:09:00, and PID 1678
ran fine all evening where the filter could not see it. Do not re-derive the old
rate; it counts normal shutdown noise.
**What is real, and it is small.** Exactly one genuine zombie has been observed
(2026-07-16 17:25:39), verified by process inspection rather than logs — `ps`
(alive 9h36m), `ss` (48 KB unread on its D-Bus socket, i.e. not reading),
`NRestarts=0` while `systemctl --user is-active` said `active (running)`. That
is upstream hyprwm/hypridle#171 ("Possible deadlock in pollThr when compositor
exits?", open since 2025-09, in neither 0.1.7 nor main) and it is a true bug:
hypridle hangs rather than exits, so `Restart=always` never fires.
**A second real one joined it with #127's close (2026-07-17):** an
inhibit-lock underflow (`BUG THIS: inhibit locks < 0: -1`, hypridle's own
words, Jul 15 10:36:29) left the fired DPMS listener's `on-resume` permanently
dead on 0.1.7 — the incident's actual mechanism (ROADMAP § "The idle brick's
CAUSE found"). Reported upstream with the full trace 2026-07-17:
https://github.com/hyprwm/hypridle/issues/208
(#74/#104/#128 are the same class, closed before 0.1.7 evidently without
killing it). Its impact path here is closed by the compositor-side wake fix
(a2151f4); same watch posture — take the upstream fix when a release carries
it.
**But its impact path is closed, which is why this is `[watch]` and not work.**
The zombie only *persists* if systemd never stops the unit — which needed the
user manager to survive logout, i.e. the stray pre-migration linger removed in
#147. Without linger, logout tears the manager down and takes any zombie with
it; the Jul 14 trace above shows the healthy path (`Stopped``Started`) doing
exactly that. A Hyprland death ends the logind session anyway (2026-07-16:
`Session 2 logged out` → greeter), so there is no known route left to a
surviving zombie.
**Do before building anything:** re-measure. Count only disconnects with **no**
`Stopped/Started hypridle` around them and **no** logind session event, and
confirm by `ps`/`ss` that the process is really wedged — not by grepping a PID.
If that count is zero over a few weeks post-linger, **close this unbuilt**. If it
is not, the upstream issue has sat ten months on vaxerski's "that will cause a
segfault no?" and a reproducer would be worth more than a local guard.
### 149. `[watch]` Waybar's exec-once workaround may be defending against nothing post-linger
Spun out of #147 (shipped 2026-07-17) so its loose thread survives the entry:
`modules/home/hyprland.nix` runs Waybar from `exec-once` rather than a systemd
unit because the unit "raced Hyprland's IPC on a **warm** relogin ... never
retried, so the bar vanished" — and a warm relogin is exactly what linger
created. With the stray linger removed (2026-07-16) that workaround may be
defending against nothing. Do not rip it out on this note alone: it works, and
the claim needs a real relogin without linger to test (V3 — the same relogin
that verifies #148's watcher fix would answer it).
**2026-07-18 evidence: the warm relogin is real post-linger.** On the dev
box the user manager survived a Hyprland logout (the tty1 greetd session
keeps it alive — no linger involved) and the teardown broke:
`graphical-session.target`'s stop transaction was rejected as destructive
(easyeffects had a queued start job), the target stayed active,
cliphist/swaync/portals/swayosd crash-looped against the dead Wayland
socket into `start-limit-hit`, and the relogin's plain `start` was a no-op
— doctor red for the whole session. Session bring-up now stops stale
targets + `reset-failed` before starting (`hyprland.nix`
systemd.extraCommands); relogin V3 check queued in HARDWARE-QUEUE. The
exec-once posture this entry watches is therefore still earning its keep.
### 120. A netinstall ISO, next to the fat offline one
**Deferred to PROPOSED 2026-07-16 (Bernardo): not now.** Nothing below is
stale — the measurements stand and the decision it needs is unchanged. It sits
here because it is the only genuinely large item left, and because the gotcha
at the bottom (no binary cache for our own derivations) probably makes a
cachix the real first step, not the ISO.
Bernardo 2026-07-14, after seeing the measured size: **keep the current ISO
exactly as it is** — the guaranteed offline install is the feature it buys —
and ship a **much lighter netinstall variant alongside it**. Two products, one
distro: "works on a plane" and "8 GiB is absurd to download" are both true, and
a second target settles them without compromising either.
**Measured facts (2026-07-14), so this starts from numbers, not vibes.**
*(These stand as measured: #121 would have cut ~0.67 GiB of duplicate chromium
from them, but it was **reverted** — decided against, ROADMAP § one chromium,
not two. If a netinstall ships, revisit it: the duplicate is worth ~195 MiB of
**download**, which is this item's whole currency, even though it is worth
almost nothing on disk or on the ISO.)*
> **Read this before using the numbers below.** They are **closure arithmetic**,
> and #121 proved the hard way that closure size is neither disk size nor image
> size: removing a 687 MiB path shrank the ISO by **8 KiB**, because
> **mksquashfs dedupes duplicate files** and **`auto-optimise-store` hardlinks**
> them on disk. So a change that looks like it sheds gigabytes of closure can
> shed nothing off the actual image. **Measure the artifact — build the ISO and
> `stat` it.** The corollary cuts the other way and is the good news for this
> item: what dedupe cannot help is the **wire**, so a netinstall's download is
> the one figure closure/NAR size predicts honestly (`nix path-info --store
> https://cache.nixos.org --json` gives the real `downloadSize`).
- Current ISO **8.078 GiB** compressed; **18.03 GiB** of store uncompressed
(`zstd -19`, 2.23:1 — compression is already near-max, not the lever).
- The offline pin (`system.extraDependencies`, 60 roots: a representative
installed system + the template HM closure + all flake inputs) is **4.02 GiB
uncompressed of that — only ~22%**. Dropping it entirely still leaves a
**~13.3 GiB** desktop → roughly **6 GiB** compressed at the same ratio.
**So "no pin" alone is NOT the lighter ISO** — this is the trap to avoid.
- The desktop's own top weights: libreoffice 1457 MiB, initrd 1369,
linux-firmware 770, chromium 1391 (two builds — #121, left in), llvm-lib 540,
bibata-cursors 322, mesa 264, mbrola-voices 259, nerd-fonts ~420 combined.
Note what that list implies: no single lever gets a desktop ISO under ~4 GB —
which is the case for (b) below.
**So the real decision is what a netinstall ISO IS**, and it should be settled
first (`[human]`): (a) the full try-before-install desktop minus the pin
(~6.3 GiB — barely lighter, probably not worth a second target); (b) a **TUI
installer only, no desktop** (~1 GiB, the actual "netinstall" in the Debian
sense) which drops "try before install" from that medium — the fat ISO still
offers it; (c) a middle desktop (no libreoffice/chromium — but note #103 just
put those there deliberately, and a *demo* desktop that can't browse is the
bug #103 fixed).
**The gotcha that decides feasibility:** without the pin, a netinstall target
fetches from `cache.nixos.org` for stock nixpkgs paths — but **Nomarchy's own
derivations are in no binary cache**, so they would build *from source on the
user's machine* during install. That is the same failure `tools/vm/gap-analysis.py`
exists to diagnose (and #113 is a live instance of). So this item probably
depends on a public binary cache (cachix) for the flake's own outputs, or it
trades an 8 GiB download for a 40-minute install. Establish that before
building the target.
Pass = a second, documented ISO target that is *substantially* smaller (state
the measured number, both ISOs built from one tree), installs successfully with
a network in a QEMU run, says clearly at boot that it needs one, and leaves the
offline ISO's behaviour untouched (`checks.*` for the offline path stay green).
- **NVIDIA first-class options** — **deferred past v1** (Bernardo
2026-07-10). Keep #59 commented install guidance; no
`nomarchy.hardware.nvidia.*` until a hybrid maintainer + queue.
_(#80#83 + #85#88 shipped 2026-07-11. Theme A day-2 + neon-glass finish
shipped — VISION ✓. Dock/hibernate V3 → HARDWARE-QUEUE. Parallel
fingerprint-or-password shipped 2026-07-12 (Bernardo promoted it live;
`fingerprint.parallel`, pam-fprint-grosshack) — reader V3 →
HARDWARE-QUEUE.)_
### v1.0 pointer
See **VISION**. Open PROPOSED: NVIDIA deferred past v1; IR portal (b)/(c)
need T14s (HARDWARE-QUEUE § T14s). Standing calls: browser = Chromium;
power = PPD. (Post-install hardware hints shipped 2026-07-17.)
## Decisions `[human]`
Open calls only Bernardo can make; agents add options/evidence but never
decide. **Resolved** entries stay for history; agents treat them as closed.
### Resolved (2026-07-17)
- **#143 rofi refilter highlight** — the 07-16 "wait for upstream" ruling
was priced against carrying a patch; the maintainer's reply on
rofi#2317 pointed at the `inputchange {}` config block instead. Adopted
same day as pure config (`kb-row-first` on query edits, generated
config.rasi) — no patch, no source build, watch closed
(ROADMAP § Rofi highlight).
- **#120 netinstall** — stays deferred in PROPOSED (re-affirmed). When it
is promoted, a public binary cache (cachix) is the likely first step.
- **#134 unstable packages** — build it: Nomarchy carries the
`nixpkgs-unstable` input, `unstable.*` via the overlay, home-scope only.
Shipped the same day (ROADMAP § `unstable.<pkg>` in the downstream).
- **#114 greeter layouts** — document only: a VT has one keymap by design,
so tuigreet cannot honour per-device layouts. Shipped as the README
"Greeter keyboard layout" note + a RECOVERY.md pointer; entry deleted.
### Resolved (2026-07-10)
- **Docs site vs Markdown-in-repo** — **markdown in-repo for now**
(`docs/`, README). A rendered docs site is FUTURE if wanted.
- **Default browser** — **ship Chromium** in
`templates/downstream/home.nix`; mime → `chromium-browser.desktop`.
Opt out: delete the line / override mime.
- **Default power backend** — **keep PPD** (`nomarchy.system.power.backend`
default). TLP remains the one-line opt-in. Rationale: stability + live
profile API for menu/Waybar; Omarchys TLP experiment reverted.
### Resolved (2026-07-10, more)
- **Formatter adoption** — **yes, but not now.** Tracked as FUTURE
(below). Nix-source style only (`nixfmt-rfc-style` or similar); one
bulk reformat + CI/check when promoted. Until then: hand-aligned
style per CONVENTIONS.
- **Hibernation** — **want by default** (product intent). Needs a
disk-backed swap (file or partition) sized for resume; not zram alone.
**Shipped as #76**; V3 power-cycle PASSED on TuringMachine 2026-07-12
(ROADMAP § Hibernation + zram by default).
### Resolved (2026-07-10, #76 design)
- **Swap sizing** — **exactly RAM** (installer default, unchanged). Hibernate
image ≤ RAM; zram takes day-to-day paging. **`swapSize=0`** stays no-swap.
- **Migration** — **docs runbook** (`docs/MIGRATION.md`), not a tool.
- **No-swap Hibernate** — keep the menu row; **notify on failure**.

View File

@@ -1,75 +0,0 @@
# Conventions — how Nomarchy code is written
The standing rules an agent must follow while coding. GOALS.md says what
we're building; this says how. Details/rationale live in docs/ROADMAP.md's
decision records and the README.
## Repo layout (the rule of thumb)
`modules/` is the distro (reusable, no machine specifics) · `hosts/` is a
machine · `themes/` is data · `pkgs/` is code · `tools/` is maintainer-only
· `agent/` is loop state. If a new file doesn't obviously belong to one of
those, it probably shouldn't exist.
## Nix style
- **No formatter.** Files use deliberate aligned hand-formatting — match
the surrounding style exactly; never reflow a file you're only touching
a line of.
- Distro defaults use `lib.mkDefault` so a plain downstream assignment
wins; bind/exec lists concatenate. Behaviour options overridable,
appearance flows from the state JSON.
- Options live in the existing surfaces: `nomarchy.system.*` /
`nomarchy.hardware.*` / `nomarchy.services.*` (NixOS, `system.nix`),
everything else `nomarchy.*` (HM, `home.nix`). Update the README tables
when the surface changes.
## Feature design
- **In-flake state:** any user-settable config gets a menu writer that
lands it in `state.json` (`settings.*`), git-tracked. No
`~/.local/state`, no side files. Instant-effect where possible
(`--no-switch` + flip the service; the service reads the *live* working
tree at start — the night-light `ExecCondition` pattern). Rebuild-baked
values graduate via `mkDefault` reads of the settings key.
- **Toggle vs package:** a `nomarchy.*` toggle only when there's real
config behind it (units, groups, udev, firewall). A bare package goes
in the downstream template's `home.packages` — opt-out is deleting the
line.
- **Opt-in features** ship a commented example in
`templates/downstream/home.nix` or `system.nix`. That template is the
single source of truth for machine files: `nomarchy-install` copies it
and patches install-time values only (never a thinner second catalog).
- **Menu:** new entries go in the right submenu (Tools / System ; root
stays six entries), end lists with the shared `↩ Back`, self-gate on
the feature's availability, and add the direct
`SUPER+CTRL+<mnemonic>` bind in `keybinds.nix` (single source — it
feeds both Hyprland and the SUPER+? cheatsheet).
- **Waybar:** new indicators self-gate (hidden when irrelevant), use
named `writeShellScriptBin`s on PATH (so static configs can exec them
by bare name), and are added to **both** the generated `waybar.nix`
config **and** every `waybar.jsonc` whole-swap — summer-day, summer-night,
executive-slate, executive-ivory, boreal, boreal-dawn, kiln and
kiln-clay (the parity rule).
- **Theming:** every new visual surface consumes the palette from the
state JSON. There is no second renderer to keep in sync — add the key
to the JSON, consume it in the module.
## Testing
- docs/TESTING.md is canonical; LOOP.md's ladder (V0V3) sets the
required tier. Cheap first: `nix flake check --no-build`, `bash -n`,
`py_compile`.
- Prefer a permanent `checks.*` runNixOSTest over a one-off manual poke;
reusable recipes (headless Hyprland with software GL, QMP screenshots,
udev-event fakes) are indexed in MEMORY.md and demonstrated by the
existing checks (`distro-id`, `hardware-toggles`,
`battery-charge-limit`).
- The honesty rule: report exactly what you verified and at which tier.
## Git
- `main` is development (direct commits, pushed); `v1` is the release
pointer — **human-only, fast-forward-only, never touched by agents**.
- Commit style: `feat|fix|test|docs|chore(scope): summary`, body with
what/why + verification tier + what remains. Bookkeeping (`agent/`
updates) rides in the same commit as the change.
- `flake.lock` moves only when the task *is* a lock bump. The release
inputs stay within their pinned release branches; the `nixpkgs-unstable`
input (#134, feeds `unstable.*`) tracks `nixos-unstable` and bumps
together with the rest — never alone, never `nix flake update`.

View File

@@ -1,112 +0,0 @@
# Delegation — capability tiers, roles, token economy
How to spend model capacity in this repo, for **any** agent harness.
Tasks are matched to capability *tiers*, not vendor model names; each
harness maps tiers to its own models (table at the bottom). GOALS says
what to build, CONVENTIONS how to write it — this says who does which
part.
## The dividing line: evidence vs judgment
Cheap models gather evidence; they never make verification claims or
design decisions. The following always stay on the **frontier** tier and
are never delegated downward:
- deciding what to test and the regression scope (VERIFICATION.md)
- interpreting an ambiguous or flaky failure
- viewing screenshots and judging visual quality — aesthetic judgment is
exactly what small models do badly, and it's load-bearing here
- writing non-trivial Nix (module structure, overlays, cross-cutting
refactors)
- the final diff review and report
Product calls ("finish vs quarantine", promoting PROPOSED items) sit
above even that: they belong to the human.
## Tiers
| Tier | Best for | Delegate to it? |
|------|----------|-----------------|
| **light** | bulk mechanical: search, summarize, audit sweeps, running the harness | freely — never for judgment |
| **standard** | well-specified scoped edits, routine research | when the spec is already written |
| **frontier** | design, novel code, ambiguous failures, taste | this is the loop's own tier |
Reasoning effort: default moderate; go maximum only for the hardest
calls. A frontier parent may spawn a frontier child for one hard call.
Work above your tier gets returned, not attempted.
## Standing roles
Two mechanical roles exist for any harness that supports subagents
(Claude Code implementations: `.claude/agents/`; other harnesses
implement the same contracts in their own format):
- **scout** (light, read-only): locate where things are defined, map
which files touch a subsystem, scan build logs / serial output for
error lines, docs-vs-code drift sweeps. Reports facts with paths and
line numbers, quotes the minimum snippet, says "not found" plainly —
never guesses, never recommends.
- **runner** (light, executes): builds, VM boots, screenshot capture,
the scripted `tools/` checks. Headless and unattended, every wait
bounded by a timeout. Returns commands, exit codes, wall time, and
artifact paths — never marks anything passed or verified; the caller
makes the verification claim.
Use them for pure information-gathering or pure execution instead of
pulling bulk (log files, wide scans) into the main context. When a
result surprises you, spot-check it yourself before building on it —
cheap models are allowed to be wrong about hard things, which is
precisely why they're not allowed to make claims.
## Economy rules
- **Only delegate when writing the spec is cheaper than doing the
work** — a spawned agent starts cold and must re-derive context. A
one-file read is cheaper done directly.
- **Brief every child cold; point at the spec, don't restate it.** "The
spec is in `agent/BACKLOG.md` #NN — implement it" plus only the
*constraints* (scope files, branch, no-VM, no-push).
- **Match the tier to the task, not the prestige.** An item whose spec
is already written (exact files, exact fixes) is standard-tier work;
reserve frontier children for genuine multi-step reasoning. This is
the single biggest saving.
- The strong model writes the spec, reviews the result, and owns the
commit.
## Fanning out parallel work (V0/V1, no VM)
When several NEXT items are independent and don't need the VM, spread
them across worktree-isolated subagents in parallel:
- **Disjoint file lanes.** Partition items so no two agents touch the
same file (map the touched files first). If two items must share a
file (README, flake.nix, rofi.nix), give both to one agent or keep
one for yourself.
- **Isolation + you own landing.** Each agent works in an isolated
worktree, commits to its own branch, and **never pushes or touches
`main`/`v1`**. You review each diff, cherry-pick onto `main`, and do
the bookkeeping — a single landing agent can't race itself. Clean up
worktrees and branches after landing.
- **Lean on scriptable checks as primary evidence.** Where a
deterministic `tools/` check or `checks.*` guard already proves the
property, that near-free run *is* the V0/V1 evidence.
- **Batch V2 at the end, once.** Delegated visual/behavioural items come
back "V2 pending"; collect the landed changes and do **one** VM pass
covering all of them — the VM render + screenshot review is the most
expensive step in the loop; amortise it.
- **Re-verify on `main`, but leanly.** After landing, confirm the
agent's V0/V1 on the merged tree with a targeted build, not a full
re-run. Trust-but-spot-check scales; blind re-running doesn't.
The judgment list above still holds: *you* review every diff before it
lands.
## Per-harness model mapping
| Tier | Claude Code |
|------|-------------|
| light | `haiku` |
| standard | `sonnet` |
| frontier | the session's top model (`opus` and up) |
Other harnesses: add a column when one is actually used on this repo.

View File

@@ -1,60 +0,0 @@
# Goals — what "done" looks like for Nomarchy
The north star every loop iteration serves. When two options conflict,
the earlier pillar wins. Product *themes* toward a **v1.0** ship (day-2
confidence, default identity, release bar) live in
[`docs/VISION.md`](../docs/VISION.md) — still subordinate to these pillars.
## The four pillars (in priority order)
1. **Rock-stable.** A workstation you never fight. Everything is a NixOS/HM
generation — atomic, rollbackable, never partial. `nix flake check` is
green at every commit on `main`. Regressions are caught by the VM-test
suite (`checks.*`), not by users. `v1` only ever advances after human
on-hardware QA.
2. **Reproducible, with zero hidden state.** The downstream flake checkout
*is* the machine. All user-settable config is menu-writable into the
git-tracked state file (`state.json` `settings.*`) — never
`~/.local/state`, never `~/.config` side files. Re-cloning your flake
reproduces the machine, settings and all.
3. **Effortless to configure.** The user never has to learn Nix. Every
common knob is reachable from `nomarchy-menu` (SUPER+M); the menu is an
ergonomic writer for the flake. Where a toggle can take effect without a
rebuild, it must (`--no-switch` + flip the running service).
4. **Beautiful.** One JSON themes the entire desktop coherently — Hyprland,
Waybar, Kitty, btop, rofi, GTK/Qt, boot splash, greeter. Every new
surface follows the palette. Informative, self-gating Waybar modules
(they hide when irrelevant). No unthemed corner survives contact with
the theme switcher.
## Quality bars (non-negotiable)
- **The honesty rule** (docs/TESTING.md): for anything visual, "the Nix
evaluates" is not "it renders". Verify at the highest tier you can reach
(see LOOP.md's verification ladder) and *state the tier you reached*.
Never claim a check you didn't run.
- **Parity rule:** any module added to the generated Waybar config must
also be added to the summer-day/night `waybar.jsonc` whole-swaps.
- **Menu placement:** new menu entries go in the right category submenu
(Tools / System ), with a direct `SUPER+CTRL+<mnemonic>` bind and
self-gating where applicable. The root picker stays six entries.
- **Opt-in features** ship a commented example in
`templates/downstream/{home,system}.nix`.
- **Option surface discipline:** a toggle exists only when there is real
config behind it. Bare package installs go in the template's
`home.packages` (opt-out = delete the line), never a `nomarchy.apps.*`.
## Non-goals (do not drift into these)
- **No binary cache.** Compile-from-source is a deliberate values call;
automation targets the config/lock channel, not artifact distribution.
- **No second theming pipeline.** The dispatcher owns menu structure; the
renderer (rofi) stays swappable. No GTK4 launcher.
- **No nixpkgs major bump on `main`.** Lock updates stay within the pinned
release branch; a release jump is a deliberate `v2`, hand-edited by the
maintainer (the last attempt was discarded over a Hyprland OOM — see
agent/MEMORY.md).
- **No repo-wide reformat.** The `.nix` files use deliberate aligned
hand-formatting; adopting a formatter is an open maintainer decision
(BACKLOG.md § Decisions), not a cleanup.
- **No multi-DE.** Hyprland is the desktop.

View File

@@ -1,657 +0,0 @@
# Hardware queue — V3 checks only a human can run
Everything shipped at V1/V2 whose final verification needs real hardware.
Agents **append** (newest at the bottom of a section) with exact steps;
Bernardo runs them and either checks off (`[x]` + date + verdict) or files
the failure as a NOW bug in BACKLOG.md. Checked-off entries are **pruned**
on the next sync sweep (LOOP.md §5) — outcomes live in the journal/ROADMAP;
git history is the archive. Machines: the **AMD dev box** (Ryzen AI
laptop: AMD + fingerprint + NPU), the **Latitude 5310/5410** (Intel QA),
the **T14s** (webcam case).
## Suggested order (sweep 2026-07-15)
Run these first when you have a laptop session (AMD dev box unless noted):
| # | Item | Why first |
|---|------|-----------|
| 1 | **#115** suspend-then-hibernate | Just shipped; quick path is minutes (battery vs AC + `nomarchy-suspend`) |
| 2 | **#127** the brick's *cause* (reopened) | Wake path fixed + fully verified 2026-07-16; cause unknown — start at the dead Ctrl+Alt+F3, not the wake path |
| 3 | **Docking recovery round 8** + headphone jack-follow | Same dock session; relogin once, then undock ×5 + jack |
| 4 | **#104** airplane mode | Fast radio smoke after `nomarchy-home` |
| 5 | **#101** charge-limit USB-C burst | Dock plug/unplug storm; pairs with #3 |
| 6 | **Parallel fingerprint** hyprlock/greeter residual | sudo path already PASSED 2026-07-14 |
| 7 | **#95** Kitty-only on Acer | Validates terminal stack on oldest GPU |
| 8 | **#123** / **#124** install bake + flake pin | Next full reinstall window |
Everything else below stays open; order is convenience, not a gate.
## Any machine (dev box is fine)
- [ ] **Theme pairs V3 — the four new themes on real GL** — after pulling
the pair commits: `nomarchy-state-sync apply <slug>` for each of
boreal-dawn, executive-ivory, kiln, kiln-clay. Pass = all of:
(1) the bar renders true — boreal-dawn's frosted pills actually blur
what's behind them (softGL VM can't prove blur), kiln's plank shows
its copper edge, no font-fallback boxes (GeistMono / JetBrainsMono /
CaskaydiaCove); (2) wallpaper paints at native res, no banding on the
kiln ember gradient; (3) Kitty + btop under each theme — btop colors
match (the VM never started the GL terminal; btop.theme is
file-asserted only); (4) boreal-dawn app launcher (SUPER+Space) lays
out its icon grid; (5) an nm-applet/tray menu is readable under all
four (the `*`-reset counter-rules). If a VM-rendered preview looks
off in the theme-picker grid next to the hardware-captured ones,
recapture that preview on hardware into themes/<slug>/preview.png.
- [ ] **Light-theme contrast refits (2026-07-18 report)** — under any
light theme: (1) with two workspaces open, the inactive number is
readable on the bar (subtext, not the washed-out grey); (2) hover a
notification popup — background stays the theme's light card with a
faint tint, text readable (the swaync default's dark
.notification-default-action hover is now overridden); close
button matches the palette. VM-verified with a hover probe under
summer-day; this is the on-hardware confirmation of the report.
- [ ] **Auto-theme pair flip on hardware** — set
`settings.autoTheme.{day,night}` to one of the new pairs (e.g.
kiln-clay / kiln), set sunset (or sunrise) a few minutes ahead via
Look & Feel Auto theme (the time edit itself rebuilds — since the
exact-time revision the timer fires AT the configured minute, no
15-min poll), let the timer flip once each way. Pass = (1)
`systemctl --user list-timers nomarchy-auto-theme` shows the two
configured times as the trigger; (2) the switch lands within ~a
minute of the configured time; (3) the whole desktop switches in
one generation, and nothing leaks from the previous theme — fonts,
rounding, terminal opacity all reset (the per-theme appearance
block), wallpaper follows; (4) with autoCommit on, `git -C
~/.nomarchy log` shows an `apply theme` commit and state.json is
not left dirty (the 0.5.1 pathspec fix).
- [ ] **#148 dock-intent enforcement without auto-profiles (dev box, docked)** —
after pulling the #148 commit: **log out and back in first** — the
watcher is `exec-once`, so `nomarchy-home` alone leaves the old one
running. Then, docked clamshell (lid closed, panel off, dockMode set
from the Display menu or a prior auto-dock), run `nomarchy-home` or a
bare `hyprctl reload`. **Pass:** eDP-1 re-lights for at most ~12 s and
goes back off by itself; `journalctl -t nomarchy-display-watch
--since -5min` shows `dock-intent=true … action=re-dock`; a "Docked —"
toast appears. Same relogin answers #149's question (does the Waybar
unit still race Hyprland IPC on a cold relogin without linger?).
**Fail:** the panel stays lit inside the shut lid (the #148 symptom).
- [ ] **#115 suspend-then-hibernate (laptop with hibernate/resume wired)** —
after system rebuild: Preferences shows **Suspend then hibernate (on)**
(hidden if `CanHibernate=no`). Confirm:
`grep -E 'HibernateDelaySec|HibernateOnACPower' /etc/systemd/sleep.conf`
`1h` / false; `grep HandleLidSwitch /etc/systemd/logind.conf`
undocked s2h, ExternalPower=suspend, Docked=ignore.
**Quick path:** on battery, `nomarchy-suspend` then
`systemctl list-jobs` / journal should show suspend-then-hibernate
(not plain suspend). On AC, same command → plain suspend.
**Full path (optional, long):** battery, lid close or idle past 15m,
leave ~1h+, open → single LUKS unlock (encrypted) or lock screen
(unencrypted), session intact. Toggle **off** + rebuild → plain
suspend only. **Pass:** battery s2h + 1h hibernate resume works;
AC never plans hibernate; no row without hibernate support.
- [ ] **#104 airplane mode radios (any laptop with Wi-Fi + BT)** —
after `nomarchy-home` + waybar restart: System **Airplane mode (off)**
(or SUPER+CTRL+R). **Pass on:** Wi-Fi and Bluetooth both drop (nm-applet /
blueman confirm); Waybar shows a plane glyph; click glyph or menu again
restores **only** the radios that were on before. If Wi-Fi was already
off, it must stay off after disengage. Glyph must **not** appear when
airplane is off.
- [ ] **#96 battery-limit row on threshold-less firmware (Acer M5-481T)** —
on the Acer (live or installed session with the commit carrying this
entry): open System (`SUPER+CTRL+I`). **Pass:** a "Battery limit" row
IS listed (battery present); picking it shows the notification
explaining the firmware exposes no charge-stop control (not a raw
error, and the row never silently disappears). On the T14s the row
must still open the working preset picker.
- [ ] **#101 charge-limit service survives USB-C power-event bursts**
(round 2) — round 1 FAILED on TuringMachine 2026-07-13 18:16: a
*spaced* storm (each run finished before the next AC event) landed 5
successful starts in 10s → `start-limit-hit`, unit marked failed,
doctor badge — although every run succeeded. Fixed by exempting the
unit from start rate limiting (`StartLimitIntervalSec=0`) + a
spaced-storm guard in `checks.battery-charge-limit` (the commit
carrying this entry). **Re-check:** after pulling/rebuilding this
commit, `sudo systemctl reset-failed nomarchy-battery-charge-limit
.service`, then plug/unplug the powered USB-C dock several times in
quick succession. **Pass:** `systemctl is-failed
nomarchy-battery-charge-limit.service` stays false, `Result=success`,
BAT0's `charge_control_end_threshold` still matches the limit, and the
journal has no new `start-limit-hit`/`Failed with result` lines.
- [ ] **Docking recovery round 8 (undock panel restore, 2026-07-14)**
round 7 **FAILED** on the AMD dev box (Bernardo, 56 consecutive
unplugs, none recovered). Its whole diagnosis was wrong, and the way it
was wrong is the lesson: round 6 read `result=enable-timeout` ×2 +
`result=ok` ×1 as an intermittent race and retried the keyword. But the
keyword is **inert, not raced** — with ZERO enabled outputs (panel
disabled by the dock, external gone) Hyprland 0.55.4 accepts
`keyword monitor` (prints `ok`, exits 0) and never flushes it until a
DRM event arrives. Retrying an inert command 25×/poll for 6 polls just
bought 30s of black screen. Every `result=ok` in that journal was
**Bernardo plugging the cable back in** — his hotplug flushed the queued
rule and the next poll took the credit. Probed live 2026-07-14: keyword
inert across 4s and 5s in two runs, `dispatch forcerendererreload` also
inert, and only `hyprctl reload` worked — 99ms and 289ms, cable out.
The transition now escalates to `reload` when the keyword proves inert,
re-asserts the rule, and restores per-device keyboard layouts (a reload
drops runtime `device[…]:kb_layout` keywords).
**Already proven on hardware** (2026-07-14, two real unplugs invoking
the fixed `nomarchy-display-transition undock eDP-1` directly with the
watcher paused): panel on and workspaces 13 home in 1.8s, journal
`keyword=inert escalate=reload``enable=via-reload``result=ok`; and
the keyboard-restore path with a Logitech K400 whose receiver is in the
laptop — it kept its remembered `us` across the undock, while a control
`hyprctl reload` with no restore dropped it to the session's `gb`
(so `restore_keyboards` is load-bearing, not insurance).
**What is NOT proven, and is this round:** the *watcher-driven* path
(the running watcher calls the transition by baked store path, so it
only picks the fix up at relogin — the round-5 trap), and repetition.
**Re-check:** relogin (mandatory — see below), dock, then unplug the
cable **at least five times**, plus once with the lid shut.
**Pass:** the panel comes back every time with workspaces intact, and
`journalctl --user
-t nomarchy-display-transition -t nomarchy-display-watch --since
'-10 min'` shows `result=ok` for each undock with **no**
`result=enable-failed`. `keyword=inert escalate=reload` on every undock
is EXPECTED — that is the fix firing, not a fault. A `result=ok`
**without** a preceding `keyword=inert` line means the keyword flushed
on its own and the reload was never needed: interesting, worth
reporting, still a pass.
- [ ] **Headphone jack-follow (last open count from rounds 46,
2026-07-14)** — rounds 46 opened five counts on the AMD dev box; four
are now **confirmed fixed on hardware** by round 6 (Bernardo): wallpaper
on the external, automatic dock mode, audio reaching the monitor, and
the external keyboard prompting **exactly once** (was four times). The
undock panel is round 7 above. This is the one count **never tested**:
plugging headphones had stopped moving audio to them, because a pinned
`default.configured.audio.sink` outranks the priority rules, which kills
jack-follow on UCM cards where the headphones are their own sink (fixed
in ce480f3's lineage, unverified). **Re-check:** docked and undocked,
plug and unplug headphones — audio must move to them and back each time,
and `journalctl --user -t nomarchy-dock-audio` must show `trigger=jack
selected=…` on each plug. Worth folding into the round 7 session: the
dock is already in hand and the two don't interfere.
**Historical detail for the four settled counts** (kept only until
round 7 closes; the fixes themselves are in the log):
— round 4 was run on the AMD dev box and **failed on four counts**
(Bernardo): no wallpaper on the external monitor, no automatic dock
mode (System Display had to be used by hand), audio staying on the
laptop speakers while docked, and one dock + one external keyboard
asking for a layout **four times**. Separately, plugging headphones had
stopped moving audio to them. Causes and fixes in the commit carrying
this entry: the display watcher's `socat -T 1` closed Hyprland's IPC
socket after a second of idle, so a dock plugged into an idle desktop
lost `monitoradded` in the reconnect gap (wallpaper + dock mode + audio
all hang off that one event); Hyprland lists every key-capable evdev
node as a "keyboard"; and a pinned `default.configured.audio.sink`
outranks the priority rules, which kills jack-follow on UCM cards where
the headphones are their own sink. **Re-run round 4 below in full**,
plus: (a) leave the desktop untouched for ~30s, then plug the dock —
wallpaper, dock mode, and audio must all follow with no manual step;
(b) the external keyboard must prompt **exactly once**, and never for
the monitor or the lid/power buttons; (c) docked and undocked, plug and
unplug headphones — audio must move to them and back each time.
`journalctl --user -t nomarchy-display-watch` should show
`outputs-changed added=…`, and `-t nomarchy-dock-audio`
`trigger=jack selected=…` on a headphone plug.
**Round 5 was itself run and failed (2026-07-14); this entry now covers
round 6.** Two of the four reports were never actually testing the fix:
both watchers are Hyprland `exec-once`, so `nomarchy-home` swaps the
config but leaves the *old* processes running — only `nomarchy-dock-audio`
(a user service) had picked up new code. **Relogin is mandatory before
testing either watcher**; confirm with `tr '\0' '\n' < /proc/$(pgrep -f
bin/nomarchy-display-profile-watch)/cmdline | sed -n 2p` and grep the
script for `outputs-changed`. The other two were real and are fixed in
the commit carrying this entry: automatic dock mode did not exist at all
(the watcher auto-*undocked* but only ever matched display profiles on
plug, and this box has none saved with `displayProfileAuto` unset, so
nothing happened — `Dock mode` was interactive-only), and the BenQ's
audio was unreachable because WirePlumber had `alsa_card.pci-0000_c3_00.1`
pinned to `pro-audio`, whose raw `pro-output-N` sinks have no ports and
no routing. The BenQ's own USB card is input-only (all profiles
`sinks: 0`), so DisplayPort is the only path to its speakers.
**Also check:** no display profile is saved, yet plugging the BenQ must
now dock by itself; and `journalctl --user -t nomarchy-dock-audio` shows
`repaired-card=alsa_card.pci-0000_c3_00.1 profile=HiFi` on the first
plug, after which audio reaches the monitor's speakers. A saved+matching
display profile must still win over auto-dock.
- [ ] **Docking recovery round 4 (#100, closed-lid BenQ sequence)** — after
updating to the commit carrying this entry, run `nomarchy-home` and
relogin. With the lid open, put visible windows on at least workspaces
1, 2, and 3 and start audio; plug the BenQ GW2790QT. **Pass on plug:**
wallpaper appears, audio toasts and moves to a BenQ/GPU HDMI sink, and
`journalctl --user -t nomarchy-dock-audio -n 10` shows
`trigger=monitoradded` plus `selected=…` (not only a card event). Open
System Display **Dock mode**. **Pass on dock:** every workspace and
window is on the BenQ, focus follows, eDP is disabled in `hyprctl
monitors all -j`, and `systemd-inhibit --list` shows Nomarchy holding
`handle-lid-switch` for “Safe dock/undock display transition”; there is
no dangling laptop workspace and no application exits. Close the lid;
audio/session must stay awake. While still docked, manually choose the
speakers and change volume/mute: it must stay on speakers. Unplug the
BenQ **with the lid still closed**, wait 10 seconds, then open it.
**Pass on undock:** no suspend/crash/hard reset, eDP is already active,
workspaces 1/2/3 and their windows are intact on it, and the inhibitor
disappears only after lid-open. `journalctl -b --since '-5 min'` must
contain no cable-removal `systemd-logind: Suspending...`. Finally close
the undocked lid normally: the laptop must suspend, proving policy was
restored. Wake it, replug the BenQ: that fresh plug must move audio back
to HDMI (manual speakers do not survive a physical replug). If any step
fails, capture `journalctl --user -t nomarchy-display-watch -t
nomarchy-display-transition -t nomarchy-dock-audio --since '-10 min'`,
`systemd-inhibit --list`, `hyprctl monitors all -j`, `hyprctl workspaces
-j`, and `pactl --format=json list sinks` before changing state again.
- [ ] **Parallel fingerprint-or-password on the real reader** (AMD dev
box, 2026-07-12) — **sudo path PASSED 2026-07-14** (Bernardo, dev box,
gen 422, `fingerprint.pam = true`, parallel default): `sudo -k true`
showed ONE prompt, the typed password worked, touching the sensor
instead worked, and wrong-finger ×3 fell back to password.
**Still open:** hyprlock and the greeter must accept both factors and
not wedge on a leftover prompt after a fingerprint win (known cosmetic
quirk of the hack — pthread_cancel'd prompt); `fingerprint.parallel =
false` must restore the old sequential behavior.
**Do NOT "check password still works with `systemctl stop fprintd`"**
that check was in this item and it is a trap that cannot fail. fprintd
is D-Bus activated, so PAM asks D-Bus and D-Bus starts it straight back
up: Bernardo ran it and *the fingerprint still authenticated*, which is
the tell that the daemon never stayed down. It proves nothing about a
dead reader. The property it was reaching for is structural anyway —
password is `auth sufficient` at order 11700, independent of the fprintd
rule at 11400, so no fprintd failure can gate it (verify by eval:
`nix eval --raw <flake>#nixosConfigurations.<host>.config.security.pam
.services.sudo.text`), and checks.hardware-toggles asserts that shape in
the VM with no reader at all. If someone genuinely wants it on hardware
it needs `systemctl mask` + stop, and masking a working machine's
authentication daemon to re-prove an evaluated invariant is a bad trade.
- [ ] **#55 fingerprint enroll on real reader** — with
`nomarchy.hardware.fingerprint.enable` and a physical reader: System
Fingerprint Enroll a finger; List shows it; Verify succeeds.
Enroll/List/Verify verified on hardware 2026-07-14 (T14s); the
remaining unknown is a reader-less machine's self-gate.
- [ ] **fingerprint + auto-login toggles on hardware** (this commit) — on the
T14s, after removing the pinning lines from `~/.nomarchy/system.nix`
(`greeter.autoLogin`, `hardware.fingerprint.pam`):
1. System Fingerprint shows **Fingerprint (on)** (state already true).
Toggle it off → terminal opens, sudo system rebuild + home switch →
toast "Fingerprint off". Expect: sudo now refuses the finger and asks
only for a password; the lock screen (SUPER+CTRL+L) shows no
"or scan your finger" line. Toggle back on → both return.
2. With fingerprint OFF and no finger enrolled, the toggle must REFUSE
with "Enroll a finger first" and write nothing.
3. System Auto-login shows **(on)**. Toggle off → sudo rebuild →
reboot → tuigreet asks, and accepts password OR finger (fingerprint
on). Toggle back on → reboot → straight to the session, no prompt.
Expected throughout: the two are independent, and auto-login on means
no boot prompt regardless of the fingerprint switch.
- [ ] **#60 non-BAT* battery name (if available)** — on a machine whose
system battery is **not** named `BAT*` (e.g. `CMB0`): confirm
charge-limit oneshot writes the threshold, System Battery limit
and Power profile rows appear, Waybar power-profile module shows,
doctor charge-limit section runs. On `BAT0`-only machines this is
a no-op (charge-limit re-apply passed on `BAT0` — Latitude
2026-07-10).
- [ ] **btop theme fidelity (#52 residual)** — softGL theme-shot cannot
open a real terminal UI, so the hand `btop.theme` assets were only
guest-file asserted (main_bg / inactive_fg keys) + desktop/bar
rendered for rose-pine, everforest, summer-night, vantablack,
catppuccin. On a real session: `nomarchy-state-sync apply <slug>`
then `btop` and confirm backgrounds/text match the theme (esp.
rose-pine Dawn light bg `#faf4ed`, vantablack near-black `#0d0d0d`,
catppuccin Mocha `#1E1E2E`). One pass cycling those five is enough.
- [ ] **SSH_AUTH_SOCK for GUI clients** — after relogin, launch a GUI git
client (or `rofi`-launched terminal-less app) and confirm it reaches
gpg-agent's SSH socket without an interactive shell parent.
- [ ] **Notification inhibited-state glyph** (item 28 color-only sweep,
iteration #69) — have an app hold a notification inhibitor (e.g. run
a fullscreen video, or `busctl --user call org.freedesktop.Notifications
… Inhibit`); the Waybar bell must switch to the muted **bell-off** 󰂛
(same glyph as DND), NOT the normal bell 󰂜/󰂚. Then release it → the
bell returns. Repeat on a whole-swap bar (summer-day/night use their
own bell-off glyph, executive-slate/boreal use 󰂛). The glyph itself
already renders (DND uses it); this only confirms swaync emits the
`inhibited-*` class and the bar routes it.
- [ ] **Keyboard layout cycle bind** — with a comma layout (e.g.
`nomarchy.keyboard.layout = "us,de"`), SUPER+SHIFT+K cycles the
focused keyboard's layout, the Waybar `󰌌` indicator follows, and
the row shows in the SUPER+? cheatsheet. — 2026-07-04 attempt:
no comma layout was configured, so the bind wasn't rendered (the
gate working as designed, not a failure) — retest after setting a
comma layout + rebuild + relogin. The SUPER+? no-op found en
route was a real bug → BACKLOG item 26 (fix shipped; SUPER+?
cheatsheet PASS on Latitude 2026-07-10).
- [ ] **swaync readable on summer-day** (item 25 fix) — on summer-day
after `home-update`: `notify-send "title" "body text"` shows
readable body text; open the control centre (SUPER+N), hover a
notification row and check the Clear button — all text legible
(body/buttons now @text on tinted chips, not subtext/surface).
- [ ] **Bar + rofi legible on flexoki-light** (item 27 fix) — switch to
flexoki-light: every Waybar module readable (window title, tray
row, dimmed inactive workspaces / muted volume visible-but-dim),
rofi inputbar/alternate rows show text. Spot-check one dark theme
(e.g. tokyo-night) for no visual regression in the same spots.
- [ ] **Stub-bar themes got the generated bar** (item 28, 90a5104) —
switch to catppuccin, lumon, nord and retro-82: each now shows
the generated styled bar in its own palette (workspace pill,
padded right cluster) instead of the old raw default Waybar.
Their rofi menus should look unchanged.
- [ ] **Back-audit spot-check** (item 24) — the keybinds cheatsheet
(SUPER+? / root menu Keybindings) now ends in ↩ Back and
returns to the root picker; spot a couple of submenus (Display,
VPN Tailscale) still Back correctly after the audit pass.
- [ ] **Per-device keyboard menu + hotplug restore (re-verify after in-flake
graduation)** — update to the current `main`, run `nomarchy-home`, and
relogin with an external keyboard already connected. Open System
Keyboard: both the built-in and external boards must be listed with
their current layouts. Pick a different layout for the external board;
it must apply only there and persist in `settings.keyboard.devices`.
Unplug/replug it: the saved layout must restore without another prompt
and graduate into a `device{}` block on the next rebuild. Finally plug
a previously unseen keyboard in after login: the automatic picker must
appear once and remember that choice too.
- [ ] **Snapshots restore + rollback exercise** (residual — the
GUI/polkit half passed on hardware 2026-07-04, a47aa3a): in
`sudo nomarchy-snapshots`, restore a single file (`undochange`)
and walk a root-config rollback up to (or through) the
typed-`yes` gate.
- [ ] **Rollback menu (item 9b)** — after a couple of theme changes,
menu System Rollback: recent desktop generations listed
(newest marked current); picking an older one opens a terminal,
activates it, and the theme visibly reverts; picking the newer one
again rolls forward. The two System rows: Snapshots opens the
snapshot flow, "boot an older generation (how)" fires an
instruction notification.
- [ ] **Open-a-file smoke (viewers + mime defaults, item 8)** — after
`nomarchy-home`: from yazi/Thunar, open a PDF (→ zathura, themed to
the palette), an image (→ imv, NOT GIMP), a video (→ mpv);
`xdg-open .` on a directory → Thunar; a link → **Chromium**
(`chromium-browser.desktop` — template default as of 2026-07-10).
- [ ] **Update awareness** — with `nomarchy.updates.enable`, let the timer
fire (or start the unit): indicator appears only when inputs are
behind, notification only on count growth, click opens the upgrade
flow.
- [ ] **VPN menu live paths** — import a real WireGuard `.conf` and an
`.ovpn` via System → VPN, toggle up/down (● / ○ state), and the
Tailscale block: up/down + exit-node without sudo (operator grant).
Exit-node rows now show "hostname — Country City" (2026-07-11,
header-offset parsing): confirm Mullvad-style nodes carry their
location, a locationless self-hosted node stays a bare hostname,
and picking either still sets the node (`tailscale status` shows it).
- [ ] **Printer menu** — with `nomarchy.services.printing`, System →
Printers opens system-config-printer; add a printer, test page.
- [ ] **GRUB UEFI ISO theme render** — boot the ISO on UEFI hardware:
composed splash background, palette menu in the lower third, accent
timeout bar.
- [ ] **Visual theme pass** — live ISO: all six identity themes (bars) +
the four authored rofi `.rasi` (nord/retro-82/lumon/kanagawa) look
right, not just parse.
- [ ] **Display profiles, slice a (item 15)** — declare two
`nomarchy.displayProfiles` (e.g. docked disables eDP-1 + arranges
the externals; undocked re-enables it), `home-update`, then:
System Display Profiles lists them (● marks active); applying
is instant (no rebuild) and survives a relogin (the baked
overlay); "Base layout" restores; a menu resolution pick made
earlier must NOT re-enable a profile-disabled panel after the
next rebuild.
- [ ] **Display profiles workspace pins, slice c (item 15)** — give the
docked profile `workspaces = { "1" = "<ext>"; "9" = "eDP-1"; }`:
applying the profile moves open workspaces 1/9 to those outputs
immediately, new visits land there too, and the pins survive a
relogin (baked `workspace` rules). Switching to a profile without
pins leaves workspaces where they are (stale session pins are
expected until reload/rebuild — noted in the applier).
- [ ] **Display profiles auto-switch, slice b (item 15)** — same setup,
then menu Profiles Auto-switch on: plugging the dock/monitor
applies the matching profile within ~3s (toast names it);
unplugging switches back; Auto-switch off stops that; with two
profiles naming the same outputs, no flapping (ties do nothing).
- [ ] **P2 retunes eyeball (item 28b)** — spot-check on top of the P1
entry below: gruvbox/nord/lumon/everforest/retro-82/white dimmed
text (inactive workspaces, muted volume) now visible-but-dim;
latte + rose-pine(dawn) + summer-day warn/battery tint reads on
the light bases; flexoki-light statuses are the deeper canonical
600s; latte selected-row/alt-accent pink darker. Anything that
lost its "dim" feel → reopen 28b.
- [ ] **Retuned palettes eyeball (item 28b P1)** — switch through
summer-day, flexoki-light, kanagawa, miasma: chips/menus/toasts
now draw on a *raised* surface (summer-day's was slate-on-cream,
kanagawa's near-black, miasma's pure black), secondary text
(tooltips, fastfetch labels) is visible on summer-day +
flexoki-light, kanagawa floats are lighter than the bg (upstream
sumiInk4). Anything that reads worse than before → reopen 28b.
- [ ] **Doctor bar tripwire (LATER item)** — with everything healthy
the bar shows nothing; `systemctl --user start doomed`-style
induced failure → within ~5 min a red appears (tooltip lists
the ✖ lines), clicking opens the sheet in a terminal;
reset-failed → it disappears on the next poll. Also visible on
both summer bars.
- [ ] **OCR region (LATER item)** — Tools Capture "OCR region →
clipboard": select a region with visible text → toast reports
the word count and `wl-paste` yields the text; Esc in slurp
cancels silently; a text-free region toasts "No text recognized"
without clobbering the clipboard.
- [ ] **Look & Feel submenu (item 19)** — root menu: "Look & Feel"
(replacing Theme, root still six rows) → Theme grid opens, Next
wallpaper cycles instantly, Night light toggles (and is GONE
from System). SUPER+T / SUPER+SHIFT+T direct binds unchanged.
- [ ] **Launch-or-focus (item 17)** — uncomment the template's
`nomarchy.launchOrFocus` firefox example (with firefox
installed), `home-update` + reload: SUPER+B launches firefox
when closed, FOCUSES the existing window when open (also from
another workspace); with firefox removed the bind fires a
"not installed" toast; the row shows under SUPER+?.
- [ ] **Themed greeter + console (item 16)** — after `sys-rebuild` +
logout: tuigreet renders in theme colors (dark container, accent
border, themed prompt/time) and a raw tty (CTRL+ALT+F2) shows the
theme's ANSI palette; on a LUKS machine the passphrase prompt
follows too. Then switch theme + `sys-rebuild` + logout → the
greeter follows the new palette. (tuigreet can't run under the
VM harness — see MEMORY.md — so rendering is hardware-tier.)
- [ ] **Color picker (item 13, final slice)** — SUPER+CTRL+P (or Tools
Color picker): hyprpicker's zoom loupe appears; click a pixel →
toast shows the hex and `wl-paste` yields it; Esc cancels with no
toast. Row present in the SUPER+? cheatsheet.
- [ ] **Low-battery toasts (item 13 slice)** — on battery, drain past
25%: one "Battery low" toast; past 10%: a critical "Battery
critical" toast that stays up until dismissed (swaync); plug in,
drain again → it re-notifies; no repeat toasts while it just keeps
draining. (Crossing logic VM-verified — this checks the real
swaync rendering in a session.)
- [ ] **Audio opens in Amberol** (iteration #60, item 37) — after
`nomarchy-home`: double-click an mp3/flac/ogg (or `xdg-open song.mp3`)
→ it opens in Amberol, not mpv; video files still open in mpv.
(`xdg-mime query default audio/mpeg` → io.bassi.Amberol.desktop.)
- [ ] **rofi menu polish** (iteration #58, item 39 + #56/#57 34/40) —
after `nomarchy-home`: (a) menu System Power profile shows a
colored icon per profile (performance/balanced/power-saver), not
just text — icons *render* (not blank); (b) every submenu's ↩ Back
shows a single arrow, no double; (c) on this single-monitor box,
System Display → pick a resolution → Back returns to System
(not back into the same resolution list).
- [ ] **Screenshot annotation (satty)** (iteration #73) — after `nomarchy-home`:
hit SUPER+SHIFT+Print (or Tools ▸ Capture ▸ Annotate region) → a region
select (slurp) appears, then the `satty` UI opens in fullscreen with the
screenshot loaded. Check that the UI draws on the current theme palette
(tools colored properly) and hitting save places the screenshot in
`~/Pictures/Screenshots/` while hitting copy places it in the clipboard.
- [ ] **#76 no-swap Hibernate notify** (#76 itself is closed — the
hibernate→resume power-cycle **PASSED on TuringMachine 2026-07-12**:
Bernardo ran the full hibernate → power off → single LUKS unlock →
session-restored cycle "flawlessly"; this notify check is the one
remaining leftover) — on a **Nomarchy** machine installed
with `swap = 0` (or temporarily `sudo swapoff -a` on one): `nomarchy-menu
→ Power → Hibernate` must surface a desktop notification ("Couldn't
hibernate — likely no swap is configured. See docs/MIGRATION.md →
Enabling hibernation."), **not** a silent no-op. (`swapon` after, if you
swapoff'd for the test.)
- [ ] **Night-light geo mode (2026-07-11)** — in `home.nix` set
`nomarchy.nightlight = { enable = true; latitude = "<lat>";
longitude = "<long>"; }` (your real coordinates), `nomarchy-home`,
relogin. Then: `systemctl --user status wlsunset` is running (and
`hyprsunset` is NOT); after local sunset the screen visibly warms
(or test by setting coordinates where it's currently night); the
Waybar moon + menu toggle still flip it instantly and an *off*
survives relogin (the ExecCondition gate on the swapped unit —
the eval check proves the wiring, not the runtime gate). **Pass** =
warm shift at the location's night + toggle/persistence intact.
- [ ] **#103 live-ISO baseline apps actually launch** (this commit) — the
half no headless check can reach: `checks.live-baseline-apps` proves the
binaries and `.desktop` entries are in the exact HM generation the ISO
ships, but not that a GUI app opens on real hardware (the agent may not
drive a graphical VM, and the live ISO has no SSH to script one). On the
**Acer M5-481T** (the machine that found this — its GPU is the oldest
shipped, so it is the honest test), boot the new live ISO and, from the
launcher only — no terminal, that's the point: open **Chromium**,
**LibreOffice** (Writer), **Text Editor**, **Amberol**, **Snapshot**.
**Pass** = all five are *listed in the launcher* and each opens a window.
Also: a `.html` file opens in Chromium (the HTTPS mime default now names
a browser that is present — #94's exact bug), and a `.txt` file opens in
**Text Editor** (`text/plain` falls through to
`org.gnome.TextEditor.desktop` after #119; vscode remains preferred on
template installs). Firefox is deliberately absent; its absence is
correct, not a miss.
- [ ] **#123 install bake: first boot fully themed without manual HM switch**
— re-install from a main ISO (this commit+) on **either** the Acer
M5-481T or the Dell XPS 9350. **Pass:** install ends with "Desktop
pre-activated", no `dconf-CRITICAL` in
`/var/log/nomarchy-hm-preactivate.log`, first graphical login has
Stylix GTK + nm-applet **without** hand `home-manager switch`.
- [ ] **#95 Kitty-only terminal on Acer M5-481T** — after rebuild/install
from main: SUPER+Return opens **themed Kitty**; System Doctor opens
a floating classed Kitty window. No Ghostty on PATH required.
- [ ] **#124 flake pin after main-ISO install** — installed machines
`~/.nomarchy/flake.nix` has `?ref=main` (not lagging v1). **Pass:**
`nomarchy-rebuild` does not die with
`The option 'nomarchy.hardware' does not exist`.
- [ ] **Relogin session recovery (2026-07-18 incident)** — after pulling
the session-recovery commit and rebuilding: from a full Hyprland
session (bar up, a browser open so easyeffects/tray are busy), log
out (SUPER+SHIFT+E) and log straight back in at the greeter — do
**not** reboot between. **Pass:** `nomarchy-doctor` shows *no failed
user units*; `systemctl --user status cliphist swaync
xdg-desktop-portal-hyprland swayosd` are all `active (running)`; the
bar, notifications, and clipboard history (SUPER+V) work. This is
the V3 close for the stale `graphical-session.target` /
`start-limit-hit` relogin breakage (BACKLOG #149 note).
- [ ] **#150 hypridle wake path now uses the FULL undock** — after pulling
the #150 commit + `nomarchy-home`: the hyprland-side transition/keyboard
tools are byte-identical (proven by store-path equality), so only the
hypridle wake behaviour changed. Two checks: **(a) suspend/resume while
docked** (lid closed, external on) — suspend, resume; pass = you land
back docked on the external with the panel still off and remembered
external-keyboard layouts intact (the wake now runs the full undock,
which `restore_keyboards` after any reload; the old mini did not). **(b)
zero-output rescue** — the #127 case (dock mode, external gone/black,
eDP disabled): pass = the internal panel comes back lit, workspaces move
to it, and `~/nomarchy-display-dump-*.txt` is written iff it stays dark.
Watch for a regression: the full undock clears dock-intent, so confirm a
*transient* zero-output blip on resume while genuinely still docked does
not leave you undocked with the laptop panel on (the external re-add
should re-dock via the watcher — verify it does).
## AMD dev box only
- [ ] **#118 smartd still runs where drives DO have SMART** (this commit) — the
half a VM cannot answer: QEMU exposes no SMART, so `checks.smartd-gate`
proves the skip but has to drive the *with-device* path through a stub.
On the dev box (real NVMe), after `nomarchy-rebuild` + reboot:
`systemctl status smartd` is **active/running**, and
`systemctl show -p ExecCondition --value smartd` names
`smartd-any-smart-device`. **Pass** = smartd is running, exactly as
before this commit — i.e. the gate skips nothing on real hardware.
**Fail** = inactive/skipped, which would mean the gate is silently
disabling drive-health monitoring: revert it, don't tune it.
Cheap bonus while you are there: `nomarchy-doctor` reports no failed
units (the red-icon symptom that started #118).
- [ ] **AMD runtime bits** — VA-API (`vainfo` → radeonsi), amd-pstate EPP
active and PPD switching governors; opt-ins: ROCm (`rocminfo`, a GPU
PyTorch/Ollama smoke) and the XDNA NPU driver loading.
- [ ] **Fingerprint**`fprintd-enroll` + (opt-in PAM) login/sudo.
- [ ] **#137/#145 splash on a real docked boot** — the one thing the render rig
cannot do: its fake heads exist from the start, so the canvas never
resizes, which *is* the bug. **Docked**, reboot: pass = the logo is centred
on **both** panels at boot **and** at shutdown (it was off-centre on the
external), and the LUKS prompt shows the padlock, the entry, and beneath it
a keyboard icon + your layout (`us`). Then reboot **undocked** — unchanged.
Rendering is already proven (`tools/plymouth-preview.sh`, both heads +
`ask-for-password`), so what is unproven is only a canvas that changes
under a real DRM boot. If the splash is ever blank, the passphrase prompt
is invisible but still live — type it blind, or pick the previous
generation in the boot menu, which carries the old theme.
- [ ] **#142 dock mode survives a rebuild** — **relogin first** (the watcher is
`exec-once`; `nomarchy-home` alone leaves the old one running and you
would be testing nothing — round 6's lesson). Then, docked: run
`nomarchy-home`. Pass = the laptop panel stays off, no workspace lands on
it, no menu trip; `journalctl --user -t nomarchy-display-watch` shows
`dock-intent=true panel=eDP-1 state=re-enabled action=re-dock` (a rebuild
re-lights the panel and the watcher takes it back within ~1s, so a brief
flicker is expected — the panel staying *up* is the failure).
Then the three gates that must NOT fire, since this code can disable a
panel: (a) Menu ▸ Display ▸ **Screen on** → the panel stays on (intent
cleared; if it snaps off again within a second, that is the bug);
(b) **undock** → panel returns and stays; (c) boot/relogin **undocked**
with `settings.display.dockMode` still `true` in state.json → panel stays
on (no external ⇒ never re-dock). (c) is the one that matters: it is the
#127 brick if it is wrong. Gates are unit-tested against stubs, but only
hardware proves the wiring.
- [ ] **#138 dock audio does not break a running browser** — the plug event
itself is the only thing V2 could not do (the tool was driven by hand;
Hyprland calls the same entry point). **Relogin first** — the watcher is
`exec-once`, so `nomarchy-home` alone leaves the old process running and
you would be testing nothing (round 6's lesson). Then, with Chromium
**already open** on a Meet call: dock/undock. Pass = output still follows
to the monitor, **Meet keeps its mic and speakers without restarting the
browser**, and `journalctl --user -t nomarchy-dock-audio` shows
`selected=…` with **no** `action=graph-restart-fallback`, while
`systemctl --user show -p MainPID --value pipewire.service` is unchanged
across the plug. If a `graph-restart-fallback` line does appear, rung 3
fired on a healthy plug — that is a bug, not the recovery working: file
it with the surrounding journal.
- [ ] **System ▸ Firmware menu on real LVFS hardware** (item #43,
`nomarchy-menu firmware`) — on a machine whose firmware/SSD/dock is
on LVFS: open Menu ▸ System ▸ **Firmware**; confirm the terminal runs
`fwupdmgr refresh` → lists real `get-updates` → the y/N confirm gates
correctly → `fwupdmgr update` applies and prints the reboot note when a
capsule needs one. Verify **no** flash happens on "N"/cancel. (VM only
proves the menu row + flow renders; a real capsule write is
hardware-only.)
## Acer Aspire M5-481T only (1366×768 — the narrow-panel case)
- [ ] **#139 sheets on a small panel** — Waybar clock → calendar, System ▸
Doctor, and the Waybar updates click. Pass = each opens floating and
centred at roughly 60%×65% / 55%×70% / 45%×50% of *that* screen (they are
computed from the focused monitor now, so this is checking the fallback
path and the font, not the arithmetic), fully on-screen, none clipped by
the bar. Dev box (2560×1440) already measured exact.
- [ ] **#131 menu width on the real narrow panel** — open Menu ▸ Recovery.
Pass = every label complete (no ellipsis) and the picker visibly *not*
hogging the screen (≤65%, the cap). The geometry was already reproduced
pixel-exactly on the dev box (888px + JetBrainsMono 14 = what 65% of 1366
produces) and passed, so what is genuinely unproven here is only the
Acer's own fontconfig/DPI resolving `ch` the same way — try it under a
**JetBrainsMono 14 theme** (summer-day/night, kanagawa), the widest case.
- [ ] **Theme-switch bar round-trip (2026-07-19 waybar-poke + gtk-pin fix)**
after `nomarchy-pull` + `nomarchy-home`, switch dark → light → dark
(e.g. boreal → kiln-clay → kiln). Pass = the bar visibly restarts on
each switch and the workspace digits land per theme (dark-on-accent
active pill, dimmed inactives on dark themes) with no color carried
over from the previous theme; then start/stop a screen recording and
toggle airplane mode — the supervisor must survive both pokes
(`pgrep -lx nomarchy-waybar` still lists it).
## Latitude 5310 / 5410 only
- [ ] **v1 QA batch on-hardware pass** (583708d batch was QEMU-verified) —
general smoke before the next `main → v1` promotion (broader than
the 2026-07-10 session; include theme/ISO/greeter leftovers).
## T14s only
- [ ] **Webcam IR-hide end-to-end on Nomarchy** — installer detects the
RGB+IR pair, bakes `hardware.camera.hideIrSensor`; `wpctl status`
shows one colour source; an app picker lists one camera; Howdy-style
direct `/dev/video2` reads still work.
- [ ] **Portal/Flatpak libcamera IR (b)/(c)** — after #71 docs: on hardware,
confirm a Flatpak/portal picker still lists the internal IR node;
only then investigate (b) WirePlumber libcamera GREY-only monitor
rule or (c) libcamera/udev-layer hide. Do not ship either without a
live dual-sensor check (see HARDWARE.md §7 / ROADMAP § Webcam).

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,161 +0,0 @@
# The loop — autonomous iteration protocol
How an AI agent works on Nomarchy unattended. One **iteration** = pick one
task, do it, verify it, commit it, record it. The protocol is
harness- and vendor-agnostic; the same iteration works under any of:
- **An interactive self-paced loop** in any agent harness (e.g. `/loop`
in Claude Code) — the agent iterates until stopped.
- **Headless** (a one-shot CLI invocation, cron/systemd-timer) — one
invocation runs one iteration (or a small fixed number) and exits.
- **A fresh manual session** — a human says "do a loop iteration"; the
files below carry all the state, so any session can pick up where the
last left off.
All loop state lives in this directory, git-tracked. There is no state
outside the checkout (the distro's own philosophy, applied to its agents).
## The files
| File | Role | Who writes it |
|---|---|---|
| `GOALS.md` | North star + quality bars + non-goals | Human (agents propose edits) |
| `BACKLOG.md` | Prioritized task queue (NOW/NEXT/LATER/PROPOSED/DECISIONS) | Both — see its header rules |
| `JOURNAL.md` | Append-only iteration log | Agents |
| `MEMORY.md` | Curated durable lessons/gotchas | Agents (curated, not append-only) |
| `HARDWARE-QUEUE.md` | Pending on-hardware checks only Bernardo can run | Agents append, human checks off |
| `CONVENTIONS.md` | Repo/design conventions to follow while coding | Human (agents propose edits) |
Instructions live next to the state: `VERIFICATION.md` (enforcement),
`DELEGATION.md` (tiers/roles/economy), `GOALS.md`, `THEME-DESIGN.md`.
## Model & token economy
Spend expensive tokens on judgment, not mechanics. Tiers, roles, and the
full delegation rules are in **`DELEGATION.md`**; the loop-specific
habits:
- **Plan and reason on the strong model.** Orientation, task selection,
design, debugging, Nix eval semantics, verification judgment, and
anything that would land in a commit unreviewed stay with the
frontier-tier model running the loop.
- **Delegate mechanical subtasks down.** When a subtask is fully
specified and needs no design judgment — grep/audit sweeps,
README-option-table reconciliation, a repeated edit applied across
files, summarizing long logs or check output — hand it to a
light/standard-tier subagent per `DELEGATION.md`. The strong model
writes the spec, reviews the result, and owns the commit.
- **Read narrowly.** Step 0's list is the whole orientation read (the
*last 35 entries* of the journal, never the full file). Read large
files by section, don't re-read what's already in context, and tail
build/check logs instead of dumping them.
- **Write tersely.** Journal entries follow the template and no more;
commit bodies state what/why/tier, not a narrative.
- **Headless runners** may run whole low-stakes iterations (QA sweeps,
docs-drift passes) on a cheaper model; iterations touching
`modules/` or `pkgs/` behavior keep the strong model.
## One iteration, step by step
### 0. Orient
1. Read `GOALS.md`, `CONVENTIONS.md`, `MEMORY.md`, the **last 35 entries**
of `JOURNAL.md`, and `BACKLOG.md`. If the top task is product-shaped
(UX, release bar, day-2 confidence), also read the matching section of
**`docs/VISION.md`** — do not invent work from VISION; only execute
BACKLOG items. Map of docs vs agent state: `docs/README.md`,
`agent/README.md`.
2. `git pull --ff-only` (skip silently if offline). Confirm you are on
`main` with a clean tree. **A dirty tree you didn't create → stop and
report; never stash or discard someone else's work.**
3. Sanity baseline: if the last journal entry reports a red
`nix flake check`, or you have any reason to suspect breakage, run
`nix flake check --no-build` first. **A red baseline preempts the
backlog — fixing it *is* this iteration's task.**
### 1. Pick exactly one task
- Take the **topmost actionable** item: NOW before NEXT; never LATER
unless NOW and NEXT are empty or all blocked.
- *Actionable* means: not `[blocked:hw]` (those wait in
`HARDWARE-QUEUE.md`), not `[human]` (decisions), and small enough to
finish + verify in one iteration. If the top item is too big, **split
it in BACKLOG.md** (that edit is part of the iteration) and take the
first slice.
- Never implement anything from **PROPOSED** — those await human triage.
- If nothing is actionable, do a **QA sweep** instead: run the full check
suite, hunt drift (README option tables vs the live `nomarchy.*`
surface, template drift, dead code), deepen a VM test, or research and
write up a PROPOSED item. An iteration that only improves the backlog
is a valid iteration. If even that yields nothing, journal it and stop
— do not manufacture churn.
### 2. Work
- Keep the diff focused on the task. Unrelated fixes you trip over become
PROPOSED/NOW entries, not scope creep.
- Mechanical, fully-specified sub-steps go to a cheaper model
(see *Model & token economy* above); design and review stay here.
- Follow `CONVENTIONS.md`. Match the surrounding hand-formatting; never
run a formatter.
- New gotcha discovered the hard way → one line in `MEMORY.md` now, while
it's fresh.
### 3. Verify — the ladder
Climb as high as the change warrants and your environment allows; **record
the tier reached** in the commit body and journal entry.
| Tier | What | When required |
|---|---|---|
| **V0** | `nix flake check --no-build` (+ `bash -n` / `py_compile` for scripts) | Every change, no exceptions |
| **V1** | Build the touched output: `system.build.toplevel`, the HM generation, the ISO, or the package | Anything beyond docs/comments |
| **V2** | VM: a `checks.*` runNixOSTest (add one if the change is guardable), or boot `tools/test-live-iso.sh` / `tools/test-install.sh` | Behavioral changes — services, boot, installer, session |
| **V3** | Real hardware | Cannot be done by the agent → append to `HARDWARE-QUEUE.md` with exact test steps |
The honesty rule governs: a visual/interactive change verified only to V1
is **not done** — it ships as "V1-verified, V2/V3 pending" with the pending
check queued. Prefer *adding a permanent `checks.*` test* over a one-off
manual VM poke when the behavior is testable headlessly (see MEMORY.md for
the reusable recipes).
### 4. Commit + push
- Style: match the log — `feat(scope): …`, `fix(scope): …`,
`test(scope): …`, `docs(scope): …`. Body explains what/why + the
verification tier reached and what remains.
- Include the `agent/` bookkeeping updates (backlog/journal/memory/queue)
**in the same commit** as the change they describe.
- Commit directly on `main` and `git push` (Bernardo's standing workflow).
- **Never:** force-push; touch the `v1` branch or any branch/tag you
didn't create; commit secrets or binaries; run `nix flake update`
unless the task is explicitly a lock bump; delete themes, wallpapers,
or user-facing assets without the backlog saying so.
### 5. Record
1. Mark the task in `BACKLOG.md` (move to its ✓ line or delete, per that
file's rules).
2. Append a `JOURNAL.md` entry (template in that file).
3. Queue any V3 checks in `HARDWARE-QUEUE.md`.
4. **Sync sweep.** Grep the item's number and feature name across
`agent/` and `docs/` and update or delete every cross-reference the
ship made stale: the PROPOSED pitch that spawned it (and BACKLOG's
v1.0 pointer), HARDWARE-QUEUE entries it supersedes, ROADMAP/README/
docs mentions of the old behavior. While there, prune checked-off
`[x]` HARDWARE-QUEUE entries whose outcome is already recorded
(journal/ROADMAP — git history is the archive). A shipped item must
leave no stale pointer behind; the sweep rides in the same commit.
### 6. Pace (self-paced runners only)
Under `/loop`, continue to the next iteration while tasks remain
actionable and checks stay green. Stop the loop when: nothing is
actionable, the same task has failed twice (journal the failure analysis
and mark the item `[stuck]`), or a `[human]` decision blocks everything
remaining.
## Stop-and-escalate conditions (any runner)
Write a journal entry + a BACKLOG note, then stop, when:
- A fix would require touching `v1`, force-pushing, or a nixpkgs release
jump.
- The working tree contains uncommitted work you didn't create.
- A task turns out to need a design decision Bernardo hasn't made → move
it to **Decisions** in BACKLOG.md with the options laid out.
- Two consecutive iterations failed on the same task (`[stuck]`).
- Anything would delete or rewrite user data, git history, or the state
file schema in a non-backward-compatible way.

View File

@@ -1,219 +0,0 @@
# Memory — durable lessons, learned the hard way
Curated, not append-only: one line per fact, newest at the top of its
section; delete entries that stop being true. Details usually live in a
docs/ROADMAP.md decision record — pointer given as (§ item). Add a fact
here the moment a debugging session teaches you something a future
iteration would otherwise rediscover.
## Testing & VM recipes
- **Doctor float V2:** `THEME=<slug> nix build --impure -f tools/doctor-float.nix`
— softGL Hyprland, `nomarchy-menu doctor`, asserts
`class=com.nomarchy.doctor` + `floating` + centered midpoints +
screenshots (Kitty `--class=…`). SoftGL may still struggle with a GPU
terminal; size can ignore percent windowrules if client geometry wins.
- **theme-shot softGL may not start Kitty** — `btop.png` is best-effort
(usually identical to desktop). Guest asserts on
`~/.config/btop/themes/nomarchy.theme` prove baking; the TUI look is
hardware/GL tier (HARDWARE-QUEUE).
- **Waybar `custom/doctor` tripwire:** status helper must invoke
`nomarchy-doctor` by **absolute store path** (waybar's env can miss
system PATH → `command -v … || exit 0` self-hides forever); empty
`"text"` also self-hides; strip ANSI before packing the tooltip; use
signal 10 + `format = "{}"`. theme-shot asserts class:bad + glyph and
pokes RTMIN+10 before the desktop shot.
- **tuigreet dies silently under runNixOSTest** (even bare, no theme
flag: greetd sits as "(greetd)" with no child, nothing in the
journal — its stderr goes to the VT) — nixpkgs' own greetd test uses
agreety instead. Greeter *rendering* is interactive-ISO/hardware
tier; don't burn another session on a checks.greeter VM test.
- In VM tests `pgrep -f PATTERN` can match the test backdoor's own
`bash -c` wrapper (the pattern is in its cmdline) — use `pgrep -x`
or a `[t]uigreet`-style bracket pattern.
- **Don't default a "timer/session" feature to V3 — most of it is VM-testable.**
A scheduled/session behaviour usually decomposes into a *generic* step
already covered elsewhere (e.g. `home-manager switch`, exercised by every
theme apply) and a *specific* decision (which theme, when). Stub the generic
step (`NOMARCHY_REBUILD=<marker>` for theme-sync) and **simulate time by
moving the VM clock** (`date -s`, `timedatectl set-ntp false`, `time.timeZone
= "UTC"`), then assert the decision + state change headlessly. `checks.auto-theme`
does exactly this for #79's sunset/sunrise. Only the literal
timer-fires-on-`OnCalendar` is truly on-hardware, and `systemd-analyze
calendar` validates that schedule. (I first mis-framed #79 as V3 — it's V2.)
- **`writeShellScriptBin` scripts run `set -euo pipefail`** (nomarchy-doctor,
the menu, lifecycle CLIs). So a **no-match `grep` inside `$(…)`** (grep exits
1 → command-sub fails → abort) and a **standalone `cond && action`** (false
cond → abort) both kill the script mid-run — the tell is output that stops
before the final/verdict line with no error. Guard: `… | grep … || true`
inside `$()`, `cmd 2>/dev/null || echo 0` for captures, and `if` instead of
`&& action`. (#77 doctor hibernate section; caught by the checks.doctor VM
test on first run.)
- A checks.* fixture CANNOT be a writeText/toFile state file read at
eval time ("path … is not valid" — flake check's eval store won't
realise it): extract the logic into a pure importable file and
unit-test THAT (monitor-rules.nix / checks.display-profiles is the
pattern).
- **Hibernation reference (Latitude / Newton, BACKLOG #76):** LUKS whole
root BTRFS; `@swap``/swap`; file `/swap/swapfile`;
`boot.resumeDevice` = LUKS root UUID; `resume_offset` from
`btrfs inspect-internal map-swapfile -r`. Swap is encrypted with root
(not a cleartext partition). No zram on that box yet — zram is additive
for live pressure only. Installer already creates this when swapSize>0.
- CI (`.gitea/workflows/check.yml`) is **eval-tier only** (standing
decision 2026-07-10): act_runner docker-compose on the Gitea VPS; no
KVM there. Full VM suite is BACKLOG **FUTURE #20**, not NEXT — needs a
separate nix+/dev/kvm runner. Container gotchas are in the workflow
header (single-user Nix + nixbld users, `sandbox=false` for Stylix
IFD, Nix pinned 2.31.5 vs lazy-trees, no JS actions past node20).
- The Gitea instance is **1.25.4**`on: schedule` workflows are
supported; bump.yml assumes the Actions token can push to `main`
(standard Gitea behaviour, but unconfirmed until the first run lands).
- The git server is **Gitea** (gitea/act_runner via docker-compose), NOT
Forgejo — workflows are read from `.gitea/workflows/` (or `.github/`),
never `.forgejo/workflows/` (a whole push cycle was lost to that).
- Reusable headless VM harness: `checks.*` via runNixOSTest — existing
examples to crib from: `distro-id` (boots + `switch-to-configuration
dry-activate`), `hardware-toggles` (kernel cmdline/PAM assertions),
`battery-charge-limit` (fake Mains adapter via `test_power`, real udev
event burst while the oneshot is active; clean inactive result plus an
`InvocationID` change proves coalesced AC re-apply). AC udev hooks for a
settling oneshot must use `start`, never `restart`: USB-C docks emit event
bursts and restarts SIGTERM the in-flight pass into `start-limit-hit`.
- Themed-desktop screenshots work headlessly: software-GL Hyprland
(`LIBGL_ALWAYS_SOFTWARE` on virtio-gpu) + `machine.screenshot()` QMP
dump — prototyped 2026-06-19, kept as the fallback for theme previews
(§ Visual theme picker).
- Hyprland needs guest GL (`virtio-vga-gl`, `gl=on`) in interactive QEMU
or the session won't start; black screen ≈ missing GL
(docs/TESTING.md § gotchas). Kitty is the sole terminal (no Ghostty).
- No KVM = slow, not broken; don't read slowness as failure.
## Known-broken / watchlist
- **btrfs-assistant "segfault" was unprivileged-only** (re-diagnosed
2026-07-04): libbtrfsutil's unprivileged subvolume iteration crashes on
btrfs-progs 6.17.1 (upstream-fixed after); **as root it works**, and the
pkexec launcher runs it as root. The real distro bug was **no polkit
agent in the session** (every pkexec failed silently) — hyprpolkitagent
now ships (hyprland.nix exec-once). `checks.snapshot-gui` guards the
root path. Lesson: before "app X is broken", check WHO it runs as — and
whether polkit prompts can render at all (§ Snapshot browse/restore).
- **NixOS release bump is a trap:** the discarded attempt
(branch deleted 2026-06-22) hit a Hyprland OOM blocker; a redo is a
deliberate `v2`, never part of routine lock bumps.
- `state.json` is git-tracked inside an 86 MB flake tree, so every
state write re-copies the source before eval — the wallpapers-artifact
split (BACKLOG LATER) is the decided fix (§ Faster switches).
- **Friendly theme-state load** (`modules/state-read.nix`, #66):
`builtins.tryEval` does **not** catch `readFile`/`fromJSON` failures —
gate with `pathExists` + empty/non-object checks before `fromJSON`.
Subtle JSON syntax errors still surface from nlohmann (line/col);
field schema stays in `theme.nix`. mkFlake must `builtins.seq` the
check onto the whole return set or lazy attr access skips it.
## Design invariants
- **Dock transitions are ordered safety operations (#100):** dock in one
`hyprctl --batch` (external on → every internal workspace moved → focus
external → internal off); undock enables internal *before* moving anything.
The Hyprland watcher, not a shell-pipeline subshell, owns a low-level
`handle-lid-switch` inhibitor until the lid is physically open; startup
cleans a validated stale process group. HDMI availability may appear only
as `change:sink`, so fresh `monitoradded` is the intent boundary for the
settled PipeWire/WirePlumber reprobe + sink pick; generic audio changes must
not override a manual in-dock speaker choice.
- **Waybar status is never color-only** (item 28 sweep, iteration #69):
every status module must distinguish its states by SHAPE (glyph) or
presence (self-hide), never color alone — good/warn/bad collapse under
color-blindness. When adding a state, give it a distinct glyph or gate
the module on it; a new `class` that only recolors an existing glyph is
a regression. Suppressed notification states (DND *and* app-inhibited)
all use the bell-off glyph + @muted.
- **Identity themes are not traffic lights** (#69): white, vantablack,
lumon, hackerman, matte-black, miasma — monochrome / mono-hue / earthy
status by design. `audit-theme-design.py` tags their hue/CVD/ANSI-family
findings `[identity]`; do not "fix" them into R/Y/G.
- **Import hierarchy ≠ ANSI** (#70): `import-palettes.py` must not set
surface==overlay when color0==color8; light color0 is often ANSI black
(not a chip). Roles are first-class — never bulk-reimport shipped JSON
without a hierarchy pass.
## Gotchas (cost a debugging session once)
- **#127 forensics/method lessons (three wrong diagnoses' worth):** drive the
real code path or measure nothing (a hand-run `dpms off` has no wake path
watching it, so "input didn't wake it" was guaranteed); do not read output
lists as lid positions — `/proc/acpi/button/lid/*/state` is the evidence
(a reload can light a panel inside a shut lid, #148); and anchor forensics
on the right boot — an *unclean boot end* (journald corruption + dirty bit
on the next boot) is itself the incident marker, and `--list-boots` comes
before any grep.
- **OVMF exposes /dev/fd0 as TYPE=disk (#112):** disk picker must exclude
`/dev/fd*` and tiny sizes; sort largest-first or blind Enter picks floppy.
- **Live ISO offline theme switch (#113):** only the *default/pinned* HM
generation is offline-safe; other presets may try to build the world —
document the contract and fail with a network-oriented message.
- **Installer HM pre-activate needs XDG_RUNTIME_DIR (+ session bus) (#123):**
`runuser … activate` inside `nixos-enter` has no user session. Without
`mkdir -p /run/user/$UID` owned by the install user and
`XDG_RUNTIME_DIR` (prefer `dbus-run-session -- activate`), dconf dies
with Permission denied and the bake aborts mid-way — first boot looks
unthemed / missing nm-applet even though the system installed.
- **ISO filename is `image.baseName` (#125):** volumeID alone does not rename
`result/iso/…`; force `image.baseName` to `nomarchy-…` or the artifact
stays `nixos-live-….iso`.
- **Install flake ref must match the ISO branch (#124):** while `v1` lags
`main`, seed `?ref=main` (and compose-lock original) from main-built ISOs;
`NOMARCHY_FLAKE_URL` must actually rewrite `inputs.nomarchy.url` (was set
but unused until #124).
- Gum `filter` returns unmatched typed text by default; catalog-only pickers
require `--strict` plus an independent exact-membership validation boundary.
- Waybar `layer: top` renders above **even real-fullscreen windows** — the
bar draws over a fullscreen video. `layer: bottom` lets the fullscreen
surface cover it while the exclusive zone still reserves the bar's space
in normal tiling (trade-off: floating windows can now overlap the bar
strip). Set in both waybar.nix and every whole-swap jsonc (item 30).
- Hyprland binds match the exact modmask: a shifted keysym (`question`)
needs SHIFT in `mods` or the bind never fires — the keypress falls
through to the focused window (§ item 26; caught on hardware, invisible
to eval-tier tests).
- Never kill a Wayland session-lock client (hyprlock): its crash
failsafe drops to a tty instead of unlocking (§ Hibernate
double-unlock).
- rofi `element-icon size` is one value = a square cell; `WxH` silently
collapses and non-square icons letterbox — pre-crop images square at
build (§ Visual theme picker).
- WirePlumber 0.5 monitor rules can only early-match `device.api`;
`device.product.name` etc. bind *after* the rule runs — surgical
libcamera scoping is impossible (§ Webcam).
- `hyprctl switchxkblayout` is a *global* layout flip; per-device isolation
needs `device[<name>]:kb_layout` keywords (§ Keyboard layouts).
- Waybar's clock captures the timezone at construction — a zone change
needs SIGUSR2 (watcher in `timezone.nix`) (§ Automatic timezone).
- Waybar `persistent_workspaces` (underscore) is dead syntax silently
ignored; the hyphen form is honoured and renders phantom workspaces
(§ Waybar shows non-existent workspaces).
- GTK4/libadwaita/Qt6 read light/dark from the portal's
`org.freedesktop.appearance color-scheme` (dconf), not Stylix polarity
(§ GTK/Qt ignore the theme's mode).
- Update order matters downstream: `nomarchy-pull` (lock) then
`nomarchy-rebuild` then `nomarchy-home`, or desktop changes are silently
skipped against the old
lock (README § 3).
- Hyprland 0.55 renames `stayfocused``stay_focused` (and similar
underscore effects); `stayfocused 1` is `invalid field type` at parse
(§ polkit workspace rules, 2026-07-10 hardware).
- Never gate a safety listener behind the optional feature it also serves:
the display menu existed with no profiles while its blackout rescue did
not. Rofi defaults to mouse-pointer output (`monitor=-5`), which is stale
in clamshell mode; use focused output (`-1`) for keyboard-launched UI.
- Hyprland 0.53 rewrote window rules: `windowrulev2` is a hard error and
the old rule-first `float, class:^…$` no longer parses — both surface a
red config-error banner on the default desktop. Hyprlang legacy form is
now `<effect> <value>, match:<prop> ^…$` (e.g. `float 1, match:class ^…$`);
effects carry a value, matchers take `match:` (§ windowrule migration).
- grub `loadfont`s every `.pf2` in a theme dir — reuse a bundled DejaVu
rather than shipping fonts (§ Distro branding).
- Agent instructions live vendor-neutrally in `agent/` (VERIFICATION,
DELEGATION, THEME-DESIGN; entry AGENTS.md) — `.claude/` is a thin
adapter (permissions + subagent defs only; skills were removed
2026-07-11). Never `git add -A` blindly: check `git status --short`
for genuine strangers first (`settings.local.json`, harness-dropped
files) and commit with explicit pathspecs (§ loop hygiene).

View File

@@ -1,54 +0,0 @@
# Agent instructions + loop state
Everything an AI agent needs to work on Nomarchy, vendor-neutral and
git-tracked. Protocol: **[LOOP.md](LOOP.md)**. Entry point for every
harness: repo-root **[AGENTS.md](../AGENTS.md)**.
## Instructions (how to work)
| File | Who writes | Role |
|------|------------|------|
| [LOOP.md](LOOP.md) | Human | One-iteration protocol (orient → pick → work → verify → commit → record) + the V0V3 ladder |
| [VERIFICATION.md](VERIFICATION.md) | Human (agents propose) | Enforcement: preflight, honesty rules, visual protocol, hardware-blocked checks, reporting |
| [DELEGATION.md](DELEGATION.md) | Human (agents propose) | Capability tiers, scout/runner roles, token economy, parallel fan-out |
| [GOALS.md](GOALS.md) | Human (agents propose) | Pillars, quality bars, non-goals |
| [CONVENTIONS.md](CONVENTIONS.md) | Human (agents propose) | How to write code/menu/state while shipping |
| [THEME-DESIGN.md](THEME-DESIGN.md) | Human (agents propose) | Theme/visual design instructions |
## State (what's happening)
| File | Who writes | Role |
|------|------------|------|
| [BACKLOG.md](BACKLOG.md) | Both | **Prioritized queue** — only executable work list |
| [JOURNAL.md](JOURNAL.md) | Agents | Append-only iteration log (read last 35 entries; older → [JOURNAL-ARCHIVE.md](JOURNAL-ARCHIVE.md)) |
| [MEMORY.md](MEMORY.md) | Agents | Curated durable gotchas |
| [HARDWARE-QUEUE.md](HARDWARE-QUEUE.md) | Agents append, human checks | On-hardware V3 tests only Bernardo can run |
## Product / design docs (not a queue)
| File | Role |
|------|------|
| [../docs/VISION.md](../docs/VISION.md) | v1.0 product themes — agents slice into BACKLOG PROPOSED |
| [../docs/ROADMAP.md](../docs/ROADMAP.md) | Design history + shipped log |
| [../docs/README.md](../docs/README.md) | Full docs map |
## Harness adapters (vendor-specific, thin)
Shared content never lives in an adapter — adapters only register/route
into the files above, in whatever format their harness requires.
| Path | Harness | Role |
|------|---------|------|
| [../AGENTS.md](../AGENTS.md) | any | Entry point (`CLAUDE.md` is a symlink to it) |
| [../.claude/settings.json](../.claude/settings.json) | Claude Code | Tool permissions |
| [../.claude/agents/](../.claude/agents/) | Claude Code | `nomarchy-scout` / `nomarchy-runner` role defs (contracts in [DELEGATION.md](DELEGATION.md)) |
Do **not** put backlog items, vision text, or policy under an adapter
directory — it is not shared with other agent runners.
## Rules of thumb
1. **Execute** from BACKLOG only (NOW → NEXT; never PROPOSED without human triage).
2. **Orient** with GOALS + CONVENTIONS + MEMORY + last journal + BACKLOG; when the task is product-shaped, also read the relevant **VISION §**.
3. **Record** lasting design in ROADMAP ✓ when something ships that future humans should know; delete the BACKLOG line.
4. **v1 branch** is human-only — never advance from an agent session.

View File

@@ -1,49 +0,0 @@
# Theme design — instructions for visual work
For creating, updating, refining, or troubleshooting Nomarchy themes and
visual design. Act as an experienced UI/UX designer and Linux ricing
expert for Wayland environments: themes here must be cohesive,
intentional, and visually striking — aesthetics are a load-bearing
feature of this distro (`agent/GOALS.md` pillar 4).
## Context & architecture
All theme data lives in `themes/` at the repo root — one JSON palette per
theme plus per-app assets (whole-swap `waybar.jsonc`/CSS, btop themes,
wallpapers). Before generating any new configuration, read the existing
files there: learn how current themes are structured, how the syntax is
formatted for each application, and how they integrate into the NixOS/HM
modules. Always match the established pattern — there is no second
theming pipeline (`agent/GOALS.md` non-goals), so a new visual surface
consumes the palette from the state JSON, never a side file.
## Design responsibilities
1. **Holistic design:** a theme spans Hyprland (borders, shadows,
animations), Waybar, Kitty, btop, fastfetch, rofi, and wallpaper —
one coherent identity, no unthemed corner.
2. **Color theory:** create or adapt advanced palettes. Draw inspiration
from established aesthetics (Everforest, Nord, Gruvbox) or r/unixporn
trends, but innovate. Ensure harmony between background, foreground,
accents, and warning/error colors.
3. **Typography & iconography:** pair UI and monospace fonts and icon
themes to the specific vibe of the palette.
4. **Accessibility:** high contrast for text readability; status
information is never color-only (see `agent/MEMORY.md` design
invariants — glyph/shape carries state, and identity themes are
deliberately not traffic-lights).
## Process
1. Read `themes/` to understand the current structure.
2. State the "vibe", the primary palette (hex codes), and typography
choices of the proposed design before implementing.
3. Implement by generating or updating files within the `themes/`
structure, following `agent/CONVENTIONS.md` (Waybar parity rule:
whole-swap `waybar.jsonc` files stay in sync with the generated
config).
4. Verify per `agent/VERIFICATION.md` §3 — scripted checks first
(`tools/check-theme-contrast.py`, `tools/audit-theme-design.py`), then
headless before/after screenshots you actually view, under at least
two themes. New themes are imported via `tools/import-palettes.py` and
round-tripped through the theme switcher.

View File

@@ -1,181 +0,0 @@
# Verification — the enforcement rules
Nomarchy's promise to its user: a rock-stable, fully functional, beautiful
workstation that is reproducible, easy to recover, and never requires the
user to become a Nix expert. Every rule below exists to protect that
promise. A change that works but degrades stability, aesthetics, or
user-simplicity is a regression, not a feature.
This document is an **enforcer**, not the workflow itself: the ladder and
iteration protocol live in `agent/LOOP.md`, VM instructions and gotchas in
`docs/TESTING.md`. It applies to **every** change to this repo — features,
fixes, theming, module changes, lock bumps, docs, backlog grooming.
"Small" or "obvious" changes are exactly where verification gets skipped,
so they trigger it too. If this file and those docs ever disagree, fix the
discrepancy in the same or a follow-up commit so they can't disagree
twice.
## 1. Preflight (once per session)
Before starting work, establish what verification tier this environment
can reach, so you never promise verification you can't deliver:
1. Linux x86_64 host? `/dev/kvm` present and readable?
2. Enough free disk for an image/ISO build (multi-GB)?
3. Network access for a cold Nix store?
If the environment cannot reach V2 (no KVM, no disk, etc.): say so
immediately, do the V0/V1 work honestly, mark the change **"V2 pending"**
exactly as you would mark a hardware-blocked change "V3 pending" (§4),
and stop short of claiming the change is done. Never simulate, guess, or
describe what a VM test "would" show.
## 2. The verification ladder (enforcement rules)
Climb the V0V3 ladder as defined in `agent/LOOP.md`. Four non-negotiable
enforcement rules on top:
1. **V2 is mandatory for anything user-visible.** If a user of the
installed system could perceive the change — behavior, layout, colors,
keybinds, timing, error messages — it must be exercised in the local
VM before commit. Docs-only, comment-only, or agent-notes changes may
stop at the tier LOOP.md assigns them; user-visible changes may not.
2. **Every "done" report names the tier reached and shows the evidence.**
Evidence means: the command run and its relevant output, the checklist
items exercised, and for visual work the screenshots viewed (§3). "It
builds" is a V1 claim, not a V2 claim. Never let a report imply a
higher tier than was actually reached.
3. **A failed or flaky test is a result, not an obstacle.** Distinguish
real failures from environment flakes using the known-gotchas section
of `docs/TESTING.md` (e.g. no-KVM slowness, missing guest GL). If you
cannot confidently classify a failure, report it as unresolved — do
not retry until green and report only the green run.
4. **VM runs are headless and unattended.** Use the repo's headless
harness — `tools/test-live-iso.sh` and `tools/test-install.sh` for
boot/install runs, `tools/vm/qmp.py` for programmatic VM control and
`tools/vm/vncshot.py` for screen capture — never a graphical VM window
or any flow that needs a human at the console. The human is not part
of the test loop: do not pause mid-run to ask them to look at the VM,
click something, or confirm what is on screen. A run must complete on
its own and leave auditable artifacts behind (logs, serial console
output, exit codes, screenshots), with every wait bounded by a timeout
so a hang becomes a recorded failure instead of a stalled session.
Prefer scripted assertions (process up, file exists, service/D-Bus
state, the checks in `tools/`) over eyeballing; where judgment is
genuinely needed — visual quality — *you* view the captured
screenshots (§3), not the human. The human reviews evidence in the
final report, never the live run.
### Regression scope after a change
Re-running the full checklist for every change wastes VM time; running
nothing invites regressions. Default rule:
- Always: the session-sanity items (boot to session, bar renders).
- Plus: every checklist item touching the layer you changed.
- Plus: the theming end-to-end item whenever theming plumbing changed,
even indirectly (palette generation, symlinks, reload hooks).
- Lock bumps and toolchain changes: run the full checklist — their blast
radius is unknowable by construction.
## 3. Visual verification protocol
Visual quality is a core feature of Nomarchy, so "it probably looks fine"
is never verification. A visual/UI change is not V2-verified until all of
the following are true:
1. **Before/after screenshots** of the changed surface were captured
headlessly — `tools/theme-shot.nix` for reproducible theme renders,
`tools/vm/vncshot.py` (driven via `tools/vm/qmp.py`) for captures from
a running VM. No VM window, no human interaction. Capture the "before"
from the base branch or prior generation, not from memory.
2. **Scripted checks first**: run `tools/check-theme-contrast.py` and
`tools/audit-theme-design.py` against the affected theme(s) before any
eyeballing — machine-checkable legibility/design violations should
never survive to the judgment stage. Use `tools/vm/gap-analysis.py`
where it applies.
3. **Two themes**: repeat the "after" capture under at least two themes,
one with a generated palette and one whole-swap theme (e.g.
summer-night). These exercise different code paths in the bar/launcher
theming; a change that looks right under one can silently break the
other.
4. **You actually viewed the images** — open the screenshot files and
look at them. State concretely what you inspected: alignment, spacing,
contrast/legibility against the palette, icon rendering, no clipped or
overlapping elements, and that the change looks intentional next to
the "before".
5. Keep the screenshots in the run's working area and reference their
paths in the report, so the human can audit the same evidence.
If the VM cannot render the surface faithfully (known GL/compositor gaps
in the guest — see `docs/TESTING.md`), that specific visual aspect is
hardware-blocked: verify everything the VM *can* show, and queue the rest
per §4.
## 4. Hardware-blocked checks
Some checks genuinely require real hardware (GPU behavior, multi-monitor
hotplug, audio devices, power/suspend, firmware). For those:
1. Add an entry to `agent/HARDWARE-QUEUE.md` with: what changed, **exact**
reproduction steps a human can follow verbatim, the expected
observation (what "pass" looks like), and the commit hash once known.
2. Mark the commit body **"V3 pending: <one-line summary>"**.
3. Say it plainly in your report. A hardware-blocked check is not a
failure and not something to hide — hiding it is the failure.
4. When the human reports back, close the queue entry in the next commit
and record the outcome; if it failed on hardware, that's a new bug at
the top of the backlog.
Do not use the hardware queue as an escape hatch: if a check *can* be
done in the VM, it must be. "The VM is slow" does not qualify.
## 5. Maintenance work
Maintenance follows the same ladder:
- **Flake lock bumps**: treat as maximum-blast-radius changes. Build,
boot the VM, run the full regression checklist, and do a visual
spot-check of the session (themes can shift with upstream package
changes). Never merge a lock bump on "it evaluates". Since #134 the
lock carries **two channels** (the release pin and the
`nixos-unstable` pin feeding `unstable.*`) — a bump moves both, and
this checklist applies to both.
- **Theme imports / new themes**: import via `tools/import-palettes.py`,
then the full §3 visual protocol; additionally verify the theme-switch
round trip (into the new theme and back out).
- **Docs drift**: run `tools/check-option-docs.py` after any change that
adds or modifies options, and fix drift in the same commit as the code
change that created it. Doc-only fixes are V0 — but verify any command
you document by actually running it.
- **Backlog grooming / agent-notes**: V0; keep entries consistent with
the conventions in `agent/`.
## 6. Guarding the philosophy
Before committing, check the change against the distro's promises
(`agent/GOALS.md` is the full statement):
- **User is not a Nix expert.** If the change requires the user to write
or read Nix to use the feature day-to-day, redesign it. Configuration
the user touches must stay in the simple, documented surface the repo
defines.
- **Rock-stable and recoverable.** Prefer boring, reproducible mechanisms
over clever ones. Any change that could break boot or the session must
have an obvious rollback story (NixOS generations count, but say so).
- **Aesthetics are load-bearing.** A functionally correct but visually
regressive change fails review by definition — that's what §3 is for.
When a requested change conflicts with these promises, stop and raise the
conflict instead of implementing it quietly.
## 7. Reporting format
End every unit of work with a short report containing:
1. What changed (one paragraph, plain language).
2. Verification tier reached, with evidence (commands + key output,
checklist items run, screenshot paths viewed).
3. Anything pending: "V2 pending" (environment) or "V3 pending"
(hardware, with queue entry reference).
4. Follow-ups added to the backlog, if any.

View File

@@ -0,0 +1,109 @@
#!/usr/bin/env bash
set -euo pipefail
# nomarchy-docs-keybindings
#
# Regenerates docs/KEYBINDINGS.md from the Hyprland binding files. Run from the
# repo root or anywhere — paths are resolved relative to this script.
#
# nomarchy-docs-keybindings # write to stdout
# nomarchy-docs-keybindings --out docs/KEYBINDINGS.md
#
# Source files in render order. Each entry is "<repo-relative path>|<title>".
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
sources=(
"core/home/config/nomarchy/default/hypr/bindings/utilities.conf|Utilities"
"core/home/config/nomarchy/default/hypr/bindings/tiling-v2.conf|Tiling"
"core/home/config/nomarchy/default/hypr/bindings/clipboard.conf|Clipboard"
"core/home/config/nomarchy/default/hypr/bindings/media.conf|Media keys"
"features/desktop/hyprland/config/bindings.conf|Apps & web shortcuts"
)
prettify_key() {
case "$1" in
code:10) echo "1" ;; code:11) echo "2" ;; code:12) echo "3" ;;
code:13) echo "4" ;; code:14) echo "5" ;; code:15) echo "6" ;;
code:16) echo "7" ;; code:17) echo "8" ;; code:18) echo "9" ;;
code:19) echo "0" ;;
XF86AudioRaiseVolume) echo "Volume Up" ;;
XF86AudioLowerVolume) echo "Volume Down" ;;
XF86AudioMute) echo "Mute" ;;
XF86AudioMicMute) echo "Mic Mute" ;;
XF86AudioPlay) echo "Play/Pause" ;;
XF86AudioStop) echo "Stop" ;;
XF86AudioNext) echo "Next Track" ;;
XF86AudioPrev) echo "Previous Track" ;;
XF86MonBrightnessUp) echo "Brightness Up" ;;
XF86MonBrightnessDown) echo "Brightness Down" ;;
XF86KbdBrightnessUp) echo "Kbd Brightness Up" ;;
XF86KbdBrightnessDown) echo "Kbd Brightness Down" ;;
XF86KbdLightOnOff) echo "Kbd Backlight" ;;
*) echo "$1" ;;
esac
}
trim() { sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//'; }
render_section() {
local file="$1" title="$2"
[[ ! -f "$repo_root/$file" ]] && return
local rows
rows=$(grep -E '^[[:space:]]*bind[a-z]*[[:space:]]*=' "$repo_root/$file" || true)
[[ -z "$rows" ]] && return
local body=""
while IFS= read -r line; do
# Strip the "bindXXX =" prefix.
local rhs="${line#*=}"
local mods key desc
IFS=',' read -r mods key desc _ <<<"$rhs"
mods=$(printf '%s' "${mods:-}" | trim)
key=$(printf '%s' "${key:-}" | trim)
desc=$(printf '%s' "${desc:-}" | trim)
[[ -z "$desc" ]] && continue # skip non-descriptive bindings
[[ -z "$mods" ]] && mods="—"
key=$(prettify_key "$key")
body+=$(printf '| %s | %s | %s |\n' "$mods" "$key" "$desc")
body+=$'\n'
done <<<"$rows"
[[ -z "$body" ]] && return
printf '\n## %s\n\n' "$title"
printf '_Source: `%s`_\n\n' "$file"
printf '| Modifiers | Key | Action |\n'
printf '| --- | --- | --- |\n'
printf '%s' "$body"
}
main() {
cat <<'HEADER'
# Nomarchy Keybindings
Auto-generated from the Hyprland binding files. **Do not edit by hand.**
Re-run the generator after changing any `bindings/*.conf`:
```bash
./bin/utils/nomarchy-docs-keybindings --out docs/KEYBINDINGS.md
```
`SUPER` is the Meta / Win key. `code:NN` keys (X11 digit keycodes) are
shown as the digit they correspond to. Media keys (`XF86Audio*`,
`XF86MonBrightness*`, …) are prettified.
HEADER
for entry in "${sources[@]}"; do
render_section "${entry%|*}" "${entry#*|}"
done
}
out=""
if [[ "${1:-}" == "--out" ]]; then
out="${2:?--out needs a path}"; shift 2
fi
if [[ -n "$out" ]]; then
main >"$out"
else
main
fi

276
bin/utils/nomarchy-docs-scripts Executable file
View File

@@ -0,0 +1,276 @@
#!/usr/bin/env bash
# Generator tolerates "no matches" exit codes from grep | sort.
# pipefail and -e off; -u stays.
set -u
# nomarchy-docs-scripts
#
# Regenerates docs/SCRIPTS.md from the repo state. Produces:
# 1. Header + status legend + regen instructions.
# 2. Table of every nomarchy-* script (location, callers, status).
# 3. Table of every menu entry in features/scripts/utils/nomarchy-menu
# (submenu, label, target command, status).
# 4. Snapshot list of orphaned references (called somewhere, no script).
#
# Status heuristic in Phase A:
# kept — file exists AND is called from at least one *.nix / *.conf /
# shell file outside its own directory.
# unused? — file exists but no caller found. Phase B decides delete-dead
# vs intentional public API.
# missing — referenced but no file. Phase B decides port-from-omarchy
# vs delete-dead vs stub-with-notify.
#
# nomarchy-docs-scripts # write to stdout
# nomarchy-docs-scripts --out docs/SCRIPTS.md
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "$repo_root"
# --- Inventory -------------------------------------------------------------
# Where scripts live, in render order.
declare -A loc_label=(
["features/scripts/utils"]="features/scripts/utils"
["core/system/scripts"]="core/system/scripts"
["themes/engine/scripts"]="themes/engine/scripts"
)
script_dirs=(features/scripts/utils core/system/scripts themes/engine/scripts)
# Build name → location map (associative array of basename → repo-relative dir).
declare -A script_loc
for dir in "${script_dirs[@]}"; do
[[ -d "$dir" ]] || continue
while IFS= read -r f; do
script_loc["$(basename "$f")"]="$dir"
done < <(find "$dir" -maxdepth 1 -type f -name 'nomarchy-*')
done
# Find every nomarchy-* token referenced anywhere outside the script dirs.
# (We exclude the script files themselves so they don't list themselves as
# their own caller.)
# File types we search for references. *.md catches docs and README;
# branding/hook/extension files have varied or no extensions.
# *.lua catches elephant providers; *.ini catches mako on-button-* hooks;
# *.desktop catches MimeType-registered URL handlers.
grep_includes=(
--include='*.nix' --include='*.conf' --include='*.sh' --include='*.md'
--include='nomarchy-*' --include='*.jsonc' --include='*.json'
--include='*.toml' --include='*.ini' --include='*.lua'
--include='*.desktop' --include='*.txt' --include='*.sample'
)
search_dirs=(core features themes installer hosts bin lib README.md)
# Files whose mentions of nomarchy-* are documentation about the scripts,
# not real callers. Excluded from caller discovery so they don't promote
# every script to `kept`.
self_refs=(docs/SCRIPTS.md docs/ROADMAP.md docs/AGENT.md)
ref_files_per_cmd() {
local cmd="$1"
local self_pattern
self_pattern=$(IFS='|'; echo "${self_refs[*]}")
grep -rlE "\\b${cmd}\\b" \
"${grep_includes[@]}" \
"${search_dirs[@]}" 2>/dev/null \
| grep -vE "^(features/scripts/utils|core/system/scripts|themes/engine/scripts)/${cmd}$" \
| grep -vE "^(${self_pattern})$" \
| sort -u
}
# All distinct nomarchy-* tokens we see anywhere in the repo.
# Final char must be alphanumeric — dropping trailing-dash matches like
# `nomarchy-pkg-` that come from glob references (`for c in nomarchy-pkg-*`).
# Restrict to grep_includes so binaries / tmpfiles don't pollute the set.
# The first `grep -vE` drops lines where `nomarchy-*` is a derivation /
# tmp file / sudoers basename / systemd unit / flake output / docker
# container identifier rather than a shell invocation.
# The second `grep -vE` is a token-level safety net for prefix-only
# tokens left over from wildcards/expansions (e.g. `nomarchy-pkg-*`).
all_refs=$(grep -rhE 'nomarchy-[a-z0-9]([a-z0-9-]*[a-z0-9])?' \
"${grep_includes[@]}" \
"${search_dirs[@]}" 2>/dev/null \
| grep -vE \
-e '(pname|name)[[:space:]]*=[[:space:]]*"nomarchy-' \
-e '/tmp/nomarchy-' \
-e '/etc/sudoers\.d/[^"[:space:]]*nomarchy-' \
-e 'nixosConfigurations\.nomarchy-' \
-e 'packages\.[^.]+\.nomarchy-' \
-e '\./result/bin/run-nomarchy-' \
-e 'mktemp[[:space:]]+[^|]*-t[[:space:]]+nomarchy-' \
-e '(TIMER_NAME|NOPASSWD_FILE|UNIT_NAME)=.*nomarchy-' \
-e 'docker[[:space:]]+[^|]*nomarchy-' \
| grep -oE 'nomarchy-[a-z0-9]([a-z0-9-]*[a-z0-9])?' \
| grep -vE '^(nomarchy-launch|nomarchy-brightness|nomarchy-cmd|nomarchy-pkg|nomarchy-restart|nomarchy-toggle|nomarchy-theme|nomarchy-webapp-handler|nomarchy-font-selector|nomarchy-theme-selector|nomarchy-wallpaper-selector|nomarchy-setup|nomarchy-refresh|nomarchy-scripts|nomarchy-system-scripts|nomarchy-theme-engine-scripts)$' \
| grep -vE '^(nomarchy-plymouth|nomarchy-sddm-theme|nomarchy-live|nomarchy-rev|nomarchy-windows)$' \
| sort -u)
# The second denylist covers identifiers whose ambiguity survives the line
# filter: `nomarchy-plymouth` / `nomarchy-sddm-theme` are Nix derivation
# names referenced as bare idents in `[...]` lists, `nomarchy-live` is an
# ISO label that shows up in comments, `nomarchy-rev` is `/etc/nomarchy-rev`
# (written by the ISO), and `nomarchy-windows` is a docker container name
# in compose heredocs.
# --- Render: header --------------------------------------------------------
main() {
cat <<'HEADER'
# Nomarchy Script & Menu Audit
Auto-generated table for [Pillar 3 of the roadmap](ROADMAP.md#3-pillar-script--menu-audit).
**Do not edit by hand.** Regenerate after script or menu changes:
```bash
./bin/utils/nomarchy-docs-scripts --out docs/SCRIPTS.md
```
The status column uses a Phase A heuristic — `kept` / `unused?` / `missing`.
Phase B (per-batch PRs) refines those into `port-from-omarchy`,
`delete-dead`, or `stub-with-notify` and updates the rows.
## Status legend
- `kept` — script exists and is called from somewhere outside its own directory.
- `unused?` — script exists but no caller was found. Could be dead, could be
intentional public API. Phase B triage decides.
- `missing` — referenced from code but no script file exists. Phase B triage
decides whether to port from Omarchy upstream, delete the caller, or stub
with `notify-send`.
- `port-from-omarchy` — Phase B verdict: lift the upstream Omarchy script,
rewrite for NixOS paths.
- `delete-dead` — Phase B verdict: remove and update callers.
- `stub-with-notify` — Phase B verdict: temporary `notify-send` stub.
HEADER
# --- Render: scripts table ----------------------------------------------
printf '## Scripts (%d)\n\n' "${#script_loc[@]}"
printf '| Script | Location | Callers | Status | Notes |\n'
printf '| --- | --- | --- | --- | --- |\n'
# Sort scripts by name.
for name in $(printf '%s\n' "${!script_loc[@]}" | sort); do
local dir="${script_loc[$name]}"
local callers status callers_str
callers=$(ref_files_per_cmd "$name")
if [[ -z "$callers" ]]; then
status='`unused?`'
callers_str='—'
else
status='`kept`'
# Trim caller list to 2 entries + count.
local count
count=$(printf '%s\n' "$callers" | wc -l)
if (( count > 2 )); then
callers_str=$(printf '%s\n' "$callers" | head -2 | paste -sd, -)
callers_str="$callers_str, +$((count - 2)) more"
else
callers_str=$(printf '%s\n' "$callers" | paste -sd, -)
fi
fi
printf '| `%s` | `%s` | %s | %s | |\n' \
"$name" "$dir" "$callers_str" "$status"
done
echo
# --- Render: missing references -----------------------------------------
printf '## Missing references\n\n'
printf 'Tokens grepped from `core/`, `features/`, `themes/`, `installer/`, `hosts/`, `bin/`, `lib/` that have no matching script file.\n\n'
printf '| Token | Referenced in | Status |\n'
printf '| --- | --- | --- |\n'
while IFS= read -r token; do
[[ -z "$token" ]] && continue
[[ -n "${script_loc[$token]:-}" ]] && continue
local refs
self_pattern=$(IFS='|'; echo "${self_refs[*]}")
refs=$(grep -rlE "\\b${token}\\b" \
"${grep_includes[@]}" \
"${search_dirs[@]}" 2>/dev/null \
| grep -vE "^(${self_pattern})$" \
| sort -u)
[[ -z "$refs" ]] && continue
local count refs_str
count=$(printf '%s\n' "$refs" | wc -l)
if (( count > 2 )); then
refs_str=$(printf '%s\n' "$refs" | head -2 | paste -sd, -)
refs_str="$refs_str, +$((count - 2)) more"
else
refs_str=$(printf '%s\n' "$refs" | paste -sd, -)
fi
printf '| `%s` | %s | `missing` |\n' "$token" "$refs_str"
done <<<"$all_refs"
echo
# --- Render: menu items -------------------------------------------------
printf '## Menu items\n\n'
printf 'Walked from `features/scripts/utils/nomarchy-menu`. Each `case` arm in a `show_*_menu` function becomes one row.\n\n'
printf '| Submenu | Entry | Calls | Status |\n'
printf '| --- | --- | --- | --- |\n'
awk '
/^show_[a-z_]+_menu\(\) {/ { sub(/\(\) {/, ""); current=$1; in_func=1; next }
/^[a-z_]+\(\) {/ && !/^show_/ { current=""; in_func=0; next }
/^}$/ { current=""; in_func=0; next }
!in_func { next }
/^ case \$\(menu / {
# extract the menu title between the first pair of double quotes
match($0, /menu "[^"]+" "[^"]+"/);
if (RSTART == 0) next;
title=substr($0, RSTART, RLENGTH);
# second quoted string is the option list
n=split(title, parts, "\"");
title=parts[2];
options=parts[4];
# split options on \n
split(options, opts, "\\\\n");
pending_submenu=current;
pending_title=title;
for (i=1;i<=length(opts);i++) pending_opts[i]=opts[i];
pending_count=length(opts);
next
}
/^ \*[A-Za-z]/ {
# case arm — extract pattern between the first * and the closing )
match($0, /\*[^)]*\)/);
if (RSTART == 0) next;
arm=substr($0, RSTART, RLENGTH);
gsub(/[*)]/, "", arm);
gsub(/^[[:space:]]+|[[:space:]]+$/, "", arm);
# action follows the )
rest=substr($0, RSTART+RLENGTH);
sub(/^[[:space:]]+/, "", rest);
sub(/[[:space:]]*;;[[:space:]]*$/, "", rest);
# match the first nomarchy-* token in the action
cmd=""
if (match(rest, /nomarchy-[a-z0-9-]+/)) {
cmd=substr(rest, RSTART, RLENGTH);
}
printf "%s|%s|%s\n", pending_submenu, arm, cmd;
}
' features/scripts/utils/nomarchy-menu > /tmp/nomarchy-menu-rows.$$
while IFS='|' read -r submenu entry cmd; do
[[ -z "$entry" ]] && continue
[[ "$entry" =~ ^\) ]] && continue
status='`kept`'
if [[ -n "$cmd" ]]; then
if [[ -z "${script_loc[$cmd]:-}" ]]; then
status='`missing`'
fi
else
cmd='_(inline)_'
fi
printf '| `%s` | %s | `%s` | %s |\n' "$submenu" "$entry" "$cmd" "$status"
done < /tmp/nomarchy-menu-rows.$$
rm -f /tmp/nomarchy-menu-rows.$$
echo
}
out=""
if [[ "${1:-}" == "--out" ]]; then
out="${2:?--out needs a path}"; shift 2
fi
if [[ -n "$out" ]]; then
main >"$out"
else
main
fi

16
core/branding/about.txt Normal file
View File

@@ -0,0 +1,16 @@
# About Nomarchy
Nomarchy is a highly curated, NixOS-based distribution designed for power users.
It features a customized Hyprland desktop environment with a declarative
theming engine and a suite of integrated utility scripts.
Built on a foundation of:
- NixOS (Linux)
- Hyprland (Window Manager)
- Waybar (Status Bar)
- Walker (Application Launcher & Menu)
- Stylix (Theming Engine)
Version: 2026.05.04
Docs: https://github.com/nomarchy/nomarchy/docs
Manual: nomarchy-manual (Command)

View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

56
core/branding/icon.svg Normal file
View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="210mm"
height="297mm"
viewBox="0 0 210 297"
version="1.1"
id="svg1"
xml:space="preserve"
inkscape:version="1.4.3 (0d15f75042, 2025-12-25)"
sodipodi:docname="icon.svg"
inkscape:export-filename="logo.svg"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
inkscape:zoom="0.82803284"
inkscape:cx="215.57116"
inkscape:cy="452.27675"
inkscape:window-width="1025"
inkscape:window-height="1012"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="layer1" /><defs
id="defs1" /><g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"><g
id="g4"
transform="translate(3.1953242,-22.686801)"
style="fill:#000088;fill-opacity:1"><path
style="fill:#000088;stroke-width:0.264583;fill-opacity:1"
d="M 25.136891,85.823024 25.557592,210.77125 88.452409,174.38061 67.417351,160.49747 57.110174,166.38729 V 105.80633 Z"
id="path1" /><path
style="fill:#000088;stroke-width:0.264583;fill-opacity:1"
d="M 67.728991,112.41131 182.54178,185.60757 153.16137,202.85259 67.830432,148.17947 Z"
id="path2" /><path
style="fill:#000088;stroke-width:0.264583;fill-opacity:1"
d="M 139.74857,145.88014 140.00405,110.4959 54.800856,56.333749 25.675926,73.32329 Z"
id="path3" /><path
style="fill:#000088;stroke-width:0.264583;fill-opacity:1"
d="M 182.2863,172.70573 V 48.286069 l -62.59305,36.406166 20.82177,13.668277 10.21927,-5.74834 0.12774,60.165978 z"
id="path4" /></g></g></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

34
core/branding/icon.txt Normal file
View File

@@ -0,0 +1,34 @@
${2},,,
${1},, ${2},,,,,,
${1},,,,,,,, ${2},,,,,,,,,,
${1},,,,,,,,,,,,,, ${2}.,,,,,,,,,,,,
${1},,,,,,,,,,,,,,,,,,,, ${2},,,,,,,,,,,,,,,,
${1},,,,,,,,,,,,,,,,,,,,,,,. ${2},,,,,,,,,,,,,,,,,,,
${1}.,,,,,,,,,,,,,,,,,,,,,,,, ${2},,,,,,,,,,,,,,,,,,,,,,,
${1}, ${1},,,,,,,,,,,,,,,,,,,,,,,,, ${2},,,,,,,,,,,,,,,,,,,,,,,,
${1},,, ${1},,,,,,,,,,,,,,,,,,,,,,,, ${2},,,,,,,,,,,,,,,,,,,,,,,,
${1},,,,,, ${1}.,,,,,,,,,,,,,,,,,,,,,,, ${2},,,,,,,,,,,,,,,,,,,,
${1},,,,,,,,,. ${1},,,,,,,,,,,,,,,,,,,,,,,,, ${2},,, ${2}.,,,,,,,,,,,,
${1},,,,,,,,,,,, ${1}.,,,,,,,,,,,,,,,,,,,,,,,, ${2},,,,,,,,,,,,,
${1},,,,,,,,,,,,, ${1},,,,,,,,,,,,,,,,,,,,,,,,. ${2},,,,,,,,,,,,,
${1},,,,,,,,,,,,, ${1},, ${1},,,,,,,,,,,,,,,,,,,,, ${2},,,,,,,,,,,,
${1},,,,,,,,,,,,, ${1},,,,, ${1},,,,,,,,,,,,,,,,,,, ${2},,,,,,,,,,,,
${1},,,,,,,,,,,,, ${1},,,,,,,, ${1},,,,,,,,,,,,,,,, ${2},,,,,,,,,,,,
${1},,,,,,,,,,,,, ${1},,,,,,,,,, ${1},,,,,,,,,,,,, ${2},,,,,,,,,,,,
${1},,,,,,,,,,,,, ${1},,,,,,,,,,,,,, ${1},,,,,,,,,, ${2},,,,,,,,,,,,
${1},,,,,,,,,,,,, ${1},,,,,,,,,,,,,,,, ${1},,,,,,, ${2},,,,,,,,,,,,
${1},,,,,,,,,,,,, ${1},,,,,,,,,,,,,,,,,,, ${1},,,, ${2},,,,,,,,,,,,
${1},,,,,,,,,,,,, ${1},,,,,,,,,,,,,,,,,,,,,, ${1},, ${2},,,,,,,,,,,,
${1},,,,,,,,,,,,, ${1},,,,,,,,,,,,,,,,,,,,,,,,, ${2},,,,,,,,,,,,
${1},,,,,,,,,,,,, ${1},,,,,,,,,,,,,,,,,,,,,,,,, ${2},,,,,,,,,,,,
${1},,,,,,,,,,,,, ${1},,, ${1},,,,,,,,,,,,,,,,,,,,,,,,, ${2},,,,,,,,,
${1},,,,,,,,,,,,,,,,,,,, ${1},,,,,,,,,,,,,,,,,,,,,,,, ${2}.,,,,,
${1},,,,,,,,,,,,,,,,,,,,,,,, ${1},,,,,,,,,,,,,,,,,,,,,,,,, ${2},,,
${1},,,,,,,,,,,,,,,,,,,,,,,, ${1},,,,,,,,,,,,,,,,,,,,,,,,,
${1},,,,,,,,,,,,,,,,,,,,,,, ${1},,,,,,,,,,,,,,,,,,,,,,,,,
${1},,,,,,,,,,,,,,,,,,, ${1},,,,,,,,,,,,,,,,,,,,,,,
${1},,,,,,,,,,,,,,,, ${1}.,,,,,,,,,,,,,,,,,,,
${1},,,,,,,,,,,,, ${1},,,,,,,,,,,,,,
${1},,,,,,,,,, ${1},,,,,,,,,.
${1},,,,,, ${1},,
${1},,,,

BIN
core/branding/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

34
core/branding/logo.txt Normal file
View File

@@ -0,0 +1,34 @@
,,,
,, ,,,,,,
,,,,,,,, ,,,,,,,,,,
,,,,,,,,,,,,,, .,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,, ,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,. ,,,,,,,,,,,,,,,,,,,
.,,,,,,,,,,,,,,,,,,,,,,,, ,,,,,,,,,,,,,,,,,,,,,,,
, ,,,,,,,,,,,,,,,,,,,,,,,,, ,,,,,,,,,,,,,,,,,,,,,,,,
,,, ,,,,,,,,,,,,,,,,,,,,,,,, ,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,, .,,,,,,,,,,,,,,,,,,,,,,, ,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,. ,,,,,,,,,,,,,,,,,,,,,,,,, ,,, .,,,,,,,,,,,,
,,,,,,,,,,,, .,,,,,,,,,,,,,,,,,,,,,,,, ,,,,,,,,,,,,,
,,,,,,,,,,,,, ,,,,,,,,,,,,,,,,,,,,,,,,. ,,,,,,,,,,,,,
,,,,,,,,,,,,, ,, ,,,,,,,,,,,,,,,,,,,,, ,,,,,,,,,,,,
,,,,,,,,,,,,, ,,,,, ,,,,,,,,,,,,,,,,,,, ,,,,,,,,,,,,
,,,,,,,,,,,,, ,,,,,,,, ,,,,,,,,,,,,,,,, ,,,,,,,,,,,,
,,,,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,,,, ,,,,,,,,,,,,
,,,,,,,,,,,,, ,,,,,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,,,
,,,,,,,,,,,,, ,,,,,,,,,,,,,,,, ,,,,,,, ,,,,,,,,,,,,
,,,,,,,,,,,,, ,,,,,,,,,,,,,,,,,,, ,,,, ,,,,,,,,,,,,
,,,,,,,,,,,,, ,,,,,,,,,,,,,,,,,,,,,, ,, ,,,,,,,,,,,,
,,,,,,,,,,,,, ,,,,,,,,,,,,,,,,,,,,,,,,, ,,,,,,,,,,,,
,,,,,,,,,,,,, ,,,,,,,,,,,,,,,,,,,,,,,,, ,,,,,,,,,,,,
,,,,,,,,,,,,, ,,, ,,,,,,,,,,,,,,,,,,,,,,,,, ,,,,,,,,,
,,,,,,,,,,,,,,,,,,,, ,,,,,,,,,,,,,,,,,,,,,,,, .,,,,,
,,,,,,,,,,,,,,,,,,,,,,,, ,,,,,,,,,,,,,,,,,,,,,,,,, ,,,
,,,,,,,,,,,,,,,,,,,,,,,, ,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,, ,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,, ,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,, .,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,, ,,,,,,,,,,,,,,
,,,,,,,,,, ,,,,,,,,,.
,,,,,, ,,
,,,,

View File

@@ -0,0 +1,11 @@
# Nomarchy Screensaver Configuration
Nomarchy uses hyprlock for locking and hypridle for idle management.
To configure the screensaver/lock screen visuals, edit:
~/.config/hypr/hyprlock.conf
To configure idle timeouts and actions, edit:
~/.config/hypr/hypridle.conf
You can also toggle the screensaver/idle management via the Nomarchy Menu:
Trigger > Toggle > Idle Lock

5
core/default.nix Normal file
View File

@@ -0,0 +1,5 @@
{ ... }:
{
imports = [ ./system ];
}

39
core/home/bash.nix Normal file
View File

@@ -0,0 +1,39 @@
{ config, lib, ... }:
{
programs.bash = {
enable = true;
bashrcExtra = ''
if [[ -f ~/.config/nomarchy/default/bash/rc ]]; then
source ~/.config/nomarchy/default/bash/rc
fi
'';
shellAliases = lib.mkDefault {
# File system
lsa = "ls -a";
# Directories
".." = "cd ..";
"..." = "cd ../..";
"...." = "cd ../../..";
# Tools
c = "opencode";
d = "docker";
r = "rails";
t = "tmux attach || tmux new -s Work";
# Git
g = "git";
gcm = "git commit -m";
gcam = "git commit -a -m";
gcad = "git commit -a --amend";
# NixOS commands
sys-update = "nomarchy-sys-update";
env-update = "nomarchy-env-update";
};
};
}

94
core/home/behavior.nix Normal file
View File

@@ -0,0 +1,94 @@
{ config, lib, ... }:
# Behavior Configuration Module
#
# This module deploys non-visual configuration files (keybindings, input settings,
# window rules, etc.) with lib.mkDefault, allowing downstream users to override.
#
# Visual/theme configs are handled separately by theme-loader.nix and stylix.nix.
#
# Behavior configs include:
# - Keybindings (bindings, media keys, clipboard)
# - Input settings (keyboard, mouse, touchpad)
# - Window rules and layouts
# - Autostart applications
# - Environment variables
let
configDir = ./config;
overridesDir = "${config.home.homeDirectory}/.config/nomarchy/overrides";
# Behavior config categories with their source paths
behaviorConfigs = {
# Hyprland behavior (non-visual)
"nomarchy/default/hypr/bindings.conf" = "hypr/bindings.conf";
"nomarchy/default/hypr/bindings/media.conf" = "hypr/bindings/media.conf";
"nomarchy/default/hypr/bindings/clipboard.conf" = "hypr/bindings/clipboard.conf";
"nomarchy/default/hypr/bindings/tiling-v2.conf" = "hypr/bindings/tiling-v2.conf";
"nomarchy/default/hypr/bindings/utilities.conf" = "hypr/bindings/utilities.conf";
"nomarchy/default/hypr/input.conf" = "hypr/input.conf";
"nomarchy/default/hypr/windows.conf" = "hypr/windows.conf";
"nomarchy/default/hypr/autostart.conf" = "hypr/autostart.conf";
"nomarchy/default/hypr/envs.conf" = "hypr/envs.conf";
"nomarchy/default/hypr/looknfeel.conf" = "hypr/looknfeel.conf";
# App-specific window rules (behavior, not visual)
"nomarchy/default/hypr/apps.conf" = "hypr/apps.conf";
"nomarchy/default/hypr/apps/qemu.conf" = "hypr/apps/qemu.conf";
"nomarchy/default/hypr/apps/steam.conf" = "hypr/apps/steam.conf";
"nomarchy/default/hypr/apps/terminals.conf" = "hypr/apps/terminals.conf";
"nomarchy/default/hypr/apps/walker.conf" = "hypr/apps/walker.conf";
"nomarchy/default/hypr/apps/browser.conf" = "hypr/apps/browser.conf";
"nomarchy/default/hypr/apps/1password.conf" = "hypr/apps/1password.conf";
"nomarchy/default/hypr/apps/bitwarden.conf" = "hypr/apps/bitwarden.conf";
"nomarchy/default/hypr/apps/pip.conf" = "hypr/apps/pip.conf";
"nomarchy/default/hypr/apps/system.conf" = "hypr/apps/system.conf";
"nomarchy/default/hypr/apps/localsend.conf" = "hypr/apps/localsend.conf";
"nomarchy/default/hypr/apps/telegram.conf" = "hypr/apps/telegram.conf";
"nomarchy/default/hypr/apps/geforce.conf" = "hypr/apps/geforce.conf";
"nomarchy/default/hypr/apps/moonlight.conf" = "hypr/apps/moonlight.conf";
"nomarchy/default/hypr/apps/retroarch.conf" = "hypr/apps/retroarch.conf";
"nomarchy/default/hypr/apps/webcam-overlay.conf" = "hypr/apps/webcam-overlay.conf";
"nomarchy/default/hypr/apps/davinci-resolve.conf" = "hypr/apps/davinci-resolve.conf";
"nomarchy/default/hypr/apps/hyprshot.conf" = "hypr/apps/hyprshot.conf";
};
in
{
options.nomarchy.behavior = {
hyprland = {
bindings = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Whether to deploy default Hyprland keybindings.";
};
input = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Whether to deploy default input settings.";
};
windowRules = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Whether to deploy default window rules.";
};
autostart = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Whether to deploy default autostart configuration.";
};
};
};
config = {
# Note: The actual config deployment is handled by configs.nix
# This module provides the options and documentation for behavior configs
# The separation allows users to selectively disable behavior categories
# Ensure behavior config directories exist in overrides
home.activation.createBehaviorOverrideDirs = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
mkdir -p "${overridesDir}/hypr/bindings"
mkdir -p "${overridesDir}/hypr/apps"
'';
};
}

View File

@@ -0,0 +1,4 @@
--ozone-platform=wayland
--ozone-platform-hint=wayland
--enable-features=TouchpadOverscrollHistoryNavigation
--load-extension=~/.config/nomarchy/default/chromium/extensions/copy-url

View File

@@ -0,0 +1,4 @@
--ozone-platform=wayland
--ozone-platform-hint=wayland
--enable-features=TouchpadOverscrollHistoryNavigation
--load-extension=~/.config/nomarchy/default/chromium/extensions/copy-url

View File

@@ -0,0 +1,137 @@
{
"$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json",
"logo": {
"type": "file",
"source": "~/.config/nomarchy/branding/icon.txt",
"color": { "1": "green", "2": "blue" },
"padding": {
"top": 2,
"right": 6,
"left": 2
}
},
"modules": [
"break",
{
"type": "custom",
"format": "\u001b[90m┌──────────────────────Hardware──────────────────────┐"
},
{
"type": "host",
"key": " PC",
"keyColor": "green"
},
{
"type": "cpu",
"key": "│ ├",
"showPeCoreCount": true,
"keyColor": "green"
},
{
"type": "gpu",
"key": "│ ├",
"detectionMethod": "pci",
"keyColor": "green"
},
{
"type": "display",
"key": "│ ├󱄄",
"keyColor": "green"
},
{
"type": "disk",
"key": "│ ├󰋊",
"keyColor": "green"
},
{
"type": "memory",
"key": "│ ├",
"keyColor": "green"
},
{
"type": "swap",
"key": "└ └󰓡 ",
"keyColor": "green"
},
{
"type": "custom",
"format": "\u001b[90m└────────────────────────────────────────────────────┘"
},
"break",
{
"type": "custom",
"format": "\u001b[90m┌──────────────────────Software──────────────────────┐"
},
{
"type": "os",
"key": "\ue900 OS",
"keyColor": "blue"
},
{
"type": "kernel",
"key": "│ ├",
"keyColor": "blue"
},
{
"type": "wm",
"key": "│ ├",
"keyColor": "blue"
},
{
"type": "de",
"key": " DE",
"keyColor": "blue"
},
{
"type": "terminal",
"key": "│ ├",
"keyColor": "blue"
},
{
"type": "packages",
"key": "│ ├󰏖",
"keyColor": "blue"
},
{
"type": "wmtheme",
"key": "│ ├󰉼",
"keyColor": "blue"
},
{
"type": "command",
"key": "│ ├󰸌",
"keyColor": "blue",
"text": "theme=$(cat ~/.config/nomarchy/current/theme.name); echo -e \"$theme \\e[38m●\\e[37m●\\e[36m●\\e[35m●\\e[34m●\\e[33m●\\e[32m●\\e[31m●\""
},
{
"type": "terminalfont",
"key": "└ └",
"keyColor": "blue"
},
{
"type": "custom",
"format": "\u001b[90m└────────────────────────────────────────────────────┘"
},
"break",
{
"type": "custom",
"format": "\u001b[90m┌────────────────Age / Uptime / Update───────────────┐"
},
{
"type": "command",
"key": "󱦟 OS Age",
"keyColor": "magenta",
"text": "echo $(( ($(date +%s) - $(stat -c %W /)) / 86400 )) days"
},
{
"type": "uptime",
"key": "󱫐 Uptime",
"keyColor": "magenta"
},
{
"type": "custom",
"format": "\u001b[90m└────────────────────────────────────────────────────┘"
},
"break"
]
}

View File

@@ -0,0 +1,2 @@
TriggerKey=
PastePrimaryKey=

View File

@@ -0,0 +1 @@
Allow Overriding System XKB Settings=False

View File

@@ -0,0 +1,79 @@
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<match target="pattern">
<test name="family" qual="any">
<string>sans-serif</string>
</test>
<edit name="family" mode="assign" binding="strong">
<string>Liberation Sans</string>
</edit>
</match>
<match target="pattern">
<test name="family" qual="any">
<string>serif</string>
</test>
<edit name="family" mode="assign" binding="strong">
<string>Liberation Serif</string>
</edit>
</match>
<match target="pattern">
<test name="family" qual="any">
<string>monospace</string>
</test>
<edit name="family" mode="assign" binding="strong">
<string>JetBrainsMono Nerd Font</string>
</edit>
</match>
<alias>
<family>system-ui</family>
<prefer>
<family>Liberation Sans</family>
</prefer>
</alias>
<alias>
<family>ui-monospace</family>
<default>
<family>monospace</family>
</default>
</alias>
<alias>
<family>-apple-system</family>
<prefer>
<family>Liberation Sans</family>
</prefer>
</alias>
<alias>
<family>BlinkMacSystemFont</family>
<prefer>
<family>Liberation Sans</family>
</prefer>
</alias>
<alias>
<family>sans-serif</family>
<accept>
<family>Noto Color Emoji</family>
</accept>
</alias>
<alias>
<family>serif</family>
<accept>
<family>Noto Color Emoji</family>
</accept>
</alias>
<alias>
<family>monospace</family>
<accept>
<family>Noto Color Emoji</family>
</accept>
</alias>
</fontconfig>

View File

@@ -0,0 +1,28 @@
# See https://git-scm.com/docs/git-config
[alias]
co = checkout
br = branch
ci = commit
st = status
[init]
defaultBranch = master
[pull]
rebase = true # Rebase (instead of merge) on pull
[push]
autoSetupRemote = true # Automatically set upstream branch on push
[diff]
algorithm = histogram # Clearer diffs on moved/edited lines
colorMoved = plain # Highlight moved blocks in diffs
mnemonicPrefix = true # More intuitive refs in diff output
[commit]
verbose = true # Include diff comment in commit message template
[column]
ui = auto # Output in columns when possible
[branch]
sort = -committerdate # Sort branches by most recent commit first
[tag]
sort = -version:refname # Sort version numbers as you would expect
[rerere]
enabled = true # Record and reuse conflict resolutions
autoupdate = true # Apply stored conflict resolutions automatically

View File

@@ -0,0 +1,13 @@
[binds]
# Print the current image file
<Ctrl+p> = exec lp "$imv_current_file"
# Delete the current image and quit the viewer
<Ctrl+x> = exec rm "$imv_current_file"; quit
# Delete the current image and move to the next one
<Ctrl+Shift+X> = exec rm "$imv_current_file"; close
# Rotate the currently open image by 90 degrees
<Ctrl+r> = exec mogrify -rotate 90 "$imv_current_file"

View File

@@ -0,0 +1,84 @@
import os
import shutil
from gi import require_version
require_version("Nautilus", "4.1")
from gi.repository import GObject, Gio, Nautilus
class SendViaLocalSendAction(GObject.GObject, Nautilus.MenuProvider):
def _launch_localsend(self, paths):
command = self._resolve_command()
if not command:
return
if command[-1] == "@@":
command = command + paths + ["@@"]
else:
command = command + paths
Gio.Subprocess.new(command, Gio.SubprocessFlags.NONE)
def _resolve_command(self):
localsend = shutil.which("localsend")
if localsend:
return [localsend, "--headless", "send"]
flatpak = shutil.which("flatpak")
if flatpak and self._has_flatpak_app(flatpak, "org.localsend.localsend_app"):
return [
flatpak,
"run",
"--file-forwarding",
"org.localsend.localsend_app",
"@@",
]
return None
def _has_flatpak_app(self, flatpak, app_id):
process = Gio.Subprocess.new(
[flatpak, "info", app_id],
Gio.SubprocessFlags.STDOUT_SILENCE | Gio.SubprocessFlags.STDERR_SILENCE,
)
return process.wait_check()
def _selected_paths(self, files):
paths = []
for file in files:
location = file.get_location()
if not location:
continue
path = location.get_path()
if path and path not in paths:
paths.append(path)
return paths
def _make_item(self, paths):
label = (
"Send via LocalSend" if len(paths) == 1 else "Send selected via LocalSend"
)
item = Nautilus.MenuItem(
name="LocalSendNautilus::send_via_localsend",
label=label,
icon="localsend",
)
item.connect("activate", self._on_activate, paths)
return item
def _on_activate(self, _menu, paths):
self._launch_localsend(paths)
def get_file_items(self, *args):
files = args[0] if len(args) == 1 else args[1]
paths = self._selected_paths(files)
if not paths or not self._resolve_command():
return []
return [self._make_item(paths)]

View File

@@ -0,0 +1,408 @@
---
name: nomarchy
description: >
REQUIRED for end-user customization of Linux desktop, window manager, or system config.
Use when editing ~/.config/hypr/, ~/.config/waybar/, ~/.config/walker/,
~/.config/alacritty/, ~/.config/kitty/, ~/.config/ghostty/, ~/.config/mako/,
or ~/.config/nomarchy/. Triggers: Hyprland, window rules, animations, keybindings,
monitors, gaps, borders, blur, opacity, waybar, walker, terminal config, themes,
wallpaper, night light, idle, lock screen, screenshots, layer rules, workspace
settings, display config, and user-facing nomarchy commands. Excludes Nomarchy
source development in ~/.local/share/nomarchy/ and repo-internal workflows.
---
# Nomarchy Skill
Manage [Nomarchy](https://nomarchy.org/) Linux systems - a beautiful, modern, opinionated Arch Linux distribution with Hyprland.
This skill is for end-user customization on installed systems.
It is not for contributing to Nomarchy source code.
## When This Skill MUST Be Used
**ALWAYS invoke this skill for end-user requests involving ANY of these:**
- Editing ANY file in `~/.config/hypr/` (window rules, animations, keybindings, monitors, etc.)
- Editing ANY file in `~/.config/waybar/`, `~/.config/walker/`, `~/.config/mako/`
- Editing terminal configs (alacritty, kitty, ghostty)
- Editing ANY file in `~/.config/nomarchy/`
- Window behavior, animations, opacity, blur, gaps, borders
- Layer rules, workspace settings, display/monitor configuration
- Themes, wallpapers, fonts, appearance changes
- User-facing `nomarchy-*` commands (`nomarchy-theme-*`, `nomarchy-refresh-*`, `nomarchy-restart-*`, etc.)
- Screenshots, screen recording, night light, idle behavior, lock screen
**If you're about to edit a config file in ~/.config/ on this system, STOP and use this skill first.**
**Do NOT use this skill for Nomarchy development tasks** — editing files in `~/.local/share/nomarchy/` or modifying repo internals.
## Critical Safety Rules
**For end-user customization tasks, NEVER modify anything in `~/.local/share/nomarchy/`** - but READING is safe and encouraged.
This directory contains Nomarchy's source files managed by git. Any changes will be:
- Lost on next `nomarchy-update`
- Cause conflicts with upstream
- Break the system's update mechanism
```
~/.local/share/nomarchy/ # READ-ONLY - NEVER EDIT (reading is OK)
├── bin/ # Source scripts (symlinked to PATH)
├── config/ # Default config templates
├── themes/ # Stock themes
├── default/ # System defaults
├── migrations/ # Update migrations
└── install/ # Installation scripts
```
**Reading `~/.local/share/nomarchy/` is SAFE and useful** - do it freely to:
- Understand how nomarchy commands work: `cat $(which nomarchy-theme-set)`
- See default configs before customizing: `cat ~/.local/share/nomarchy/config/waybar/config.jsonc`
- Check stock theme files to copy for customization
- Reference default hyprland settings: `cat ~/.config/nomarchy/default/hypr/*`
**Always use these safe locations instead:**
- `~/.config/` - User configuration (safe to edit)
- `~/.config/nomarchy/themes/<custom-name>/` - Custom themes (must be real directories)
- `~/.config/nomarchy/hooks/` - Custom automation hooks
If the request is to develop Nomarchy itself, this skill is out of scope. Follow repository development instructions instead of this skill.
## System Architecture
Nomarchy is built on:
| Component | Purpose | Config Location |
|-----------|---------|-----------------|
| **Arch Linux** | Base OS | `/etc/`, `~/.config/` |
| **Hyprland** | Wayland compositor/WM | `~/.config/hypr/` |
| **Waybar** | Status bar | `~/.config/waybar/` |
| **Walker** | App launcher | `~/.config/walker/` |
| **Alacritty/Kitty/Ghostty** | Terminals | `~/.config/<terminal>/` |
| **Mako** | Notifications | `~/.config/mako/` |
| **SwayOSD** | On-screen display | `~/.config/swayosd/` |
## Command Discovery
Nomarchy provides ~145 commands following `nomarchy-<category>-<action>` pattern.
```bash
# List all nomarchy commands
compgen -c | grep -E '^nomarchy-' | sort -u
# Find commands by category
compgen -c | grep -E '^nomarchy-theme'
compgen -c | grep -E '^nomarchy-restart'
# Read a command's source to understand it
cat $(which nomarchy-theme-set)
```
### Command Categories
| Prefix | Purpose | Example |
|--------|---------|---------|
| `nomarchy-refresh-*` | Reset config to defaults (backs up first) | `nomarchy-refresh-waybar` |
| `nomarchy-restart-*` | Restart a service/app | `nomarchy-restart-waybar` |
| `nomarchy-toggle-*` | Toggle feature on/off | `nomarchy-toggle-nightlight` |
| `nomarchy-theme-*` | Theme management | `nomarchy-theme-set <name>` |
| `nomarchy-install-*` | Install optional software | `nomarchy-install-docker-dbs` |
| `nomarchy-launch-*` | Launch apps | `nomarchy-launch-browser` |
| `nomarchy-cmd-*` | System commands | `nomarchy-cmd-screenshot` |
| `nomarchy-pkg-*` | Package management | `nomarchy-pkg-install <pkg>` |
| `nomarchy-setup-*` | Initial setup tasks | `nomarchy-setup-fingerprint` |
| `nomarchy-update-*` | System updates | `nomarchy-update` |
## Configuration Locations
### Hyprland (Window Manager)
```
~/.config/hypr/
├── hyprland.conf # Main config (sources others)
├── bindings.conf # Keybindings
├── monitors.conf # Display configuration
├── input.conf # Keyboard/mouse settings
├── looknfeel.conf # Appearance (gaps, borders, animations)
├── envs.conf # Environment variables
├── autostart.conf # Startup applications
├── hypridle.conf # Idle behavior (screen off, lock, suspend)
├── hyprlock.conf # Lock screen appearance
└── hyprsunset.conf # Night light / blue light filter
```
**Key behaviors:**
- Hyprland auto-reloads on config save (no restart needed for most changes)
- Use `hyprctl reload` to force reload
- Use `nomarchy-refresh-hyprland` to reset to defaults
### Waybar (Status Bar)
```
~/.config/waybar/
├── config.jsonc # Bar layout and modules (JSONC format)
└── style.css # Styling
```
**Waybar does NOT auto-reload.** You MUST run `nomarchy-restart-waybar` after any config changes.
**Commands:** `nomarchy-restart-waybar`, `nomarchy-refresh-waybar`, `nomarchy-toggle-waybar`
### Terminals
```
~/.config/alacritty/alacritty.toml
~/.config/kitty/kitty.conf
~/.config/ghostty/config
```
**Command:** `nomarchy-restart-terminal`
### Other Configs
| App | Location |
|-----|----------|
| btop | `~/.config/btop/btop.conf` |
| fastfetch | `~/.config/fastfetch/config.jsonc` |
| lazygit | `~/.config/lazygit/config.yml` |
| starship | `~/.config/starship.toml` |
| git | `~/.config/git/config` |
| walker | `~/.config/walker/config.toml` |
## Safe Customization Patterns
### Pattern 1: Edit User Config Directly
For simple changes, edit files in `~/.config/`:
```bash
# 1. Read current config
cat ~/.config/hypr/bindings.conf
# 2. Backup before changes
cp ~/.config/hypr/bindings.conf ~/.config/hypr/bindings.conf.bak.$(date +%s)
# 3. Make changes with Edit tool
# 4. Apply changes
# - Hyprland: auto-reloads on save (no restart needed)
# - Waybar: MUST restart with nomarchy-restart-waybar
# - Walker: MUST restart with nomarchy-restart-walker
# - Terminals: MUST restart with nomarchy-restart-terminal
```
### Pattern 2: Make a new theme
1. Create a directory under ~/.config/nomarchy/themes.
2. See how an existing theme is done via ~/.local/share/nomarchy/themes/catppuccin.
3. Download a matching background (or several) from the internet and put them in ~/.config/nomarchy/themes/[name-of-new-theme]
4. When done with the theme, run nomarchy-theme-set "Name of new theme"
### Pattern 3: Use Hooks for Automation
Create scripts in `~/.config/nomarchy/hooks/` to run automatically on events:
```bash
# Available hooks (see samples in ~/.config/nomarchy/hooks/):
~/.config/nomarchy/hooks/
├── theme-set # Runs after theme change (receives theme name as $1)
├── font-set # Runs after font change
└── post-update # Runs after nomarchy-update
```
Example hook (`~/.config/nomarchy/hooks/theme-set`):
```bash
#!/bin/bash
THEME_NAME=$1
echo "Theme changed to: $THEME_NAME"
# Add custom actions here
```
### Pattern 4: Reset to Defaults -- ALWAYS SEEK USER CONFIRMATION BEFORE RUNNING
When customizations go wrong:
```bash
# Reset specific config (creates backup automatically)
nomarchy-refresh-waybar
nomarchy-refresh-hyprland
# The refresh command:
# 1. Backs up current config with timestamp
# 2. Copies default from ~/.local/share/nomarchy/config/
# 3. Restarts the component
```
## Common Tasks
### Themes
```bash
nomarchy-theme-list # Show available themes
nomarchy-theme-current # Show current theme
nomarchy-theme-set <name> # Apply theme (use "Tokyo Night" not "tokyo-night")
nomarchy-theme-next # Cycle to next theme
nomarchy-theme-bg-next # Cycle wallpaper
nomarchy-theme-install <url> # Install from git repo
nomarchy-theme-remove <name> # Remove an installed extra theme
nomarchy-theme-refresh # Re-apply current theme from templates
nomarchy-theme-bg-install # Open backgrounds dir to drop in custom images
```
### Keybindings
Edit `~/.config/hypr/bindings.conf`. Format:
```
bind = SUPER, Return, exec, xdg-terminal-exec
bind = SUPER, Q, killactive
bind = SUPER SHIFT, E, exit
```
View current bindings: `nomarchy-menu-keybindings --print`
**IMPORTANT: When re-binding an existing key:**
1. First check existing bindings: `nomarchy-menu-keybindings --print`
2. If the key is already bound, you MUST add an `unbind` directive BEFORE your new `bind`
3. Inform the user what the key was previously bound to
Example - rebinding SUPER+F (which is bound to fullscreen by default):
```
# Unbind existing SUPER+F (was: fullscreen)
unbind = SUPER, F
# New binding for file manager
bind = SUPER, F, exec, nautilus
```
Always tell the user: "Note: SUPER+F was previously bound to fullscreen. I've added an unbind directive to override it."
### Display/Monitors
Edit `~/.config/hypr/monitors.conf`. Format:
```
monitor = eDP-1, 1920x1080@60, 0x0, 1
monitor = HDMI-A-1, 2560x1440@144, 1920x0, 1
```
List monitors: `hyprctl monitors`
### Window Rules
**CRITICAL: Hyprland window rules syntax changes frequently between versions.**
Before writing ANY window rules, you MUST fetch the current documentation from the official Hyprland wiki:
- https://github.com/hyprwm/hyprland-wiki/blob/main/content/Configuring/Window-Rules.md
DO NOT rely on cached or memorized window rule syntax. The format has changed multiple times and using outdated syntax will cause errors or unexpected behavior.
Window rules go in `~/.config/hypr/hyprland.conf` or a sourced file. Always verify the current syntax from the wiki first.
### Fonts
```bash
nomarchy-font-list # Available fonts
nomarchy-font-current # Current font
nomarchy-font-set <name> # Change font
```
### System
```bash
nomarchy-update # Full system update
nomarchy-version # Show Nomarchy version
nomarchy-debug --no-sudo --print # Debug info (ALWAYS use these flags)
nomarchy-lock-screen # Lock screen
nomarchy-system-shutdown # Shutdown
nomarchy-system-reboot # Reboot
nomarchy-sudo-passwordless-toggle # Toggle 15-min passwordless sudo
nomarchy-sudo-reset # Clear sudo lockout / faillock
nomarchy-restart-trackpad # Reload intel_quicki2c (fixes dead THC trackpad)
```
**IMPORTANT:** Always run `nomarchy-debug` with `--no-sudo --print` flags to avoid interactive sudo prompts that will hang the terminal.
### Custom App Launchers
```bash
nomarchy-webapp-install # Add a web app launcher (interactive)
nomarchy-webapp-remove [name...] # Remove web apps (interactive if no name)
nomarchy-webapp-remove-all # Bulk-remove every web app
nomarchy-tui-install # Add a TUI launcher for a terminal program
nomarchy-tui-remove [name...] # Remove TUI launchers
nomarchy-tui-remove-all # Bulk-remove every TUI launcher
```
Both families write `.desktop` files into `~/.local/share/applications/` so they appear in the app launcher (walker / rofi).
### Voice dictation (Voxtype)
```bash
nomarchy-voxtype-install # Install Voxtype + AI model (~150MB)
nomarchy-voxtype-remove # Uninstall Voxtype
nomarchy-voxtype-status # Running state (also shown in waybar)
```
Toggle dictation with `SUPER+CTRL+X` after install.
### Virtualization
```bash
nomarchy-windows-vm install # Provision a Windows VM via docker-compose
nomarchy-windows-vm launch # Connect to the VM (auto-stop on disconnect)
nomarchy-windows-vm launch -k # Connect, keep VM running after disconnect
nomarchy-windows-vm stop # Shut the VM down
nomarchy-windows-vm status # Show current state
```
Requires KVM (`/dev/kvm`) and Docker (enable via `nomarchy.system.virtualization.docker`).
## Troubleshooting
```bash
# Get debug information (ALWAYS use these flags to avoid interactive prompts)
nomarchy-debug --no-sudo --print
# Upload logs for support
nomarchy-upload-log
# Reset specific config to defaults
# Examples: nomarchy-refresh-fastfetch, nomarchy-refresh-hyprland, nomarchy-refresh-waybar
nomarchy-refresh-<app>
# Refresh specific config file
# config-file path is relative to ~/.config/
# eg. nomarchy-refresh-config hypr/hyprlock.conf will refresh ~/.config/hypr/hyprlock.conf
nomarchy-refresh-config <config-file>
# Full reinstall of configs (nuclear option)
nomarchy-reinstall
```
## Decision Framework
When user requests system changes:
1. **Is it a stock nomarchy command?** Use it directly
2. **Is it a config edit?** Edit in `~/.config/`, never `~/.local/share/nomarchy/`
3. **Is it a theme customization?** Create a NEW custom theme directory
4. **Is it automation?** Use hooks in `~/.config/nomarchy/hooks/`
5. **Is it a package install?** Use `nomarchy-pkg-add` (or `nomarchy-pkg-aur-add` for AUR-only packages)
6. **Unsure if command exists?** Search with `compgen -c | grep nomarchy`
## Out of Scope
This skill intentionally does not cover Nomarchy source development. Do not use this skill for:
- Editing files in `~/.local/share/nomarchy/` (`bin/`, `config/`, `default/`, `themes/`, `migrations/`, etc.)
- Creating or editing migrations
- Modifying Nomarchy's own source tree
## Example Requests
- "Change my theme to catppuccin" -> `nomarchy-theme-set catppuccin`
- "Add a keybinding for Super+E to open file manager" -> Check existing bindings first, add `unbind` if needed, then add `bind` in `~/.config/hypr/bindings.conf`
- "Configure my external monitor" -> Edit `~/.config/hypr/monitors.conf`
- "Make the window gaps smaller" -> Edit `~/.config/hypr/looknfeel.conf`
- "Set up night light to turn on at sunset" -> `nomarchy-toggle-nightlight` or edit `~/.config/hypr/hyprsunset.conf`
- "Customize the catppuccin theme colors" -> Create `~/.config/nomarchy/themes/catppuccin-custom/` by copying from stock, then edit
- "Run a script every time I change themes" -> Create `~/.config/nomarchy/hooks/theme-set`
- "Reset waybar to defaults" -> `nomarchy-refresh-waybar`

View File

@@ -0,0 +1,11 @@
[colors.primary]
background = "0x000000"
[colors.cursor]
cursor = "0x000000"
[font]
size = 18.0
[window]
opacity = 1.0

View File

@@ -0,0 +1,57 @@
# File system
if command -v eza &> /dev/null; then
alias ls='eza -lh --group-directories-first --icons=auto'
alias lsa='ls -a'
alias lt='eza --tree --level=2 --long --icons --git'
alias lta='lt -a'
fi
if [[ "$TERM" == "xterm-kitty" ]]; then
alias ff="fzf --preview 'case \$(file --mime-type -b {}) in image/*) kitty icat --clear --transfer-mode=memory --stdin=no --place=\${FZF_PREVIEW_COLUMNS}x\${FZF_PREVIEW_LINES}@0x0 {} ;; *) bat --style=numbers --color=always {} ;; esac'"
else
alias ff="fzf --preview 'bat --style=numbers --color=always {}'"
fi
alias eff='$EDITOR "$(ff)"'
sff() { if [ $# -eq 0 ]; then echo "Usage: sff <destination> (e.g. sff host:/tmp/)"; return 1; fi; local file; file=$(find . -type f -printf '%T@\t%p\n' | sort -rn | cut -f2- | ff) && [ -n "$file" ] && scp "$file" "$1"; }
if command -v zoxide &> /dev/null; then
alias cd="zd"
zd() {
if (( $# == 0 )); then
builtin cd ~ || return
elif [[ -d $1 ]]; then
builtin cd "$1" || return
else
if ! z "$@"; then
echo "Error: Directory not found"
return 1
fi
printf "\U000F17A9 "
pwd
fi
}
fi
open() (
xdg-open "$@" >/dev/null 2>&1 &
)
# Directories
alias ..='cd ..'
alias ...='cd ../..'
alias ....='cd ../../..'
# Tools
alias c='opencode'
alias cx='printf "\033[2J\033[3J\033[H" && claude --allow-dangerously-skip-permissions'
alias d='docker'
alias r='rails'
alias t='tmux attach || tmux new -s Work'
n() { if [ "$#" -eq 0 ]; then command nvim . ; else command nvim "$@"; fi; }
# Git
alias g='git'
alias gcm='git commit -m'
alias gcam='git commit -a -m'
alias gcad='git commit -a --amend'

View File

@@ -0,0 +1,6 @@
# Editor used by CLI
export SUDO_EDITOR="$EDITOR"
export BAT_THEME=ansi
# Duplicated from .config/uwsm/env so SSH works too
export PATH=$PATH:$HOME/.local/bin

View File

@@ -0,0 +1,3 @@
# Compression
compress() { tar -czf "${1%/}.tar.gz" "${1%/}"; }
alias decompress="tar -xzf"

View File

@@ -0,0 +1,59 @@
# Write iso file to sd card
iso2sd() {
if (( $# < 1 )); then
echo "Usage: iso2sd <input_file> [output_device]"
echo "Example: iso2sd ~/Downloads/ubuntu-25.04-desktop-amd64.iso /dev/sda"
return 1
fi
local iso="$1"
local drive="$2"
if [[ -z $drive ]]; then
local available_sds=$(lsblk -dpno NAME | grep -E '/dev/sd')
if [[ -z $available_sds ]]; then
echo "No SD drives found and no drive specified"
return 1
fi
drive=$(nomarchy-drive-select "$available_sds")
if [[ -z $drive ]]; then
echo "No drive selected"
return 1
fi
fi
sudo dd bs=4M status=progress oflag=sync if="$iso" of="$drive"
sudo eject "$drive"
}
# Format an entire drive for a single partition using exFAT
format-drive() {
if (( $# != 2 )); then
echo "Usage: format-drive <device> <name>"
echo "Example: format-drive /dev/sda 'My Stuff'"
echo -e "\nAvailable drives:"
lsblk -d -o NAME -n | awk '{print "/dev/"$1}'
else
echo "WARNING: This will completely erase all data on $1 and label it '$2'."
read -rp "Are you sure you want to continue? (y/N): " confirm
if [[ $confirm =~ ^[Yy]$ ]]; then
sudo wipefs -a "$1"
sudo dd if=/dev/zero of="$1" bs=1M count=100 status=progress
sudo parted -s "$1" mklabel gpt
sudo parted -s "$1" mkpart primary 1MiB 100%
sudo parted -s "$1" set 1 msftdata on
partition="$([[ $1 == *"nvme"* ]] && echo "${1}p1" || echo "${1}1")"
sudo partprobe "$1" || true
sudo udevadm settle || true
sudo mkfs.exfat -n "$2" "$partition"
echo "Drive $1 formatted as exFAT and labeled '$2'."
fi
fi
}

View File

@@ -0,0 +1,20 @@
# SSH Port Forwarding Functions
fip() {
(( $# < 2 )) && echo "Usage: fip <host> <port1> [port2] ..." && return 1
local host="$1"
shift
for port in "$@"; do
ssh -f -N -L "$port:localhost:$port" "$host" && echo "Forwarding localhost:$port -> $host:$port"
done
}
dip() {
(( $# == 0 )) && echo "Usage: dip <port1> [port2] ..." && return 1
for port in "$@"; do
pkill -f "ssh.*-L $port:localhost:$port" && echo "Stopped forwarding port $port" || echo "No forwarding on port $port"
done
}
lip() {
pgrep -af "ssh.*-L [0-9]+:localhost:[0-9]+" || echo "No active forwards"
}

View File

@@ -0,0 +1,97 @@
# Create a Tmux Dev Layout with editor, ai, and terminal
# Usage: tdl <c|cx|codex|other_ai> [<second_ai>]
tdl() {
[[ -z $1 ]] && { echo "Usage: tdl <c|cx|codex|other_ai> [<second_ai>]"; return 1; }
[[ -z $TMUX ]] && { echo "You must start tmux to use tdl."; return 1; }
local current_dir="${PWD}"
local editor_pane ai_pane ai2_pane
local ai="$1"
local ai2="$2"
# Use TMUX_PANE for the pane we're running in (stable even if active window changes)
editor_pane="$TMUX_PANE"
# Name the current window after the base directory name
tmux rename-window -t "$editor_pane" "$(basename "$current_dir")"
# Split window vertically - top 85%, bottom 15% (target editor pane explicitly)
tmux split-window -v -p 15 -t "$editor_pane" -c "$current_dir"
# Split editor pane horizontally - AI on right 30% (capture new pane ID directly)
ai_pane=$(tmux split-window -h -p 30 -t "$editor_pane" -c "$current_dir" -P -F '#{pane_id}')
# If second AI provided, split the AI pane vertically
if [[ -n $ai2 ]]; then
ai2_pane=$(tmux split-window -v -t "$ai_pane" -c "$current_dir" -P -F '#{pane_id}')
tmux send-keys -t "$ai2_pane" "$ai2" C-m
fi
# Run ai in the right pane
tmux send-keys -t "$ai_pane" "$ai" C-m
# Run nvim in the left pane
tmux send-keys -t "$editor_pane" "$EDITOR ." C-m
# Select the nvim pane for focus
tmux select-pane -t "$editor_pane"
}
# Create multiple tdl windows with one per subdirectory in the current directory
# Usage: tdlm <c|cx|codex|other_ai> [<second_ai>]
tdlm() {
[[ -z $1 ]] && { echo "Usage: tdlm <c|cx|codex|other_ai> [<second_ai>]"; return 1; }
[[ -z $TMUX ]] && { echo "You must start tmux to use tdlm."; return 1; }
local ai="$1"
local ai2="$2"
local base_dir="$PWD"
local first=true
# Rename the session to the current directory name (replace dots/colons which tmux disallows)
tmux rename-session "$(basename "$base_dir" | tr '.:' '--')"
for dir in "$base_dir"/*/; do
[[ -d $dir ]] || continue
local dirpath="${dir%/}"
if $first; then
# Reuse the current window for the first project
tmux send-keys -t "$TMUX_PANE" "cd '$dirpath' && tdl $ai $ai2" C-m
first=false
else
local pane_id=$(tmux new-window -c "$dirpath" -P -F '#{pane_id}')
tmux send-keys -t "$pane_id" "tdl $ai $ai2" C-m
fi
done
}
# Create a multi-pane swarm layout with the same command started in each pane (great for AI)
# Usage: tsl <pane_count> <command>
tsl() {
[[ -z $1 || -z $2 ]] && { echo "Usage: tsl <pane_count> <command>"; return 1; }
[[ -z $TMUX ]] && { echo "You must start tmux to use tsl."; return 1; }
local count="$1"
local cmd="$2"
local current_dir="${PWD}"
local -a panes
tmux rename-window -t "$TMUX_PANE" "$(basename "$current_dir")"
panes+=("$TMUX_PANE")
while (( ${#panes[@]} < count )); do
local new_pane
local split_target="${panes[-1]}"
new_pane=$(tmux split-window -h -t "$split_target" -c "$current_dir" -P -F '#{pane_id}')
panes+=("$new_pane")
tmux select-layout -t "${panes[0]}" tiled
done
for pane in "${panes[@]}"; do
tmux send-keys -t "$pane" "$cmd" C-m
done
tmux select-pane -t "${panes[0]}"
}

View File

@@ -0,0 +1,53 @@
# Transcode a video to a good-balance 1080p that's great for sharing online
transcode-video-1080p() {
ffmpeg -i "$1" -vf scale=1920:1080 -c:v libx264 -preset fast -crf 23 -c:a copy "${1%.*}-1080p.mp4"
}
# Transcode a video to a good-balance 4K that's great for sharing online
transcode-video-4K() {
ffmpeg -i "$1" -c:v libx265 -preset slow -crf 24 -c:a aac -b:a 192k "${1%.*}-optimized.mp4"
}
# Transcode any image to JPG image that's great for shrinking wallpapers
img2jpg() {
img="$1"
shift
magick "$img" "$@" -quality 85 -strip "${img%.*}-converted.jpg"
}
# Transcode any image to a small JPG (max 1080px wide)
img2jpg-small() {
img="$1"
shift
magick "$img" "$@" -resize 1080x\> -quality 85 -strip "${img%.*}-small.jpg"
}
# Transcode any image to a 4K JPG (max 2160px wide)
img2jpg-medium() {
img="$1"
shift
magick "$img" "$@" -resize 2160x\> -quality 85 -strip "${img%.*}-medium.jpg"
}
# Transcode any image to a 6K JPG (max 3160px wide)
img2jpg-large() {
img="$1"
shift
magick "$img" "$@" -resize 3160x\> -quality 85 -strip "${img%.*}-large.jpg"
}
# Transcode any image to compressed-but-lossless PNG
img2png() {
img="$1"
shift
magick "$img" "$@" -strip -define png:compression-filter=5 \
-define png:compression-level=9 \
-define png:compression-strategy=1 \
-define png:exclude-chunk=all \
"${img%.*}-optimized.png"
}

View File

@@ -0,0 +1,36 @@
# Create a new worktree and branch from within current git directory.
ga() {
if [[ -z "$1" ]]; then
echo "Usage: ga [branch name]"
return 1
fi
local branch="$1"
local base="$(basename "$PWD")"
local wt_path="../${base}--${branch}"
git worktree add -b "$branch" "$wt_path"
mise trust "$wt_path"
cd "$wt_path"
}
# Remove worktree and branch from within active worktree directory.
gd() {
if gum confirm "Remove worktree and branch?"; then
local cwd base branch root worktree
cwd="$(pwd)"
worktree="$(basename "$cwd")"
# split on first `--`
root="${worktree%%--*}"
branch="${worktree#*--}"
# Protect against accidentally nuking a non-worktree directory
if [[ "$root" != "$worktree" ]]; then
cd "../$root"
git worktree remove "$cwd" --force || return 1
git branch -D "$branch"
fi
fi
}

View File

@@ -0,0 +1 @@
for f in $HOME/.config/nomarchy/default/bash/fns/*; do source "$f"; done

View File

@@ -0,0 +1,24 @@
if command -v mise &> /dev/null; then
eval "$(mise activate bash)"
fi
if command -v starship &> /dev/null; then
eval "$(starship init bash)"
fi
if command -v zoxide &> /dev/null; then
eval "$(zoxide init bash)"
fi
if command -v try &> /dev/null; then
eval "$(SHELL=/bin/bash command try init ~/Work/tries)"
fi
if command -v fzf &> /dev/null; then
if [[ -f /usr/share/fzf/completion.bash ]]; then
source /usr/share/fzf/completion.bash
fi
if [[ -f /usr/share/fzf/key-bindings.bash ]]; then
source /usr/share/fzf/key-bindings.bash
fi
fi

View File

@@ -0,0 +1,47 @@
set meta-flag on
set input-meta on
set output-meta on
set convert-meta off
set completion-ignore-case on
set completion-prefix-display-length 2
set show-all-if-ambiguous on
set show-all-if-unmodified on
# Arrow keys match what you've typed so far against your command history
"\e[A": history-search-backward
"\e[B": history-search-forward
"\e[C": forward-char
"\e[D": backward-char
# Immediately add a trailing slash when autocompleting symlinks to directories
set mark-symlinked-directories on
# Do not autocomplete hidden files unless the pattern explicitly begins with a dot
set match-hidden-files off
# Show all autocomplete results at once
set page-completions off
# If there are more than 200 possible completions for a word, ask to show them all
set completion-query-items 200
# Show extra file information when completing, like `ls -F` does
set visible-stats on
# Be more intelligent when autocompleting by also looking at the text after
# the cursor. For example, when the current line is "cd ~/src/mozil", and
# the cursor is on the "z", pressing Tab will not autocomplete it to "cd
# ~/src/mozillail", but to "cd ~/src/mozilla". (This is supported by the
# Readline used by Bash 4.)
set skip-completed-text on
# Coloring for Bash 4 tab completions.
set colored-stats on
# Cycle forward and backward through completion candidates (tab/shift+tab)
# (completion listing and display behavior configured above)
TAB: menu-complete
"\e[Z": menu-complete-backward
# On first Tab, complete the common prefix before cycling candidates
set menu-complete-display-prefix on

View File

@@ -0,0 +1,6 @@
source ~/.config/nomarchy/default/bash/envs
source ~/.config/nomarchy/default/bash/shell
source ~/.config/nomarchy/default/bash/aliases
source ~/.config/nomarchy/default/bash/functions
source ~/.config/nomarchy/default/bash/init
[[ $- == *i* ]] && bind -f ~/.config/nomarchy/default/bash/inputrc

View File

@@ -0,0 +1,13 @@
# History control
shopt -s histappend
HISTCONTROL=ignoreboth
HISTSIZE=32768
HISTFILESIZE="${HISTSIZE}"
# Autocompletion
if [[ ! -v BASH_COMPLETION_VERSINFO && -f /usr/share/bash-completion/bash_completion ]]; then
source /usr/share/bash-completion/bash_completion
fi
# Ensure command hashing is off for mise
set +h

View File

@@ -0,0 +1,21 @@
chrome.commands.onCommand.addListener((command) => {
if (command === 'copy-url') {
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
const currentTab = tabs[0];
chrome.scripting.executeScript({
target: { tabId: currentTab.id },
func: () => {
navigator.clipboard.writeText(window.location.href);
}
}).then(() => {
chrome.notifications.create({
type: 'basic',
iconUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==',
title: ' URL copied to clipboard',
message: ''
});
});
});
}
});

View File

@@ -0,0 +1 @@
../../../../icon.png

View File

@@ -0,0 +1,19 @@
{
"manifest_version": 3,
"name": "Copy URL",
"version": "1.0",
"description": "Copy current URL to clipboard, this extension is installed by Nomarchy",
"permissions": ["activeTab", "scripting", "notifications"],
"icons": {
"16": "icon.png",
"48": "icon.png",
"128": "icon.png"
},
"commands": {
"copy-url": {
"suggested_key": {"default": "Alt+Shift+L"},
"description": "Copy URL"
}
},
"background": {"service_worker": "background.js"}
}

View File

@@ -0,0 +1,73 @@
Name = "nomarchyBackgroundSelector"
NamePretty = "Nomarchy Background Selector"
Cache = false
HideFromProviderlist = true
SearchName = true
local function ShellEscape(s)
return "'" .. s:gsub("'", "'\\''") .. "'"
end
function FormatName(filename)
-- Remove leading number and dash
local name = filename:gsub("^%d+", ""):gsub("^%-", "")
-- Remove extension
name = name:gsub("%.[^%.]+$", "")
-- Replace dashes with spaces
name = name:gsub("-", " ")
-- Capitalize each word
name = name:gsub("%S+", function(word)
return word:sub(1, 1):upper() .. word:sub(2):lower()
end)
return name
end
function GetEntries()
local entries = {}
local home = os.getenv("HOME")
-- Read current theme name
local theme_name_file = io.open(home .. "/.config/nomarchy/current/theme.name", "r")
local theme_name = theme_name_file and theme_name_file:read("*l") or nil
if theme_name_file then
theme_name_file:close()
end
-- Directories to search
local dirs = {
home .. "/.config/nomarchy/current/theme/backgrounds",
}
if theme_name then
table.insert(dirs, home .. "/.config/nomarchy/backgrounds/" .. theme_name)
end
-- Track added files to avoid duplicates
local seen = {}
for _, wallpaper_dir in ipairs(dirs) do
local handle = io.popen(
"find " .. ShellEscape(wallpaper_dir)
.. " -maxdepth 1 -type f \\( -name '*.jpg' -o -name '*.jpeg' -o -name '*.png' -o -name '*.gif' -o -name '*.bmp' -o -name '*.webp' \\) 2>/dev/null | sort"
)
if handle then
for background in handle:lines() do
local filename = background:match("([^/]+)$")
if filename and not seen[filename] then
seen[filename] = true
table.insert(entries, {
Text = FormatName(filename),
Value = background,
Actions = {
activate = "nomarchy-theme-bg-set " .. ShellEscape(background),
},
Preview = background,
PreviewType = "file",
})
end
end
handle:close()
end
end
return entries
end

View File

@@ -0,0 +1,95 @@
--
-- Dynamic Nomarchy Theme Menu for Elephant/Walker
--
Name = "nomarchythemes"
NamePretty = "Nomarchy Themes"
HideFromProviderlist = true
-- Check if file exists using Lua (no subprocess)
local function file_exists(path)
local f = io.open(path, "r")
if f then
f:close()
return true
end
return false
end
-- Get first matching file from directory using ls (single call for fallback)
local function first_image_in_dir(dir)
local handle = io.popen("ls -1 '" .. dir .. "' 2>/dev/null | head -n 1")
if handle then
local file = handle:read("*l")
handle:close()
if file and file ~= "" then
return dir .. "/" .. file
end
end
return nil
end
-- The main function elephant will call
function GetEntries()
local entries = {}
local user_theme_dir = os.getenv("HOME") .. "/.config/nomarchy/themes"
local default_theme_dir = os.getenv("HOME") .. "/.local/share/nomarchy/themes"
local seen_themes = {}
-- Helper function to process themes from a directory
local function process_themes_from_dir(theme_dir)
-- Single find call to get all theme directories
local handle = io.popen("find -L '" .. theme_dir .. "' -mindepth 1 -maxdepth 1 -type d 2>/dev/null")
if not handle then
return
end
for theme_path in handle:lines() do
local theme_name = theme_path:match(".*/(.+)$")
if theme_name and not seen_themes[theme_name] then
seen_themes[theme_name] = true
-- Check for preview images directly (no subprocess)
local preview_path = nil
local preview_png = theme_path .. "/preview.png"
local preview_jpg = theme_path .. "/preview.jpg"
if file_exists(preview_png) then
preview_path = preview_png
elseif file_exists(preview_jpg) then
preview_path = preview_jpg
else
-- Fallback: get first image from backgrounds (one ls call)
preview_path = first_image_in_dir(theme_path .. "/backgrounds")
end
if preview_path and preview_path ~= "" then
local display_name = theme_name:gsub("_", " "):gsub("%-", " ")
display_name = display_name:gsub("(%a)([%w_']*)", function(first, rest)
return first:upper() .. rest:lower()
end)
display_name = display_name .. " "
table.insert(entries, {
Text = display_name,
Preview = preview_path,
PreviewType = "file",
Actions = {
activate = "nomarchy-theme-set " .. theme_name,
},
})
end
end
end
handle:close()
end
-- Process user themes first (they take precedence)
process_themes_from_dir(user_theme_dir)
-- Then process default themes (only if not already seen)
process_themes_from_dir(default_theme_dir)
return entries
end

View File

@@ -0,0 +1,3 @@
window-padding-x = 0
window-padding-y = 0
window-padding-color = "extend-always"

View File

@@ -0,0 +1,7 @@
keyserver hkps://keyserver.ubuntu.com
keyserver hkps://pgp.surfnet.nl
keyserver hkps://keys.mailvelope.com
keyserver hkps://keyring.debian.org
keyserver hkps://pgp.mit.edu
connect-quick-timeout 4

View File

@@ -0,0 +1,9 @@
# App-specific tweaks
source = ~/.config/nomarchy/default/hypr/apps/browser.conf
source = ~/.config/nomarchy/default/hypr/apps/hyprshot.conf
source = ~/.config/nomarchy/default/hypr/apps/localsend.conf
source = ~/.config/nomarchy/default/hypr/apps/retroarch.conf
source = ~/.config/nomarchy/default/hypr/apps/telegram.conf
source = ~/.config/nomarchy/default/hypr/apps/terminals.conf
source = ~/.config/nomarchy/default/hypr/apps/walker.conf
source = ~/.config/nomarchy/default/hypr/apps/webcam-overlay.conf

View File

@@ -0,0 +1,2 @@
windowrulev2 = noscreenshare, class:^(1[p|P]assword)$
windowrulev2 = tag +floating-window, class:^(1[p|P]assword)$

View File

@@ -0,0 +1,6 @@
windowrulev2 = noscreenshare, class:^(Bitwarden)$
windowrulev2 = tag +floating-window, class:^(Bitwarden)$
# Bitwarden Chrome Extension
windowrulev2 = noscreenshare, class:chrome-nngceckbapebfimnlniiiahkandclblb-Default
windowrulev2 = tag +floating-window, class:chrome-nngceckbapebfimnlniiiahkandclblb-Default

View File

@@ -0,0 +1,16 @@
# Browser types
windowrulev2 = tag +chromium-based-browser, class:((google-)?[cC]hrom(e|ium)|[bB]rave-browser|[mM]icrosoft-edge|Vivaldi-stable|helium)
windowrulev2 = tag +firefox-based-browser, class:([fF]irefox|zen|librewolf)
windowrulev2 = tag -default-opacity, tag:chromium-based-browser
windowrulev2 = tag -default-opacity, tag:firefox-based-browser
# Video apps: remove chromium browser tag so they don't get opacity applied
windowrulev2 = tag -chromium-based-browser, class:(chrome-youtube.com__-Default|chrome-app.zoom.us__wc_home-Default)
windowrulev2 = tag -default-opacity, class:(chrome-youtube.com__-Default|chrome-app.zoom.us__wc_home-Default)
# Force chromium-based browsers into a tile to deal with --app bug
windowrulev2 = tile, tag:chromium-based-browser
# Only a subtle opacity change, but not for video sites
windowrulev2 = opacity 1.0 0.97, tag:chromium-based-browser
windowrulev2 = opacity 1.0 0.97, tag:firefox-based-browser

View File

@@ -0,0 +1,2 @@
# Focus floating DaVinci Resolve dialog windows
windowrulev2 = stayfocused, class:.*[Rr]esolve.*, float:1

View File

@@ -0,0 +1,5 @@
windowrule {
name = geforce
class:= GeForceNOW
idle_inhibit = fullscreen
}

View File

@@ -0,0 +1,2 @@
# Remove 1px border around hyprshot screenshots
layerrule = noanim, namespace:selection

View File

@@ -0,0 +1,4 @@
# Float LocalSend and fzf file picker
windowrulev2 = float, class:(Share|localsend)
windowrulev2 = center, class:(Share|localsend)
windowrulev2 = size 1100 700, class:localsend

View File

@@ -0,0 +1,6 @@
windowrule {
name = moonlight
class:= com.moonlight_stream.Moonlight
fullscreen = 1
idle_inhibit = fullscreen
}

View File

@@ -0,0 +1,10 @@
# Picture-in-picture overlays
windowrulev2 = tag +pip, title:(Picture.?in.?[Pp]icture)
windowrulev2 = tag -default-opacity, tag:pip
windowrulev2 = float, tag:pip
windowrulev2 = pin, tag:pip
windowrulev2 = size 600 338, tag:pip
windowrulev2 = keep_aspect_ratio on, tag:pip
windowrulev2 = border_size 0, tag:pip
windowrulev2 = opacity 1 1, tag:pip
windowrulev2 = move (monitor_w-window_w-40) (monitor_h*0.04), tag:pip

View File

@@ -0,0 +1,2 @@
windowrulev2 = tag -default-opacity, class:qemu
windowrulev2 = opacity 1 1, class:qemu

View File

@@ -0,0 +1,4 @@
windowrulev2 = fullscreen, class:com.libretro.RetroArch
windowrulev2 = tag -default-opacity, class:com.libretro.RetroArch
windowrulev2 = opacity 1 1, class:com.libretro.RetroArch
windowrulev2 = idleinhibit, fullscreen, class:com.libretro.RetroArch

View File

@@ -0,0 +1,8 @@
# Float Steam
windowrulev2 = float, class:steam
windowrulev2 = center, class:steam, title:Steam
windowrulev2 = tag -default-opacity, class:steam.*
windowrulev2 = opacity 1 1, class:steam.*
windowrulev2 = size 1100 700, class:steam, title:Steam
windowrulev2 = size 460 800, class:steam, title:Friends List
windowrulev2 = idleinhibit, fullscreen, class:steam

View File

@@ -0,0 +1,23 @@
# Floating windows
windowrulev2 = float, tag:floating-window
windowrulev2 = center, tag:floating-window
windowrulev2 = size 875 600, tag:floating-window
windowrulev2 = tag +floating-window, class:(org.nomarchy.bluetui|org.nomarchy.impala|org.nomarchy.wiremix|org.nomarchy.btop|org.nomarchy.terminal|org.nomarchy.bash|org.gnome.NautilusPreviewer|org.gnome.Evince|com.gabm.satty|Nomarchy|About|TUI.float|imv|mpv)
windowrulev2 = tag +floating-window, class:(xdg-desktop-portal-gtk|sublime_text|DesktopEditors|org.gnome.Nautilus), title:^(Open.*Files?|Open [F|f]older.*|Save.*Files?|Save.*As|Save|All Files|.*wants to [open|save].*|[C|c]hoose.*)
windowrulev2 = float, class:org.gnome.Calculator
# Fullscreen screensaver
windowrulev2 = fullscreen, class:org.nomarchy.screensaver
windowrulev2 = float, class:org.nomarchy.screensaver
windowrulev2 = animation slide, class:org.nomarchy.screensaver
# No transparency on media windows
windowrulev2 = tag -default-opacity, class:^(zoom|vlc|mpv|org.kde.kdenlive|com.obsproject.Studio|com.github.PintaProject.Pinta|imv|org.gnome.NautilusPreviewer)$
windowrulev2 = opacity 1 1, class:^(zoom|vlc|mpv|org.kde.kdenlive|com.obsproject.Studio|com.github.PintaProject.Pinta|imv|org.gnome.NautilusPreviewer)$
# Popped window rounding
windowrulev2 = rounding 8, tag:pop
# Prevent idle while open
windowrulev2 = idleinhibit, always, tag:noidle

View File

@@ -0,0 +1,2 @@
# Prevent Telegram from stealing focus on new messages
windowrulev2 = focusonactivate off, class:org.telegram.desktop

View File

@@ -0,0 +1,4 @@
# Define terminal tag to style them uniformly
windowrulev2 = tag +terminal, class:(Alacritty|kitty|com.mitchellh.ghostty)
windowrulev2 = tag -default-opacity, tag:terminal
windowrulev2 = opacity 0.97 0.9, tag:terminal

View File

@@ -0,0 +1,2 @@
# Application-specific animation
layerrule = noanim, namespace:walker

View File

@@ -0,0 +1,6 @@
# Webcam overlay for screen recording
windowrulev2 = float, title:WebcamOverlay
windowrulev2 = pin, title:WebcamOverlay
windowrulev2 = noinitialfocus, title:WebcamOverlay
windowrulev2 = nodim, title:WebcamOverlay
windowrulev2 = move (monitor_w-window_w-40) (monitor_h-window_h-40), title:WebcamOverlay

View File

@@ -0,0 +1,17 @@
exec-once = uwsm-app -- hypridle
exec-once = uwsm-app -- mako
exec-once = nomarchy-welcome
# exec-once = uwsm-app -- waybar
# fcitx5 is autostarted by NixOS's i18n.inputMethod when
# nomarchy.system.inputMethod.enable = true; no manual exec-once needed.
# swaybg is started as a systemd user service (nomarchy-wallpaper.service)
# so failures surface in logs and the wallpaper survives Hyprland restarts.
exec-once = uwsm-app -- swayosd-server
exec-once = nomarchy-on-boot
# Slow app launch fix -- set systemd vars
exec-once = systemctl --user import-environment $(env | cut -d'=' -f 1)
exec-once = dbus-update-activation-environment --systemd --all
# Network Manager
exec-once = uwsm-app 'nm-applet --indicator'

View File

@@ -0,0 +1,16 @@
# Deprecated bindings file. New installations include everything directly.
bindd = SUPER, RETURN, Terminal, exec, $terminal
bindd = SUPER, F, File manager, exec, $fileManager
bindd = SUPER, B, Web browser, exec, $browser
bindd = SUPER, M, Music player, exec, $music
bindd = SUPER, N, Neovim, exec, $terminal -e nvim
bindd = SUPER, T, Top, exec, $terminal -e btop
bindd = SUPER, D, Lazy Docker, exec, $terminal -e lazydocker
bindd = SUPER, G, Messenger, exec, $messenger
bindd = SUPER, O, Obsidian, exec, obsidian -disable-gpu
bindd = SUPER, SLASH, Password manager, exec, $passwordManager
source = ~/.config/nomarchy/default/hypr/bindings/media.conf
source = ~/.config/nomarchy/default/hypr/bindings/tiling-v2.conf
source = ~/.config/nomarchy/default/hypr/bindings/utilities.conf

View File

@@ -0,0 +1,5 @@
# Copy / Paste
bindd = SUPER, C, Universal copy, sendshortcut, CTRL, Insert,
bindd = SUPER, V, Universal paste, sendshortcut, SHIFT, Insert,
bindd = SUPER, X, Universal cut, sendshortcut, CTRL, X,
bindd = SUPER CTRL, V, Clipboard manager, exec, nomarchy-launch-walker -m clipboard

View File

@@ -0,0 +1,28 @@
# Only display the OSD on the currently focused monitor
$osdclient = swayosd-client --monitor "$(hyprctl monitors -j | jq -r '.[] | select(.focused == true).name')"
# Laptop multimedia keys for volume and LCD brightness (with OSD)
bindeld = ,XF86AudioRaiseVolume, Volume up, exec, $osdclient --output-volume raise
bindeld = ,XF86AudioLowerVolume, Volume down, exec, $osdclient --output-volume lower
bindeld = ,XF86AudioMute, Mute, exec, $osdclient --output-volume mute-toggle
bindeld = ,XF86AudioMicMute, Mute microphone, exec, $osdclient --input-volume mute-toggle
bindeld = ,XF86MonBrightnessUp, Brightness up, exec, nomarchy-brightness-display +5%
bindeld = ,XF86MonBrightnessDown, Brightness down, exec, nomarchy-brightness-display 5%-
bindeld = ,XF86KbdBrightnessUp, Keyboard brightness up, exec, nomarchy-brightness-keyboard up
bindeld = ,XF86KbdBrightnessDown, Keyboard brightness down, exec, nomarchy-brightness-keyboard down
bindld = ,XF86KbdLightOnOff, Keyboard backlight cycle, exec, nomarchy-brightness-keyboard cycle
# Precise 1% multimedia adjustments with Alt modifier
bindeld = ALT, XF86AudioRaiseVolume, Volume up precise, exec, $osdclient --output-volume +1
bindeld = ALT, XF86AudioLowerVolume, Volume down precise, exec, $osdclient --output-volume -1
bindeld = ALT, XF86MonBrightnessUp, Brightness up precise, exec, nomarchy-brightness-display +1%
bindeld = ALT, XF86MonBrightnessDown, Brightness down precise, exec, nomarchy-brightness-display 1%-
# Requires playerctl
bindld = , XF86AudioNext, Next track, exec, $osdclient --playerctl next
bindld = , XF86AudioPause, Pause, exec, $osdclient --playerctl play-pause
bindld = , XF86AudioPlay, Play, exec, $osdclient --playerctl play-pause
bindld = , XF86AudioPrev, Previous track, exec, $osdclient --playerctl previous
# Switch audio output with Super + Mute
bindld = SUPER, XF86AudioMute, Switch audio output, exec, nomarchy-cmd-audio-switch

View File

@@ -0,0 +1,128 @@
# Close windows
bindd = SUPER, W, Close window, killactive,
bindd = CTRL ALT, DELETE, Close all windows, exec, nomarchy-hyprland-window-close-all
# Control tiling
bindd = SUPER, J, Toggle window split, layoutmsg, togglesplit
bindd = SUPER, P, Pseudo window, pseudo, # dwindle
bindd = SUPER, T, Toggle window floating/tiling, togglefloating,
bindd = SUPER, F, Full screen, fullscreen, 0
bindd = SUPER CTRL, F, Tiled full screen, fullscreenstate, 0 2
bindd = SUPER ALT, F, Full width, fullscreen, 1
bindd = SUPER, O, Pop window out (float & pin), exec, nomarchy-hyprland-window-pop
bindd = SUPER, L, Toggle workspace layout, exec, nomarchy-hyprland-workspace-layout-toggle
# Move focus with SUPER + arrow keys
bindd = SUPER, LEFT, Move window focus left, movefocus, l
bindd = SUPER, RIGHT, Move window focus right, movefocus, r
bindd = SUPER, UP, Move window focus up, movefocus, u
bindd = SUPER, DOWN, Move window focus down, movefocus, d
# Switch workspaces with SUPER + [1-9; 0]
bindd = SUPER, code:10, Switch to workspace 1, workspace, 1
bindd = SUPER, code:11, Switch to workspace 2, workspace, 2
bindd = SUPER, code:12, Switch to workspace 3, workspace, 3
bindd = SUPER, code:13, Switch to workspace 4, workspace, 4
bindd = SUPER, code:14, Switch to workspace 5, workspace, 5
bindd = SUPER, code:15, Switch to workspace 6, workspace, 6
bindd = SUPER, code:16, Switch to workspace 7, workspace, 7
bindd = SUPER, code:17, Switch to workspace 8, workspace, 8
bindd = SUPER, code:18, Switch to workspace 9, workspace, 9
bindd = SUPER, code:19, Switch to workspace 10, workspace, 10
# Move active window to a workspace with SUPER + SHIFT + [1-9; 0]
bindd = SUPER SHIFT, code:10, Move window to workspace 1, movetoworkspace, 1
bindd = SUPER SHIFT, code:11, Move window to workspace 2, movetoworkspace, 2
bindd = SUPER SHIFT, code:12, Move window to workspace 3, movetoworkspace, 3
bindd = SUPER SHIFT, code:13, Move window to workspace 4, movetoworkspace, 4
bindd = SUPER SHIFT, code:14, Move window to workspace 5, movetoworkspace, 5
bindd = SUPER SHIFT, code:15, Move window to workspace 6, movetoworkspace, 6
bindd = SUPER SHIFT, code:16, Move window to workspace 7, movetoworkspace, 7
bindd = SUPER SHIFT, code:17, Move window to workspace 8, movetoworkspace, 8
bindd = SUPER SHIFT, code:18, Move window to workspace 9, movetoworkspace, 9
bindd = SUPER SHIFT, code:19, Move window to workspace 10, movetoworkspace, 10
# Move active window silently to a workspace with SUPER + SHIFT + ALT + [1-9; 0]
bindd = SUPER SHIFT ALT, code:10, Move window silently to workspace 1, movetoworkspacesilent, 1
bindd = SUPER SHIFT ALT, code:11, Move window silently to workspace 2, movetoworkspacesilent, 2
bindd = SUPER SHIFT ALT, code:12, Move window silently to workspace 3, movetoworkspacesilent, 3
bindd = SUPER SHIFT ALT, code:13, Move window silently to workspace 4, movetoworkspacesilent, 4
bindd = SUPER SHIFT ALT, code:14, Move window silently to workspace 5, movetoworkspacesilent, 5
bindd = SUPER SHIFT ALT, code:15, Move window silently to workspace 6, movetoworkspacesilent, 6
bindd = SUPER SHIFT ALT, code:16, Move window silently to workspace 7, movetoworkspacesilent, 7
bindd = SUPER SHIFT ALT, code:17, Move window silently to workspace 8, movetoworkspacesilent, 8
bindd = SUPER SHIFT ALT, code:18, Move window silently to workspace 9, movetoworkspacesilent, 9
bindd = SUPER SHIFT ALT, code:19, Move window silently to workspace 10, movetoworkspacesilent, 10
# Control scratchpad
bindd = SUPER, S, Toggle scratchpad, togglespecialworkspace, scratchpad
bindd = SUPER ALT, S, Move window to scratchpad, movetoworkspacesilent, special:scratchpad
# TAB between workspaces
bindd = SUPER, TAB, Next workspace, workspace, e+1
bindd = SUPER SHIFT, TAB, Previous workspace, workspace, e-1
bindd = SUPER CTRL, TAB, Former workspace, workspace, previous
# Move workspaces to other monitors
bindd = SUPER SHIFT ALT, LEFT, Move workspace to left monitor, movecurrentworkspacetomonitor, l
bindd = SUPER SHIFT ALT, RIGHT, Move workspace to right monitor, movecurrentworkspacetomonitor, r
bindd = SUPER SHIFT ALT, UP, Move workspace to up monitor, movecurrentworkspacetomonitor, u
bindd = SUPER SHIFT ALT, DOWN, Move workspace to down monitor, movecurrentworkspacetomonitor, d
# Swap active window with the one next to it with SUPER + SHIFT + arrow keys
bindd = SUPER SHIFT, LEFT, Swap window to the left, swapwindow, l
bindd = SUPER SHIFT, RIGHT, Swap window to the right, swapwindow, r
bindd = SUPER SHIFT, UP, Swap window up, swapwindow, u
bindd = SUPER SHIFT, DOWN, Swap window down, swapwindow, d
# Cycle through applications on active workspace
bindd = ALT, TAB, Cycle to next window, cyclenext
bindd = ALT SHIFT, TAB, Cycle to prev window, cyclenext, prev
bindd = ALT, TAB, Reveal active window on top, bringactivetotop
bindd = ALT SHIFT, TAB, Reveal active window on top, bringactivetotop
# Resize active window
bindd = SUPER, code:20, Expand window left, resizeactive, -100 0 # - key
bindd = SUPER, code:21, Shrink window left, resizeactive, 100 0 # = key
bindd = SUPER SHIFT, code:20, Shrink window up, resizeactive, 0 -100
bindd = SUPER SHIFT, code:21, Expand window down, resizeactive, 0 100
# Scroll through existing workspaces with SUPER + scroll
bindd = SUPER, mouse_down, Scroll active workspace forward, workspace, e+1
bindd = SUPER, mouse_up, Scroll active workspace backward, workspace, e-1
# Move/resize windows with mainMod + LMB/RMB and dragging
bindmd = SUPER, mouse:272, Move window, movewindow
bindmd = SUPER, mouse:273, Resize window, resizewindow
# Toggle groups
bindd = SUPER, G, Toggle window grouping, togglegroup
bindd = SUPER ALT, G, Move active window out of group, moveoutofgroup
# Join groups
bindd = SUPER ALT, LEFT, Move window to group on left, moveintogroup, l
bindd = SUPER ALT, RIGHT, Move window to group on right, moveintogroup, r
bindd = SUPER ALT, UP, Move window to group on top, moveintogroup, u
bindd = SUPER ALT, DOWN, Move window to group on bottom, moveintogroup, d
# Navigate a single set of grouped windows
bindd = SUPER ALT, TAB, Next window in group, changegroupactive, f
bindd = SUPER ALT SHIFT, TAB, Previous window in group, changegroupactive, b
# Window navigation for grouped windows
bindd = SUPER CTRL, LEFT, Move grouped window focus left, changegroupactive, b
bindd = SUPER CTRL, RIGHT, Move grouped window focus right, changegroupactive, f
# Scroll through a set of grouped windows with SUPER + ALT + scroll
bindd = SUPER ALT, mouse_down, Next window in group, changegroupactive, f
bindd = SUPER ALT, mouse_up, Previous window in group, changegroupactive, b
# Activate window in a group by number
bindd = SUPER ALT, code:10, Switch to group window 1, changegroupactive, 1
bindd = SUPER ALT, code:11, Switch to group window 2, changegroupactive, 2
bindd = SUPER ALT, code:12, Switch to group window 3, changegroupactive, 3
bindd = SUPER ALT, code:13, Switch to group window 4, changegroupactive, 4
bindd = SUPER ALT, code:14, Switch to group window 5, changegroupactive, 5
# Cycle monitor scaling
bindd = SUPER, Slash, Cycle monitor scaling, exec, nomarchy-hyprland-monitor-scaling-cycle

View File

@@ -0,0 +1,62 @@
# Menus
bindd = SUPER, SPACE, Launch apps, exec, nomarchy-launch-walker
bindd = SUPER CTRL, E, Emoji picker, exec, nomarchy-menu symbols
bindd = SUPER CTRL, C, Capture menu, exec, nomarchy-menu capture
bindd = SUPER CTRL, O, Toggle menu, exec, nomarchy-menu toggle
bindd = SUPER ALT, SPACE, Toggle top bar, exec, nomarchy-toggle-waybar
bindd = SUPER, ESCAPE, System menu, exec, nomarchy-menu system
bindld = , XF86PowerOff, Power menu, exec, nomarchy-menu system
bindd = SUPER, K, Show key bindings, exec, nomarchy-menu-keybindings
bindd = , XF86Calculator, Calculator, exec, gnome-calculator
# Aesthetics
bindd = SUPER SHIFT, SPACE, Nomarchy menu, exec, nomarchy-menu
bindd = SUPER CTRL, SPACE, Theme background menu, exec, nomarchy-menu background
bindd = SUPER SHIFT CTRL, SPACE, Theme menu, exec, nomarchy-menu theme
bindd = SUPER, BACKSPACE, Toggle window transparency, exec, nomarchy-hyprland-active-window-transparency-toggle
bindd = SUPER SHIFT, BACKSPACE, Toggle window gaps, exec, nomarchy-hyprland-window-gaps-toggle
bindd = SUPER CTRL, BACKSPACE, Toggle single-window square aspect, exec, nomarchy-hyprland-window-single-square-aspect-toggle
# Notifications
bindd = SUPER, COMMA, Dismiss last notification, exec, makoctl dismiss
bindd = SUPER SHIFT, COMMA, Dismiss all notifications, exec, makoctl dismiss --all
bindd = SUPER CTRL, COMMA, Toggle silencing notifications, exec, nomarchy-toggle-notification-silencing
bindd = SUPER ALT, COMMA, Invoke last notification, exec, makoctl invoke
bindd = SUPER SHIFT ALT, COMMA, Restore last notification, exec, makoctl restore
# Toggles
bindd = SUPER CTRL, I, Toggle locking on idle, exec, nomarchy-toggle-idle
bindd = SUPER CTRL, N, Toggle nightlight, exec, nomarchy-toggle-nightlight
# Control Apple Display brightness
bindd = CTRL, F1, Apple Display brightness down, exec, nomarchy-brightness-display-apple -5000
bindd = CTRL, F2, Apple Display brightness up, exec, nomarchy-brightness-display-apple +5000
bindd = SHIFT CTRL, F2, Apple Display full brightness, exec, nomarchy-brightness-display-apple +60000
# Captures
bindd = , PRINT, Screenshot, exec, nomarchy-cmd-screenshot
bindd = ALT, PRINT, Screenrecording, exec, nomarchy-menu screenrecord
bindd = SUPER, PRINT, Color picker, exec, pkill hyprpicker || hyprpicker -a
# File sharing
bindd = SUPER CTRL, S, Share, exec, nomarchy-menu share
# Waybar-less information
bindd = SUPER CTRL ALT, T, Show time, exec, notify-send -u low " $(date +"%A %H:%M · %d %B %Y · Week %V")"
bindd = SUPER CTRL ALT, B, Show battery remaining, exec, notify-send -u low "$(nomarchy-battery-status)"
# Control panels
bindd = SUPER CTRL, A, Audio controls, exec, nomarchy-launch-audio
bindd = SUPER CTRL, B, Bluetooth controls, exec, nomarchy-launch-bluetooth
bindd = SUPER CTRL, W, Wifi controls, exec, nomarchy-launch-wifi
bindd = SUPER CTRL, T, Activity, exec, nomarchy-launch-tui btop
# Dictation
bindd = SUPER CTRL, X, Toggle dictation, exec, voxtype record toggle
# Zoom
bindd = SUPER CTRL, Z, Zoom in, exec, hyprctl keyword cursor:zoom_factor $(hyprctl getoption cursor:zoom_factor -j | jq '.float + 1')
bindd = SUPER CTRL ALT, Z, Reset zoom, exec, hyprctl keyword cursor:zoom_factor 1
# Lock system
bindd = SUPER CTRL, L, Lock system, exec, nomarchy-lock-screen

View File

@@ -0,0 +1,12 @@
# Hyprland Color Configuration (Visual)
# This file contains only color-related settings.
# It's sourced AFTER theme colors are applied, allowing theme-specific overrides.
# These are placeholder values - actual colors come from the active theme
# via ~/.config/nomarchy/current/theme/hyprland.conf
# Border colors (set by theme-loader based on active theme)
# The theme's hyprland.conf will define $activeBorderColor and $inactiveBorderColor
# Group bar colors (set by theme)
# Themes can override group bar colors in their hyprland.conf

View File

@@ -0,0 +1,29 @@
# Cursor size
env = XCURSOR_SIZE,24
env = HYPRCURSOR_SIZE,24
# Force all apps to use Wayland
env = GDK_BACKEND,wayland,x11,*
env = QT_QPA_PLATFORM,wayland;xcb
env = QT_STYLE_OVERRIDE,kvantum
env = SDL_VIDEODRIVER,wayland,x11
env = MOZ_ENABLE_WAYLAND,1
env = ELECTRON_OZONE_PLATFORM_HINT,wayland
env = OZONE_PLATFORM,wayland
env = XDG_SESSION_TYPE,wayland
# Allow better support for screen sharing (Google Meet, Discord, etc)
env = XDG_CURRENT_DESKTOP,Hyprland
env = XDG_SESSION_DESKTOP,Hyprland
xwayland {
force_zero_scaling = true
}
# Use XCompose file
env = XCOMPOSEFILE,~/.XCompose
# Don't show update on first launch
ecosystem {
no_update_news = true
}

View File

@@ -0,0 +1,21 @@
# https://wiki.hyprland.org/Configuring/Variables/#input
input {
kb_layout = us
kb_variant =
kb_model =
kb_options = compose:caps
kb_rules =
follow_mouse = 1
sensitivity = 0 # -1.0 - 1.0, 0 means no modification.
touchpad {
natural_scroll = false
}
}
misc {
key_press_enables_dpms = true # key press will trigger wake
mouse_move_enables_dpms = true # mouse move will trigger wake
}

View File

@@ -0,0 +1,146 @@
# Refer to https://wiki.hyprland.org/Configuring/Variables/
# Variables
$activeBorderColor = rgba(33ccffee) rgba(00ff99ee) 45deg
$inactiveBorderColor = rgba(595959aa)
# https://wiki.hyprland.org/Configuring/Variables/#general
general {
gaps_in = 5
gaps_out = 10
border_size = 2
# https://wiki.hyprland.org/Configuring/Variables/#variable-types for info about colors
col.active_border = $activeBorderColor
col.inactive_border = $inactiveBorderColor
# Set to true enable resizing windows by clicking and dragging on borders and gaps
resize_on_border = false
# Please see https://wiki.hyprland.org/Configuring/Tearing/ before you turn this on
allow_tearing = false
layout = dwindle
}
# https://wiki.hyprland.org/Configuring/Variables/#decoration
decoration {
rounding = 0
shadow {
enabled = true
range = 2
render_power = 3
color = rgba(1a1a1aee)
}
# https://wiki.hyprland.org/Configuring/Variables/#blur
blur {
enabled = true
size = 2
passes = 2
special = true
brightness = 0.60
contrast = 0.75
}
}
# https://wiki.hypr.land/Configuring/Variables/#group
group {
col.border_active = $activeBorderColor
col.border_inactive = $inactiveBorderColor
col.border_locked_active = -1
col.border_locked_inactive = -1
groupbar {
font_size = 12
font_family = monospace
font_weight_active = ultraheavy
font_weight_inactive = normal
indicator_height = 0
indicator_gap = 5
height = 22
gaps_in = 5
gaps_out = 0
text_color = rgb(ffffff)
text_color_inactive = rgba(ffffff90)
col.active = rgba(00000040)
col.inactive = rgba(00000020)
gradients = true
gradient_rounding = 0
gradient_round_only_edges = false
}
}
# https://wiki.hyprland.org/Configuring/Variables/#animations
animations {
enabled = yes
# Default animations, see https://wiki.hyprland.org/Configuring/Animations/ for more
bezier = easeOutQuint,0.23,1,0.32,1
bezier = easeInOutCubic,0.65,0.05,0.36,1
bezier = linear,0,0,1,1
bezier = almostLinear,0.5,0.5,0.75,1.0
bezier = quick,0.15,0,0.1,1
animation = global, 1, 10, default
animation = border, 1, 5.39, easeOutQuint
animation = windows, 1, 4.79, easeOutQuint
animation = windowsIn, 1, 4.1, easeOutQuint, popin 87%
animation = windowsOut, 1, 1.49, linear, popin 87%
animation = fadeIn, 1, 1.73, almostLinear
animation = fadeOut, 1, 1.46, almostLinear
animation = fade, 1, 3.03, quick
animation = layers, 1, 3.81, easeOutQuint
animation = layersIn, 1, 4, easeOutQuint, fade
animation = layersOut, 1, 1.5, linear, fade
animation = fadeLayersIn, 1, 1.79, almostLinear
animation = fadeLayersOut, 1, 1.39, almostLinear
animation = workspaces, 0, 0, ease
animation = specialWorkspace, 1, 4, easeOutQuint, slidevert
}
# See https://wiki.hyprland.org/Configuring/Dwindle-Layout/ for more
dwindle {
pseudotile = true # Master switch for pseudotiling. Enabling is bound to mainMod + P in the keybinds section below
preserve_split = true # You probably want this
force_split = 2 # Always split on the right
}
# See https://wiki.hyprland.org/Configuring/Master-Layout/ for more
master {
new_status = master
}
# https://wiki.hyprland.org/Configuring/Variables/#misc
misc {
disable_hyprland_logo = true
disable_splash_rendering = true
disable_scale_notification = true
focus_on_activate = true
}
# https://wiki.hypr.land/Configuring/Variables/#cursor
cursor {
hide_on_key_press = true
warp_on_change_workspace = 1
}
# Auto toggle scratchpad on switching workspace from scratchpad
binds {
hide_special_on_workspace_change = true
}
# Style Gum confirm to match terminal theme
env = GUM_CONFIRM_PROMPT_FOREGROUND,6 # Cyan
env = GUM_CONFIRM_SELECTED_FOREGROUND,0 # Black
env = GUM_CONFIRM_SELECTED_BACKGROUND,2 # Green
env = GUM_CONFIRM_UNSELECTED_FOREGROUND,7 # White
env = GUM_CONFIRM_UNSELECTED_BACKGROUND,8 # Dark grey
# Dark grey

View File

@@ -0,0 +1,16 @@
# Application bindings
bindd = SUPER, RETURN, Terminal, exec, uwsm-app -- xdg-terminal-exec --dir="$(nomarchy-cmd-terminal-cwd)"
bindd = SUPER SHIFT, RETURN, Browser, exec, nomarchy-launch-browser
bindd = SUPER SHIFT, F, File manager, exec, uwsm-app -- nautilus --new-window
bindd = SUPER ALT SHIFT, F, File manager (cwd), exec, uwsm-app -- nautilus --new-window "$(nomarchy-cmd-terminal-cwd)"
bindd = SUPER SHIFT, B, Browser, exec, nomarchy-launch-browser
bindd = SUPER SHIFT ALT, B, Browser (private), exec, nomarchy-launch-browser --private
bindd = SUPER SHIFT, N, Editor, exec, nomarchy-launch-editor
# Add extra bindings
# bindd = SUPER SHIFT, A, ChatGPT, exec, nomarchy-launch-webapp "https://chatgpt.com"
# bindd = SUPER SHIFT, R, exec, alacritty -e ssh your-server
# Overwrite existing bindings, like putting Nomarchy Menu on Super + Space
# unbind = SUPER, SPACE
# bindd = SUPER, SPACE, Nomarchy menu, exec, nomarchy-menu

View File

@@ -0,0 +1,15 @@
# See https://wiki.hyprland.org/Configuring/Window-Rules/ for more
# Hyprland 0.53+ syntax
#windowrulev2 = suppressevent maximize, class:.*
# Tag all windows for default opacity (apps can override with -default-opacity tag)
#windowrulev2 = tag +default-opacity, class:.*
# Fix some dragging issues with XWayland
#windowrulev2 = nofocus, class:^$, title:^$, xwayland:1, float:1, fullscreen:0, pin:0
# App-specific tweaks (may remove default-opacity tag)
source = ~/.config/nomarchy/default/hypr/apps.conf
# Apply default opacity after apps have had a chance to opt out
#windowrulev2 = opacity 0.97 0.9, tag:default-opacity

View File

@@ -0,0 +1,34 @@
anchor=top-right
default-timeout=5000
width=420
outer-margin=20
padding=10,15
border-size=2
max-icon-size=32
font=sans-serif 14px
[app-name=Spotify]
invisible=1
[mode=do-not-disturb]
invisible=true
[mode=do-not-disturb app-name=notify-send]
invisible=false
[urgency=critical]
default-timeout=0
layer=overlay
[summary~="Setup Wi-Fi"]
on-button-left=exec sh -c 'nomarchy-notification-dismiss "Setup Wi-Fi"; nomarchy-launch-wifi'
[summary~="Update System"]
on-button-left=exec sh -c 'nomarchy-notification-dismiss "Update System"; nomarchy-launch-floating-terminal-with-presentation nomarchy-update'
[summary~="Learn Keybindings"]
on-button-left=exec sh -c 'nomarchy-notification-dismiss "Learn Keybindings"; nomarchy-menu-keybindings'
[summary~="Screenshot copied & saved"]
max-icon-size=80
format=<b>%s</b>\n%b

Some files were not shown because too many files have changed in this diff Show More