Commit Graph

261 Commits

Author SHA1 Message Date
7ebfab4bd4 fix(docking): make clamshell hotplug safe and discoverable
All checks were successful
Check / eval (push) Successful in 3m32s
Real dock QA exposed pointer-bound menus, an absent default rescue watcher, the unsafe zero-output panel-off path, missing keyboard/audio hotplug coverage, and unpainted new outputs.

Route Rofi to the focused output; replace panel disable with safe workspace migration; always run and reconnect the display watcher and repaint hotplugged outputs; expose a default per-device keyboard picker; handle audio card availability transitions.

Verified: V1 — nix flake check --no-build; checks.docking-ux real Home Manager artifact build; option-docs/template-sot; Nix parse, py_compile, and git diff --check. V2 unavailable: no readable KVM. V3 pending in agent/HARDWARE-QUEUE.md.
2026-07-13 11:53:01 +01:00
7aae204014 feat(fingerprint): password OR fingerprint in parallel at one prompt
Some checks failed
Check / eval (push) Failing after 2m45s
Bernardo promoted the PROPOSED item live: with fingerprint PAM on,
sudo/login should accept whichever factor comes first instead of
pam_fprintd's wait-for-the-reader-then-password. Stock PAM cannot
express parallel factors (linux-pam#301), so this packages
pam-fprint-grosshack v0.3.0 (pkgs/, pinned from GitLab — the
field-standard fprintd fork), source-reviewed before packaging: every
failure path (no reader, no prints, fprintd absent/hung, timeout,
password typed) returns PAM_AUTHINFO_UNAVAIL and falls through; a
typed password is only ferried via PAM_AUTHTOK to the stock
`auth sufficient pam_unix.so … try_first_pass` rule — the module never
validates passwords itself, so it cannot lock out password login.

New option nomarchy.hardware.fingerprint.parallel, default TRUE (the
better UX is what opting into fingerprint PAM buys; false = stock
sequential). Wiring swaps the modulePath of stock fprintd's rule slot
(mkForce) so the sufficient-before-pam_unix ordering is inherited, not
recomputed. README + downstream template rows added.

Verified: V2 — checks.hardware-toggles extended to three nodes, green:
parallel node asserts the grosshack auth line precedes pam_unix in
/etc/pam.d/sudo and that with NO reader a correct password still
passes sudo while a wrong one fails (the lockout-safety invariant);
seqpam node gets stock pam_fprintd and no grosshack; nopam gets
neither. flake check + option-docs + template-sot green.
V3 pending (HARDWARE-QUEUE, AMD dev box): the real type-or-touch race,
fprintd-stopped fallback, hyprlock/greeter after a fingerprint win.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 19:03:56 +01:00
c840202018 feat(display): blackout rescue — re-enable a disabled panel on undock
Some checks failed
Check / eval (push) Failing after 1m40s
Bernardo's question: with "Laptop screen off" active, does the panel
come back when the dock is yanked? VM answer (new maintainer harness
tools/monitor-fallback.nix, softGL desktop): no — Hyprland 0.55.4
leaves the session with zero active outputs and never re-enables a
soft-disabled monitor. rescue_blackout in the display hotplug watcher
now re-enables every disabled output (immediate, retried, idempotent
keyword + toast) whenever a monitorremoved leaves nothing active;
independent of the profile auto-switch and its settings gate.

Sharp edge documented rather than hidden: the zero-output state itself
crashes Hyprland in the VM ~4/5 runs (aquamarine CBackend::dispatchIdle
ABRT within ~0.5s — faster than any userland rescue; a control run
removing the external with the panel ACTIVE survives 5/5, so the state
is the trigger, not headless removal; crash reproduced with the rescue
provably inert, so not caused by it). Rescue ships as defense-in-depth:
it recovers every surviving case, and the worst case on real DRM is
session-to-greeter on a re-lit panel instead of a black brick. Rider
fix: `grep -c .` exits 1 at count zero — the ||-guard on that pipeline
silently disabled the rescue exactly when it mattered.

Verified: hazard V2 (VM reproduces the dead state); control
discriminator 5/5; flake check green. V3 pending: real-dock
undock-while-off in HARDWARE-QUEUE (run with nothing important open;
crash on real DRM = rework the row to mirror or wait for a Hyprland
bump).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 18:09:17 +01:00
6439105d38 feat(audio,menu): dock audio auto-follow watcher + docked Display/Audio menu rows
Some checks failed
Check / eval (push) Failing after 1m41s
Bernardo's dock test (the #87 V3): audio stayed on EasyEffects→speakers.
Root cause, established live: WirePlumber's stored default
(default-nodes state, written by any past explicit pick) outranks the
priority.session rules, so #87 alone can never move the default on a
real machine; EasyEffects 8 follows the default device (kcfg default
true, verified by watching its output links). Fix: nomarchy-dock-audio
(modules/home/dock-audio.nix, nomarchy.dockAudio.enable, default on) —
a pactl-subscribe watcher that set-default-sinks new dock-class sinks
(regex data imported from modules/nixos/dock-audio-rules.nix, single
source) with a toast, plus a startup sweep for login-while-docked that
never overrides a manual in-dock pick. Unplug falls back by priority.

Menus: Display gains docked rows — "Laptop screen off · everything →
<ext>" (live-only disable; a persisted one could black-screen an
undocked boot), "Screen on · <name>", "Move workspace · → next
monitor", "Swap workspaces" (exactly-2 gate); Audio gains a self-gated
"Send output → <dock sink>" quick row. All self-gate on real outputs.

