#+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 ** 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. ** DOING [#A] Separate dotfiles from archsetup *** 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. ** DOING [#A] Prepare for GitHub open-source release Remove personal info, credentials, and code quality issues before publishing. *** 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 - =.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 =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 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 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. *** TODO [#A] Make claude-code installation optional Line 1781: =curl | sh= from claude.ai — should be behind a config flag. Not all users want AI tooling; curl-pipe-bash is a red flag for reviewers. *** DOING [#A] Add input validation for username and paths Variables like ~$username~, ~$source_dir~, and paths are not validated. Special characters or malicious input could break the script or cause security issues. Should validate inputs match expected patterns (alphanumeric, valid paths, etc.). **** 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 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 Mixed =[ "$var" = "true" ]= vs =$var= evaluation — pick one pattern. *** TODO [#A] Replace eval with safer alternatives Line 434: =eval "$cmd"= — use arrays or direct execution. ** TODO [#A] Review post-archsetup laptop setup steps (velox 2026-04-10) 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. *** TODO Review: pocketbook not installed Custom Python package from ~/code/pocketbook needed ~pip install --user -e .~ Data in ~/.local/share/pocketbook/ needed to be copied from ratio. Consider: document in post-install steps (not automatable in archsetup). *** 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 [#A] Ensure sleep/suspend works on laptops 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 [#A] Build CI/CD pipeline that runs archsetup on every commit Core automation infrastructure - enables continuous validation ** TODO [#B] Fix install errors surfaced by the 2026-05-11 VM test run 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 [#C] Investigate the 2026-05-11 VM-test warnings The 18:36 =make test= run passed (52/0/5) but raised 5 validation warnings. Each is investigated below with a recommendation. Most look like headless-VM / QEMU-slirp false positives the test harness should skip rather than archsetup bugs — but a couple have a real archsetup angle worth checking. Source: =test-results/20260511-183643/test.log= (WARN lines) and =scripts/testing/lib/validation.sh=. *** TODO [#C] Warning: Hyprland socket not found (Hyprland may not be running) =validate_hyprland_socket()= (=validation.sh:495=) looks for the Hyprland IPC socket and warns when it's absent. In the test VM that's expected — it's headless, nobody logs in graphically, Hyprland never starts, so there's no socket. Not an archsetup bug. Recommendation: harness fix in =validate_hyprland_socket()= — detect "no graphical session" (no =$WAYLAND_DISPLAY=/=$DISPLAY=, no graphical user in =loginctl=) and emit =validation_skip=/=validation_info= instead of =validation_warn=. Spinning up a real Hyprland session in the VM just to validate the socket is not worth it. *** TODO [#C] Warning: Could not query Settings portal (portal may not be running) =validate_portal_dark_mode()= (=validation.sh:506=) queries =org.freedesktop.portal.Desktop= for =color-scheme= and warns when it can't (portal not running). Two causes: (1) headless VM → =xdg-desktop-portal= isn't running; (2) the GNOME interface settings (=color-scheme=prefer-dark= etc.) didn't get written during install because =dconf write= failed (exit 1 — see the =[#B]= item above), so even with a portal there'd be nothing to read. Recommendation: (a) harness — skip the portal check in headless mode, same as the Hyprland-socket one; (b) the real fix is the =dconf write= exit-1 failure tracked in =[#B] Fix install errors= — once those writes succeed during install, the settings persist and the portal returns them on a real login (this is the "without these, portal-gtk waits ~50s for a settings proxy timeout" comment in =archsetup=). *** TODO [#C] Warning: mDNS ping failed (avahi may need time to propagate) =validate_avahi()= (=validation.sh:577=) checks =systemctl is-enabled avahi-daemon= (passes), then pings =.local= as a full-stack mDNS test (warns on failure). The VM uses QEMU user-mode networking (slirp, =10.0.2.x=), which doesn't pass multicast — so mDNS / =.local= resolution genuinely can't work there regardless of timing. The propagation-delay message is misleading. Recommendation: harness fix in =validate_avahi()= — when the VM is on slirp networking (detect the =10.0.2.x= address or the absence of multicast), skip the =.local= ping and keep only the =is-enabled= check; or downgrade the ping result to =validation_info=. Optionally also bump the ping timeout/retry for the bridged-networking case. Not an archsetup bug. *** TODO [#C] Warning: User lingering not enabled (syncthing may not autostart) =validation.sh:661= runs =loginctl show-user -p Linger= and warns if it isn't =yes=. archsetup *does* call =loginctl enable-linger "$username"= (=archsetup:1438= and =:1741=), and the install log shows =...enabling user-services lingering for cjennings @ 18:40:28= ran with no error — yet the check still says lingering is off. Likely the =logind=-unhappy-in-the-VM issue (the log-diff shows =logind: Failed to start session scope … Permission denied=) — =loginctl enable-linger= may return 0 but not actually create =/var/lib/systemd/linger/=, or the =show-user= query itself may be wrong while logind is degraded. Possibly a real concern *if* the same happens on bare metal; almost certainly a VM artifact otherwise. Recommendation: investigate with =make test-keep= — after a run, check =ls -l /var/lib/systemd/linger/cjennings= and =loginctl show-user cjennings -p Linger= on the VM. If the file exists but =loginctl= disagrees → logind/dbus health issue (cross-ref the logind errors in =[#B]=; the fix may be to ensure =systemd-logind= is healthy before =enable-linger=). If the file doesn't exist → =enable-linger= is silently no-op'ing in the VM; consider a fallback (=install -d /var/lib/systemd/linger && touch /var/lib/systemd/linger/$username=) or running it later in the install. Either way the =enable-linger= call in =archsetup= is wired correctly. *** TODO [#C] Warning: Docker enabled but not responding =validation.sh:791= does =systemctl is-enabled docker= (passes) then =docker info= (warns on failure). Root cause: =archsetup:1981= does =systemctl enable docker.service= — *enable*, not =enable --now= — so docker is enabled-on-boot but not started during install. The test runs validations immediately after archsetup with no reboot, so =docker info= fails because the daemon isn't running yet. That's expected, not a bug. (The =daemon.json= with =storage-driver: zfs= at =archsetup:1971-1979= is correctly gated on =is_zfs_root=, so on the btrfs test VM Docker auto-picks the =btrfs= driver — not the cause here.) Recommendation: harness fix in the docker check — treat "enabled, not running pre-reboot" as a pass/info, or =systemctl start docker= first and then run =docker info=. Separately, decide whether =archsetup= should =enable --now docker= for immediate use or keep enable-on-boot (most steps use =enable --now=; docker being the exception may be intentional given the daemon's weight — if so, leave it and just fix the validation). 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. ** TODO [#A] Generate recovery scripts from test failures Auto-create post-install fix scripts for failed packages - makes failures actionable ** TODO [#A] Create package inventory system *** 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 [#A] Establish monthly review workflow *** 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 [#A] Automate the inventory comparison Make package diff a runnable script instead of manual process ** TODO [#A] Complete security education within 3 months Read recommended resources to make informed security decisions (see metrics for Claude suggestions) ** TODO [#A] Prevent X termination and VT switching (security risk) 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 ** TODO [#B] All error messages should be actionable with recovery steps Currently just reports errors without guidance on how to fix them ** TODO [#B] Enable TLP power management for laptops TLP manages power-saving modes for Wi-Fi, USB, PCIe, Bluetooth, CPU scheduler Install tlp, enable service, add custom Framework 13 config to /etc/tlp.d/01-custom.conf Improves battery life and prevents power-related issues during install/post-install ** TODO [#B] Improve logging consistency 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 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 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 Weekly full run to catch deprecated packages even without commits ** TODO [#B] Implement manual test trigger capability Allow on-demand test runs when automation is toggled off ** TODO [#B] Create test results dashboard/reporting Make test outcomes visible and actionable ** TODO [#B] Block merges to main if tests fail Enforce quality gate - broken changes don't enter main branch ** TODO [#B] Add network failure testing to test suite Simulate network disconnect mid-install to verify resilience ** TODO [#B] Keep container base images up to date Regular updates to Arch base image with review process and schedule ** TODO [#B] Persist test logs for historical analysis Archive logs with review process and schedule to identify failure patterns and trends ** TODO [#B] Implement automated deprecation detection Parse package warnings and repo metadata to catch upcoming deprecations proactively ** TODO [#B] Audit dotfiles/common directory *** 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] Remove unnecessary linux-firmware packages (velox only) Remove firmware packages for hardware not present on Framework laptop. *NOTE:* This applies to Framework Laptop (velox), not Framework Desktop (ratio) Only needed: - linux-firmware-intel (CPU/GPU/Audio) - linux-firmware-atheros (WiFi) Can remove: - linux-firmware (meta-package) - linux-firmware-amdgpu - linux-firmware-broadcom - linux-firmware-cirrus - linux-firmware-mediatek - linux-firmware-nvidia - linux-firmware-other - linux-firmware-radeon - linux-firmware-realtek Disk space savings: ~600 MB After removal, update archsetup script to install only needed firmware packages. ** TODO [#B] Identify and replace packages no longer in repos Systematic check for availability issues ** TODO [#B] Verify package origin for all packages Ensure packages are installed from correct source (official repos vs AUR) - prevent installing from wrong place ** TODO [#B] Automate script usage tracking Parse shell history files for ~/.local/bin script names to identify last usage date and unused scripts ** TODO [#B] Automate dotfile validation Parse config files for binary/command references and verify those binaries exist - catch orphaned references ** TODO [#B] Test security + functionality together *** TODO [#B] Verify no unexpected open ports or services ** TODO [#B] Security audit tooling *** 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 Identify attack vectors, what's mitigated, what remains ** TODO [#B] Verify package signature verification not bypassed by --noconfirm 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] Document evaluation criteria and trade-offs Establish clear process for tool evaluation decisions ** TODO [#B] Test each modernization thoroughly before replacing Ensure new tools integrate with DWM environment and don't break workflow ** TODO [#B] Add Rust installation via rustup instead of pacman package 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 ** TODO [#B] Add NVIDIA preflight check for Hyprland 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 [#B] Add org-capture popup frame on keyboard shortcut Set up a quick-capture popup using emacsclient that opens a small floating org-capture frame, with Hyprland window rules to float, size, and center it. Frame should auto-close on finalize (C-c C-c) or abort (C-c C-k). Implementation: 1. Create =~/.local/bin/quick-capture= script: - =emacsclient -c -F '((name . "org-capture") (width . 80) (height . 20))' -e '(org-capture)'= - Requires Emacs daemon running (already configured via systemd) 2. Add Hyprland window rules to =hyprland.conf=: - =windowrulev2 = float, title:^(org-capture)$= - =windowrulev2 = size 800 400, title:^(org-capture)$= - =windowrulev2 = center, title:^(org-capture)$= - =windowrulev2 = stayfocused, title:^(org-capture)$= 3. Add keybind in =hyprland.conf= (choose available key combo) 4. Add Elisp hook to auto-delete the frame after capture: =(defun my/org-capture-delete-frame () (when (equal (frame-parameter nil 'name) "org-capture") (delete-frame))) (add-hook 'org-capture-after-finalize-hook #'my/org-capture-delete-frame)= 5. Notes go directly into existing org capture templates — zero new infrastructure Reference: Protesilaos Stavrou's popup frame pattern for emacsclient. ** TODO [#C] Review theme config architecture for dunst/fuzzel 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] Create Chrome theme with dupre colors Create a Chrome browser theme using the dupre color palette. ** TODO [#C] Monitor and optimize test execution time Keep test runs performant as installs and post-install tests grow (target < 2 hours) ** TODO [#C] Set up alerts for deprecated packages Proactive monitoring integrated with testing ** TODO [#C] Fix VM cloning machine-ID conflicts for parallel testing 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 Practical guidelines for working in public spaces ** TODO [#C] Build security dashboard command Single command shows: encryption status, firewall status, open ports, running services ** TODO [#C] Evaluate modern CLI tool replacements bat, eza, zoxide, dust, ripgrep-all - only adopt if clear friction reduction ** TODO [#C] Consider paru instead of yay Evaluate if paru offers meaningful improvements for AUR management ** TODO [#C] Evaluate terminal emulator alternatives ghostty for ligature support - addresses known deficiency ** TODO [#C] Review file manager options for Wayland Ranger image previews don't work in foot terminal (Wayland). Ranger's kitty graphics method checks TERM for "kitty" string, and foot's kitty protocol implementation has subtle incompatibilities that cause hangs. ueberzug is X11-only. Tried yazi (2026-02) - theming/icon color customization was problematic. Revisit later when yazi matures or try lf with custom preview scripts. Keep ranger for DWM/X11 where ueberzug works fine. ** TODO [#C] Review current tool pain points annually Once-yearly systematic inventory of known deficiencies and friction points in current toolset ** TODO [#C] Install Zoxide integration into Ranger https://github.com/jchook/ranger-zoxide - enables zoxide jumping within ranger file manager ** 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 [#D] Add retry logic to git_install function pacman_install and aur_install have retry logic, but git_install doesn't ** TODO [#D] Add cpupower installation and enabling to archsetup cpupower service configures the default CPU scheduler (powersave or performance) Install cpupower, configure /etc/default/cpupower, enable service: ~systemctl enable --now cpupower.service~ * 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.