feat(waybar): show the layout indicator only when the layout is ambiguous (#109)
All checks were successful
Check / eval (push) Successful in 3m27s

The indicator answers "which layout am I typing in?", so it is worth bar
space exactly when that question has more than one answer. It was gated on a
per-device override merely *existing*, which is the trap this box walked
into: the junk rows a keyboard's extra HID collections leave behind are all
remembered at the session layout, so they added no second answer and still
raised an indicator that could only ever show one value.

Gate on layouts rather than devices, and read the runtime-remembered
settings.keyboard.devices alongside the declared option — the new-keyboard
watcher writes those without a rebuild and they only graduate into
keyboard.devices later, so a bar reading the option alone was wrong for
exactly as long as the memory was fresh, which is when it matters most.

keyboard.layouts is now excluded on purpose: hyprland.nix keeps it as the
pool the picker offers first and deliberately never merges it into kb_layout,
so listing candidates cannot make the current layout ambiguous. A device
actually using one of them still does.

All four whole-swaps list the module unconditionally, so a single-layout
setup saw on a swapped bar what the generated bar hides — the parity rule was
already broken. Filter a swap through the same answer instead of asking four
hand-written files to re-derive it.

Logic is pure in modules/home/waybar-language.nix, matching monitor-rules /
dock-audio-rules; checks.waybar-language unit-tests both halves against
boreal's real swap, including a fixture-drift guard so the parity assertion
cannot pass vacuously.

Verified V2: new check passes, docking-ux and theme-wholeswap still pass,
`nix flake check` is clean, and the live config's generated bar now resolves
modules-right without hyprland/language on its single-layout default.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-14 11:10:48 +01:00
parent ce480f3669
commit f625c0eaf4
5 changed files with 161 additions and 14 deletions

View File

@@ -500,6 +500,52 @@ Design/decision records and a running log of shipped work (items marked
the internal-first recovery before deletion and leaves real cable-removal
timing to V3. V3 is queued as round 4; no speculative audio power policy was
added.
✓ **Docking rounds 45 — the hotplug event is not the truth (2026-07-14):**
the dev-box dock pass failed on wallpaper, dock mode, docked audio and a
four-times keyboard prompt. Three causes, and a process lesson. `socat -T 1`
closed Hyprland's IPC socket after a second of *inactivity* — a dock is
normally plugged into an idle desktop, so `monitoradded` was routinely lost
in the reconnect gap, and wallpaper, profile and audio all hung off that one
event. The connection is now long-lived with the tick coming from a 1s
`read` timeout, and the *output set* is the source of truth: `reconcile`
diffs it on ticks and events alike, so an event only makes the reaction
instant. A newly-present external is as unambiguous a plug as the event, so
it may still override a manual sink choice. Automatic dock mode never
existed — the watcher auto-*undocked* but on plug only matched display
profiles, so with none saved it did nothing while `Dock mode` stayed
menu-only; the pair is now symmetric, with a matched profile winning as the
more specific layout. Hyprland calls every key-capable evdev node a
"keyboard" (lid/power buttons, a monitor's control channel, each extra HID
collection), so udev's `ID_INPUT_KEYBOARD` is now the filter and the prompt
is once per physical keyboard, applied to every node it brings. Audio to the
BenQ was unreachable *by any means* because WirePlumber had the GPU's DP
card pinned to `pro-audio`, whose raw `pro-output-N` sinks have no ports,
no routing and no volume — and card profiles persist, so it survived
forever; a plug that finds nothing routable now repairs cards carrying an
*available* HDMI/DP port (the monitor asking for audio over the cable),
which keeps the internal analog card and considered pro-audio setups
untouched. On UCM cards headphones are a sink rather than a route, and a
stored default outranks every priority rule, so the jack is now followed
explicitly; unplug needs no undo since the pin goes stale by itself. The
process lesson: both watchers are `exec-once`, so `nomarchy-home` leaves the
*old* processes running and two of the four reports were never testing the
fix — **relogin before testing a watcher**. V3 is queued as round 6.
✓ **Conditional layout indicator + swap parity (#109, 2026-07-14):** the
indicator answers "which layout am I typing in?", so it earns bar space
exactly when that has more than one answer — not when a device merely
exists. Counting devices was the trap: the junk rows a keyboard's extra HID
collections leave behind are all remembered at the session layout and added
no second answer. The gate reads the runtime-remembered
`settings.keyboard.devices` alongside the declared option, since the watcher
writes them without a rebuild and they only graduate later — reading the
option alone was wrong for exactly as long as the memory was fresh.
`keyboard.layouts` is excluded on purpose: hyprland.nix keeps it as the
picker's candidate pool and never merges it into `kb_layout`, so it cannot
make the current layout ambiguous by itself. All four whole-swaps list the
module unconditionally and so used to show what the generated bar hid; they
are filtered through the same answer rather than re-deriving it by hand
(the parity rule). Logic is pure in `modules/home/waybar-language.nix`;
`checks.waybar-language` unit-tests both halves against boreal's real swap.
✓ **Parallel fingerprint-or-password (2026-07-12, Bernardo promoted
from PROPOSED):** `nomarchy.hardware.fingerprint.parallel` (default
**true** — the better UX is what opting into fingerprint PAM gets you;