#+TITLE: ArchSetup Tasks #+AUTHOR: Craig Jennings #+DATE: 2026-02-14 * Archsetup Priority Scheme Four levels, matching the Emacs config (=org-highest-priority ?A=, =org-lowest-priority ?D=, =org-default-priority ?D= in =modules/org-config.el=). Priority answers "how much does this matter"; a date answers "when". They are independent — assign both deliberately. Org priority alone never schedules anything, which is why undated [#A]/[#B] tasks feel ungrounded. - [#A] Must happen. Broken install, data loss, security, or a blocker for other work. An [#A] REQUIRES a SCHEDULED or DEADLINE date — if it can't be dated, it isn't really an A; drop it to B. (The main agenda always shows open A's.) - [#B] Should happen, this cycle. Real improvement or fix with no hard date. Surfaces in the agenda's priority-B block only while undated; add a SCHEDULED date when you commit to a week and it moves into the schedule. - [#C] Nice to have / someday. Kept for the record, low urgency. Date it only when it graduates to B. - [#D] Default / unsorted. A bare TODO with no cookie is D. Stays out of the agenda — the inbox of priorities. Triage D's up to A/B/C or let them sit. Rule of thumb: A = dated-and-must; B = the active backlog; C = parking lot; D = untriaged. Fixing the undated A/B tasks means either dating them or demoting to C. * Archsetup Open Work ** TODO [#B] Scrolling layout: frame fit + wrap-around :hyprland: 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. - Wrap-around: navigating past the last frame should wrap to the first (and vice versa). - Whatever else surfaces in daily use once the above land. The support machinery was deliberately kept for this task: =layout-navigate= and =layout-resize= retain their scrolling branches, =waybar-layout= still renders the scrolling state, and the unbound legacy =cycle-layout= script still lists it. Re-enabling is two lines: add =scrolling= back to =LAYOUTS= in =layout-cycle= and restore a direct-jump bind (the old chord is taken now — pick a new one). The =tests/layout-cycle= suite pins the disabled state and will go red on re-enable, which is the reminder to update it. ** TODO [#C] Waybar indicators unevenly spaced :quick:solo: :PROPERTIES: :LAST_REVIEWED: 2026-06-09 :END: The right-side module icons don't sit at even intervals — spacing reads as inconsistent across the group. Tune the per-module margin/padding in =dotfiles/hyprland/.config/waybar/style.css= so the icons are evenly distributed. Noticed 2026-05-21 after adding the airplane indicator. ** TODO [#C] Wlogout exit-menu buttons are rectangular, not square :PROPERTIES: :LAST_REVIEWED: 2026-06-09 :END: The wlogout exit menu renders its buttons taller than they are wide on velox, so the cells read as vertical rectangles instead of squares. They render square (centered) correctly on ratio, so this is a per-host / resolution difference, not a flat bug. Fix the button sizing in the wlogout style (=~/.dotfiles/hyprland/.config/wlogout/style.css=) so each cell is square on both hosts. Noticed 2026-05-21. Related: the [#D] VERIFY about wlogout sizing across displays. Add a regression test so the square-cell fix doesn't silently break on a resolution change: assert the rendered (or computed) wlogout button cells are square across ratio's and velox's resolutions. Dropped :quick: — the cross-host test pushes this past a spare-moment fix. ** TODO [#B] Guard against live mesa/hyprland/wayland-runtime updates :hyprland: :PROPERTIES: :LAST_REVIEWED: 2026-06-09 :END: A live =pacman -Syu= that swaps mesa/hyprland/wayland runtime libs out from under a running Hyprland session can crash the compositor: the next GPU-lib call hits a now-"(deleted)" library and SIGABRTs, taking the Wayland clients down with it. Hit ratio 2026-06-07 (mesa 26.0.6 -> 26.1.2 + hyprland upgraded live; Hyprland SIGABRT took down awww/insync/emacs). Likely the driver behind ratio's high lifetime unsafe-shutdown ratio — a crashed compositor forces a hard reset. Ship a guard: an update wrapper, or a documented practice, that when a pending =-Syu= set includes mesa/hyprland/wayland runtime libs advises running it from a TTY (or after logging out of Hyprland) rather than live. Returned to archsetup from archangel 2026-06-09 — hyprland/mesa are installed and managed by archsetup, not the ISO installer. ** TODO [#C] Pocketbook development backlog :pocketbook: :PROPERTIES: :LAST_REVIEWED: 2026-05-26 :END: Pocketbook (GTK4 layer-shell notes panel, toggled via waybar) was pulled from publication 2026-05-26 — github repo + cjennings.net repo deleted, mirror hook removed — and folded into this repo at =pocketbook/= until it's ready to spin back out. Src-layout Python package with pytest tests and a Makefile. Develop it in-tree; the backing modules are =store/note/panel/layer_shell/app/note_widget= + =style.css=. Backlog (unordered; promote items to their own dated tasks as they're picked up): - Configurable options, possibly a dedicated configuration panel. - Lose-focus hides pocketbook — configurable on/off. - Configurable display order: chronological by creation date (asc/desc), manual, alphabetical (asc/desc). - Search / filter notes. - Global toggle keybind (Hyprland =bind=) alongside the waybar click; document the waybar integration. - Note CRUD polish (create/edit/delete) + optional markdown rendering. - Pin / favorite notes. - Tags or notebooks / categories. - Persistence: confirm store format + =~/.local/share/pocketbook/= location, add versioning/migration, decide a backup/sync story. - Theming: track the dupre/hudson theme system so =style.css= follows =set-theme=. - Layer-shell geometry config (anchor edge, width, margins) + HiDPI / multi-monitor behavior — ties into [[file:docs/PLAN-per-host-overrides.org][per-host overrides]] scaling work. - Config file format (toml) + reload-without-restart. - Expand test coverage (TDD per testing standards; =tests/= already exists). - Release prep for the eventual spin-back-out: pyproject metadata, version, license. - Re-wire the archsetup install (gtk4-layer-shell dep + install step + post-install clone) when pocketbook ships. Removed 2026-05-26 — see git history of =archsetup= / =scripts/post-install.sh=. ** TODO [#B] Provision Eask in archsetup :tooling:eask: :PROPERTIES: :LAST_REVIEWED: 2026-05-26 :END: Add =@emacs-eask/cli= to archsetup's provisioning so fresh machines get it. Eask is installed by hand today and declared nowhere in archsetup or the dotfiles repo, yet both chime and linear-emacs depend on it (their =make setup/test/coverage= shell out to =eask=). Source: handoff from linear-emacs 2026-05-23. - Add a global npm install after the node block (=archsetup= ~2030, after =aur_install nvm=), modeled on the claude-code native-install block: run as =$username=, wrapped in =display=/=error_warn=, output to =$logfile=. Roughly =sudo -u "$username" bash -c 'npm install -g --prefix "$HOME/.local" @emacs-eask/cli'=. - Pin the prefix to =~/.local= so eask lands at =~/.local/bin/eask= (already on PATH) and the install runs as the user, not root. On the current machine =npm config get prefix= returns =/usr=, so eask was installed with an explicit =--prefix=. - Decision: also set a persistent user npm prefix (=~/.npmrc= with =prefix=${HOME}/.local=)? If yes, that =~/.npmrc= is a legitimate dotfile to stow; if no, rely on the explicit =--prefix= flag alone. =~/.eask/= is a regenerable cache — leave un-stowed. - Acceptance: fresh run leaves =eask= on PATH at =~/.local/bin/eask= (no root); =cd ~/code/chime && make setup && make test= works. ** TODO [#B] Waybar timer module :waybar: :PROPERTIES: :LAST_REVIEWED: 2026-05-26 :END: A custom waybar module providing three time-keeping functions, surfaced in the bar with click/scroll controls and dunst notifications on completion. - *Alarm* — fire a notification at a wall-clock time (e.g. 2:00pm). Builds on the existing =notify= + =at= pattern from protocols.org. - *Timer* — count down a duration (e.g. 25m) and notify when it elapses. - *Pomodoro* — alternating work/break cycles (default 25/5, long break after 4) with the bar showing phase + remaining time. Implementation notes (to flesh out when picked up): waybar =custom= module(s) with =exec= polling or a persistent =exec= script emitting JSON; click actions to start/pause/reset; a small state file under =~/.local/state= or =~/.local/var=. Lives in the hyprland tier (=dotfiles/hyprland/.config/waybar/= + a backing script in =hyprland/.local/bin/=). TDD the backing script per testing.md. ** TODO [#B] Collapsible waybar sides :waybar: :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. - *Right-side base set* (proposed): the date/time, optionally plus the systray. Everything else on the right (sysmonitor group, netspeed, pulseaudio, the toggles) hides. - *Left-side base set*: TBD (workspaces only, or menu + workspaces). Implementation notes: waybar has no native per-side collapse, so this is custom. Options to explore: (a) swap between a full and a collapsed waybar config on click via a signal/exec, (b) rewrite the modules array and reload (heavy), (c) a state file the modules read to hide/show a group via CSS. Likely a state file (=$XDG_RUNTIME_DIR=) + per-side toggle scripts + a targeted waybar refresh, mirroring the existing custom-module + signal pattern. Lives in the dotfiles repo (=hyprland/.config/waybar/= + =hyprland/.local/bin/=). TDD the toggle scripts per the dotfiles suite. ** TODO [#B] Network-manager dropdown, nmcli-backed with GPG-stored secrets :waybar:network: :PROPERTIES: :LAST_REVIEWED: 2026-06-09 :END: Replace the current wifi/network waybar component with a self-contained network manager driving nmcli directly (no =nmtui= dependency). Same look as the existing indicator; clicking it drops down the management interface (design open, keep it minimalistic). Core functionality: - Add / edit / remove connections. - List saved connections by SSID, ordered by recency (most-recently-used first); select one to switch to it. - Recognize a wired/ethernet connection even when plugged in after the session started, and allow selecting it at any time. Switch freely: ethernet↔wifi, wifi↔wifi. - Match all current functionality of the existing wifi/network component (status icon, signal strength, tooltip). Credential storage: - Store connection definitions + passwords in a GPG-encrypted file under =~/.config= (appropriate XDG location), encrypted to Craig's private key. - Passphrase cadence configurable: decrypt once per session, once per hour (via gpg-agent cache TTL), or never (plaintext / stays decrypted). *Default is unencrypted* — encryption is opt-in. Design / open questions (propose before building): - Dropdown UI tech: a GTK layer-shell panel (like pocketbook), a fuzzel/rofi-style menu, or a waybar-native expanding group. - Relationship to NetworkManager's own store (=/etc/NetworkManager/system-connections=, root-only): does the GPG store supplement or replace it, and how do they stay in sync. - Whether to keep the existing =custom/netspeed= throughput readout alongside the new SSID/status indicator. Implementation notes: backing scripts in the dotfiles repo (hyprland tier); nmcli for every NM op (device status, con up/down, add/modify/delete, wifi rescan/list). TDD the nmcli-wrapper logic with a fake nmcli on PATH. Sizable — worth a =docs/design/= doc before implementation. ** TODO [#B] Desktop-settings dropdown panel :waybar: :PROPERTIES: :LAST_REVIEWED: 2026-06-09 :END: 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). - *Brightness* slider (backlight, via brightnessctl). - *Keyboard-backlight* brightness slider (brightnessctl on the kbd_backlight class). - *Mouse* enable/disable toggle — shown only when a mouse is connected. - *Trackpad* enable/disable toggle — shown only when a trackpad is connected (mirror =toggle-touchpad= / =touchpad-auto=). - *Idle inhibitor* (the existing =idle_inhibitor= module). - *Airplane mode* (the existing =airplane-mode= toggle; laptop-only). The conditional rows (mouse, trackpad, airplane) appear only when their hardware/context applies — reuse the laptop/device detection the airplane and touchpad indicators already do. Design / open questions (propose before building): - Panel tech: sliders need a real toolkit (waybar can't host a slider), so a GTK4 + gtk4-layer-shell app like pocketbook is the likely shape. - Which existing standalone bar modules (dim, touchpad, airplane, idle_inhibitor) collapse INTO this panel vs. stay on the bar as quick-access indicators. Craig's call. Implementation notes: a small GTK layer-shell app (mirror pocketbook's structure: src-layout Python package, pytest, Makefile) talking to brightnessctl / hyprctl / the touchpad + airplane helpers. Lives in the dotfiles repo or in-tree like pocketbook. TDD the backing toggle/slider logic. Sizable — worth a design doc first. ** TODO [#B] Separate mpd playlist_directory from music_directory :mpd:music:quick: :PROPERTIES: :LAST_REVIEWED: 2026-06-09 :END: Spec written and approved (option 1), pinned before execution on 2026-06-03. Root issue: mpd.conf has =playlist_directory= == =music_directory= == ~/music, so the whole audio library is the playlist store and radio streams mix with curated playlists. Option 1: radio stream playlists (portable, 73 in the dotfiles repo) move to a dedicated =playlist_directory= (=~/.local/share/mpd/playlists=) via stow; the 22 curated local playlists (machine-specific track refs) live in the music tree. Also removes the broken ~/music/radio/ orphan (73 dead symlinks). Full step-by-step spec (mpd.conf edit, repo restructure of =common/music/= → =common/.local/share/mpd/playlists/=, curated relocation, restow, verification incl. the 7 relative-path curated playlists, ratio propagation) is in the 2026-06-03 session record under .ai/sessions/. Two open decisions before executing: (1) drop the empty =60s Sounds.m3u= or refill with the SomaFM 60s URL; (2) curated playlists into =~/music/playlists/= subdir vs leave flat in ~/music/. Side cleanup surfaced: a stray audio file =Black Flamingos - Space Bar.m4a= is wrongly committed in the dotfiles repo's =common/music/= — git rm it and move to the synced library. ** TODO [#B] Local offline LLM runtime + per-host model cache :tooling:llm: :PROPERTIES: :LAST_REVIEWED: 2026-05-29 :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]]). Per-host model targets (from the handoff): - *ratio* (Strix Halo, 128 GiB) — Qwen3-Coder-30B Q6_K (default) + Q4_K_M (compat) + Qwen3-Next-80B Q4_K_M (long-context fallback). - *velox* (i7-1370P, 64 GiB iGPU) — Qwen3-Coder-30B Q4_K_M + an 8B fallback for low-latency triage. Install behavior: prefetch idempotent (skip if file exists, match size/hash); download failure must NOT fail the install — surface a clear "local LLM support incomplete" follow-up instead. Ship a smoke-test command (boot endpoint + short prompt). Decisions to resolve before code: *** TODO Decide model cache location: per-user vs system-wide Handoff lists both =~/.local/share/llm/models= (per-user) and =/srv/models/llm= (system-wide). Per-user matches the existing archsetup user-config style and avoids root ownership of large model files. System-wide matches the "machine-local model inventory" phrasing and shares cache across users on multi-user boxes (not the case here — single user per machine). Pick one as the default; the other stays available via =LLM_MODEL_CACHE=. *** TODO Decide whether =ollama= ships by default or is opt-in Handoff calls =ollama= "optional". Likely shape: =llama.cpp= is the only mandatory runtime; =ollama= behind =INSTALL_OLLAMA= (default no) for users who prefer its model-manager API. Confirm. *** TODO Define config keys for the LLM block in =archsetup.conf.example= Likely: =INSTALL_LOCAL_LLM= (default yes), =LLM_RUNTIME= (=llama.cpp= / =ollama=), =LLM_MODEL_CACHE= (path), =LLM_MODELS= (space-separated, or empty → per-host autodetect). Lock names + defaults before writing install code. *** TODO Decide per-host model selection: auto-detect by =uname -n= vs explicit =LLM_MODELS= Auto-detect against a known-host table (ratio → Q6_K + 80B, velox → Q4_K_M + 8B) is simple for current machines but brittle for any new host (silently picks no models). Explicit =LLM_MODELS= per machine in =archsetup.conf= is more verbose but never surprises. Pick the default; the other stays available. *** TODO Decide network-down behavior for model prefetch Three shapes: (a) emit =error_warn= and write =/var/lib/archsetup/state/llm-models-pending= for inspection; (b) install a one-shot systemd unit that retries on next boot with network; (c) just log and forget — user re-runs the prefetch helper manually when network returns. Implementation work (gated on the decisions above): *** TODO Install =llama.cpp= with CPU + Vulkan backend where supported Add to the appropriate install section in =archsetup= (=llama.cpp= / =llama.cpp-vulkan= in AUR). Decide CPU-only vs Vulkan per host from the hardware detection already used for GPU drivers. *** TODO Install =ollama= behind config flag (if Decision 2 = opt-in) Add =ollama= package install gated on =INSTALL_OLLAMA=yes=. *** TODO Configure shared model cache + OpenAI-compat local endpoints Create =$LLM_MODEL_CACHE= with the right ownership; configure llama.cpp (and ollama if installed) to serve =127.0.0.1:8081= (coding) and =:8082= (general). Likely systemd user units; decide launcher pattern when implementing. *** TODO Prefetch per-host models (idempotent, non-fatal on network failure) Download the per-host model set (from Decision 4) into the cache; skip files that exist with matching size/hash. On failure, fall back per Decision 5. Models from HuggingFace GGUF mirrors (URLs locked at implementation time). *** TODO Ship a local-LLM smoke-test command Boot the configured endpoint and send a short prompt; surface success/failure + timing. Useful as both a post-install check and a triage tool when something later breaks. Likely =scripts/llm-smoke-test.sh=; runs at end of install if =INSTALL_LOCAL_LLM=yes=. 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. ** DOING [#B] Prepare for GitHub open-source release :PROPERTIES: :LAST_REVIEWED: 2026-06-09 :END: Remove personal info, credentials, and code quality issues before publishing. *** 2026-06-09 Tue @ 19:21:36 -0500 Reconciliation: six sub-tasks now target the ~/.dotfiles repo, not archsetup Phase 3.2 removed the in-repo =dotfiles/= tree, so six sub-tasks below no longer describe archsetup content — they target files now owned by the =~/.dotfiles= repo (=git.cjennings.net/dotfiles.git=): "Remove credentials and secrets from dotfiles", "Remove/template personal info from dotfiles", "Remove binary font files from repo", "Move battery out of waybar sysmonitor group", "Resolution-adaptive scratchpad sizing", and "Dynamic waybar/foot config based on screen resolution". Their paths are relative to that repo now. Kept here for tracking per Craig (2026-06-09); he'll re-scope the archsetup-vs-dotfiles split shortly. archsetup-proper release work (scripts personal-info, device-specific config, shellcheck, and scrubbing the pre-=b10cba5= dotfiles secrets from archsetup's own history) stays this task. *** 2026-05-11 Mon @ 13:01:29 -0500 AI Response: Open-source-prep source audit Checked each subtask below against the source / git state. Bottom line: almost nothing is fully done. =LICENSE= and =README.md= were added this session (see those subtasks); the rest still stands. - *Remove credentials and secrets from dotfiles* — NOT DONE. All five named files still tracked: =dotfiles/common/.config/.tidal-dl.token.json=, =.config/calibre/smtp.py.json=, =.config/transmission/settings.json=, =.msmtprc=, =.mbsyncrc=. =.gitignore= lists none of them; no =.example= templates exist. - *Remove/template personal info from scripts* — PARTIALLY DONE. Repo URLs ARE config-driven (=archsetup:141-146= use =${dwm_repo:-https://git.cjennings.net/...}=, documented in =archsetup.conf.example=). Still personal: =archsetup:2-3= (email/website header), =init:8,21= (=root:welcome=), =scripts/post-install.sh:17-56= (personal repos). - *Remove/template personal info from dotfiles* — NOT DONE. =.gitconfig= has =c@cjennings.net=, =name = Craig Jennings=, =github user = cjennings=, =safe.directory= and employer creds; =.config/mpd/musicpd.conf= + =mpd.conf= still use =~cjennings/= / =/home/cjennings/= paths; =.ssh/config= has personal/employer hosts; =.config/yt-dlp/config:2= has =c@cjennings.net=; =hyprland.conf:3= has personal attribution. - *Scrub git history of secrets* — NOT DONE. 275 commits; history not fresh, no filter-repo evidence. - *Remove device-specific configuration* — NOT DONE. =archsetup:1486-1493= still creates the Logitech BRIO udev rule unconditionally; no config flag. - *Add README.md for GitHub* — DONE (this session — initial draft, pending review). See subtask below. - *Add LICENSE file* — DONE (this session — GPL-3). See subtask below. - *Remove binary font files from repo* — NOT DONE. =dotfiles/common/.local/share/fonts/= still tracks 8 PragmataPro =.ttf= files, =AppleColorEmoji.ttf=, and other commercial fonts (Cartograph, MonoLisa, ComicCode, etc.). - *Make claude-code installation optional* — NOT DONE. =archsetup:1817-1818= runs =curl -fsSL https://claude.ai/install.sh | sh= unconditionally; no flag. - *Add input validation for username and paths* — PARTIALLY DONE. =archsetup:326-328= validates =$username= against =^[a-z][a-z0-9_]*$= (plus reserved-names check, marked DONE separately). No validation of =$source_dir= or other path vars. - *Move battery out of waybar sysmonitor group* — NOT DONE. =dotfiles/hyprland/.config/waybar/config:27-37= still has =battery= inside =group/sysmonitor=. - *Resolution-adaptive scratchpad sizing* — NOT DONE. No size/move windowrules for scratchpads in =hypr/conf.d=. - *Dynamic waybar/foot config based on screen resolution* — NOT DONE. No resolution-detection/generation script. - *Bulk shellcheck cleanup* — PARTIALLY DONE. =shellcheck archsetup= still shows 68 findings: 30×SC2329, 16×SC2174, 15×SC2024, 4×SC2086, 1 each SC2155/SC2129/SC2005. The 4 SC2086 (unquoted) are the ones a reviewer would flag — those are the priority. - *Document testing process in README* — NOT DONE. =scripts/testing/README.org= exists but isn't the project README. (Now unblocked — root README exists.) - *Add guard for rm -rf on constructed paths* — DONE 2026-05-20. All three constructed-path deletes routed through a =safe_rm_rf= guard (absolute / no-=..= / inside-allowed-prefix / real-dir checks); unit-tested in =tests/safe-rm-rf/=. - *Standardize boolean comparison style* — NOT DONE. Mixed: =[ "$var" = "true" ]= at =archsetup:542,544,569= vs bare =if $var;= form ~7 places elsewhere. - *Replace eval with safer alternatives* — NOT DONE. =archsetup:442= still =if eval "$cmd" >> "$logfile" 2>&1;= in =retry_install=. *** TODO [#A] Remove credentials and secrets from dotfiles :quick: - =.config/.tidal-dl.token.json= — active Tidal API token with userId - =.config/calibre/smtp.py.json= — hex-encoded relay password, personal email mappings (family Kindle accounts) - =.config/transmission/settings.json= — bcrypt-hashed RPC password - =.msmtprc= — mail server credentials (gpg password references) - =.mbsyncrc= — ProtonBridge IMAP credentials Add all to =.gitignore=, remove from git tracking, create =.example= templates where appropriate. *** TODO [#A] Rotate exposed calendar feed URLs Needs the ratio GUI (browser-based regeneration), so deferred until I'm in front of ratio. Three private ical URLs sat in git history (commit =500b1f5=, 2026-05-13) until the 2026-05-20 scrub. The scrub removed them from local + remote history, but anyone who pulled the repo between those dates still has the tokens, so regenerate all three: - Google personal (=craigmartinjennings@gmail.com= private ical URL) - Proton (calendar.proton.me URL with PassphraseKey) - Google DeepSat (=craig.jennings@deepsat.com= private ical URL) After regenerating, update the live =~/.emacs.d/calendar-sync.local.el= (now owned by the emacs/dotemacs project — see its inbox handoff from 2026-05-20). *** 2026-05-20 Wed @ 12:09:32 -0500 Scrubbed the calendar secret from git history =dotfiles/common/.emacs.d/calendar-sync.local.el= (private Google/Proton/DeepSat ical URLs, added in =500b1f5= for stow distribution) was discovered while folding tmux-util into stow. Sent the file back to the emacs project's inbox, =git rm='d it, then =git filter-repo --invert-paths --path= purged it from all 29 affected commits. Force-pushed (=0921e4d...618e6cc=, with lease) and ran =reflog expire= + =gc --prune=now= on the bare repo at =/var/git/archsetup.git=. Verified: the file is in zero commits, the secret tokens return zero matches across all history, and =500b1f5= / =0921e4d= are unreachable on both local and remote. Rotation of the URLs tracked as the sibling TODO above. This also proves =filter-repo= works cleanly here — relevant precedent for the broader [[*Scrub git history of secrets (or start fresh)][history-scrub task]] below (the 5 credential files are still in history). *** TODO [#A] Remove/template personal information from scripts - =archsetup= lines 2-3: personal email and website in header - =archsetup= lines 141-146: hardcoded =git.cjennings.net= repository URLs — make configurable via conf - =scripts/post-install.sh=: personal git repos (finances, documents, danneel-*, nextjob, etc.) - =scripts/gitrepos.sh=: personal server URLs - =init= line 8: hardcoded password =welcome= *** TODO [#A] Remove/template personal info from dotfiles - =.gitconfig=: hardcoded name, email, GitHub username - =.config/musicpd.conf=: hardcoded =~cjennings/= paths (use =~/= instead) - =.ssh/config=: personal host configuration - =.config/yt-dlp/config=: personal domain reference - =hyprland.conf= line 3: personal attribution *** TODO [#A] Scrub git history of secrets (or start fresh) Even after removing files, secrets remain in git history. Options: =git filter-repo= to rewrite history, or start a fresh repo for the GitHub remote. Recommend: fresh repo for GitHub (keep cjennings.net remote with full history). *** TODO [#A] Remove device-specific configuration :quick: =archsetup= lines 1458-1463: Logitech BRIO webcam udev rule — move to optional/configurable section. *** DOING [#A] Add README.md for GitHub Project description, features, requirements, installation instructions, configuration guide (archsetup.conf), security considerations, contributing guidelines (or separate CONTRIBUTING.md), and license. **** 2026-05-11 Mon @ 13:01:29 -0500 AI Response: Initial README draft Drafted =README.md= at repo root, modeled on =~/code/chime/README.org=. First pass — review and run a voice/style pass before committing. Personal info (emails, =cjennings.net= URLs, personal repo names) intentionally replaced with placeholders for the eventual public release. *** 2026-05-19 Tue @ 01:54:29 -0500 Added GPL-3 LICENSE file at repo root GPL-3 chosen. Canonical GPLv3 text landed at =LICENSE= on 2026-05-11 (commit =f80e664=). README already links to it. SPDX/license headers across source files (or a NOTICE file) split out as a new sub-task below for the eventual public release. *** TODO [#A] Add SPDX/license headers to source files :quick: For a real GPL-3 release on GitHub, every source file should carry an SPDX-License-Identifier header (or the repo should ship a NOTICE file naming the license + contributors). Not blocking — the =LICENSE= file at root is what GitHub needs. Worth doing once the credentials-cleanup work for open-sourcing actually ships. Light lift: pick a header template, sweep the install script + scripts/ + dotfile scripts. *** TODO [#A] Remove binary font files from repo :quick: PragmataPro and Apple Color Emoji fonts in =dotfiles/common/.local/share/fonts/=. Add to =.gitignore=, document font installation separately. May have licensing issues for redistribution. *** 2026-06-09 Tue @ 19:21:36 -0500 Made claude-code install optional Shipped in =f2dad22= (feat: make the claude-code install optional). The =curl | sh= from claude.ai now sits behind a config flag instead of running unconditionally. *** 2026-06-09 Tue @ 19:21:36 -0500 Input validation added (validate_config + validate_username) validate_config + validate_username shipped (detail in the 2026-05-11 note below). The =$source_dir= path check was judged unnecessary — it derives from the now-always-validated =$username= (=/home/$username/.local/src=). Closed as done. **** 2026-05-11 Mon @ 18:20:49 -0500 AI Response: validate_config + validate_username added Added two pre-flight validators to =archsetup= (right after =load_config=, before any install step): - =validate_username()= — the lowercase / starts-with-letter / =[a-z0-9_]= / not-reserved check, extracted from the inline block in =preflight_checks()=. Fixes an existing gap: the inline check only ran on the *prompted* path, so a config with =USERNAME=root= (or =USERNAME=foo bar=) slipped through unvalidated. Now both =preflight_checks= and =validate_config= call it. - =validate_config()= — runs whenever =--config-file= is used: rejects unknown =DESKTOP_ENV= (must be dwm/hyprland/none) early instead of dying in step 7-9; rejects =AUTOLOGIN=/=NO_GPU_DRIVERS= values that aren't =yes=/=no= (currently silently ignored); basic shape check on =LOCALE=; and a scheme + no-whitespace/no-leading-dash check on the six =*_REPO= URLs that get passed to =git clone= (rejects e.g. =--upload-pack=…= injection). Plain =echo …>&2; exit 1= (the logging helpers aren't defined that early). =$source_dir= needs no separate check — it's =/home/$username/.local/src=, derived from the now-always-validated =$username=. Not a security boundary (=load_config= sources the config as bash; a hostile config can already run anything) — it's typo-catching. Verified with =bash -n= and a smoke-test matrix of good/bad inputs through both functions. The next =make test= run confirms valid configs still install. Leaving as DOING for review. *** TODO [#A] Move battery out of waybar sysmonitor group :quick: Battery module is inside =group/sysmonitor= which bundles cpu, temp, memory, disk, and battery together. Battery should be a standalone module in =modules-right= so it's visible on laptops without the full sysmonitor group. *** TODO [#A] Resolution-adaptive scratchpad sizing Pyprland scratchpad percentages (50% wide, 70% tall) look good on 3440x1440 but tall/narrow on 2256x1504 laptops. Currently using local config overrides per machine. Options: - Hyprland windowrulev2 size/move rules in conf.d (cleanest — reuses existing per-machine pattern) - Launcher script that generates config.toml based on detected resolution - Hostname-based symlink swap at login - Fixed pixel sizes (pyprland clamps to screen bounds) *** TODO [#A] Dynamic waybar/foot config based on screen resolution Resolution-aware font sizes and conditional module inclusion. A startup script detects resolution and generates waybar CSS and foot config with appropriate values, so both machines use the same stowed templates. *** 2026-05-20 Wed @ 06:50:25 -0500 Swept shellcheck across the shell scripts Census across the 16 shell scripts (=archsetup=, =init=, =scripts/*.sh=, =scripts/testing/=): 124 findings, zero errors. Triaged against "what matters for public review" and confirmed the 2026-01-24 read — most are intentional or documented-acceptable: - SC2024 (14, sudo redirects), SC2174 (16, =mkdir -p -m=), SC1091 (13, unfollowable sources), SC2329 (32, functions invoked indirectly via the =STEPS= dispatch array), SC2153 (1, =DISK_PATH= sourced from =vm-utils.sh=) — all false positives or accepted. - SC2086 on =$SSH_OPTS= in =vm-utils.sh= (×4) and =$TEMP_DISKS= in =cleanup-tests.sh= — intentional word-splitting; quoting would break them. The SSH_OPTS-as-array refactor is the proper fix, deliberately deferred (codebase-wide, one atomic change). - SC2086 integer tests in =[ ]= (=archsetup=, =cleanup-tests=) — safe, note-level style; left to avoid churn in the just-fixed =retry_install=. - SC2015 (×2, =vm_exec && success || warn=) — =success=/=warn= return 0, so C won't spuriously fire. Idiomatic. Fixed the four that are genuine: =init= (a =#!/bin/sh= script) used =$( = helper to =archsetup= and routed all three constructed-path deletes through it. The guard refuses to run unless the target is absolute, free of =..=, deeper than a bare top-level dir, strictly inside the allowed prefix (not the prefix itself), and a real directory (not a symlink); otherwise it prints the reason and returns non-zero without deleting. On the happy path it delegates to =rm -rf=. Sites converted (the line numbers in the original task body were stale — actual sites located by grep): - =--fresh= state-dir wipe — prefix =/var/lib/archsetup=. - =git_install= clone-retry cleanup (=build_dir= under =$source_dir=). - =aur_installer= yay clone-retry cleanup (same prefix). The helper is defined before the top-level =--fresh= handler (which runs at load time, before the logging helpers exist), so it carries no =error_warn= dependency and reports refusals to stderr itself. The two in-function sites keep their existing =|| error_warn= / =|| error_fatal= handling. Tests: =tests/safe-rm-rf/test_safe_rm_rf.py= sources the real function out of the script and exercises Normal/Boundary/Error cases (13 tests) against real temp dirs. =make test-unit= green (61 tests), =bash -n= clean, no new shellcheck warnings. *** TODO [#A] Standardize boolean comparison style :quick: Mixed =[ "$var" = "true" ]= vs =$var= evaluation — pick one pattern. *** 2026-05-26 Tue @ 15:27:09 -0500 eval task moot — the line-434 eval is gone, the survivor is deliberate Verified: the only =eval= left in =archsetup= is line 578 in =retry_install=, and it's intentional and documented — it captures =$?= directly from =eval "$cmd"= to dodge the if-compound-swallows-exit-code trap. Replacing it with an array would reintroduce that bug. The line-434 eval this task pointed at no longer exists. Nothing to change. ** TODO [#B] Review post-archsetup laptop setup steps (velox 2026-04-10) :PROPERTIES: :LAST_REVIEWED: 2026-06-09 :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. *** TODO Review: rfkill soft blocks bluetooth and wifi at boot Both bluetooth and wifi were soft-blocked by rfkill. Fix was ~rfkill unblock bluetooth/wifi~. ~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. *** TODO Review: tmpfs layered over ZFS /tmp causing systemd-tmpfiles failures ~systemd-tmpfiles-clean.service~ failed repeatedly with "Protocol driver not attached". Root cause: systemd's ~tmp.mount~ (tmpfs) mounted before ZFS's ~/tmp~ dataset, creating a stale layer. Fix: ~systemctl mask tmp.mount~. Consider: mask tmp.mount in archsetup when ZFS is used. *** TODO Review: intel-ucode not installed 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. *** TODO Review: awww-daemon crashes at boot (coredump) Wallpaper daemon crashed with abort() shortly after boot. Hyprland also coredumped at same time. May be a race condition. Restarting awww-daemon fixed it. Monitor for recurrence. *** TODO Review: touchpad-indicator missing (X11 only, no Wayland equivalent) Old ~touchpad-indicator-git~ was X11-only and removed as broken. Created ~touchpad-auto~ (auto-disable touchpad when mouse connected) and ~toggle-touchpad~ scripts. ~touchpad-auto~ watches Hyprland socket for mouseadded/mouseremoved/configreloaded events. Device name ~pixa3854:00-093a:0274-touchpad~ is hardcoded — will differ on other machines. Added to exec-once and $mod+F9 keybinding. Consider: add scripts to stowed dotfiles, make touchpad device name auto-detected. *** TODO Review: Bluetooth mouse pairing is manual post-install Paired Logi M650 via ~bluetoothctl scan on~, ~pair~, ~trust~. This is inherently interactive (scan, select device, pair, trust). Consider: document as post-install step. No automation possible. *** 2026-05-26 Tue @ 13:32:31 -0500 pocketbook install concern moot — pulled from publication, folded in-tree Resolved by removing pocketbook from archsetup's provisioning entirely. It's nowhere near ready, so the github mirror + cjennings.net repo were deleted and the project was folded into the archsetup tree at =pocketbook/=. Dropped the =gtk4-layer-shell= dep + =pip_install= from =archsetup= and the clone from =scripts/post-install.sh=. No fresh install pulls pocketbook now, so "not installed on velox" no longer applies. Re-wiring the install is tracked in the new pocketbook development backlog. *** TODO Review: Tailscale needs login after install ~tailscaled~ service was enabled but needed ~tailscale up~ for interactive auth. Old machine entry needed cleanup in admin console. Consider: document as post-install step. *** TODO Review: docs/ directories need manual sync from existing machine docs/ dirs (gitignored) for ~/code and ~/projects repos needed scp/rsync from ratio. Same for ~/.emacs.d/docs/. Not in git, so not available after clone. Consider: document as post-install step or create a sync script. ** TODO [#C] Ensure sleep/suspend works on laptops :PROPERTIES: :LAST_REVIEWED: 2026-06-09 :END: Critical functionality for laptop use - current battery drain unacceptable *NOTE:* This applies to Framework Laptop (velox), not Framework Desktop (ratio) Add kernel parameter: ~rtc_cmos.use_acpi_alarm=1~ (will become systemd default) Consider: ~acpi_mask_gpe=0x1A~ for battery drain, suspend-then-hibernate config See Framework community notes on logind.conf and sleep.conf settings ** TODO [#B] Build CI/CD pipeline that runs archsetup on every commit :PROPERTIES: :LAST_REVIEWED: 2026-05-21 :END: Core automation infrastructure - enables continuous validation ** TODO [#B] Fix install errors surfaced by the 2026-05-11 VM test run :PROPERTIES: :LAST_REVIEWED: 2026-05-21 :END: Errors logged during the VM install. Status as of the 2026-05-11 18:36 run (=test-results/20260511-183643/archsetup-output.log=) after the =48c9439= fontconfig/dconf fix: 7 → 6. - refreshing font cache — RESOLVED in =48c9439= (now installs =fontconfig= before calling =fc-cache=). - configuring GTK file chooser — RESOLVED in =ecab29f= (switched to a system-wide dconf db at =/etc/dconf/db/site.d/=; needs no session bus during install). - configuring GNOME interface settings in dconf — RESOLVED in =ecab29f= (same fix as the GTK file chooser above). - enabling firewall — exit 1: =iptables v1.8.13 (nf_tables): Could not fetch rule set generation id: Invalid argument=. Still present in the 18:36 run; likely a VM-kernel/nf_tables artifact — confirm on bare metal before treating as an archsetup bug. - verifying firewall is active — exit 1 (follow-on from the firewall-enable error). - enabling gamemode for user — exit 1 → step "gaming" FAILED — non-critical. - tidaler (AUR) — logged in the error summary with exit code 0 (odd; logging quirk or transient AUR build noise?). Also seen in the 18:36 run's log-diff (post-install systemd noise, probably VM-environment): =pam_systemd … CreateSession failed= / =logind: Failed to start session scope … Permission denied=, and =Failed to start Proton VPN Daemon= (no VPN config in the test VM). *** 2026-05-19 Tue @ 13:18:56 -0500 Fixed AUR exit-0 logging bug at the root Root cause was in =retry_install=: =last_exit_code=$?= ran AFTER =if eval ...; then return 0; fi=. Bash defines an if-compound's exit status as zero when no condition tested true, so a failing eval's exit code got overwritten with 0 before reaching =error_warn=. Fix in =8221c54=: capture =$?= from =eval= directly into a local var, then compare against the captured value in the if. VM-verified in =test-results/20260519-115318/=: =mkinitcpio-firmware (AUR)= and =tidaler (AUR)= now report =error code: 1= (yay's actual exit) instead of the misleading =error code: 0=. The same packages still appear in the summary because yay returns non-zero when sub-deps fail to build (e.g. =aic94xx-firmware=), but the codes are accurate now. If the underlying sub-dep failures stay noisy, that's a separate concern — open a new task. *** 2026-05-16 Sat @ 09:00:41 -0500 AI Response: Surfaced the expanded AUR-exit-0 pattern 2026-05-16 07:40 VM run passed (52/0/5) with the same warning profile as the 2026-05-11 18:36 run. Error count went 7 → 13: 5 fixed/unchanged, +5 new AUR-exit-0 entries (broadens the existing tidaler item into the dedicated =[#B]= subtask above), +1 genuinely new error in =setting up emacs configuration files= (=git pull= ran in =~/.emacs.d= which existed from stow but had no =.git=). Patched =archsetup:1932-1945= with a three-branch check: clone if missing/empty, pull if =.git= exists, =git init=/=fetch=/=checkout= in place if the dir came from stow. *** 2026-05-19 Tue @ 01:25:26 -0500 Verified the b9907c7 emacs-stow fix end-to-end =make test= 21:44 → 22:29 (42 min), =test-results/20260518-214516/=. 52/0/5, =ArchSetup Exit Code: 0=. The third-branch path fired correctly — install log =archsetup-2026-05-18-21-45-46.log:14358-14365= shows =From https://git.cjennings.net/dotemacs= → =[new branch] main -> origin/main= → =Reset branch 'main'= → =branch 'main' set up to track 'origin/main'=. No exit-128, no =fatal: not a git repository=. Error Summary down to 7 (was 13 on 2026-05-16); the emacs entry is gone. AUR exit-0 logging triggered for 2 packages this run (mkinitcpio-firmware, tidaler) vs 6 on 2026-05-16 — same bug class, fewer triggers, still tracked under =[#B] AUR exit-0 logged as error=. Issue Attribution: 1 ARCHSETUP entry (Proton VPN Daemon failed — known VM-no-VPN-config artifact). Cleanup ran clean via the normal path. ** TODO [#B] Generate recovery scripts from test failures :PROPERTIES: :LAST_REVIEWED: 2026-05-21 :END: Auto-create post-install fix scripts for failed packages - makes failures actionable ** TODO [#B] Create package inventory system :PROPERTIES: :LAST_REVIEWED: 2026-05-21 :END: *** TODO [#A] List all packages archsetup would install (including dependencies) *** TODO [#A] List all packages currently installed on live system *** TODO [#A] Generate diff showing what's in archsetup vs what's on system ** TODO [#B] Establish monthly review workflow :PROPERTIES: :LAST_REVIEWED: 2026-05-21 :END: *** TODO [#A] For packages in archsetup but not on system: determine if still needed *** TODO [#A] For packages on system but not in archsetup: decide add or remove *** TODO [#A] Schedule monthly package diff review ** TODO [#B] Automate the inventory comparison :PROPERTIES: :LAST_REVIEWED: 2026-05-21 :END: Make package diff a runnable script instead of manual process ** TODO [#B] Complete security education within 3 months :PROPERTIES: :LAST_REVIEWED: 2026-05-21 :END: Read recommended resources to make informed security decisions (see metrics for Claude suggestions) ** TODO [#B] All error messages should be actionable with recovery steps :PROPERTIES: :LAST_REVIEWED: 2026-05-21 :END: Currently just reports errors without guidance on how to fix them ** TODO [#B] Improve logging consistency :PROPERTIES: :LAST_REVIEWED: 2026-05-21 :END: Some operations log to ~$logfile~, others don't - standardize logging All package installs should log, all system modifications should log, all errors should log with context Makes debugging failed installations easier ** TODO [#B] Add backup before system file modifications :PROPERTIES: :LAST_REVIEWED: 2026-05-21 :END: Safety net for /etc/X11/xorg.conf.d and other system file edits Files like ~/etc/sudoers~, ~/etc/pacman.conf~, ~/etc/default/grub~ modified without backup If modifications fail or are incorrect, difficult to recover - should backup files to ~.backup~ before modifying ** TODO [#B] Implement Testinfra test suite for archsetup :PROPERTIES: :LAST_REVIEWED: 2026-05-21 :END: Create comprehensive integration tests using Testinfra (Python + pytest) to validate archsetup installations Tests should cover: - Smoke tests: user created, key packages installed, dotfiles present - Integration tests: services running, configs valid, X11 starts, apps launch - End-to-end tests: login as user, startx, open terminal, run emacs, verify workflows Framework: Testinfra with pytest (SSH-native, built-in modules for files/packages/services/commands) Location: scripts/testing/tests/ directory Integration: Run via pytest against test VMs after archsetup completes Benefits: Expressive Python tests, excellent reporting, can test interactive scenarios A design doc (not yet written) should cover: - Complete example test suite (test_integration.py) - Tiered testing strategy (smoke/integration/end-to-end) - How to run tests and integrate with run-test.sh - Comparison with alternatives (Goss) ** TODO [#B] Set up automated test schedule :PROPERTIES: :LAST_REVIEWED: 2026-05-21 :END: Weekly full run to catch deprecated packages even without commits ** TODO [#B] Implement manual test trigger capability :PROPERTIES: :LAST_REVIEWED: 2026-05-21 :END: Allow on-demand test runs when automation is toggled off ** TODO [#B] Create test results dashboard/reporting :PROPERTIES: :LAST_REVIEWED: 2026-05-21 :END: Make test outcomes visible and actionable ** TODO [#B] Block merges to main if tests fail :PROPERTIES: :LAST_REVIEWED: 2026-05-21 :END: Enforce quality gate - broken changes don't enter main branch ** TODO [#B] Add network failure testing to test suite :PROPERTIES: :LAST_REVIEWED: 2026-05-21 :END: Simulate network disconnect mid-install to verify resilience ** TODO [#B] Keep container base images up to date :PROPERTIES: :LAST_REVIEWED: 2026-05-21 :END: Regular updates to Arch base image with review process and schedule ** TODO [#B] Persist test logs for historical analysis :PROPERTIES: :LAST_REVIEWED: 2026-05-21 :END: Archive logs with review process and schedule to identify failure patterns and trends ** TODO [#B] Implement automated deprecation detection :PROPERTIES: :LAST_REVIEWED: 2026-05-21 :END: Parse package warnings and repo metadata to catch upcoming deprecations proactively ** TODO [#B] Audit dotfiles/common directory :PROPERTIES: :LAST_REVIEWED: 2026-05-21 :END: *** TODO [#B] Review all 50+ scripts in ~/.local/bin - remove unused scripts *** TODO [#B] Check dotfiles for uninstalled packages - remove orphaned configs *** TODO [#B] Verify all stowed files are actually used ** TODO [#B] Test security + functionality together :PROPERTIES: :LAST_REVIEWED: 2026-05-21 :END: *** TODO [#B] Verify no unexpected open ports or services ** TODO [#B] Security audit tooling :PROPERTIES: :LAST_REVIEWED: 2026-05-21 :END: *** TODO [#B] Implement port scanning check *** TODO [#B] Create security posture verification script *** TODO [#B] Set up intrusion detection monitoring ** TODO [#B] Document threat model and mitigations within 6 months :PROPERTIES: :LAST_REVIEWED: 2026-05-21 :END: Identify attack vectors, what's mitigated, what remains ** TODO [#B] Verify package signature verification not bypassed by --noconfirm :PROPERTIES: :LAST_REVIEWED: 2026-05-21 :END: Packages installed with ~--noconfirm~ may skip signature checks AUR had issues previously requiring --noconfirm workaround - verify this doesn't compromise security Ensure package signatures are still verified despite --noconfirm flag ** TODO [#B] Test each modernization thoroughly before replacing :PROPERTIES: :LAST_REVIEWED: 2026-05-21 :END: Ensure new tools integrate with DWM environment and don't break workflow ** TODO [#B] Add NVIDIA preflight check for Hyprland :PROPERTIES: :LAST_REVIEWED: 2026-05-21 :END: Detect NVIDIA GPU and warn user about potential Wayland issues: - Require driver version 535+ or abort - Document required env vars (LIBVA_DRIVER_NAME, GBM_BACKEND, etc.) - Prompt to continue or abort if NVIDIA detected ** TODO [#C] Review theme config architecture for dunst/fuzzel :PROPERTIES: :LAST_REVIEWED: 2026-05-21 :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 the stowed locations at runtime, so edits to the common file get overwritten on theme switch. This split between stowed configs and theme templates is error-prone — changes must be made in both places. Consider: - Having set-theme be the single source of truth (remove common dunstrc from stow) - Or symlinking the stowed config to a theme-managed location - Same situation applies to fuzzel.ini The goal is a single place to edit each config, not two. ** TODO [#C] Monitor and optimize test execution time :PROPERTIES: :LAST_REVIEWED: 2026-05-21 :END: Keep test runs performant as installs and post-install tests grow (target < 2 hours) ** TODO [#C] Set up alerts for deprecated packages :PROPERTIES: :LAST_REVIEWED: 2026-05-21 :END: Proactive monitoring integrated with testing ** TODO [#C] Fix VM cloning machine-ID conflicts for parallel testing :PROPERTIES: :LAST_REVIEWED: 2026-05-21 :END: Currently using snapshot-based testing which works but limits to sequential test runs Cloned VMs fail to get DHCP/network even with machine-ID manipulation (truncate/remove) Root cause: Truncating /etc/machine-id breaks systemd/NetworkManager startup 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 [#C] Create security checklist for cafe/public wifi scenarios :PROPERTIES: :LAST_REVIEWED: 2026-05-21 :END: Practical guidelines for working in public spaces ** TODO [#C] Build security dashboard command :solo: :PROPERTIES: :LAST_REVIEWED: 2026-05-21 :END: Single command shows: encryption status, firewall status, open ports, running services ** VERIFY [#C] Evaluate modern CLI tool replacements :PROPERTIES: :LAST_REVIEWED: 2026-06-10 :END: Research done 2026-06-10, adoption decisions pending. Full report: [[file:docs/2026-06-10-modern-cli-tools-evaluation.org][docs/2026-06-10-modern-cli-tools-evaluation.org]]. Recommendation: adopt bat, dust, hyperfine, tealdeer, doggo (all in extra, all actively maintained); optional xh/jless/sd/ouch; nothing already-adopted has been superseded. Say which to install and I'll add them to archsetup + the machines. ** 2026-06-10 Wed @ 18:25:11 -0500 paru vs yay — evaluated, staying with yay 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. ** VERIFY [#C] Evaluate terminal emulator alternatives :PROPERTIES: :LAST_REVIEWED: 2026-06-10 :END: Research done 2026-06-10, your read pending. Full report: [[file:docs/2026-06-10-terminal-emulator-evaluation.org][docs/2026-06-10-terminal-emulator-evaluation.org]]. Recommendation: stay with foot — it wins on latency, ties on Wayland purity, fits the theme system, and stays healthy (1.26.0, Mar 2026). ghostty is the only real challenger (and the original ligature motivation favors it — foot still doesn't do ligatures), so the open question is whether ligatures matter enough to trade foot's latency edge. wezterm is effectively unmaintained (no release since Feb 2024). ** VERIFY [#C] Review file manager options for Wayland :PROPERTIES: :LAST_REVIEWED: 2026-06-10 :END: Research done 2026-06-10, adoption call pending. Full report: [[file:docs/2026-06-10-file-manager-evaluation.org][docs/2026-06-10-file-manager-evaluation.org]]. Recommendation: keep nautilus (only candidate that's Wayland-native, libadwaita-dark native, and actively developed); add yazi as the Wayland TUI (v26.5.6, monthly releases, sixel previews work in foot with zero scripting, zoxide built in — it has matured substantially since the problematic 2026-02 try). ranger upstream is effectively frozen (still 1.9.4, 700+ open issues), so porting it to the Wayland machines is the one option the evidence rules out. Original body's history preserved in git. ** TODO [#C] Review current tool pain points annually :PROPERTIES: :LAST_REVIEWED: 2026-05-21 :END: Once-yearly systematic inventory of known deficiencies and friction points in current toolset ** TODO [#D] Consider Customizing Hyprland Animations Current: windows pop in, scratchpads slide from bottom. Customizable animations: - windows / windowsOut / windowsMove - window open/close/move - fade - opacity changes - border / borderangle - border color and gradient angle - workspaces - workspace switching - specialWorkspace - scratchpads (currently slidevert) - layers - waybar, notifications, etc. Styles: slide, slidevert, popin X%, fade Parameters: animation = NAME, ON/OFF, SPEED, BEZIER, STYLE Speed: lower = faster (1-10 typical) Example tweaks: #+begin_src conf animation = windows, 1, 2, myBezier, popin 80% animation = workspaces, 1, 4, default, slide animation = fade, 1, 2, default animation = layers, 1, 2, default, fade #+end_src ** VERIFY [#D] Test wlogout menu on laptop Test wlogout exit menu on laptop to verify sizing works on different display. Current config uses fixed pixel margins - may need adjustment for laptop screen. ** TODO [#D] Parse and improve AUR error reporting Parse yay errors and provide specific, actionable fixes instead of generic error messages ** TODO [#D] Improve progress indicators throughout install Enhance existing indicators to show what's happening in real-time ** TODO [#C] Teach archsetup to stow the host tier :solo: :PROPERTIES: :LAST_REVIEWED: 2026-06-11 :END: Phase 5 of the per-host overrides spec, deferred from the 2026-06-11 implementation: the installer's stow calls in =user_customizations()= stow =common= + the DE package only. Add the host tier (=$(cat /etc/hostname)= at install time, or a conf key) guarded so a host without a tier is skipped with a message — same semantics as the dotfiles Makefile. Matters only for fresh installs of ratio/velox-named machines; the post-install =make stow= path already handles it. ** TODO Manual testing and validation *** velox per-host env applies after Hyprland restart What we're verifying: the velox tier's env lines (GDK_SCALE/QT_SCALE_FACTOR 1.5, XCURSOR_SIZE 36) only apply at Hyprland startup, and the foot font moved to host.ini — neither can be confirmed over ssh. - On velox, log out of Hyprland and back in (or reboot) - Open a foot window — text should render at 12pt (same as before the migration) - Launch Zoom (ideally from a browser link) — it should open at normal size with no per-app patch - Check the cursor isn't tiny on the HiDPI panel Expected: foot at 12pt, Zoom normally sized, cursor 36px — all from the velox tier, no local real files involved. *** Dupre Chrome theme renders correctly What we're verifying: the new Chrome theme's colors look right in a real browser — palette mapping can't be eyeballed from a manifest. - Open chrome://extensions in Chrome - Enable "Developer mode" (top right) - Click "Load unpacked" and select =~/code/archsetup/assets/color-themes/dupre/chrome-theme/= - Look at the window frame, toolbar, tab strip, and a new tab page Expected: near-black frame (#151311), dark toolbar/omnibox (#252321), gold links on the new-tab page, steel-gray inactive tab text — coherent with the rest of the dupre desktop. *** 2026-06-10 Wed @ 17:46:34 -0500 velox post-trim reboot verified; realtek firmware restored Craig rebooted velox (passphrase at console); checks ran over SSH after boot. Wifi connected, TLP active, graphics fine. One dmesg hit: r8152 failed to load rtl_nic/rtl8156b-2.fw — the Framework Ethernet expansion card (RTL8156B) is Realtek, so the trim list wrongly dropped linux-firmware-realtek (a Realtek laptop camera is on USB too). Reinstalled the package on velox (its hook rebuilt the initramfs) and removed realtek from archsetup's trim list. The driver worked even without the blob (internal-defaults fallback), so this was correctness, not breakage. * Archsetup Resolved ** 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= ([[file:~/.emacs.d/inbox/2026-06-12-1859-from-archsetup-org-capture-popup-frame-split.org][handoff note]], [[file:~/.emacs.d/inbox/2026-06-12-1859-from-archsetup-popup-crop.png][screenshot evidence]]). 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.