1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
|
#+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 =$(</etc/hostname)= (SC3034 bashism → =$(cat ...)=) and an unquoted =$interface_up= (SC2086 → quoted); =shellcheck init= now clean, =sh -n= passes. Suppressed the two =VM_IP= SC2034 warnings with documented =# shellcheck disable= directives (consumed by the sourced =validation.sh=, which shellcheck can't follow). 124 → 120; the remaining 120 are the triaged-acceptable set above.
*** 2026-05-20 Wed @ 06:32:17 -0500 Documented the testing process in the README
The README only covered the VM integration harness; the unit-test layer under =tests/= (Python =unittest=, fake-binary-on-PATH, one dir per script — =layout-navigate=, =tmux-util=) was undocumented. Added a =make test-unit= target that runs every =tests/*/test_*.py= suite explicitly (=unittest discover= can't find them — hyphenated dir names aren't valid package paths), then rewrote the README Testing section into "Unit tests" and "Integration tests (VM harness)" subsections, including how to add a suite for a new script. Updated Contributing to point at =make test-unit= for script changes. 61 unit tests pass via the new target.
*** 2026-05-20 Wed @ 18:22:42 -0400 Added safe_rm_rf guard on constructed-path deletes
Added a self-contained =safe_rm_rf <path> <allowed_prefix>= 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 <name>= 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.
|