aboutsummaryrefslogtreecommitdiff
path: root/docs/design
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-07-04 12:38:10 -0500
committerCraig Jennings <c@cjennings.net>2026-07-04 12:38:10 -0500
commit22101e1f1bc846885798b5815318a41110110a1c (patch)
treeab0b0656a990bbb14475fa3954102a4bec87f1df /docs/design
parent16a53cdff389e2059fa2e3c20de98e8c0a18dcb6 (diff)
downloadarchsetup-22101e1f1bc846885798b5815318a41110110a1c.tar.gz
archsetup-22101e1f1bc846885798b5815318a41110110a1c.zip
docs(spec): sort formal specs into docs/specs/ with lifecycle status
Moved the seven formal specs from docs/design/ into docs/specs/, each stamped with a lifecycle status heading: four IMPLEMENTED (bluetooth, net-other-interfaces, audio, instrument-console), one CANCELLED (file-manager-swallow), two DRAFT (desktop-settings, timer). Rewrote the seven todo.org links to the new paths. The two -spec.org files without the spec spine (waybar-network-module, waybar-timer-module) stayed in docs/design/ as notes.
Diffstat (limited to 'docs/design')
-rw-r--r--docs/design/2026-07-02-bluetooth-panel-spec.org470
-rw-r--r--docs/design/2026-07-02-desktop-settings-panel-spec.org128
-rw-r--r--docs/design/2026-07-02-file-manager-swallow-spec.org141
-rw-r--r--docs/design/2026-07-02-net-panel-other-interfaces-spec.org189
-rw-r--r--docs/design/2026-07-02-timer-panel-spec.org113
-rw-r--r--docs/design/2026-07-03-audio-panel-spec.org160
-rw-r--r--docs/design/2026-07-03-instrument-console-panels-spec.org158
7 files changed, 0 insertions, 1359 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-audio-panel-spec.org b/docs/design/2026-07-03-audio-panel-spec.org
deleted file mode 100644
index 16a087d..0000000
--- a/docs/design/2026-07-03-audio-panel-spec.org
+++ /dev/null
@@ -1,160 +0,0 @@
-#+TITLE: Audio Panel — the pulsemixer console
-#+AUTHOR: Craig Jennings
-#+DATE: 2026-07-03
-#+TODO: TODO | DONE
-#+TODO: DRAFT READY DOING | IMPLEMENTED SUPERSEDED CANCELLED
-
-* IMPLEMENTED Status
-:PROPERTIES:
-:ID: 71f556c6-ee02-47cc-a3be-68c8289380f3
-:END:
-- [2026-07-03 Fri] IMPLEMENTED — built in a no-approvals speedrun in the
- dotfiles repo (branch panel-bugfixing): engine (pactl), presenter, GTK
- panel, PTT arming, bar indicator, and the bar/keybind wiring, across four
- commits 65e5bb0..9601420. 102 unit tests + a passing AT-SPI smoke on velox.
- All five Decisions below resolved. Live-eyeball validation (visual polish,
- PTT-in-a-meeting, fader feel, the master-mute hardware key) is the one open
- follow-up, tracked as a manual-testing task in todo.org.
-- [2026-07-03 Fri] DRAFT — stub from the todo.org task "Audio panel spec"
- (roam ask 2026-07-02) plus the 2026-07-03 waybar/sound design discussion.
- Written to iterate alongside the prototype
- (=working/sound-panel/sound-panel-prototype.html=). Spine is present; the
- Decisions and Design detail get filled in as we go.
-
-* Metadata
-
-| Field | Value |
-|--------+---------------------------------------------------|
-| Status | implemented |
-|--------+---------------------------------------------------|
-| Owner | Craig Jennings |
-|--------+---------------------------------------------------|
-| Repo | dotfiles |
-|--------+---------------------------------------------------|
-| Kin | net panel + bt panel (architecture + aesthetic |
-| | donors), desktop-settings panel (sibling) |
-|--------+---------------------------------------------------|
-
-* Problem
-
-Audio control today is the pyprland audio scratchpad (Super+A) — a floating
-pulsemixer TUI — plus scattered bar affordances: =pulseaudio= (volume, click
-to mute sink), =pulseaudio#mic= (mic glyph + mic-toggle), Super+M audio-cycle
-ring, Super+Shift+A mic-toggle. There's no single glanceable surface that
-shows every sink and source, lets you set the default output/input, and
-carries the meeting-grade mic controls Craig wants (a clean muted mode and a
-hold-to-talk mode). The net + bluetooth panels set the pattern for exactly
-this shape; audio is the third instrument in the family.
-
-* Goals
-
-1. One panel, opened from the bar's sound glyph, exposing the full pulsemixer
- surface: every sink and source, per-device volume, per-device mute, and
- switching the default output and input.
-2. Replace the pyprland audio scratchpad (Super+A) as the primary audio UI.
-3. Mic modes for meetings: *live*, *muted*, and *push-to-talk* (mic stays
- muted except while Space is held, releasing re-mutes).
-4. A *master quick-mute* — one action mutes all output — reachable from the
- faceplate and a keybind.
-5. Instrument-console aesthetic and architecture consistent with net + bt:
- same faceplate, lamps, engraved sections, console keys, needle gauges,
- verify-everything contract.
-6. The bar glyph reflects live state: speaker + three arcs normally, a
- speaker-with-✕ when muted (Craig's called glyphs).
-
-* Design sketch
-
-Prototype: =working/sound-panel/sound-panel-prototype.html= (the reference for
-layout + idioms below).
-
-** Surface (from the prototype)
-
-- *Faceplate* — status lamp, sound glyph, state word (PLAYBACK / MUTED), a
- MUTED badge, the SND·01 unit label, and the *master quick-mute switch*
- (same switch idiom as net wifi / bt power), plus the close ✕.
-- *OUTPUTS section* — one row per sink. Row body click = set default (gold
- DEF tag). A machined fader sets that sink's volume; the trailing glyph
- mutes just that device. Active/default row is emphasized (cream name, gold
- lamp/glyph).
-- *INPUTS section* — one row per source, same idioms.
-- *Mic mode* — three console keys: LIVE / MUTED / PUSH·TALK. Push-to-talk
- keeps the mic muted (red IN needle) and un-mutes only while Space is held.
-- *Twin VU needles* — output level + input level, the sound analog of net
- throughput and bt battery gauges. Needle goes red when its side is muted.
-
-** Architecture — clone the net/bt panel stack
-
-- GTK4 + gtk4-layer-shell, Blueprint =.blp= → committed =.ui= (=make ui=,
- dev-only build dep).
-- Humble-object split: a GTK-free PanelModel presenter (unit-tested like the
- net/bt PanelModels) + thin composite-widget pages. Backing actions in a
- GTK-free =audio.py= that shells to the audio control layer (pactl /
- wpctl / pulsemixer — pick below), TDD'd with fake binaries.
-- One gated AT-SPI smoke (=run-panel-smoke.sh= pattern).
-- Shared instrument-console palette CSS asset (the one net/bt/settings all
- load) — do not duplicate the palette block.
-- Code lives in dotfiles =audio/= sibling to =net/= (src-layout, tests in
- =tests/audio/=).
-
-* Decisions (Craig)
-
-** DONE Audio control backend — pactl vs wpctl vs pulsemixer
-CLOSED: [2026-07-03 Fri]
-Resolved: =pactl= (the engine module is =pactl.py=). Both ratio and velox run
-PipeWire with the pipewire-pulse compat layer and no PulseAudio daemon, so
-pactl and wpctl hit the same graph — but =pactl -f json= gives structured,
-name-addressable output where wpctl offers only a volatile-id tree. Reads go
-through =pactl -f json list sinks|sources= + =get-default-*=; writes target
-devices by stable name behind an argv-charset guard.
-
-** DONE Push-to-talk mechanism under Wayland (feasibility — phase 1)
-CLOSED: [2026-07-03 Fri]
-Resolved: route (a), Hyprland dynamic binds. The phase-1 spike confirmed all
-three primitives on velox (Hyprland 0.55.4): =hyprctl keyword bind/unbind=
-adds and removes a bind live, =bindr= fires on release, and =pactl
-set-source-mute @DEFAULT_SOURCE@ 0|1= toggles the mic cleanly. =ptt.py= arms a
-press bind (un-mute) + a bindr (re-mute) on entering PTT mode and unbinds on
-leaving, so the talk key isn't grabbed globally otherwise. No evdev needed.
-Documented behavior: while PTT is armed, the talk key is the talk key.
-
-** DONE Quick-mute keybind + scope
-CLOSED: [2026-07-03 Fri]
-Resolved: the XF86AudioMute hardware key (Super+Shift+M turned out to be taken
-by the monocle-layout bind, so the spec's assumption was wrong). The mute key
-now runs =audio quick-mute=, which mutes every output (master), not just the
-default sink — identical on a single-sink machine, correct on a multi-sink
-one. Also reachable from the faceplate master switch and the panel. Scope:
-master mute of all sinks, with verify-after-apply per sink.
-
-** DONE Bar glyph click map
-CLOSED: [2026-07-03 Fri]
-Resolved with the low-regret wiring: kept the existing =pulseaudio= waybar
-module (left-click mute, scroll volume — no regression) and repointed its
-right-click from the retired pulsemixer scratchpad to =audio-panel=. So: left
-= mute, right = open panel, scroll = volume. A fuller =custom/audio= indicator
-(state-following speaker glyph + its own click map) is built and tested
-(=indicator.py= + =waybar-audio=) but stays unwired until the new bar glyph
-gets a live eyeball — the swap is a one-line waybar edit when Craig's ready.
-
-** DONE Fate of the existing audio affordances
-CLOSED: [2026-07-03 Fri]
-Resolved: Super+A repurposed from =pypr toggle audio= (the pulsemixer
-scratchpad) to =audio-panel= — the panel is the primary audio UI now, so the
-scratchpad is retired. Its definition still sits in the machine-local
-=pyprland.toml= (not stowed) and can be deleted by hand. Kept: =pulseaudio= +
-=pulseaudio#mic= waybar modules (glance + scroll + the mic-mute glance),
-Super+M cycle, Super+Shift+A + XF86AudioMicMute mic-toggle. Changed:
-XF86AudioMute → master quick-mute (see the quick-mute decision above).
-
-* Implementation phases
-
-1. Push-to-talk feasibility spike (decision above) — the one unknown; settle
- the mechanism before committing the mic-mode design.
-2. =audio.py= backings (list/get/set/mute/default for sinks + sources) —
- pure engine, TDD with a fake audio backend.
-3. PanelModel presenter (rows, default tracking, mic modes, master mute,
- verify-after-apply) — unit-tested, no GTK.
-4. Blueprint UI + sound bar glyph (normal / muted / ptt states) + open/close
- wiring; shared palette css; AT-SPI smoke.
-5. Bar-affordance consolidation per the decision above; retire the Super+A
- scratchpad; keybinds.
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.