diff options
Diffstat (limited to 'docs/design')
| -rw-r--r-- | docs/design/2026-07-02-bluetooth-panel-spec.org | 470 | ||||
| -rw-r--r-- | docs/design/2026-07-02-desktop-settings-panel-spec.org | 128 | ||||
| -rw-r--r-- | docs/design/2026-07-02-file-manager-swallow-spec.org | 141 | ||||
| -rw-r--r-- | docs/design/2026-07-02-net-panel-other-interfaces-spec.org | 189 | ||||
| -rw-r--r-- | docs/design/2026-07-02-timer-panel-spec.org | 113 | ||||
| -rw-r--r-- | docs/design/2026-07-03-instrument-console-panels-spec.org | 158 | ||||
| -rw-r--r-- | docs/design/maintenance-console-design-ideas.org | 527 | ||||
| -rw-r--r-- | docs/design/system-monitor-design-ideas.org | 1008 |
8 files changed, 1535 insertions, 1199 deletions
diff --git a/docs/design/2026-07-02-bluetooth-panel-spec.org b/docs/design/2026-07-02-bluetooth-panel-spec.org deleted file mode 100644 index 121197a..0000000 --- a/docs/design/2026-07-02-bluetooth-panel-spec.org +++ /dev/null @@ -1,470 +0,0 @@ -#+TITLE: Bluetooth Panel — CLI-Driven, Net-Panel Kin -#+AUTHOR: Craig Jennings -#+DATE: 2026-07-02 -#+TODO: TODO | DONE -#+TODO: DRAFT READY DOING | IMPLEMENTED SUPERSEDED CANCELLED - -* IMPLEMENTED Status -:PROPERTIES: -:ID: 1271a845-4463-4831-9902-990eda6b2265 -:END: -- [2026-07-02 Thu] IMPLEMENTED — all five phases shipped the same day - (dotfiles eb2230f / 76b2c05 / e372de3 / 2a026b1; archsetup d8d8c53): - engine, panel, bar module + blueman retirement, bt-priv + package swap, - install wiring proven by VM assertions. 43 dotfiles suites green, both - AT-SPI smokes green, panels verified live; the phase 4-5 VM assertions - run on the next VM pass. -- [2026-07-02 Thu] DOING — spec-response decomposed the five phases into - build sub-tasks under the todo.org parent (:SPEC_ID: bound); build - started same day per Craig ("4 first, then 1" — bugs then bluetooth). -- [2026-07-02 Thu] READY — spec-review passed the gate: all four - decisions resolved, phases decomposable, CLI verbs verified against - bluez 5.86. Two non-blocking findings recorded and dispositioned in - the same pass (donor-pattern answers). -- [2026-07-02 Thu] DRAFT — initial spec from Craig's request: a bluetooth - module driving a CLI underneath, consistent with the net panel, minimal - interface, full functionality, diagnostics section, visual mockups. - -* Metadata - -| Field | Value | -|--------+---------------------------------------------------------------------------------| -| Status | implemented | -|--------+---------------------------------------------------------------------------------| -| Owner | Craig Jennings | -|--------+---------------------------------------------------------------------------------| -| Repo | dotfiles (bt module); archsetup (packages, sudoers, keybind defaults) | -|--------+---------------------------------------------------------------------------------| -| Kin | net panel (architecture donor), desktop-settings panel (same donor, shared css) | -|--------+---------------------------------------------------------------------------------| - -* Problem - -Bluetooth on both daily drivers runs through blueman: a tray applet plus a -GTK3 manager window (Super+Shift+B). It's the odd one out on the desktop — -a foreign visual style next to the dupre-themed panels, a tray icon where -every other indicator is a first-class waybar module, and no diagnostics -story at all. When the BT mouse fails to reconnect at boot (a recurring -gotcha — touchpad-auto exists because of it) or headphones pair but route -no audio, the fix is a terminal séance: bluetoothctl, rfkill, systemctl, -wpctl, in whatever order folklore suggests. - -The net panel proved the shape that fixes this: a minimal layer-shell -popup over a GTK-free engine that drives a CLI, with a diagnostics tab -that names the failure and offers the repair. Bluetooth is the same -problem with a smaller surface: one adapter, a handful of devices, a -short list of well-known failure modes. - -* Goals - -1. Visibility: adapter power state and every known device with live state - (connected, battery, signal) in one glance — panel and bar module agree. -2. Control: power, scan, pair, connect, disconnect, forget — full - functionality from the panel, zero terminals (the net panel's V2 - contract). -3. Diagnostics: a doctor that walks the known failure chain (adapter → - rfkill → service → power → device → audio profile), names the broken - link in evidence rows, and offers tiered repairs. -4. Consistency: same stack, same window shape, same interaction grammar, - same palette as the net panel. A user who knows one panel knows both. - -Audio-profile switching is in scope for v1 (Craig, 2026-07-02 — "bitten -by this too many times to count"): the doctor's audio-profile step -carries a one-click repair, not just a diagnosis, and connected audio -devices surface their active profile (details in the doctor chain below). - -Non-goals (this iteration): OBEX file transfer, multi-adapter support -(both machines have one controller), BLE sensor/GATT browsing. - -* Design sketch - -** Architecture — the net panel's stack, verbatim - -- GTK4 + gtk4-layer-shell, Blueprint .blp compiled to committed .ui - (=make ui=), PyGObject at runtime. -- Humble-object split: GTK-free =PanelModel= presenter (unit-tested like - net's), thin composite-widget pages, =bg(work, done)= worker-thread - helper for every slow call. -- Engine: a new =bt= package in dotfiles (=bluetooth/src/bt/=, sibling of - =net/=), CLI entry =bt= with =bt status= / =bt panel= / =bt doctor= — - the same cmd/cli layout as net. -- Layer-shell OVERLAY popup anchored TOP+RIGHT, 380x520, Esc closes, - focus-out auto-hides, single-instance toggle via a =bt-panel= wrapper. - Dupre palette css shared with the net panel (the factored css asset the - desktop-settings spec calls for — three consumers now, so the factoring - happens in this project's phase 1 if settings hasn't landed it). -- Testing: engine TDD with fake binaries on a temp PATH (fake-bluetoothctl, - fake-rfkill, fake-systemctl, fake-wpctl); PanelModel unit suite; one - gated AT-SPI smoke (=make test-panel= pattern). - -** CLI backing — bluetoothctl one-shot verbs - -bluez 5.86 (installed) supports everything non-interactive: - -- Adapter: =bluetoothctl show= (powered, discoverable, pairable), - =bluetoothctl power on|off=. -- Device lists: =bluetoothctl devices Paired|Connected|Trusted= — the - Paired view is a merge of Paired + Connected states; =bluetoothctl info - <mac>= per row fills caption detail (battery percentage rides bluez's - built-in Battery1 profile and appears in info output; RSSI appears - during discovery). -- Scan: =bluetoothctl --timeout N scan on= (bounded discovery burst), - then =devices= diffed against Paired for the Nearby list. The panel - scans in 8s bursts with a live "Scanning…" state rather than an - unbounded scan. -- Connect/disconnect/forget: =bluetoothctl connect|disconnect|remove <mac>=. -- Pairing: the one interactive corner. =bluetoothctl pair <mac>= can demand - a passkey confirmation. The engine drives bluetoothctl's line protocol - over a pty with a bounded state machine (expect "Confirm passkey", - reply yes/no); a passkey prompt surfaces as a panel dialog showing the - six digits, mirroring the net panel's password dialog. NoInputNoOutput - devices (mice, most headphones) sail through without the dialog. -- rfkill: the user is in the =rfkill= group, so block/unblock is - unprivileged (=rfkill unblock bluetooth=). -- Privileged path: exactly one verb needs root — =systemctl restart - bluetooth= — so =bt-priv= is a one-verb closed helper with its own - NOPASSWD sudoers rule placed by archsetup, cloning net-priv's - regex-validated pattern rather than widening net-priv's scope. - -** Panel anatomy - -Two tabs. Devices is the panel; Diagnostics is the escape hatch. - -Devices tab, Paired sub-view (the default — daily use is reconnecting -known devices, not discovering new ones): - -#+begin_example -╭──────────────────────────────────────────────╮ -│ [ Devices ] [ Diagnostics ] │ ← top switcher -│ │ -│ Bluetooth ●──○ hci0 on │ ← adapter row: power switch -│ ──────────────────────────────────────────── │ -│ [ Paired ] [ Nearby ] │ ← sub-view switcher -│ ┌──────────────────────────────────────────┐ │ -│ │ MX Master 3 │ │ -│ │ Connected · battery 80% │ │ -│ │ WH-1000XM4 │ │ -│ │ Paired, not connected │ │ -│ │ K380 Keyboard │ │ -│ │ Paired, not connected │ │ -│ │ │ │ -│ └──────────────────────────────────────────┘ │ -│ [ Disconnect ] [ Forget ] │ ← acts on selected row -╰──────────────────────────────────────────────╯ -#+end_example - -The primary button is one control with a state-following label: -"Connect" when the selection is disconnected (suggested-action styling), -"Disconnect" when connected. Row-activate (Enter / double-click) -connects — never disconnects — matching the net panel's asymmetry. -Captions carry the human state line; the MAC lives in the row tooltip, -not the visible caption. - -Devices tab, Nearby sub-view: - -#+begin_example -╭──────────────────────────────────────────────╮ -│ [ Devices ] [ Diagnostics ] │ -│ │ -│ Bluetooth ●──○ hci0 on │ -│ ──────────────────────────────────────────── │ -│ [ Paired ] [ Nearby ] │ -│ ┌──────────────────────────────────────────┐ │ -│ │ Scanning… (6s) │ │ ← overlay state label -│ │ JBL Flip 6 −58 dBm │ │ -│ │ Pixel 9 −71 dBm │ │ -│ │ (unnamed) 74:A5:… −83 dBm │ │ -│ └──────────────────────────────────────────┘ │ -│ [ Pair ] [ Rescan ] [ Discoverable ⊙ ] │ -╰──────────────────────────────────────────────╯ -#+end_example - -Pair does the whole intended thing — pair, then trust, then connect — -because pairing a device means "use it now and reconnect on its own -later" (decision below). Discoverable is a toggle for the inbound case -(pairing a phone TO the laptop), off by default, auto-off with bluez's -discoverable-timeout. Rows sort by RSSI, strongest first; named devices -above unnamed ones. - -Diagnostics tab (mirrors the net panel's shape: one big verb + streaming -evidence rows + tiered repairs behind confirmation): - -#+begin_example -╭──────────────────────────────────────────────╮ -│ [ Devices ] [ Diagnostics ] │ -│ │ -│ [ Get Bluetooth Working ] [ Advanced ▸]│ -│ ┌──────────────────────────────────────────┐ │ -│ │ ✓ Adapter present (hci0) │ │ -│ │ ✓ Not blocked (rfkill clear) │ │ -│ │ ✓ bluetooth.service active │ │ -│ │ ✓ Adapter powered │ │ -│ │ ✗ MX Master 3: paired but unreachable │ │ -│ │ … Re-pair suggested — see below │ │ -│ │ │ │ -│ │ Fix: [ Reconnect ] [ Re-pair device ] │ │ -│ └──────────────────────────────────────────┘ │ -│ power-cycle · restart service · unblock │ ← tiered repairs (confirm) -╰──────────────────────────────────────────────╯ -#+end_example - -The doctor chain, in order, each an evidence row: - -1. Adapter present — =bluetoothctl list= / rfkill has an hci entry. - Absent → hardware/driver verdict, no repair offered. -2. rfkill state — soft-blocked names the likely cause when the - airplane-mode state file says airplane is on ("Blocked by airplane - mode — turn airplane mode off"), otherwise offers Unblock (no root - needed, rfkill group). -3. bluetooth.service — inactive/failed → offer restart (the one bt-priv - verb), evidence quotes the last journal line. -4. Adapter powered — off → offer power on (and note if a boot-time - policy keeps turning it off). -5. Per-device reachability — paired-but-connect-fails distinguishes - "device off/out of range" (RSSI absent in a scan burst) from "bond - corrupt" (connect error string), and only the latter suggests the - re-pair repair (remove + pair + trust + connect, confirmed first — - it's the destructive tier). -6. Audio profile (audio devices only) — device connected but no wpctl - sink/source, or the card stuck in HSP/HFP when A2DP is expected: - evidence names the active profile and offers the repair inline — - "Switch to A2DP" drives =wpctl set-profile <card> <index>= (profile - inventory from =pw-dump= — ground truth 2026-07-02: wpctl can't - enumerate a card's profiles, and the card's =bluez5.profile= prop - reads "off" mid-stream; the card's Profile param and the sink node's - =api.bluez5.profile= are authoritative), verifies the sink came back - in the expected profile, and reports fixed or no-change. In v1 per - Craig (2026-07-02): this failure mode has bitten repeatedly, so it - gets the one-click fix, not just a diagnosis. Connected audio-device - row captions also show the profile when it's the degraded one - ("Connected · mic mode (HSP)") so the state is visible before the - doctor runs. - -Repairs confirm with the net panel's future-tense scope copy ("This will -restart the Bluetooth service. Connected devices will drop and -reconnect."), run on the worker thread, verify after (re-read state, -report "fixed" or "no change"), and never chain silently. - -** Bar module - -=custom/bluetooth= replacing the blueman-applet tray icon: the panel's -glanceable layer, one glyph, state-following like =custom/net=: - -#+begin_example - off / blocked (dim; red slash variant when rfkill-blocked) - on, nothing connected (dim) - connected (white; tooltip lists devices + battery) -#+end_example - -Tooltip carries device names, battery percentages, and the keybind hints -(the module-tooltip convention shipped 2026-07-02). Click opens the -panel (=bt-panel= toggle wrapper); the existing Super+Shift+B bind moves -from blueman-manager to =bt panel=. Low-battery on a connected device -(<15%) adds a red percentage to the glyph text — the mouse dying -mid-meeting is the one state worth surfacing unprompted. - -** UX conformance notes - -Named against the heuristics the panel family follows (Nielsen's ten, -plus the rulesets patterns catalog): - -- Visibility of status: live captions, scan countdown, elapsed ticker on - long ops, verify-after-repair rows. -- Match to the real world: device-kind glyphs + plain state lines; MACs - demoted to tooltips; "Forget" not "Remove bond". -- User control: Esc closes, Rescan is idempotent, scan bursts are - bounded, repairs confirm, running ops show a Stop where stoppable. -- Consistency: interaction grammar is the net panel's — same switcher - layout, same primary-button contract, same confirm copy shape. -- Error prevention: Forget and Re-pair confirm; power-off while devices - are connected states the consequence in the confirm body. -- Recognition over recall: every action is a visible button; no context - menus, no hidden gestures (transient-state-buttons pattern). -- Minimalism: two tabs, one primary action per view, detail behind - tooltips and the Advanced reveal. -- Help users recover: the doctor's evidence rows name the broken link - and carry the repair inline (default-most-common-friction-proportional: - the likely fix is one click, the destructive one is confirmed). - -Tension found with the net panel while writing this (filed as todo.org -tasks per Craig's instruction, 2026-07-02): transient error toasts -auto-dismiss in 4s, and the V2 spec's keyboard-navigation claims -(tab-between-sections, arrow rows, type-to-filter) aren't verifiably -implemented. Both filed against the net panel rather than cloned here; -this panel adopts whatever resolution those tasks land on. - -* Decisions (Craig) [4/4] - -** DONE Pair implies trust + connect? -CLOSED: [2026-07-02 Thu] -Decided (Craig, 2026-07-02): yes — one Pair verb does pair → trust → -connect. A device that shouldn't auto-reconnect gets untrusted later; a -per-device auto-reconnect toggle can ride a later pass. - -** DONE Retire blueman entirely? -CLOSED: [2026-07-02 Thu] -Decided (Craig, 2026-07-02): drop it outright, no bake-in period — the -package leaves archsetup and both machines once phase 2 lands, -bluetoothctl stays as the terminal fallback. Craig's framing: any issue -after retirement is a signal the doctor needs another check or the panel -has a real bug, and it gets fixed there rather than papered over by -keeping blueman around. - -** DONE Battery in the row caption or tooltip only? -CLOSED: [2026-07-02 Thu] -Approved (Craig, 2026-07-02): caption when the device reports it -("Connected · battery 80%"), tooltip otherwise. - -** DONE Scan burst length and auto-rescan? -CLOSED: [2026-07-02 Thu] -Approved (Craig, 2026-07-02): 8s bursts, no auto-repeat — Rescan stays -explicit, matching the net panel's Available view. - -* Review findings [2/2] - -** DONE Empty-state and no-adapter presentation copy undefined :nonblocking: -CLOSED: [2026-07-02 Thu] -The mockups show populated lists; the spec didn't say what an empty Paired -list, an empty post-scan Nearby list, or a machine with no adapter shows -in the panel and on the bar glyph. Dispositioned same pass: clone the -donor — the net panel's in-box overlay message pattern (=show_loading= / -placeholder label) carries the copy. Paired empty: "No paired devices — -switch to Nearby to pair one." Nearby post-scan empty: "Nothing found — -Rescan, or make the device discoverable." No adapter: adapter row reads -"No Bluetooth adapter", Devices controls disable, Diagnostics stays -usable (the doctor's step 1 names the hardware/driver verdict); bar -glyph shows the off/blocked state. Non-blocking; recorded so the -implementer doesn't invent copy mid-build. - -** DONE Logging/redaction carry-over unstated :nonblocking: -CLOSED: [2026-07-02 Thu] -The spec says "the net panel's stack, verbatim" but didn't name whether -the engine adopts net's =eventlog= (structured op log) and =redact= -(sensitive-field scrubbing) modules. Dispositioned same pass: yes, both -carry over — every mutating verb (pair/connect/forget/repair) logs an -eventlog entry, and MACs are the redaction surface (device names stay, -MACs redact in copied reports, mirroring net's report redaction). -Non-blocking; it's the donor default made explicit. - -* Implementation phases - -1. Engine =bt= package: adapter/device/scan probes over fake-bluetoothctl, - status + doctor chain (rfkill, service, powered, reachability, audio - profile probe + A2DP switch repair over fake-wpctl) — pure TDD, no - GTK. =bt status= and =bt doctor= work from a terminal. Shared dupre - css factored to the common asset if the settings panel hasn't already - done it. -2. Panel: PanelModel presenter + Blueprint pages (Devices with - Paired/Nearby, Diagnostics), worker-thread wiring, pairing-dialog - state machine, bt-panel toggle wrapper, AT-SPI smoke. Super+Shift+B - rebind. -3. Bar module =custom/bluetooth= (glyph states, tooltip, low-battery - surface, refresh signal), waybar config + suite coverage; blueman - retirement per the decision. -4. bt-priv one-verb helper + sudoers rule in archsetup; package-list - swap (blueman out per decision, bluez-utils stays); VM test - assertions. -5. archsetup keybind/config defaults so a fresh install lands the panel - wired (waybar module present, bind set, sudoers placed). - -* Review and iteration history - -** 2026-07-02 Thu @ 15:19:58 -0400 — Claude Code (archsetup) — phase 5 builder, spec closed -- *What changed or was recommended:* Phase 5 shipped and the spec flipped - to IMPLEMENTED. No new install code was needed — the waybar module, the - =Super+Shift+B= bind, and the shared panel css all ride the dotfiles - hyprland tier that a fresh install already clones and stows, and sudoers - is covered by the blanket grant. The phase's substance is proof: - =test_desktop.py= gained hyprland-gated assertions for the four stowed - bt bins, the =custom/bluetooth= waybar entry, the =bt-panel= keybind, - and the stowed =panel.css=. -- *Why:* Final phase of the DOING decomposition; with it the todo parent - closed and the lifecycle keyword flipped with a history line. -- *Artifacts:* archsetup =scripts/testing/tests/test_desktop.py=; todo.org - parent DONE + dated phase 5 / test-surface entries; this spec's Status - heading. - -** 2026-07-02 Thu @ 15:16:51 -0400 — Claude Code (archsetup) — phase 4 builder -- *What changed or was recommended:* Phase 4 shipped. Dotfiles =2a026b1=: - the stowed =bt-priv= shim (one verb, verified against the fake-systemctl) - and the sxhkd =Super+Shift+B= bind repointed from blueman-manager to - =st -e bluetoothctl= (terminal fallback per the retirement decision — the - panel is Wayland-only). archsetup: blueman dropped from the - =desktop_environment= package loop; VM assertions added (bluez/bluez-utils - present, blueman absent). blueman also removed live from velox. -- *Why:* Build order per the DOING decomposition. The spec's "sudoers rule" - item resolved as net-priv's did: archsetup already grants the primary - user blanket =NOPASSWD: ALL= (archsetup:1089), so a narrow bt-priv rule - would be dead config — no new sudoers needed, and phase 5's "sudoers - placed" is satisfied by the existing grant. -- *Artifacts:* dotfiles =hyprland/.local/bin/bt-priv=, - =common/.config/sxhkd/sxhkdrc=; archsetup =archsetup= (bluetooth loop), - =scripts/testing/tests/test_packages.py=; dated phase 4 entry under the - todo.org parent. - -** 2026-07-02 Thu @ 15:06:00 -0400 — Claude Code (archsetup) — phase 3 builder -- *What changed or was recommended:* Phase 3 shipped (dotfiles =e372de3=): - the =custom/bluetooth= bar module (state-following glyph, low-battery red - percentage, device+battery tooltip with the keybind hint, signal 10 with - the panel poking it after each reload) and the blueman retirement from the - Hyprland session (exec-once + windowrules removed, applet killed live). - The phase 2 deferred items also closed this pass: both AT-SPI smokes green - (the bt smoke's primary-button assertion fixed for the state-following - label, =c1a8219=), both panels eyeballed correct in dupre, and the - net-panel keyboard claims verified live (archsetup =e80df2b= — false - claims struck from the net spec). -- *Why:* Build order per the DOING decomposition; the Zoom meeting ended, - unblocking the visual work. Phases 4-5 (bt-priv/sudoers/packages, install - defaults — archsetup side) remain. -- *Artifacts:* dotfiles =bluetooth/src/bt/indicator.py=, =waybar-bt=, - waybar config + three css files; dated phase 3 entry under the todo.org - parent. - -** 2026-07-02 Thu @ 14:15:27 -0400 — Claude Code (archsetup) — phase 2 builder -- *What changed or was recommended:* Phase 2 shipped (dotfiles =76b2c05=): - the GTK panel — PanelModel/viewmodel presenter pair (69 tests), Blueprint - pages, pairing pty state machine with default-deny passkey confirms, - manage.py op envelopes shared by CLI and panel (power + discoverable verbs - added), =bt-panel= toggle, Super+Shift+B rebind. The shared dupre css - factoring landed as planned: net's inline =_CSS= became - =themes/dupre/panel.css= with =dupre-*= classes, both panels consume it. - 43 suites green. The AT-SPI smoke (=make test-panel-bt=) is written but - not yet run live — a Zoom meeting occupied the compositor; it runs when - the meeting ends, along with a visual check of both panels. -- *Why:* Build order per the DOING decomposition; phases 3-5 (bar module, - bt-priv/sudoers, install defaults) remain. -- *Artifacts:* dotfiles =bluetooth/src/bt/{panel,viewmodel,pairing,manage, - gui,pages}.py=, =ui/*.blp=, =tests/bt/test_btpanel.py=, the panel smoke; - dated phase 2 entry under the todo.org parent. - -** 2026-07-02 Thu @ 13:31:00 -0400 — Claude Code (archsetup) — phase 1 builder -- *What changed or was recommended:* Phase 1 shipped (dotfiles =eb2230f=): - the =bt= engine package, 101 tests over fakes, live-verified read-only - on velox. Two spec corrections from ground truth: profile inventory - comes from =pw-dump= (wpctl can't enumerate profiles), and the active - profile reads from the card's Profile param / sink's - =api.bluez5.profile= (the card's =bluez5.profile= prop is unreliable). - The shared-css factoring moved into phase 2 — net's css is an inline - string in its =gui.py=, so extracting it belongs with the first second - consumer rather than as a standalone poke at the working net panel. -- *Why:* Build order per the DOING decomposition; corrections keep the - spec honest for the phase 2 implementer. -- *Artifacts:* dotfiles =bluetooth/src/bt/=, =tests/bt/=, the stowed - =bt= shim; dated phase 1 entry under the todo.org parent. - -** 2026-07-02 Thu @ 13:10:00 -0400 — Claude Code (archsetup) — reviewer + responder -- *What changed or was recommended:* Ran the spec-review gate: passed. - All four decisions were already DONE (cookie added to the heading); - the five phases are each a clean single-session stop; CLI verbs are - verified against installed bluez 5.86. Two non-blocking findings - recorded and dispositioned in the same fused pass (empty-state / - no-adapter copy, eventlog + redaction carry-over) — both resolve to - "clone the net-panel donor," now stated explicitly. Flipped DRAFT → - READY → DOING and decomposed the phases into build sub-tasks under the - todo.org parent with :SPEC_ID: bound. -- *Why:* Craig queued the build ("4 first, then 1", 2026-07-02) after - resolving all decisions the same morning; the gate held nothing back, - so review and response fused to keep the speedrun moving. -- *Artifacts:* Findings in =* Review findings [2/2]= above; build parent - in todo.org ("Bluetooth panel + bar module"); net-panel toast fix the - UX-conformance note references landed as dotfiles =0f017d4=. diff --git a/docs/design/2026-07-02-desktop-settings-panel-spec.org b/docs/design/2026-07-02-desktop-settings-panel-spec.org deleted file mode 100644 index 8becf71..0000000 --- a/docs/design/2026-07-02-desktop-settings-panel-spec.org +++ /dev/null @@ -1,128 +0,0 @@ -#+TITLE: Desktop-Settings Dropdown Panel -#+AUTHOR: Craig Jennings -#+DATE: 2026-07-02 -#+TODO: TODO | DONE -#+TODO: DRAFT READY DOING | IMPLEMENTED SUPERSEDED CANCELLED - -* DRAFT Status -:PROPERTIES: -:ID: fb7eec22-a214-4568-82c4-903612f4832f -:END: -- [2026-07-02 Thu] DRAFT — initial spec from the todo.org task "Desktop-settings - dropdown panel" (2026-06-24 review), updated for the Blueprint/GTK4 pipeline - the net panel stood up 2026-07-01. - -* Metadata - -| Field | Value | -|--------+----------------------------------------------| -| Status | draft | -|--------+----------------------------------------------| -| Owner | Craig Jennings | -|--------+----------------------------------------------| -| Repo | dotfiles | -|--------+----------------------------------------------| -| Kin | net panel (architecture donor), theme studio | -|--------+----------------------------------------------| - -* Problem - -Desktop toggles are scattered: dim, caffeine/idle, touchpad/mouse, airplane -mode each own a bar module and a keybind; brightness and keyboard-backlight -have keybinds but no visible control or level readout. The bar is running out -of glanceable width (hence the collapse arrows), and sliders can't live in -waybar at all. One settings dropdown — a gear glyph opening a small panel — -gathers them. - -* Goals - -1. One panel with every desktop toggle + slider: auto-dim, idle/caffeine, - touchpad, mouse, airplane (laptop-only), screen brightness, keyboard - backlight. -2. Conditional rows appear only when the hardware/context applies (mouse - present, trackpad present, battery present) — reuse the detection the - airplane/touchpad indicators already do. -3. Every control reflects live state and verifies its action took (the net - panel's verify-everything contract). -4. Bar stays the quick layer: which standalone indicators survive is a - decision below. - -* Design sketch - -** Architecture — clone the net panel's proven stack - -- GTK4 + gtk4-layer-shell, Blueprint .blp sources compiled to committed .ui - (=make ui=; dev-only build dependency, fresh clones run without the - compiler). -- Humble-object split: a GTK-free PanelModel presenter (unit-tested to 100% - like the net PanelModel) + thin composite-widget pages. Backing actions in - a GTK-free settings.py that shells out to brightnessctl / hyprctl / the - existing toggle scripts, TDD'd with fake binaries like every dotfiles - suite. -- One gated AT-SPI smoke (the run-panel-smoke.sh pattern), no bespoke - headless widget suite. -- Dupre WIP palette CSS, shared with the net panel — factor the palette - block into a common css asset both panels load rather than duplicating - (feeds the theme-studio task later). - -** Controls and their backings - -| Control | Backing | -|--------------------+----------------------------------------------| -| Auto-dim toggle | hyprctl decoration:dim_inactive (dim-toggle) | -|--------------------+----------------------------------------------| -| Idle / caffeine | hypridle start/stop (caffeine-toggle) | -|--------------------+----------------------------------------------| -| Touchpad toggle | toggle-touchpad + touchpad-state file | -|--------------------+----------------------------------------------| -| Mouse toggle | same mechanism, mouse-state file | -|--------------------+----------------------------------------------| -| Airplane mode | airplane-mode script (laptop-only row) | -|--------------------+----------------------------------------------| -| Screen brightness | brightnessctl (backlight class), slider + % | -|--------------------+----------------------------------------------| -| Keyboard backlight | brightnessctl (kbd_backlight class), slider | -|--------------------+----------------------------------------------| - -Slider changes apply live (throttled) and read back the actual level after -apply — verify-everything. Toggles re-read their source of truth after -firing, same as the bar indicators do, and the bar modules get their refresh -signals so both surfaces agree. - -** Open/close behavior - -Gear glyph module on the bar right cluster; click toggles the panel -(layer-shell anchored under the bar, right-aligned). Focus-out auto-hide + -Close button, matching the net panel. Keybind decision below. - -* Decisions (Craig) - -** TODO Which standalone bar indicators collapse into the panel? -Options per module (dim, touchpad, caffeine): keep on bar + mirrored in -panel; or panel-only (frees bar width). Recommendation: keep touchpad and -caffeine visible on the bar (state you glance at), move dim into the panel -(you set it rarely), keep airplane where it is. - -** TODO Keybind for the panel? -Super+Shift+G (gear) is free. Or no keybind — mouse-only surface. - -** TODO Where does the code live? -Recommendation: dotfiles =settings/= sibling to =net/= (same src-layout, -tests in tests/settings/), sharing the palette css. In-tree pocketbook-style -was the old note; the net panel is the better donor now. - -** TODO Slider granularity and floor -brightnessctl exposes 0-100%; a 5% floor stops "screen went black in a dark -room" lockouts. Confirm the floor (or allow 0 with a long-press escape -hatch). - -* Implementation phases - -1. settings.py backings (brightness get/set, kbd backlight, toggle - state readers) — pure engine, TDD with fake brightnessctl/hyprctl. -2. PanelModel presenter (rows, conditional visibility, verify-after-apply - semantics) — unit-tested, no GTK. -3. Blueprint UI + gear bar module + open/close wiring; palette css factored - to a shared asset; AT-SPI smoke. -4. Bar-module consolidation per the decision above (drop/keep indicators, - refresh-signal wiring, keybind). diff --git a/docs/design/2026-07-02-file-manager-swallow-spec.org b/docs/design/2026-07-02-file-manager-swallow-spec.org deleted file mode 100644 index 4c61be1..0000000 --- a/docs/design/2026-07-02-file-manager-swallow-spec.org +++ /dev/null @@ -1,141 +0,0 @@ -#+TITLE: File-Manager Swallow Pattern -#+AUTHOR: Craig Jennings -#+DATE: 2026-07-02 -#+TODO: TODO | DONE -#+TODO: DRAFT READY DOING | IMPLEMENTED SUPERSEDED CANCELLED - -* CANCELLED Status -:PROPERTIES: -:ID: d92e0074-f594-4e83-81a0-faf282e15ed0 -:END: -- [2026-07-02 Thu] CANCELLED — targeted the wrong file manager. Craig's ask - is about the dirvish popup (Super+F, an Emacs frame), not nautilus (the - Super+Shift+F bind that misled the grounding). For dirvish the right - design is elisp-side and strictly better: Emacs is the launcher, so it - can spawn the handler directly (=start-process=), hide the popup frame, - and restore it from a process sentinel — exact exit tracking plus a - failure notify, no window-event heuristics. Reassigned to .emacs.d via - its inbox (2026-07-02-2231-from-archsetup-dirvish-popup-swallow-handoff). - The gio double-fork finding below stands for any gio-launching file - manager; the daemon design is kept for reference only. -- [2026-07-02 Thu] DRAFT — initial spec from Craig's roam capture ("when the - file manager launches another app, it should hide and return when that - process ends"). Feasibility ground truth sampled live on velox same - evening: Hyprland's native swallow cannot work here (see Problem), so the - design is an event-listener daemon. - -* Metadata - -| Field | Value | -|--------+----------------------------------------------------| -| Status | cancelled | -|--------+----------------------------------------------------| -| Owner | Craig Jennings | -|--------+----------------------------------------------------| -| Repo | dotfiles (daemon + config); archsetup (none) | -|--------+----------------------------------------------------| -| Kin | touchpad-auto (socket-listener donor), | -| | hypr-refocus-scratchpad (event-daemon sibling) | -|--------+----------------------------------------------------| - -* Problem - -Opening a file from nautilus (Super+Shift+F, tiled, class -=org.gnome.Nautilus=) spawns a viewer window while nautilus stays in the -layout. The wanted behavior is the swallow pattern: the file manager hides -while the app it launched runs, and returns when that app exits. Today -there's no signal connecting the two windows — the viewer lands wherever -the layout puts it, nautilus lingers, and quitting is manual. - -*Hyprland's native swallow is ruled out — measured, not assumed.* -=misc:enable_swallow= + =swallow_regex= would be exactly this feature in two -config lines, but it matches by walking the new window's PID ancestry to -the swallow candidate's PID. Nautilus launches handlers through GLib -(=g_app_info_launch_default_for_uri=), and that path orphans the child: -reproduced live on velox 2026-07-02 with a python-gi launcher — feh came up -with PPID 1 (reparented to init) while the launcher was still alive. The -ancestry walk hits init before it hits nautilus, every time, for every -handler. Any design that depends on PID parentage is dead on arrival; the -signal has to come from window events instead. - -Ground truth on handlers (velox, 2026-07-02): pdf → zathura, image → feh, -video → mpv, text/code → emacsclient (window belongs to the emacs daemon). -Side-note, out of scope here: feh is X11 — an XWayland viewer on a -no-XWayland-by-preference setup; a default-handler review is its own task. - -* Goals - -- Double-click a file in nautilus → the viewer takes its place; nautilus is - gone (special workspace, not killed — state and tabs survive). -- Quit the viewer → nautilus returns and has focus. -- Nothing else changes: terminals, scratchpads, and every other window keep - their current behavior. -- Config-driven, testable logic, one small daemon — the touchpad-auto shape. - -* Design sketch - -A =hypr-swallow= daemon (dotfiles, =hyprland/.local/bin/=) listening on the -Hyprland IPC event socket (socket2), same as =touchpad-auto=: - -- Track the active window (=activewindow>>= events carry class + title; - =activewindowv2>>= carries the address). -- On =openwindow>>= (address, workspace, class, title) while the active - window's class is a configured *parent* (nautilus): dispatch - =movetoworkspacesilent special:swallow,address:0x<parent>=, record - child-address → {parent-address, origin workspace}. -- On =closewindow>>= of a recorded child: bring the parent back - (=movetoworkspace=) and focus it; drop the record. -- On =closewindow>>= of a hidden parent (nautilus quit while hidden): drop - the record, nothing to restore. -- Exception classes (fuzzel, dunst, scratchpad classes, the panels) never - trigger a swallow even when they open over nautilus. -- Pure event-machine core (parse lines → state transitions → dispatch list), - unit-tested against recorded event streams; a thin socket loop around it. - -Known edge, handled: Super+Shift+F while nautilus is hidden re-runs -=nautilus=, which activates the existing (hidden) instance instead of -opening a window. The daemon (or the bind) must restore-and-untrack in that -case so the bind never appears dead. - -Known limitation, accepted: the emacsclient case never swallows — the -window belongs to the long-running emacs daemon and =closewindow= for it -means a frame closed, not "the file is done." The parent-class trigger plus -exception list naturally leaves it alone only if we exclude it explicitly — -see decision 2. - -* Decisions (Craig) - -** TODO Trigger breadth: any new window while nautilus is active, or an allowlist of viewer classes? -"Any window" is simple and catches every handler, but a false positive -exists: an app you launched seconds earlier from elsewhere finishes starting -while you're focused on nautilus → nautilus gets swallowed by an unrelated -window. An allowlist (zathura, mpv, imv, feh, …) can't be surprised but -needs maintaining. Recommendation: any-window + exception list — the false -positive is rare and self-healing (close the window or refocus). - -** TODO The emacs frame case: swallow or exempt? -Opening a text file from nautilus raises/creates an emacs frame. Swallowing -nautilus under it "works" going in, but the restore fires when *any* frame -closes, which may be much later or never. Recommendation: exempt =emacs= — -text files just open, nautilus stays. - -** TODO Restore destination: the workspace nautilus came from, or the one you're on when the viewer closes? -If you move the viewer to another workspace and quit it there, "origin" -teleports you back; "current" brings nautilus to you. Recommendation: -current workspace — the restore should land where your attention is. - -** TODO Multiple children: refcount or single-slot? -You can only launch a second file after restoring nautilus manually, so -overlap is rare — but a fast double-launch can record two children. -Recommendation: refcount — restore when the last tracked child closes. - -* Implementation phases - -1. =hypr-swallow= core: pure event-machine (TDD over recorded event - streams; fake hyprctl for dispatch assertions), config block at the top - (parent classes, exception classes), unittest suite in =tests/=. -2. Socket loop + wiring: exec-once in hyprland.conf, the Super+Shift+F - restore-if-hidden interplay, daemon single-instance guard. -3. Live verification on velox (zathura + mpv round-trips, the emacs case, - the false-positive probe) + manual-testing entries; ratio rides the - dotfiles pull. diff --git a/docs/design/2026-07-02-net-panel-other-interfaces-spec.org b/docs/design/2026-07-02-net-panel-other-interfaces-spec.org deleted file mode 100644 index 6b0a72d..0000000 --- a/docs/design/2026-07-02-net-panel-other-interfaces-spec.org +++ /dev/null @@ -1,189 +0,0 @@ -#+TITLE: Net Panel — Tailscale, VPN, and WireGuard Interfaces -#+AUTHOR: Craig Jennings -#+DATE: 2026-07-02 -#+TODO: TODO | DONE -#+TODO: DRAFT READY DOING | IMPLEMENTED SUPERSEDED CANCELLED - -* IMPLEMENTED Status -:PROPERTIES: -:ID: 79a1075a-4b56-4f25-a861-b69f120a636a -:END: -- [2026-07-02 Thu] IMPLEMENTED — all six phases shipped (dotfiles 2d9d060, - 21db05a, 31ba056, b4010bf, b5c8442; archsetup 0389790 + the wireguard - import script): probes, panel Tunnels view, diagnose/doctor route - awareness, bar badge, installer swap + operator, velox config migration. - Residual human steps filed under todo.org "Manual testing and - validation": proton CLI sign-in (per machine) and the first live - badge/tunnel round-trip. Ratio picks up the import + package swap on its - trip. -- [2026-07-02 Thu] DOING — decomposed into six build phases under the - todo.org parent (:SPEC_ID: bound); build started same evening per Craig - ("tunnels build now + audio-panel spec alongside"). -- [2026-07-02 Thu] READY — fused review passed the gate: 4/4 decisions - resolved, phases decomposable, claims re-verified live (proton-vpn-cli - 1.0.1 in extra, binary =/usr/bin/protonvpn=, no package conflict with the - GTK app; =tailscale status --json= shape confirmed on velox — Self/Peer/ - CurrentTailnet.Name/MagicDNSSuffix; zero NM wireguard connections yet, - seven configs in assets awaiting the phase 6 import). -- [2026-07-02 Thu] DRAFT — initial spec from the roam capture "other network - interfaces (tailscale, VPNs, wireguard)" filed in todo.org 2026-07-02. - -* Metadata - -| Field | Value | -|--------+---------------------------------------------------| -| Status | implemented | -|--------+---------------------------------------------------| -| Owner | Craig Jennings | -|--------+---------------------------------------------------| -| Repo | dotfiles (net module); archsetup (packages) | -|--------+---------------------------------------------------| -| Parent | Waybar network module spec (2026-06-29), V2 panel | -|--------+---------------------------------------------------| - -* Problem - -The net panel's Connections tab shows what NetworkManager knows: WiFi networks -and wired links. The machines also run overlay and tunnel interfaces the panel -is blind to: - -- Tailscale (tailscaled, both daily drivers; the tailnet is how the machines - reach each other; not an NM device) -- WireGuard configs (assets/wireguard-config/ carries Proton VPN configs; - importable as NM connections of type wireguard or run via wg-quick) -- Commercial VPN clients (Proton VPN GTK app is installed on velox; owns its - own tunnel device) - -When one of these is up it changes routing, DNS, and reachability — exactly -the things the Diagnostics tab reasons about — yet the panel neither shows nor -controls them, and the doctor can misattribute a VPN-caused failure to the -underlying link. - -* Goals - -1. Visibility: the Connections tab shows overlay/tunnel interfaces with live - state (up/down, address, and for tailscale the tailnet peers summary). -2. Control: bring each up or down from the panel row, same interaction shape - as Join/Disconnect on WiFi rows (no terminals — V2 contract). -3. Diagnostics awareness: diagnose/doctor know when a tunnel owns the default - route or DNS, name it in evidence rows, and stop misattributing its - failures to the physical link. - -Non-goals (this iteration): installing or configuring VPN providers, tailnet -ACL management, exit-node selection UI (a "use exit node" affordance can ride -a later pass), kill-switch management (tracked separately in the spec's -failure catalog). - -* Design sketch - -** Data sources — one probe per backend, engine-side - -New GTK-free module net/src/net/overlays.py with one probe per backend, -each returning the same small dict shape ({kind, name, state, addr, detail, -can_toggle}): - -- tailscale: =tailscale status --json= (rich: self, peers, exit node, health - messages). Daemon down → state "stopped". Binary absent → backend absent. -- wireguard-nm: =nmcli -t connection show= filtered to type wireguard — - up/down via the existing nmcli wrapper (activate/deactivate connection). - The seven Proton configs in assets/wireguard-config/ import cleanly - (=nmcli connection import type wireguard file <conf>=, then - =connection.autoconnect no= immediately — imports default to autoconnect - yes). They use only PrivateKey/Address/DNS + PublicKey/AllowedIPs/Endpoint, - no PostUp/PostDown anywhere, so no wg-quick path is needed at all - (Craig, 2026-07-02). All are full-tunnel (AllowedIPs 0.0.0.0/0) — the - panel should treat them as mutually exclusive. -- proton: drive the official proton-vpn-cli (Arch extra repo, v1.0.x, - stable since 2026-04) — connect/disconnect/status verbs. It drives NM - underneath (python-proton-vpn-network-manager), so the panel still sees - connection events through NM. Runtime-exclusive with the GTK app, which - gets dropped from the install. The imported NM wireguard configs remain - a raw fallback when the CLI/API path is down; the CLI stays primary - because the raw configs lack kill switch, port forwarding, and server - rotation. - -** Panel - -A fourth Connections group "Tunnels" (after Saved / Available now / Wired) -using the existing group-header + row machinery. Row: glyph per kind, name, -state caption; primary action Up/Down where can_toggle, else Open app. -Tailscale row detail (subtitle or tooltip): tailnet name, peer count online, -exit node if any. - -** Privileged path - -- tailscale up/down: needs root or operator — =tailscale set --operator= at - install time (archsetup) makes the user an operator, so no sudo needed at - runtime. Fallback: the V2 net-priv helper gains tailscale-up/down verbs. -- NM wireguard connections: no privilege needed (NM polkit default for the - active user). - -** Diagnostics awareness - -- diag gains an "overlay owns default route/DNS" detection step: when the - default route or resolv.conf points at a tunnel interface, evidence names - it ("default route via tailscale0") and failure classification runs the - physical-link checks against the underlying device instead. -- doctor: a tunnel-caused egress failure (VPN up but its endpoint dead) - classifies fixable with next_action "bring the tunnel down / reconnect", - not a WiFi reset. - -** Bar indicator - -Part of v1 (Craig, 2026-07-02 — "shouldn't be optional"): a small overlay -badge on the net glyph when a tunnel owns the default route. Rides the same -route/DNS-ownership detection the diagnostics step adds. - -* Decisions (Craig) - -** DONE Which backends ship in the first pass? -CLOSED: [2026-07-02 Thu] -Approved (Craig, 2026-07-02): tailscale + NM-managed wireguard. Craig asked -whether the wireguard configs can be ported to NM so wg-quick drops out -entirely — yes: all seven configs in assets/wireguard-config/ use only the -six directives NM imports cleanly (verified 2026-07-02; import command and -autoconnect caveat now in the design sketch). wg-quick is out of the spec, -not deferred. Proton control is CLI-driven per the Proton decision below, -superseding the detection-only recommendation here. - -** DONE Tailscale control path: operator flag at install vs net-priv verbs? -CLOSED: [2026-07-02 Thu] -Approved (Craig, 2026-07-02): =tailscale set --operator=$USER= in archsetup's -tailscale step (declarative, no sudo at runtime); net-priv verbs only if -operator mode proves insufficient (e.g. up with flags). -** DONE Does "Tunnels" belong in Connections or its own tab? -CLOSED: [2026-07-02 Thu] -Approved (Craig, 2026-07-02): a Connections group. A fourth top tab dilutes -the V2 nav for three rows. - -** DONE Proton VPN: detect-only or drive its CLI? -CLOSED: [2026-07-02 Thu] -Decided (Craig, 2026-07-02): drive it through a CLI. Research (2026-07-02): -Proton shipped an official Linux CLI — first release 2025-11, stable v1.0.0 -2026-04, packaged in Arch extra as proton-vpn-cli (1.0.1 at check time), -with kill switch, port forwarding, NetShield, server selection, and a -status command. It drives NM underneath, so the panel sees its connections -through the existing NM event path. Spec changes: the proton backend calls -protonvpn connect/disconnect/status instead of device-detection -(can_toggle true); archsetup installs proton-vpn-cli and drops -proton-vpn-gtk-app (the two can't run concurrently per the project README — -untested locally); the imported NM wireguard configs stay as a raw fallback. -Sources: [[https://protonvpn.com/support/linux-cli][Proton Linux CLI guide]], -[[https://protonvpn.com/support/release-notes-linux-cli][CLI release notes]], -[[https://github.com/ProtonVPN/proton-vpn-cli][proton-vpn-cli repo]]. -* Implementation phases - -1. overlays.py probes (tailscale JSON, nmcli wireguard filter, proton-vpn-cli - status) — pure engine, TDD with fake binaries; =net status= grows an - overlays section. -2. Panel Tunnels group + Up/Down wiring through the worker thread; AT-SPI - smoke extension. -3. Diagnose/doctor overlay awareness (route/DNS ownership step, classifier - rows, evidence text) — TDD against the diag harness. -4. waybar-net tunnel badge on the net glyph (v1 per the bar-indicator - decision), riding phase 3's route-ownership detection; suite coverage. -5. archsetup: tailscale operator flag in the tailscale install step; - proton-vpn-cli replaces proton-vpn-gtk-app in the package list; VM test - assertions. -6. One-time per-machine migration: import the seven assets/wireguard-config - configs into NM with autoconnect off (scriptable; both daily drivers). diff --git a/docs/design/2026-07-02-timer-panel-spec.org b/docs/design/2026-07-02-timer-panel-spec.org deleted file mode 100644 index 2c9f7d4..0000000 --- a/docs/design/2026-07-02-timer-panel-spec.org +++ /dev/null @@ -1,113 +0,0 @@ -#+TITLE: Timer GTK Panel -#+AUTHOR: Craig Jennings -#+DATE: 2026-07-02 -#+TODO: TODO | DONE -#+TODO: DRAFT READY DOING | IMPLEMENTED SUPERSEDED CANCELLED - -* DRAFT Status -:PROPERTIES: -:ID: 1770af2e-b093-4024-a512-ae4324a2869f -:END: -- [2026-07-02 Thu] DRAFT — initial spec from Craig's roam capture "give the - timer a gtk UI/UX like the network panel. spec this out." - -* Metadata - -| Field | Value | -|--------+---------------------------------------------------| -| Status | draft | -|--------+---------------------------------------------------| -| Owner | Craig Jennings | -|--------+---------------------------------------------------| -| Repo | dotfiles | -|--------+---------------------------------------------------| -| Kin | net panel (architecture donor), wtimer (backing), | -| | desktop-settings panel spec (sibling) | -|--------+---------------------------------------------------| - -* Problem - -The timer's whole UI is a chain of three fuzzel prompts (type, value, label) -plus a fourth for cancel. That flow can't show what's already running while -you create, can't offer one-tap presets, gives no feedback on a typo until -the add silently fails, and pomodoro state (phase, cycle) is only visible in -a tooltip. The 2026-07-02 styling pass made the dialogs presentable, but the -shape is still four blind modals for what is really one small control -surface. - -* Goals - -1. One panel, opened from the bar's timer module, that shows everything - running (live countdowns, pomodoro phase/cycle, paused state) and creates - new items without leaving it. -2. One-tap presets for the common cases (tea, pomodoro, quick alarm) next to - freeform entry, with inline validation before the add. -3. Per-item controls: pause/resume, cancel, promote to primary (the bar - glyph slot). -4. wtimer stays the single owner of timer state and the notification path; - the panel is a view over it, never a second engine. - -* Design sketch - -** Architecture — clone the net panel's proven stack - -- GTK4 + gtk4-layer-shell dropdown anchored under the timer module, Blueprint - .blp compiled to committed .ui (=make ui=; compiler is dev-only). -- Humble-object split: GTK-free PanelModel presenter, unit-tested to 100%, - with thin widget bindings; one gated AT-SPI smoke via the - run-panel-smoke.sh pattern. -- Backing: shell out to the existing wtimer CLI (=add=, =toggle=, =cancel=, - =cycle=, =render=). =render= already emits a JSON payload; the panel polls - it (or subscribes to the same RTMIN+14 refresh signal) for live state. - wtimer's 89-case suite keeps owning the logic; panel tests fake the CLI - like every dotfiles suite fakes binaries. -- Dupre WIP palette CSS shared with the net panel (same factoring the - desktop-settings spec calls for — one palette asset, three panels). - -** Layout sketch - -- Header row: running-item count + a Clear All button (maps to cancel-all). -- Item list: one row per item — type glyph, label, live countdown / clock - time / phase+cycle for pomodoro, pause and cancel buttons, click-to-promote. -- Create strip: four type buttons (the wtimer glyphs), preset chips per type - (e.g. 5m / 15m / 25m / 60m for timers), a freeform entry validated with - wtimer's own parsers, an optional label field. -- Empty state: the create strip alone, centered. - -** What happens to the fuzzel flow - -The keybind/fuzzel path stays as the keyboard-fast lane (it's now styled and -tested); the panel replaces the click-driven path on the bar module. Whether -the fuzzel chain eventually retires is a decision below. - -* Decisions (Craig) - -** TODO Panel scope: standalone timer panel, or a page in the desktop-settings panel? -The desktop-settings spec (sibling DRAFT) could host timers as a page. -Standalone matches the net panel's one-domain-one-panel shape and keeps the -timer dropdown small; folding in means one panel binary fewer. Recommend -standalone, sharing the palette/css asset. - -** TODO Fuzzel flow: keep as keyboard fast lane, or retire once the panel lands? -Keeping both costs two creation paths to maintain (though the fuzzel chain is -small and freshly tested). Recommend keep until the panel proves itself, then -revisit. - -** TODO Presets: which chips per type? -Strawman: timer 5m/15m/25m/60m; alarm +30m/top-of-hour/07:00; pomodoro -default cycle only; stopwatch needs none. Adjust to taste. - -** TODO Live updates: poll render (1s, like the bar) or a wtimer "watch" mode? -Polling reuses what exists and matches the bar's cadence; a watch/subscribe -mode is cleaner but grows wtimer. Recommend polling first. - -* Implementation phases - -1. PanelModel presenter + CLI-backing seam (TDD, GTK-free, 100% like the net - PanelModel). -2. Blueprint UI: item list + create strip, wired to the presenter; palette - css factored to the shared asset. -3. Bar integration: timer module left-click opens the panel (replacing the - fuzzel menu binding there), RTMIN+14 refresh keeps bar and panel in step. -4. AT-SPI smoke + manual-testing checklist; decide the fuzzel flow's future - after a week of real use. diff --git a/docs/design/2026-07-03-instrument-console-panels-spec.org b/docs/design/2026-07-03-instrument-console-panels-spec.org deleted file mode 100644 index 315e0b4..0000000 --- a/docs/design/2026-07-03-instrument-console-panels-spec.org +++ /dev/null @@ -1,158 +0,0 @@ -#+TITLE: Instrument-console rebuild — net + bluetooth panels -#+DATE: 2026-07-03 -#+TODO: TODO | DONE -#+TODO: DRAFT READY DOING | IMPLEMENTED SUPERSEDED CANCELLED - -* IMPLEMENTED Status -:PROPERTIES: -:ID: e73877f5-4f5b-4f81-b946-dbaa6145e0d5 -:END: -- 2026-07-03 Fri @ 06:49 -0400 :: DOING → IMPLEMENTED: all six phases shipped - (net GTK-free layer 81ec9c3, net view 800ef60; bt GTK-free layer 5318b34, bt - view 66f03d9; phase-6 dead-code removal f4e688e). Both panels are single-screen - instrument consoles, verified live on velox — 46 suites + full make test green, - both AT-SPI smokes green end to end, render matching the approved prototype. The - three folded tasks (network panel redesign, bt switch placement + title, bt - rename devices) closed with the build. -- 2026-07-03 Fri @ 02:07 -0400 :: DRAFT → READY → DOING in one stroke: Craig - approved the design through five interactive prototype iterations and - authorized the no-approvals speedrun ("let's build them now... go"). The - review gate was the live prototype session itself. -- 2026-07-03 Fri @ 02:07 -0400 :: Created (DRAFT) from the prototype session. - -* Metadata - -| Field | Value | -|---------------------+------------------------------------------------------------| -| Status | implemented | -|---------------------+------------------------------------------------------------| -| Owner | Craig Jennings | -|---------------------+------------------------------------------------------------| -| Repos | dotfiles (net/, bluetooth/, themes), archsetup | -|---------------------+------------------------------------------------------------| -| Normative reference | [[file:../../assets/2026-07-03-instrument-console-panels-prototype.html][assets/2026-07-03-instrument-console-panels-prototype.html]] | -|---------------------+------------------------------------------------------------| - -* Summary - -Rebuild both GTK layer-shell panels (net, bluetooth) from the tabbed layout -to the instrument-console design: one screen, no tabs, a faceplate with a -state word + badges + radio switch + close, engraved section labels, lamp -rows that act on click, dial meters under the console keys, and a doctor -that does it all. The interactive prototype =panel-console-v3.html= is the -normative design reference — when this spec and the prototype disagree on a -visual or interaction, the prototype wins. - -* Decisions (all resolved — Craig, prototype session 2026-07-02/03) - -- Replace the tabbed panels outright. No fallback flag; git history is the - rollback. Net panel first, bluetooth second. -- Advanced repair tiers leave the panel entirely. DOCTOR runs the full - diagnose → classify → lightest-repair → re-verify escalation (the engine - already does this). The surgical tiers stay CLI-only (=net repair ...=). -- Faceplate (both panels): state lamp + state word, badges, unit label - (NET·01 / BT·01), radio switch (wifi radio / bt adapter power), close ✕. - Badges: TUNNEL (gold, net), AIRPLANE (gold, both), LOW BATT (red, bt). -- Sections in order — net: CHANNEL, NETWORKS (+ hidden action), TUNNELS, - CONSOLE (DOCTOR / SPEED TEST keys), meters, output. bt: ADAPTER (with - clickable =discoverable= chip), PAIRED, NEARBY (+ scanning note), CONSOLE - (DOCTOR / SCAN), battery gauges, output. -- Section row budgets, half-row peek, internal scroll (thin slate - scrollbar): NETWORKS 5.5 rows, TUNNELS 4.5, PAIRED 5.5, NEARBY 4.5. - In-range networks sort active-first then strongest-signal-first. Counts on - the engraved headers ("networks · 12 in range", "tunnels · 1 up of 9", - "paired · 3", "nearby · 12"). The panel silhouette never grows with list - length; only the output well is variable and it caps at ~170px. -- Lamp-row grammar: green = live/connected, gold = available/actionable, - off = down/stored, red = failed; busy = pulsing gold during transitions. - Rows act on click (tunnels toggle, networks join, paired devices - connect/disconnect toggle, nearby devices pair). -- Arm-first for anything disruptive or destructive, 3s auto-disarm: - - forget (network or bt device): hover reveals ✕; first click arms the - row terracotta ("forget? click ✕ again"), second fires. No dialog. - - disconnect (active network): click the active row; first click arms in - GOLD ("disconnect? click again") — disruptive, not destructive — second - fires. -- Meters (net): two dials, RX·DOWN / TX·UP, gold needles, mode tag top-left - (LIVE green / TEST gold), HOLD tag top-right. Idle: live link throughput. - Speed test: cards flash gold, needles sweep the measured rate, then PIN - the final value with HOLD; clicking a held meter releases it to LIVE. - Scale 0–100 Mbps, auto-relabel to 0–1000 when a reading exceeds 100. - Dial top inset ~13px so the corner tags never touch the arc. -- Speed test output well gets ONLY: location line ("location: <city> by - <sponsor>"), ping (+jitter), final line, conditioned tip(s). The rates - live in the meters, not the text. -- Battery gauges (bt): same dial chrome; one per connected device (two - slots; empty slot dim "NO DEVICE"/"ADAPTER OFF"); needle+value red under - 15% and the LOW BATT faceplate badge lights. -- Output well: doctor streams the checks with their narration lines - (viewmodel.STEP_NARRATION) and repair steps in gold; verdict line closes - (olive for pass/fixed). A dismiss ✕ appears in the well's corner whenever - it has content. Both panels. -- WiFi radio switch: =nmcli radio wifi on|off=. Off empties NETWORKS to one - dim "wifi radio off" row, drops the connection, kills tailscale rows' - reachability; on rejoins the last network (NM autoconnect does this for - real). Airplane mode is system-level (Super+Shift+A owns it): both panels - reflect it (state word AIRPLANE, gold badge, switches down); a switch - flipped under airplane refuses with a toast naming the exit. A routed - ethernet link keeps the net panel ONLINE through airplane mode. -- Ethernet: presence-based row pinned atop NETWORKS when a cable is up - ("enp… · active · wired · 1.0 Gbps" / "wired · standby"); CHANNEL swaps - the signal ladder for "wired · <speed> full-duplex" when routed; clicking - the row toggles route ownership via device disconnect/connect. -- Pairing (bt): nearby row click → busy → passkey-confirm dialog (large - gold digits) → device moves to PAIRED and connects. SCAN key refreshes - with a "scanning…" note on the header. -- Rename (bt): hover ✎ on a paired row → dialog prefilled → bluez - =set-alias= (closes the filed rename task). -- Tooltips: any ellipsized row label carries its full text as the tooltip. -- Dialogs (join / hidden SSID / passkey / rename) keep the in-panel dupre - dialog style (gold border, dark well inputs, gold caret). -- Close: ✕ on the faceplate + Esc (already shipped; keep). -- Folded tasks: "Network panel redesign", "Bluetooth panel: switch placement - + panel title", "Bluetooth panel: rename devices" — all close with this - build's phases. - -* Engine gaps (small, close during phases) - -- radio verb: =nmcli radio wifi on|off= helper (manage or sysio) + tests. -- hidden-SSID join: =manage.add= grows a hidden flag - (=802-11-wireless.hidden yes=). -- ethernet: device rows from =nmcli dev= (type ethernet) + disconnect/ - connect verbs (device-level; =net down --iface= already disconnects). -- bt rename: btctl =set-alias= one-shot verb + verify-after read. -- bt battery: already exposed (indicator uses it). -- speedtest meters: =run_speedtest_stream= on_update already ticks (pty). -- link speed for wired channel line: =ethtool=-free read from - =/sys/class/net/<dev>/speed=. - -* Implementation phases - -1. [X] Spec + task wiring (this file; todo.org parent task with :SPEC_ID:). -2. [X] Net GTK-free layer (TDD): viewmodel row composers for the console - sections (network rows sorted+counted, tunnel rows, channel facts, - faceplate state word derivation, meter scale logic, arm state machines - for forget/disconnect), PanelModel restructure (sections, no tabs). - Engine gaps: radio verb, hidden join, ethernet rows, wired link speed. -3. [X] Net view rebuild: gui.py single-page console built in Python - (faceplate, engraved scrolled sections, console keys, cairo dial meters - with mode/hold tags, output well + dismiss), panel.css additions - (engrave, lamps, dial, badges, arm tints). AT-SPI smoke + driver - rewritten for the console layout. Shipped with phase 4 (dotfiles - 800ef60): a view-only intermediate is a broken panel (rows and switches - that do nothing), so view + interactions landed together. -4. [X] Net interactions: join/hidden/forget (arm terracotta)/disconnect - (arm gold)/radio switch/ethernet toggle/doctor stream/speed-test-drives- - meters, toasts. Verified live on velox (DOCTOR streams, SPEED TEST sweeps - both dials then HOLD). Shipped in dotfiles 800ef60 with phase 3. -5. [X] Bluetooth panel: same treatment end to end (faceplate + power - switch, adapter chip, paired/nearby lamp rows, pair passkey flow, - rename via set-alias, forget arm, battery gauges + LOW BATT, DOCTOR / - SCAN keys, output). bt smoke rewritten. Shipped in two commits mirroring - net: dotfiles 5318b34 (GTK-free layer + engine gaps) and 66f03d9 (view + - interactions + smoke). rename lands on the bluez Alias via busctl - (set-alias has no MAC-addressed one-shot); verified live on velox (smoke - green end to end, screenshot matches the prototype). -6. [X] Live verification both panels on velox + all suites + smokes green; - summary of findings written to file; folded tasks closed; dead code - removed; session context finalized. diff --git a/docs/design/maintenance-console-design-ideas.org b/docs/design/maintenance-console-design-ideas.org new file mode 100644 index 0000000..066c25d --- /dev/null +++ b/docs/design/maintenance-console-design-ideas.org @@ -0,0 +1,527 @@ +#+TITLE: Maintenance Console — Design Ideas +#+AUTHOR: Craig Jennings +#+DATE: 2026-07-06 + +* Direction + +A single-host maintenance console (GUI, waybar-launched) that surfaces every +health metric for this Arch workstation and, where the remediation is safe, +runs it. It reshapes the earlier install-health/workstation-contract framing +(=system-monitor-design-ideas.org=) into a maintenance surface with a doctor +action. + +The console is the glanceable, single-host version of the home project's +=system-health-check.org= workflow (~1000 lines, capability-dispatched across +ratio/velox/mybitch/truenas). The console owns the routine-maintenance and +at-a-glance-status slice; the workflow stays as the escalation path for +forensic investigation and as the only sanctioned way to run updates. + +* Scope: hosts (Q2 resolved 2026-07-06) + +v1 targets *both* daily drivers — ratio and velox — as first-class hosts, not +ratio-first-velox-later. Consequences for v1: + +- *Capability dispatch is mandatory from day one*, not deferred. The console + probes the live host and runs only applicable checks (btrfs RAID1 on ratio vs + ZFS-primary on velox; AMD amd-pstate on ratio vs Intel intel_pstate on velox). + This mirrors the home workflow's Phase 0 probe. +- *velox-only metrics ship in v1*: battery health (capacity / cycles) and the + unclean-shutdown / suspend-failure rate — both currently GAPs, both driven by + velox being the machine that travels and suspends. +- The ZFS-primary remediation paths (scrub, snapshot retention, pool health) are + built and tested, not stubbed. + +* Thresholds source of truth (Q3 resolved 2026-07-06) + +One machine-readable file, =maintenance-thresholds.toml=, holds every severity +value (cache-size trigger, disk %, scrub-age warn/crit, snapshot retention +limits, temp bands, backup-staleness windows, etc.). archsetup owns and ships +it; both consumers read the *installed* path, so neither reaches into the +other's repo: + +- the maintenance console (dotfiles code) reads it at runtime, +- the system-health-check workflow reads it instead of hardcoding severity + rules in prose. + +The workflow's hard-won values migrate into the TOML as the seed content (the +snapshot MONTHLY limit that bit /home, the 10 GB cache trigger, the scrub-age +bands). Install path is an implementation sub-question — a stable location both +consumers reach (e.g. =~/.config/archsetup/maintenance-thresholds.toml=). + +* Workflow ownership — move system-health-check into archsetup (proposed 2026-07-06) + +Craig's call: the =system-health-check.org= workflow should live in archsetup, +not the home project. Rationale — the home project is scoped to finances, +health, and personal matters; system design, execution, and maintenance are +archsetup's domain. Home only owns the workflow by inherited accident, not by +fit. Moving it here also collapses the Q3 coupling: the TOML source-of-truth and +its workflow consumer end up in the same project. + +Move scope (home → archsetup), to sequence when Craig gives the go: +- =system-health-check.org= (the workflow itself) +- =homelab-inventory/*.org= (ratio/velox/mybitch/truenas capability inventories + it cross-references) +- any home-project references to the workflow (startup, project-workflows index) + +Cross-project mechanics: the archsetup side (receiving the files, wiring the +TOML) is in-scope here; the home side (removing the originals, updating home's +references) is the home project's scope — handled by a handoff note to home's +inbox or a home session, not edited blind from here. + +* Panel shape + +Three regions, driven by the Automation column of the metrics table below: + +- *Actionable* (left) — metrics with a lever. Automation = Auto or Confirm. + Two doctor actions serve this column: "Clean up" fires every Auto metric + unattended; "Review & fix" opens a preview for the Confirm metrics and acts + only on approval. +- *Diagnostic* (right) — read-only telemetry. Automation = None or Human. A + state color (green/amber/red) and the value; no button. Red here is the + signal to run the workflow. +- *Updates* (quarantined strip) — Automation = Workflow. Shown as a count with + notable packages named; the only affordance is "run the workflow." The panel + never applies updates in place. + +*Bar glyph* tracks the worst *Diagnostic* state only — not the actionable +count. A big package cache is boring; a SMART failure is a fire. Actionable +clutter must not turn the bar red or it trains you to ignore it. + +** Doctor = live output wall (Q1 resolved 2026-07-06) + +"Clean up" and "Review & fix" are not fire-and-forget buttons. Running a doctor +opens an *output wall* — one lamp per action, streaming in realtime as each +runs: + +- *amber* while the action is running, +- *green* on success (with the reclaimed amount / result inline), +- *red* on failure. + +Feedback is always shown (not just when something was reclaimed) — you watch it +happen. This is the same live-results shape that should back the other doctors +(net, bluetooth), so every doctor in the system reads the same way. See the +todo task to retrofit the net + bt doctors to realtime lamp output. + +* Automation legend + +| Value | Meaning | +|----------+-----------------------------------------------------------------------------------------------------------| +| Auto | Doctor "Clean up" button — fire unattended, reversible or harmless | +|----------+-----------------------------------------------------------------------------------------------------------| +| Confirm | Doctor "Review & fix" — preview the change, act only on one click | +|----------+-----------------------------------------------------------------------------------------------------------| +| Human | Panel nudges; a human decides and acts (no button, not the agent) | +|----------+-----------------------------------------------------------------------------------------------------------| +| Workflow | vLater — metric shown read-only; no in-panel button. Agent-workflow assistance deferred (decided | +| | 2026-07-07, see below) | +|----------+-----------------------------------------------------------------------------------------------------------| +| None | Diagnostic only — no software remediation exists (hardware, telemetry) | +|----------+-----------------------------------------------------------------------------------------------------------| + +*Workflow buttons removed (decided 2026-07-07, prototyping E5).* The panel +carries no "run workflow" affordances — not on failed units, unclean boots, +updates, or CVEs. The metrics stay on the board as read-only telemetry; AI +assistance via the system-health-check workflow is a vLater feature. v1 ships +only determinate remedies (fixed, scriptable actions with predictable +outcomes). + +*Determinate remedies adopted (decided 2026-07-07).* Every remedy below is +Confirm-tier; contextual levers appear only when the metric is off-nominal: + +- /Service restarts & enables/: failed units (systemctl restart + reset-failed), + fail2ban, cronie, chronyd (+ makestep), tailscaled, snapper timers, zram + config re-apply. DNS/NetworkManager delegates to the net panel's doctor + (deep-link, not a duplicate repair chain). +- /Deterministic maintenance ops/: btrfs balance -dusage=50 (unallocated low), + reinstall owning packages on pacman -Qkk failures, rsyncshot RUN NOW (result + streams to the output well), smartctl -t short self-test, snapshot-retention + repair (write sane TIMELINE limits + cleanup — the /home lesson as a + one-press fix). btrfs device-error counter reset stays manual: resetting + without diagnosis masks a dying drive. +- /Composite macro/: RECLAIM SPACE on the disk-usage cell — runs every reclaim + lever (cache, journal, coredumps, app logs, docker tier-1, snapper cleanup) + as one output-wall stream. +- /Disruptive but determinate/: REBOOT behind arm-to-fire, offered when + running kernel != installed. +- /Still read-only/: temps, throttling, battery, memory/OOM, taint, journal + error content, kernel/hw events, listeners, unclean-shutdown rate — physical + or investigative; vLater AI territory. + +*Updates join the Confirm layer (decided 2026-07-07).* Two levers on the +updates strip, both behind the live-update guard (mesa/hyprland/wayland +runtime in the pending set): + +- UPDATE — repo + AUR system update. +- TOPGRADE — full ecosystem run. The panel's wrapper always passes + --disable git (topgrade's git step rebase-autostashes ~/code/*/ — never + under a live session). + +*MEM·PWR: evidence + two levers + expectation tags (decided 2026-07-07).* +Mostly physics, so the category leans watch-only — with these additions: + +- /CPU mode selector/: a free segmented control (PERF · BAL · POWER) writing + the EPP hint — set the active mode to anything, not drift-repair against a + declared default. amd-pstate on ratio, intel_pstate on velox. +- /Battery charge limit/ (velox-only, capability-gated): SET 80% writes + charge_control_end_threshold — the standard longevity cap. Battery *health* + (capacity vs design, cycles) stays watch-only hardware telemetry. +- /Evidence drill-downs/ (digest idiom): top-5 RAM consumers under memory, + recent boots listed clean/unclean under the unclean-rate, and throttle/OOM + events with timestamps. Evidence makes the numbers actionable even where no + button exists. +- /KILL on top-memory items/ (revised 2026-07-07 — Craig): arm-to-fire, four + guards: the arm shows the exact victim (name + size); SIGTERM not SIGKILL, + with the outcome reported to the wall; PID + process name revalidated at + fire time so a recycled PID can't be hit; session-critical names (systemd, + the compositor, the panel itself) render a disabled key — protected. A + SIGKILL escalation for TERM-survivors is vLater. +- /Expectation-setting, panel-wide/: every leverless cell's sub-line carries an + explicit tag ("hardware — watch only", "evidence below"), and each subpanel + header shows the split — "N fixable · M watch" — so the user knows their + agency before reading a single cell. + +*Refresh cadence (decided 2026-07-07).* Four tiers, matching probe cost: + +- /Live group, panel open/: temps, memory free + top consumers, throttle + state — re-read every ~3 s while their subpanel is visible, gated on + panel-open exactly like the audio panel's meters. Stop when hidden. +- /Fast local tier, panel open/: re-probed every ~30 s while the panel is up; + additionally, any metric re-probes immediately after an action that touches + it (fire CLEAN → cache re-measured, not assumed). +- /On open/: the hydration tiers re-run (fast reads first, process probes + behind them — sub-second perceived). +- /Network tier/: checkupdates / arch-audit / AUR / firmware stay on the + hourly systemd-timer cache with age shown; refreshed on demand only. +- /Panel closed/: the waybar glyph is fed by a light background scan every + ~30 min (systemd timer writing the state file the glyph reads) — the bar + stays honest without the panel running. + +*Journal errors get a digest, not a fix (decided 2026-07-07).* No generic +remedy exists — an error-priority line is a symptom of an arbitrary subsystem — +so the panel ships four determinate assists instead: + +- /Digest/: the cell expands to errors grouped by syslog identifier — count, + message snippet, first/last seen, and the exact next command (journalctl -u + <unit> -b) when the identifier maps to a unit. Top-10 groups, read-only. +- /MARK KNOWN with a full lifecycle/: arm-to-fire showing the exact pattern + before it stores; marked groups move to a dim KNOWN section (never vanish) + with per-row UNMARK; every mark/unmark logs to the results wall; marks carry + date + example. Patterns bind to identifier + message, never a whole unit — + a muted service's *new* errors still surface. +- /Two noise layers/: shipped defaults (bluetoothd HFP, pixman, xkbcomp) in + the packaged TOML, user marks in a separate user file merged over it (a + template sync never eats curation). CLEAR MARKS (arm-to-fire) empties the + user layer and re-enables shipped defaults; unmarking a shipped default + records a disable flag in the user layer. +- /OPEN JOURNAL/: launches a terminal running journalctl -p err -b — the same + delegation pattern as NET DOCTOR. + +Ruled out: auto-restarting units that log errors (error ≠ failed) and +keyword-driven fix suggestions (vLater AI territory). + +*Full-sweep findings — all committed to v1 (decided 2026-07-07).* Every metric +was audited against the converged checklist (honest label, evidence digest +where a count hides detail, curation lifecycle where "expected" is config +knowledge, guarded per-item remedies, cross-links, watch-only tags). Adopted, +all prototyped: + +- /Storage/: disk top-consumers digest (evidence only — no file deletion + keys); per-device error rows on RAID1 when counters are nonzero, + cross-checked against SMART; SMART sub-line carries the last self-test + result. Spec note: a real scrub runs hours — the ring needs a running-% + state, not an instant reset. +- /Packages/: orphan digest (name + size) with per-package REMOVE (armed) and + KEEP — the curation lifecycle encoding "intentional, not orphaned" (the + rust lesson); batch REMOVE ALL skips kept packages. Per-file pacnew rows + tagged safe-delete (reflector-managed) vs needs-merge, MERGE delegating to + a terminal diff. CVEs named: package · CVE id · severity. AUR and firmware + names spelled out. +- /systemd/: failed units upgraded from a count-lever to a per-unit roster + (name · since · exit code · journalctl hint) with per-row RESTART + RESET; + is-system-running names its cause ("degraded — N failed units below"); + taint letters decoded. +- /Logs/: coredumps grouped by binary (count · last · coredumpctl hint), + cleared with the CLEAR action; kernel/hw events listed when not clean + (hardware — watch only). +- /Services/: docker system df breakdown (images / containers / volumes / + build cache with per-type reclaimable); stopped containers upgraded to the + full signal/expected curation lifecycle (MARK EXPECTED / UNMARK, shipped + default: winvm) with per-container START; cron expected-entries drift + roster. +- /Snapshots/: count split by type — timeline (auto-pruned) · single + (manual — escapes timeline cleanup, the pile-up risk) · pre/post — with + oldest-single named and DELETE STALE (armed, keeps newest 2) when singles + accumulate. +- /Network minors/: fail2ban shows recent-ban count; NTP shows offset. + +Rationale for prototyping everything (Craig): real estate and complexity have +bitten before — surface those limits in the disposable prototype, not after +functionality exists behind the UI. + +*Vertical compression → rotary band selector (2026-07-07).* First attempt — +the MEM·PWR three-column layout on Packages/Logs/Services — lost too much row +detail to third-width truncation (Craig's verdict after use). Replaced by a +*rotary band selector*: the amplifier input-selector idiom. A machined knob +(click to cycle) whose needle swings to engraved band labels, one per evidence +section (ORPHANS · PACNEW · ADVISORIES; SIGNAL · KNOWN NOISE · COREDUMPS · +KERNEL/HW; CONTAINERS · DOCKER DISK · CRON & BACKUPS). Each band carries its +own status lamp (section health at a glance without switching) plus a count; +the selected band gets a gold underline and the needle. One section renders at +a time at full width, restoring complete row detail. Deliberately distinct +from the category tiles, console keys, and the CPU-mode segmented control — +each selection idiom in the panel now has its own visual voice. MEM·PWR keeps +its three-column evidence strip (short rows fit fine at third-width). + +*Listeners get the same treatment (decided 2026-07-07).* The count becomes +"unexpected listeners" — evidence digest (process · port · bind address from +ss -tlnp), expected-list curation with the full MARK EXPECTED / UNMARK / +CLEAR lifecycle (shipped defaults: sshd, mpd, tailscaled; user marks in the +user layer), and guarded per-socket remedies: STOP (systemctl stop, armed) +when a unit owns the socket, KILL (SIGTERM, armed) otherwise. Severity keys +on unexpected AND public-bind (0.0.0.0/::) — a loopback listener warns, an +exposed one fails — and when ufw is down the signal header names the exposure +("ufw down — N public binds exposed"). Stopped containers likewise gained a +contextual START lever (allowlist: winvm), and the firewall its ENABLE. + +*Updates strip border is state-tiered (decided 2026-07-07).* Green when +nothing pending, amber for ordinary pending/AUR/firmware counts, red when +CVEs exist or pending exceeds the "a lot" threshold (or the update cache has +gone stale — staleness window in the TOML). The CVE badge renders only when +the count is nonzero. + +*Guard arms instead of blocking (revised 2026-07-07).* When the guard trips, +the key arms (red, "press again to run anyway — or apply from a TTY") rather +than hard-refusing. The user decides; the footgun is acknowledged and +deliberately handed over. After a system update lands, the panel offers a +reboot: a REBOOT key (arm-to-fire) appears on the updates strip and the +reboot-required metric flips. + +*No per-ecosystem update metrics.* Topgrade's step set (yay, rustup, cargo, +pipx, npm/pnpm, gem, go, flatpak, fwupd, tmux/zsh/nvim plugins, git repos) has +no cheap offline "updates available?" probe — mirroring it means a network +round-trip per registry at panel-open. Instead: one *topgrade freshness* +metric (wrapper stamps last-run time; threshold in the TOML) whose remedy is +the TOPGRADE lever, plus a *firmware updates* count in the updates strip +(fwupd refreshes metadata on its own timer; the panel reads the cache). + +Rationale for the hard lines: system updates are Workflow, never Auto — the +2026-06-07 Hyprland crash was a live -Syu swapping mesa+hyprland under the +running session, and the standing rule is never -Syu live under Hyprland when +the mesa/hyprland/wayland runtime is in the set. Hardware findings (SMART, MCE, +thermal) are None — the fix is replacing a drive or clearing a fan, not +software. + +* Metrics — Storage & filesystem integrity + +| Metric | Fix / lever | Automation | Notes | +|----------------------------------+----------------------------------+------------+-----------------------------------| +| Package cache size | paccache -r / -ruk0 | Auto | Reclaim, all re-downloadable | +|----------------------------------+----------------------------------+------------+-----------------------------------| +| paccache -rk1 (keep 1 version) | paccache -rk1 | Confirm | Frees most; kills downgrade | +| | | | headroom | +|----------------------------------+----------------------------------+------------+-----------------------------------| +| Disk usage (df) | — | None | Fix is cache/snapshot/prune | +| | | | levers | +|----------------------------------+----------------------------------+------------+-----------------------------------| +| Btrfs unallocated space | — | None | Chunk headroom; diagnostic | +|----------------------------------+----------------------------------+------------+-----------------------------------| +| Btrfs scrub age | btrfs scrub start | Confirm | GAP; ZFS has this, btrfs doesn't. | +| | | | IO-heavy, on-demand | +|----------------------------------+----------------------------------+------------+-----------------------------------| +| Btrfs device stats (per-drive | btrfs device stats --reset | None | GAP; RAID1 early-warning ahead of | +| error counters) | | | SMART; reset after review | +|----------------------------------+----------------------------------+------------+-----------------------------------| +| ZFS pool health / errors | — | None | CRITICAL if state != ONLINE or | +| | | | errors > 0 | +|----------------------------------+----------------------------------+------------+-----------------------------------| +| ZFS scrub age | zpool scrub | Confirm | Covered | +|----------------------------------+----------------------------------+------------+-----------------------------------| +| ZFS capacity | — | None | Perf degrades > 80%; the ZFS | +| | | | headroom metric (no unallocated | +| | | | concept) | +|----------------------------------+----------------------------------+------------+-----------------------------------| +| ZFS fragmentation (FRAG) | — | None | GAP; no defrag exists — the | +| | | | remedy is snapshot pruning + | +| | | | staying under 80% | +|----------------------------------+----------------------------------+------------+-----------------------------------| +| ZFS autotrim (SSD TRIM) | zpool set autotrim=on / zpool | Confirm | GAP; velox; the fstrim.timer | +| | trim | | counterpart on ZFS | +|----------------------------------+----------------------------------+------------+-----------------------------------| +| fstrim.timer enabled + firing | systemctl enable --now | Confirm | GAP; standard SSD hygiene | +| | fstrim.timer | | | +|----------------------------------+----------------------------------+------------+-----------------------------------| +| pacman file integrity (Qkk) | reinstall package | Workflow | GAP; modified/missing files need | +| | | | judgment | +|----------------------------------+----------------------------------+------------+-----------------------------------| +| SMART health | — | None | Hardware; replace drive | +|----------------------------------+----------------------------------+------------+-----------------------------------| +| SMART wear / temperature | — | None | Diagnostic | +|----------------------------------+----------------------------------+------------+-----------------------------------| + +* Metrics — Snapshots + +| Metric | Fix / lever | Automation | Notes | +|--------------------------------+--------------------------+------------+---------------------------------------------| +| Snapper count / retention | snapper cleanup / delete | Confirm | Manual (single) snapshots need explicit | +| | | | choice | +|--------------------------------+--------------------------+------------+---------------------------------------------| +| ZFS snapshot count / retention | zfs destroy | Confirm | Runaway retention | +|--------------------------------+--------------------------+------------+---------------------------------------------| +| Snapshot auto-timer running | systemctl enable timer | Confirm | Is the auto-snapshot service firing | +|--------------------------------+--------------------------+------------+---------------------------------------------| + +* Metrics — Packages & security + +| Metric | Fix / lever | Automation | Notes | +|-----------------------------------+------------------------------+------------+--------------------------------------| +| Orphaned packages | pacman -Rns (named args) | Confirm | Review first (rust looked orphaned, | +| | | | was intentional) | +|-----------------------------------+------------------------------+------------+--------------------------------------| +| Pending updates count | — | Workflow | Never auto; workflow-only | +|-----------------------------------+------------------------------+------------+--------------------------------------| +| .pacnew files | diff + merge / delete | Confirm | Allowlist auto-deletes mirrorlist / | +| | | | locale.gen | +|-----------------------------------+------------------------------+------------+--------------------------------------| +| arch-audit CVEs | reviewed update | Workflow | GAP; top-priority add. Fix is an | +| | | | update | +|-----------------------------------+------------------------------+------------+--------------------------------------| +| Keyring freshness | pacman -Sy archlinux-keyring | Confirm | GAP; stale keyring breaks update | +| | | | signatures | +|-----------------------------------+------------------------------+------------+--------------------------------------| +| AUR / foreign staleness (Qm) | paru -Sua | Workflow | GAP; AUR updates reviewed, not auto | +|-----------------------------------+------------------------------+------------+--------------------------------------| +| Reboot required (kernel mismatch) | reboot | Human | GAP; uname -r vs /usr/lib/modules. | +| | | | You pick when | +|-----------------------------------+------------------------------+------------+--------------------------------------| + +* Metrics — systemd & boot + +| Metric | Fix / lever | Automation | Notes | +|--------------------------------+------------------------+------------+-----------------------------------------------| +| is-system-running (degraded?) | — | None | GAP; one-token whole-system verdict; | +| | | | candidate for the bar glyph | +|--------------------------------+------------------------+------------+-----------------------------------------------| +| Failed systemd units | restart / investigate | Workflow | Depends why it failed | +|--------------------------------+------------------------+------------+-----------------------------------------------| +| Maintenance timers enabled + | systemctl enable timer | Confirm | GAP; meta-metric — are | +| firing | | | paccache/btrfs-scrub/fstrim/reflector/snapper | +| | | | firing | +|--------------------------------+------------------------+------------+-----------------------------------------------| +| Kernel taint flag | — | None | GAP; tainted != 0 = proprietary module or | +| | | | prior crash | +|--------------------------------+------------------------+------------+-----------------------------------------------| + +* Metrics — Logs & coredumps + +| Metric | Fix / lever | Automation | Notes | +|------------------------------------+---------------------------------+------------+----------------------------------| +| Coredump count | coredumpctl clean (keep recent) | Auto | Keep last few days for forensics | +|------------------------------------+---------------------------------+------------+----------------------------------| +| Journald disk usage | journalctl --vacuum-size/time | Auto | Bounded journal | +|------------------------------------+---------------------------------+------------+----------------------------------| +| App-log cleanup (>7d) | log-cleanup cron trigger | Auto | Already a cron; manual trigger | +|------------------------------------+---------------------------------+------------+----------------------------------| +| Journal error count (real vs | — | Workflow | Forensic; noise-filtered count | +| noise) | | | | +|------------------------------------+---------------------------------+------------+----------------------------------| +| Kernel/hardware events | — | None | Forensic, hardware | +| (MCE/USB/thermal/GPU) | | | | +|------------------------------------+---------------------------------+------------+----------------------------------| + +* Metrics — Memory, thermal, power + +| Metric | Fix / lever | Automation | Notes | +|-----------------------------------------+-------------+------------+--------------------------------------------| +| Memory free / OOM kills | — | Workflow | OOM = investigate | +|-----------------------------------------+-------------+------------+--------------------------------------------| +| Swap / zram present + healthy | — | None | Diagnostic | +|-----------------------------------------+-------------+------------+--------------------------------------------| +| CPU / GPU temperatures | — | None | Hardware | +|-----------------------------------------+-------------+------------+--------------------------------------------| +| Thermal throttling active | — | None | Cooling issue | +|-----------------------------------------+-------------+------------+--------------------------------------------| +| Battery health (capacity / cycles) | — | None | GAP; laptop; ties to open suspend todo | +|-----------------------------------------+-------------+------------+--------------------------------------------| +| Unclean-shutdown / suspend-failure rate | — | Workflow | GAP; ratio flagged ~75% unclean 2026-06-08 | +|-----------------------------------------+-------------+------------+--------------------------------------------| + +* Metrics — Network & security posture + +| Metric | Fix / lever | Automation | Notes | +|-----------------------------------+---------------------------+------------+-----------------------| +| DNS / NetworkManager reachability | restart NM | Workflow | CRITICAL if down | +|-----------------------------------+---------------------------+------------+-----------------------| +| Firewall active (ufw / nftables) | ufw enable | Confirm | GAP; security posture | +|-----------------------------------+---------------------------+------------+-----------------------| +| Unexpected listeners (ss -tlnp) | — | None | GAP; security review | +|-----------------------------------+---------------------------+------------+-----------------------| +| Tailscale peers | tailscale up | Confirm | Covered | +|-----------------------------------+---------------------------+------------+-----------------------| +| fail2ban running + bans | systemctl start | Confirm | Covered | +|-----------------------------------+---------------------------+------------+-----------------------| +| NTP sync (chrony) | systemctl restart chronyd | Confirm | Covered | +|-----------------------------------+---------------------------+------------+-----------------------| + +* Metrics — Services, backups, virt + +| Metric | Fix / lever | Automation | Notes | +|---------------------------------+--------------------------+------------+--------------------------------------------| +| rsyncshot backup freshness | — | Workflow | CRITICAL if daily > 48h; investigate | +| | | | failure | +|---------------------------------+--------------------------+------------+--------------------------------------------| +| Docker/podman reclaimable | prune tier 1 / tiers 2-3 | Confirm | Tier 1 nearly Auto; 2-3 destructive | +|---------------------------------+--------------------------+------------+--------------------------------------------| +| Docker stopped containers | — | None | Mostly expected (WinVM on-demand) | +|---------------------------------+--------------------------+------------+--------------------------------------------| +| libvirt VM state | — | None | Expected off | +|---------------------------------+--------------------------+------------+--------------------------------------------| +| Cron running + expected entries | systemctl enable cronie | Confirm | rsyncshot + log-cleanup entries | +|---------------------------------+--------------------------+------------+--------------------------------------------| + +* Architecture & testing (decided 2026-07-07) + +*CLI-first, GUI as a face.* The console ships as the fourth panel sibling: a +=maint= Python package in dotfiles (like =net/=, =bt/=, =audio/=) with probe +modules (read-only collectors), a remedies module, =cli.py=, and =gui.py= +driving the same code. =maint status --json= is the contract; =maint fix +<thing>= is every lever. The GUI never does anything the CLI can't. + +Safety mechanics baked into the CLI: +- global =--dry-run= prints the exact command instead of executing — free test + surface, and the GUI's arm-press can display it ("this will run: …"). +- hard read/write split: collectors never elevate; every remedy is an + allowlisted exact argv in one small auditable module. + +*Four test layers (safest → scariest):* + +1. /Unit, fake binaries — no VM, no root (~90% of surface)./ Probes are + parsers over command output: feed canned smartctl/btrfs/journalctl/pacman/ + ss/docker output via fakes on PATH (the net suite's fake-curl and audio's + fake-parec pattern). Remedies tested as command construction (assert the + argv, don't run it). The live-update guard is a pure function over a + package list. +2. /Read-only integration on the live machine./ All collectors are read-only + by design — =maint status --json= runs safely against real hosts. +3. /Remedies in a VM — archsetup's existing harness./ + =scripts/testing/run-test.sh= boots the installer VM; a maint scenario + breaks things deliberately over ssh (stop cronie, mask fstrim, orphan + packages, fill the cache), runs =maint fix …=, asserts post-state. No GUI + in the VM. Add qcow2 snapshot/restore between remedy tests so each starts + pristine and destructive remedies can't contaminate each other. Pure + pacman-level tests may use a throwaway systemd-nspawn container instead + (lighter); the VM stays for systemd/btrfs/reboot territory. +4. /GUI on the host, never in the VM./ AT-SPI smoke like the sibling panels, + driven by fixture data. The prototype's GOOD/BAD snapshots become those + fixtures — =MAINT_PANEL_FIXTURE=bad= renders the degraded board without a + degraded machine, conforming to the =maint status --json= schema. + +* Open questions + +- RESOLVED 2026-07-06 — live output wall (amber running / green done / red fail), + realtime, always shown. See "Doctor = live output wall" above. +- RESOLVED 2026-07-06 — velox is a first-class v1 target alongside ratio. See + "Scope: hosts" below. +- RESOLVED 2026-07-06 — single machine-readable thresholds file + (=maintenance-thresholds.toml=) is the source of truth, *owned by archsetup*. + Both the console and the system-health-check workflow read it, so they can + never drift. See "Thresholds source of truth" and "Workflow ownership" below. diff --git a/docs/design/system-monitor-design-ideas.org b/docs/design/system-monitor-design-ideas.org new file mode 100644 index 0000000..26619a0 --- /dev/null +++ b/docs/design/system-monitor-design-ideas.org @@ -0,0 +1,1008 @@ +#+TITLE: System Monitor Design Ideas +#+DATE: 2026-07-04 +#+TODO: TODO | DONE +#+TODO: DRAFT READY DOING | IMPLEMENTED SUPERSEDED CANCELLED + +* DRAFT Status +:PROPERTIES: +:ID: system-monitor-design-ideas +:END: +- [2026-07-04 Sat] DRAFT — initial design sketch for a health monitor covering + Archangel ISO/base-install health, Archsetup workstation health, and the local + laptop as the daily canary. + +* Metadata + +| Field | Value | +|--------+--------------------------------------------| +| Status | draft | +|--------+--------------------------------------------| +| Owner | Craig Jennings | +|--------+--------------------------------------------| +| Repos | archsetup, archangel, dotfiles | +|--------+--------------------------------------------| +| Kin | net panel, bluetooth panel, audio panel | +|--------+--------------------------------------------| + +* Problem + +Archangel and Archsetup can fail in ways that are individually obvious only +after the damage is done: an ISO build goes stale against Arch or archzfs, +ZFSBootMenu or GRUB boots once but not after the first upgrade, a snapshot +hook silently disappears, a package database ages out, systemd services fail +after a reboot, or the desktop contract is technically installed but not +usable. + +The health surface should compress those risks into one operational question: +"Can I trust a fresh install, and is this current workstation drifting away +from the known-good install contract?" + +This monitor is not a generic CPU/RAM graph. It is an install-health and +workstation-contract console. CPU, memory, and temperature belong only as +secondary context unless they block install/test operations. + +* Priority Model + +Rank metrics by the cost of blindness: what happens if Craig never sees the +metric, no one mitigates it, and the next install/upgrade/reboot simply happens. + +Severity: + +- =P0= — can cause data loss, unbootable systems, or loss of rollback path. +- =P1= — can break fresh installs, upgrades, remote access, or core + workstation use. +- =P2= — causes degraded workstation behavior, security drift, or accumulating + maintenance debt. +- =P3= — useful context, not a release gate by itself. + +The panel should sort by live severity first, then by this priority. A red =P2= +row appears above a green =P0= row, but in the steady state the layout keeps the +P0/P1 rows in the first viewport. + +* Priority Ranking + +| Rank | Priority | Metric | Why this rank exists | +|------+----------+---------------------------------+------------------------------------------------------------------------------| +| 1 | P0 | Storage health | Silent pool/filesystem degradation is the nearest thing to data loss. | +|------+----------+---------------------------------+------------------------------------------------------------------------------| +| 2 | P0 | Snapshot safety coverage | Without snapshots, upgrades lose their rollback safety net. | +|------+----------+---------------------------------+------------------------------------------------------------------------------| +| 3 | P0 | Bootloader and EFI redundancy | A machine that cannot boot is operationally dead. | +|------+----------+---------------------------------+------------------------------------------------------------------------------| +| 4 | P1 | First-upgrade bootability | Catches the classic "installed fine, broke after update" failure. | +|------+----------+---------------------------------+------------------------------------------------------------------------------| +| 5 | P1 | End-to-end VM install pass rate | Best release gate for the whole Archangel + Archsetup chain. | +|------+----------+---------------------------------+------------------------------------------------------------------------------| +| 6 | P1 | Package sync and repo freshness | Arch, archzfs, keyring, and mirror drift are leading break signals. | +|------+----------+---------------------------------+------------------------------------------------------------------------------| +| 7 | P1 | Archangel ISO reproducibility | If current inputs cannot build an ISO, recovery/install confidence is stale. | +|------+----------+---------------------------------+------------------------------------------------------------------------------| +| 8 | P1 | Post-install service health | Network, DNS, SSH, and user services decide whether the system is usable. | +|------+----------+---------------------------------+------------------------------------------------------------------------------| +| 9 | P1 | Archsetup state/log cleanliness | Prevents "half-installed but looks fine" machines. | +|------+----------+---------------------------------+------------------------------------------------------------------------------| +| 10 | P2 | Workstation contract checks | Confirms this is Craig's workstation, not just generic Arch. | +|------+----------+---------------------------------+------------------------------------------------------------------------------| +| 11 | P2 | Backup and rollback readiness | Catches loss of off-machine recovery and edited-file backups. | +|------+----------+---------------------------------+------------------------------------------------------------------------------| +| 12 | P2 | Security and hardening drift | Important, but usually less immediately destructive than boot/storage. | +|------+----------+---------------------------------+------------------------------------------------------------------------------| + +This order intentionally puts ZFS/Btrfs health above VM install evidence. A +broken future install is expensive; silent damage to the current root or backup +chain is worse. + +* Consequence Matrix + +This is the design justification for every metric. A row earns panel space only +if blindness has a clear failure mode and Doctor has at least a useful +diagnostic or mitigation. + +| Rank | Metric | If never seen / never mitigated | Typical failure | Worst plausible failure | Doctor posture | +|------+---------------------------------+-----------------------------------------------------------+----------------------------------------------------------------+-------------------------------------------------------------------+-------------------------------------------------| +| 1 | Storage health | Disk, pool, or metadata degradation accumulates silently. | Correctable ZFS/Btrfs errors, low EFI/root space, stale scrub. | Data loss, degraded root, failed import/mount during boot. | Diagnose + scrub/cleanup with confirmation. | +|------+---------------------------------+-----------------------------------------------------------+----------------------------------------------------------------+-------------------------------------------------------------------+-------------------------------------------------| +| 2 | Snapshot safety coverage | Upgrades proceed without a rollback point. | Missing recent snapshot or missing pre-pacman hook. | Bad upgrade cannot be rolled back cleanly; manual repair required. | Create snapshot; restore hook; never rollback. | +|------+---------------------------------+-----------------------------------------------------------+----------------------------------------------------------------+-------------------------------------------------------------------+-------------------------------------------------| +| 3 | Bootloader and EFI redundancy | Boot path rots until the next reboot or disk failure. | Missing GRUB/ZBM file on one EFI partition. | Unbootable machine after update, firmware reset, or disk loss. | Diagnose; regenerate config; advanced reinstall only. | +|------+---------------------------------+-----------------------------------------------------------+----------------------------------------------------------------+-------------------------------------------------------------------+-------------------------------------------------| +| 4 | First-upgrade bootability | Installs look good until the first real system update. | Kernel/initramfs/bootloader mismatch in VM. | Fresh bare-metal install dies on first reboot after upgrade. | VM-only upgrade test; collect boot evidence. | +|------+---------------------------------+-----------------------------------------------------------+----------------------------------------------------------------+-------------------------------------------------------------------+-------------------------------------------------| +| 5 | End-to-end VM install pass rate | Unit tests give false confidence about the real workflow. | Current branch fails one filesystem path or desktop assertion. | Bare-metal install fails mid-flight after disks are wiped. | Run/schedule VM test; clean stale VM artifacts. | +|------+---------------------------------+-----------------------------------------------------------+----------------------------------------------------------------+-------------------------------------------------------------------+-------------------------------------------------| +| 6 | Package sync/repo freshness | Arch/keyring/archzfs drift surprises the next install. | 404s, stale keyring, bad mirror, stale archzfs DB. | Installer cannot pacstrap or installs mismatched ZFS/kernel bits. | Refresh DB; update keyring; reflector. | +|------+---------------------------------+-----------------------------------------------------------+----------------------------------------------------------------+-------------------------------------------------------------------+-------------------------------------------------| +| 7 | ISO build reproducibility | Recovery/install media confidence becomes historical. | AUR package fails to build; mkarchiso or DKMS breaks. | Need rescue/install media and discover no current ISO can be built. | Parse logs; clean work; explicit rebuild. | +|------+---------------------------------+-----------------------------------------------------------+----------------------------------------------------------------+-------------------------------------------------------------------+-------------------------------------------------| +| 8 | Post-install service health | System is installed but degraded in daily operation. | DNS, NetworkManager, fail2ban, tailscale, or user service down. | No remote access, no network, broken sync, or security tooling off. | Restart/re-enable classified services only. | +|------+---------------------------------+-----------------------------------------------------------+----------------------------------------------------------------+-------------------------------------------------------------------+-------------------------------------------------| +| 9 | Archsetup state/log cleanliness | Half-completed provisioning masquerades as success. | Missing marker, log error, skipped step after resume. | Fresh workstation lacks critical config but looks mostly usable. | Summarize; rerun resumable archsetup. | +|------+---------------------------------+-----------------------------------------------------------+----------------------------------------------------------------+-------------------------------------------------------------------+-------------------------------------------------| +| 10 | Workstation contract checks | The box drifts from "Craig's workstation" to generic Arch. | Dotfile symlink broken, keyring wrong, missing tool/package. | Desktop/session workflow is broken during real work. | Restow, repair perms, reinstall with confirm. | +|------+---------------------------------+-----------------------------------------------------------+----------------------------------------------------------------+-------------------------------------------------------------------+-------------------------------------------------| +| 11 | Backup and rollback readiness | Recovery assumptions go stale. | Missing =.archsetup.bak=, backup timer stale, dry-run fails. | Local rollback works but important personal/system state is gone. | Dry-run, start configured job, no deletes. | +|------+---------------------------------+-----------------------------------------------------------+----------------------------------------------------------------+-------------------------------------------------------------------+-------------------------------------------------| +| 12 | Security and hardening drift | Small protective edits quietly regress. | SSH/firewall/fail2ban/sysctl/EFI mask drift. | Exposed service or weakened local boot/config protections. | Restore owned snippets; no broad rewrite. | +|------+---------------------------------+-----------------------------------------------------------+----------------------------------------------------------------+-------------------------------------------------------------------+-------------------------------------------------| + +The worst cases are intentionally conservative, not dramatic. The monitor is +useful because it catches the boring early signal: stale, missing, not recently +tested, or silently degraded. + +* Product Shape + +Use the existing instrument-console panel language rather than a dashboard page: +lamps for pass/warn/fail state, engraved metric groups, dense rows, physical +console keys for explicit actions, and an output well for the last diagnosis. + +The bar module should be tiny: + +- icon: a pulse/terminal glyph or compact =SYS= label. +- state lamp: green/yellow/red/grey. +- text: one word only: =OK=, =WARN=, =FAIL=, =STALE=, =RUNNING=, =UNKNOWN=. +- click: opens the system monitor panel. +- middle click or secondary action: run a cheap refresh only, never repairs. + +The panel should be one screen with internal scroll only where needed. The +first viewport should show the decision state and the Doctor controls without +scrolling. + +* Layout + +** Faceplate + +Top row: + +- State lamp + state word: =OK= / =WARN= / =FAIL= / =STALE= / =RUNNING= / + =UNKNOWN=. +- Unit label: =SYS·01=. +- Scope segmented control: =HOST= / =INSTALL= / =BUILD=. +- Badges: =ZFS= or =BTRFS=, =VM STALE=, =DB STALE=, =SNAPSHOT=, =BACKUP=, + =ROOT= when elevated actions are available. +- Close button. + +The scope control changes the metric emphasis, not the underlying data model: + +- =HOST= is this laptop/workstation right now. +- =INSTALL= is the last Archangel+Archsetup VM install result. +- =BUILD= is ISO, AUR repo, archzfs, and test artifact health. + +** Health Stack + +Arrange metrics as four horizontal bands. Each band has a section title, a +summary lamp, two to four row lamps, and a short "age" or "count" value. + +1. =BOOT + STORAGE= + - bootloader + - EFI redundancy + - pool/filesystem + - snapshots + +2. =INSTALL PIPELINE= + - first-upgrade reboot + - ZFS VM install + - Btrfs VM install + - ISO build + +3. =PACKAGES + SERVICES= + - pacman sync freshness + - archzfs/AUR health + - failed services + - journal errors + +4. =WORKSTATION CONTRACT= + - Archsetup markers/logs + - user/dotfiles + - desktop/session + - backups/replication + +Each row is clickable. Clicking a row opens the evidence drawer in the output +well with: + +- last command run +- normalized verdict +- raw excerpt, redacted where needed +- suggested Doctor action, if any + +** Console Keys + +Use physical console-key buttons, same family as net/bt: + +| Key | Purpose | +|--------------+------------------------------------------------------------| +| REFRESH | Cheap read-only probe of host state | +|--------------+------------------------------------------------------------| +| DOCTOR | Diagnose, classify, run safe mitigations, re-check | +|--------------+------------------------------------------------------------| +| TEST VM | Run or schedule Archsetup VM validation | +|--------------+------------------------------------------------------------| +| BUILD ISO | Run or schedule Archangel ISO build | +|--------------+------------------------------------------------------------| +| CLEAN | Clean old test artifacts, package cache, stale logs | +|--------------+------------------------------------------------------------| +| SNAPSHOT | Create a manual pre-change snapshot | +|--------------+------------------------------------------------------------| + +Keys that can take a long time stream progress into the output well. Mutating +keys must use arm-first behavior: + +- first click arms for 3 seconds and explains the action. +- second click runs. +- destructive cleanup names what will be deleted before it runs. + +* Metric Details + +Details below are grouped by workflow rather than priority. The authoritative +importance order is the priority table above. + +** 1. End-to-end VM install pass rate =P1/rank 5= + +Problem overcome: unit tests can pass while the real install is broken by +mirrors, bootloader state, pacstrap, SSH, disk layout, or the actual desktop +contract. This metric fights false confidence. + +Representation: + +- Lamp row in =INSTALL PIPELINE=. +- Two child lamps: =ZFS= and =BTRFS=. +- Age chip: =last pass 2d= / =never= / =stale 14d=. +- Red if either required filesystem has no recent pass. + +Tools: + +- =scripts/testing/run-test.sh= +- =scripts/testing/create-base-vm.sh= +- =pytest= testinfra suite under =scripts/testing/tests/= +- =qemu-img=, =qemu-system-x86_64=, =sshpass= + +Doctor: + +- read-only first: summarize last =test-results/*/test-report.txt= and failing + test names. +- mitigation: offer =TEST VM= for the failed filesystem. +- cleanup: remove stale temporary VM overlays before retrying. +- no automatic retry loop if the failure is in the installer itself. + +** 2. First-upgrade bootability =P1/rank 4= + +Problem overcome: the machine can boot immediately after install but fail after +the first =pacman -Syu= because initramfs hooks, ZFS modules, GRUB, ZFSBootMenu, +or kernel packages drift. + +Representation: + +- Lamp row: =first-upgrade reboot=. +- Badge: =not run=, =passed=, =failed=. +- Evidence drawer includes boot count, kernel version, and last reachable SSH + timestamp. + +Tools: + +- VM harness +- =pacman -Syu= +- =reboot= +- =ssh= reachability checks +- =journalctl -b -1= where available + +Doctor: + +- run upgrade-in-VM only, never on host without explicit confirmation. +- if failure is ZFS, collect =zpool import=, =lsinitcpio=, =mkinitcpio.conf=, + and EFI files. +- if failure is Btrfs, collect =grub.cfg=, =crypttab=, =fstab=, and snapper + config. + +** 3. Package database freshness and sync health =P1/rank 6= + +Problem overcome: Arch rolling-release state changes faster than installer +assumptions. Stale sync databases, stale keyrings, archzfs drift, or broken +mirrors are leading indicators of a failing install. + +Representation: + +- Row lamp in =PACKAGES + SERVICES=. +- Small meter: newest sync DB age vs threshold. +- Child chips: =core=, =extra=, =multilib=, =archzfs=. +- Yellow over 48 hours, red over 7 days or failed sync. + +Tools: + +- =find /var/lib/pacman/sync= +- =pacman -Syyu --needed archlinux-keyring= +- =checkupdates= +- =reflector= +- =pacman-conf= + +Doctor: + +- safe: refresh package databases. +- safe: update =archlinux-keyring= before full upgrades. +- mitigation: run =reflector= with the configured country/age policy. +- no unattended full system upgrade from the panel unless separately approved. + +** 4. Archangel ISO build reproducibility =P1/rank 7= + +Problem overcome: an old "good" ISO can hide broken current inputs. Archiso, +archzfs, DKMS, AUR package recipes, and pacoloco cache state can all break the +next install. + +Representation: + +- Row lamp in =INSTALL PIPELINE= or =BUILD= scope. +- Shows latest ISO date, kernel version, and AUR manifest age. +- Red if latest build failed or no ISO exists. +- Yellow if latest successful ISO is older than the configured freshness + window. + +Tools: + +- =make build= in =~/code/archangel= +- =build.sh --skip-aur= for fast non-AUR iteration +- =build-aur.sh= +- =mkarchiso= +- =pacoloco= status if installed + +Doctor: + +- read-only: parse latest =out/*.log= for pacman, DKMS, archzfs, AUR, and + mkarchiso failures. +- cleanup: safe build-work cleanup only through Archangel's cleanup function + or =make clean=. +- mitigation: suggest =--skip-aur= when the failure is unrelated to baked AUR. +- build retry is explicit via =BUILD ISO=, not automatic. + +** 5. ZFS/Btrfs storage health =P0/rank 1= + +Problem overcome: the root filesystem can degrade silently before the user +notices. For ZFS this means pool errors or degraded vdevs; for Btrfs this means +device stats, scrub failures, metadata pressure, or degraded RAID. + +Representation: + +- =BOOT + STORAGE= band. +- Filesystem-specific lamp grammar: + - ZFS green: =zpool status -x= healthy. + - Btrfs green: device stats clean and recent scrub clean. +- Capacity strip for root/home/EFI. + +Tools: + +- ZFS: =zpool status -x=, =zpool list=, =zfs list=. +- Btrfs: =btrfs device stats=, =btrfs filesystem usage=, + =btrfs scrub status=. +- Common: =df -h=, =findmnt=, =lsblk=. + +Doctor: + +- safe: start a scrub only with arm-first confirmation. +- safe: clear stale Btrfs stats only after a clean scrub and explicit + confirmation. +- mitigation: warn on low EFI/root space and offer package cache cleanup. +- never destroy snapshots, pools, subvolumes, or datasets from Doctor. + +** 6. Snapshot safety coverage =P0/rank 2= + +Problem overcome: rollback safety is assumed during upgrades but can disappear +when hooks, services, or snapshot tools drift. + +Representation: + +- Row lamp: =snapshots=. +- Child chips: =genesis=, =pre-pacman=, =recent=, =pruned=. +- Yellow if no recent snapshot. +- Red if genesis or pre-transaction hook is missing. + +Tools: + +- ZFS: =zfs list -t snapshot=, =zfs-pre-snapshot=, + =/etc/pacman.d/hooks/zfs-snapshot.hook=. +- Btrfs: =snapper list=, =snap-pac=, =grub-btrfs-mkconfig=, + =/.snapshots=. +- Common: =pacman -Q= for snapshot packages. + +Doctor: + +- safe: create a manual snapshot. +- safe: reinstall or re-enable missing hook only if the expected script exists. +- cleanup: prune only snapshots matching the tool-owned policy and prefix. +- mitigation: show exact command for manual rollback; do not perform rollback + from the panel. + +** 7. Bootloader and EFI redundancy =P0/rank 3= + +Problem overcome: single-disk bootloader success can mask missing redundant EFI +installs on multi-disk systems. A system can also pass install but lose a boot +entry or generate an invalid config. + +Representation: + +- Row lamp: =bootloader=. +- Child chips: =ZBM= or =GRUB=, =EFI=, =entries=, =all disks=. +- Yellow if redundancy cannot be proven. +- Red if the expected loader/config is missing. + +Tools: + +- =bootctl status= +- =efibootmgr -v= +- =findmnt /efi /boot= +- ZFS: check =/efi/EFI/ZBM/zfsbootmenu.efi=. +- Btrfs: check =/boot/grub/grub.cfg= and grub-btrfs entries. + +Doctor: + +- read-only by default. +- mitigation: regenerate GRUB config for Btrfs with arm-first confirmation. +- mitigation: rebuild initramfs with arm-first confirmation. +- no automatic EFI reinstall without an explicit advanced flow. + +** 8. Post-install service health =P1/rank 8= + +Problem overcome: the install can complete while the real workstation is +degraded: DNS broken, NetworkManager failed, fail2ban not responding, user +services not lingering, or Docker/Tailscale/Syncthing not in their expected +state. + +Representation: + +- =PACKAGES + SERVICES= band. +- Count badge: =0 failed= or =3 failed=. +- Child lamps: =network=, =dns=, =security=, =user services=. + +Tools: + +- =systemctl --failed= +- =systemctl is-enabled/is-active= +- =resolvectl status= +- =nmcli general status= +- =fail2ban-client status= +- =loginctl show-user= + +Doctor: + +- safe: restart known flaky non-destructive services such as + =NetworkManager= only after classifying the failure. +- safe: re-enable expected services from Archsetup's contract. +- mitigation: bounce DNS resolver and re-check. +- no blanket =systemctl restart --failed=. + +** 9. Archsetup state and log cleanliness =P1/rank 9= + +Problem overcome: a resumable installer can leave a half-finished system that +looks usable until a missing marker or skipped step matters later. + +Representation: + +- =WORKSTATION CONTRACT= band. +- Step-progress mini bar: completed markers / expected markers. +- Red if =archsetup --status= reports incomplete required steps. +- Red if latest log contains fatal errors. + +Tools: + +- =./archsetup --status= +- =/var/log/archsetup-*.log= +- marker files from the Archsetup state directory +- existing testinfra assertions in =scripts/testing/tests/test_archsetup.py= + +Doctor: + +- read-only: summarize incomplete steps and latest log errors. +- mitigation: offer to rerun =archsetup= in normal resumable mode. +- cleanup: archive old logs, keep the latest N. +- never run =--fresh= from Doctor. + +** 10. Workstation contract checks =P2/rank 10= + +Problem overcome: a fresh Arch system is not the goal. The goal is Craig's +working machine: user, shell, groups, dotfiles, Emacs, Hyprland/DWM, keyring, +VPN tools, Bluetooth tools, and local scripts. + +Representation: + +- =WORKSTATION CONTRACT= band. +- Child lamps: =user=, =dotfiles=, =desktop=, =tools=. +- Evidence drawer mirrors the testinfra checks. + +Tools: + +- =id=, =getent passwd= +- =test -L ~/.zshrc= +- =stow= via dotfiles Makefile +- =pacman -Q=, =yay -Qi yay= +- =hyprctl=, =gdbus= portal checks when session is running + +Doctor: + +- safe: restow dotfiles with the selected profile. +- safe: repair keyring directory permissions. +- mitigation: reinstall missing official packages. +- AUR package rebuilds require confirmation and stream output. + +** 11. Security and hardening drift =P2/rank 12= + +Problem overcome: security settings are easy to regress because they are small +file edits: SSH root login, EFI mount masks, firewall, issue banner, fail2ban, +quiet printk. + +Representation: + +- Compact row under =WORKSTATION CONTRACT= or =PACKAGES + SERVICES=. +- Red only for high-risk drift, yellow for unknown/unreadable state. + +Tools: + +- =sshd -T= or config file checks +- =ufw status= +- =fail2ban-client status= +- =findmnt /efi= +- =sysctl kernel.printk= + +Doctor: + +- safe: restore known Archsetup-owned config snippets. +- safe: re-enable firewall if policy file is present. +- mitigation: write missing drop-ins only from version-controlled templates. +- no broad hardening rewrite from panel state. + +** 12. Backup and rollback readiness =P2/rank 11= + +Problem overcome: rollback only helps local state. The install also needs +backups of edited system files and confidence that personal data replication is +not silently stale. + +Representation: + +- Row lamp: =backups=. +- Chips: =system-file .bak=, =replication=, =last run=. +- Yellow if last replication exceeds policy. +- Red if expected backup files for edited system config are missing. + +Tools: + +- Archsetup backup assertions in =scripts/testing/tests/test_backups.py=. +- =zfs-replicate= if configured. +- =systemctl list-timers= for backup timers. +- =journalctl -u= relevant backup units. + +Doctor: + +- safe: create missing =.archsetup.bak= for files before editing. +- safe: run dry-run replication check. +- mitigation: start a configured backup timer/unit with confirmation. +- never delete backup targets from Doctor. + +* Doctor Model + +Doctor is a classifier with bounded mitigations, not a magic repair button. + +Flow: + +1. Probe the selected scope. +2. Normalize each metric to =ok=, =warn=, =fail=, =unknown=, or =running=. +3. Classify failures as: + - =safe-fix= — local, reversible, low risk. + - =safe-cleanup= — removes only known generated artifacts. + - =mitigation= — improves the chance of success but does not claim repair. + - =needs-confirmation= — mutating, long-running, or system-wide. + - =manual= — too dangerous or context-heavy for Doctor. +4. Run only safe actions automatically after the user presses Doctor. +5. Arm-first for anything mutating beyond safe local cleanup. +6. Re-run the affected probe. +7. Stream a verdict into the output well. + +Doctor should say exactly what it did: + +#+BEGIN_EXAMPLE +doctor: package db stale + check: core.db age 4d, archzfs.db age 4d + action: refreshed sync databases + action: updated archlinux-keyring + result: ok, newest db age 2m +#+END_EXAMPLE + +* Common Tool Drivers + +** Host probes + +| Area | Commands | +|------------+----------------------------------------------------------------| +| systemd | =systemctl --failed=, =systemctl is-active=, =journalctl= | +|------------+----------------------------------------------------------------| +| packages | =pacman=, =checkupdates=, =pacman-conf=, =yay= | +|------------+----------------------------------------------------------------| +| storage | =zpool=, =zfs=, =btrfs=, =df=, =findmnt=, =lsblk= | +|------------+----------------------------------------------------------------| +| boot | =bootctl=, =efibootmgr=, =mkinitcpio=, =grub-mkconfig= | +|------------+----------------------------------------------------------------| +| network | =nmcli=, =resolvectl=, =ping= or HTTPS probe | +|------------+----------------------------------------------------------------| +| desktop | =hyprctl=, =gdbus=, =loginctl=, dotfiles Makefile | +|------------+----------------------------------------------------------------| + +** Project probes + +| Area | Commands | +|------------+----------------------------------------------------------------| +| archangel | =make test=, =make build=, =build.sh --skip-aur= | +|------------+----------------------------------------------------------------| +| archsetup | =make test-unit=, =make test=, =scripts/testing/run-test.sh= | +|------------+----------------------------------------------------------------| +| VM | =qemu-img=, =qemu-system-x86_64=, =sshpass=, =pytest= | +|------------+----------------------------------------------------------------| +| artifacts | latest =out/*.log=, =out/*aur-manifest.tsv=, =test-results/*= | +|------------+----------------------------------------------------------------| + +* Top-family Comparison + +This monitor should borrow the mature display ideas from =top=-style tools +without becoming another CPU/process viewer. The domain objects are install +contracts, boot/storage health, package freshness, snapshots, services, and +artifacts. The interaction model is still the same: sort the thing that hurts, +filter to the thing you care about, expand one row for evidence, and act only +when the diagnosis is clear. + +** Comparison table + +| Tool | What it represents well | Sorting/filtering model | Useful pattern for system monitor | Gaps for our domain | +|-------------------+---------------------------------------------------------------+---------------------------------------------------------------+-----------------------------------------------------------------+----------------------------------------------------------| +| =htop= | Dense live table plus configurable meters; process tree; direct process actions. | Interactive sort by column, search, filter, tree toggle. | Metric table should support column sort, search, filter, and tree/group mode. | No historical artifact model; actions are process-centric. | +|-------------------+---------------------------------------------------------------+---------------------------------------------------------------+-----------------------------------------------------------------+----------------------------------------------------------| +| =btop++= | Boxed dashboard: CPU, memory, disks, network, processes, battery, GPU; strong graph language; selected process detail. | Easy switching between process sort modes; filter; tree view; pause. | Use boxed bands, mini time-series, detail pane, pause/freeze, and clickable controls. | Graph-first layout can overemphasize volatile values over install risk. | +|-------------------+---------------------------------------------------------------+---------------------------------------------------------------+-----------------------------------------------------------------+----------------------------------------------------------| +| =bottom/btm= | Custom widget layout, per-widget focus/expand, zoomable time windows, basic mode. | Process widget supports sort, search, tree; widgets can be filtered/configured. | Every health band should be expandable; stale/history windows should be zoomable. | Mostly resource telemetry, not remediation workflow. | +|-------------------+---------------------------------------------------------------+---------------------------------------------------------------+-----------------------------------------------------------------+----------------------------------------------------------| +| =atop= | Interval deltas, critical-resource highlighting, all active processes including exited ones, long-term logs. | Resource views and interval replay; emphasizes deviations and active load. | Add history/replay for health events and show "new since last good" changes. | Lower immediate visual polish; Linux-performance scoped. | +|-------------------+---------------------------------------------------------------+---------------------------------------------------------------+-----------------------------------------------------------------+----------------------------------------------------------| +| =Glances= | Broad plugin dashboard, thresholds, remote/web/API modes, export to JSON/CSV/time-series backends. | Configurable visible plugins; API/stdout selectors instead of only interactive sorting. | Use plugin architecture, threshold config, JSON output, remote/headless mode. | Too broad; can become a generic monitoring surface. | +|-------------------+---------------------------------------------------------------+---------------------------------------------------------------+-----------------------------------------------------------------+----------------------------------------------------------| +| =procs= | Modern table ergonomics: custom columns, keyword search across selected fields, sort by named column, tree view. | CLI sort asc/desc by partial column name; watch mode cycles sort columns; AND/OR/NAND/NOR search. | Use named metric columns, saved views, multi-keyword filters, and value-aware coloring. | Process-only; no graphs or remediation model. | +|-------------------+---------------------------------------------------------------+---------------------------------------------------------------+-----------------------------------------------------------------+----------------------------------------------------------| +| =gotop/gtop= family | Fast glanceable terminal dashboard with compact graphs and gauges. | Usually lighter than htop/btop; less important than presentation density. | Use compact sparklines/gauges for "age", "last pass", and "failure count". | Not enough evidence/action depth for this monitor. | +|-------------------+---------------------------------------------------------------+---------------------------------------------------------------+-----------------------------------------------------------------+----------------------------------------------------------| + +** What to pull in + +*** htop: table discipline + +Pull: + +- Column headers that are real controls: click or key-cycle to sort by + =priority=, =state=, =age=, =last_checked=, =last_pass=, =failure_count=, + =scope=, and =doctor_class=. +- Search and filter as first-class actions, not hidden debug commands. +- Tree mode for ownership: + - =system= → =boot/storage= → =bootloader= → =efi entries=. + - =archangel= → =iso build= → =aur repo= → =manifest rows=. + - =archsetup= → =state markers= → =desktop= → =dotfiles=. +- Horizontal detail access for long evidence, like htop's horizontal scrolling + for full commands. + +Equivalent-or-better requirement: + +- htop sorts processes; this monitor sorts risk. The default sort is + live-state severity, then priority rank, then age. + +*** btop++: instrument boxes and live graphs + +Pull: + +- Boxed bands with stable geometry. +- Small time-series graphs, but only where history matters: + - package DB age over time + - failed-service count + - journal error count + - snapshot count / newest snapshot age + - VM pass/fail history + - ISO build duration/result history +- Selected-row detail pane with the last command, verdict, and raw excerpt. +- Pause/freeze button so a failure does not scroll away while reading. +- Mouse-clickable controls where every visible key has the same keyboard path. + +Equivalent-or-better requirement: + +- btop's graphs answer "what is hot right now?" Our graphs answer "is the + safety margin shrinking?" Trend charts should be muted unless the threshold + is crossed. + +*** bottom: focus/expand and layout presets + +Pull: + +- Expand one band full-height: + - =BOOT + STORAGE= expands into boot files, EFI entries, pools, snapshots. + - =INSTALL PIPELINE= expands into last VM runs and build artifacts. + - =PACKAGES + SERVICES= expands into DB ages, repo status, failed units. + - =WORKSTATION CONTRACT= expands into Archsetup markers and testinfra-style + checks. +- Zoomable history windows: 24h / 7d / 30d / all artifacts. +- Layout presets: + - =compact= for bar dropdown. + - =full= for terminal/TUI. + - =host-only= for laptop health. + - =release-gate= for Archangel/Archsetup changes. + +Equivalent-or-better requirement: + +- bottom expands widgets; this monitor expands evidence and remediation state. + The expanded view must show "what changed since last good" before raw logs. + +*** atop: history and vanished failures + +Pull: + +- Permanent, compact health-event log. +- Interval deltas instead of only current values: + - new failed services since last check + - new journal errors since last check + - packages/repos newly stale + - snapshot hook present before, missing now + - bootloader file changed since last known-good +- "Show active/deviating only" mode. In normal use, hide green rows unless + their age is approaching threshold. +- Replay mode: inspect the state at the time an install/test/build failed. + +Equivalent-or-better requirement: + +- atop can report processes that already exited. This monitor should report + failures that already passed through: a transient failed unit, a VM test that + failed last night, an ISO build that failed before the current successful + build, or a package DB that was stale until Doctor fixed it. + +*** Glances: plugin/API/export model + +Pull: + +- Plugin-like probes. Each metric owns: + - =probe= + - =normalize= + - =thresholds= + - =doctor_actions= + - =redaction= + - =evidence= +- JSON output as a stable contract before GTK work. +- Optional stdout selectors: + - =system-monitor --stdout packages.state,storage.state= + - =system-monitor --json boot,snapshots= +- Remote/headless mode for VMs and bare-metal test targets. +- Threshold config in one file, not hardcoded in the UI. + +Equivalent-or-better requirement: + +- Glances is broad; this must stay opinionated. A plugin is accepted only if it + maps to install health, rollback safety, workstation contract, or recovery + readiness. + +*** procs: custom columns and query grammar + +Pull: + +- Named columns and saved views: + - =risk=: state, priority, age, doctor class. + - =install=: last pass, filesystem, artifact, branch, commit. + - =host=: state, source, last checked, command. + - =doctor=: action class, requires root, reversible, last run. +- Multi-keyword search: + - =zfs failed= + - =doctor safe-fix= + - =archangel stale= + - =service red= +- Boolean query modes: + - AND default for narrowing. + - OR for "show any boot or storage issue". + - NOT for "hide green". +- Value-aware coloring for age, severity, and units. + +Equivalent-or-better requirement: + +- procs lets the user build a process table. This monitor should let Craig + build a risk table without editing code. + +*** gotop/gtop: glance density + +Pull: + +- Small sparklines for trend, not full charts. +- Big obvious state words. +- Compact gauges for bounded values: + - EFI usage + - root/home usage + - DB age as percent of freshness window + - VM evidence age + - snapshot age +- Simple default screen that is useful without learning keys. + +Equivalent-or-better requirement: + +- The first screen should answer "am I safe to upgrade or install?" in under + two seconds. + +* Sorting and Views + +The monitor needs two sorting layers: global row ordering and per-band evidence +tables. + +** Global row ordering + +Default: + +1. =state= severity: red, yellow, unknown, running, green. +2. =priority= rank: P0 before P1 before P2. +3. =age= or =staleness=, descending. +4. =last_changed=, newest first. + +Alternate sorts: + +| Sort key | Use case | +|-----------------+------------------------------------------------------| +| =priority= | Release-gate review; keep P0/P1 at the top. | +|-----------------+------------------------------------------------------| +| =state= | Triage; show all red/yellow rows first. | +|-----------------+------------------------------------------------------| +| =age= | Find stale tests, stale package DBs, old backups. | +|-----------------+------------------------------------------------------| +| =doctor_class= | Find what Doctor can safely fix now. | +|-----------------+------------------------------------------------------| +| =scope= | Group host vs install vs build. | +|-----------------+------------------------------------------------------| +| =last_changed= | See what recently regressed. | +|-----------------+------------------------------------------------------| +| =source= | Group by archangel, archsetup, dotfiles, host. | +|-----------------+------------------------------------------------------| + +** Per-band sorts + +| Band | Sorts | +|------------------------+------------------------------------------------------------| +| =BOOT + STORAGE= | severity, mountpoint, filesystem, capacity, last scrub, newest snapshot age | +|------------------------+------------------------------------------------------------| +| =INSTALL PIPELINE= | result, filesystem, duration, artifact age, commit age, last pass | +|------------------------+------------------------------------------------------------| +| =PACKAGES + SERVICES= | severity, unit name, repo name, DB age, error count, enabled/active state | +|------------------------+------------------------------------------------------------| +| =WORKSTATION CONTRACT= | severity, check name, owner repo, last pass, doctor class | +|------------------------+------------------------------------------------------------| + +** Filters + +Quick filters should be visible as chips: + +- =red= +- =yellow= +- =doctorable= +- =needs-root= +- =stale= +- =zfs= +- =btrfs= +- =host= +- =install= +- =build= +- =changed= +- =hidden-green= + +The default view can hide healthy low-priority rows, but it must show enough +green P0/P1 summary state to prove the monitor is working. + +* Display Requirements Borrowed from Tops + +1. Every table has sortable columns and a visible sort indicator. +2. Every visible metric row has a filterable state, priority, age, and source. +3. Every row can expand to evidence without losing the list context. +4. Every graph has a threshold marker; trend without threshold is decoration. +5. Every long-running action can be paused/frozen in the display. +6. Every mutating action has an equivalent CLI command shown in the output well. +7. Every Doctor action records before/after state so fixed failures remain + visible in history. +8. Every band has a compact mode and an expanded mode. +9. Green rows are quiet; new regressions are loud. +10. The system must be useful over SSH/TUI before GTK polish. + +* Source Notes + +- =htop=: upstream README describes configurable system/process display, + interactive sorting/filtering/search, tree view, and process actions. +- =btop++=: upstream README describes resource boxes, detailed process stats, + filter, sort switching, tree view, mouse support, auto-scaling network graphs, + disk IO, battery, GPU support, and themes. +- =bottom/btm=: upstream README describes customizable widgets, process sort + and search, tree mode, expand/focus, zoomable graph intervals, filters, and + basic mode. +- =atop=: upstream README describes interval resource accounting, critical + highlighting, long-term compressed logs, exited-process visibility, cgroup + views, and active/deviation-focused output. +- =Glances=: upstream README describes plugin-style broad monitoring, web/API + modes, stdout JSON/CSV, remote monitoring, exports, and threshold-oriented + dashboard use. +- =procs=: upstream README describes configurable columns, named-column sort, + watch mode, tree view, logical keyword search, value-aware coloring, and + pager behavior. + +* Data Model + +Emit JSON from a CLI first; the panel is a client. + +#+BEGIN_SRC json +{ + "v": 1, + "scope": "host", + "state": "warn", + "ts": "2026-07-04T12:00:00-04:00", + "metrics": [ + { + "id": "packages.sync_freshness", + "label": "package databases", + "state": "warn", + "summary": "archzfs.db age 4d", + "evidence": [ + {"command": "find /var/lib/pacman/sync", "excerpt": "archzfs.db 2026-06-30"} + ], + "doctor": { + "class": "safe-fix", + "actions": ["refresh-sync-db", "update-keyring"] + } + } + ] +} +#+END_SRC + +* Implementation Notes + +- Start with a CLI: =system-monitor status --json=, =system-monitor doctor + --json=, =system-monitor refresh=. +- Keep probes read-only by default. Actions live in separate verbs. +- Cache slow probes. The bar should read a cache, not run VM tests. +- VM/build actions should create job records and stream logs; the panel follows + the job rather than blocking the UI process. +- Reuse the net/bt panel architecture if this becomes a GTK panel: GTK-free + model + fake-command unit tests + one AT-SPI smoke. +- Redact secrets from logs and JSON: WiFi PSKs, tokens, private repo URLs with + credentials, SSH material, and backup target credentials. + +* Open Decisions + +** TODO Where should the first implementation live? + +Recommendation: dotfiles owns the user-facing panel and CLI wrapper because it +is workstation UI. Archsetup owns reusable install-contract probes and testinfra +assertions. Archangel owns ISO/build probes. + +** TODO Should Doctor run elevated actions through polkit or terminal? + +Recommendation: read-only checks run unprivileged; elevated actions launch a +terminal or polkit prompt with the exact command visible. Do not hide long +privileged operations inside the panel process. + +** TODO How fresh must VM evidence be? + +Recommendation: host checks go stale after 1 hour; package DB after 48 hours; +VM install evidence after 7 days; ISO build evidence after 14 days or whenever +Archangel/Archsetup has changed since the last successful artifact. + +** TODO Which actions are allowed on bare metal? + +Recommendation: host Doctor may refresh databases, update keyring, restow +dotfiles, create snapshots, run scrub, and restart narrowly classified services. +It may not perform full upgrades, bootloader reinstalls, destructive snapshot +prune, or filesystem repair without a separate advanced flow. + +* First Build Slice + +1. CLI read-only host status: + - package DB freshness + - failed services + - ZFS/Btrfs health + - snapshot presence + - Archsetup status/log check +2. Doctor safe actions: + - refresh package DB + - update keyring + - restow dotfiles + - create manual snapshot +3. Artifact parser: + - latest Archsetup VM test result + - latest Archangel ISO build result +4. Panel prototype: + - faceplate + - four health bands + - evidence output well + - REFRESH and DOCTOR keys only |
