From 22101e1f1bc846885798b5815318a41110110a1c Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Sat, 4 Jul 2026 12:38:10 -0500 Subject: 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. --- docs/design/2026-07-02-bluetooth-panel-spec.org | 470 -------------------- .../2026-07-02-desktop-settings-panel-spec.org | 128 ------ .../2026-07-02-file-manager-swallow-spec.org | 141 ------ .../2026-07-02-net-panel-other-interfaces-spec.org | 189 -------- docs/design/2026-07-02-timer-panel-spec.org | 113 ----- docs/design/2026-07-03-audio-panel-spec.org | 160 ------- .../2026-07-03-instrument-console-panels-spec.org | 158 ------- docs/specs/2026-07-02-bluetooth-panel-spec.org | 476 +++++++++++++++++++++ .../2026-07-02-desktop-settings-panel-spec.org | 134 ++++++ .../specs/2026-07-02-file-manager-swallow-spec.org | 147 +++++++ .../2026-07-02-net-panel-other-interfaces-spec.org | 195 +++++++++ docs/specs/2026-07-02-timer-panel-spec.org | 119 ++++++ docs/specs/2026-07-03-audio-panel-spec.org | 166 +++++++ .../2026-07-03-instrument-console-panels-spec.org | 164 +++++++ todo.org | 14 +- 15 files changed, 1408 insertions(+), 1366 deletions(-) delete mode 100644 docs/design/2026-07-02-bluetooth-panel-spec.org delete mode 100644 docs/design/2026-07-02-desktop-settings-panel-spec.org delete mode 100644 docs/design/2026-07-02-file-manager-swallow-spec.org delete mode 100644 docs/design/2026-07-02-net-panel-other-interfaces-spec.org delete mode 100644 docs/design/2026-07-02-timer-panel-spec.org delete mode 100644 docs/design/2026-07-03-audio-panel-spec.org delete mode 100644 docs/design/2026-07-03-instrument-console-panels-spec.org create mode 100644 docs/specs/2026-07-02-bluetooth-panel-spec.org create mode 100644 docs/specs/2026-07-02-desktop-settings-panel-spec.org create mode 100644 docs/specs/2026-07-02-file-manager-swallow-spec.org create mode 100644 docs/specs/2026-07-02-net-panel-other-interfaces-spec.org create mode 100644 docs/specs/2026-07-02-timer-panel-spec.org create mode 100644 docs/specs/2026-07-03-audio-panel-spec.org create mode 100644 docs/specs/2026-07-03-instrument-console-panels-spec.org 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 - = 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 =. -- Pairing: the one interactive corner. =bluetoothctl pair = 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 = (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=, 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 =, 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: by - "), 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 · 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//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/specs/2026-07-02-bluetooth-panel-spec.org b/docs/specs/2026-07-02-bluetooth-panel-spec.org new file mode 100644 index 0000000..f1b3ac1 --- /dev/null +++ b/docs/specs/2026-07-02-bluetooth-panel-spec.org @@ -0,0 +1,476 @@ +#+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 Bluetooth Panel — CLI-Driven, Net-Panel Kin +:PROPERTIES: +:ID: 8af6a76a-5665-4d20-9efd-ffdf7460c981 +:END: +- 2026-07-04 Sat @ 12:36:56 -0500 — retrofitted by spec-sort; status set to IMPLEMENTED (reason: Shipped through phase 3; build task DONE and manual tests filed.) + +* 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 + = 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 =. +- Pairing: the one interactive corner. =bluetoothctl pair = 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 = (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/specs/2026-07-02-desktop-settings-panel-spec.org b/docs/specs/2026-07-02-desktop-settings-panel-spec.org new file mode 100644 index 0000000..d147249 --- /dev/null +++ b/docs/specs/2026-07-02-desktop-settings-panel-spec.org @@ -0,0 +1,134 @@ +#+TITLE: Desktop-Settings Dropdown Panel +#+AUTHOR: Craig Jennings +#+DATE: 2026-07-02 +#+TODO: TODO | DONE +#+TODO: DRAFT READY DOING | IMPLEMENTED SUPERSEDED CANCELLED + +* DRAFT Desktop-Settings Dropdown Panel +:PROPERTIES: +:ID: d6bb1e73-ec90-4327-85ee-bfa762da5bce +:END: +- 2026-07-04 Sat @ 12:36:56 -0500 — retrofitted by spec-sort; status set to DRAFT (evidence-based, human-confirmed) + +* 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/specs/2026-07-02-file-manager-swallow-spec.org b/docs/specs/2026-07-02-file-manager-swallow-spec.org new file mode 100644 index 0000000..b898f11 --- /dev/null +++ b/docs/specs/2026-07-02-file-manager-swallow-spec.org @@ -0,0 +1,147 @@ +#+TITLE: File-Manager Swallow Pattern +#+AUTHOR: Craig Jennings +#+DATE: 2026-07-02 +#+TODO: TODO | DONE +#+TODO: DRAFT READY DOING | IMPLEMENTED SUPERSEDED CANCELLED + +* CANCELLED File-Manager Swallow Pattern +:PROPERTIES: +:ID: 179a1cd2-7a02-4c44-a09d-685c5a154895 +:END: +- 2026-07-04 Sat @ 12:36:56 -0500 — retrofitted by spec-sort; status set to CANCELLED (reason: Native swallow ruled out by test; reassigned to .emacs.d dirvish handling.) + +* 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=, 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/specs/2026-07-02-net-panel-other-interfaces-spec.org b/docs/specs/2026-07-02-net-panel-other-interfaces-spec.org new file mode 100644 index 0000000..0d63feb --- /dev/null +++ b/docs/specs/2026-07-02-net-panel-other-interfaces-spec.org @@ -0,0 +1,195 @@ +#+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 Net Panel — Tailscale, VPN, and WireGuard Interfaces +:PROPERTIES: +:ID: 09f4cd40-f391-4eba-a4ff-c22bad00ad7f +:END: +- 2026-07-04 Sat @ 12:36:56 -0500 — retrofitted by spec-sort; status set to IMPLEMENTED (reason: Tunnels track shipped detection, diagnose, and panel bring-up; build task DONE.) + +* 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 =, 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/specs/2026-07-02-timer-panel-spec.org b/docs/specs/2026-07-02-timer-panel-spec.org new file mode 100644 index 0000000..c0dbd2c --- /dev/null +++ b/docs/specs/2026-07-02-timer-panel-spec.org @@ -0,0 +1,119 @@ +#+TITLE: Timer GTK Panel +#+AUTHOR: Craig Jennings +#+DATE: 2026-07-02 +#+TODO: TODO | DONE +#+TODO: DRAFT READY DOING | IMPLEMENTED SUPERSEDED CANCELLED + +* DRAFT Timer GTK Panel +:PROPERTIES: +:ID: 25ed5321-f035-42b3-b115-69364d775f41 +:END: +- 2026-07-04 Sat @ 12:36:56 -0500 — retrofitted by spec-sort; status set to DRAFT (evidence-based, human-confirmed) + +* 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/specs/2026-07-03-audio-panel-spec.org b/docs/specs/2026-07-03-audio-panel-spec.org new file mode 100644 index 0000000..82041ed --- /dev/null +++ b/docs/specs/2026-07-03-audio-panel-spec.org @@ -0,0 +1,166 @@ +#+TITLE: Audio Panel — the pulsemixer console +#+AUTHOR: Craig Jennings +#+DATE: 2026-07-03 +#+TODO: TODO | DONE +#+TODO: DRAFT READY DOING | IMPLEMENTED SUPERSEDED CANCELLED + +* IMPLEMENTED Audio Panel — the pulsemixer console +:PROPERTIES: +:ID: 9175e017-46ad-4887-ae45-887e9551c005 +:END: +- 2026-07-04 Sat @ 12:36:56 -0500 — retrofitted by spec-sort; status set to IMPLEMENTED (reason: Shipped; build task DONE, manual tests filed.) + +* 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/specs/2026-07-03-instrument-console-panels-spec.org b/docs/specs/2026-07-03-instrument-console-panels-spec.org new file mode 100644 index 0000000..c0a0c56 --- /dev/null +++ b/docs/specs/2026-07-03-instrument-console-panels-spec.org @@ -0,0 +1,164 @@ +#+TITLE: Instrument-console rebuild — net + bluetooth panels +#+DATE: 2026-07-03 +#+TODO: TODO | DONE +#+TODO: DRAFT READY DOING | IMPLEMENTED SUPERSEDED CANCELLED + +* IMPLEMENTED Instrument-console rebuild — net + bluetooth panels +:PROPERTIES: +:ID: ac23e996-a51a-466b-ad80-2faff46447bf +:END: +- 2026-07-04 Sat @ 12:36:56 -0500 — retrofitted by spec-sort; status set to IMPLEMENTED (reason: Panel rebuild shipped (dotfiles e993c3f); build task DONE.) + +* 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: by + "), 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 · 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//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/todo.org b/todo.org index e9c419f..abd4eee 100644 --- a/todo.org +++ b/todo.org @@ -189,7 +189,7 @@ Rescoped 2026-07-04 (audit): the tunnels track already shipped most of the origi :PROPERTIES: :LAST_REVIEWED: 2026-07-02 :END: -Initial spec written 2026-07-02: [[file:docs/design/2026-07-02-timer-panel-spec.org]] (DRAFT — four decisions await Craig's review before build; net-panel Blueprint/GTK4 stack, wtimer stays the state owner). +Initial spec written 2026-07-02: [[file:docs/specs/2026-07-02-timer-panel-spec.org]] (DRAFT — four decisions await Craig's review before build; net-panel Blueprint/GTK4 stack, wtimer stays the state owner). From Craig's roam capture 2026-07-02: give the timer a GTK UI/UX like the network panel. @@ -197,7 +197,7 @@ From Craig's roam capture 2026-07-02: give the timer a GTK UI/UX like the networ :PROPERTIES: :LAST_REVIEWED: 2026-06-24 :END: -Initial spec written 2026-07-02: [[file:docs/design/2026-07-02-desktop-settings-panel-spec.org]] (DRAFT — four decisions await Craig's review before build; architecture updated to the net panel's Blueprint/GTK4 stack). +Initial spec written 2026-07-02: [[file:docs/specs/2026-07-02-desktop-settings-panel-spec.org]] (DRAFT — four decisions await Craig's review before build; architecture updated to the net panel's Blueprint/GTK4 stack). One waybar dropdown gathering the desktop toggles and sliders into a single settings panel, opened from a gear/settings glyph on the bar. Incorporate: - *Auto-dim* toggle (the =custom/dim= feature just shipped — fold in here, or keep the standalone indicator and mirror it). @@ -1284,7 +1284,7 @@ CLOSED: [2026-07-02 Thu] :PROPERTIES: :SPEC_ID: 1271a845-4463-4831-9902-990eda6b2265 :END: -Spec: [[file:docs/design/2026-07-02-bluetooth-panel-spec.org]] (IMPLEMENTED 2026-07-02 — all five phases shipped same day: engine eb2230f, panel 76b2c05, bar module e372de3, bt-priv + blueman retirement 2a026b1/d8d8c53, install wiring proven by VM assertions). Residual: the phase 4-5 VM assertions run on the next VM pass; ratio picks up the package removal + hand-links on its trip list. +Spec: [[file:docs/specs/2026-07-02-bluetooth-panel-spec.org]] (IMPLEMENTED 2026-07-02 — all five phases shipped same day: engine eb2230f, panel 76b2c05, bar module e372de3, bt-priv + blueman retirement 2a026b1/d8d8c53, install wiring proven by VM assertions). Residual: the phase 4-5 VM assertions run on the next VM pass; ratio picks up the package removal + hand-links on its trip list. A bluetooth panel driving a CLI underneath (bluetoothctl one-shot verbs), consistent in look and feel with the net panel (GTK4 + layer-shell + Blueprint, humble-object presenter, verify-everything). Minimalistic interface, full functionality, plus a diagnostics/troubleshooting section mirroring the net panel's Diagnostics tab. Bar module glyph opens it. Craig's ask (2026-07-02): follow UX/UI best practices; where the net panel's patterns conflict with best practices, file a net-panel bug task rather than clone the flaw. @@ -1407,7 +1407,7 @@ CLOSED: [2026-07-03 Fri] :PROPERTIES: :SPEC_ID: e73877f5-4f5b-4f81-b946-dbaa6145e0d5 :END: -The no-approvals speedrun build of the console design Craig approved through five prototype iterations (2026-07-02/03). Spec: [[file:docs/design/2026-07-03-instrument-console-panels-spec.org]] — the interactive prototype [[file:assets/2026-07-03-instrument-console-panels-prototype.html][assets/2026-07-03-instrument-console-panels-prototype.html]] is the normative design reference. Folds three open tasks: network panel redesign, bt switch placement + title, bt rename devices. Code in ~/.dotfiles (net/, bluetooth/, themes/dupre/panel.css). Final step: flip the spec to IMPLEMENTED, write the findings summary to file, finalize session context. +The no-approvals speedrun build of the console design Craig approved through five prototype iterations (2026-07-02/03). Spec: [[file:docs/specs/2026-07-03-instrument-console-panels-spec.org]] — the interactive prototype [[file:assets/2026-07-03-instrument-console-panels-prototype.html][assets/2026-07-03-instrument-console-panels-prototype.html]] is the normative design reference. Folds three open tasks: network panel redesign, bt switch placement + title, bt rename devices. Code in ~/.dotfiles (net/, bluetooth/, themes/dupre/panel.css). Final step: flip the spec to IMPLEMENTED, write the findings summary to file, finalize session context. *** 2026-07-03 Fri @ 03:20:00 -0400 Phase 2 shipped: net GTK-free console layer + engine verbs Dotfiles =81ec9c3= (TDD, 52 new tests, 581 net green). Pure presenter logic for the single-screen console, no view code touched: =viewmodel.net_faceplate= (state word + lamp + TUNNEL/AIRPLANE badges, wired-link-wins precedence), =network_console_rows= (ethernet pinned, radio-off note, active-then-signal sort, per-row lamp/caption/ladder/forget), =channel_headline= (wired device+speed / SSID+ladder+dBm / not-connected placeholder), =tunnel_console_rows=, dial-meter geometry (=meter_needle_deg= + =meter_scale= 100→1000 auto-relabel), =signal_bars=/=mbps_label=, and =panel.ArmState= (two-click arm-to-fire for forget/disconnect). Engine verbs: =manage.wifi_radio= (nmcli radio wifi on|off), =manage.device_up= (ethernet take-the-route), =sysio.link_speed_mbps= (/sys wired speed), =connections.ethernet_devices=, hidden flag on =manage.add=. @@ -1489,7 +1489,7 @@ CLOSED: [2026-07-02 Thu] :PROPERTIES: :SPEC_ID: 79a1075a-4b56-4f25-a861-b69f120a636a :END: -Spec: [[file:docs/design/2026-07-02-net-panel-other-interfaces-spec.org]] (DOING — reviewed READY and decomposed 2026-07-02 evening; all four decisions were resolved same morning, claims re-verified live at review: protonvpn binary, tailscale JSON shape, seven importable wireguard configs). +Spec: [[file:docs/specs/2026-07-02-net-panel-other-interfaces-spec.org]] (DOING — reviewed READY and decomposed 2026-07-02 evening; all four decisions were resolved same morning, claims re-verified live at review: protonvpn binary, tailscale JSON shape, seven importable wireguard configs). Tunnels visible and controllable in the net panel: tailscale + NM wireguard + proton-vpn-cli probes, a Tunnels group in Connections, diagnose/doctor route-ownership awareness, a bar badge when a tunnel owns the default route, archsetup operator flag + package swap, and the one-time NM import of the seven Proton configs. Origin: roam inbox capture 2026-07-02. @@ -1518,7 +1518,7 @@ CLOSED: [2026-07-02 Thu] :PROPERTIES: :LAST_REVIEWED: 2026-07-02 :END: -Reassigned to .emacs.d 2026-07-02 (handoff: =~/.emacs.d/inbox/2026-07-02-2231-from-archsetup-dirvish-popup-swallow-handoff.org=). The "file manager" is the dirvish popup (Super+F, an Emacs frame), not nautilus — so the fix is elisp in dirvish's external-open path (=cj/xdg-open=): spawn the handler directly with =start-process=, hide the popup frame, restore it from the process sentinel, notify on non-zero exit. The spec drafted here first ([[file:docs/design/2026-07-02-file-manager-swallow-spec.org]], now CANCELLED) records the feasibility finding that stays useful: gio/xdg-open launches double-fork, so no PID-ancestry approach (Hyprland native swallow included) can ever connect viewer to launcher. +Reassigned to .emacs.d 2026-07-02 (handoff: =~/.emacs.d/inbox/2026-07-02-2231-from-archsetup-dirvish-popup-swallow-handoff.org=). The "file manager" is the dirvish popup (Super+F, an Emacs frame), not nautilus — so the fix is elisp in dirvish's external-open path (=cj/xdg-open=): spawn the handler directly with =start-process=, hide the popup frame, restore it from the process sentinel, notify on non-zero exit. The spec drafted here first ([[file:docs/specs/2026-07-02-file-manager-swallow-spec.org]], now CANCELLED) records the feasibility finding that stays useful: gio/xdg-open launches double-fork, so no PID-ancestry approach (Hyprland native swallow included) can ever connect viewer to launcher. When the file manager launches another app, it should hide to a special workspace (the "swallow" pattern) and return when that process ends, rather than vanishing. Today it disappears with no signal of whether it's coming back, so the user can't tell success from failure — they should quit explicitly instead. Origin: roam inbox capture. @@ -1704,6 +1704,6 @@ CLOSED: [2026-07-03 Fri] :PROPERTIES: :LAST_REVIEWED: 2026-07-02 :END: -Went past the spec to a full build in a no-approvals speedrun. Spec is now IMPLEMENTED ([[file:docs/design/2026-07-03-audio-panel-spec.org]], all 5 Decisions resolved). The panel shipped in the dotfiles repo (branch panel-bugfixing, commits 65e5bb0..9601420): pactl engine, GTK-free presenter, GTK instrument-console panel (OUTPUTS/INPUTS device rows with faders + per-device mute, LIVE/MUTED/PUSH·TALK mic keys, twin VU gauges, master quick-mute), Hyprland-bind push-to-talk, bar indicator, and the bar/keybind wiring (Super+A → panel, XF86AudioMute → master quick-mute). 102 unit tests + a passing AT-SPI smoke on velox. Live-eyeball validation filed under Manual testing and validation. Apply steps + follow-ups handed to the dotfiles project inbox. +Went past the spec to a full build in a no-approvals speedrun. Spec is now IMPLEMENTED ([[file:docs/specs/2026-07-03-audio-panel-spec.org]], all 5 Decisions resolved). The panel shipped in the dotfiles repo (branch panel-bugfixing, commits 65e5bb0..9601420): pactl engine, GTK-free presenter, GTK instrument-console panel (OUTPUTS/INPUTS device rows with faders + per-device mute, LIVE/MUTED/PUSH·TALK mic keys, twin VU gauges, master quick-mute), Hyprland-bind push-to-talk, bar indicator, and the bar/keybind wiring (Super+A → panel, XF86AudioMute → master quick-mute). 102 unit tests + a passing AT-SPI smoke on velox. Live-eyeball validation filed under Manual testing and validation. Apply steps + follow-ups handed to the dotfiles project inbox. Original ask (roam inbox, 2026-07-02): net/bt-panel kin — change default output/input, volume for both, push-to-talk mic mode for meetings, master quick-mute, bar sound-glyph state. Related bindings: Super+M audio-cycle ring, Super+Shift+A mic-toggle. Prototype: =working/sound-panel/sound-panel-prototype.html=. -- cgit v1.2.3