aboutsummaryrefslogtreecommitdiff
path: root/todo.org
diff options
context:
space:
mode:
Diffstat (limited to 'todo.org')
-rw-r--r--todo.org368
1 files changed, 248 insertions, 120 deletions
diff --git a/todo.org b/todo.org
index 6e6047d..bef8149 100644
--- a/todo.org
+++ b/todo.org
@@ -21,26 +21,9 @@ 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
-** TODO [#B] Panels moveable + resizable by drag :feature:waybar:network:bluetooth:
-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.
-
-** TODO [#B] Net panel wider initial width :waybar:network:quick:
-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.
-
-** TODO [#B] Net panel doctor results can't display :bug:waybar:network:
-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 [#B] Audio panel spec :feature:waybar:audio:solo:
-:PROPERTIES:
-:LAST_REVIEWED: 2026-07-02
-:END:
-Work Craig's ask (roam inbox, 2026-07-02) into a spec, net/bt-panel kin: an audio panel replacing the pypr audio scratchpad (Super+A) with the same functionality — change the default/active output (speaker) and input (mic), volume control for both. The one new capability: a push-to-talk mic mode for meetings — mic stays muted except while the space bar is held, releasing re-mutes. (Hold-to-talk under Wayland needs a global key grab — likely a hyprland bind pair on press/release or an evdev listener; feasibility research belongs in the spec.) Related current bindings: Super+M audio-cycle ring, Super+Shift+A mic-toggle.
-
** 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.
@@ -72,7 +55,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-06-29
+:LAST_REVIEWED: 2026-07-04
: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
@@ -178,25 +161,28 @@ troubleshooting from the failure table, rollback); archsetup Hyprland dep instal
stow step.
Verify: =net --help= and each subcommand complete; user-guide covers every command
+ the recovery targets.
+Build handed off to the dotfiles project 2026-07-04 (=~/.dotfiles/inbox/2026-07-04-1305-from-archsetup-phase4-handoff.md=): archsetup deps confirmed installed, the remaining help/user-guide/rollout-doc work is in the net package. dotfiles pings back when it lands.
-*** TODO Phase 5 — VPN / WireGuard (vNext) :network:
-Fold the existing archsetup wireguard tooling into the panel + CLI (=net vpn ...=).
-Out of the v1 milestone; spec separately when picked up. (v1 only detects +
-classifies a VPN-routed failure, it doesn't repair it.)
+*** 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/design/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:
:PROPERTIES:
:LAST_REVIEWED: 2026-06-24
:END:
-Initial spec written 2026-07-02: [[file:docs/design/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).
+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).
One waybar dropdown gathering the desktop toggles and sliders into a single settings panel, opened from a gear/settings glyph on the bar. Incorporate:
- *Auto-dim* toggle (the =custom/dim= feature just shipped — fold in here, or keep the standalone indicator and mirror it).
@@ -217,7 +203,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]]).
@@ -253,9 +239,17 @@ 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.
+
** TODO [#B] Review post-archsetup laptop setup steps (velox 2026-04-10)
:PROPERTIES:
-:LAST_REVIEWED: 2026-06-09
+:LAST_REVIEWED: 2026-07-04
:END:
Items discovered during velox setup that needed manual intervention after archsetup.
Decide which should be automated in archsetup vs documented as post-install steps.
@@ -265,10 +259,8 @@ Both bluetooth and wifi were soft-blocked by rfkill. Fix was ~rfkill unblock blu
~systemd-rfkill~ persists state, so unblocking once should stick, but new installs may default to blocked.
Consider: add ~rfkill unblock all~ to archsetup post-install or a firstboot script.
-*** TODO Review: /efi mount permissions world-accessible (security)
-Default vfat mount had ~fmask=0022,dmask=0022~. Fixed to ~fmask=0077,dmask=0077~ in fstab.
-~bootctl~ warned about world-accessible random seed file.
-Consider: set restrictive fmask/dmask in archsetup's fstab generation.
+*** 2026-07-04 Sat @ 11:48:24 -0500 Automated /efi restrictive mount permissions in fstab generation
+archsetup:2827-2836 now rewrites the /efi fstab line to =fmask=0177,dmask=0077= (idempotent), so fresh installs no longer land the world-accessible =fmask=0022,dmask=0022= default. Confirmed via the 2026-07-04 task audit. (Original velox note: default vfat mount had =fmask=0022,dmask=0022=, hand-fixed to restrictive; bootctl warned about a world-accessible random-seed file.)
*** TODO Review: tmpfs layered over ZFS /tmp causing systemd-tmpfiles failures
~systemd-tmpfiles-clean.service~ failed repeatedly with "Protocol driver not attached".
@@ -279,9 +271,8 @@ Fix: ~systemctl mask tmp.mount~. Consider: mask tmp.mount in archsetup when ZFS
CPU running old microcode. Installed ~intel-ucode~ and rebuilt initramfs.
Consider: add intel-ucode (or amd-ucode) to archsetup package list based on CPU vendor.
-*** TODO Review: syncthing installed but not enabled
-Package was installed but service was not enabled. Fixed with ~systemctl enable --now syncthing@cjennings~.
-Consider: enable syncthing service in archsetup post-install.
+*** 2026-07-04 Sat @ 11:48:24 -0500 Automated syncthing user-service enable in archsetup
+archsetup:2263-2271 now installs syncthing and enables the user service (via symlink), so fresh installs no longer leave it installed-but-disabled. Confirmed via the 2026-07-04 task audit. (Original velox note: package installed but service not enabled; hand-fixed with =systemctl enable --now syncthing@cjennings=.)
*** TODO Review: awww-daemon crashes at boot (coredump)
Wallpaper daemon crashed with abort() shortly after boot. Hyprland also coredumped at same time.
@@ -398,9 +389,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.
@@ -509,21 +500,9 @@ Read recommended resources to make informed security decisions (see metrics for
:END:
Practical guidelines for working in public spaces
-** TODO [#B] Test each modernization thoroughly before replacing
+** TODO [#B] Ensure sleep/suspend works on laptops
:PROPERTIES:
-:LAST_REVIEWED: 2026-06-28
-:END:
-Ensure new tools integrate with the Hyprland environment and don't break workflow (the fleet is all Hyprland now; archsetup still supports DWM/X11 but no current machine uses it)
-
-** TODO [#C] Window focus lost when unhiding stashed windows :bug:hyprland:
-:PROPERTIES:
-:LAST_REVIEWED: 2026-06-24
-:END:
-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.
-
-** TODO [#C] 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)
@@ -547,7 +526,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
@@ -561,7 +540,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
@@ -595,29 +574,162 @@ Parse yay errors and provide specific, actionable fixes instead of generic error
Enhance existing indicators to show what's happening in real-time
** TODO Manual testing and validation
-*** 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).
+*** 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
+cd ~/.dotfiles && git pull
+cd ~/.dotfiles && make restow hyprland
+#+end_src
+- Reload waybar: mod+B (or =killall -SIGUSR2 waybar=).
+- Reload the Hyprland config: mod+Shift+R (or =hyprctl reload=).
+Expected: =which audio audio-panel waybar-audio= resolves all three to ~/.local/bin symlinks; no stow conflicts reported.
+
+*** Audio panel: opens and reads the live graph
+What we're verifying: Super+A opens the instrument-console panel (not the old pulsemixer scratchpad) and it shows the real devices.
+- Press Super+A.
+Expected: the audio panel opens top-right. OUTPUTS lists every sink, INPUTS every mic (no .monitor entries), the default device in each is emphasized (cream name, gold glyph), each row shows its volume percent, and the twin OUT·PLAY / IN·MIC needles deflect. Esc closes it.
+
+*** Audio panel: set default, volume fader, per-device mute
+What we're verifying: the three row interactions drive the real graph and the panel re-reads to confirm.
+- With two or more outputs present, click a non-default output row's body.
+Expected: that device becomes default (gold DEF emphasis moves to it) and playback follows.
+- Drag a device's fader.
+Expected: the volume percent tracks the fader and the actual device volume changes (no lag, no jump to the border).
+- Click a device's trailing mute glyph.
+Expected: that one device mutes/unmutes; the glyph and caption reflect it; other devices unaffected.
+
+*** Audio panel: master quick-mute (faceplate switch + mute key)
+What we're verifying: the master switch and the XF86AudioMute key both mute every output at once (not just the default sink).
+- Flip the faceplate master switch.
+Expected: every output mutes, the state word reads MUTED, the badge shows, the faceplate glyph goes to the slashed speaker. Flip back to restore.
+- With two outputs audible, press the hardware mute key (XF86AudioMute).
+Expected: both outputs mute (master), not just the default. Press again to unmute all.
+
+*** Audio panel: mic modes + push-to-talk (the meeting case)
+What we're verifying: LIVE/MUTED work, and PUSH·TALK holds the mic muted except while the talk key is held — the one genuinely new capability, and the one only a live test can confirm.
+- Click LIVE, then MUTED, watching the default mic row.
+Expected: LIVE unmutes the mic (needle lifts), MUTED mutes it (needle red); the active mode key shows a gold lamp.
+- Click PUSH·TALK. In a call app (or =audio status= in a loop), watch the mic while you hold Space, speak, then release.
+Expected: mic is muted at rest, un-mutes for exactly as long as Space is held, re-mutes on release. Switching back to LIVE or MUTED disarms the hold (Space types normally again).
+
+*** Audio panel: visual polish eyeball + bar entry point
+What we're verifying: the panel looks right in the dupre instrument-console language, and the bar opens it.
+- Look over the whole panel: faceplate spacing, engraved section labels, fader styling, VU needle centering, the muted-vs-audible glyph/color states.
+Expected: it reads as a sibling of the net and bt panels; nothing overflows the gold border; the faders and gauges look machined, not stock GTK.
+- Right-click the waybar sound module.
+Expected: the panel opens (left-click still mutes, scroll still changes volume).
+
+*** 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).
+NOTE (2026-07-04 audit): these steps describe the OLD four-tab Blueprint panel ("Diagnostics → Network Performance"). The net panel was rebuilt as a single-screen instrument console (dotfiles 800ef60, f4e688e dropped the Blueprint pages), so the navigation below no longer matches the shipped UI — reword to the console layout before running. Don't delete; the streaming-render behavior is still worth verifying.
- Open the net panel, Diagnostics → Network Performance → Run Speedtest.
Expected: a Ping/Download/Upload checklist appears under the running row; ping lands within seconds, download mid-run, upload near the end; then the final rows (with jitter on Ping, a Packet loss row) replace it. A Tip row appears only if a rule fired, and it names the numbers that triggered it.
*** 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
@@ -626,8 +738,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).
@@ -812,6 +924,7 @@ What we're verifying: the custom/net clicks and the airplane keybind. Clicks (se
- Check =airplane-mode= is still present (=ls ~/.local/bin/airplane-mode=), and =waybar-airplane= / =waybar-netspeed= / =custom/airplane= are gone.
*** Network module Phase 3 — panel Diagnose / Repair / Speed test tabs
What we're verifying: the four-tab panel works end to end. Left-click =custom/net= to open it.
+NOTE (2026-07-04 audit): the "four-tab panel" framing predates the instrument-console rebuild (dotfiles 800ef60, f4e688e dropped the Blueprint pages). Diagnose/Repair/Speed-test now live in the single-screen console, not tabs — reword the steps to the console layout before running. Don't delete; the underlying behaviors still need verification.
- Diagnose tab → "Run diagnose".
- Expected: a list of steps (link, DHCP, gateway, DNS config, DNS resolution, internet) each with a ✓/✗/… glyph + evidence; on a captive network an "Open portal" button appears.
- Repair tab → click Reset (or Bounce, or DNS override test).
@@ -822,7 +935,7 @@ What we're verifying: the four-tab panel works end to end. Left-click =custom/ne
** DOING [#B] Prepare for GitHub open-source release
:PROPERTIES:
-:LAST_REVIEWED: 2026-06-28
+:LAST_REVIEWED: 2026-07-04
:END:
Remove personal info, credentials, and code quality issues before publishing.
*** 2026-06-16 Tue @ 00:55:39 -0500 Six dotfiles-scoped sub-tasks moved to the ~/.dotfiles project
@@ -869,6 +982,8 @@ Options: =git filter-repo= to rewrite history, or start a fresh repo for the Git
Recommend: fresh repo for GitHub (keep cjennings.net remote with full history).
**** 2026-06-28 Sun @ 13:29:29 -0400 Reconciled: 589 commits, 5 credential files still in history
History is now 589 commits (the 2026-05-11 note's "275" is stale). Only the calendar-feed file has been filter-repo'd so far (2026-05-20). The five credential files remain in history at their pre-=b10cba5= paths: =.tidal-dl.token.json= (5 commits), =calibre/smtp.py.json= (6), =transmission/settings.json= (5), =.msmtprc= (8), =.mbsyncrc= (9). None are tracked in the current tree. The scrub-or-fresh-repo decision still stands.
+***** 2026-07-04 Sat @ 11:48:24 -0500 Count refresh — history now 565 commits; re-verify the 5-file claim before scrubbing
+The 2026-07-04 audit found the history is now 565 commits, down from the 589 recorded above. Because the count dropped, re-verify that the five credential files are still present in history (re-run the per-file =git log --all -- <path>= check) before relying on the scrub scope — the earlier count is stale and the file set may have moved.
*** 2026-06-24 Wed @ 19:41:56 -0400 Gated device-specific udev rules behind a flag
The Logitech BRIO udev rule is now wrapped in =if [ "$install_device_udev_rules" = "true" ]=, fed by a new =INSTALL_DEVICE_UDEV_RULES= key (default yes, opt-out — still mainly a personal project). Added the var default, the config read, a =validate_config= check, and an =archsetup.conf.example= entry. Verified: default/yes writes the rule, no skips it, bogus is rejected; =bash -n= clean.
@@ -927,21 +1042,6 @@ Verified: the only =eval= left in =archsetup= is line 578 in =retry_install=, an
* 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]
:PROPERTIES:
@@ -950,34 +1050,6 @@ CLOSED: [2026-06-28 Sun]
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.
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.
-
-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]].
-
-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/=.
-
-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.
-
-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]
-:PROPERTIES:
-:LAST_REVIEWED: 2026-06-24
-: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.
@@ -1234,7 +1306,7 @@ CLOSED: [2026-07-02 Thu]
:PROPERTIES:
:SPEC_ID: 1271a845-4463-4831-9902-990eda6b2265
:END:
-Spec: [[file:docs/design/2026-07-02-bluetooth-panel-spec.org]] (IMPLEMENTED 2026-07-02 — all five phases shipped same day: engine eb2230f, panel 76b2c05, bar module e372de3, bt-priv + blueman retirement 2a026b1/d8d8c53, install wiring proven by VM assertions). Residual: the phase 4-5 VM assertions run on the next VM pass; ratio picks up the package removal + hand-links on its trip list.
+Spec: [[file:docs/specs/2026-07-02-bluetooth-panel-spec.org]] (IMPLEMENTED 2026-07-02 — all five phases shipped same day: engine eb2230f, panel 76b2c05, bar module e372de3, bt-priv + blueman retirement 2a026b1/d8d8c53, install wiring proven by VM assertions). Residual: the phase 4-5 VM assertions run on the next VM pass; ratio picks up the package removal + hand-links on its trip list.
A bluetooth panel driving a CLI underneath (bluetoothctl one-shot verbs), consistent in look and feel with the net panel (GTK4 + layer-shell + Blueprint, humble-object presenter, verify-everything). Minimalistic interface, full functionality, plus a diagnostics/troubleshooting section mirroring the net panel's Diagnostics tab. Bar module glyph opens it. Craig's ask (2026-07-02): follow UX/UI best practices; where the net panel's patterns conflict with best practices, file a net-panel bug task rather than clone the flaw.
@@ -1357,7 +1429,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/design/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=.
@@ -1439,7 +1511,7 @@ CLOSED: [2026-07-02 Thu]
:PROPERTIES:
:SPEC_ID: 79a1075a-4b56-4f25-a861-b69f120a636a
:END:
-Spec: [[file:docs/design/2026-07-02-net-panel-other-interfaces-spec.org]] (DOING — reviewed READY and decomposed 2026-07-02 evening; all four decisions were resolved same morning, claims re-verified live at review: protonvpn binary, tailscale JSON shape, seven importable wireguard configs).
+Spec: [[file:docs/specs/2026-07-02-net-panel-other-interfaces-spec.org]] (DOING — reviewed READY and decomposed 2026-07-02 evening; all four decisions were resolved same morning, claims re-verified live at review: protonvpn binary, tailscale JSON shape, seven importable wireguard configs).
Tunnels visible and controllable in the net panel: tailscale + NM wireguard + proton-vpn-cli probes, a Tunnels group in Connections, diagnose/doctor route-ownership awareness, a bar badge when a tunnel owns the default route, archsetup operator flag + package swap, and the one-time NM import of the seven Proton configs. Origin: roam inbox capture 2026-07-02.
@@ -1468,7 +1540,7 @@ CLOSED: [2026-07-02 Thu]
:PROPERTIES:
:LAST_REVIEWED: 2026-07-02
:END:
-Reassigned to .emacs.d 2026-07-02 (handoff: =~/.emacs.d/inbox/2026-07-02-2231-from-archsetup-dirvish-popup-swallow-handoff.org=). The "file manager" is the dirvish popup (Super+F, an Emacs frame), not nautilus — so the fix is elisp in dirvish's external-open path (=cj/xdg-open=): spawn the handler directly with =start-process=, hide the popup frame, restore it from the process sentinel, notify on non-zero exit. The spec drafted here first ([[file:docs/design/2026-07-02-file-manager-swallow-spec.org]], now CANCELLED) records the feasibility finding that stays useful: gio/xdg-open launches double-fork, so no PID-ancestry approach (Hyprland native swallow included) can ever connect viewer to launcher.
+Reassigned to .emacs.d 2026-07-02 (handoff: =~/.emacs.d/inbox/2026-07-02-2231-from-archsetup-dirvish-popup-swallow-handoff.org=). The "file manager" is the dirvish popup (Super+F, an Emacs frame), not nautilus — so the fix is elisp in dirvish's external-open path (=cj/xdg-open=): spawn the handler directly with =start-process=, hide the popup frame, restore it from the process sentinel, notify on non-zero exit. The spec drafted here first ([[file:docs/specs/2026-07-02-file-manager-swallow-spec.org]], now CANCELLED) records the feasibility finding that stays useful: gio/xdg-open launches double-fork, so no PID-ancestry approach (Hyprland native swallow included) can ever connect viewer to launcher.
When the file manager launches another app, it should hide to a special workspace (the "swallow" pattern) and return when that process ends, rather than vanishing. Today it disappears with no signal of whether it's coming back, so the user can't tell success from failure — they should quit explicitly instead. Origin: roam inbox capture.
@@ -1649,3 +1721,59 @@ GUI. That's a terminal reporting to the user, but only because there's no panel
as an explicit carve-out (recovery-only, not terminal-as-UI), or replace it with something else
(a TTY text UI still counts as a terminal)? Your call settles whether the Makefile/CLI recovery
targets stay in the spec.
+** DONE [#B] Audio panel spec :feature:waybar:audio:solo:
+CLOSED: [2026-07-03 Fri]
+:PROPERTIES:
+:LAST_REVIEWED: 2026-07-02
+: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: =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.