Commit Graph

94 Commits

Author SHA1 Message Date
fbc93159a6 docs(agent): queue #123–#124 + diagnose #95 from Acer/XPS install photos
Some checks failed
Check / eval (push) Has been cancelled
Real installs on Acer Aspire M5-481T and Dell XPS 9350 (2026-07-14):

- #123: HM pre-activate dies at dconf without XDG_RUNTIME_DIR on both
  machines — partial first boot (weird GTK + missing nm-applet are
  symptoms of the incomplete bake).
- #124: install seeds ?ref=v1 while writing main-era nomarchy.hardware.*,
  and v1 lags hard enough that rebuilds die with "option does not exist".
- #95: Ghostty on the Acer is OpenGL 4.2 < required 4.3 (OpenGLOutdated);
  XPS starts Ghostty fine.

HARDWARE-QUEUE V3 rows; photos stay local (.gitignore WhatsApp Image*).
2026-07-15 09:33:37 +01:00
355d8cb1a4 fix(mime): text/plain falls through to Text Editor on live (#119)
All checks were successful
Check / eval (push) Successful in 4m5s
A singleton default naming code.desktop left the live ISO with no text
handler — GIO skips missing .desktop files and offers nothing. Prefer
vscode when present, then org.gnome.TextEditor.desktop. Live also gains
mpv (video defaults had the same hole; free via the template pin).

checks.live-baseline-apps now requires every Default Applications key
to resolve to a present .desktop on live (HM + system path) and on the
default template install. Proved to fail on a vscode-only text/plain.

Verified: V2 (live-baseline-apps green + prove-to-fail); V0 flake check.
2026-07-15 09:24:48 +01:00
19ba4e2b12 fix(nixos): smartd self-gates on the hardware — the doctor was right, smartd wasn't
Some checks failed
Check / eval (push) Has been cancelled
BACKLOG #118. Bernardo booted the live ISO and the Waybar health icon was red,
reporting smartd. Everything downstream turned out to be working correctly,
which is the part worth recording: smartd's config is DEVICESCAN, and where no
drive answers SMART it exits 17 ("Unable to monitor any SMART enabled
devices"), systemd marks the unit failed, nomarchy-doctor faithfully reports a
failed system unit, and Waybar paints @bad. The doctor was telling the truth.
smartd was the bug.

Scope was never live-only, which is why this sat in NOW rather than as a live
nit: services.smartd.enable mkDefaults true on every machine and QEMU virtio
exposes no SMART, so every VM install has been booting to a health warning
about a daemon with nothing to do — and every V2 run had been showing it as
noise.

Fixed with the distro's own self-gate convention: an ExecCondition running
`smartctl --scan`, which prints nothing exactly when smartd would find nothing.
A failed condition leaves the unit inactive rather than failed. Deliberately
NOT SuccessExitStatus = 17: that would also swallow exit 17 from a machine that
does have drives, which is the entire reason the daemon ships.

V2. checks.smartd-gate boots the REAL distro module rather than a restatement
of it (its nixpkgs.config needs mkForce to yield to the test's pkgs) and
asserts both halves, because they pull in opposite directions: a gate that
never skips leaves the red icon, and a gate that always skips silently disables
drive-health monitoring on real hardware — the failure nobody notices until a
disk dies quietly. So the no-SMART node must go ActiveState=inactive, unfailed,
and absent from `systemctl --failed` (what the doctor actually reads); and the
gate's logic is driven against a scan that DOES find a device, since QEMU
cannot answer SMART honestly and pretending otherwise would test nothing. The
check was proved to fail by unwiring the condition. flake check, doctor,
hardware-toggles, live-baseline-apps, option-docs and state-bridges pass.

V3 pending: that smartd still RUNS where drives have SMART (dev box, real
NVMe). Queued with an explicit fail-condition — if it skips there, revert the
gate rather than tune it.

Also swept: #120's size table said the duplicate chromium was gone; #121 was
reverted, so it is back and the table says so.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 17:38:58 +01:00
00b1e10fc3 feat(live): the live ISO gets a desktop — browser, office, editor — for +41 MiB
All checks were successful
Check / eval (push) Successful in 3m22s
BACKLOG #103. Bernardo, live ISO 2026-07-14: still no browser, no office —
"that makes a live iso useful". He is right, and it is the thing a user judges
the distro by before installing, and what they boot to rescue a machine that
won't start. It could do neither.

The live HM user now names its own set: chromium, libreoffice-fresh,
gnome-text-editor, amberol, snapshot. Firefox deliberately excluded (Bernardo's
call): chromium already owns the HTTP mime default and ships in the installed
template, so it is the browser that matches what a user gets post-install.

I asked him to choose on a "~+2 GB" premise. The premise was wrong, and
measuring rather than assuming is what corrected it: system.extraDependencies
ALREADY pins the template's HM closure into the ISO for offline installs, so
chromium/libreoffice/amberol were already in the image's store — merely absent
from the live user's profile, so nothing put them on PATH or in the launcher.
The live and template chromium resolve to the SAME store path, verified, so
reusing the template's exact `chromium.override { enableWideVine = true; }`
costs zero; a plain `chromium` would have been a second 2.5 GiB closure. Only
gnome-text-editor and snapshot are new: 9 paths, 133 MiB uncompressed. His
Firefox call still held for the right reason — it was the one item genuinely
unpinned.

V2. Measured ISO delta, both built from the same tree — the item's pass
condition: 8.038 → 8.078 GiB = +41.2 MiB (+0.50%). New
checks.live-baseline-apps asserts each app is on PATH *and* has a .desktop the
launcher can see (a binary without one is invisible, which is the failure that
matters), that HTTPS resolves to a chromium entry actually PRESENT (#94's exact
trap — a mime default naming a package nothing ships), and that firefox has not
crept back, since that is a size decision not a drive-by. The guard was proved
to fail: dropping snapshot makes it name the missing entry. flake check,
live-install-entry, option-docs, template-sot green.

V3 pending: that the apps LAUNCH needs hardware — queued on the Acer M5-481T,
from the launcher only, since that is the claim a file-level check cannot make.

Also filed, from measuring the 8 GiB rather than speculating about it:
  * #120 (NEXT, Bernardo's call) — a netinstall ISO beside the offline one.
    Starts from numbers: the offline pin is only 4.02 of 18.03 GiB uncompressed
    (~22%), so dropping it still leaves a 14 GiB desktop (~6.3 GiB compressed).
    "No pin" is not the lighter ISO; and without a binary cache for Nomarchy's
    own outputs a netinstall trades a download for a from-source install.
  * #121 — the Widevine wrapper drags a SECOND 687 MiB unwrapped chromium in
    for its share/applications alone. Pre-existing, on every installed machine.
  * #119 — text/plain names vscode, which the live ISO never ships.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 17:01:20 +01:00
eb38008ebb feat(menu): one fingerprint switch, an auto-login toggle, and the state bridge they needed
All checks were successful
Check / eval (push) Successful in 3m7s
Bernardo, post-reboot: "Use for login" was the wrong question. Whether the
finger works is one decision, not two, and whether login prompts at all is
a different decision that was never in the menu.

System › Fingerprint is now a single Fingerprint (on/off) switch, leading
the menu with enroll/list/verify/delete as the plumbing behind it. It
writes the one settings.fingerprint.pam key, and modules/home/idle.nix now
defaults idle.fingerprint from that same key — so the lock screen and
login/sudo move together instead of drifting apart the way they did until
e2de906. nomarchy-fingerprint does the two rebuilds this needs (sudo
system for PAM, home switch for hyprlock) and refuses to turn on with no
finger enrolled.

System › Auto-login is new (nomarchy-autologin), and it is what decides
whether anything is asked at boot: auto-login on means no prompt whatever
the fingerprint switch says; off means the greeter asks, for a password or
a finger. Installer-seeded ON for LUKS machines — the passphrase already
gates the disk — and off without it, where the greeter is the only thing
between power-on and the desktop.

Both had to become state-owned to be toggleable at all, which surfaced two
real bugs:

  * nomarchy.system.greeter.autoLogin defaulted from
    `config.nomarchy.settings…` — an attribute that exists ONLY on the Home
    Manager side. On NixOS it is absent and `or null` swallowed the error,
    so the default silently evaluated to null on every machine ever built.
    That is why the installer baked a Nix line: the state path never
    worked. Now read via theme-state-read.nix (the hardware.nix/timezone.nix
    pattern) and mkDefault'd, so the menu owns it and a hand-set line still
    pins it. Two more options read the same phantom bridge — BACKLOG #116.
  * `theme-sync get` printed Python's "None" for a JSON null, so every
    `case … null)` a caller writes would miss. Now prints "null", as the
    comment above it already promised for booleans.

The installer seeds the state instead of emitting the system.nix line,
because that line outranks the state and would strand the toggle.

V1 (V3 pending: HARDWARE-QUEUE). nix flake check --no-build, installer-
safety and option-docs all pass. Proved by eval/build, not assumed: a state
carrying autoLogin yields greetd initial_session {"user":"bernardo"}, the
template state (no autoLogin) yields none, and a hand-set null beats a state
that says otherwise; a state with only fingerprint.pam=true — nothing set by
hand — renders the hyprlock auth.fingerprint block; both new tools pass
bash -n and land in systemPackages (nomarchy-fingerprint only with a
reader); the patcher writes settings.greeter.autoLogin and no system.nix
line; and the get round trip prints null, so the menu reads "Auto-login
(off)" where it would have read "(on)".

The reader itself, the two rebuilds, and the reboot are hardware — queued.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 14:34:56 +01:00
625b7e38a3 docs(hardware): sudo path of parallel fingerprint passes; drop a check that cannot fail
All checks were successful
Check / eval (push) Successful in 3m4s
Bernardo, AMD dev box, gen 422, fingerprint.pam = true (parallel default):
one prompt, typed password works, sensor works, wrong-finger x3 falls back to
password. Three of the item's counts confirmed on the real reader.

The fourth — "password keeps working with fprintd stopped" — is removed as a
trap rather than checked off. fprintd is D-Bus activated, so `systemctl stop`
does not keep it down: PAM asks D-Bus, D-Bus restarts it. Bernardo ran it and
reported that *the fingerprint still worked*, which is precisely the evidence
that the daemon came back and the dead-reader path was never exercised. A
check that passes whether or not the property holds is worse than no check.

The property itself needs no hardware: password is `auth sufficient` at order
11700, independent of the fprintd rule at 11400, so no fprintd failure can
gate it — visible by eval on the rendered pam.d text, and asserted in the VM
with no reader by checks.hardware-toggles. Doing it for real would need
`systemctl mask`, and masking a working machine's auth daemon to re-prove an
evaluated invariant is not a trade worth making.

Still open on this item: hyprlock, the greeter (needs the reboot), and
`fingerprint.parallel = false` restoring sequential behavior.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 12:48:16 +01:00
e4404e0162 test(docking): prove the keyboard-restore path; the reload clobber is real
All checks were successful
Check / eval (push) Successful in 3m10s
The last unproven half of bec826b, and it needed a keyboard that stays behind
when the dock leaves. The dev box's by-tech hangs off the BenQ's own hub, so
it always walked away with the cable — a Logitech K400 with its receiver in
the laptop is the real case, and Bernardo happened to plug one in.

Both halves measured on hardware (dev box, 2026-07-14):

  - PRIMARY: across a real unplug + the fixed transition, the K400 kept its
    remembered `us`. Panel back, layout intact.
  - CONTROL: a bare `hyprctl reload` with no restore took the same board from
    `us` to the session's `gb`.

The control matters more than the pass. That reload clobbers runtime
`device[<name>]:kb_layout` was an inference from the mechanism when
restore_keyboards was written — reasonable, but unmeasured, and this session
has already shown what unmeasured reasoning about this watcher is worth.
It holds: without that call the undock's escalation would have silently
broken the layout of every keyboard that outlives the cable, trading a black
panel for a keyboard that types the wrong characters at the password prompt.

Verified V2 again after the comment/doc edits: nix flake check --no-build,
checks.docking-ux, shellcheck clean.

Round 8 now asks only for what is still open: relogin (the watcher calls the
transition by baked store path, so exec-once means it runs the old build
until then) and repetition — 5+ unplugs, one with the lid shut.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 12:35:23 +01:00
bec826baf0 fix(docking): the undock keyword is inert, not raced; escalate to reload
All checks were successful
Check / eval (push) Successful in 3m10s
Round 7 FAILED: 5-6 consecutive unplugs on the dev box, the panel never came
back. Round 6's retry could not have worked, because round 6's diagnosis was
wrong — and so was the "phantom re-add" theory I brought to this session.

With ZERO enabled outputs — panel disabled by the dock, external gone —
Hyprland 0.55.4 accepts `hyprctl keyword monitor eDP-1,preferred,auto,1`,
prints `ok`, exits 0, and never flushes it: the rule waits for a DRM event
that is not coming. It is inert, not raced. Retrying it 25x/poll for 6 polls
bought 30s of black screen and nothing else.

What made this survive two rounds is worth more than the fix: every
`transition=undock result=ok` in the round-6 journal was Bernardo plugging
the cable back in because the screen was black. His hotplug flushed the
queued rule and the next poll took the credit. Success was indistinguishable
from the user working around the failure, so the logs confirmed whichever
story we brought to them. Ten minutes of probing on hardware killed both.

Probed live 2026-07-14 (dev box, cable out): keyword inert across 4s and 5s
in two runs; `dispatch forcerendererreload` inert too; only `hyprctl reload`
escapes — 99ms and 289ms. So: issue the keyword (enough whenever another
output is still enabled, e.g. the menu's Dock mode), and escalate to reload
only when `monitors` proves it inert. After a reload, re-assert the rule so a
config that parks the panel off cannot undo the undock, and restore per-device
keyboard layouts — a reload re-reads the config and drops the runtime
`device[<name>]:kb_layout` an external board depends on. The menu's `enable`
now proves itself against `monitors` too, instead of cheering for an exit code.

Verified V3 (partial): the fixed transition driven through a real unplug —
panel on, workspaces 1-3 home, 1.8s, `keyword=inert escalate=reload` ->
`enable=via-reload` -> `result=ok`. V2: nix flake check --no-build; docking-ux
+ dock-audio; monitor-fallback.nix; shellcheck clean on display-transition.

NOT proven, and queued as HARDWARE-QUEUE round 8: the watcher-driven path
(exec-once + baked store path = relogin required, the round-5 trap) and
repetition. The keyboard-restore path is untested — the dev box's keyboard
hangs off the dock's hub, so it leaves with the cable; round 8 adds a check
with a keyboard in the laptop. The VM harness still cannot reach any of this:
QEMU aborts Hyprland if the last active output is deleted while the DRM output
is disabled, which is the same zero-output degeneracy from the other side.

New BACKLOG #114 (PROPOSED): tuigreet ignores per-device keyboard layouts —
a VT has one keymap, so the greeter cannot honour `device[]:kb_layout`.
Bernardo hit it logging out while docked; not a regression, a design gap.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 12:28:06 +01:00
28be6779a7 docs(hardware): round 6 settles 4 of 5 counts; headphones still untested
All checks were successful
Check / eval (push) Successful in 3m15s
Bernardo's round 6 report plus the keyboard result confirms on hardware:
wallpaper on the external, automatic dock mode, audio reaching the
monitor, and the external keyboard prompting exactly once (was four).

That leaves headphone jack-follow as the only count from rounds 4-6 never
tested at all, so it gets its own entry rather than staying buried in a
list of settled ones. Fold it into the round 7 session — the dock is
already in hand and the two checks don't interfere.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 11:37:52 +01:00
44ecc9094e fix(docking): retry the undock; a lost keyword left the panel black
Some checks failed
Check / eval (push) Has been cancelled
Round 6 passed on the plug side but unplugging the cable left the laptop
panel black until it was re-docked. The journal named it: across three
undocks, two logged result=enable-timeout and one result=ok — a race, not
a logic error.

Two halves, both needed:

  - `hyprctl keyword monitor eDP-1,preferred,auto,1` is accepted (exit 0)
    while Hyprland is still tearing down the departing external, then
    silently dropped. Re-issue it on every poll for 5s and keep `monitors`
    listing the panel — not hyprctl's exit code — as the only proof.

  - The watcher fired the undock once off the removal event and marked the
    departure handled regardless (`|| true` + awaiting_lid_open=1). Since
    known_outputs had already moved, the set-changed guard never reopened
    and nothing retried, so one lost keyword cost the panel until re-dock.
    The undock is now a queued invariant driven on the 1s tick, bounded at
    6 attempts so a genuinely unenableable panel can't pin the inhibitor.

The VM harness cannot reach this race: QEMU's headless backend aborts
Hyprland if the last active output is deleted while the DRM output is
disabled, so the test must undock before removing DP-1 and never collides
the two. Noted there, and V3 queued as round 7 — unplug 5+ times, since
the old failure only hit ~2 in 3.

Verified: V2 — nix flake check --no-build; monitor-fallback.nix exit 0 in
57s; shellcheck clean. The fix itself is V3 pending.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 11:35:12 +01:00
ce480f3669 fix(docking): dock automatically; repair cards parked on pro-audio
All checks were successful
Check / eval (push) Successful in 3m9s
Bernardo's retest on the AMD dev box. Two of the four reports were never
running the new code — both watchers are Hyprland `exec-once`, so a
`nomarchy-home` switch leaves the old processes alive until relogin; only
`nomarchy-dock-audio`, a user service, had picked the fix up. The journal
showed the old watcher's `event=monitoradded` next to the new audio tool's
`result=no-available-sink`, which is what gave it away. The other two were
real.

Automatic dock mode did not exist. The watcher auto-*undocked* when the last
external left, but on plug it only ever matched a display profile — and with
no profile saved and displayProfileAuto unset it did nothing at all, while
`Dock mode` was reachable only from the menu. Half the pair being automatic
is what made the dock feel broken: the session came home by itself but never
left. Mirror it, letting a matched profile win as the more specific layout.

The BenQ's audio was unreachable by any means because WirePlumber had the
GPU's DisplayPort card pinned to `pro-audio`, whose raw `pro-output-N` sinks
carry no ports, no routing and no volume — so no sink matched, nothing was
selectable, and the profile is stored per card and so survived forever. The
monitor's own USB card is input-only (every profile has sinks: 0), leaving
DisplayPort as the only path to its speakers. Repair the card when a plug
finds nothing routable, which is both the symptom and the only moment worth
touching profiles: narrow to cards carrying an *available* HDMI/DP port —
the monitor asking for audio over the cable — so the internal analog card is
never touched and a considered pro-audio setup elsewhere survives.

Verified V2: checks.docking-ux and checks.dock-audio pass with guards for
both; the repair filter picks exactly the GPU card → HiFi against the dev
box's live graph, leaving the BenQ USB, internal analog and Bluetooth cards
alone. Hotplug itself still needs hardware — HARDWARE-QUEUE round 6, which
now leads with the relogin requirement that invalidated round 5.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 10:56:52 +01:00
e0da1ea40f fix(docking): don't lose the hotplug event; one prompt per keyboard; follow the jack
All checks were successful
Check / eval (push) Successful in 3m31s
Four failures from Bernardo's round-4 dock pass on the AMD dev box, plus a
headphone regression, tracked to three causes.

The display watcher ran socat with `-T 1`, which closes Hyprland's IPC
socket after one second of *inactivity*. A dock is normally plugged into an
idle desktop, so the socket was almost always mid-reconnect at that moment
and `monitoradded` was lost outright — and the wallpaper repaint, the
docking profile, and the dock-audio reprobe all hung off that single event.
The reconnect path recovered only the lid inhibitor, so the dock stayed
half-configured until something else poked the watcher.

Make the connection long-lived and take the periodic tick from a 1s `read`
timeout instead, then let the *output set* — not the event — decide: a new
`reconcile` diffs it and is called on both ticks and monitor events, so an
event now only makes the reaction instant rather than necessary. A newly
present external output is as unambiguous a physical plug as the event was,
so it may still override a manual sink choice. Wallpaper paints are retried
over the first few seconds because awww registers an output on its own
schedule, not the compositor's.

Hyprland calls every key-capable evdev node a "keyboard": the lid and power
buttons, a monitor's control channel, and each extra HID collection of a
real keyboard. One dock with one keyboard asked for a layout four times.
udev already draws the distinction properly, so take ID_INPUT_KEYBOARD from
it, map the names into Hyprland's spelling, and ask once per physical
keyboard — applying the answer to every node it brought with it.

Finally, a sink WirePlumber has been told to prefer is stored as the
configured default and outranks every priority rule. On UCM cards the
headphones are a sink of their own rather than a route, so once anything
pinned a sink (this feature on a dock plug, or the user in the Audio menu),
plugging headphones in could no longer take the audio back. The `watch`
loop already subscribed to exactly the right events with an empty handler;
finish it. Unplug needs no undo — the pin goes stale and WirePlumber falls
back by priority on its own, to the dock when docked and the speakers
otherwise.

Verified V2: checks.docking-ux and checks.dock-audio pass with new guards
against each regression; the keyboard filter and both sink selectors run
against the dev box's real devices and sink names. The hotplug paths need
hardware — queued as docking round 5.

Also queues #107, renaming theme.json now that it holds the system state.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 10:24:48 +01:00
79528ff392 fix(menu): explain unsupported battery-limit and BlueZ-down states
Some checks failed
Check / eval (push) Has been cancelled
BACKLOG #96 + #97 (Acer live pass). Battery limit: the System row now
gates on battery presence (same rule as Power profile) instead of the
charge-threshold sysfs node, so firmware without the control gets the
leaf's explanation ("no charge_control_end_threshold — hardware
capability") rather than a silently missing row that reads as a broken
feature. Bluetooth: the leaf checks bluetooth.service before launching
blueman-manager and replaces the raw "Connection to BlueZ failed" with
a state-aware message (service stopped vs no adapter in
/sys/class/bluetooth, with rfkill/firmware hints).

Verified: V1 — nomarchy-menu builds, bash -n green, both paths in the
generated script; V0 flake check green. QEMU can fake neither a
threshold-less battery nor a BT adapter: #96 behavioral check queued in
HARDWARE-QUEUE (Acer + T14s); #97's no-adapter message will be
click-checked in the next VM run (V2 pending).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 18:36:19 +01:00
f7246941e8 fix(power): exempt charge-limit oneshot from start rate limiting
All checks were successful
Check / eval (push) Successful in 3m9s
Round 1 of #101's V3 failed on TuringMachine (2026-07-13 18:16): a
SPACED dock/AC event storm — each run finishing before the next event —
never coalesces, so 5 successful sub-second starts inside systemd's
default 10s window tripped start-limit-hit and marked the unit failed
(doctor badge) although every run succeeded. The write is idempotent
and cheap; rate limiting it only manufactures false failures, so the
unit sets StartLimitIntervalSec=0.

checks.battery-charge-limit now asserts the exemption and runs 8
sequential starts expecting a clean, non-failed state; the original
mid-run coalescing phase is unchanged.

Verified: V2 — the check passes under KVM (exit 0). V0 flake check
green. V3 round 2 re-queued in HARDWARE-QUEUE.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 18:26:00 +01:00
9d0abe5422 fix(install): normalize empty keyboard variants
All checks were successful
Check / eval (push) Successful in 3m18s
Treat the picker-only (none) row as an empty XKB variant at the shared input boundary and again in the template patcher. Guard the real downstream output and console keymap, and carry the sentinel through the offline installer VM.

Verified: V0 full flake evaluation plus shell/Python/Nix/diff checks; V1 installer-keyboard, installer-safety, and template-SoT builds; V2 full KVM offline LUKS+swap install and themed first boot. No V3 required.
2026-07-13 15:01:51 +01:00
7bfe1af5b1 fix(power): coalesce charge-limit reapply events
All checks were successful
Check / eval (push) Successful in 3m16s
USB-C power event bursts could repeatedly restart and SIGTERM the settling oneshot until systemd reached its start limit. Let successful runs return inactive and use coalescing start jobs for immediate and delayed reapplication.

Verified: V0 nix flake check --no-build, module parse, and git diff --check; V2 focused battery-charge-limit KVM test with a 12-event active-run burst. V3 hardware item #101 remains queued.
2026-07-13 14:09:05 +01:00
2a34c7398b fix(docking): make workspace and lid transitions atomic
All checks were successful
Check / eval (push) Successful in 3m19s
Restore the old flake's external-only workspace handoff, protect closed-lid cable removal with a verified low-level logind inhibitor, and use monitor-added as the settled audio reprobe boundary.

Verified: V2 — nix flake check --no-build; docking-ux, option-docs, and template-sot checks; KVM tools/monitor-fallback.nix lifecycle test.

V3 pending: closed-lid BenQ round 4 in agent/HARDWARE-QUEUE.md.
2026-07-13 13:56:54 +01:00
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
4cdc80c9bc docs(agent): close #76 — hibernate→resume V3 PASSED on TuringMachine
Some checks failed
Check / eval (push) Failing after 1m31s
Bernardo ran the real power-cycle on the T14s (LUKS + btrfs @swap
swapfile): hibernate → power off → single LUKS unlock → session
restored. BACKLOG #76 deleted; HARDWARE-QUEUE resume entry pruned
(outcome recorded in the remaining no-swap-notify entry, JOURNAL, and
the ROADMAP ✓-entry); Decisions hibernation note marked shipped. The
no-swap Hibernate notify check remains the one open leftover.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 07:52:10 +01:00
dbc7741f58 docs(backlog): #89 slice-1 tray inventory done; #76 unblocked by migration
Some checks failed
Check / eval (push) Failing after 1m37s
Tray SNI inventory run live on the T14s: nm-applet, udiskie, Telegram
and EasyEffects are name-based (icon-lookup overridable); Signal and
ZapZap publish pixmaps only (slice 3). Method: watcher's
RegisteredStatusNotifierItems + busctl get-property.

#76: the dev box now runs Nomarchy (migrated 2026-07-11), so the
'not a Nomarchy machine' blocker is gone — retagged [human]; queue
step 1 (swapfile/zram/resume_offset wiring) verified live, only the
power-cycle test remains.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 19:21:50 +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
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
4c656b1e73 chore(agent): sync-sweep rule on task completion + apply it; zram user docs
All checks were successful
Check / eval (push) Successful in 4m45s
New standing rule (LOOP.md §5 step 4, echoed in AGENTS.md): when a task
ships, grep its number/feature name across agent/ and docs/ and update
or delete every cross-reference it made stale, in the same commit —
including pruning checked-off [x] HARDWARE-QUEUE entries whose outcome
is recorded (journal/ROADMAP; git history is the archive).

First application: BACKLOG PROPOSED groomed (the auto time-of-day theme
pitch was stale — shipped as #79; shipped/promoted placeholder lines
removed; v1.0 pointer rewritten; FUTURE promoted-stub dropped);
HARDWARE-QUEUE pruned of 29 checked entries (363→253 lines) with
dangling references reworded and the prune rule documented in its
header.

Also closes the zram user-doc gap: README §3 now documents the zram
default (zstd/50%/prio 100), hibernation working out of the box on new
installs, and the MIGRATION.md runbook for existing machines.

Verification: V0 (docs/meta only).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 09:20:54 +01:00
Bernardo Magri
aff50e6070 docs(agent): correct #76 V3 scope — dev box isn't a Nomarchy install
All checks were successful
Check / eval (push) Successful in 4m20s
The dev box runs Bernardo's own NixOS (TuringMachine, 25.11), not
Nomarchy — so hibernating it tests that config's hibernate, not
Nomarchy's oom.nix/patch-template/rofi wiring, and can't close #76's V3.
Its layout (LUKS cryptroot + btrfs @ + /swap/swapfile + resume_offset)
does resume, which corroborates the MIGRATION.md runbook, but nothing
more. Rescope the two #76 HARDWARE-QUEUE entries to require a machine
actually running Nomarchy (a Latitude install, or the dev box after
migration), and fix the matching reference in the BACKLOG #76 stub.

Verification: V0 (docs/agent-notes only).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 19:53:23 +01:00
Bernardo Magri
ba8963a385 test(hibernate): #76 slice 3 — V2 hibernate→resume VM check (PASS)
All checks were successful
Check / eval (push) Successful in 4m11s
Add checks.hibernate: a runNixOSTest modeled on nixpkgs' hibernate.nix,
wired to the actual risk #76 introduces — coexistence with the zram swap
that oom.nix now enables by default. It imports oom.nix, puts the disk
swap at a lower priority than zram with an explicit boot.resumeDevice,
and uses systemd-initrd. The test drives a full
hibernate -> power-off -> resume -> crash cycle: a volatile ramfs marker
survives resume but NOT a crash-boot (proving genuine resume), and it
asserts zram sits at priority 100 while the disk swap is lower, so the
hibernate image lands on disk rather than volatile zram.

What QEMU genuinely can't do — feed a boot-time resume_offset for a btrfs
swapfile (offset only exists after first-boot allocation) or drive a
LUKS-initrd unlock — stays a V3 laptop check (HARDWARE-QUEUE, dev box
already runs that exact layout), plus the no-swap notify in a real
session. With this, all V0/V1/V2 work on #76 is done; #76 collapses to a
[blocked:hw] stub and the design record moves to ROADMAP.

Verification: V2 PASS — nix build .#checks.x86_64-linux.hibernate built
the driver fresh and exited 0. nix flake check --no-build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 19:45:47 +01:00
Bernardo Magri
59975a6a59 docs(agent): close #41 — OpenFile portal float confirmed on hardware
All checks were successful
Check / eval (push) Successful in 3m1s
Bernardo verified the GTK file-chooser (OpenFile) portal floats on real
hardware; the xdg-desktop-portal-gtk float/center rule already shipped in
hyprland.nix. Drop the "OpenFile not re-probed / optional residual"
caveats in HARDWARE-QUEUE and journal the V3 close. Bookkeeping only, no
code change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 15:58:36 +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
8002757ca3 docs(agent): tick HARDWARE-QUEUE for Latitude 5310 2026-07-10 QA
All checks were successful
Check / eval (push) Successful in 2m57s
Mark PASSes from the session: charge Custom type, bar/menus/capture,
polkit, combined power, mixer, arrows, SUPER+?, caffeine, doctor,
network blanks, lifecycle CLIs. Leave residual theme/ISO/greeter/etc.
2026-07-10 14:20:30 +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
84c145467e docs(agent): clock tooltip V3 PASS on Latitude
Some checks failed
Check / eval (push) Has been cancelled
2026-07-10 13:54:35 +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
79630314aa docs(agent): mark calendar V3 PASS on Latitude
Some checks failed
Check / eval (push) Has been cancelled
2026-07-10 13:06:52 +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
82776d7da4 docs(webcam): portal/Flatpak IR gap is v4l2-only (#71)
Ship the recommended document-only path: WirePlumber hideIrSensor acts
on the v4l2 monitor only; libcamera / xdg-desktop-portal / Flatpak
camera pickers can still list the internal IR sensor.

- HARDWARE.md §7: dual-sensor subsection (scope, workaround, deferred)
- ROADMAP § Webcam: ✓ #71; (b)/(c) stay hardware-gated
- README option row + HARDWARE-QUEUE T14s check for (b)/(c)
- Close BACKLOG #71; no libcamera rules

Verified: V0 (docs only).
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
Bernardo Magri
e81529c1bb feat(menu): #55 System › Fingerprint enroll/list/PAM
Self-gated System › Fingerprint when fprintd is on PATH: enroll, list,
verify, delete-all (terminal), and Use for login writing
settings.fingerprint.pam (hardware.nix default from theme-state). Close #55.

Verified: V1 (HM menu rebuild). V3 pending: real-reader enroll (HARDWARE-QUEUE).
2026-07-10 08:45:33 +01:00
Bernardo Magri
c962d07841 fix(power): #60 name-agnostic system-battery scan
All checks were successful
Check / eval (push) Successful in 2m53s
Replace BAT* globs with type=Battery + !scope=Device (same filter as
nomarchy-battery-notify) in charge-limit oneshot, menu gates, Waybar
power-profile self-gate, doctor, and install chassis detection. CMB0-
style names get charge-cap and laptop modules consistently.

V3 pending: confirm on non-BAT* hardware (HARDWARE-QUEUE). Close #60.

Verified: V1 (option-docs, hardware-db-modules, battery-charge-limit VM).
2026-07-10 08:32:24 +01:00
Bernardo Magri
be4efd38ea fix(waybar): #51 doctor glyph renders + #52 V2 bake asserts
All checks were successful
Check / eval (push) Successful in 3m6s
Doctor tripwire was self-hiding under waybar (PATH miss / weak glyph).
Call nomarchy-doctor by store path, strip ANSI tooltips, use 󰀨 + format
+ signal 10 (generated and whole-swap). theme-shot asserts class:bad,
guest btop.theme, and pokes RTMIN+10 before desktop shots.

V2: theme-shot green for rose-pine, summer-night, catppuccin, everforest,
vantablack; bar crops show the red alert-circle. btop TUI visual is
softGL-blocked (Ghostty) → HARDWARE-QUEUE. Closes #51 and #52.
2026-07-09 20:56:51 +01:00
Bernardo Magri
c89cace149 feat(menu): System ▸ Firmware — fwupd/LVFS update surface (#43)
All checks were successful
Check / eval (push) Successful in 3m3s
Add a self-gated Firmware row to the System submenu (present whenever
fwupdmgr is on PATH, i.e. services.fwupd.enable — default-on), its
dispatch, and a `firmware` handler that opens a terminal and runs
fwupdmgr refresh → get-updates → y/N confirm → fwupdmgr update. Never
auto-flashes: fwupdmgr update confirms each device and prompts for the
reboot a capsule needs (pillar 1 — no silent BIOS writes). HARDWARE.md
§4 updated to "shipped".

Verified V0 (nix flake check --no-build) + V1 (HM generation builds;
generated nomarchy-menu passes bash -n; firmware handler emitted
correctly). V2 (menu render in a session) queued in #43 and V3 (real
LVFS flash) in HARDWARE-QUEUE — the VM was held by the #46 install test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 18:43:46 +01:00
Bernardo Magri
1d8c1a4314 feat(home): add satty for screenshot annotation
All checks were successful
Check / eval (push) Successful in 3m1s
Added satty (Wayland-native screenshot annotation tool) and integrated it with the Capture flow.
Wired `$mod SHIFT, Print` to slurp a region and open satty.
Added "Annotate region" to the rofi Capture menu (Tools > Capture).
Added satty's configuration via home-manager to inject the active theme's palette colors into its UI.

Verified: V1 (flake check --no-build, home activation package build).
Pending: V3 hardware/visual check for the satty UI (queued in HARDWARE-QUEUE.md).
2026-07-08 21:49:06 +01:00
Bernardo Magri
6bd03747c9 fix(waybar): inhibited notification state reads by shape, not color alone
All checks were successful
Check / eval (push) Successful in 2m59s
Item 28 color-only accessibility sweep. Swept every Waybar status
indicator for shape/glyph redundancy: all already carry non-color
distinction (updates shows a count + self-hides; vpn/doctor/nightlight/
recording self-gate; battery/idle/muted are glyph-distinct) except the
swaync notification module — its `inhibited-*` states reused the normal
bells (󰂜/󰂚), so an app-inhibited (suppressed) state was distinguished
from a normal one by color alone.

Route every suppressed state (DND + inhibited) to the bell-off glyph 󰂛
+ @muted, so "notifications are off right now" reads by SHAPE. Applied
to the generated bar and all four whole-swaps (summer-day/night use
their own bell-off glyph; executive-slate/boreal use 󰂛) — parity rule.
Reuses the glyph DND already ships, so no new codepoint / tofu risk.

Verified: V0 (flake check --no-build; 4 jsoncs valid JSON) + V1 (built
homeConfigurations.nomarchy.activationPackage — the built config has
inhibited-* = U+F009B and style.css routes .inhibited-* → @muted).
V3 pending: live inhibited-state render with an app holding an inhibitor
(HARDWARE-QUEUE). MEMORY gains the "status is never color-only" invariant.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 21:07:10 +01:00
Bernardo Magri
be8d8a7d9b feat(waybar): open a calendar (calcurse) on the clock click
All checks were successful
Check / eval (push) Successful in 2m54s
Item 42, de-scoped by Bernardo to app-only — no account sync (the user
configures that themselves locally). Clicking the Waybar date/clock now
runs nomarchy-calendar → calcurse in a floating, centered ghostty window
tagged --class=com.nomarchy.calendar (+ --gtk-single-instance=false for a
fresh standalone window), matched by a float+size+center windowrule. The
launcher self-gates on calcurse. Dropped the clock's format-alt date
toggle (conflicts with on-click) and moved the long date into the tooltip.
Parity: on-click added to all four whole-swap jsoncs.

Ships calcurse + pwvucontrol uncommented in the downstream template
(opt-out). Enforces ghostty always installed (ghostty.nix: enable = true
unconditional; nomarchy.ghostty.enable now gates only the theming/config)
so the --class launcher can rely on it.

Verified: V0 green; four jsoncs valid JSON; V1 HM built — launcher ships,
generated clock renders on-click=nomarchy-calendar (format-alt gone, date
in tooltip), hyprland has the 3 calendar windowrules, ghostty in the HM
profile. Click→float→calcurse month view is V3 (queued). Sync is
deliberately the user's own config, not a distro feature.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 20:19:30 +01:00