Verified: V2-equivalent on live hardware (no dock available) — watcher
followed a fake dock-named null sink and fell back on unload, control
run without the watcher stayed pinned (proving the stored-default
diagnosis), EE output links moved mid-playback; generated menu bash -n
green, dock-row jq exercised on real pactl JSON, dispatchers probed on
live Hyprland; flake check + option-docs + template-sot green.
V3 pending: real-dock retest ×2 in HARDWARE-QUEUE (watcher keys on
new-sink events; pre-existing nodes that only flip route availability
would need the queued diagnosis data).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 14:22:49 +01:00
9e37e11915 feat(lifecycle): auto-commit sweep of the machine flake before pull/rebuild/home
Some checks failed
Check / eval (push) Failing after 3m11s
With settings.autoCommit on, only menu/theme mutations were committed
(theme-sync pathspec-limits to theme-state.json by design), so hand
edits to system.nix/home.nix and lock bumps stayed forever-dirty.

New internal nomarchy-autocommit (nomarchy-lifecycle): live-reads the
same flag, commits everything dirty as "nomarchy: auto-commit before
<pull|rebuild|home switch>" with the swept file list in the body,
fallback identity, never fatal (|| true at call sites). Called at the
top of all three lifecycle commands; before the ff-only pull it also
un-dirties the tree. Exposed via passthru.autocommit + package export;
guarded by checks.lifecycle-autocommit (sandbox repos: sweep, no-op on
clean/off/non-repo). Sync sweep: README, RECOVERY, ROADMAP, rofi
toggle, control-center prompt, theme-sync docstring, JOURNAL.

