#+TITLE: Task Archive #+FILETAGS: :archive: * Resolved (archived) ** DONE [#B] Full install logs should contain timestamps CLOSED: [2026-02-23 Sun] Log filename includes timestamp via =date +'%Y-%m-%d-%H-%M-%S'=. Functions =error_warn()=, =error_fatal()=, and =display()= all output timestamps via =date +'%T'=. ** DONE [#B] Validate DESKTOP_ENV default behavior CLOSED: [2026-02-23 Sun] Defaults to =hyprland= silently via =desktop_env="${desktop_env:-hyprland}"=. Overridable via config file or =DESKTOP_ENV= environment variable. ** DONE [#B] Test archsetup username/password prompts CLOSED: [2026-02-23 Sun] Username prompt with regex validation (lines 320-332) and password prompt with confirmation (lines 339-353) implemented and functional. ** DONE [#B] Verify SSH to remote server works CLOSED: [2026-02-02 Mon] Tested 2026-02-02: ssh cjennings.net returns "connected" successfully. SSH key authentication working, no password required. ** DONE [#B] Verify Proton Mail Bridge retrieves email CLOSED: [2026-02-02 Mon] Verified 2026-02-02: Proton Mail Bridge running, ports 1143 (IMAP) and 1025 (SMTP) listening on 127.0.0.1. mu4e email retrieval functional. ** DONE [#B] Fix unsafe sed patterns with user input CLOSED: [2026-02-23 Sun] Quoted =$username= in sed replacement, switched locale and wireless-regdom sed patterns to pipe delimiter to avoid conflicts with path/encoding characters. ** DONE [#B] Fix unsafe heredoc variable expansion CLOSED: [2026-02-23 Sun] Quoted =UDEVEOF= heredoc and used placeholder + sed replacement pattern (same as hyprpm hook). ** DONE [#C] Add mountpoint check before ramdisk mount CLOSED: [2026-02-23 Sun] Added =mountpoint -q= guard before mount; skips with info message if already mounted. ** DONE [#C] Improve error handling in chained commands :chore: CLOSED: [2026-05-07 Thu] Line 820: three operations chained with =&&= reported as single failure. Broken into separate error-handled steps. ** DONE [#C] Add comments on complex logic CLOSED: [2026-02-23 Sun] Added comments explaining wireless region locale-to-ISO3166 mapping and archsetup clone strategy (why symlinks need user-owned repo). ** DONE [#D] Validate reserved usernames CLOSED: [2026-02-23 Sun] Added check against list of reserved system usernames (root, bin, daemon, sys, etc.). ** DONE Review: Hyprland conf.d source ordering :chore: CLOSED: [2026-05-07 Thu] ~source = $HOME/.config/hypr/conf.d/*.conf~ was at top of hyprland.conf (line 9). Machine-local overrides (gaps, monitor scale) were overwritten by defaults later in the file. Fixed by moving source line to end of file. Update stowed hyprland.conf. ** DONE Review: natural_scroll not set for mouse (only touchpad) :chore: CLOSED: [2026-05-07 Thu] ~input:natural_scroll~ was missing; only ~touchpad:natural_scroll~ was set. Added ~natural_scroll = true~ to input block. ** DONE [#B] Extend layout-navigate to escape special workspaces CLOSED: [2026-04-19 Sun] With the =special:stash= overlay visible and focus on a window inside it, =$mod+J= was trapped because =layoutmsg cyclenext= only operates within the current workspace. The 2026-04-09 fix handled floating→tiled but not special-workspace→regular. Fix in =dotfiles/hyprland/.local/bin/layout-navigate=: when the active window's =workspace.name= begins with =special:= and the user is navigating focus (not moving), dispatch =togglespecialworkspace = first, re-read activewindow state, then fall through to the existing floating/layout branches. Move variant (=$mod SHIFT J=) is intentionally left untouched so moving a window out of a scratchpad remains a deliberate separate action. Unit tests live in =tests/layout-navigate/= (stdlib =unittest=, fakes =hyprctl= via PATH). Run with: =python3 -m unittest tests.layout-navigate.test_layout_navigate= ** DONE Check linux-lts version until 6.18+ CLOSED: [2026-03-07 Sat] Run =topgrade= and check =pacman -Q linux-lts=. Once 6.18+, remove =/etc/modprobe.d/amdgpu.conf= and mark this DONE. Background: AMD Strix Halo VPE power gating bug causes system freeze. Workaround disables power gating. Fix is in kernel 6.15+. Running linux-lts 6.18.16-1. amdgpu.conf workaround already removed. ** DONE [#D] Find or create a monocle layout for Hyprland CLOSED: [2026-03-07 Sat] Both existing monocle plugins (zakk4223/hyprlandMonocle, pianocomposer321/hyprland-monocle) are abandoned and broken against current Hyprland. Options: fork and fix hyprlandMonocle (more features), script a pseudo-monocle using fullscreen 1, or wait for a maintained plugin. Lower priority since stash-window ($mod+O / $mod+Shift+O) covers the main use case. More important for laptop installs. Resolved: Hyprland 0.54 added native monocle layout. Bound to $mod SHIFT M. ** DONE [#B] Investigate rlwrap not installed after archsetup run CLOSED: [2026-05-11 Mon] rlwrap was declared in archsetup (Emacs Dependencies) but missing after a run on ratio (2026-02-06). The 2026-05-11 VM test run shows it installs cleanly in a fresh install (=...installing rlwrap via pacman @ 15:36:55=; =rlwrap 0.48-1= in the captured package list), so it doesn't reproduce — likely a one-off / machine-specific glitch on ratio, not a systemic skip. Closing; reopen if it recurs. ** DONE [#C] Remove stale hyprpm/plugins validations; make run-test.sh tolerant of validation failures CLOSED: [2026-05-11 Mon] The 2026-05-11 VM test aborted because =validate_hyprland_plugins= in =scripts/testing/lib/validation.sh= checked for =~/.local/bin/hyprland-plugins-setup=, which was deliberately removed in dd543e3 (=feat(hyprland): remove plugins, add layout cycling=; Hyprland 0.54 brings the layouts into core). The function's =return 1= under run-test.sh's =set -e= killed the run before the test report was written or the VM cleaned up. Fix: deleted =validate_hyprland_plugins= and =validate_hyprpm_hook= (the hyprpm pacman hook was removed in the same commit) plus their calls in =validate_window_manager=; disabled errexit in =run-test.sh= from the validation phase onward so a failed check is counted (=VALIDATION_FAILED=) instead of fatal — the script signals pass/fail via its exit code at the end. Verified with =bash -n=; the next =make test= run confirms the count-not-abort behavior. ** DONE [#B] toggle key for touchpad on/off CLOSED: [2026-05-20 Wed] *** 2026-05-20 Wed @ 18:18:30 -0400 Spec: touchpad toggle + waybar indicator **** Current state A toggle mechanism already exists in the live home dir but is only partly committed. - =~/.local/bin/toggle-touchpad= (live, NOT in repo): reads/writes a state file at =${XDG_RUNTIME_DIR:-/tmp}/touchpad-state= (values "enabled"/"disabled"), flips =hyprctl keyword "device[$TOUCHPAD]:enabled" true|false=, and fires a =notify info "Touchpad" ...= toast. Hardcodes =TOUCHPAD="pixa3854:00-093a:0274-touchpad"=. - =~/.local/bin/touchpad-auto= (live, NOT in repo): daemon watching Hyprland's =.socket2.sock= for mouseadded/mouseremoved/configreloaded, auto-disables the touchpad when an external mouse is present, writes the same state file. Same hardcoded device name. - Keybinding already committed: =bind = $mod, F9, exec, toggle-touchpad= (=hyprland.conf:315=). - State file confirmed live at =/run/user/1000/touchpad-state= (reads "enabled"). **** Gap 1. No waybar indicator — nothing in modules-right shows touchpad state; no =custom/touchpad= module exists. 2. Neither =toggle-touchpad= nor =touchpad-auto= is committed into the repo. They live only in =~/.local/bin=, so a fresh stow won't install them. They belong in =dotfiles/hyprland/.local/bin/= (the =dotfiles/dwm/.local/bin/toggle-touchpad= is the old X11/xinput version, unrelated). 3. =touchpad-auto= is never started — no =exec-once= launches it. 4. The toggle doesn't refresh waybar, so an indicator would lag until its poll interval. **** Proposed implementation 1. New status script =dotfiles/hyprland/.local/bin/waybar-touchpad= mirroring =waybar-layout= / =waybar-netspeed= (emit one JSON line: text + tooltip + class). Reads the state file the toggle already writes — single source of truth, no extra hyprctl call. Emits a "disabled" class + off-icon when the state file reads "disabled", else "enabled" + on-icon. 2. Waybar module in =dotfiles/hyprland/.config/waybar/config=, using "signal" so the toggle pushes an instant refresh (no polling — state only changes on toggle or mouse hotplug): =, "custom/touchpad": { "exec": "waybar-touchpad", "return-type": "json", "signal": 9, "on-click": "toggle-touchpad" }= Add =custom/touchpad= to modules-right, near =idle_inhibitor=. 3. Refresh-on-toggle: have =toggle-touchpad= (and =touchpad-auto='s set function) run =pkill -RTMIN+9 waybar= after each write to the state file (RTMIN+N ⇄ waybar "signal": N). Alternative: drop "signal", use "interval": 2 (simpler, ~2s lag, constant poll). Signal is the cleaner fit. 4. =style.css= (=dotfiles/hyprland/.config/waybar/style.css=): add =#custom-touchpad= to the shared padding/hover selector lists; add =#custom-touchpad.disabled { color: #d47c59; }= (the dupre orange already used for warnings). Enabled state inherits the default color. 5. Keybinding: keep =$mod+F9= (=hyprland.conf:315=). The waybar on-click gives a mouse path to the same action. 6. Commit the live scripts so stow installs them: =toggle-touchpad= and =touchpad-auto= into =dotfiles/hyprland/.local/bin/= (plus the =pkill= line), and =waybar-touchpad= (new). If the auto-disable-on-external-mouse behavior is wanted at boot, add =exec-once = touchpad-auto= near the other daemon exec-once lines. **** Decisions (Craig, 2026-05-20) 1. Icons: 󰍽 enabled / 󰍾 disabled (the mouse / mouse-off pair). 2. Waybar on-click toggles the touchpad. 3. Commit =touchpad-auto= and add its =exec-once= so it runs at login. 4. Signal-driven refresh (=pkill -RTMIN+9 waybar=). Note: the hardcoded device name =pixa3854:00-093a:0274-touchpad= is Framework-laptop-specific — a portability concern for other machines, not a blocker for this task. *** 2026-05-20 Wed @ 18:29:06 -0400 Implemented the toggle + waybar indicator (in repo) Built per spec + decisions above. Committed the two formerly-live-only scripts into the repo and added the indicator: - =dotfiles/hyprland/.local/bin/waybar-touchpad= (new) — reads =$XDG_RUNTIME_DIR/touchpad-state=, emits JSON (text/tooltip/class), fail-safe to "enabled". Unit-tested in =tests/waybar-touchpad/= (6 Normal/Boundary cases). - =dotfiles/hyprland/.local/bin/toggle-touchpad= — copied from =~/.local/bin=, added =pkill -RTMIN+9 waybar= so the indicator refreshes on toggle. - =dotfiles/hyprland/.local/bin/touchpad-auto= — copied in, =pkill -RTMIN+9 waybar= inside =set_touchpad= so auto on/off events refresh too. Added =exec-once = touchpad-auto= to =hyprland.conf=. - =waybar/config= — =custom/touchpad= module (signal:9, on-click toggle-touchpad), placed in modules-right before idle_inhibitor. - =waybar/style.css= — =#custom-touchpad= in padding + hover lists; =.disabled { color: #d47c59 }= (dupre orange). - =$mod+F9= bind already present (=hyprland.conf=), left as-is. *** 2026-05-20 Wed @ 18:36:26 -0400 Deployed + verified on velox Discovered =.local/bin= is stow-symlinked (waybar-layout/netspeed point into the repo); the two touchpad scripts were real files only because they weren't committed. Replaced both real files with repo symlinks and symlinked the new =waybar-touchpad= (matching the existing relative-symlink form). velox needed no hyprland.conf change — =exec-once = touchpad-auto= and the =$mod+F9= bind were already present. waybar =config= / =style.css= are real local files on velox (config diverges: standalone battery, no sysmonitor group), so applied targeted edits there rather than a copy. Verified end-to-end after a waybar restart: config loads with no parse errors; toggle round-trips state enabled → disabled (󰍾, class disabled) → enabled (󰍽), and the =pkill -RTMIN+9 waybar= refresh fires into the running bar. Touchpad left enabled. Visual confirmation (icon in bar, orange when off) is Craig's to eyeball. Other machines (ratio) pick this up on =git pull && make restow hyprland= — their =.local/bin= and waybar configs are symlinks, so no real-file conflict there. ** DONE [#B] Airplane-mode toggle + waybar indicator CLOSED: [2026-05-21 Thu] Laptop-only low-power toggle, modeled on the touchpad indicator. Wifi off (bluetooth left alone for earbuds), CPU EPP → power, brightness → 35%, and stops network-only services. Disengage restores only what it recorded, so anything already off stays off. *** 2026-05-21 Thu @ 17:43:07 -0400 Built the toggle, indicator, and tests - =dotfiles/hyprland/.local/bin/airplane-mode= (new) — toggle. Engage records prior state (wifi enabled/disabled, EPP value, brightness, which services were active) to =$XDG_RUNTIME_DIR/airplane-state=, then applies low-power: =nmcli radio wifi off=, EPP → power on all CPUs (sudo sysfs write), =brightnessctl set 35%=, and stops Tier 1+2 services (tailscaled, proton.VPN, avahi-daemon, cups, wsdd, geoclue, sshd, fail2ban + user syncthing). Disengage replays the recorded state — only re-enables wifi if it was on, only restarts services it stopped. Refreshes the bar via =pkill -RTMIN+10 waybar=. - =dotfiles/hyprland/.local/bin/waybar-airplane= (new) — indicator. Reads =mode= from the state file; fail-safe to inactive. Laptop-gated: exits silently (module hidden) when no battery is present (=/sys/class/power_supply/BAT*=). One clear plane glyph (FA U+F072) for both states; color carries state (gold active / gray inactive). - =waybar/config= — =custom/airplane= module (signal 10, on-click airplane-mode), placed after custom/touchpad. =waybar/style.css= — =#custom-airplane= in padding + hover lists; =.active { color: #d7af5f }= (dupre gold). - Tests: =tests/airplane-mode/= (20 — engage/disengage/preserve-existing-state/dispatch, via command stubs + fake EPP sysfs) and =tests/waybar-airplane/= (10 — states/boundary/laptop-gating). All green; shellcheck clean. - Deployed + live-verified on velox (engage → disengage round-trip works). Other machines pick it up via git pull && make restow hyprland. ** DONE [#C] super+e emacs launch doesn't grab focus from tiled browser :quick: CLOSED: [2026-05-22 Fri] :PROPERTIES: :LAST_REVIEWED: 2026-05-22 :END: Launching emacs with super+e while a browser window is open in tiled mode leaves focus on the browser instead of moving it to the newly opened emacs window in the main (left) portion of the screen. Expected: the new emacs window takes focus. Noticed 2026-05-22. Resolved 2026-05-22: not a focus *failure* but a focus *fight*. Live socket2 capture showed the new (XWayland, non-pgtk Emacs 30.2) frame does get focus on open, then Firefox reclaims it via an activation request because =misc:focus_on_activate=true=. Set it =false= in the dotfiles repo (=3bfba5a=) — new-window focus is a separate path so emacs still focuses on open, but the browser can no longer steal it back. Verified by Craig. ** DONE [#C] Dim inactive windows in Hyprland :hyprland: CLOSED: [2026-05-27 Wed] :PROPERTIES: :LAST_REVIEWED: 2026-05-26 :END: Shipped in the =~/.dotfiles= repo (=66124e8=): =dim_inactive = true=, =dim_strength = 0.4= (tuned by eye), =dim_special = 0.2= for pyprland scratchpads, and a =no_dim true= window rule for Zoom. The opt-out rule is =no_dim= (underscore), not =nodim= — the latter throws a config-error banner. Config uses Hyprland 0.55's =windowrule = match:class ...= grammar. ** CANCELLED [#A] Prevent X termination and VT switching (security risk) CLOSED: [2026-05-21 Thu] If someone grabs laptop at cafe and hits ctrl+alt+backspace, they kill screensaver/X and get console access Need to disable: ctrl+alt+backspace (zap X) and ctrl+alt+F# (VT switching) Previous attempts to configure in xorg.conf.d failed - need to investigate what's overriding the settings Tried: /etc/X11/xorg.conf.d/00-no-vt-or-zap.conf with DontVTSwitch and DontZap options Removed conflicting setxkbmap statements, gdm, and keyd configs - still didn't work ** DONE [#B] Add Rust installation via rustup instead of pacman package :quick: CLOSED: [2026-05-26 Tue] :PROPERTIES: :LAST_REVIEWED: 2026-05-21 :END: Already implemented — =archsetup= lines 1976-1979 (Programming Languages and Utilities) =pacman_install rustup= then =rustup default stable= as the user. Closing on verification; the task predated that work. The =rust= package has been removed from archsetup. Need to add Rust installation using =rustup= (the official Rust toolchain manager) instead of the Arch package. Steps: - Install rustup: =pacman -S rustup= - Initialize default toolchain: =rustup default stable= - Consider adding to archsetup or post-install script Reference: Removed from archsetup on 2025-11-15 ** CANCELLED [#D] Add cpupower installation and enabling to archsetup :quick: CLOSED: [2026-05-26 Tue] Implemented, VM-verified, then removed — wrong tool for this fleet. Both machines run active-mode pstate drivers (ratio amd-pstate-epp, velox intel_pstate) where only performance/powersave exist and the driver self-manages frequency via EPP; both correctly sit on powersave. cpupower's governor-forcing only helps older acpi-cpufreq systems, which we don't run. Forcing performance would pin max clocks (worse on the laptop, pointless on the desktop). Dropped from archsetup rather than ship a backwards default. cpupower service configures the default CPU scheduler (powersave or performance) Install cpupower, configure /etc/default/cpupower, enable service: ~systemctl enable --now cpupower.service~ ** DONE [#C] Airplane-mode toggle robustness follow-ups :quick:solo: CLOSED: [2026-06-10 Wed] :PROPERTIES: :LAST_REVIEWED: 2026-06-10 :END: Shipped 2026-06-10 as dotfiles commit =16fbe4e=, TDD'd (23 tests green). Both gaps closed: the toggle now no-ops without a BAT* (same check as waybar-airplane, AIRPLANE_POWER_SUPPLY_DIR override for tests), and an empty recorded brightness at disengage falls back to 100% (AIRPLANE_BRIGHTNESS_DEFAULT) instead of stranding the screen at 35%. ** DONE [#B] protonmail-bridge package service conflicts with Hyprland autostart :cmail: CLOSED: [2026-06-10 Wed] :PROPERTIES: :LAST_REVIEWED: 2026-06-10 :END: Craig confirmed resolved 2026-06-10 — the per-machine fix (disable the packaged user service, Hyprland exec-once as sole launcher) has held since 2026-05-22 with no recurrence. The =protonmail-bridge= package ships an enabled systemd user service (=/usr/lib/systemd/user/protonmail-bridge.service=, =--noninteractive=, =Restart=always=) that double-launches with the Hyprland =exec-once = protonmail-bridge --no-window= GUI autostart. Two symptoms: (1) no tray icon — the headless service grabs ports 127.0.0.1:1143/:1025 before the GUI =--no-window= instance can bind; (2) TLS cert mismatch — the headless service can't reach gnome-keyring (starts outside the graphical session), falls back to its own self-signed cert, so =mbsync=/mu4e and cmail-action.py fail STARTTLS against =~/.config/protonbridge.pem= with SSL CERTIFICATE_VERIFY_FAILED. Fix applied per-machine 2026-05-22: =systemctl --user disable --now protonmail-bridge.service=, leaving the Hyprland exec-once GUI as the sole bridge (tray icon returns, served cert matches, =mbsync -a= clean). A fresh install re-enables the package service, so make it durable: mask/disable =protonmail-bridge.service= during install (likely in =scripts/cmail-setup-finish.sh=) and document that the Hyprland exec-once is the intended launcher — never run both. Source: handoff from .emacs.d 2026-05-22. ** DONE [#B] Add signal-cli to the standard install :tooling:signal:solo: CLOSED: [2026-06-10 Wed] :PROPERTIES: :LAST_REVIEWED: 2026-06-10 :END: Shipped 2026-06-10 as archsetup commit =1229fb2= — =aur_install signal-cli= beside signal-desktop, with the JRE/update-cadence/manual-linking caveats as comments. Add =signal-cli= (AUR) to the regular package set so every provisioned machine has it. It's the headless JSON-RPC engine for an in-Emacs Signal client (a =signel= fork) that's the same across all machines. Source: handoff from .emacs.d 2026-05-26. - =aur_install signal-cli= in the appropriate section (comms/messaging or AUR utilities). - Runtime needs a JRE (OpenJDK 17+) — already satisfied by =jdk-openjdk=; note it as a dependency if the install set is ever trimmed. - Keep-current caveat: signal-cli must update roughly every 3 months or Signal-Server rejects it (client-version floor moves). It belongs in the regularly-updated AUR set, not pinned. - Linking is per-machine and interactive (QR scan from phone's Linked Devices), so that stays manual. archsetup only guarantees the binary is present. ** DONE [#B] Mic-mute keybind + waybar indicator :waybar:hyprland:solo: CLOSED: [2026-06-10 Wed] :PROPERTIES: :LAST_REVIEWED: 2026-06-10 :END: Shipped 2026-06-10 as dotfiles commit =07d056c= (script + 5 unit tests + bind + waybar module + CSS in all three theme files; old CTRL+ALT+SPACE bind removed). Verified live on ratio: state flips in wpctl, indicator renders both states with correct glyphs and colors, notifications fire. velox picks it up via pull + restow. A single mute state in PipeWire, reachable from a keybind and a waybar indicator, each reflecting the other. Agreed design (2026-06-10): - *Keybind*: Super+Shift+A (=bindl= so it works on the lock screen), running a =mic-toggle= script in =hyprland/.local/bin/=: =wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle=, then read the new state and fire =notify= (alert "Mic muted" / success "Mic live"). wpctl targets PipeWire's default source, so the bind keeps working if the default mic changes (ratio has three capture devices). - *Waybar indicator*: a second pulseaudio module instance (=pulseaudio#mic=) using =format-source= / =format-source-muted= — waybar subscribes to PipeWire events natively, so the keybind and the click both update the icon with no signal plumbing (unlike =custom/dim=). =on-click= runs the same wpctl toggle. - *Icons*: Nerd Font MD glyphs — mic (U+F036C) live, mic-off (U+F036D) muted — matching the MD volume glyphs already in the pulseaudio block. Verify by rendering, not by name (BerkeleyMono remaps codepoints; see the 2026-06-10 glyph lesson). - *Coloring* (dupre): default =#969385= when live; =#d47c59= when muted — same semantic as =#custom-touchpad.disabled= (an input device turned off). The gold =#d7af5f= stays reserved for active/attention states (airplane, dim). Mirror the rule in the hudson theme's waybar css with its palette equivalent. - *Remove the old mechanism entirely*: the =CTRL ALT, SPACE= amixer Capture-toggle bind in =hyprland.conf= (~line 325) — ALSA-level, fragile with multiple capture devices, brittle notify grep chain. Lives in the dotfiles repo (=hyprland/.config/hypr/hyprland.conf=, =hyprland/.config/waybar/=, =hyprland/.local/bin/=). TDD the =mic-toggle= script per the dotfiles suite. velox picks it up via pull + restow. ** DONE [#B] Waybar theme-CSS drift — live style.css ahead of theme copies :waybar:hyprland:solo: CLOSED: [2026-06-11 Thu] :PROPERTIES: :LAST_REVIEWED: 2026-06-11 :END: Shipped 2026-06-10/11 across two dotfiles commits: =1589734= reconciled dupre to a byte-copy of the live style.css, rebuilt hudson with the full live selector set in its palette, and added the guard suite (dupre must equal live; hudson must cover every live selector). The same guards were extended to the foot.ini family in =c5e699b= when the per-host work touched it (set-theme overwrites foot.ini the same way). The symlink-instead-of-cp alternative wasn't needed — the test guard catches drift at =make test= time. ** DONE [#B] Add =uv= to the install playbook :tooling:python:solo: CLOSED: [2026-06-10 Wed] :PROPERTIES: :LAST_REVIEWED: 2026-06-10 :END: Shipped 2026-06-10 as archsetup commit =3e22b06= — =pacman_install uv= in the Python tooling block (uv 0.11.19 in extra). Exercised by the same-day hyprland VM run. Add =uv= (Astral's Python package + script runner) to archsetup so fresh machines pick it up automatically. Currently installed by hand on ratio + velox (=/usr/bin/uv= 0.11.15), not in the standard set — a fresh install would skip it, and project scripts using PEP 723 inline-script metadata (=#!/usr/bin/env -S uv run --script= shebangs) would fail with =env: uv: No such file or directory=. Source: handoff from health 2026-05-29 ([[file:assets/outbox/2026-05-29-1127-from-health-todo-a-add-uv-to-the-install-playbook.org][outbox copy]]). Health requested [#A] (load-bearing for the PEP 723 pattern they're promoting + the rulesets template-script proposal). Demoted to [#B] for archsetup: no current install is broken (uv is pre-installed everywhere it's needed), and the shape matches the existing [#B] tooling-codification tasks (eask, signal-cli) — load-bearing for other projects, manually installed today, codify so fresh installs pick it up. - *Install via pacman* — =uv= is in extra (=pacman -S uv=). Cleanest path; auto-updates with the rest of the system. AUR =uv-bin= and Astral's official installer are alternatives but add a non-pacman path to maintain. - *Placement* — alongside the existing language-tooling block in =archsetup= (near =rustup=, =nvm=, or the Python set). Decide the exact section at implementation time. - *Verification* — post-install =which uv && uv --version=; PEP 723 end-to-end check per the health handoff (=/tmp/uv-test.py= shebang script with inline =requests= dep). Related: the new [#B] LLM task above may grow scripts that benefit from PEP 723 (e.g. =scripts/llm-smoke-test.sh= if Python-based). =uv= landing here removes that friction. ** DONE [#A] Separate dotfiles from archsetup CLOSED: [2026-06-09 Tue] :PROPERTIES: :LAST_REVIEWED: 2026-06-09 :END: *** 2026-05-11 Mon @ 13:01:29 -0500 AI Response: Dotfile separation plan Approach: keep =dotfiles/= committed in this repo as the working default (Craig's machines and CI keep functioning untouched), but make the *source location* a config variable. The install script learns one new conf key — =DOTFILES_REPO= / =DOTFILES_BRANCH= — and when set, clones that repo into =~/.dotfiles= and stows from there instead of from =dotfiles/= inside archsetup. The Makefile gets a =DOTFILES= override env var so the same stow targets work whether dotfiles live in-repo or elsewhere. No submodule (adds fragility for a curl|bash installer); a separate published =archsetup-dotfiles= repo is optional follow-up, not a blocker. 1. Add conf keys to =archsetup.conf.example= under the "Git Repositories" block (after line 57): =DOTFILES_REPO= (commented, with note "leave unset to use the dotfiles bundled with archsetup"), =DOTFILES_BRANCH= (default =main=), and =DOTFILES_DIR= (target clone path, default =~/.dotfiles=). Document that a user's repo must have =common/= plus optionally =dwm/= and =hyprland/= subdirs that stow cleanly to =~=. 2. In =archsetup= lines 114-122, map =DOTFILES_REPO=/=DOTFILES_BRANCH=/=DOTFILES_DIR= to lowercase vars. At lines 136-146, leave =dotfiles_dir="$archsetup_dir/dotfiles"= as the fallback default and add =dotfiles_repo="${dotfiles_repo:-}"=. 3. In =user_customizations()= (lines 828-854): after the archsetup clone (line 838-841), branch — if =dotfiles_repo= is non-empty, =git clone --depth 1 --branch "$dotfiles_branch" "$dotfiles_repo" "$dotfiles_clone_dir"= (chown to user) and set =dotfiles_dir="$dotfiles_clone_dir"=; else keep =dotfiles_dir="$user_archsetup_dir/dotfiles"= (line 844). The stow calls at lines 847-854 stay as-is since they just =cd "$dotfiles_dir"=. Guard the hyprland stow (851) so it no-ops if the user repo has no =hyprland/= dir. 4. The waybar-battery sed block (lines 856-865) and the =git restore= step (lines 896-902) both assume Craig's exact files — wrap each in an existence check (=[[ -f "$waybar_config" ]]=, and only =git -C "$dotfiles_dir" restore .= when =dotfiles_dir= is a git repo). Right now they'd error on a foreign dotfiles tree. 5. =Makefile= line 5: change =DOTFILES := $(shell pwd)/dotfiles= to =DOTFILES ?= $(shell pwd)/dotfiles= so a user with external dotfiles runs =make stow hyprland DOTFILES=~/.dotfiles=. =reset= (line 123, =git checkout -- dotfiles/=) and =import= (writes to =$(DOTFILES)/$(DEST)=) already key off =$(DOTFILES)= except that one hardcoded path — fix line 123 to =git -C $(DOTFILES) checkout -- .=. Update the =help= text (lines 16-45) to mention the =DOTFILES== override. 6. Migration: this is purely additive. Default behavior = today's behavior, so Craig's existing machines and =make test= VMs are unaffected. Craig can later extract =dotfiles/= to =git.cjennings.net/archsetup-dotfiles= and set =DOTFILES_REPO= in his own =archsetup.conf= — but that's his choice and a separate commit; the in-repo copy stays as the canonical default. Update =CLAUDE.md= "Project Structure" + "Makefile Targets" sections to document the override. 7. Simple-UX summary to put in =archsetup.conf.example= and README: "Accept the defaults → you get the bundled dotfiles (DWM or Hyprland). Bring your own → set =DOTFILES_REPO= to your git URL; it gets cloned to =~/.dotfiles= and stowed automatically." Optional stretch: ship a tiny =dotfiles/minimal/= (bash/zsh/git/tmux only) and a conf option =DESKTOP_ENV=none= path that stows just that. Open questions for Craig: - Extract =dotfiles/= to a standalone repo now, or defer? (Plan above defers — keeps it as the in-repo default.) - Clone target: =~/.dotfiles= vs =~/code/dotfiles= vs let the user pick via =DOTFILES_DIR=? - Should =DOTFILES_REPO= unset mean "bundled dotfiles" (proposed) or "no dotfiles at all"? - Do you want the minimal/headless starter set (=dotfiles/minimal/=), or out of scope? - Keep =git restore=/=git checkout= conflict-recovery behavior only when dotfiles are a git checkout, and silently skip otherwise — acceptable? *** 2026-05-13 Wed @ 13:07:54 -0500 AI Response: Open questions answered; full spec written All 5 questions resolved: extract now (Q1), clone to =~/.dotfiles= (Q2), no opt-out — =DOTFILES_REPO= always has a default (Q3), include =minimal/= as Tier B with TUI apps (Q4), error out if dotfiles dir isn't a git checkout (Q5). Full spec at [[file:docs/PLAN-dotfiles-separation.org][docs/PLAN-dotfiles-separation.org]] — covers the =minimal/= tree, SSH/GPG availability, three-phase implementation plan (Phase 1: extract + populate new repo at =cjennings.net/archsetup-dotfiles.git=; Phase 2: wire archsetup + VM test; Phase 3: migrate machines + remove =dotfiles/=), commit map, and open observations. Implementation gated on spec review. *** 2026-05-14 Thu Review docs/PLAN-dotfiles-separation.org CLOSED: [2026-05-14 Thu] Review the spec for accuracy, edge cases, and scope. Flag changes before implementation starts. See [[file:docs/PLAN-dotfiles-separation.org][docs/PLAN-dotfiles-separation.org]]. *** 2026-05-14 Thu @ 21:43:41 -0500 AI Response: Review resolved; spec locked for Phase 1 Walked the spec's 5 open questions plus my 5 review concerns. Locked: URL =https://git.cjennings.net/dotfiles.git= (anonymous HTTPS read confirmed against existing repos at the same host), bare repo path =/var/git/dotfiles.git=, scope = Phase 1 only (~30 min). Added =environment.d/envvars.conf= (with rofi path stripped) and =systemd/user/emacs.service= to the =minimal/= tree; skipped =ncmpcpp= and =systemd/user/geoclue-agent.service=. Phase 2/3 constraints folded into the spec body for the executor: =DESKTOP_ENV=none= VM test required (was optional), clone uses =sudo -u "$username"= to avoid chown-after races, Phase 3 unstow/restow runs without an intermediate Hyprland reload, dotfiles repo can't go on GitHub until secrets cleanup ships, and Step 3.3 documents the post-install update flow. Latest spec at =docs/PLAN-dotfiles-separation.org= (=817d939=). End-of-day Phase 1 session reads from there and executes. *** 2026-05-22 Fri @ 13:41:08 -0500 Phase 1 executed — dotfiles repo live on cjennings.net Created the bare repo at =/var/git/dotfiles.git=, extracted =dotfiles/= from archsetup with =git filter-repo --subdirectory-filter= (229 commits, per-file history preserved), built the =minimal/= stow target per the spec, and pushed to =git@cjennings.net:dotfiles.git= (HEAD =68daeab=). Anonymous read at =https://git.cjennings.net/dotfiles.git= confirmed. Two spec corrections committed in archsetup (=7c26495=): push URL switched to SSH (HTTPS is read-only), and =minimal/.profile.d/= now ships 5 files including =claude.sh= (added on Craig's call, post-dated the spec lock). Phase 2 (wire archsetup config + VM test, ~2-3 hrs) and Phase 3 (migrate machines, remove =dotfiles/= from archsetup) remain. *** 2026-05-22 Fri @ 17:05 -0500 Phase 2 shipped — archsetup clones the dotfiles repo Wired archsetup to the external dotfiles repo: clones =DOTFILES_REPO= to =~/.dotfiles= and stows per =DESKTOP_ENV= (dwm/hyprland → common + that DE; none → minimal). Added =DOTFILES_REPO=/=BRANCH=/=DIR= config keys + validation; test harness serves the repo to the VM as =/tmp/dotfiles-test=. Commits =bab6901= (feat) + =68172c8= (test infra), pushed to origin/main. Spec-directed =sudo -u= clone hit a real bug — =useradd -m= skips the home-dir chown when =/home/$username= pre-exists (root-owned), so the user-clone failed with Permission denied; fixed by cloning as root + =chown -R= (mirrors the archsetup clone). git restore now runs for all DE paths (minimal ships skel-colliding .bashrc etc.). *** 2026-05-22 Fri @ 18:10 -0500 Phase 3.1 + 3.3 done — this machine on ~/.dotfiles Migrated this workstation: cloned the dotfiles repo to =~/.dotfiles=, committed the gpg-agent SSH routing (=.zshenv= + =envvars.conf=) that was uncommitted in the live tree as =888a599= in the dotfiles repo, then =make unstow hyprland= + =make stow hyprland DOTFILES=~/.dotfiles=. Snag: unstowing while Hyprland ran made it write a stub hyprland.conf that blocked the restow — quit Hyprland, removed the stub, restowed clean. All symlinks now resolve into =~/.dotfiles=. CLAUDE.md updated with the external-repo docs + migration steps + the quit-Hyprland gotcha (=e1810ce=). Remaining: 3.2 (=git rm dotfiles/=) blocked until ratio + velox migrate the same way. *** 2026-05-22 Fri @ 21:20 -0500 velox migrated to ~/.dotfiles (laptop overrides preserved) ratio is THIS machine (was "fractal" pre-reinstall) — migrated in 3.1. velox migrated over SSH (Craig quit its Hyprland): cloned ~/.dotfiles, stowed common+hyprland from it. velox carries deliberate laptop-local real-file overrides (foot.ini font 12, pypr config.toml laptop scratchpad sizing, waybar config battery module) that shadow stow — preserved them as local real files (backed up, restowed the rest, restored the overrides). All machines now on ~/.dotfiles. *** 2026-06-02 Tue @ 12:16:54 -0500 Phase 3.2 done — removed in-repo dotfiles/ from archsetup git rm'd the in-repo =dotfiles/= tree (831 files) now that ratio + velox both stow from =~/.dotfiles=; the installer already clones DOTFILES_REPO so nothing read it at install time. Stripped the stow targets from archsetup's Makefile (kept VM-integration + the safe-rm-rf installer-helper suite). Updated CLAUDE.md (Project Structure, Makefile Targets, Dotfiles Repository, Script Counts, Theme/Key-Config path refs) and README.md (dotfile-management, theme, DE, unit-test sections) to point at =~/.dotfiles=; the README had been describing the pre-Phase-2 in-repo model. Commit b10cba5 on archsetup origin/main. velox + ratio local clones drop dotfiles/ on their next archsetup pull (ratio: see the "Pull Phase 3.2 changes onto ratio" task). 4 untracked calibre cache/annotation files that were never committed got moved aside to /tmp/archsetup-dotfiles-orphan-untracked-20260602 (disposable reading-position markers). *** 2026-06-02 Tue @ 12:16:54 -0500 Migrated script unit-test suites + a Makefile into ~/.dotfiles Gave =~/.dotfiles= its own Makefile rather than repointing archsetup's =DOTFILES= default — the dotfiles repo now owns its stow tooling and tests, so it manages and validates standalone (relevant to the open-source release too). Authored =~/.dotfiles/Makefile= with the stow family (=stow/restow/reset/unstow/import= + check-de/check-dest + DE/DEST machinery) plus a =make test= target (mirrors archsetup's hyphenated-dir test-unit loop). Moved-Makefile fixups: =DOTFILES := $(shell pwd)= (trees at repo root), =reset='s revert scoped to =git checkout -- common $(DE)= (not the whole repo — caught in review), import header/path "dotfiles/$(DEST)" → "$(DEST)", =minimal= added to the import DEST filter only. Moved 6 suites (=airplane-mode=, =layout-navigate=, =notify=, =tmux-util=, =waybar-airplane=, =waybar-touchpad=) into =~/.dotfiles/tests/=, dropping the =dotfiles/= =SCRIPT=-path prefix (=REPO_ROOT= is now the dotfiles root), and copied their fixtures (=layout-navigate/fake-hyprctl=, =tmux-util/fake-{fzf,kill,sleep,tmux}=). =waybar-netspeed='s suite was already there. =safe-rm-rf= stayed in archsetup (it tests the installer, not a dotfile). =make test= green: 7 suites, 124 tests. Committed 59b10c4 + pushed to the dotfiles repo. =minimal= is a standalone tree (stowed alone, not =common + minimal=), so a =make stow minimal= target needs its own branch — deferred as a small follow-up; the move kept stow/restow/reset/unstow behavior-identical to archsetup (dwm/hyprland). *** 2026-06-09 Tue @ 19:21:36 -0500 Pulled Phase 3.2 onto ratio + cleaned dangling links ratio's archsetup clone was already current with origin/main (Phase 3.2 pulled), but the migration had left stale symlinks pointing into the now-deleted =~/code/archsetup/dotfiles=: =~/.config/calibre= plus a manual =~/music/radio/= playlist farm (73 broken =.m3u= links) and one dead reference under =~/projects/home/reconciliation=. Re-pointed calibre into =~/.dotfiles/common/.config/calibre=. Deleted the 73 radio links — dead and redundant, since the same playlists already stow correctly to =~/music/*.m3u=, which is what mpd reads (=music_directory=/=playlist_directory= both =~/music=) — and removed the reconciliation link. ratio now has zero archsetup-dangling symlinks. (The ~3400 other dangling links in =~= are unrelated system/flatpak noise: ca-certificates, =/run/host=, =/bin=.) ** DONE [#B] Cleaner per-machine override mechanism for the dotfiles repo CLOSED: [2026-06-11 Thu] :PROPERTIES: :LAST_REVIEWED: 2026-06-11 :END: Shipped 2026-06-11 as dotfiles =c5e699b= after spec review (all five questions decided — see the spec's Status table). Host tiers =ratio/= + =velox/= auto-included by every stow target; first tenants: hypr local.conf (velox HiDPI scale + XWayland toolkit env, replacing the Zoom per-app hack), pypr whole-file split, foot font via per-host host.ini include. waybar stays shared (velox's was stale, not divergent). velox restows cleanly for the first time and caught up on all pending dotfiles work. Drift guards extended to foot.ini; Makefile host logic unit-tested (15 suites green). Phase 5 (installer) filed as a follow-on below. velox needs a Hyprland restart for the env vars — see Manual testing. velox keeps laptop-specific configs (foot font, pypr scratchpad sizing for 2256x1504, waybar battery) as local REAL files shadowing the stow symlinks. That's fragile: any =make restow= on velox re-conflicts (hit exactly this during the 2026-05-22 migration — stow aborts on the real files). The =~/.dotfiles= model needs a real per-machine override story (a =minimal/=-style per-host package, a documented local-override convention with =.stow-local-ignore=, or host-conditional includes) so overrides survive restows without manual backup/restore. Spec: [[file:docs/PLAN-per-host-overrides.org][docs/PLAN-per-host-overrides.org]] — gated on review before implementation. *** 2026-06-11 Wed @ 04:40:00 -0500 AI Response: Second restow-conflict instance on velox The roam-units rollout hit this again: =make restow hyprland= on velox aborted on three real files (foot.ini font size 12, the velox-tuned pypr config.toml, an older waybar config). The roam systemd units were linked manually to unblock; everything else velox should have picked up from recent dotfiles work (mic-toggle, quick-capture, airplane-mode hardening, ranger plugin) is still NOT stowed there — velox's tree stays partially stale until this mechanism ships. That raises this task's practical urgency: velox can no longer cleanly receive dotfiles changes at all. *** 2026-05-26 Tue @ 10:21:08 -0500 AI Response: Spec written, gated on review Surfaced by a HiDPI scaling failure: a per-app =QT_SCALE_FACTOR=1.5= in the shared =Zoom.desktop= (meant for velox) made Zoom open enormous on ratio. Reverted that patch to plain =/usr/bin/zoom %U=; the durable fix is this mechanism. Proposed approach: a per-host stow tier (=ratio/=, =velox/=) stowed as =common + hyprland + $(uname -n)=, with the existing =conf.d/*.conf= glob as the first clean tenant — move =local.conf= out of the shared =hyprland/= tier into per-host tiers so each machine gets its own (HiDPI monitor scale + =env = QT_SCALE_FACTOR/GDK_SCALE= on velox, minimal on ratio). XWayland apps don't scale via the compositor (=force_zero_scaling=true=), so toolkit env vars set in =conf.d= are the right layer — kills per-app =.desktop= hacks. Open question in the spec: whole-file configs with no include directive (waybar JSON, pypr toml) need a separate strategy. Full design + 5 open questions for Craig in the spec. ** DONE [#B] Verify Phase 2 in the VM (hyprland + none) — pending clean run :solo: CLOSED: [2026-06-10 Wed] :PROPERTIES: :LAST_REVIEWED: 2026-06-10 :END: Both runs clean on 2026-06-10. Hyprland (=make test=, results =20260610-151228=): 52 passed / 0 failed, and the same-day uv + signal-cli install additions were exercised in-run. None (results =20260610-165438=-ish, second attempt): 50 passed / 0 failed — the minimal/ tree stowed correctly. The first none attempt failed on a test-harness bug, not the installer: validation.sh hardcoded the common/ symlink target, fixed in =1754a94= (expected path now follows DESKTOP_ENV). The only attributed issue in both runs is the Proton-VPN-daemon-fails-in-VM known noise. The Phase 2 none/minimal path is now verified end-to-end. ** DONE [#C] Investigate the 2026-05-11 VM-test warnings CLOSED: [2026-06-11 Thu] :PROPERTIES: :LAST_REVIEWED: 2026-06-11 :END: All five resolved. Four were environment-impossible checks converted to uncounted skips (=ced91c4= + the portal refinement =19015c7=) — socket, portal, mDNS-on-slirp, docker-pre-reboot — and all four skips verified firing in the 2026-06-11 12:56 run (52/0, 1 warning). The fifth (lingering) turned out to be a harness quoting bug, not a logind issue — fixed in =5b51900=, dated entry below. The next clean run should report zero warnings. The 18:36 =make test= run that filed this passed 52/0/5; the sub-entries below carry each investigation. *** 2026-06-10 Wed @ 19:07:54 -0500 Hyprland-socket warning converted to a skip Shipped in =ced91c4=: the check now passes when the socket exists, skips (uncounted) when no Hyprland process is running — the headless-VM state — and warns only in the genuinely odd case of a running compositor with no socket. Verified live: the skip fired in the 2026-06-10 19:06 run. *** 2026-06-10 Wed @ 19:07:54 -0500 Portal-query warning converted to a skip Shipped in =ced91c4= + a follow-up refinement: the first condition (portal process absent) didn't fire because a socket-activated =xdg-desktop-portal= exists even headless; the precondition is really a running compositor, so the skip now keys on =pgrep -x Hyprland= like the socket check. The conf-file checks (the part install controls) still pass/fail normally. The dconf-write angle stays tracked under =[#B] Fix install errors=. *** 2026-06-10 Wed @ 19:07:54 -0500 mDNS-ping warning converted to a slirp-aware skip Shipped in =ced91c4=: when the VM is on QEMU slirp (a =10.0.2.x= address), the =.local= ping is skipped — multicast genuinely can't pass there — and the =is-enabled= check stands alone. On real networking the full ping test still runs and still warns on failure. Verified live: the skip fired in the 2026-06-10 19:06 run. *** 2026-06-11 Thu @ 12:58:19 -0500 Lingering warning was a harness quoting bug — fixed, hypothesis disproven make test-keep forensics on the kept VM: the linger file existed (created mid-install), =loginctl show-user cjennings -p Linger= said yes, logind active with zero errors — lingering was correctly enabled all along, so the logind-degraded hypothesis was wrong and archsetup's =enable-linger= calls were always fine. The actual bug was in the check itself (=validation.sh=): it captured =ls path && echo yes=, so a present file produced "path\nyes", which never string-equals "yes" — the check warned on every run regardless of state. Fixed in =5b51900= with =test -e=; the corrected expression verified returning "yes" against the live VM. With this, all five 2026-05-11 warnings are resolved and a clean run should report zero. *** 2026-06-10 Wed @ 19:07:54 -0500 Docker warning converted to a pre-reboot skip Shipped in =ced91c4=: =docker info= success still passes; enabled-but-inactive (the deliberate enable-not-now install state, validated pre-reboot) now skips; active-but-unresponsive still warns — that's the real failure case. Verified live: the skip fired in the 2026-06-10 19:06 run. The enable vs enable-now question for archsetup itself was left as-is (the daemon's weight makes enable-on-boot defensible). Note: the run also logged two log-diff meta-warnings — "Found 4 new error lines after archsetup" and "New failed services detected (before: 1, after: 2)". Those correspond to the post-install systemd noise (pam_systemd / logind / Proton VPN) already captured under =[#B] Fix install errors= above; not duplicated here. ** DONE [#B] Enable TLP power management for laptops :quick: CLOSED: [2026-06-10 Wed] :PROPERTIES: :LAST_REVIEWED: 2026-06-10 :END: Done live on velox 2026-06-10: tlp 1.10.1 installed, =/etc/tlp.d/01-custom.conf= written (EPP balance_performance/power + platform-profile per power source; 80% charge cap present but commented off), service enabled and active, systemd-rfkill masked per TLP docs. Verified: tlp-stat runs, EPP reads balance_performance on AC. Codified in archsetup commit =adb39f2= as a battery-gated block. ** DONE [#B] Remove unnecessary linux-firmware packages (velox only) :quick: CLOSED: [2026-06-10 Wed] :PROPERTIES: :LAST_REVIEWED: 2026-06-10 :END: Done live on velox 2026-06-10. Hardware re-verified first (i915 graphics, ath9k wifi), then removed the meta + 12 subpackages (the task's 9 plus liquidio/mellanox/nfp/qlogic from the finer 2026 split), keeping intel + atheros + whence. The meta needed =-Rdd= — mkinitcpio-firmware declares a dep on it; the dangling dep is cosmetic. Initramfs rebuilt clean (warnings only for absent hardware), wifi stayed connected. Codified in archsetup commit =adb39f2= as a DMI-gated Framework-Intel block. Full confidence needs the next reboot — see Manual testing below. ** DONE [#B] Identify and replace packages no longer in repos CLOSED: [2026-06-11 Thu] :PROPERTIES: :LAST_REVIEWED: 2026-06-11 :END: Shipped 2026-06-11 as =1f89523=: =scripts/audit-packages.sh= (unit-tested) makes the check repeatable, and its first run over 420 packages found four casualties, all fixed in the same commit — libva-mesa-driver (folded into mesa), nvidia-dkms → nvidia-open-dkms, swww → awww (set-theme's stale swww call fixed in dotfiles =4ea35a1=), libappindicator-gtk3 → libayatana-appindicator. Re-run anytime: =scripts/audit-packages.sh=. ** DONE [#B] Verify package origin for all packages CLOSED: [2026-06-11 Thu] :PROPERTIES: :LAST_REVIEWED: 2026-06-11 :END: Covered by the same auditor (=1f89523=): it flags movers in both directions. Current state: zero official packages wrongly routed through aur_install-only territory; 15 aur_install entries have graduated to official repos (duf, flameshot, gist, inxi, nsxiv, nvm, papirus-icon-theme, ptyxis, qt5ct, qt6ct, ttf-lato, ueberzug, warpinator, xcolor, xdg-desktop-portal-hyprland). Left as-is deliberately — yay resolves repo packages fine — but switching them to pacman_install is a clean :quick: cleanup whenever wanted; the auditor lists them on every run. ** DONE [#B] Automate script usage tracking :solo: CLOSED: [2026-06-10 Wed] :PROPERTIES: :LAST_REVIEWED: 2026-06-10 :END: Shipped 2026-06-10 as dotfiles commit =e5044b8=: =script-usage= in =common/.local/bin/= (10 unit tests). Reads zsh extended + bash history, reports last-used date per ~/.local/bin script, =--unused= lists the never-seen set. First run on ratio: 109 scripts, 98 unseen by the current (short) history window. ** DONE [#B] Automate dotfile validation :solo: CLOSED: [2026-06-10 Wed] :PROPERTIES: :LAST_REVIEWED: 2026-06-10 :END: Shipped 2026-06-10 as dotfiles commit =2054da4=: =dotfiles-validate= in =common/.local/bin/= (11 unit tests). Extracts commands from hypr exec/bind-exec lines, waybar exec/on-click/on-scroll values, and systemd user-unit Exec* lines, then verifies each resolves. First run found 4 real orphans — see the follow-up task below. *** 2026-06-11 Thu @ 00:44:41 -0500 All 4 orphaned references fixed; validator fully clean Both emacs.service units repointed to /usr/bin/emacs (dotfiles =cd15d9b=), and per Craig's call the tor-browser and virtualbox keybinds were dropped rather than backed by installs (dotfiles =e4cb4c2= — Ctrl+Alt+W and Super+V now free). dotfiles-validate: 102 references checked, all resolve. ** DONE [#B] Document evaluation criteria and trade-offs CLOSED: [2026-06-10 Wed] :PROPERTIES: :LAST_REVIEWED: 2026-06-10 :END: Written 2026-06-10: [[file:docs/2026-06-10-tool-evaluation-criteria.org][docs/2026-06-10-tool-evaluation-criteria.org]] — four gating criteria (Wayland-native, actively maintained with live verification, automation-compatible, stowable config), five weighting criteria, the process, and the trade-offs accepted in the 2026-06-10 evaluation round. ** DONE [#B] Add org-capture popup frame on keyboard shortcut CLOSED: [2026-06-10 Wed] :PROPERTIES: :LAST_REVIEWED: 2026-06-10 :END: Shipped 2026-06-10, all five spec steps: =quick-capture= script (dotfiles =08ae188=, 3 unit tests, notify-on-failure when the daemon's down), Hyprland window rules in current 0.53+ syntax (float, 900x500, center, stay_focused on title org-capture) + Super+Shift+N bind (same commit), and the auto-close hook in =org-capture-config.el= (.emacs.d =1a25fada=, .elc recompiled, loaded live). Verified end-to-end on ratio: popup opens floating/centered with the template menu (screenshot), frame auto-deletes on org-capture-kill — finalize uses the same hook. Existing capture templates untouched. ** DONE [#C] Create Chrome theme with dupre colors :quick:solo: CLOSED: [2026-06-10 Wed] :PROPERTIES: :LAST_REVIEWED: 2026-06-10 :END: Shipped 2026-06-10 as archsetup commit =4736058=: unpacked-extension theme at =assets/color-themes/dupre/chrome-theme/= (manifest.json + README with the color mapping and load-unpacked install steps). Visual check is yours — see Manual testing below. ** DONE [#C] Install Zoxide integration into Ranger :quick: CLOSED: [2026-06-10 Wed] :PROPERTIES: :LAST_REVIEWED: 2026-06-10 :END: Shipped 2026-06-10 as dotfiles commit =220dde6=: jchook/ranger-zoxide vendored (with MIT license) into both =common/= and =minimal/= ranger plugin dirs — :z and :zi commands wherever ranger runs. Python syntax verified; live verification is yours (see Manual testing) and needs a machine with ranger installed — note neither Wayland box has it, and the same-day file-manager evaluation recommends yazi over porting ranger forward. ** DONE [#D] Add retry logic to git_install function :quick: CLOSED: [2026-06-10 Wed] Already shipped before this review — commit =798b86f= gave git_install the same MAX_INSTALL_RETRIES loop as pacman/aur, with a clean-slate build dir per attempt. The task predates the fix; closing as done. ** DONE [#B] Org-capture popup frame split (quick-task Super+Shift+N) CLOSED: [2026-06-13 Sat] SCHEDULED: <2026-06-12 Fri> :PROPERTIES: :LAST_REVIEWED: 2026-06-12 :END: Resolved: .emacs.d fixed it config-side (single-window display + cj/quick-capture command); archsetup pointed the popup script at cj/quick-capture (8cc1be7). Verified end-to-end on ratio. The quick-capture popup opens split in two windows — a top sliver of the daemon's last-visited buffer plus the =*Org Select*= menu below — so the two stacked modelines read like tmux status bars. Root cause: =org-mks= displays the template menu via =org-switch-to-buffer-other-window=, splitting the fresh popup frame instead of taking it over. Coordinating with the .emacs.d project: handoff sent 2026-06-12 18:59 requesting a config-side fix scoped to frames named =org-capture= (handoff note + screenshot evidence delivered to .emacs.d's inbox, since processed and removed). Waiting on its reply in this project's inbox; then verify the popup end-to-end on ratio (Super+Shift+N → single-window menu → single-window capture buffer). Fallback if .emacs.d declines: carry the fix in the dotfiles =quick-capture= script's =-e= elisp. Related finding, no change needed: whole-desktop screenshot already exists at CTRL+Super+S (=screenshot fullscreen=, grim fires before the fuzzel menu so popups survive). Possible follow-up decision: rebind Super+Shift+S (currently layout-switch to scrolling) if Craig wants fullscreen capture there. *** 2026-06-12 Fri @ 20:21:00 -0500 Incorporated .emacs.d's fix and verified end-to-end .emacs.d replied same evening with two notes (now in [[file:assets/outbox/2026-06-12-1947-from-.emacs.d-org-capture-popup-singlewindow-reply.org][outbox]] and [[file:assets/outbox/2026-06-12-2006-from-.emacs.d-quick-capture-script-change.org][outbox]]): the single-window fix landed config-side (frame-scoped =display-buffer-alist=, 7 ERT tests, live in the daemon), plus a new =cj/quick-capture= command (Task/Bug/Event only, global-inbox targets, frame closes on every exit path, 12 ERT tests). Our side: test-first one-line change in the dotfiles =quick-capture= script — =(org-capture)= → =(cj/quick-capture)= — suite 15/15 green, live immediately via stow. Verified on ratio with sendshortcut-driven popups + grim: menu single-window with the 3-template subset, capture buffer single-window targeting =CAPTURE-inbox.org=, no orphan frames, nothing leaked into the inbox file. Verification reply + screenshot evidence sent back to .emacs.d. Remaining: commit the dotfiles change (Craig's gate) and the Super+Shift+S rebind decision. ** DONE [#C] Silent notifications for the mic-mute toggle :quick:solo: CLOSED: [2026-06-11 Thu] :PROPERTIES: :LAST_REVIEWED: 2026-06-11 :END: Shipped 2026-06-11 as dotfiles =a4ae4a4=, minutes after filing: =--silent= on all four of mic-toggle's notify calls (Muted/Live/unknown/fail), tests assert the flag on every path (5/5, full suite 15 suites green), and a live round-trip on ratio confirmed the toggle works with the toast and without the chime. velox picks it up on next pull. ** DONE [#B] Create package inventory system CLOSED: [2026-06-14 Sun] :PROPERTIES: :LAST_REVIEWED: 2026-06-13 :END: Satisfied by =scripts/package-inventory= (the same script that closes "Automate the inventory comparison" above). It lists archsetup's declared packages, lists the live system's packages, and prints the diff in both directions. Design note: it compares explicit-vs-explicit (=pacman -Qqe= against declared =pacman_install=/=aur_install=), which is the meaningful comparison — the original "including dependencies" framing was superseded, since transitive deps are pulled automatically and listing full closures would only add noise. *** 2026-06-14 Sun @ 22:13:48 -0500 Listed archsetup's declared packages — package-inventory extraction (pacman_install/aur_install + for-loop lists) *** 2026-06-14 Sun @ 22:13:48 -0500 Listed live-system packages — package-inventory via pacman -Qqe / -Qq / -Qqen / -Qqem *** 2026-06-14 Sun @ 22:13:48 -0500 Generated archsetup-vs-system diff — package-inventory, both directions, AUR/official split ** DONE [#B] Automate the inventory comparison :test:solo: CLOSED: [2026-06-14 Sun] :PROPERTIES: :LAST_REVIEWED: 2026-06-13 :END: Make package diff a runnable script instead of manual process Resolved 2026-06-14: the runnable script already existed — =scripts/package-inventory= (built 2026-02-06) extracts archsetup's declared packages and diffs them against the live system (=--summary= / =--archsetup-only= / =--system-only= / full report). This pass added the missing coverage: 7 characterization tests in =tests/package-inventory/= pinning the extraction and both diff directions behind injectable =PKGINV_ARCHSETUP= / =PKGINV_PACMAN= seams, plus a =make package-diff= target for discoverability. Full unit suite green (26 tests, 3 suites). ** DONE [#C] paru vs yay — evaluated, staying with yay CLOSED: [2026-06-10 Wed] Research done 2026-06-10: [[file:docs/2026-06-10-paru-vs-yay-evaluation.org][docs/2026-06-10-paru-vs-yay-evaluation.org]]. The maintenance picture inverted since the task was filed: yay released v12.6.0 on 2026-06-07 with active triage, while paru has had no release in 11 months, no commit in 5, and a stable that fails to build against current libalpm (issue #1468 open 6 months). For an installer that bootstraps the AUR helper unattended, paru is the riskier choice on every axis that matters. No decision needed — the evidence closes this one; revisit only if paru's maintenance resumes.