diff options
Diffstat (limited to 'todo.org')
| -rw-r--r-- | todo.org | 657 |
1 files changed, 382 insertions, 275 deletions
@@ -21,29 +21,50 @@ The vocabulary is open — topic tags are coined as needed — so these are conv - *Effort / autonomy*: =:quick:= a spare-moment fix (minutes, not a sitting); =:solo:= Claude can carry it end to end — there's a build path, a test path, and no upfront decision needed (a leftover manual spot-check doesn't disqualify it). - *Topic / area* (open): the subsystem a task touches — e.g. =:hyprland:= =:waybar:= =:mpd:= =:music:= =:network:= =:tooling:= =:llm:= =:eask:= =:pocketbook:= =:cmail:=. Coin a new one when it aids filtering. * Archsetup Open Work -** DONE [#B] Panels moveable + resizable by drag :feature:waybar:network:bluetooth: -CLOSED: [2026-07-04 Sat] -Resolved by the 2026-07-03 instrument-console rebuild (dotfiles e993c3f): both net + bt panels switched from anchored gtk4-layer-shell overlays to normal floating windows (set_decorated(False), positioned by the net.cjennings.netpanel window rule), so Hyprland moves them on drag and resizes on corner-drag natively. That was exactly the "switch to a normal floating window" approach the design note flagged as the required decision. +** TODO [#D] Maintenance console vNext :feature: +Deferred from the [[file:docs/specs/2026-07-07-maintenance-console-spec.org][spec]] (v1 ships determinate remedies only): AI/workflow assistance on read-only metrics (the vLater tier — failed-unit diagnosis, journal-error fix suggestions, OOM investigation); SIGKILL escalation for TERM-survivors on the KILL lever; live streaming meters on evidence rows where a count could be a level. -Both the net and bluetooth instrument-console panels should be repositionable and resizable at runtime: click-drag to move the panel anywhere on screen, drag the corners to resize. Raised from roam capture 2026-07-03. +** TODO [#D] Retention-repair remedy unreachable from the panel GUI :bug:dotfiles: +snapshot_retention_repair (WRITE SANE LIMITS — =snapper -c <config> set-config TIMELINE_CREATE=yes TIMELINE_LIMIT_*=<TOML values>=) sits in the maint remedy table but is wired into no GUI layer (no reference in panel.py, viewmodel.py, or gui.py) — it's reachable only via =maint doctor review= / =maint fix=. Worse, the REVIEW & FIX roster's wording for item-bearing remedies says "per-item — on its subpanel", which for this remedy points at a key that doesn't exist. Found 2026-07-08 while walking the SNAPSHOTS subpanel with Craig. -Design note: the panels are gtk4-layer-shell overlays anchored TOP+RIGHT with fixed margins — layer-shell surfaces are compositor-positioned, so free drag-move/resize needs either dynamic margin updates on pointer motion or a switch to a normal floating window (Hyprland moves/resizes those natively). Approach decision required before build. +Two fix shapes, decide at work time: (1) wire a digest key — e.g. on the timeline row when the config's installed TIMELINE_LIMIT_* drift from the TOML values (needs the probe to read the installed limits for comparison); or (2) keep it CLI-only deliberately and special-case the roster wording for remedies with no panel key ("CLI only — maint fix"). Option 2 is a few lines; option 1 makes limit drift visible on the board, which is the 2026-05-26 pile-up's root cause. Severity minor × rare edge = P4 per the bug matrix. -** CANCELLED [#B] Net panel wider initial width :waybar:network:quick: -CLOSED: [2026-07-04 Sat] -Superseded by the 2026-07-03 instrument-console rebuild (dotfiles e993c3f): the panel is now a floating, user-resizable window (set_default_size(420, 560)), no longer a right-anchored layer-shell surface. The task's mechanic ("keep the right edge fixed, extend the left border leftward") assumed the old anchored surface, which no longer exists — the width is now drag-adjustable. Cancelled per the 2026-07-04 audit (Craig's call to close rather than re-file a "bump the 420px default" task). +** DONE [#C] minimal tier zsh login PATH gap :bug:dotfiles:quick:solo: +CLOSED: [2026-07-09 Thu] +:PROPERTIES: +:LAST_REVIEWED: 2026-07-09 +:END: +Fixed (dotfiles =c6a7878=). =minimal/.zprofile= added, mirroring =common/='s PATH-only prepend and its rationale comment. -Start the network panel a bit wider — keep the right edge fixed (it's right-anchored), extend the left border leftward. Raised from roam capture 2026-07-03. +Tested the invariant rather than the file, because the bug *was* a per-file fix missing a tier: any stow tier shipping a =.zshrc= must ship a =.zprofile= that prepends =~/.local/bin=. Red first, and it named =minimal=. Verified behaviorally as the task asked — stowed the tier into a throwaway HOME and read a real =zsh -lc 'echo $PATH'=, which now leads with =~/.local/bin=. Full =make test= green. -** DONE [#B] Net panel doctor results can't display :bug:waybar:network: -CLOSED: [2026-07-04 Sat] -Resolved by the 2026-07-03 instrument-console rebuild (dotfiles e993c3f): the panel gained a streaming output well (gui.py) with a "Copy results" button (via wl-copy) and a dismiss control that collapses the well back to the panel's pre-open height (_shrink_to_compact asks Hyprland to resize back). Doctor/speed-test output streams into it as appended lines — matching the task's ask for a tall results box, copy button, and collapse-back. This capture (filed 2026-07-03 morning) predates the same-day 22:06 redesign that addressed it. +The 2026-07-08 fix gave common/ a .zprofile (zsh logins never read .profile, so ~/.local/bin was missing from TTY/ssh PATH — dotfiles 39cef40). The minimal/ tier has the same gap: it carries .zshrc and .profile but no .zprofile, so headless installs (DESKTOP_ENV=none stows minimal/ instead of common/) still get zsh logins without ~/.local/bin. Add a matching minimal/.zprofile (PATH-only prepend, same rationale comment). Verify: fresh minimal stow, zsh -lc 'echo $PATH' shows ~/.local/bin first. The installer itself is clean — it writes no shell profiles; the stow layer owns this. -The doctor diagnostic output is unreadable — the results well is too constrained to show the multi-line result. It should open a results box tall enough for several lines with a copy-results button; closing it via an X in the box's upper-right collapses the space back to what it occupied before. Raised from roam capture 2026-07-03. +** TODO [#C] zfs base VM image build failure: ZFS DKMS module missing :bug:zfs: +:PROPERTIES: +:LAST_REVIEWED: 2026-07-09 +:END: +=FS_PROFILE=zfs make test-vm-base= fails inside the VM at initramfs time: archangel reports "ZFS module not found! DKMS build may have failed" against the installed kernel (linux-lts 6.18.38 at the 2026-07-08 attempt). Likely a kernel/OpenZFS version skew — the archzfs zfs-dkms may not support 6.18 yet. Consequences: the maint scenario harness's zfs lane (Phase 12) is filtered but unexercised, and a real zfs bare-metal install via archangel would plausibly hit the same wall. Diagnosis needs the in-VM pacman/DKMS logs (rerun create-base-vm.sh with =--keep=-style access or pull /var/log from the ISO session before teardown). Priority per the bug matrix: Major severity (zfs install path broken) × some-users-sometimes = P3. When fixed, run =FS_PROFILE=zfs bash scripts/testing/run-maint-scenarios.sh --list= and add zfs scenario files (zpool scrub / autotrim / snapshot destroy) to the harness. + +** TODO [#C] Consistent keybinding family for the panel console :feature:hyprland: +:PROPERTIES: +:LAST_REVIEWED: 2026-07-09 +:END: +Consider putting every panel (net, bluetooth, audio, timer, and the coming maintenance console) on one consistent chord family — a shared modifier set (Super+Shift, Control+Alt, or similar) plus a mnemonic letter per panel (N/B/A/T/M). Today the panels open via waybar clicks only; a uniform chord family makes them keyboard-reachable and predictable. Watch for collisions with existing binds: Super+Shift+A is already PTT toggle, and the hold-to-talk grave bind is load-bearing. Decide the family, audit current hyprland binds for conflicts, wire via the dotfiles hyprland config, and document in the keybind reference. Both machines (velox can't QMK-remap, so chords must work on a plain laptop keyboard). + +** TODO [#C] Realtime lamp output for the net + bt doctors :feature:solo: +:PROPERTIES: +:LAST_REVIEWED: 2026-07-09 +:END: +Retrofit the net doctor (=~/.dotfiles/net/src/net/doctor.py=) and bluetooth doctor (=~/.dotfiles/bluetooth/src/bt/doctor.py=) to stream results as a live output wall — one lamp per escalation step, amber while running, green on success, red on failure — instead of a final summary. Matches the maintenance-console doctor design (see [[file:docs/design/maintenance-console-design-ideas.org][maintenance-console-design-ideas.org]], "Doctor = live output wall"). Goal: every doctor in the system reads the same way. Both doctors already step through an escalation chain re-probing after each, so the steps are natural lamp boundaries. + +Scope note (Craig, 2026-07-07): realtime lamp *behavior* only. The maintenance console's wider results-wall layout (date+time stamp column, COPY, persistent history) does NOT backport — the net/bt panels are ~400px wide and lack the horizontal real estate. Their existing output wells keep their compact layout; this task just makes them stream live. + +Addendum (Craig, 2026-07-07): DO backport the 3.5-entry height convention — every panel's output well caps at 3.5 visible entries, the half-visible entry being the scroll cue, with the dark slate-on-black scrollbar. Layout stays compact per above; only the height cap + scroll affordance carries over. ** TODO [#B] Scrolling/Carousel layout: frame fit + wrap-around :hyprland: :PROPERTIES: -:LAST_REVIEWED: 2026-06-13 +:LAST_REVIEWED: 2026-07-06 :END: Disabled 2026-06-12 (bind and cycle entry points removed; Super+Shift+S reassigned to whole-desktop screenshot). The layout needs real work before it earns its chord back: - What fits in each frame: column/frame sizing so windows land at usable widths instead of arbitrary slices. @@ -75,7 +96,7 @@ Refiled from the archsetup task audit (2026-06-28), landed via ~/.dotfiles/inbox ** TODO [#B] Waybar network module — custom/net :feature:waybar:network: :PROPERTIES: -:LAST_REVIEWED: 2026-07-04 +:LAST_REVIEWED: 2026-07-09 :END: Unifies the old wifi-no-internet indicator (was =[#C]=) and the network-manager dropdown (was =[#B]=) into one =custom/net= module: a tested Python =net= engine @@ -173,7 +194,10 @@ The waybar network module ([#B] parent) is now COMPLETE through Phase 3. Phase 4 feature (indicator + recovery + panel + diagnostics + speed test) is done. Verify (manual, live): see Manual testing and validation. -*** TODO Phase 4 — docs + rollout :network: +*** 2026-07-09 Thu @ 16:32:54 -0500 Audit reconcile: Phase 4 is filed on the dotfiles side, waiting on them +The dotfiles project accepted the Phase 4 handoff and filed it as a =[#C]= task in their own =todo.org= (their note, 2026-07-08 16:56): the help-text audit + panel help affordance, the user-guide/README, and the ratio rollout doc. Not started there. They ping when it lands, and this task's Phase 4 child closes then. Nothing to do here meanwhile. + +*** TODO Phase 4 — docs + rollout :network:blocked: Deliverable: in-app help (=net --help= + per-command, panel help affordance); README/user-guide (commands, indicator states, panel, config keys, make targets, troubleshooting from the failure table, rollback); archsetup Hyprland dep install @@ -186,17 +210,21 @@ Build handed off to the dotfiles project 2026-07-04 (=~/.dotfiles/inbox/2026-07- *** TODO Phase 5 — VPN / WireGuard CLI fold (vNext) :network: Rescoped 2026-07-04 (audit): the tunnels track already shipped most of the original Phase 5. Panel tunnel bring-up/down and detection landed (dotfiles 2d9d060 probes tailscale/NM-wireguard/Proton; 21db05a brings overlays up/down from the panel's Tunnels sub-view; 31ba056 diagnose/doctor understand tunnel routes; archsetup 2e40781 wireguard config import; the net-panel-other-interfaces spec is IMPLEMENTED). What remains for Phase 5 is only the =net vpn ...= CLI subcommand — cli.py still has no vpn/tunnel parser. Fold the panel's existing tunnel operations into a CLI surface; spec separately when picked up. -** TODO [#B] Timer GTK panel :feature:waybar: +** TODO [#C] Waybar collapse control: replace the triangle glyph :feature:waybar: :PROPERTIES: -:LAST_REVIEWED: 2026-07-02 +:LAST_REVIEWED: 2026-07-04 :END: -Initial spec written 2026-07-02: [[file:docs/specs/2026-07-02-timer-panel-spec.org]] (DRAFT — four decisions await Craig's review before build; net-panel Blueprint/GTK4 stack, wtimer stays the state owner). +From the 2026-07-04 roam capture. The waybar collapse mechanism (click the triangle, the bar sections redisplay shortened) works, but the triangle glyph doesn't match the instrument-console aesthetic the panels now use. Replace it with something in keeping with the console look. Aesthetic decision — bring Craig two or three concrete glyph/style options (a machined chevron, a console-key style expander, an engraved caret) before wiring. Dotfiles waybar config (handled per the archsetup-owns-dotfiles rule). Raised alongside the net-panel/audio speedrun; deferred from it because the glyph choice is a taste call. -From Craig's roam capture 2026-07-02: give the timer a GTK UI/UX like the network panel. +** TODO [#C] Net panel: driver-health diagnostic tier :feature:network: +:PROPERTIES: +:LAST_REVIEWED: 2026-07-04 +:END: +Follow-up from the 2026-07-04 net-panel hardening speedrun (Craig's cj question on the no-WiFi item). The shipped no-wifi-hardware verdict covers "no adapter at all." This tier covers "adapter present but the driver is wedged": read-only health signals — =ip link= (device present but no-carrier / down), =dmesg= / =journalctl -k= for firmware-load failures, =rfkill= for a hard block, =modinfo= / =lsmod= for the driver module — classified before a generic reset. Remedy actions: a privileged =modprobe -r <mod> && modprobe <mod>= reload of the wifi driver, and a firmware-package pointer when the failure is a missing/failed firmware load. Dotfiles net-package work (handled per the archsetup-owns-dotfiles rule). Design pass first to decide whether it's worth a repair tier vs a needs-user-action pointer. -** TODO [#B] Desktop-settings dropdown panel :waybar: +** TODO [#B] Desktop-settings dropdown panel :feature:waybar: :PROPERTIES: -:LAST_REVIEWED: 2026-06-24 +:LAST_REVIEWED: 2026-07-09 :END: Initial spec written 2026-07-02: [[file:docs/specs/2026-07-02-desktop-settings-panel-spec.org]] (DRAFT — four decisions await Craig's review before build; architecture updated to the net panel's Blueprint/GTK4 stack). @@ -219,7 +247,7 @@ Implementation notes: a small GTK layer-shell app (mirror pocketbook's structure ** TODO [#B] Local offline LLM runtime + per-host model cache :tooling:llm: :PROPERTIES: -:LAST_REVIEWED: 2026-05-29 +:LAST_REVIEWED: 2026-07-06 :END: Add a local-LLM provisioning track so machines can run an offline coding agent when there's no network. Install =llama.cpp= (CPU + Vulkan where practical) and prefetch per-host model files while network is available; expose OpenAI-compat local endpoints (=127.0.0.1:8081= coding, =:8082= general; =:11434= reserved for =ollama= if used). Per the rulesets generic-agent-runtime design pass — rulesets becomes runtime-neutral and owns the runtime manifests + project instructions; archsetup owns machine provisioning + the per-machine model inventory. Source: handoff from rulesets 2026-05-28 ([[file:assets/outbox/2026-05-28-from-rulesets-local-llm-install.org][outbox copy]]). @@ -256,6 +284,9 @@ Boot the configured endpoint and send a short prompt; surface success/failure + Acceptance: fresh VM install of the ratio profile reaches an endpoint on =:8081= that answers a smoke prompt; velox profile gets Q4_K_M + 8B and answers a prompt within reasonable laptop latency; network-down install completes successfully with the pending-models warning surfaced. ** TODO [#C] Voice dictation / speech-to-text input :feature:tooling: +:PROPERTIES: +:LAST_REVIEWED: 2026-07-06 +:END: Push-to-talk dictation that types transcribed speech into the focused Wayland window — usable at any text field, including the Claude Code terminal prompt and Emacs buffers. Claude Code has no built-in voice input; dictation has to happen at the OS level and inject text. Raised 2026-07-03. Tool choice is the open decision (needs Craig): =nerd-dictation= (Vosk, lighter, lower accuracy) vs a =whisper.cpp=-based daemon (heavier, higher accuracy, optional GPU). Wayland typing backend is =wtype= or =ydotool=. Scope once chosen: install + model download, a push-to-talk keybind (Hyprland), and an autostart entry; fold into archsetup so it lands on both daily drivers. Consider an Emacs-native path (=whisper.el=) as a complement for in-buffer dictation. @@ -402,9 +433,9 @@ Need to investigate proper machine-ID regeneration that doesn't break networking Would enable parallel test execution in CI/CD Priority C because snapshot-based testing meets current needs -** TODO [#B] Fix install errors surfaced by the 2026-05-11 VM test run +** TODO [#C] Fix install errors surfaced by the 2026-05-11 VM test run :PROPERTIES: -:LAST_REVIEWED: 2026-06-15 +:LAST_REVIEWED: 2026-07-06 :END: *** 2026-06-28 Sun @ 13:29:29 -0400 Audit reconcile: 2026-06-28 btrfs+zfs runs reproduce the same residual set Newer full runs landed since the 2026-06-11 reconcile below: the 2026-06-25 zfs run (Testinfra 96/0) and the 2026-06-28 btrfs+zfs runs (97/0, "zero attributed issues"). The residual four were NOT fixed and reproduce unchanged: =enabling firewall= (archsetup:1496-1498, carries a VM-kernel note), =enabling gamemode for user= (archsetup:2221, non-critical), and =tidaler (AUR)=. Zero archsetup-attributed Testinfra issues across both profiles confirms these are environment / non-critical, not archsetup bugs. Bare-metal confirmation of the firewall pair is still the open thread. @@ -430,9 +461,9 @@ Root cause was in =retry_install=: =last_exit_code=$?= ran AFTER =if eval ...; t *** 2026-05-19 Tue @ 01:25:26 -0500 Verified the b9907c7 emacs-stow fix end-to-end =make test= 21:44 → 22:29 (42 min), =test-results/20260518-214516/=. 52/0/5, =ArchSetup Exit Code: 0=. The third-branch path fired correctly — install log =archsetup-2026-05-18-21-45-46.log:14358-14365= shows =From https://git.cjennings.net/dotemacs= → =[new branch] main -> origin/main= → =Reset branch 'main'= → =branch 'main' set up to track 'origin/main'=. No exit-128, no =fatal: not a git repository=. Error Summary down to 7 (was 13 on 2026-05-16); the emacs entry is gone. AUR exit-0 logging triggered for 2 packages this run (mkinitcpio-firmware, tidaler) vs 6 on 2026-05-16 — same bug class, fewer triggers, still tracked under =[#B] AUR exit-0 logged as error=. Issue Attribution: 1 ARCHSETUP entry (Proton VPN Daemon failed — known VM-no-VPN-config artifact). Cleanup ran clean via the normal path. -** TODO [#B] Review undeclared ratio packages for installer inclusion +** TODO [#B] Review undeclared ratio packages for installer inclusion :chore: :PROPERTIES: -:LAST_REVIEWED: 2026-06-24 +:LAST_REVIEWED: 2026-07-09 :END: Triggered by the 2026-06-14 =make package-diff= run on ratio: 62 packages are installed but not declared in archsetup. Stripped of the structural buckets — pacstrap base/boot/kernel (base, linux*, grub, efibootmgr, sudo, btrfs-progs, fwupd, logrotate, ex-vi-compat, linux-lts-strix, zram-generator), the =make deps= VM set (qemu-full, virt-manager, virt-viewer, libguestfs, bridge-utils, dnsmasq, archiso), and the yay bootstrap — these 40 remain. Check the ones to add to the installer, then rerun =make package-diff= to confirm they clear. @@ -513,19 +544,9 @@ Read recommended resources to make informed security decisions (see metrics for :END: Practical guidelines for working in public spaces -** CANCELLED [#B] Test each modernization thoroughly before replacing -CLOSED: [2026-07-04 Sat] -Retired in the 2026-07-04 audit (Craig's call): a standing-judgment umbrella with no completion criterion. The fleet is Hyprland-only now, and per-change test discipline is already carried by the actual work (TDD + the VM harness), so this adds nothing to track. Original intent: ensure new tools integrate with the Hyprland environment and don't break workflow (archsetup still supports DWM/X11 but no current machine uses it). - -** DONE [#C] Window focus lost when unhiding stashed windows :bug:hyprland: -CLOSED: [2026-07-04 Sat] -Verified fixed live on ratio 2026-07-04 (Craig at the machine). Stash (Super+O) → restore (Super+Shift+O) left the restored window focused, and Super+J/K (layout-navigate) cycled focus normally afterward — both original symptoms gone. Resolved by two fixes that postdated the filing: dotfiles 5619342 (raise window on focus nav, stop float focus-follow, 2026-06-28) and 09815f3 (cycle focus by address so j/k works in monocle, 2026-06-29). Both confirmed present in ratio's HEAD and in the live layout-navigate script at test time. - -From the roam inbox: hiding a window (e.g. the org-capture popup) then unhiding it should leave the unhidden window focused, but another window typically takes focus. Also =ctrl+j/k= (layout-navigate) can't reach the unhidden window afterward — it should always reach any visible window except the waybar. Involves stash-restore + layout-navigate; needs interactive reproduction with Craig. (Note: the actual bind is Super+J/K, not ctrl+j/k as the capture said.) - -** TODO [#C] Ensure sleep/suspend works on laptops +** TODO [#B] Ensure sleep/suspend works on laptops :PROPERTIES: -:LAST_REVIEWED: 2026-06-09 +:LAST_REVIEWED: 2026-07-06 :END: Critical functionality for laptop use - current battery drain unacceptable *NOTE:* This applies to Framework Laptop (velox), not Framework Desktop (ratio) @@ -533,9 +554,9 @@ Add kernel parameter: ~rtc_cmos.use_acpi_alarm=1~ (will become systemd default) Consider: ~acpi_mask_gpe=0x1A~ for battery drain, suspend-then-hibernate config See Framework community notes on logind.conf and sleep.conf settings -** TODO [#C] Re-check python-lyricsgenius --skipinteg workaround :solo: +** TODO [#C] Re-check python-lyricsgenius --skipinteg workaround :chore:solo: :PROPERTIES: -:LAST_REVIEWED: 2026-06-24 +:LAST_REVIEWED: 2026-07-09 :END: archsetup installs =python-lyricsgenius= with =--mflags --skipinteg=, skipping makepkg integrity + PGP checks — a workaround originally for an expired-signature issue upstream (surfaced by the 2026-06-23 --noconfirm audit). Periodically test whether the cause has cleared: if a plain =aur_install python-lyricsgenius= builds without complaint, drop the =--skipinteg= workaround. Removal needs a real AUR build to confirm, so it isn't a blind change. @@ -549,7 +570,7 @@ Ran =makepkg --verifysource= on the current AUR PKGBUILD (3.7.0-1). The package ** TODO [#C] Review theme config architecture for dunst/fuzzel :PROPERTIES: -:LAST_REVIEWED: 2026-05-21 +:LAST_REVIEWED: 2026-07-06 :END: The active dunst config is stowed from dotfiles/common/ but theme templates live in dotfiles/hyprland/.config/themes/. set-theme copies the templates to @@ -563,7 +584,7 @@ The goal is a single place to edit each config, not two. ** TODO [#C] Review current tool pain points annually :PROPERTIES: -:LAST_REVIEWED: 2026-05-21 +:LAST_REVIEWED: 2026-07-06 :END: Once-yearly systematic inventory of known deficiencies and friction points in current toolset @@ -596,7 +617,141 @@ Parse yay errors and provide specific, actionable fixes instead of generic error ** TODO [#D] Improve progress indicators throughout install Enhance existing indicators to show what's happening in real-time -** TODO Manual testing and validation +** TODO [#B] Manual testing and validation :test: +:PROPERTIES: +:LAST_REVIEWED: 2026-07-09 +:END: +Craig's standing checklist of everything that isn't agent-verifiable. Each child is one test in the =verification.md= shape (title, what we're verifying, steps, Expected). A child that fails gets its actual behavior written under it and is promoted to a top-level TODO. 44 checks pending as of the 2026-07-09 audit. + +Priority and type tag added by that audit: the task carried neither, which kept the project's largest live container out of the agenda entirely. + +*** Maintenance console — in-person checklist +Re-homed here by the 2026-07-09 audit: this was a second top-level "Manual testing and validation" parent. One parent, per verification.md. Priority and the :test: tag now live on the parent. +Promoted from the build parent when it closed 2026-07-08 — Craig's checklist, runs once in person. Collects everything not agent-verifiable; populate per verification.md as phases land. Known so far: panel look-and-feel vs the E5 prototype (Craig's eyeball); arm-press wording reads right at the moment of use; results-wall readability during a real doctor run; a real UPDATE through the armed guard (and the TTY path once); REBOOT offer after an update; velox in-person glyph check (battery %, charging glyph, low-charge red); SET 80% charge limit sticks across a charge cycle; KILL on a real memory hog. +Phase 8 additions: subpanel scroll position survives an arm press on a long digest (MAINT_PANEL_FIXTURE=bad, PACKAGES band, scroll into the 51-orphan list, press REMOVE — the list should stay put and the key read REMOVE?); a KEEP on a real orphan dims it into the kept section and UNKEEP restores it; MERGE on a needs-merge pacnew opens pacdiff in a foot terminal. +Phase 9a additions: MARK KNOWN on a real journal group (ratio's wpa_supplicant bgscan spam is the standing candidate) — arm shows the identifier + sample wording, fire writes curation.toml, the next re-scan drops the group into KNOWN NOISE with "marked <date>", and UNMARK restores it to SIGNAL; OPEN JOURNAL opens journalctl -p 3 -b in a foot terminal; a failed unit's RESTART on a real unit (systemctl restart via the armed key) lands the re-probe on the act line. +Phase 9b additions: MARK EXPECTED on ratio's real unexpected listeners (postgres :5432, docker :8080 — the standing curation material) dims them into the expected list and UNMARK restores them; a CPU-mode press (e.g. BAL·PWR) writes the EPP hint and the row re-reads it; KILL renders disabled (insensitive, tooltip) on a session-critical name in top memory; on velox the battery row shows NO SET 80% key (cros_ec knob absent — correct withholding, not a bug). +Phase 11 additions: glyph left-click opens the console and a second click closes it; right-click still toggles the btop scratchpad; the glyph color matches the board's worst lever-less diagnostic (open the console and check the watch items against the glyph tint); after fixing/curating a diagnostic, the glyph follows within one 30-min scan (or immediately after a manual =maint scan=); on velox in person — battery % + charging icon in the bar, collapsed bar keeps the battery glyph, low-charge red (drain below 15% or temporarily raise battery_low_pct in the TOML); after velox's reboot confirm systemctl --user list-timers shows both maint timers scheduled. +Phase 10 additions: results-wall readability during a real doctor run (press CLEAN UP on the live board — every step should appear as a running line the moment it starts, resolve in place with the result, and the re-probed truth should follow; the wall should feel like watching the run, not reading a report afterward); COPY puts the same lines the CLI wall prints on the clipboard, paste somewhere to check; HIDE collapses the wall to its header and SHOW restores it with the scroll position at the bottom; REVIEW & FIX reads right at the moment of use (each row: what's wrong, the maint fix usage, and either a FIX key or "per-item — on its subpanel"); RECLAIM on a degraded disk row arms with the first step + "(+N more steps)" and the fire streams every step to the wall individually; a guard-tripped UPDATE fired from the REVIEW roster re-arms with the override wording and the second press actually runs (the token fix — verify the loop terminates). +Phase 11b additions: the fidelity eyeball — open the live console beside the settled E5 prototype (headless-Chrome render or the browser) and judge whether the board finally reads as the same instrument: big-number typography, bar fills with the gold threshold tick on the cache card, the scrub/keyring/topgrade rings, status chips, the lit CPU-mode segment, selector count chips + severity left borders, right-aligned attention/ok/fixable/watch header; press an EPP segment on the card (e.g. BAL·PWR) and confirm it arms then fires exactly like the old digest control; on the bad fixture (MAINT_PANEL_FIXTURE=bad maint panel) check a long readout (the BACKUPS card) ellipsizes with a tooltip carrying the full value. +**** 2026-07-08 Wed @ 06:31:00 -0500 Fixed TOPGRADE remedy failure found in testing (dotfiles 9b5384f) +Craig's first testing find: TOPGRADE exited 2 on every launch — topgrade 17.6.1 names the git step git_repos and the remedy passed --disable git, rejected by clap before any step ran. Fixed red-first (test pins git_repos), full make test green, dry-run on the installed binary confirms the step name lands and the repo-rebasing step stays excluded. Velox pulled. RE-TEST: press TOPGRADE again (it's guard-armed on ratio's pending mesa set — TTY is still the safe path for the mesa run). + +*** CPU mode survives a reboot +What we're verifying: =maint-epp-restore.service= replays the CPU mode the pill last set. The kernel resets EPP to the driver default on every boot, so this is the only check that can prove the fix; nothing an agent runs before a reboot distinguishes "it works" from "nothing was reset yet." +- Open the maintenance console, MEM·PWR band, and press a CPU-mode segment other than the current one (say BAL·PWR). +- Confirm the act line says the mode was remembered. +#+begin_src sh :results output +cat /sys/devices/system/cpu/cpu0/cpufreq/energy_performance_preference +grep -A1 '^\[power\]' ~/.config/maint/*.toml 2>/dev/null +#+end_src +- Reboot. +- After logging back in, run the block below. +#+begin_src sh :results output +cat /sys/devices/system/cpu/cpu0/cpufreq/energy_performance_preference +systemctl --user status maint-epp-restore.service --no-pager | head -5 +#+end_src +Expected: the EPP value after the reboot is the mode you selected, not the driver default, and the unit shows =Active: inactive (dead)= with =Result=success= (a oneshot that ran and exited). + +*** Audio signal metering: apply the package (precondition) +What we're verifying: the signal-metering build is live. Only a =git pull= is needed — the =audio/= package .py files are stowed and no new launcher files were added, so no restow or reboot. The shared panel CSS gained a =.lamp.dim= class (live-idle); it's in the same stowed =panel.css=. +#+begin_src sh :results output +cd ~/.dotfiles && git pull +#+end_src +Expected: the pull succeeds (commits 6054d3d..21437b4). +*** Three-state activity lamp reads live +What we're verifying: the INPUT/OUTPUT lamps show muted / live-idle / live-active from real device state, not just mute. +- Open the audio panel (bar sound glyph / Super+A). With nothing playing and the output unmuted, look at the OUTPUT lamp. +Expected: a dim (not bright) green lamp — live-idle: unmuted but no audio flowing. +- Start playing audio (music, a video). +Expected: the OUTPUT lamp brightens to a bright glowing green — live-active. +- Mute the output (click OUTPUT). +Expected: the lamp goes red — muted. +*** OUTPUT needle tracks real signal, not the fader +What we're verifying: the OUTPUT VU needle moves with actual output level, replacing the old volume-fed needle. +- With audio playing, watch the OUT · PLAY needle; then pause the audio (leave the volume fader where it is). +Expected: the needle deflects and rides the music while playing, and falls to rest when paused even though the fader hasn't moved. (The old behavior held the needle up at the fader position.) +*** INPUT needle registers the mic, including during PTT +What we're verifying: the IN · MIC needle shows real mic level and confirms PTT is capturing. +- With the mic live (INPUT unmuted), speak. +Expected: the IN · MIC needle deflects with your voice. +- Set the mic to muted, then hold the PUSH·TALK key and speak. +Expected: while held, the INPUT lamp reads live and the IN · MIC needle registers your voice; on release it rests again. +*** No parec / CPU cost when the panel is closed +What we're verifying: the meter runs only while the panel is open and leaves no orphan =parec=. +- Open the audio panel, then close it (✕, Esc, or the bar click). +#+begin_src sh :results output +pgrep -af 'parec .*@DEFAULT_(MONITOR|SOURCE)@' || echo "no parec running — clean" +#+end_src +Expected: no matching =parec= process after the panel is closed (the process groups were reaped on teardown). +*** Per-device activity lamp on each device row (option 3) +What we're verifying: each OUTPUTS/INPUTS row leads with a lamp showing whether THAT device is live — red (muted) / dim green (unmuted, idle) / bright green (carrying audio) — so you can see which specific device has sound, not just the default. +- Open the panel. Scan the leading lamp on each output/input row. +Expected: muted devices show a red lamp; unmuted devices show green. +- Play audio to a non-default output (or switch the default to a device, then play). +Expected: the row of the device actually playing shows the brightest (live-active) green; an unmuted-but-silent device shows the dimmer (live-idle) green. Judge whether the dim-vs-bright contrast is clear enough — if not, say so and the CSS is a one-line tweak. +- Note: the row lamps update on the panel's status refresh (~3s), so allow a moment after audio starts. +*** Timer redesign: apply the package + wtimer, clear stale timers (precondition) +What we're verifying: the redesigned =timer/= package + =wtimer= are live. Only a =git pull= is needed — the package .py files and =wtimer= are already stowed, and this build added no new launcher files, so no restow or reboot. Clear any pre-existing timers first so a stale-shape state file (an old pomodoro item lacking the new =cfg=) can't crash =wtimer render=. +#+begin_src sh :results output +cd ~/.dotfiles && git pull +wtimer cancel-all +#+end_src +Expected: the pull succeeds and the bar timer glyph goes idle (no active items). +*** Panel opens hero-on-top; close via the ✕, Esc, and the bar +What we're verifying: the redesigned layout renders (header, hero, CONFIGURE, queue) and every close path works. +- Click the timer module on the bar. +Expected: the panel opens floating top-right — a header faceplate, a HERO block for the primary item, the CONFIGURE strip, then a QUEUE well below. +- Click the ✕ in the header; reopen; press Esc; reopen; click the bar module again. +Expected: each of ✕, Esc, and the bar click closes the panel. +*** Timer with repeat +What we're verifying: the REPEAT toggle re-arms a timer on fire instead of dropping it. +- Pick TIMER, type =10s= (fast to observe), toggle REPEAT on, click +. +Expected: the timer appears carrying a "repeat" badge and a "timer · repeats" sub-line. +- Let it fire. +Expected: it notifies and immediately re-arms to a fresh full countdown rather than vanishing. +*** Alarm: recurring day, snooze, ringing, dismiss +What we're verifying: a recurring alarm rings (not fire-and-vanish), snooze re-arms, and dismiss re-arms a recurring one. +- Pick ALARM, type a clock time about a minute ahead (HH:MM), select today's weekday in the day picker, set SNOOZE to 1, click +. +Expected: the row shows the fire clock time and a weekday badge; the hero donut is a countdown ring to the fire time. +- Let it reach the fire time. +Expected: the hero shows RING and the bar glyph goes urgent (RING); SNOOZE and DISMISS buttons appear on the hero. +- Click SNOOZE. +Expected: ringing stops and the alarm re-arms one minute out. +- Let it ring again, click DISMISS. +Expected: ringing stops; because it is recurring it re-arms to its next matching day (a one-shot alarm would be removed instead). +*** Pomodoro: configurable cycle, auto vs await, cycle dots +What we're verifying: the config grid drives the cycle and the AUTO toggle switches between auto-advance and awaiting a start. +- Pick POMODORO, tap a named cycle (e.g. Deep) to fill the grid, set WORK S to 1 (min) for a fast run, toggle AUTO off, click + ADD CYCLE. +Expected: a pomodoro starts in work; the hero shows cycle dots and "work · cycle 1/N". +- Let the work phase fire with AUTO off. +Expected: it enters the rest phase awaiting a start — the hero shows "ready · start break" and a START BREAK button, and it does not count down until pressed. +- Press START BREAK. +Expected: the rest phase counts down; the glyph/color shifts to the break (sage) state. +*** Stopwatch: sweep dial, lap badge, promote, stop +What we're verifying: the analog sweep dial animates, the last-lap ghost badge shows, promote works, and STOP just stops (run-save deferred). +- Pick STOPWATCH, click +. If it isn't the hero, promote it (the ▲ on its queue row). +Expected: the hero donut is an analog second-hand dial sweeping once per minute (not a percentage ring); the elapsed value counts up. +- Hit LAP a couple of times (name one via the popover). +Expected: a "LAP m:ss" ghost badge appears beside the elapsed value and the sub-line shows the lap count. +- Hit STOP. +Expected: the stopwatch is removed. No =~/org/stopwatch-runs.org= entry is written — run-save is deferred to a later version. +*** Queue sort, promote/cycle, 10-item cap +- Add a 25m timer, a 5m timer, and an alarm a few minutes out. +Expected: the queue orders soonest-first; a ringing alarm jumps above everything. +- Use ‹ / › on the hero (or ▲ on a queue row) to change the primary. +Expected: the hero (bar-slot) item changes accordingly and the bar glyph follows. +- Queue items until 10 exist. +Expected: + disables with a "queue is full (10/10)" reason; running countdowns advance live (the =wtimer watch= subscription). +*** Presets: locked defaults, half-past, custom add/delete +- On TIMER, note the default chips carry no × (locked); add a custom preset via + preset, then delete it with its ×. +Expected: locked defaults can't be deleted; a custom preset adds and deletes cleanly. +- On ALARM, tap the half-past chip, click +. +Expected: an alarm is created at the next :30. +*** Bar tooltip parity +What we're verifying: the bar tooltip mirrors the redesign verbatim. +- With a pomodoro, a paused timer, and a ringing alarm active, hover the bar module. +Expected: each item lists on its own line — the pomodoro as "label cycle/iv countdown" (no phase word), the paused one with a "(paused)" suffix, the ringing one as "RING (ringing)". *** Audio panel: apply the new shims + configs (precondition for the tests below) What we're verifying: the new =audio=/=audio-panel=/=waybar-audio= bin shims and the hyprland.conf + waybar config edits are live. New files need a restow (a plain =git pull= doesn't symlink them). Quit Hyprland or run from a TTY — restowing live Hyprland writes a stub hyprland.conf. #+begin_src sh :results output @@ -642,20 +797,8 @@ Expected: it reads as a sibling of the net and bt panels; nothing overflows the - Right-click the waybar sound module. Expected: the panel opens (left-click still mutes, scroll still changes volume). -*** Timer dialog: Escape cancels at every step -What we're verifying: Escape in the real fuzzel dialogs aborts the whole flow (the fix rides fuzzel's abort exit code; the unit tests fake it, this is the live confirmation). -- Left-click the timer module on the bar, pick "timer", then hit Escape at the Duration prompt. -Expected: no Label prompt appears and no timer is created (bar count unchanged). -- Left-click again and hit Escape at the very first Timer-type menu, and once more at the Label prompt after entering a duration. -Expected: each Escape ends the flow with nothing created. - -*** Timer alarm: 12-hour time accepted, bad input notifies -What we're verifying: the alarm prompt takes 12h shapes live and errors are audible instead of silent. -- Left-click the timer module, pick "alarm", enter 2:30pm (or any 12h form), Enter through the label. -Expected: an alarm appears on the bar showing 14:30 as its fire time. -- Repeat with garbage like 99:99. -Expected: a fail notification names the bad input; nothing is created. -- Cancel the test alarm (right-click the module, pick it). +*** SUPERSEDED — Timer fuzzel dialogs (Escape-cancel, 12h alarm entry) +These two tests exercised the old fuzzel creation dialogs, retired when the bar's =custom/timer= on-click became =timer-panel=. The panel redesign checklist above covers the same ground: alarm entry (12h shapes, bad-input reject) is now the panel's ALARM freeform + inline validation, and the whole-flow abort is the panel's Esc/✕/bar close. Nothing to run here; kept as a pointer so the intent isn't lost. *** Speed test streams in the panel What we're verifying: the panel's speed test fills in as phases complete instead of dumping everything at the end (the CLI path is live-verified; this is the GTK rendering). @@ -665,7 +808,7 @@ Expected: a Ping/Download/Upload checklist appears under the running row; ping l *** Proton VPN CLI sign-in (velox now, ratio on its trip) What we're verifying: the proton CLI has its own account store (separate from the retired GTK app's), so the panel's proton rows can't toggle until you sign in once per machine. -- Run in a terminal: protonvpn login <your-proton-username> (it prompts for the password). +- Run in a terminal: protonvpn signin <your-proton-username> (it prompts for the password). The CLI's action is =signin=, not =login=. #+begin_src sh :results output protonvpn info #+end_src @@ -674,8 +817,8 @@ Expected: Account shows your Proton username instead of 'None'. After that, prot *** Tunnels round-trip: panel rows + bar badge (first real tunnel-owned route) What we're verifying: the panel's Tunnels tab drives a real wireguard tunnel up and down, and the bar indicator grows the vpn badge while the tunnel owns the default route (the badge has never rendered live — every prior check ran with the wlan owning the route). - Open the net panel (left-click the bar's net module), switch Connections to the Tunnels page. -- Confirm the rows: tailscale (up), and the seven Proton configs (USNY, USDC, USCALA, USCASF, USGAAT, switzerlan-zurich1/2), all down. -- Select USNY, press Bring Up, wait for the row to land. +- Confirm the rows: tailscale (up), and the three Proton configs (wg-US-TX-714, wg-US-CA-144, wg-NL-781), all down. +- Select wg-US-TX-714, press Bring Up, wait for the row to land. Expected: the bar's net glyph gains the small vpn badge; its tooltip names the owner ("Tunnel: default route via wgpvpn (wireguard)"). - Press Bring Down on the same row. Expected: badge gone, tooltip back to normal, internet still works (the wlan owns the route again). @@ -871,9 +1014,15 @@ NOTE (2026-07-04 audit): the "four-tab panel" framing predates the instrument-co ** DOING [#B] Prepare for GitHub open-source release :PROPERTIES: -:LAST_REVIEWED: 2026-07-04 +:LAST_REVIEWED: 2026-07-09 :END: Remove personal info, credentials, and code quality issues before publishing. +*** 2026-07-09 Thu @ 16:32:54 -0500 Audit reconcile: docs/ scrubbed; scripts and history still open +The =docs/= half of "remove personal information" landed (=a8f7e9b=). Four absolute =/home/cjennings= paths became the repo root, a home-relative path, or a named variable — every snippet stays runnable. Three =~/projects/home= references and three =.ai/sessions/= links, all dead in any clone but Craig's, were replaced by what they mean. No =file:= links broke. + +The scrub also turned up something the epic didn't name: nine tracked files carried =#+AUTHOR: Craig Jennings & Claude=, a co-author who is not a person and which survives conversion into docx, a wiki page, or a PDF. The five under =docs/= are fixed. *Four remain, and they are Craig's call* (dated records under =assets/=, which the rule says stay as history, but which are tracked and would publish): =assets/2026-06-19-collapsible-waybar-sides-spec.org=, =assets/2026-07-03-instrument-console-panels-build-summary.org=, =assets/outbox/2025-11-08-keyring-fix-next-steps.org=, =assets/outbox/2025-11-08-test-failure-analysis.org=. + +The two open children below are untouched: the =scripts/= personal-information pass, and the git-history secret scrub. *** 2026-06-16 Tue @ 00:55:39 -0500 Six dotfiles-scoped sub-tasks moved to the ~/.dotfiles project Per the 2026-06-16 task audit, the six sub-tasks targeting files now owned by the standalone =~/.dotfiles= repo were handed off to that project (newly bootstrapped as its own AI project) and removed from this epic: "Remove credentials and secrets from dotfiles", "Remove/template personal info from dotfiles", "Remove binary font files from repo", "Move battery out of waybar sysmonitor group", "Resolution-adaptive scratchpad sizing", and "Dynamic waybar/foot config based on screen resolution". Handoff: =~/.dotfiles/inbox/2026-06-16-0053-from-archsetup-dotfiles-release-prep-handoff.org=. This epic now covers archsetup-proper release work only (scripts personal-info, device-specific config, history scrub, shellcheck, SPDX headers, README/LICENSE). The 2026-06-09 reconciliation note below is the prior state. *** 2026-06-09 Tue @ 19:21:36 -0500 Reconciliation: six sub-tasks now target the ~/.dotfiles repo, not archsetup @@ -976,189 +1125,34 @@ Rewrote the bare =if $var= boolean conditionals (=show_status_only=, =fresh_inst *** 2026-05-26 Tue @ 15:27:09 -0500 eval task moot — the line-434 eval is gone, the survivor is deliberate Verified: the only =eval= left in =archsetup= is line 578 in =retry_install=, and it's intentional and documented — it captures =$?= directly from =eval "$cmd"= to dodge the if-compound-swallows-exit-code trap. Replacing it with an array would reintroduce that bug. The line-434 eval this task pointed at no longer exists. Nothing to change. -* Archsetup Resolved - -** DONE [#B] VM test harness shared one NVRAM file across filesystem profiles :bug:test: -CLOSED: [2026-06-27 Sat] -The harness shared one OVMF NVRAM file (=vm-images/OVMF_VARS.fd=) across the btrfs -and zfs profiles (=init_vm_paths= suffixed the disk image per profile but not the -NVRAM). NVRAM lives outside the qcow2, so a disk-snapshot revert can't restore it, -and a zfs run's ZFSBootMenu boot entries clobbered the btrfs GRUB entry. With no -removable =\EFI\BOOT\BOOTX64.EFI= fallback on the base ESP, the next btrfs run -booted into UEFI with no bootable device ("BdsDxe: No bootable option or device -was found", then PXE/HTTP, then SSH timeout before archsetup ran). Found -2026-06-27 trying to VM-validate the installer refactor. - -Fixed: =OVMF_VARS= now carries the same per-profile suffix as the disk image -(=OVMF_VARS${img_suffix}.fd=) in =vm-utils.sh init_vm_paths=, so btrfs and zfs keep -separate NVRAM. Validated by a full green zfs run 2026-06-27 (ArchSetup exit 0, -Testinfra 96 passed / 0 failed). Remaining hardening tracked below. -** DONE [#B] Guard against live mesa/hyprland/wayland-runtime updates :hyprland: -CLOSED: [2026-06-28 Sun] +** TODO [#B] Doctor button for the audio panel :feature:audio: :PROPERTIES: -:LAST_REVIEWED: 2026-06-09 +:LAST_REVIEWED: 2026-07-09 :END: -A live =pacman -Syu= that swaps mesa/hyprland/wayland runtime libs out from under a running Hyprland session can crash the compositor: the next GPU-lib call hits a now-"(deleted)" library and SIGABRTs, taking the Wayland clients down with it. Hit ratio 2026-06-07 (mesa 26.0.6 -> 26.1.2 + hyprland upgraded live; Hyprland SIGABRT took down awww/insync/emacs). Likely the driver behind ratio's high lifetime unsafe-shutdown ratio — a crashed compositor forces a hard reset. +What happens when pipewire or pulseaudio is broken — would we know? Spec: [[file:docs/specs/2026-07-09-audio-doctor-spec.org][docs/specs/2026-07-09-audio-doctor-spec.org]] — *READY*, all eight decisions closed 2026-07-09, Phase 0 shipped. Buildable: Phase 1 diag, Phase 2 classify, Phase 3 repair + doctor, Phase 4 the DOCTOR key and wall. Grounded in a live survey of ratio's stack: there is no PulseAudio (pipewire + wireplumber + pipewire-pulse, all user-scope, so no sudo anywhere), =pactl= hangs against a server that accepts and never answers, and the panel can't diagnose itself because =pactl= is the layer most likely to be down. Mirrors the net panel's diag/classify/repair/doctor split, not the maint console's priv-verb table. -Shipped as a pacman PreTransaction hook rather than a wrapper, so it fires no matter how the upgrade is launched (pacman, yay, topgrade). =scripts/hypr-live-update-guard= aborts the transaction before any package is swapped when the GPU/compositor runtime set is being upgraded AND Hyprland is running, pointing the user to re-run from a TTY with the session stopped; it stays quiet when Hyprland isn't running (the safe from-a-TTY path). Override via =HYPR_ALLOW_LIVE_UPDATE=1= or by touching the sentinel file named in the abort message. archsetup installs the script to =/usr/local/bin= and the hook to =/etc/pacman.d/hooks/= in the hyprland path. Decision logic unit-tested (=tests/hypr-live-update-guard=, 9 cases). Live firing test filed under Manual testing and validation. Commits: archsetup (this session). -** DONE [#B] Collapsible waybar sides :waybar: -CLOSED: [2026-06-27 Sat] -:PROPERTIES: -:LAST_REVIEWED: 2026-06-09 -:END: -Let either side of the waybar collapse horizontally to a minimal base set, toggled by a click. Each collapsible side carries a small triangle / arrowhead pointing toward the screen edge it collapses into (away from center). Clicking it collapses that side to its base set and flips the arrow to point back toward center; clicking again restores the full side. Same shape-changes-with-state idea as the auto-dim indicator. +Re-graded =[#C]= → =[#B]= by the 2026-07-09 audit: the spec is written, Phase 0 shipped, and six decisions are all that stand between this and a build. Active backlog, not parking lot. -Spec (2026-06-19): [[file:assets/2026-06-19-collapsible-waybar-sides-spec.org]]. Spike that settled the mechanism: [[file:assets/2026-06-18-collapsible-waybar-sides-spike-findings.org]]. +*** 2026-07-09 Thu @ 14:12:00 -0500 Fixed: waybar's audio module died against a half-wedged server +My first read of this was wrong. =pactl.run()= already carries =timeout=8= and raises =PactlTimeout=, so the panel degrades and does not freeze. Correcting the record rather than leaving the claim standing. -Decisions locked: right base set = date + worldclock + tray; left base set = menu + workspaces; per-side independent; host-agnostic (base set constant, full set is each host's existing config). Mechanism = config-swap + SIGUSR2 reload via an active-config copy in =$XDG_RUNTIME_DIR= (the CSS/state-file approach was disproven — GTK3 can't reflow-hide native modules). Lives in =~/.dotfiles/hyprland/=. +The real bug, found by driving it: =build_status()= guarded its two device-list reads but called =default_sink()= and =default_source()= outside the guard. A server that answered the lists and then stopped answering raised =PactlTimeout= straight out of a function whose whole contract is to degrade to =ok: False=. =waybar-audio= calls it with nothing catching it, so the bar's audio module died instead of dimming; the GTK panel survived only because its background worker catches everything. -Shipped per spec (dotfiles 804bef6): 3 TDD'd scripts (=waybar-active-config=, =waybar-collapse=, =waybar-arrow=; 22 cases), arrow modules wired into the config (left arrow innermost-left, right arrow innermost-right), CSS ×3, =$mod+[= / =$mod+]= keybinds, and =waybar-toggle= relaunch updated to load the active config so a crash preserves collapse state. Verified live: click, keybind, and per-side independence all work; expand round-trips exactly to canonical. -** DONE [#C] Collapse waybar sysmonitor to a single icon + hover :feature:waybar: -CLOSED: [2026-06-27 Sat] -:PROPERTIES: -:LAST_REVIEWED: 2026-06-24 -:END: -From the roam inbox (2026-06-22): replace the spread-out sysmonitor readouts (temp, cpu, mem, storage) with one visible icon showing a single chosen metric, the rest in the hover tooltip. Open question: fold it into the battery component instead of a standalone module. Implementation lives in the waybar config under ~/.dotfiles. +A server that hangs on the *first* call never exposes this — =list_sinks= raises inside the guard and masks the unguarded reads behind it — so the obvious regression test passes against broken code. =fake-pactl= grew =FAKE_PACTL_SLEEP_DEFAULTS= to express "answers the lists, hangs on the defaults". Verified end to end: =waybar-audio= now emits =class: degraded= / "Audio graph unavailable" and exits 0. dotfiles =4d42eb3=; full =make test= green. -Shipped as a standalone =custom/sysmon= module (Craig's call: host-dependent primary — battery on laptop, disk on desktop — rather than fold into battery, which is laptop-only). Backing script =waybar-sysmon= gathers cpu/temp/mem/disk/battery, shows the host-appropriate metric, rest in tooltip; 13-case TDD suite; removed the 5 native modules + their CSS across all 3 themes. Dotfiles be7469b. -** DONE [#C] Rename idle inhibitor to something more intuitive :chore:waybar: -CLOSED: [2026-06-27 Sat] +** TODO [#D] Telega coredump recurrence tell :bug:maint: :PROPERTIES: -:LAST_REVIEWED: 2026-06-24 +:LAST_REVIEWED: 2026-07-09 :END: -From the roam inbox (2026-06-24): the "idle inhibitor" name doesn't work as a mnemonic — something like "sleep" (i.e. "keep awake" / "no-sleep") would land better. Decide the new name, then rename across the touchpoints: the =custom/idle= waybar module, the keybind mnemonic, and the backing script names (=hypridle-toggle= / =waybar-idle= from the 2026-06-24 idle-inhibitor work). Needs Craig's call on the name first, so not solo. -Renamed to "caffeine" (Craig's call, 2026-06-27): =custom/caffeine= module, =waybar-caffeine= + =caffeine-toggle= scripts, tooltip "Caffeine: ON/OFF", CSS + test suites updated. Keybind stays =$mod+I= (=$mod+C= is hyprpicker). Shipped in dotfiles 8b45b51. -** DONE [#B] ZFS pre-pacman snapshot installer step (ZFS-root) :feature:zfs: -CLOSED: [2026-06-30 Tue] -Add a ZFS-root-gated installer step that installs the pre-pacman snapshot pacman hook plus a self-pruning =/usr/local/bin/zfs-pre-snapshot= (KEEP=10). The script is hand-placed on velox, not authored by archsetup, so a reinstall loses it; snapshots accumulated unbounded (53 since April) because nothing prunes them and Sanoid ignores non-autosnap_ names. Gate to ZFS-root (velox; ratio is btrfs). Also correct the stale 2026-01-17 security-doc line claiming it's "already in install-archzfs". Needs the hook file (source from velox) and a ZFS-root VM test. +*** 2026-07-09 Thu @ 16:32:54 -0500 Audit reconcile: the fix is holding; re-graded C → D +Verified rather than assumed: =~/.telega/telega-server.log= carries zero =tdat_plist_value= assertions, and the newest telega-server coredump is 2026-07-08 16:57 — before .emacs.d upgraded the elisp. Nothing has recurred. + +Re-graded =[#C]= → =[#D]= per the bug matrix. There is no defect to fix here; it is a watch item with a named tell, and the severity × frequency read is cosmetic (host coredump noise on a metric we own) × rare edge case → P4 → =[#D]=. It stays on the list only so the tell isn't lost. +The maintenance console's coredump metric flagged telega-server on ratio (8 coredumps) and velox (18). Root cause was a version skew: the Dockerized =zevlg/telega-server:latest= is frozen at the 2026-06-05 build while the installed elisp lagged at 20260513, so the newer server's plist parser choked on the older elisp's output. .emacs.d fixed it by upgrading telega to 20260706 on both machines (docker kept, =docker pull= is a no-op against the frozen image). Host-coredump pollution should stop. If zevlg later pushes a =:latest= that outruns the installed elisp, the skew and the coredumps recur — the tell is a fresh =tdat_plist_value:500= assertion in =~/.telega/telega-server.log=. The durable escape is a host-native pinned TDLib build, at the cost of an AUR source build. + +* Archsetup Resolved -Shipped: =configure_pre_pacman_snapshots()= in boot_ux (late, ZFS-gated) + =scripts/zfs-pre-snapshot=; unit tests for pruning + a Testinfra assertion. VM-verified ZFS install passed 97/0 (test_zfs_pre_pacman_snapshot_hook PASSED). The "stale doc" turned out accurate (it's an install-archzfs archive) — left as-is. Design notes and the KEEP=10 script: [[file:docs/design/2026-06-29-zfs-pre-snapshot-installer.org]]. Origin: home handoff 2026-06-29. -** DONE [#B] Waybar timer module :waybar: -CLOSED: [2026-06-29 Mon] -:PROPERTIES: -:LAST_REVIEWED: 2026-05-26 -:END: -Shipped as =wtimer= in the dotfiles repo (=134d61e=), a single always-visible module right of the battery/resource readout, non-collapsible. Covers all four modes (timer / alarm / stopwatch / pomodoro) with multiple running at once: the bar shows the most urgent item with a per-type glyph + "+N" badge, the tooltip lists them all. Left-click creates (fuzzel), middle-click pauses, right-click cancels, scroll cycles the primary; notify fires on completion and pomodoro phase changes. Pure-functions-over-injected-clock design; CLI serializes state with flock + atomic write so the 1s render and click handlers never lose an update or double-fire. TDD: 86 cases, 95% line coverage. Design spec: [[file:docs/design/2026-06-29-waybar-timer-module-spec.org][docs/design/2026-06-29-waybar-timer-module-spec.org]]. Live-verified on velox (glyph renders, position, countdown); the color states + click interactions filed under Manual testing and validation. - -A custom waybar module providing three time-keeping functions, surfaced in the bar with click/scroll controls and dunst notifications on completion. - -- *Alarm* — fire a notification at a wall-clock time (e.g. 2:00pm). Builds on the existing =notify= + =at= pattern from protocols.org. -- *Timer* — count down a duration (e.g. 25m) and notify when it elapses. -- *Pomodoro* — alternating work/break cycles (default 25/5, long break after 4) with the bar showing phase + remaining time. - -Implementation notes (to flesh out when picked up): waybar =custom= module(s) with =exec= polling or a persistent =exec= script emitting JSON; click actions to start/pause/reset; a small state file under =~/.local/state= or =~/.local/var=. Lives in the hyprland tier (=dotfiles/hyprland/.config/waybar/= + a backing script in =hyprland/.local/bin/=). TDD the backing script per testing.md. - -*** 2026-06-24 Wed @ 17:32:37 -0400 Scope expansion from roam capture (folded duplicate) -A roam-inbox capture asked for the same widget and expands the scope, so folding it in here rather than duplicating: -- *One panel, mode-selectable* — a single component where you choose timer / stopwatch / alarm; the icon changes to reflect the selected mode. -- *Stopwatch* — a count-up (the third function alongside the alarm/timer/pomodoro above), hover shows start time ("Stopwatch started: 12:22pm"). -- Hover text per mode: timer "Timer: 5 min", alarm "Alarm: 12:15pm", stopwatch "Stopwatch started: 12:22pm". -- *Multiple simultaneous* — several timers/alarms/stopwatches set and displayed at once, in one panel. -- Deliverable includes proposing a few panel designs and recommending one before building. -** DONE [#B] Sysmon module right-click cycles the visible metric :feature:waybar:solo: -CLOSED: [2026-06-28 Sun] -Shipped in the dotfiles repo (=f7b6896=, implemented from this archsetup session per Craig). =waybar-sysmon= reads a selected metric from =$XDG_RUNTIME_DIR/waybar/sysmon-metric= (absent = host default, so the old behavior is preserved); the new =sysmon-cycle= helper advances through a host-appropriate ring (battery only on a laptop), wraps, and refreshes waybar via signal 12 wired to =on-click-right=. Left-click stays the btop popup. Added cpu/temp/mem icons + thresholds. TDD: 13 new =waybar-sysmon= selection cases + a 9-case =sysmon-cycle= suite, full dotfiles suite green (29 suites). =sysmon-cycle= symlinked into =~/.local/bin= on velox. Live visual/relogin check filed under "Manual testing and validation". Handoff sent to the dotfiles inbox. -Builds on the just-shipped =custom/sysmon= collapse (dotfiles be7469b). Right-clicking the module rotates which metric is the visible one, in a fixed order: battery → cpu → temp → mem → disk → back to battery. Each click advances one step and wraps around. The host default (battery on a laptop, disk on a desktop) is the starting/reset metric; the tooltip keeps showing all metrics regardless. Left-click stays =pypr toggle monitor= (the btop popup) — the cycle lives on =on-click-right=. - -Implementation notes: =waybar-sysmon= needs a persisted selection (a state file in =$XDG_RUNTIME_DIR/waybar/=, absent = host default) that it reads to pick the visible metric. A new =sysmon-cycle= helper bumps the index and signals the module to refresh (add a =signal= to =custom/sysmon=, like the other custom modules; wire =sysmon-cycle= to =on-click-right=). TDD both — extend =tests/waybar-sysmon= for selection-driven output, add a =tests/sysmon-cycle= for the index advance/wrap and the signal. -** DONE [#B] Network module: enterprise WiFi add/edit deferred to vNext :waybar:network: -CLOSED: [2026-06-29 Mon] -Decided 2026-06-29 (Craig): keep v1 to open + WPA-PSK add/edit; the -WPA-Enterprise / 802.1X add/edit form is vNext, not a v1 phase. v1 still -*activates* any saved enterprise profile and points editing at nmtui/nmcli. -Evidence that settled it: 24 saved profiles on velox, 18 WPA-PSK, 0 enterprise — -no 802.1X network in Craig's history, so the form would be unused UI. If one ever -appears, nmtui adds it once and the module activates it thereafter. Spec: -[[file:docs/design/2026-06-29-waybar-network-module-spec.org][2026-06-29-waybar-network-module-spec.org]]. -** CANCELLED [#B] Migrate terminal emulator from foot to ghostty :tooling: -CLOSED: [2026-06-28 Sun 13:58] -:PROPERTIES: -:LAST_REVIEWED: 2026-06-24 -:END: -Decision (Craig, 2026-06-24): switch from foot to ghostty. Drivers: ligatures (foot won't add them) and kitty-graphics + sixel image support (foot is sixel-only, no kitty-graphics plans). ghostty is pure-Wayland on Hyprland, declarative config that fits the theme system, runtime config reload (keybind / SIGUSR2 since 1.2). Trade-off accepted: slightly higher input latency than foot. Already in use as Emacs's terminal renderer, so the config + rendering are familiar and the 06-18 tmux theme was tuned against that surface. Full evaluation: [[file:docs/2026-06-10-terminal-emulator-evaluation.org][docs/2026-06-10-terminal-emulator-evaluation.org]]. - -Migration scope: -- archsetup: add =ghostty= to the package list; decide whether to keep =foot= installed as a fallback or drop it. -- dotfiles: port =foot.ini= → ghostty config (flat key=value). The shared foot.ini sets no font (per-host via =host.ini= include) — replicate that per-host font split for ghostty. -- Themes: the dupre/hudson =themes/<name>/= dirs hold foot configs; add ghostty theme files and teach =set-theme= to write + reload the ghostty config. Watch the reload-clobbers-OSC-10/11 bug (ghostty #2795) when wiring runtime theme switch. -- hyprland.conf: default-terminal keybind, pyprland scratchpad terminals, and any other =foot= references → ghostty. -- Verify on velox + ratio: ligatures render, latency acceptable in tmux+vterm use, dupre theme correct, sixel/kitty-graphics previews work. -** DONE [#C] Scratchpad launch turns on focus-follows-mouse :bug:hyprland: -CLOSED: [2026-06-28 Sun] -:PROPERTIES: -:LAST_REVIEWED: 2026-06-28 -:END: -Root cause: =float_switch_override_focus = 1= in hyprland.conf. With =follow_mouse = 0=, focus still jumped to the window under the pointer when it crossed a floating-tiled boundary, so launching a floating scratchpad re-enabled focus-follows-mouse onto tiled windows. Fixed by setting it to 0 (dotfiles =5619342=). Not a pyprland side effect. - -Imported from roam inbox 2026-06-25. Repro: with two tiled windows, moving the mouse over the other tile does nothing (focus-follows-mouse off, as expected). Then launch a terminal (scratchpad), move the mouse over a tile, and focus now switches to the window under the pointer. Something about the scratchpad/terminal launch flips focus-follows-mouse on. Find what re-enables it (likely a Hyprland focus/input setting or a pyprland scratchpad side effect) and keep it off. -** DONE [#B] mod+J/K focus navigation: raise to front, reach floating, monocle fix :feature:bug:hyprland: -CLOSED: [2026-06-29 Mon] -Three improvements to =layout-navigate= (mod+J/K), validated live on velox: -- Raise the focused window to the front on focus navigation, so focusing a window behind an overlapping floating one brings it forward (dotfiles =5619342=, bundled with the =float_switch_override_focus = 0= scratchpad fix tracked above). -- Cycle into floating windows, so you can navigate back to a scratchpad like any window instead of it being a one-way trip (dotfiles =f2107f7=). -- Fixed a monocle regression from that change: the =cyclenext= dispatcher no-ops between monocle-stacked tiles, so focus navigation now computes the workspace window list and focuses the next/prev by address — layout-independent and floating-inclusive (dotfiles =09815f3=). -** CANCELLED [#C] archsetup Waybar Wi-Fi module should show no-internet state :feature:waybar: -CLOSED: [2026-06-29 Mon] -Consolidated, not dropped: the no-internet/captive indicator + the diagnostics/ -bounce/speed-test scope are now Phase 1 + Phase 3 of the unified -[[*Waybar network module — custom/net][Waybar network module — custom/net]] parent. The work continues there; -this separate entry is retired so it's tracked in one place. Spec: -[[file:docs/design/2026-06-29-waybar-network-module-spec.org][2026-06-29-waybar-network-module-spec.org]]. -** CANCELLED [#B] Audit dotfiles/common directory -CLOSED: [2026-06-28 Sun] -Refiled to the standalone =~/.dotfiles= repo, which owns this content since the 2026-06-16 split. Handoff sent 2026-06-28: =~/.dotfiles/inbox/2026-06-28-1335-from-archsetup-refiled-from-archsetup-task-audit-2026.org=. The three sub-tasks (review ~/.local/bin scripts, remove orphaned configs, verify stowed files are used) travel with it. Cancelled here, not abandoned. -** CANCELLED [#C] Zoom launches in a tiny window :bug:hyprland: -CLOSED: [2026-06-28 Sun 13:56] -:PROPERTIES: -:LAST_REVIEWED: 2026-06-24 -:END: -From the roam inbox: Zoom opens at a tiny size. Needs diagnosis (HiDPI scaling vs a window rule vs XWayland) and live verification with Zoom actually running — held for a Craig-driven debug pass, not a blind fix. -** DONE [#B] btrfs base VM unbuildable — archangel ISO bakes zfs-auto-snapshot :bug:test: -CLOSED: [2026-06-28 Sun] -Resolved: archangel shipped a fixed ISO (2026-06-27) that conditions the baked AUR list on the filesystem, so a btrfs install no longer drags in =zfs-auto-snapshot=. The btrfs base rebuilt and went green in the 2026-06-28 VM run (97/0, zero attributed issues). The EFI removable-fallback hardening is archangel-side and optional. -=make test-vm-base= (btrfs) fails in archangel's installer: the ISO bakes a fixed -AUR list ("downgrade yay informant zrepl pacman-cleanup-hook zfs-auto-snapshot -topgrade ventoy-bin") into every install regardless of =FILESYSTEM=. On a btrfs -install =zfs= isn't present, so =zfs-auto-snapshot='s =zfs= dependency can't -resolve and the unattended pacstrap aborts ("unable to satisfy dependency 'zfs' -required by zfs-auto-snapshot"). This is an archangel ISO bug (the baked list isn't -controllable from =archsetup-test.conf=), so it blocks btrfs-profile VM testing -until archangel ships an ISO that conditions the AUR list on the filesystem (or -drops zfs tooling from non-zfs installs). The 2026-06-27 btrfs base regen attempt -also wiped the prior (unbootable) btrfs base, so there's no btrfs base image until -this is fixed. zfs-profile testing works (=make test FS_PROFILE=zfs=). - -Companion hardening (defense-in-depth, archangel-side): install the bootloader -with a removable =\EFI\BOOT\BOOTX64.EFI= fallback so a base boots even from -fresh/empty NVRAM, and real installs survive firmware that drops boot entries. -** DONE [#B] Network panel UI — review findings :feature:waybar:network: -CLOSED: [2026-07-01 Wed] -Full UI review 2026-07-01 (visual walk of every state + code pass over the view logic), 30 findings: 21 from the agent review + color audit, 9 from Craig. All fixed the same night in a no-approvals speedrun, five commits, each test-gated (33 suites) and the whole panel re-verified via AT-SPI smoke + screenshots. - -*** 2026-07-02 Wed @ 00:00 -0400 Theme pass: contrast, hierarchy, focus, hover, destructive, dialogs (dotfiles 82aad0b, 998829b) -Selected-row captions turned cream (the dim gray measured 2.2:1 on the slate fill, a WCAG fail on the auto-selected active row). Names gained weight and captions dropped a size (type hierarchy beyond color). Focus rings gold, scrollbars slim slate, rows got a hover wash. Disconnect and Forget became terracotta destructive-action (red = off). The Join/Add dialogs carry the dupre contract now (ground, mono, styled entries with a gold focus border, no stock blue anywhere), the Add dialog disables its action button until the SSID is non-empty, its password field gained the peek icon, and long SSIDs ellipsize. - -*** 2026-07-02 Wed @ 00:00 -0400 Connections logic: lies, gaps, races (dotfiles 693f820) -Saved profiles stopped claiming "open" (security shows only when the scan knows it; subtitles are view-aware — Available adds signal %, Saved says just "out of range"). An active wired connection pins above the wifi scan in Available. Add connects immediately (Craig's decision), both add dialogs' action reads Connect. Rescan went through the op state machine (the dead guard let double-clicks double-scan). A failed initial load shows in the boxes with a retry instead of stranding "Loading…". Available's first message is "Scanning networks…". Live-info ages humanize (7m, not 445s). A held portal replaces the internet line (stable row height under the poll). The poll pauses on other tabs. VPN profiles got a VPN glyph. Forgetting the active network warns it will disconnect you. - -*** 2026-07-02 Wed @ 00:00 -0400 Diagnostics restructure: selector, live speed test, streamed verdicts, leaner chrome (dotfiles 787b475) -The six-button wall became a dropdown tool selector (full tool names, a description of the selection, one Run button) revealed under Advanced. Speed Test became Network Performance: a reveal with Run Speedtest + Stop (no button-flips-to-Cancel), a once-a-second elapsed ticker while running, and Download / Upload / Ping (high-latency warn) / Server / Tip rows in the diagnose aesthetic. Get Me Online streams the diagnose rows first, announces each attempt ("Trying Reset Connection…"), then its result, and closes with a bold verdict row — as do diagnose, the single tools, and the portal login; verdicts left the status line. Get Me Online at a held captive portal opens the login flow (doctor runs the safe, reversible portal-login when fix is requested). Connecting to a network that turns out captive sends a desktop heads-up, waits a beat, then opens the login page. Diagnose evidence humanized ("open internet (HTTP 204)", "names resolve (captive.apple.com)"). The title row and Close button are gone (Esc + focus-loss auto-hide cover a transient popup) and the status line became a self-clearing toast. The AT-SPI driver anchors on the Diagnostics tab now. -** DONE [#B] Advanced repair buttons: half width, two per row :feature:waybar:network:quick: -CLOSED: [2026-07-01 Wed] -The wide Advanced buttons shrink the panel and leave the diagnostics output impossible to read. Make each half width, two to a row, and rename where needed to fit. Origin: roam inbox capture. - -Done 2026-07-01 (dotfiles aca6827): the Advanced reveal became a 3-column, 2-row grid (Diagnose, Unblock WiFi, Reset / Restart, Test DNS, Force Portal) per Craig's follow-up; labels shortened, tooltips carry the full descriptions. Verified live + AT-SPI smoke. -** DONE [#B] Panel action-button rows fill the panel width :feature:waybar:network:quick: -CLOSED: [2026-07-01 Wed] -Disconnect / Rescan / Add / Add Hidden, and the Saved row, should be as wide as the panel and the buttons above them. Apply the same homogeneous full-width treatment used on the Available / Saved sub-tabs. Origin: roam inbox capture. - -Done 2026-07-01 (dotfiles aca6827): both Connections action rows are homogeneous full-width, which also fixed the panel resizing when Connect flips to Disconnect. Verified live. -** DONE [#B] Live connection info in the row subtitle :feature:waybar:network: -CLOSED: [2026-07-01 Wed] -The live connection information shown in the row hover should also appear in the small print under the connection name, updated in realtime like the hover. Origin: roam inbox capture. - -Done 2026-07-01 (dotfiles aca6827): a 1.5s poll fills the active row's subtitle with the bar-tooltip fields minus the SSID (signal, interface, internet + age, portal note, throughput), sharing the bar's per-field formatters. Verified live. ** DONE [#B] Right-click date/time: ntp sync + timezone update :feature:waybar: CLOSED: [2026-07-02 Thu] Right-click on the date updates the clock from ntpd (or whatever keeps the clock in sync); right-click on the time runs the update-timezone script. Neither opens a terminal — both run transparently in the background. Errors surface as notifications. The module should detect whether we're online and, if not, show a message instead of running the script. Origin: roam inbox capture 2026-07-02. @@ -1198,35 +1192,6 @@ CLOSED: [2026-07-02 Thu] The end of a wtimer timer didn't fire a desktop notification. Needs reproduction to confirm frequency; priority follows the severity-by-frequency matrix once known (a reliably-missing timer-end alert would rate higher). Origin: roam inbox capture. Root-caused 2026-07-02 (dotfiles ca35642). The pipeline works (a live 3s timer fired and persisted), but notify sent everything --urgency=normal and dunstrc delays normal-urgency popups while a fullscreen window has focus — a timer ending mid-video sat invisible until fullscreen exit. Alarms now go critical urgency, which rides the fullscreen_show_critical rule; verified CRITICAL in dunst history. The alarm sound (paplay, separate from dunst) was never affected. -** DONE [#B] Bake captive-portal login into the net panel :feature:network: -CLOSED: [2026-07-01 Wed] -Make the captive-portal login a first-class net-panel feature instead of the one-off =~/.local/bin/hotel-wifi= script. When the engine sees a held portal, offer "Log in to this network" that runs the plain-DNS + clean-browser flow reversibly (disable DoT -> recover the portal URL from the redirect -> open a clean Chrome profile -> restore DoT when online). Reconcile with the existing =net portal= / =captive= helper, whose DNS-hijack-to-gateway model did NOT match the real Hyatt portal. - -Full mechanism writeup, the working script, and the integration plan: [[file:docs/design/2026-06-30-captive-portal-login.org]]. From the 2026-06-30 Hyatt saga. - -*** 2026-06-30 Tue @ 11:40 -0400 Engine core landed (dotfiles a7d7559) -Replaced =net portal='s old captive-helper hand-off with a =portal-login= repair tier: drop DoT to plain DNS, probe the portal URL (302 / meta-refresh), open a throwaway browser profile, spawn a detached watcher that restores DoT once online (or on timeout). =net portal --restore= is the manual fallback. 7 tests. So =net doctor= / the bar's =net portal= hookups already run the real flow now. Remaining: (1) name the DoT-blocking cause in =net diagnose=; (2) a dedicated "Log in to this network" button in the panel's Diagnose/Repair tab (today it rides the generic =net portal=); (3) live validation against a real captive portal (unit-tested only — didn't run it live to avoid disrupting a meeting). - -*** 2026-07-01 Wed @ 22:41:51 -0400 Live-validated end to end against a local captive simulator (dotfiles c1401db) -The last remainder. tests/net/captive_sim.py is a local redirect portal (302s to a login page until "logged in", then a clean 204). NET_PROBE_URL and NET_PORTAL_TRIGGERS point the whole flow at it (an overridden probe skips the interface binding, which can't reach loopback). Ran live on velox, both restore paths verified: online-detect (login click, watcher saw the 204, DoT drop-in restored within ~2s, clean exit) and the timeout fallback (a watcher that never saw online restored DoT at its 300s deadline). Real sudo mv, real resolved restarts, real redirect URL recovery, real clean-profile Chrome — against a temp drop-in dir, so live DNS was untouched. All three remainders are done; the task is closed. The remaining what-if is a real venue's walled-garden quirks, which only an actual portal exercises. - -*** 2026-07-01 Wed @ 21:44:05 -0400 Diagnose names the DoT block; panel gained Log in to This Network (dotfiles 51e0e2d) -Remainders 1 and 2 landed. The dns-resolve step names the DoT pin when resolution is dead and the drop-in exists (sysio.dot_forced), and routes next_action to the portal login. The panel's hidden Open Portal button became a first-class suggested-action "Log in to This Network", shown whenever the report holds a portal signal (portal step with or without a URL, or the DoT-blocked resolution) via the unit-tested viewmodel.wants_portal_login. TDD, 33 suites green. Remainder 3 (live validation against a real portal) still open. - -*** 2026-06-30 Tue @ 14:59:53 -0400 Live test on velox surfaced two fixed bugs + a deeper follow-up -Force portal (panel Repair tab) = =net-popup net portal= = the same portal-login tier. Tested live on @Hyatt_WiFi (already authorized, so no real intercept). Two bugs fixed in dotfiles (TDD, full suite green): -- Chrome first-run wizard fired on every launch — =_open_portal= made a fresh tempfile profile but passed no first-run flags. Added =--no-first-run --no-default-browser-check= + a unit test. -- Flashing sudo prompt for the DoT drop + pointless resolved restart on velox, where the DoT drop-in the code looks for (=/etc/systemd/resolved.conf.d/dns-over-tls.conf=) doesn't exist. Guarded =_disable_dot=/=_restore_dot= to be true no-ops (no sudo, no restart) when there's no DoT drop-in to move; tests assert no systemctl call fires. -** DONE [#B] Consistent red=off across waybar toggle modules :waybar: -CLOSED: [2026-07-01 Wed] -Extend the red=off convention (just added to the touchpad/mouse indicator) to the other toggles — sound volume, microphone mute, and caffeine — so a disabled / muted / off state reads red across the board. Skip the "cross"/slash; the color alone carries it. Origin: roam inbox capture. - -Already implemented (verified 2026-07-01): =style.css= gives =#pulseaudio.muted=, =#pulseaudio.mic.source-muted=, and =#custom-caffeine.inhibited= the off-state color =#d47c59=, matching =#custom-touchpad.disabled=. Note: caffeine's red fires on =.inhibited= (caffeine ON / staying awake), which is arguably the inverse of "off" — leave as-is unless you want strict off=red semantics there. -** DONE [#B] Microphone-mute keybind :feature:waybar:quick: -CLOSED: [2026-07-01 Wed] -A keyboard shortcut to toggle the mic mute. The pulseaudio#mic module shows the state but there's no hotkey to flip it. Wire a hyprland bind to a mic-mute toggle. Origin: roam inbox capture. - -Already implemented (verified 2026-07-01): hyprland.conf binds both =XF86AudioMicMute= and =Super+Shift+A= to =mic-toggle= (no conflict — airplane is Super+Shift+X). ** DONE [#C] Keybind hints in waybar module tooltips :waybar: CLOSED: [2026-07-02 Thu] Every module's hover tooltip should list its keyboard shortcut(s), for discoverability. Audit the modules and add the bindings to each tooltip. Origin: roam inbox capture. @@ -1242,16 +1207,6 @@ CLOSED: [2026-07-02 Thu] Let each wtimer item carry an optional short text label. The data model already supports it (=add_timer/add_alarm/add_stopwatch/add_pomodoro= all take =label=""=, and =_describe= shows =label or type=); the gap is the fuzzel-driven creation flow, which doesn't prompt for a label. Add the optional label prompt on create. Origin: roam inbox capture. Shipped 2026-07-02 (dotfiles ca35642): =wtimer new= gained a "label (optional)" fuzzel prompt after the type/value prompts; empty keeps the unlabeled default. 2 new tests (89 total in the suite). -** DONE [#C] Alarm tooltip shows time remaining, not alarm time :bug:waybar:quick: -CLOSED: [2026-07-01 Wed] -The =wtimer= alarm tooltip displays the countdown (time remaining) instead of the alarm's wall-clock fire time. For an alarm set to 2:00pm, the tooltip should name the target time, not "1h 23m left". Fix the tooltip rendering in =wtimer= (dotfiles repo). Origin: roam inbox capture. - -Fixed 2026-07-01 (dotfiles): =_describe= now renders an alarm's wall-clock target via a new =format_clock= helper instead of =format_time(remaining)=. TDD test added; full wtimer suite (87) green. -** DONE [#C] Waybar right-cluster module order :waybar:quick: -CLOSED: [2026-07-01 Wed] -Move the timer module to the rightmost position, just left of the systray, and move the battery/sysmonitor module to second-to-rightmost. Config edit in the waybar config (dotfiles hyprland tier). Origin: roam inbox capture. - -Done 2026-07-01 (dotfiles waybar config): =custom/timer= now sits just left of =tray= with =custom/sysmon= second-to-rightmost. waybar regenerated + reloaded live on velox; visual confirmation pending Craig. ** DONE [#B] Pocketbook finish-or-cancel decision :pocketbook: CLOSED: [2026-07-02 Thu] :PROPERTIES: @@ -1408,7 +1363,7 @@ CLOSED: [2026-07-03 Fri] :PROPERTIES: :SPEC_ID: e73877f5-4f5b-4f81-b946-dbaa6145e0d5 :END: -The no-approvals speedrun build of the console design Craig approved through five prototype iterations (2026-07-02/03). Spec: [[file:docs/specs/2026-07-03-instrument-console-panels-spec.org]] — the interactive prototype [[file:assets/2026-07-03-instrument-console-panels-prototype.html][assets/2026-07-03-instrument-console-panels-prototype.html]] is the normative design reference. Folds three open tasks: network panel redesign, bt switch placement + title, bt rename devices. Code in ~/.dotfiles (net/, bluetooth/, themes/dupre/panel.css). Final step: flip the spec to IMPLEMENTED, write the findings summary to file, finalize session context. +The no-approvals speedrun build of the console design Craig approved through five prototype iterations (2026-07-02/03). Spec: [[file:docs/specs/2026-07-03-instrument-console-panels-spec.org]] — the interactive prototype [[file:docs/prototypes/2026-07-03-instrument-console-panels-prototype.html][docs/prototypes/2026-07-03-instrument-console-panels-prototype.html]] is the normative design reference. Folds three open tasks: network panel redesign, bt switch placement + title, bt rename devices. Code in ~/.dotfiles (net/, bluetooth/, themes/dupre/panel.css). Final step: flip the spec to IMPLEMENTED, write the findings summary to file, finalize session context. *** 2026-07-03 Fri @ 03:20:00 -0400 Phase 2 shipped: net GTK-free console layer + engine verbs Dotfiles =81ec9c3= (TDD, 52 new tests, 581 net green). Pure presenter logic for the single-screen console, no view code touched: =viewmodel.net_faceplate= (state word + lamp + TUNNEL/AIRPLANE badges, wired-link-wins precedence), =network_console_rows= (ethernet pinned, radio-off note, active-then-signal sort, per-row lamp/caption/ladder/forget), =channel_headline= (wired device+speed / SSID+ladder+dBm / not-connected placeholder), =tunnel_console_rows=, dial-meter geometry (=meter_needle_deg= + =meter_scale= 100→1000 auto-relabel), =signal_bars=/=mbps_label=, and =panel.ArmState= (two-click arm-to-fire for forget/disconnect). Engine verbs: =manage.wifi_radio= (nmcli radio wifi on|off), =manage.device_up= (ethernet take-the-route), =sysio.link_speed_mbps= (/sys wired speed), =connections.ethernet_devices=, hidden flag on =manage.add=. @@ -1707,4 +1662,156 @@ CLOSED: [2026-07-03 Fri] :END: Went past the spec to a full build in a no-approvals speedrun. Spec is now IMPLEMENTED ([[file:docs/specs/2026-07-03-audio-panel-spec.org]], all 5 Decisions resolved). The panel shipped in the dotfiles repo (branch panel-bugfixing, commits 65e5bb0..9601420): pactl engine, GTK-free presenter, GTK instrument-console panel (OUTPUTS/INPUTS device rows with faders + per-device mute, LIVE/MUTED/PUSH·TALK mic keys, twin VU gauges, master quick-mute), Hyprland-bind push-to-talk, bar indicator, and the bar/keybind wiring (Super+A → panel, XF86AudioMute → master quick-mute). 102 unit tests + a passing AT-SPI smoke on velox. Live-eyeball validation filed under Manual testing and validation. Apply steps + follow-ups handed to the dotfiles project inbox. -Original ask (roam inbox, 2026-07-02): net/bt-panel kin — change default output/input, volume for both, push-to-talk mic mode for meetings, master quick-mute, bar sound-glyph state. Related bindings: Super+M audio-cycle ring, Super+Shift+A mic-toggle. Prototype: =working/sound-panel/sound-panel-prototype.html=. +Original ask (roam inbox, 2026-07-02): net/bt-panel kin — change default output/input, volume for both, push-to-talk mic mode for meetings, master quick-mute, bar sound-glyph state. Related bindings: Super+M audio-cycle ring, Super+Shift+A mic-toggle. Prototype: =docs/prototypes/2026-07-03-sound-panel-prototype.html=. +** DONE [#B] Panels moveable + resizable by drag :feature:waybar:network:bluetooth: +CLOSED: [2026-07-04 Sat] +Resolved by the 2026-07-03 instrument-console rebuild (dotfiles e993c3f): both net + bt panels switched from anchored gtk4-layer-shell overlays to normal floating windows (set_decorated(False), positioned by the net.cjennings.netpanel window rule), so Hyprland moves them on drag and resizes on corner-drag natively. That was exactly the "switch to a normal floating window" approach the design note flagged as the required decision. + +Both the net and bluetooth instrument-console panels should be repositionable and resizable at runtime: click-drag to move the panel anywhere on screen, drag the corners to resize. Raised from roam capture 2026-07-03. + +Design note: the panels are gtk4-layer-shell overlays anchored TOP+RIGHT with fixed margins — layer-shell surfaces are compositor-positioned, so free drag-move/resize needs either dynamic margin updates on pointer motion or a switch to a normal floating window (Hyprland moves/resizes those natively). Approach decision required before build. +** CANCELLED [#B] Net panel wider initial width :waybar:network:quick: +CLOSED: [2026-07-04 Sat] +Superseded by the 2026-07-03 instrument-console rebuild (dotfiles e993c3f): the panel is now a floating, user-resizable window (set_default_size(420, 560)), no longer a right-anchored layer-shell surface. The task's mechanic ("keep the right edge fixed, extend the left border leftward") assumed the old anchored surface, which no longer exists — the width is now drag-adjustable. Cancelled per the 2026-07-04 audit (Craig's call to close rather than re-file a "bump the 420px default" task). + +Start the network panel a bit wider — keep the right edge fixed (it's right-anchored), extend the left border leftward. Raised from roam capture 2026-07-03. +** DONE [#B] Net panel doctor results can't display :bug:waybar:network: +CLOSED: [2026-07-04 Sat] +Resolved by the 2026-07-03 instrument-console rebuild (dotfiles e993c3f): the panel gained a streaming output well (gui.py) with a "Copy results" button (via wl-copy) and a dismiss control that collapses the well back to the panel's pre-open height (_shrink_to_compact asks Hyprland to resize back). Doctor/speed-test output streams into it as appended lines — matching the task's ask for a tall results box, copy button, and collapse-back. This capture (filed 2026-07-03 morning) predates the same-day 22:06 redesign that addressed it. + +The doctor diagnostic output is unreadable — the results well is too constrained to show the multi-line result. It should open a results box tall enough for several lines with a copy-results button; closing it via an X in the box's upper-right collapses the space back to what it occupied before. Raised from roam capture 2026-07-03. +** DONE [#B] Timer GTK panel :feature:waybar: +CLOSED: [2026-07-05 Sun] +Built and shipped to dotfiles 2026-07-05 in a no-approvals speedrun (4 commits =1f4f270=..=78d3cbb=): wtimer gained watch/lap/save; a new =timer/= package holds a GTK-free PanelModel (62 tests) and the GTK instrument-console panel; the bar's =custom/timer= now opens the panel and the fuzzel creation flow retired. Spec: [[file:docs/specs/2026-07-02-timer-panel-spec.org]] (IMPLEMENTED). Code-complete; live GTK verification filed under Manual testing and validation below. + +From Craig's roam capture 2026-07-02: give the timer a GTK UI/UX like the network panel. Scope expanded via a later cj comment (queue/output-wall auto-sorted by fire time, stopwatch lap/stop + saveable runs, 5/25 configurable defaults, up to 10 timers, widget-gallery elements) — folded into the spec's Build scope and shipped. + +*** 2026-07-05 Sun @ 07:20:20 -0500 Redesign shipped — hero-on-top rebuild +The UI/UX redesign (decided through the prototype process, final = [[file:docs/prototypes/2026-07-02-timer-panel-prototype-3.html]]) built and shipped to dotfiles in a no-approvals speedrun, 5 commits =c7ac193=..=5a863b5=: Phase 1 wtimer engine (timer repeat; recurring alarms with snooze/ringing/dismiss; =@half=/=@hour=/=+dur= alarm parse; the rebuilt configurable pomodoro cycle — work/rest short+long, long-every-N, auto vs awaiting); Phase 2 PanelModel view-data (=row_view=, ringing-first sort, per-type create options as wtimer flags, locked presets + half-past + named pomodoro cycles); Phase 3 GTK hero-on-top panel (Cairo progress ring + stopwatch analog sweep dial, per-type create strips, one transport row, close ✕/Esc); Phase 4 bar-tooltip parity. wtimer + timer suites 231 green, full =make test= green. Spec re-flipped DOING → IMPLEMENTED. Stopwatch run-save deferred to vNext. Live GTK render is the refreshed manual checklist below. +** CANCELLED [#B] Test each modernization thoroughly before replacing +CLOSED: [2026-07-04 Sat] +Retired in the 2026-07-04 audit (Craig's call): a standing-judgment umbrella with no completion criterion. The fleet is Hyprland-only now, and per-change test discipline is already carried by the actual work (TDD + the VM harness), so this adds nothing to track. Original intent: ensure new tools integrate with the Hyprland environment and don't break workflow (archsetup still supports DWM/X11 but no current machine uses it). +** DONE [#C] Window focus lost when unhiding stashed windows :bug:hyprland: +CLOSED: [2026-07-04 Sat] +Verified fixed live on ratio 2026-07-04 (Craig at the machine). Stash (Super+O) → restore (Super+Shift+O) left the restored window focused, and Super+J/K (layout-navigate) cycled focus normally afterward — both original symptoms gone. Resolved by two fixes that postdated the filing: dotfiles 5619342 (raise window on focus nav, stop float focus-follow, 2026-06-28) and 09815f3 (cycle focus by address so j/k works in monocle, 2026-06-29). Both confirmed present in ratio's HEAD and in the live layout-navigate script at test time. + +From the roam inbox: hiding a window (e.g. the org-capture popup) then unhiding it should leave the unhidden window focused, but another window typically takes focus. Also =ctrl+j/k= (layout-navigate) can't reach the unhidden window afterward — it should always reach any visible window except the waybar. Involves stash-restore + layout-navigate; needs interactive reproduction with Craig. (Note: the actual bind is Super+J/K, not ctrl+j/k as the capture said.) +** DONE [#C] Instrument-console panel bugs (net/bt/audio) :bug:dotfiles: +CLOSED: [2026-07-05 Sun] +Batch from the roam inbox (2026-07-05). Panel code lives in =~/.dotfiles= (net/, bluetooth/, audio/). All eight shipped 2026-07-05, each verified live and covered by the panel test suites. +*** 2026-07-05 Sun @ 17:49:51 -0400 Titled the panel windows Network/Bluetooth/Audio (was python3) +The GTK app set no window title, so it fell back to the process name. dotfiles 2d03451. +*** 2026-07-05 Sun @ 17:49:51 -0400 Faceplate word is the subsystem name, not live state (net/bt/audio) +NETWORKING / BLUETOOTH / AUDIO; state now reads off the lamp colour + badges. Bluetooth's DOCTOR feedback (CHECKING/FIXING) moved to the status line and output well. dotfiles 5c58833. +*** 2026-07-05 Sun @ 17:49:51 -0400 Net panel flags a signed-out Proton CLI as needs-login +The proton probe reads =protonvpn info= first, since =protonvpn status= says Disconnected either way. The panel already blocks a needs-login row with a sign-in hint. dotfiles 2671472. +*** 2026-07-05 Sun @ 17:49:51 -0400 Net panel sorts live tunnels to the top +Stable within each backend group. dotfiles 2671472. +*** 2026-07-05 Sun @ 17:49:51 -0400 Net panel refuses a second full-tunnel VPN while one is active +tailscale is a mesh overlay, not a full tunnel, so it never conflicts. dotfiles 307a0fe. +*** 2026-07-05 Sun @ 17:49:51 -0400 Net panel dedupes Proton's own wireguard row +The proton CLI's =ProtonVPN <server>= NM profile no longer shows alongside the proton backend row. dotfiles dbc9ee8. +** DONE [#B] Maintenance console build :feature: +CLOSED: [2026-07-08 Wed] +:PROPERTIES: +:SPEC_ID: 9d9df833-c592-4aec-a7df-50d588e943ce +:END: +Build the maintenance console per [[file:docs/specs/2026-07-07-maintenance-console-spec.org][maintenance-console-spec.org]] (DOING; review incorporated 2026-07-07 — two rounds, all 13 decisions DONE, all 10 findings DONE, verdict Ready; Craig approved config paths + sysmon right-click re-homing). Design source of truth: [[file:docs/design/maintenance-console-design-ideas.org][maintenance-console-design-ideas.org]]; pixel reference: [[file:docs/prototypes/2026-07-07-maint-console-E5-selector-subpanel.html][E5 prototype]]. CLI-first =maint= package in dotfiles (archsetup-owns-dotfiles applies: edit, test, commit, push, note dotfiles inbox per phase); thresholds TOML + VM harness in archsetup. Each phase gates on green tests before its commit. + +*** 2026-07-07 Tue @ 20:11:23 -0500 Built Phase 1 — package skeleton, thresholds, contract (dotfiles 43a39ac) +maint/ package + shim + tests/maint/ (48 tests, fake-tool harness); thresholds two-layer merge with disable flags + env overrides; capability probe (battery ⇔ type==Battery, verified against ratio's Mains+USB tree); =maint status --json= with disk-usage/pacman-cache/failed-units pilots. Seeded configs/maintenance-thresholds.toml in archsetup from the workflow's values and installed a copy to ~/.config/archsetup/ on ratio (installer wiring stays Phase 13). Full make test green (50 suites). Live on ratio: worst=warn from the real 10.75 GB cache; du grades on its printed total, not its exit code (root-owned partial-download dirs make it exit 1) — pinned in a test. + +*** 2026-07-07 Tue @ 20:49:05 -0500 Built Phase 2 — storage & snapshot collectors (dotfiles 94e8371) +All Phase 2 collectors TDD'd against the fake-tool harness (argv-matched cases added): btrfs unallocated/scrub-age/device-errors (SMART cross-check), per-disk SMART health + last self-test, fstrim posture (discard mount option = continuous trim, ratio's real setup), disk top-consumers on a new slow-local cache (=maint scan --slow=, ~/.local/state/maint), snapper counts split timeline·single·pre-post (split keys on cleanup, not type), full ZFS family. =priv.py= pulled forward from Phase 6 (read-only verbs =smart_json= + =btrfs_scrub_status=, validated args, MAINT_SUDO) so probes stay elevation-free. maint suites 48→118; make test green (51). Live read-only verified on BOTH hosts: ratio worst=warn (10.75 GB cache; scrub 0d, SMART ok), velox over tailscale shows the ZFS family live (ONLINE/autotrim-on/scrub 1d/231 snaps) with a genuine fstrim warn (timer off — Phase 6 Confirm remedy). TOML gained storage keys (btrfs_unalloc_warn_gb, smart_*, fstrim_stale_days, hog_*); installed copies refreshed on ratio + velox. + +*** 2026-07-07 Tue @ 21:30:59 -0500 Built Phase 3 — packages, security, systemd collectors (dotfiles 3eaab6e) +All Phase 3 collectors TDD'd: orphans name+size with [curation.kept_orphans], pacnew safe-delete vs needs-merge, keyring freshness, reboot-required (uname -r vs modules dir), -Qkk on =maint scan --slow= with a noise split (unverifiable-as-user reads, mtime-only, [curation.qkk_known] paths tallied but never counted — an unfiltered live run sat permanently red at 68 "altered" files; classified, ratio shows 10 real findings); =maint scan --net= writes checkupdates/yay/arch-audit/fwupd caches (yay rc-1-silent = AUR-clean zero; failures never clobber good cache; malformed payloads degrade to unprobed), readers grade with age + stale bump; is-system-running with state-aware cause, failed-unit roster since/exit/journalctl hint, maintenance-timers meta-metric (capability-derived expected set, @-prefix instance match), taint letters decoded. Prereq done: arch-audit added to archsetup deps + installed both hosts. Review subagent (live-verified) found 2 Important (yay AUR-clean rc quirk, cache-shape envelope kill) + 2 minor — all fixed red-first; archsetup now enables btrfs-scrub@-.timer on btrfs installs (was ratio-only out-of-band, the meta-metric expects it). maint suites 118→169; full make test green. Live on ratio: 23-metric envelope, worst=warn from real findings (10.75GB cache, 8 orphans, mirrorlist.pacnew, 713 repo updates, 32 CVEs, 1 firmware). + +*** 2026-07-07 Tue @ 21:59:27 -0500 Built Phase 4 — logs + mem·pwr collectors (dotfiles 40c448b) +All Phase 4 collectors TDD'd: journal error digest (identifier groups w/ first/last seen + journalctl hint, known-noise curation tallied but never counted, byte-array MESSAGE decoded), coredumps by binary over a TOML window, kernel/hw event classes (MCE/EDAC/IO/thermal/GPU — generic driver noise excluded), journald disk usage, app-log staleness mirroring the log-cleanup cron's filename-date rules; memory pressure + top-5 RSS, OOM kills (kill-verdict lines only — one event spans several matching lines), swap/zram, temps via sensors -j (k10temp Tctl / coretemp package / amdgpu edge), Intel thermal-throttle count, EPP read, battery capacity/health/charge-cap, unclean-shutdown rate from wtmp (last -x pairing). Throttle/EPP/battery self-gate on sysfs and drop out of the envelope. Review subagent (live-verified) found 1 Blocking — windowed-empty coredumpctl exits 1 (my earlier --since-now pin was wrong), which left the metric permanently unprobed on healthy hosts — plus OOM ~3× overcount and a malformed-curation-entry envelope crash; all fixed red-first, re-review Approve. maint suites 169→227; full make test green. Live read-only both hosts: ratio 34 metrics (telega-server crashing 3× in window — real find), velox 38 (genuine 75% battery-health warn; charge_control_end_threshold ABSENT on velox's cros_ec hardware → Phase 9's SET 80% lever must gate on the file). TOML +journal_disk_warn_gb/coredump_window_days, new [memory], [power] temp/health/unclean keys; installed both hosts. + +*** 2026-07-07 Tue @ 22:35:28 -0500 Built Phase 5 — network posture + services collectors (dotfiles 2e92a9f) +All Phase 5 collectors TDD'd after live-pinning every tool format on both hosts (velox presence sweep over tailscale: full tool set present): nm_state (passive — NM's own connectivity check covers DNS+HTTP, so status never generates traffic), chronyc offset vs [network].ntp_offset_warn_ms, tailscale Health + peer tally, fail2ban per-jail bans, listeners digest via priv ss (expected-entry curation tallied-not-counted, unexpected+wildcard=crit, v4/v6 dedup, multi-holder sockets match any name), firewall (priv ufw status; fallback requires unit-active AND ufw.conf ENABLED=yes; names "ufw down — N public binds exposed" from the digest), rsyncshot freshness (1MB log-tail parse; hourly graded only where the log shows hourly runs — velox is daily-only; daily ≥48h/missing = crit), docker df JSONL + stopped containers w/ [curation.expected_containers], libvirt roster (queried only when libvirtd already active — a read must not socket-activate), cron drift (root rsyncshot expectations gate on backup-source). priv.py +4 read-only verbs; thresholds.curation_entries() tolerant helper. Review subagent (live-verified): Request Changes — 1 Important (malformed fail2ban counts raise ValueError into the envelope) + ufw-disable unit-state false positive, docker unknown-unit sizes reading 0, unfloatable chrony offset; all fixed red-first, re-review Approve. maint suites 227→297; full make test green (54 suites, unpiped exit 0 — an earlier tail-piped run masked make's exit code, rerun properly). Live both hosts: ratio 45 metrics, velox 49. REAL FINDS: listeners crit on both (ratio postgres/8080 wildcard binds, velox syncthing *:8384/*:22000) — curation material for Craig; backups stale on both since 2026-07-06 (truenas down for repairs, Craig confirmed — clears when it returns). TOML +[network]/hourly-backup/cron keys, installed both hosts. + +*** 2026-07-07 Tue @ 23:14:56 -0500 Built Phase 6 — remedy layer, priv verbs, guard, doctor CLI (dotfiles e171c08) +The full remedy layer TDD'd in one pass (no 6a/6b split needed): remedies.py allowlisted table (~30 remedies — exact argv, tiers per the design tables as amended by the dated decisions, re_probe_ids, long flags, RECLAIM SPACE macro resolving steps against the envelope incl. per-config snapper expansion); priv.py +28 write verbs (closed builders, type-validated args, per-verb timeouts; =build()= returns the exact sudo-prefixed argv so dry-run and the GUI arm-press share one string); guard.py pure live-update matcher over new TOML =[updates] guard_patterns= (fnmatch, case-sensitive, pairwise-tested; cold pending-cache is surfaced as a note, never silent); curation.py user-layer writes (mark/unmark/clear + KEEP/MARK KNOWN/EXPECTED wrappers, disable-flags for shipped defaults, self-verifying TOML emitter); doctor.py clean|review + iter_fix streaming the event feed (begin/ok/fail/guard/note; failures red without aborting; re-probed truth after each action); cli =maint fix <id> [--dry-run] [--force]= + =maint doctor clean|review= wall renderer; status envelope now stamps =levers= (always-on for free controls: epp_set, charge_limit, update, topgrade). KILL four guards: pid:name victim, TERM-only verb, session-critical denylist ([curation.session_critical], new TOML table) at preview, comm revalidation + denylist re-check at fire. Live-pinned before coding: coredumpctl HAS NO clean verb (find -mtime +N -delete via priv), log-cleanup at ~/.local/bin/cron/, rsyncshot takes <mode> <keep>, ufw enable needs --force, EPP value enum. Review subagent (live-verified): Request Changes — 1 Blocking (json.dumps astral-char surrogate pairs = invalid TOML: one emoji in a MARK KNOWN example would brick thresholds.load() for the whole console → ensure_ascii=False + write self-verifies by re-parsing before replace) + 1 Important (KeyError from sparse installed TOML escapes the event stream → caught, degrades to a red wall entry) + 2 Minor (cold-cache guard note, comm-vs-denylist re-check); all fixed red-first (6 new tests), re-review Approve. maint suites 297→393 (96 in the new suite); full make test green ×2 (unpiped, exit 0). VERIFIED LIVE on ratio: doctor clean executed the Auto tier for real — paccache -ruk0 freed 223.91 MiB and the re-probe measured the cache drop exactly; the live-update guard trips on ratio's genuinely-pending mesa/vulkan-radeon set. TOML +guard_patterns/+session_critical/+timeline_quarterly installed both hosts. + +*** 2026-07-07 Tue @ 23:52:40 -0500 Built Phase 7 — GUI shell (dotfiles d28435e) +The GTK4 window lands as the fourth panel sibling (net.cjennings.maintpanel, humble-object layering: GTK-free PanelModel presenter + pure viewmodel formatters + thin gui.py view). Read-only shell: faceplate (worst lamp, CVE/ATTN badges, MNT·01, close), inert doctor/update keys, updates strip (pending/CVE/AUR/firmware from the strip ids), eight-band selector (lamp + fixable/watch split from the envelope's levers; updates/security categories fold into PACKAGES), metric-row subpanel per band with per-id value formatting (bytes/percent/days/°C/ms/bool). Hydration off-thread; 3s live tier (build_live: cheap mem·pwr reads) + 30s full rebuild, both visibility-gated; MAINT_PANEL_FIXTURE=good|bad renders shipped envelope fixtures (reshaped from the E5 prototype by tests/maint/gen_fixtures.py, deterministic, real remedy ids) and freezes both tiers so the degraded board stays put on a healthy machine. Review subagent: Request Changes — 1 Blocking (build_live returned bare metrics, so the 3s merge stripped the levers the full envelope established and the band splits oscillated between tiers → attach_levers in build_live) + 2 Minor (synthetic error envelope missing worst read as healthy green → panel.error_envelope with worst=unprobed; smoke leaked the panel process on mid-run assertion errors → try/finally reap); all fixed red-first, re-review Approve (+1 hardening: merge into an error envelope is a no-op). maint suites 393→447 (54 in test_panel.py); full make test green (56 suites, unpiped exit 0). AT-SPI smoke PASS on live Hyprland (faceplate, badges, strip, all 8 tiles, band switch, clean close; window parked on a special workspace, never the active one). Screenshots off-workspace via headless output: bad fixture + live board vs the E5 reference. Live board on ratio honest: 32 CVE / 13 ATTN, NETWORK red from the real listeners crit, SERVICES fixable from the truenas-down backup staleness. GOTCHAS: windowrules need hyprctl reload before first launch (panel opens tiled otherwise); pkill -f "maint panel" self-matches the invoking shell — anchor with "maint panel$". + +*** 2026-07-08 Wed @ 00:39:23 -0500 Built Phase 8 — GUI levers: digests, armed keys, rotary band, live strip (dotfiles 388521e) +The storage/snapshots/packages subpanels grew their evidence digests (disk hogs, per-drive SMART, device errors, snapper by-type, orphans KEEP/REMOVE, pacnew DELETE/MERGE, CVE advisories), packages got the E5 rotary band selector (ORPHANS/PACNEW/ADVISORIES), and the updates strip went live: state-tiered border, UPDATE/TOPGRADE behind the arm-to-override live-update guard, REBOOT offered when required or after an update lands. Every lever is arm-then-fire — first press shows the exact argv on the new act line (identical to =maint fix --dry-run=, the parity criterion), second press fires through =doctor.iter_fix= with the re-probe landing back on the act line; one key armed at a time, band/deck switch disarms; fixture boards always dry-run and never write curation, which is what lets the AT-SPI smoke exercise the full press-press path safely. Engine support: snapper probe emits =stale_singles= (DELETE STALE's candidates, newest kept), both scrub probes parse running-%, =doctor.pending_updates()= public for the arm-press guard read, fixtures regenerated with deterministic evidence. Review subagent (live-verified): Request Changes — 2 Important (refresh tiers raced a firing remedy: a 30s tick could probe the package DB mid-transaction and land stale data after the fire's own re-probe → tiers pause on =firing= + post-fire rebuild retries past an in-flight tick; digest builders KeyError'd on identity-less evidence rows replayed from cache files → skip-and-degrade, 8 new red-first tests) + 4 Minor (all taken: broadened strip-key except, scrub key offers first idle target instead of vanishing while any runs, busy-refused press no longer consumes the arm, fire-time guard refusal names the tripped set in GUI wording). Re-review: Approve. maint suites 447→516; full =make test= green (exit 0, unpiped); AT-SPI smoke PASS end-to-end on the bad fixture; headless-output screenshots vs E5; live board honest on ratio (real orphans w/ KEEP/REMOVE, 10.5 GB cache CLEAN, arm-press names the genuinely pending mesa set). Velox pulled to 388521e (no restow/TOML/windowrule changes). Scroll-position-on-arm check added to Manual testing. + +*** 2026-07-08 Wed @ 01:51:26 -0500 Built Phase 9 — GUI: systemd, logs, mem·pwr, network, services (dotfiles 9a2985f + a3da942) +Shipped in two gated halves. 9a (9a2985f): failed-unit + timer rosters (armed RESTART/RESET per unit; ENABLE only on present-but-inactive timers), logs rotary deck (SIGNAL / KNOWN NOISE / COREDUMPS / KERNEL·HW) with the journal MARK KNOWN lifecycle (arm-then-fire mark bound to identifier + sample, UNMARK, armed CLEAR MARKS, empty-sample withhold), OPEN JOURNAL terminal delegation, row levers VACUUM/CLEAR/RUN. 9b (a3da942): CPU-mode segmented control (current + "default" excluded — priv's closed verb refuses "default"), top-memory guarded KILL (session-critical disabled pre-press, arm refusal behind it, fire-time comm revalidation behind both), battery SET 80% gated on the sysfs knob (velox: unsupported → withheld, verified live), listeners curation (probe emits pid + owning unit from the cgroup v2 line; STOP for system.slice services, KILL otherwise, "?" watch-only), firewall ENABLE / tailscale UP / chrony STEP row levers, services deck (containers MARK EXPECTED + START, docker df, cron roster + backups w/ worst-of chip lamp). RECLAIM SPACE deferred to Phase 10's wall by design — the act line can't honestly render a 7-step stream. Reviews: 9a Request Changes (curation done-callbacks bypassed the busy-retry rebuild → routed through _refresh_after_fire) → Approve; 9b Request Changes (DEFAULT EPP dead control + cgroup hybrid-hierarchy, "?"-curation, cron chip lamp, unprobed ✓ minors — all fixed red-first) → Approve. maint 516→612 tests; make test 58 suites exit 0; AT-SPI smoke PASS across all 8 bands; headless screenshots vs E5; live envelopes verified both hosts. +**** 2026-07-08 Wed @ 01:13:48 -0500 Built 9a — systemd + logs subpanels (dotfiles 9a2985f) +Failed-unit roster (armed RESTART/RESET per unit, since/exit context, journalctl hint) + expected-timer roster (ENABLE only on present-but-inactive timers); logs rotary deck (SIGNAL / KNOWN NOISE / COREDUMPS / KERNEL·HW) with the journal MARK KNOWN lifecycle (arm-then-fire mark bound to identifier + sample, UNMARK, armed CLEAR MARKS, empty-sample withhold), OPEN JOURNAL terminal delegation, and row levers VACUUM / CLEAR / RUN. Review subagent (live-verified): Request Changes — 1 Important (curation done-callbacks called refresh_full, which silently no-ops while a tick refresh holds busy → a mid-tick write left a pre-write envelope on the board up to 30s while the act line claimed success; routed all four incl. Phase 8's keep through _refresh_after_fire) + minors taken (single _mark_token definition, three test gaps closed, docstring truthfulness, stale arm wording cleared on band/deck switch — my own find during screenshot eyeballing). Re-review: Approve. maint 516→562 tests; make test 58 suites exit 0 unpiped; AT-SPI smoke PASS incl. the new rosters/deck/mark path; headless screenshots vs E5. velox pulled (no restow). Accepted gap: KNOWN NOISE subsec lists the live curation table on a fixture board — fixture-sourced entries list is later polish. + +*** 2026-07-08 Wed @ 02:36:54 -0500 Built Phase 10 — results wall + doctor keys (dotfiles cc2fb5d) +Results wall shipped as the session action log: doctor events stream per-event (begin opens a running entry with the exact argv, ok/fail resolves in place, re-probe notes follow), 3.5-visible-row scroll cap with autoscroll, HIDE/COPY (COPY emits CLI-wall text), curation writes logged (fixture-suppressed as dim notes). CLEAN UP streams the auto tier with no arm press; REVIEW & FIX swaps the subpanel to the confirm-tier roster (item-less remedies get armed FIX keys, per-item ones point at their subpanel; review events carry item + worst severity). RECLAIM SPACE macro landed on the disk-usage row (deferred from 9b); macro arm line shows first step + remaining count; zero-step macros refuse by name. Review (live-verified): Request Changes — 1 Important: the roster's FIX for update/topgrade could never override the live-update guard (fire-time re-arm used the bare rid while the FIX key arms on "rid:", and _on_lever never passed force — an infinite arm→guard loop); fixed by threading the pressed token through _fire and sharing the strip's guard arming (_guard_arm_line + panel.guarded). Minors all taken: wall_abort resolves stranded running entries red when a stream dies, empty-macro IndexError → named RemedyError, test predicate tightened. Re-review: Approve, no residuals. maint 612→645; make test 59 suites exit 0 ×2; AT-SPI smoke extended (wall, HIDE/SHOW, CLEAN UP stream, review roster, 14-line RECLAIM stream) PASS; screenshots vs E5; LIVE verify on ratio: real CLEAN UP on the live board streamed "3 done" with real re-probes (coredumps warn 5, disk_usage ok 60). Velox pulled cc2fb5d. + +*** 2026-07-08 Wed @ 03:24:44 -0500 Built Phase 11 — waybar glyph + timers (dotfiles 10033be) +custom/maint replaced custom/sysmon: waybar-maint renders from the state file maint-scan.timer writes every 30 min; color tracks the worst diagnostic (lever-less) metric only, actionable findings never color the bar; missing/stale scan data dims to an honest unprobed state naming the timer. Battery hosts show live battery % + charging icon from sysfs on waybar's interval; low charge forces red. Left-click toggles the console (new maint-panel wrapper); right-click keeps the btop scratchpad. Bare =maint scan= became the glyph scan (envelope → state → RTMIN+12); maint-net-scan.timer drives =--net --slow= hourly. Retired same commit: waybar-sysmon, sysmon-cycle, both suites, #custom-sysmon CSS ×3 — plus unnamed collateral the grep caught: waybar-collapse's collapsed base set (maint glyph stays on battery hosts — it IS the battery display now) and a stale dotfiles todo. Review (live-verified): Approve with 4 Minor, all taken red-first — scope!=Device gate so a wireless mouse's battery can't masquerade as the machine's (shared capability.device_scope), unprobed diagnostics named in the tooltip instead of "all clear" under a dimmed glyph, comment truth, dead state field dropped; re-confirm clean (677 maint tests). Full make test exit 0; smoke PASS (first run's pipe masked a transient AT-SPI race — rerun unpiped clean). LIVE both hosts: ratio timer fired for real (glyph amber from genuine diagnostics: firmware update + journal errors; 11 actionable NOT coloring), velox pulled + one-time stow + battery 100% text w/ warning class. velox's user systemd manager found wedged (pre-existing) — timers enabled via manual symlinks + state seeded; Craig rebooting to clear. + +*** 2026-07-08 Wed @ 04:15:45 -0500 Built Phase 11b — prototype fidelity pass (dotfiles 3ee22a8) +Rebuilt the GUI presentation to the E5 instrument-card idiom. Each band renders a 4-column card grid: big-number readouts (viewmodel.card_spec registry — one builder per bespoke metric id, count-card fallback for the rest), cairo progress bars w/ threshold ticks (cache scaled to 1.5× the warn line), radial gauges (scrub cadence, keyring/topgrade age, temperatures — sweep scaled to the metric's own threshold), status chips (PASSED/FAILING/INACTIVE/YES; firewall chips the state word, exposure detail moves to the caption), and the CPU-mode segmented control promoted from digest section to card (current mode lit, alternatives as joined armed keys — same DIGEST_ONLY epp_set arm-then-fire). Evidence digests stay full-width below the grid. Selector became a two-row 4-col grid of wide tiles w/ count chips (3! · 7✓, crit reddens) + severity left borders; subpanel header carries the attention/ok/fixable/watch summary right-aligned beside a gold band title. The scoped NVMe wear dial was skipped honestly — no standalone wear metric exists (wear rides SMART evidence rows). Presentation only: engine/remedies/doctor/curation and the lever contracts unchanged. New 42-test suite (card kinds, threshold scales, running-scrub override, unprobed degrade incl. tick drop, seg exclusions, chips/subhead/band_counts, both-fixture sweep). Review (live-verified): Approve w/ 1 Important (pre-existing: chrony "not running" string crashed format_value's ms branch — fixed w/ card guard) + 4 Minor all taken red-first (disk None guard, _pct negative clamp, unprobed tick, dead row_detail/split_label/.maint-val removed); re-review Approve. make test 61 suites exit 0 ×2 unpiped; AT-SPI smoke 92 checks PASS (new gauge/chip/subhead assertions — DrawingAreas carry accessible_role=IMG + names so the smoke can see them); all 8 bands + review roster screenshotted on both fixtures + the live board against the settled headless-Chrome prototype render. Velox pulled (no restow). Gotcha pinned: a card body's vexpand propagates up a Gtk.Grid — a one-card band ballooned until grid.set_vexpand(False). + +*** 2026-07-08 Wed @ 05:05:20 -0500 Ran the granted /refactor sweep over the maint package (dotfiles a178470..73e9d94) +Four parallel read-only scans (complexity, duplication, dead-code, simplification) converged on the same hotspots; ten behavior-preserving commits applied and pushed. Headlines: six curation handlers folded into one _curation helper (~150 lines of repeated fixture/done plumbing, act/wall strings byte-identical), _on_lever/_on_strip_key merged into _press_lever, _on_fired split (_rearm_after_guard + _fire_summary), the "tool: not found / exit N" wording single-sited in cmd.why() across 20 probe sites, exact clones deduped (packages curation_entries, _parse_asctime, double VERSION), ten uniform digest sections folded into _evidence_digest, _card_body split per kind, listener key rule named, plus a small-simplifications batch. Deliberately skipped: format_value's flat chain (readable as-is), _journal_lines generalization (rc-semantics risk), test-panel-maint Makefile target (Phase 12 scope). Verification: suite parity exact pre/post (59 suites / 2490 tests, exit 0 unpiped), AT-SPI smoke PASS end-to-end, live ratio envelope 45 metrics zero unprobed, review subagent walked the full diff against 3ee22a8 and approved (no behavior change; accessible names untouched; its one latent-robustness minor pinned as an invariant comment). gui.py 1680→1605; net −103 lines. Velox pulled. + +*** 2026-07-08 Wed @ 05:46:02 -0500 Built Phase 12 — VM remedy scenarios + AT-SPI target (archsetup d6993d3, dotfiles 0636554) +Scenario orchestration landed as =run-maint-scenarios.sh= over the existing vm-utils.sh snapshot primitives: nine break → =maint fix= → assert scenarios in three groups (logs, packages, systemd), batched per Decision 11 (one boot per group, maint-ready snapshot restored between groups, clean-install restored at the end). =run-maint-nspawn.sh= is the fast lane — the packages group against a cached pacstrap rootfs in seconds. Plan layer is pure (=--list=, no KVM) with a 19-test unit suite; =make test-maint= wires it in. Dotfiles side: panel smoke now runs GOOD + BAD fixture passes (healthy render, badge tallies from the fixture, no-REBOOT, leak check) and =make test-panel-maint= is enumerated. Verified: full VM run 9/9 green from pristine snapshot ×2 (test-results/maint-20260708-054029), nspawn 4/4, smoke 100 checks exit 0, unit suite green under both FS_PROFILE values. Review subagent: Request Changes (FS_PROFILE env leak in the unit suite, journal-vacuum assert vacuous both directions) — all six findings fixed red-first, re-review Approve. FS_PROFILE=zfs scenarios: filtered but unexercised — the zfs base image fails to build (see the zfs DKMS task). + +*** 2026-07-08 Wed @ 06:18:58 -0500 Built Phase 13 — install wiring, workflow move, docs (archsetup bef7053 + 18c081f, dotfiles 9a3f0c7) +Installer: install_maintenance_config in user_customizations installs the shipped TOML to ~/.config/archsetup/ (re-run refreshes it, ~/.config/maint/ user layer never touched) and enables maint-scan/maint-net-scan timers via wants-symlinks (hyprland-only — the units live in that stow tier; no session bus during install, syncthing idiom); orchestrator + dispatch-branch tests pin the wiring. Deps swept from the probes' argv: expac, lm_sensors, fwupd added (arch-audit was already in from Phase 3; the rest ride base/archinstall or existing sections). maint/README.md in dotfiles covers user (CLI, panel, glyph, config layers, guard, privilege) + developer (module map, env seams, four test layers, add-a-metric recipe). system-health-check.org moved home → archsetup docs/workflows/ and rewired: TOML-authoritative severity section, threshold lines cite TOML keys (TOML wins on disagreement), inventory paths → docs/homelab-inventory/, maint-CLI fast-path note; only the four #+HOSTNAME: host inventories moved (+ the truenas specs asset they link) — the personal gear records stay in home per the domain split; discoverability via .ai/project-workflows symlink + notes.org entry; handoff note in home's inbox (2026-07-08-0607) lists home-side removals and flags strix-soak-watch as a candidate to move later. Verified: fresh-shell =maint status= green from installed paths on ratio; make test-unit exit 0; dotfiles make test exit 0; shellcheck count unchanged; review subagent Approve (2 Minor doc nits, both taken). + +*** 2026-07-08 Wed @ 06:18:58 -0500 Flipped the spec to IMPLEMENTED +Spec keyword DOING → IMPLEMENTED with a dated history line naming the closing commits (phases 1-13, dotfiles 43a39ac..9a3f0c7, archsetup d6993d3/bef7053/18c081f) and the Status mirror updated. Residuals tracked as their own tasks: manual-testing checklist, zfs base-image DKMS bug [#C], vNext [#D]. +** DONE [#B] Signal and known-noise counts disagree with the journal :bug:maint:solo: +CLOSED: [2026-07-09 Thu] +Not a counting bug — a semantics bug (dotfiles eded0c0). journal_errors grouped by identifier and summed lines, but displayed one sample message, and logs.py overwrote that sample on every iteration so it named the *last* error rather than the counted one. Live on ratio: systemd had 10 error lines this boot — 6 "Failed to start Emacs text editor." and 4 org-roam auto-sync — which is exactly the 10-vs-6. Now grouped by identifier + normalized message (pids, hex addresses, uuids, long integer runs blanked), so a row's count is that message's count, and each row's hint carries a =--grep= reproducing it (verified: 6 and 4). MARK KNOWN binds the volatile-free literal instead of the pid-bearing sample, so marking suppresses exactly what the row counted. The KNOWN NOISE headline counts matched lines while the deck lists patterns; the caption now names both units. +Logs > SIGNAL reports 10 journal errors and the signal item claims 10, but the group holds one item and the open journal shows it 6 times. KNOWN NOISE reports 19 against 12 visible entries. The counts are the point of the band, so a wrong number undermines the whole diagnostic. Find where the tally diverges from the rendered set — likely counting matched journal lines rather than distinct grouped entries, and counting curated entries against a different window than the one displayed. +** DONE [#B] Persist the CPU pill setting across reboot :feature:maint: +CLOSED: [2026-07-09 Thu] +The kernel resets energy_performance_preference to the driver default every boot. A successful epp_set now records the preference in the maint user config; maint-epp-restore.service replays it at login. No-op when nothing is remembered, the host lacks EPP, or the mode already matches. A hand-edited bogus value is refused by priv's closed verb. Installer enables the unit via the existing wants-symlink idiom. Verified live on ratio: unit enabled, ran clean, EPP untouched. (dotfiles a7f34bd, archsetup 430ef1a) + +The power/perf/balance pill sets EPP live, but the value reverts to its original setting after a reboot. Add a way to make a selection permanent. Needs a call on where persistence lives — a systemd unit, a udev rule, or a config file the maint package owns. +** DONE [#B] Preview the pacman + AUR update queue before upgrade :feature:maint:solo: +CLOSED: [2026-07-09 Thu] +A QUEUE key on the updates strip streams the pending pacman + AUR set onto the results wall, each package with its version move; `maint queue` prints the same in a terminal. The cap reports how many it dropped rather than truncating silently, and a cold cache says it has no pending set instead of rendering "nothing pending". Verified live against ratio's 105 pending packages. (dotfiles 8837a47) + +Show which pacman and AUR packages are queued before UPDATE or topgrade runs, so the armed live-update guard isn't the first place the package set becomes visible. +** DONE [#B] Audio PTT state desync across panel, waybar icon, and actual state :bug:audio:solo: +CLOSED: [2026-07-09 Thu] +Two state stores and two copies of the logic. The panel flipped an in-memory ptt_armed flag with its own enter/exit rules; the CLI toggle and the bar tag read a runtime state file. The file is now the only state and ptt.toggle_plan the only decision. The panel adopts the persisted state on every refresh, routes its toggle through the shared plan, and persists before signalling waybar (writing after would race the bar into re-rendering the pre-toggle class). Tests drive both entry points against one state across armed/disarmed x muted/live. (dotfiles 9524a44) + +The audio panel's push-to-talk, the waybar PTT icon, and the real PTT state disagree. All three must agree at all times. Cover every state transition with tests, not just the happy path — this is the second PTT bug after the pre-talk mute restore landed 2026-07-05 (dotfiles 1443b9e). +** DONE [#B] Publish the homelab inventory into the agent knowledge base :chore:docs: +CLOSED: [2026-07-09 Thu] +The four host inventories, the TrueNAS specs asset, and the ratio USB/xHCI record now live as roam nodes in =~/org/roam/hardware/=, linked from the "Homelab Hardware Inventory" index node. =docs/homelab-inventory/= is gone; roam is canonical. + +Carried rather than pointed, per home's argument, which beat mine. Pointing keeps one canonical copy but breaks the write path: a third project that discovers a durable hardware fact would have to write into archsetup's repo to record it, which the cross-project rules forbid, so the fact would sit in an inbox instead of on the device's page. Moving answers the canonical-copy worry without that cost. + +The =* Automated Capabilities= drawer moved with the pages. =system-health-check.org= now resolves a host's node by its =#+HOSTNAME:= keyword under =${ROAM_DIR:-$HOME/org/roam}/hardware/= — never by filename, since the timestamp prefix isn't stable. Verified: all four hosts resolve, an unknown host degrades to "NO INVENTORY FILE", and ratio's capability drawer still parses. A host without the roam clone (mybitch, truenas) takes the same no-inventory path it always did. + +The strix kernel watch stayed at =docs/workflows/strix-soak-watch.org= — it's a workflow, not an inventory page. +** DONE [#C] Optional ticking sound for the pomodoro :feature:timer: +CLOSED: [2026-07-09 Thu] +WTIMER_TICK=1 plays a quiet tick each second through a pomodoro's work phase; off by default, silent through rest (a tick there would undo the break), while paused, and while awaiting a manual advance. Rides the bar's existing once-a-second `wtimer render` call. Playback is fire-and-forget so it can never stall or break the bar. Sound is an 18ms 1.4kHz sine at a tenth of full scale (notify/tick.ogg); WTIMER_TICK_SOUND overrides. Craig still has to judge whether the tick sounds right. (dotfiles 886dafd) + +Timers module. Off by default; needs a sound choice and a volume decision. +** DONE [#C] Refresh indicator for the top-like items :feature:maint: +CLOSED: [2026-07-09 Thu] +A hairline under the top-memory section drains toward the next live refresh, so a slow board reads differently from a frozen one. Withheld on a fixture board, which never live-ticks — a countdown that never counts is worse than none. The fraction is a pure viewmodel function; only the cairo stroke lives in the GUI. (dotfiles 5d384d9) + +Show when the process/memory rows are about to refresh, so a stale-looking board is distinguishable from a frozen one. Shape is a design call — a countdown, a pulse, or a progress hairline. +** DONE [#C] CPU pill should not reposition on selection :bug:maint:quick:solo: +CLOSED: [2026-07-09 Thu] +epp_rows dropped the current preference from the key list, so the remaining segments slid sideways on every selection. Every preference now renders in a fixed order with the current one lit and unpressable in place, at full contrast rather than GTK's disabled dimming. The separate lit label is gone — the active segment is the lit one. Three separate tests encoded the old contract (unit, AT-SPI smoke, phase11b card); all moved with the behavior. The smoke caught a real gap the unit tests missed: the EPP row renders through the card path, which ignored the disabled flag. (dotfiles 5d384d9) + +The perf/balance/power pill moves position depending on which option is selected. The segmented control should hold a fixed layout and mark the active segment in place. +** DONE [#C] Session identifier needs more width :bug:waybar:quick: +CLOSED: [2026-07-09 Thu] +Not waybar — the tmux status line. status-left-length was never set, so tmux clipped the drawn status-left to its default of 10 columns: "[aiv-archsetup] " came out "[aiv-archs", losing the closing bracket and the separating space, which is why the window list ran into it. Set to 40. Applied to the live tmux server too. (dotfiles dc3c27f) + +The session section truncates; give it more characters, and keep the window sections that follow from running into it. Screenshot: [[file:/home/cjennings/pictures/screenshots/2026-07-07_145236.png][2026-07-07_145236.png]] |