Verified: V1+ — flake check --no-build green; the new check builds;
end-to-end run of the built nomarchy-rebuild (stubbed sudo/rebuild)
against a copy of a real dirty ~/.nomarchy: 4 files → one commit,
clean tree. No VM boot — no session-visible surface.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 12:52:05 +01:00
b98248fad8 feat(theme): themed monochrome override for the EasyEffects tray icon (#89)
Some checks failed
Check / eval (push) Has been cancelled
EasyEffects 8 publishes SNI IconName com.github.wwmm.easyeffects, which
Papirus renders as its full-colour blue equaliser — the one splash of
colour in an otherwise monochrome Waybar tray (nm-applet, udiskie & co
resolve to Papirus symbolics).

Fix at the shared icon-theme layer so it holds for ALL themes at once:
wrap the resolved icon set in a thin child theme, Nomarchy-icons, that
Inherits it and ships a palette-coloured monochrome easyeffects glyph
(the app's own -symbolic shape, filled with the theme's  colour —
the same hue as the bar's glyphs, so it tracks every theme/mode on
switch). The child becomes the session icon theme; a scalable override
out-resolves the parent's fixed-size icon at every requested size, while
every other icon still falls through to the parent unchanged.

Since iconTheme feeds both stylix (GTK) and rofi, wrapping it once here
covers generated and whole-swap themes with no per-theme files. Adding a
future offender is one more SVG in scalable/apps.

Mechanism validated by GTK IconTheme lookup (override wins at 16/22/24/
32px; udiskie/nm/folder icons still resolve from Papirus). Icon pkg
builds; fill=#D3DAE0, Inherits=Papirus-Dark under Boreal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 08:31:31 +01:00
8eb672bada fix(waybar): carry tray-menu counter-rules in the bar stylesheet itself
Some checks failed
Check / eval (push) Has been cancelled
The e8658fe cure (USER-priority gtk.css counter-rules) verified in a
plain GTK app but not in the real Waybar-hosted nm-applet menu: Waybar
adds its provider at GTK_STYLE_PROVIDER_PRIORITY_USER as well
(src/client.cpp), and in practice its rules win the tie against
~/.config/gtk-3.0/gtk.css on GTK 3.24 — reproduced by injecting the
bar stylesheet above USER priority: arrow, checkmark and separator all
vanish, matching the live menu exactly.

The only placement that wins at ANY provider priority is inside the
same stylesheet: after each '*' reset, restore the CSS-sized menu
nodes (menuitem arrow 16px, check/radio 14px, separator 1px line) and
give menus a text face instead of the bar's glyph font. Applied to
boreal + executive-slate waybar.css and waybar.nix generatedStyle;
stylix.nix comment corrected (its rules now cover ordinary GTK apps
only).

Ground truth checked over D-Bus first: nm-applet exports per-network
Wi-Fi icons as dbusmenu icon-data, and with gtk-menu-images=1 the
connected network's icon already renders in the live menu — the
remaining loss was arrows/checks/separators, all CSS-box nodes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 08:15:08 +01:00
e8658fea5f fix(gtk): tray-menu arrows via USER-priority counter-rule; show menu icons
Some checks failed
Check / eval (push) Failing after 1m26s
Two corrections to ac7e6bd, which broke the bar it meant to spare:

1. Scoping the waybar '*' reset to 'window#waybar *' gave every reset
   property id-level specificity, so it started BEATING the later
   class rules (Boreal's pill border-radius/borders, workspace pills)
   that plain '*' correctly lost to — visibly wrecking the bar.
   Reverted all three stylesheets to the plain '*' block (bar restored
   byte-identical to pre-ac7e6bd) and instead restored the arrow where
   priority genuinely outranks waybar's stylesheet: stylix's gtk.css
   loads at USER priority (800) vs waybar's APPLICATION (600), so a
   'menu menuitem arrow { min-width/height: 16px }' there survives the
   reset in Waybar-hosted menus — the exact mechanism the neighbouring
   check/radio rule already uses for the same collapse. NB comments at
   each '*' block warn against re-scoping.

2. The 'missing icons' were never just arrows: GTK3 ships
   gtk-menu-images=false since 3.10, so image menu items (nm-applet's
   Wi-Fi list, udiskie) render iconless everywhere. Set
   gtk.gtk3.extraConfig.gtk-menu-images = 1 (merges into the
   settings.ini Stylix already writes).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 08:01:01 +01:00
ac7e6bdb4e fix(waybar): scope the universal CSS reset to the bar — un-hides tray submenu arrows
Some checks failed
Check / eval (push) Failing after 1m22s
BACKLOG #90 root-caused: the arrows were never missing from the icon
theme — Papirus-Dark resolves pan-end-symbolic through its breeze-dark
inheritance (breeze ships inside the nixpkgs papirus output). The real
culprit: Waybar loads its stylesheet screen-wide at application
priority, so the tray menus it hosts for SNI apps (nm-applet & co)
inherit the bar's '* { min-height: 0; }', which out-prioritizes
adw-gtk3's 16px min-height on the menuitem arrow node and collapses
the submenu arrow to zero height. Property bisect confirmed
min-height:0 alone reproduces; the font/border resets don't.

- boreal + executive-slate waybar.css, waybar.nix generatedStyle:
  '*' block scoped to 'window#waybar, window#waybar *'; tooltip rules
  restate the font (tooltips are separate toplevels outside the scope).
- stylix.nix: drop the go-next-symbolic remap — a workaround for the
  misdiagnosis; native pan-end-symbolic resolves fine.
- BACKLOG #90 deleted; JOURNAL entry records the method (live GTK3
  repro + grim screenshots + CSS property bisect, V3 on TuringMachine).

V3 pending: human click on the real nm-applet tray icon post-switch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 07:47:37 +01:00
2020ab5853 fix(gtk): bypass fragile SVG recoloring with static palette assets
Some checks failed
Check / eval (push) Failing after 1m55s
GTK's -gtk-recolor proved too fragile across different contexts, failing
to substitute the foreground color even when the SVG path used #000000.
Instead, we now generate two statically colored SVGs (normal + hover)
directly from the Nix palette hex codes, bypassing GTK's SVG recoloring
engine entirely.
2026-07-11 21:07:25 +01:00
ebfd344390 fix(gtk): add fill=#000000 to bundled submenu arrow SVG
Some checks failed
Check / eval (push) Failing after 1m33s
GTK's -gtk-recolor requires the explicit string #000000 inside the SVG
to correctly substitute the foreground color. Without it, the shape defaults
to a hard un-recolorable black, which remained invisible against the dark
popover menu backgrounds. Added viewBox for robustness.
2026-07-11 20:16:11 +01:00
6967d8973c fix(home): easyeffects waits for a live tray host, not waybar.service
Some checks failed
Check / eval (push) Failing after 4m10s
The tray-race guard (wwmm/easyeffects#4636) ordered the unit
After=waybar.service — inert here: Nomarchy's waybar runs from the
nomarchy-waybar supervisor (Hyprland exec-once), never as a systemd
unit, so the icon vanished whenever easyeffects won the boot race
(seen on TuringMachine 2026-07-11). Gate ExecStartPre on the SNI
watcher's IsStatusNotifierHostRegistered with a 30s timeout, non-fatal
so audio processing still starts on tray-less setups.

Also: #89 inventory narrowed by Bernardo's visual verdict — Telegram/
ZapZap fine; offenders are EasyEffects (slice 2) and Signal (slice 3).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 19:28:55 +01:00
c608f58b5a fix(home): pin xdg.userDirs.setSessionVariables — HM 26.05 default flip
Some checks failed
Check / eval (push) Failing after 3m33s
home-manager 26.05 changes the default to false and warns on every
eval when home.stateVersion is older (every migrated machine). Pin the
legacy true: scripts and non-glib apps read the XDG_*_DIR session vars,
and an explicit value gives all downstreams identical behavior
regardless of stateVersion. mkDefault so a downstream can still opt out.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 19:13:19 +01:00
13ecfa92f0 fix(gtk): bundle the submenu arrow — Papirus has no pan-end-symbolic
Some checks failed
Check / eval (push) Failing after 1m33s
05bab55's menu fix pointed -gtk-icon-source at the icon theme, but
Papirus doesn't ship pan-end-symbolic and its inheritance chain
(breeze-dark → hicolor) has no provider installed, so GTK menu submenu
arrows rendered as nothing (nm-applet: no cue that 'VPN Connections'
opens a submenu). Bundle the triangle as a store-path SVG recolored
via -gtk-recolor(url(…)) — the same mechanism adw-gtk3 uses for its
own check-mark assets, immune to icon-pack coverage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 19:06:11 +01:00
61d591f5d2 feat(waybar): explain 'Not charging' hold state in the battery tooltip
With a charge cap, AC-plugged inside the hysteresis band (power.nix:
start = cap − 10) reports 'Not charging' — indistinguishable from a
broken charger without a hint. tooltip-format-plugged now says the cap
is holding the level and how charging resumes. No cap value baked: the
menu changes it live without a rebuild.

Whole-swap parity: the same tooltip added to all four theme
waybar.jsonc files (boreal/executive-slate/summer-day/summer-night) —
the generated config is bypassed under these themes.

Verified on hardware (TuringMachine, boreal theme, cap 80, battery 71%
in the hold band): all four jsonc validate with jq; home-manager switch
deploys the boreal config with the tooltip; battery currently in the
exact Plugged state the tooltip targets.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 19:06:11 +01:00
b39808d847 fix(waybar): VPN tooltip names the active VPN(s)
The shield's tooltip said only 'VPN active' — with both NM VPNs and
Tailscale feeding the same indicator, a forgotten tailscaled read as
'the icon is stuck after disconnecting'. List the active NM
VPN/WireGuard connection names and/or 'Tailscale' in the tooltip.

Verified on hardware (TuringMachine): script exercised with Tailscale
up → tooltip 'VPN: Tailscale'; tailscale down → empty output
(self-hide). NM name path is the same awk the nomarchy-vpn picker uses.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 19:06:11 +01:00
Bernardo Magri
50a558615c feat(hyprland): SUPER+0 → workspace 10; migration doc gains autoLogin
Workspace binds were generated for 1-9 only, leaving SUPER+0 dead.
Generate 10, with the 0 key mapping to workspace 10 (and SHIFT+0 to
move); cheatsheet updated to match.

MIGRATION.md's example system.nix now includes
nomarchy.system.greeter.autoLogin — the migration path missed what
nomarchy-install sets on every LUKS machine, so migrated boxes stopped
at tuigreet asking for a password behind the LUKS prompt.

Verified: nix flake check --no-build passes; generated
homeConfigurations.nomarchy hyprland binds eval'd and contain
'$mod, 0, workspace, 10' + SHIFT variant. V3 next: rebuild on the
migrated T14s (TuringMachine) and press SUPER+0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 19:06:11 +01:00
Bernardo Magri
482cb87c70 fix(menu): release Left from kb-move-char-back before binding it to Back
Some checks failed
Check / eval (push) Failing after 8m34s
rofi hard-fails on a duplicate keybinding and reports it in a GUI error
dialog, not stderr — so 05bab55's Left=Back killed every menu entry
point on a real session while passing all stubbed tests. Rebind
kb-move-char-back to Control+b in both rofi_menu copies (menu + vpn).

V3 on the migrated T14s: error dialog reproduced with old flags, menu
renders with fixed flags. Journal + two PROPOSED pitches (EasyEffects 8
tray icon, parallel fingerprint/password PAM).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 16:58:04 +01:00
Bernardo Magri
05bab5576e fix: migration first-boot, fingerprint PAM, menus, mic LED
Some checks failed
Check / eval (push) Failing after 9m12s
Post-migration annoyances from TuringMachine:

- Force fprintAuth from fingerprint.pam (NixOS defaults it on with fprintd)
- First-boot toast waits for swaync, retries, only marks after success
- Night light status/toggle handles masked units + hyprsunset/wlsunset
- VPN menu: define rofi_menu in nomarchy-vpn (was a silent no-op)
- Left arrow = ↩ Back in list menus; GTK menu contrast via Stylix
- Mic-mute LED udev + sync after wpctl; EasyEffects starts after tray
- Migration guide: HM before GUI login, fingerprint/browser/VPN notes

Verified: flake check --no-build; checks.first-boot; checks.hardware-toggles
(pam=false node). V3 session items pending after user rebuild.
2026-07-11 15:06:21 +01:00
Bernardo Magri
f47903a6a6 feat(audio): prefer dock/HDMI/USB sinks in WirePlumber (#87)
Some checks failed
Check / eval (push) Has been cancelled
Raise priority.session for HDMI/DisplayPort (1100) and USB (1050) ALSA
outputs so the default sink follows dock plug/unplug; built-in analog
wins again when externals disappear. Rules live in
modules/nixos/dock-audio-rules.nix; checks.dock-audio covers the contract.

Verified: V2 — flake check --no-build; checks.dock-audio green.
V3 pending: HARDWARE-QUEUE #87 real HDMI/USB-dock hotplug.
2026-07-11 10:36:07 +01:00
Bernardo Magri
27f18e4ccc feat(power): clamshell — ignore lid suspend when docked (#86)
Some checks failed
Check / eval (push) Has been cancelled
Explicit HandleLidSwitchDocked=ignore under services.logind so closing
the lid with an external display connected does not suspend. Undocked
lid path left to upstream defaults. checks.clamshell-logind asserts the
baked logind.conf and active logind; real multi-monitor lid V3 queued.

Verified: V2 — flake check --no-build; checks.clamshell-logind green.
V3 pending: HARDWARE-QUEUE #86 lid+external on a laptop.
2026-07-11 10:32:18 +01:00
Bernardo Magri
639f553cb7 feat(lifecycle): plain-language generation diffs (#82)
Some checks failed
Check / eval (push) Has been cancelled
nomarchy-what-changed wraps nvd into "N added, M removed, K updated"
summaries. nomarchy-rebuild / nomarchy-home print full nvd and toast
the one-liner; System › What changed? offers toast + floating report.
checks.what-changed fixtures nvd via NOMARCHY_NVD.

Verified: V2 — flake check --no-build; checks.what-changed green;
local smoke on real HM generations.
2026-07-11 10:26:57 +01:00
Bernardo Magri
8f720b1078 feat(session): first-boot welcome toast (#81)
All checks were successful
Check / eval (push) Successful in 4m23s
One-shot user unit fires a dismissible "You're set" notification on the
first graphical session (SUPER+M / SUPER+T / SUPER+? + Network + Doctor),
then writes settings.firstBootShown into the flake checkout. Skips the
live ISO hostname (hosts/live.nix keeps its own toast). Toggle:
nomarchy.firstBootWelcome.enable. checks.first-boot covers the gate.

Verified: V2 — flake check --no-build; checks.first-boot green; local
shim smoke; downstream-template-home ships the unit.
2026-07-11 10:19:46 +01:00
Bernardo Magri
3d40a7e1ed feat(menu): open Doctor in a floating centered terminal
All checks were successful
Check / eval (push) Successful in 4m26s
Launch nomarchy-doctor via ghostty --class=com.nomarchy.doctor (same
pattern as the calendar popup) and match float/size/center windowrules
so System › Doctor and the Waybar doctor click no longer tile.

Verified: V1 — downstream-template-home builds; menu carries the class;
hyprland.conf has float/size/center for com.nomarchy.doctor;
windowrule-syntax green (19 rules). V2 live float confirm after
nomarchy-home (same class mechanism as calendar).
2026-07-11 10:08:50 +01:00
Bernardo Magri
53f75f6e1b polish(nightlight): geo-mode tooltip says it follows the location
Some checks failed
Check / eval (push) Has been cancelled
"Warm on schedule" was wrong in geo mode — the schedule is computed
from lat/long. Tooltip now reads "follows your location" when geo is
active.

Verification: V1 — rode the same flake check + template-home build as
the exit-node commit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 09:41:34 +01:00
Bernardo Magri
656ebc9735 feat(menu): Tailscale exit-node rows show country/city
The Exit node picker listed bare hostnames; Mullvad-style nodes are
meaningless without their location. Rows are now
"hostname  — Country City", sliced from `tailscale exit-node list` by
the header's fixed-width column offsets (COUNTRY/CITY can be
multi-word, so field splitting is wrong by construction); nodes without
a location stay a bare hostname, and the pick strips back to the
hostname at the first space before `tailscale set --exit-node`.

Closes BACKLOG LATER "VPN exit-node richer display"; the VPN
HARDWARE-QUEUE entry now covers the location rows.

Verification: V1 — the awk was proven against a fabricated fixture
(one-word city, multi-word "United States / New York, NY", bare
self-hosted node), flake check exit 0, downstream-template-home builds
(writeShellScriptBin syntax-gates the script). Real `tailscale
exit-node list` output stays V3 (existing VPN menu live-paths entry).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 09:41:34 +01:00
Bernardo Magri
4ebd6770ec feat(nightlight): geo mode — lat/long-computed sunset via wlsunset
All checks were successful
Check / eval (push) Successful in 4m20s
Set BOTH nomarchy.nightlight.latitude/.longitude and the night light
switches backing unit: wlsunset computes sunrise/sunset from the
location daily (the fixed .sunrise/.sunset are ignored;
.temperature feeds the night temp). Everything user-facing is
unchanged — same nomarchy-nightlight toggle script, Waybar moon, and
live-state ExecCondition gate; the unit name is a single let-binding
so all three follow together. Declarative-only on purpose: coordinates
are machine config like the keyboard layout, so home.nix (commented
template example), not a menu writer.

checks.nightlight-geo guards the wiring at pure eval: rendered
wlsunset ExecStart carries the coords + night temperature, the
ExecCondition gate landed on the swapped unit, hyprsunset stays off in
geo mode.

Verification: V1 — nix flake check --no-build exit 0 (evals the new
asserts + option-docs sync); built downstream-template-home (default
hyprsunset path unregressed). V3 pending: visible warm shift at the
location's night + instant toggle/off-persistence on the swapped unit
(HARDWARE-QUEUE § Any machine).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 09:30:52 +01:00
Bernardo Magri
2fa5231215 fix(theme): #79 review fixes — wrap-around schedule + HH:MM guard
All checks were successful
Check / eval (push) Successful in 4m6s
Two low-severity edge bugs from a fresh-eyes review of the session batch:

- cmd_auto: an inverted schedule (sunrise > sunset, i.e. a day window that
  crosses midnight) stayed perpetually on the day theme — the old
  `not between` shortcut is an empty interval when sunrise > sunset.
  Replaced with a real wrap-around: daytime = mins >= sunrise or
  mins < sunset, so both directions switch. Normal sunrise < sunset path
  is unchanged (checks.auto-theme still holds).
- rofi autotheme HH:MM validator: `[0-2][0-9]` accepted 24:00–29:59.
  Tightened the case pattern to `[01][0-9]:[0-5][0-9]|2[0-3]:[0-5][0-9]`
  so out-of-range hours are rejected (they'd otherwise skew the boundary).

(Two other review findings dismissed: papirus DOES ship breeze/breeze-dark
so that iconPacks prefix is correct; the doctor RAM-unknown row is cosmetic
since /proc/meminfo is always readable.)

Verification: py_compile; boundary truth-table (normal + wrap-around);
HH:MM case table (23:00/19:59/20:00 accept, 24/25/30:00 reject); auto
--which normal-case unchanged; menu bash -n; flake check green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 08:19:05 +01:00
Bernardo Magri
860c70466f feat(menu): #79 slice 3 — Look & Feel › Auto theme (completes #79)
All checks were successful
Check / eval (push) Successful in 3m46s
Adds an "Auto theme (on/off)" row to Look & Feel and an `autotheme`
submenu: toggle the feature, pick the day/night themes (from
`nomarchy-theme-sync list`), and set the sunrise/sunset times (HH:MM,
validated). This is the menu surface over settings.autoTheme, completing
the auto time-of-day theme feature (slices: CLI / timer / menu).

Enabling writes the flag and defaults the summer-day/summer-night pair
--no-switch, then runs `auto --force`, so a single rebuild both installs
the timer and applies the correct theme for now. Disabling is instant
(--no-switch): the flag flips and `auto` self-gates, so the lingering
timer no-ops until the next rebuild. Day/night/time writes are live
--no-switch (read by `auto` at tick time).

Verification: V1 — HM generation builds (bash -n on nomarchy-menu at
build) + bash -n on the built menu. Logic test with stubbed
rofi/theme-sync/notify: selecting the toggle from off runs exactly `set
day/night/enable ... --no-switch` then `auto --force` — the intended
one-rebuild path. V0 flake check green. All three #79 slices shipped;
the live timer switch across sunset is a real-session (V3) check on a
Nomarchy machine.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 21:53:37 +01:00
Bernardo Magri
c44616aeb0 feat(autotheme): #79 slice 2 — systemd timer runs auto
All checks were successful
Check / eval (push) Successful in 3m53s
modules/home/autotheme.nix installs a user service + timer
(nomarchy-auto-theme) that periodically runs `nomarchy-theme-sync auto`,
so the day/night theme is applied on the current schedule through the
same one engine. Install gates on settings.autoTheme.enable (read from
state, the way updates.nix gates on its enable), so a machine not using
the feature carries no timer.

Timer: OnStartupSec=1min (settle after login) + OnCalendar=*:0/15 +
Persistent (catch a transition missed while off). Periodic rather than
exact sunrise/sunset timers — robust to suspend/DST, and `auto` is
idempotent (no rebuild unless the theme must change) so ticks are cheap.

Verification: V1 — real-config eval (state temporarily enabled, then
restored) confirms the units land when enabled (OnCalendar *:0/15,
ExecStart `... nomarchy-theme-sync auto`) and are absent when disabled;
systemd-analyze validates the schedule. V0 flake check green. The live
timer -> home-manager switch on a real Nomarchy session is pending a
Nomarchy machine (same blocker as #76).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 21:34:56 +01:00
Bernardo Magri
7eb9b8ad4f feat(theme): opt-in cross-pack icon overrides, no default bloat
All checks were successful
Check / eval (push) Successful in 3m39s
theme.nix now resolves nomarchy.theme.iconThemePackage from a small
icon-pack registry (Papirus/Tela/Qogir/Reversal/Numix, matched by name
prefix). Default themes name Papirus-*, so the resolved package is the
plain papirus-icon-theme derivation — byte-identical closure, zero added
MB. Only when a theme's `icons` names a set from another pack is that
pack (and only it) symlinkJoined alongside papirus. stylix.nix consumes
it via mkDefault. Adding a pack is one iconPacks row.

Deliberately NOT shipping more packs by default: papirus alone is already
~1 GiB and each extra pack adds ~hundreds of MB, so variety is opt-in per
install (set `icons` in theme-state.json), not a default closure cost.
Documented in templates/downstream/home.nix + ROADMAP § Icon themes.

Verification: V1 (HM activationPackage builds). Eval proofs — default
resolves to plain papirus (zero delta); icons="Tela-dark" ->
nomarchy-icon-themes symlinkJoin (papirus+tela) with the name flowing,
without building tela; all 5 pack attrs exist in the pinned nixpkgs;
template-sot + option-docs + flake check green. No default visual change
(same papirus), so no screenshot; the opt-in render is downstream.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 20:12:52 +01:00
Bernardo Magri
2d0cb48d5c feat(menu): #76 slice 2 — Hibernate notifies on failure
All checks were successful
Check / eval (push) Successful in 4m7s
Per the settled #76 UX call: keep the Power-menu Hibernate row
unconditional, but when hibernate fails (typically a swap=0 machine with
no disk swap), surface a notification pointing at the enable-hibernation
runbook instead of a silent no-op. On success `systemctl hibernate`
blocks until resume, so the notify only fires on a real failure.

Verification: V1 — homeConfigurations.nomarchy.activationPackage builds
(rebuilds nomarchy-menu; writeShellScriptBin runs bash -n). Control-flow
proved on the arm extracted from the built script: failing systemctl ->
notify fires, succeeding -> no notify. flake check --no-build green.
V2 hibernate->resume VM test + V3 laptop remain open (#76 slice 3).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 19:36:52 +01:00
Bernardo Magri
2055842c33 feat(memory): enable zram compressed-RAM swap by default (#76 slice)
All checks were successful
Check / eval (push) Successful in 4m1s
First slice of BACKLOG #76 (hibernation + zram, split): zramSwap on by
default in modules/nixos/oom.nix — the memory-pressure layer before
earlyoom. zstd @ 50% RAM (nixpkgs defaults, explicit for reproducibility),
priority 100 so day-to-day paging fills zram first and any disk swapfile
stays reserved for #76's hibernate image (hibernation can't resume from
volatile zram). Follows the earlyoom mkDefault precedent — no toggle.

Adds checks.zram-swap runNixOSTest importing oom.nix, asserting the device
is an active swap, uses [zstd], and carries priority 100.

Verified: V2 checks.zram-swap PASS (/dev/zram0 in /proc/swaps, zstd,
prio 100); V1 full distro toplevel builds; V0 flake check --no-build green.
#76 remainder (disk swapfile default + resume + installer) still open.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 16:07:55 +01:00
ac3b75b018 feat(template): ship Chromium by default; record product decisions
All checks were successful
Check / eval (push) Successful in 2m49s
- home.packages: chromium active; mime → chromium-browser.desktop
- Decisions: markdown docs stay in-repo; PPD default confirmed
- Formatter + zram still open (explained in session, not closed)

V0: flake check --no-build green.
2026-07-10 14:37:52 +01:00
c41c8abaa7 fix(cli): lifecycle tools in HM so stale pull can self-heal
All checks were successful
Check / eval (push) Successful in 2m57s
Extract nomarchy-pull/rebuild/home to pkgs/nomarchy-lifecycle; install
via system + home. Home profile wins on PATH so home-manager switch
installs the fixed pull (skip git when no upstream) without waiting
for a system rebuild that needed a working pull first.

V0: flake check --no-build green.
2026-07-10 14:13:00 +01:00
45c584db26 fix(power): Dell charge limit needs Custom type, not Adaptive
All checks were successful
Check / eval (push) Successful in 2m58s
charge_control_end_threshold was written (80) but charge_types stayed
[Adaptive], which ignores the cap while still reporting it — battery
kept charging past 80% on Latitude. Oneshot now selects Custom when a
limit is set, Adaptive/Standard when off; sets start hysteresis; doctor
warns if type ≠ Custom while limited.

V0: flake check --no-build green. V3: re-apply limit, confirm
charge_types shows [Custom] and status stops Charging above cap.
2026-07-10 14:01:19 +01:00
44264428af feat(hyprland): float GTK portal file chooser; close #41
All checks were successful
Check / eval (push) Successful in 2m50s
Class xdg-desktop-portal-gtk (+ Nix .…-wrapped) from package desktop/
libexec. Polkit already V3 PASS on Latitude. NEXT only #20 remains.

V0: flake check --no-build green.
2026-07-10 13:57:22 +01:00
0834038072 fix(waybar): clock tooltip uses single chrono format block
All checks were successful
Check / eval (push) Successful in 2m57s
Two bare `{:%…}` specs emptied the hover tooltip (box with no text).
One strftime block with an embedded newline shows date + zone; ensure
tooltip label colors on generated + summer bars.
2026-07-10 13:50:15 +01:00
869ca6b16c fix(cli): nomarchy-pull skips git when no upstream
All checks were successful
Check / eval (push) Successful in 2m54s
Machine flakes often have no remote (local auto-commits only). Distro
updates come from nix flake update of the nomarchy input. Only git pull
--ff-only when @{u} is set; always update the lock; print nomarchy rev.
2026-07-10 13:41:34 +01:00
1e1d568947 fix(hyprland): stay_focused not stayfocused (0.55)
Some checks failed
Check / eval (push) Has been cancelled
Hyprland 0.55 renamed the effect; stayfocused 1 → invalid field type
on line 158 (polkit rules). Keep float/center/workspace current.
2026-07-10 13:38:54 +01:00
e2ede3d813 refactor(binds): app launcher is SUPER+Space only
Some checks failed
Check / eval (push) Has been cancelled
Drop the duplicate SUPER+D drun bind; menu Apps hint shows SUPER+Space.
2026-07-10 13:33:06 +01:00
3d5cb21302 feat(cli): nomarchy-pull / nomarchy-rebuild / nomarchy-home
Some checks failed
Check / eval (push) Has been cancelled
Split lifecycle into three clear commands (user pick):
  nomarchy-pull     git pull (if checkout) + flake.lock update
  nomarchy-rebuild  system switch on current lock (snap + nvd)
  nomarchy-home     Home Manager switch

Full upgrade: pull && rebuild && home. Legacy sys-update / sys-rebuild /
home-update remain as wrappers. nixos-rebuild-snap uses NOMARCHY_PATH.

V0: flake check --no-build green.
2026-07-10 13:32:50 +01:00
910f357f08 fix(desktop): hardware findings — charge limit, polkit ws, bar deps
All checks were successful
Check / eval (push) Successful in 3m3s
Latitude 5310 QA:
- Clock tooltip: drop empty {calendar} popup; plain date+zone
- Charge limit: restart oneshot via polkit + dual write/delay for
  Dell unplug race; toast verifies sysfs; looser menu match
- pwvucontrol ships with waybar (right-click volume)
- Print/region→clipboard toasts success
- Polkit (and pinentry) open on current workspace + stayfocused

V0: flake check --no-build green. V3: retest A3/A4/B1/tooltip/polkit.
2026-07-10 13:29:44 +01:00
3bc8a46927 fix(menu): case-insensitive filter on all rofi -dmenu menus
All checks were successful
Check / eval (push) Successful in 2m53s
dmenu mode is case-sensitive by default (dmenu compat), so config.rasi
case-sensitive=false fixed apps/drun but not nomarchy-menu pickers.
rofi_menu helper always passes -i; networkmanager_dmenu gets rofi -i.

V0: flake check --no-build green. V3: type sys in root Menu → System.
2026-07-10 13:08:33 +01:00
b5263bcef8 fix(desktop): tighter bar icon pad + case-insensitive rofi match
Some checks failed
Check / eval (push) Has been cancelled
- Double-space pad on volume (esp. high), battery, keyboard layout glyphs
- rofi: matching=normal,fuzzy; case-smart off (ste finds Steam)
- Queue: unfree+calendar PASS; retest spacing + rofi search

V0: nix flake check --no-build green. V3: bar/rofi on session.
2026-07-10 13:06:47 +01:00
d547393e7b fix(desktop): unfree CLI default, bar icon spacing, unified clock
All checks were successful
Check / eval (push) Successful in 3m0s
Hardware findings on Latitude: nix-shell blocked unfree; whole-swap
bars collided icon+percentage; clock needed calcurse + one module.

- Export NIXPKGS_ALLOW_UNFREE=1 + ~/.config/nixpkgs/config.nix
- Mono Nerd face + trailing spaces on battery/volume icons (all bars)
- Single clock `HH:MM · Day DD Mon`; calcurse ships with waybar
- V3 queue entries for laptop re-check

Verification: V0 — nix flake check --no-build green; V2/V3 pending on
session (user-visible bar + CLI unfree).
2026-07-10 12:58:37 +01:00
Bernardo Magri
eb951569cf feat: live charge limit, identity retunes, btop assets (product calls)
All checks were successful
Check / eval (push) Successful in 3m0s
- Charge limit: udev GROUP=users on threshold; menu applies sysfs live
  and persists state; boot/AC oneshot prefers live theme-state.json.
- Look & Feel: Reset wallpaper (auto); Theme/next/night light already there.
- NVIDIA first-class wrappers deferred past v1 (keep #59 comments).
- Identity taste: white, lumon, hackerman, matte-black, miasma.
- Hand btop.theme for identity + boreal/executive-slate/neon-glass.

Verified: V0 contrast; V1 battery-charge-limit VM. V3 live sysfs on laptop.
2026-07-10 12:12:13 +01:00
Bernardo Magri
ad6b76e1eb feat(hints): #73 fingerprint + doctor MOTD/first-boot tips
#43 shipped Firmware MOTD + control-center --first-boot tip. Residual:
- MOTD always lists nomarchy-doctor; Fingerprint line only when
  nomarchy.hardware.fingerprint.enable (no nag without a reader).
- first-boot tips self-gate on fprintd-list / nomarchy-doctor (same
  gum style as Firmware). No dismiss state file — first-boot is once.

V0 flake check --no-build + bash -n; V1 build control-center + eval
MOTD with/without fingerprint.enable. V3 enroll stays hardware-queue.
2026-07-10 09:39:10 +01:00
Bernardo Magri
a640de4fd4 fix(theme): #66 fail-closed theme-state errors at mkFlake/read
Missing, empty, or non-object theme-state.json now throws a short
message pointing at the template and `nomarchy-theme-sync validate`
instead of a raw readFile/fromJSON stack. Shared reader:
modules/theme-state-read.nix — wired in theme.nix, plymouth/timezone/
hardware, and lib.mkFlake (seq-forced early gate). Field-level schema
in theme.nix kept; unknown border roles still fail there; resolveColor
no longer falls through on missing palette keys. Defaults added for
settings.displayProfile / displayProfileAuto.

Verified: V0 nix flake check --no-build; negative evals (missing,
empty, [], bad border role); good-path template mode + displayProfile
defaults.
2026-07-10 09:39:10 +01:00
Bernardo Magri
14558ad296 test+fix: close #14, V2 #57 live install entry, #41 float slice
All checks were successful
Check / eval (push) Successful in 2m57s
- #14: first scheduled lock bump confirmed (8fded63 nomarchy-bump Mon
  05:20 UTC 2026-07-06); remove queue item.
- #57 V2: checks.live-install-entry builds nomarchy-live HM gen and
  asserts Install Nomarchy.desktop + installer bin + menu gate.
- #41: float/center hyprpolkitagent + pinentry-qt from package app-ids;
  capture-float-classes.nix harness (softGL empty); residual portal
  class stays NEXT [blocked:hw]. HARDWARE-QUEUE confirm steps.

Verified: live-install-entry + windowrule-syntax green.
2026-07-10 09:20:15 +01:00