diff options
| author | Craig Jennings <c@cjennings.net> | 2026-07-04 12:38:10 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-07-04 12:38:10 -0500 |
| commit | 22101e1f1bc846885798b5815318a41110110a1c (patch) | |
| tree | ab0b0656a990bbb14475fa3954102a4bec87f1df /docs/design/2026-07-02-bluetooth-panel-spec.org | |
| parent | 16a53cdff389e2059fa2e3c20de98e8c0a18dcb6 (diff) | |
| download | archsetup-22101e1f1bc846885798b5815318a41110110a1c.tar.gz archsetup-22101e1f1bc846885798b5815318a41110110a1c.zip | |
docs(spec): sort formal specs into docs/specs/ with lifecycle status
Moved the seven formal specs from docs/design/ into docs/specs/, each stamped with a lifecycle status heading: four IMPLEMENTED (bluetooth, net-other-interfaces, audio, instrument-console), one CANCELLED (file-manager-swallow), two DRAFT (desktop-settings, timer). Rewrote the seven todo.org links to the new paths. The two -spec.org files without the spec spine (waybar-network-module, waybar-timer-module) stayed in docs/design/ as notes.
Diffstat (limited to 'docs/design/2026-07-02-bluetooth-panel-spec.org')
| -rw-r--r-- | docs/design/2026-07-02-bluetooth-panel-spec.org | 470 |
1 files changed, 0 insertions, 470 deletions
diff --git a/docs/design/2026-07-02-bluetooth-panel-spec.org b/docs/design/2026-07-02-bluetooth-panel-spec.org deleted file mode 100644 index 121197a..0000000 --- a/docs/design/2026-07-02-bluetooth-panel-spec.org +++ /dev/null @@ -1,470 +0,0 @@ -#+TITLE: Bluetooth Panel — CLI-Driven, Net-Panel Kin -#+AUTHOR: Craig Jennings -#+DATE: 2026-07-02 -#+TODO: TODO | DONE -#+TODO: DRAFT READY DOING | IMPLEMENTED SUPERSEDED CANCELLED - -* IMPLEMENTED Status -:PROPERTIES: -:ID: 1271a845-4463-4831-9902-990eda6b2265 -:END: -- [2026-07-02 Thu] IMPLEMENTED — all five phases shipped the same day - (dotfiles eb2230f / 76b2c05 / e372de3 / 2a026b1; archsetup d8d8c53): - engine, panel, bar module + blueman retirement, bt-priv + package swap, - install wiring proven by VM assertions. 43 dotfiles suites green, both - AT-SPI smokes green, panels verified live; the phase 4-5 VM assertions - run on the next VM pass. -- [2026-07-02 Thu] DOING — spec-response decomposed the five phases into - build sub-tasks under the todo.org parent (:SPEC_ID: bound); build - started same day per Craig ("4 first, then 1" — bugs then bluetooth). -- [2026-07-02 Thu] READY — spec-review passed the gate: all four - decisions resolved, phases decomposable, CLI verbs verified against - bluez 5.86. Two non-blocking findings recorded and dispositioned in - the same pass (donor-pattern answers). -- [2026-07-02 Thu] DRAFT — initial spec from Craig's request: a bluetooth - module driving a CLI underneath, consistent with the net panel, minimal - interface, full functionality, diagnostics section, visual mockups. - -* Metadata - -| Field | Value | -|--------+---------------------------------------------------------------------------------| -| Status | implemented | -|--------+---------------------------------------------------------------------------------| -| Owner | Craig Jennings | -|--------+---------------------------------------------------------------------------------| -| Repo | dotfiles (bt module); archsetup (packages, sudoers, keybind defaults) | -|--------+---------------------------------------------------------------------------------| -| Kin | net panel (architecture donor), desktop-settings panel (same donor, shared css) | -|--------+---------------------------------------------------------------------------------| - -* Problem - -Bluetooth on both daily drivers runs through blueman: a tray applet plus a -GTK3 manager window (Super+Shift+B). It's the odd one out on the desktop — -a foreign visual style next to the dupre-themed panels, a tray icon where -every other indicator is a first-class waybar module, and no diagnostics -story at all. When the BT mouse fails to reconnect at boot (a recurring -gotcha — touchpad-auto exists because of it) or headphones pair but route -no audio, the fix is a terminal séance: bluetoothctl, rfkill, systemctl, -wpctl, in whatever order folklore suggests. - -The net panel proved the shape that fixes this: a minimal layer-shell -popup over a GTK-free engine that drives a CLI, with a diagnostics tab -that names the failure and offers the repair. Bluetooth is the same -problem with a smaller surface: one adapter, a handful of devices, a -short list of well-known failure modes. - -* Goals - -1. Visibility: adapter power state and every known device with live state - (connected, battery, signal) in one glance — panel and bar module agree. -2. Control: power, scan, pair, connect, disconnect, forget — full - functionality from the panel, zero terminals (the net panel's V2 - contract). -3. Diagnostics: a doctor that walks the known failure chain (adapter → - rfkill → service → power → device → audio profile), names the broken - link in evidence rows, and offers tiered repairs. -4. Consistency: same stack, same window shape, same interaction grammar, - same palette as the net panel. A user who knows one panel knows both. - -Audio-profile switching is in scope for v1 (Craig, 2026-07-02 — "bitten -by this too many times to count"): the doctor's audio-profile step -carries a one-click repair, not just a diagnosis, and connected audio -devices surface their active profile (details in the doctor chain below). - -Non-goals (this iteration): OBEX file transfer, multi-adapter support -(both machines have one controller), BLE sensor/GATT browsing. - -* Design sketch - -** Architecture — the net panel's stack, verbatim - -- GTK4 + gtk4-layer-shell, Blueprint .blp compiled to committed .ui - (=make ui=), PyGObject at runtime. -- Humble-object split: GTK-free =PanelModel= presenter (unit-tested like - net's), thin composite-widget pages, =bg(work, done)= worker-thread - helper for every slow call. -- Engine: a new =bt= package in dotfiles (=bluetooth/src/bt/=, sibling of - =net/=), CLI entry =bt= with =bt status= / =bt panel= / =bt doctor= — - the same cmd/cli layout as net. -- Layer-shell OVERLAY popup anchored TOP+RIGHT, 380x520, Esc closes, - focus-out auto-hides, single-instance toggle via a =bt-panel= wrapper. - Dupre palette css shared with the net panel (the factored css asset the - desktop-settings spec calls for — three consumers now, so the factoring - happens in this project's phase 1 if settings hasn't landed it). -- Testing: engine TDD with fake binaries on a temp PATH (fake-bluetoothctl, - fake-rfkill, fake-systemctl, fake-wpctl); PanelModel unit suite; one - gated AT-SPI smoke (=make test-panel= pattern). - -** CLI backing — bluetoothctl one-shot verbs - -bluez 5.86 (installed) supports everything non-interactive: - -- Adapter: =bluetoothctl show= (powered, discoverable, pairable), - =bluetoothctl power on|off=. -- Device lists: =bluetoothctl devices Paired|Connected|Trusted= — the - Paired view is a merge of Paired + Connected states; =bluetoothctl info - <mac>= per row fills caption detail (battery percentage rides bluez's - built-in Battery1 profile and appears in info output; RSSI appears - during discovery). -- Scan: =bluetoothctl --timeout N scan on= (bounded discovery burst), - then =devices= diffed against Paired for the Nearby list. The panel - scans in 8s bursts with a live "Scanning…" state rather than an - unbounded scan. -- Connect/disconnect/forget: =bluetoothctl connect|disconnect|remove <mac>=. -- Pairing: the one interactive corner. =bluetoothctl pair <mac>= can demand - a passkey confirmation. The engine drives bluetoothctl's line protocol - over a pty with a bounded state machine (expect "Confirm passkey", - reply yes/no); a passkey prompt surfaces as a panel dialog showing the - six digits, mirroring the net panel's password dialog. NoInputNoOutput - devices (mice, most headphones) sail through without the dialog. -- rfkill: the user is in the =rfkill= group, so block/unblock is - unprivileged (=rfkill unblock bluetooth=). -- Privileged path: exactly one verb needs root — =systemctl restart - bluetooth= — so =bt-priv= is a one-verb closed helper with its own - NOPASSWD sudoers rule placed by archsetup, cloning net-priv's - regex-validated pattern rather than widening net-priv's scope. - -** Panel anatomy - -Two tabs. Devices is the panel; Diagnostics is the escape hatch. - -Devices tab, Paired sub-view (the default — daily use is reconnecting -known devices, not discovering new ones): - -#+begin_example -╭──────────────────────────────────────────────╮ -│ [ Devices ] [ Diagnostics ] │ ← top switcher -│ │ -│ Bluetooth ●──○ hci0 on │ ← adapter row: power switch -│ ──────────────────────────────────────────── │ -│ [ Paired ] [ Nearby ] │ ← sub-view switcher -│ ┌──────────────────────────────────────────┐ │ -│ │ MX Master 3 │ │ -│ │ Connected · battery 80% │ │ -│ │ WH-1000XM4 │ │ -│ │ Paired, not connected │ │ -│ │ K380 Keyboard │ │ -│ │ Paired, not connected │ │ -│ │ │ │ -│ └──────────────────────────────────────────┘ │ -│ [ Disconnect ] [ Forget ] │ ← acts on selected row -╰──────────────────────────────────────────────╯ -#+end_example - -The primary button is one control with a state-following label: -"Connect" when the selection is disconnected (suggested-action styling), -"Disconnect" when connected. Row-activate (Enter / double-click) -connects — never disconnects — matching the net panel's asymmetry. -Captions carry the human state line; the MAC lives in the row tooltip, -not the visible caption. - -Devices tab, Nearby sub-view: - -#+begin_example -╭──────────────────────────────────────────────╮ -│ [ Devices ] [ Diagnostics ] │ -│ │ -│ Bluetooth ●──○ hci0 on │ -│ ──────────────────────────────────────────── │ -│ [ Paired ] [ Nearby ] │ -│ ┌──────────────────────────────────────────┐ │ -│ │ Scanning… (6s) │ │ ← overlay state label -│ │ JBL Flip 6 −58 dBm │ │ -│ │ Pixel 9 −71 dBm │ │ -│ │ (unnamed) 74:A5:… −83 dBm │ │ -│ └──────────────────────────────────────────┘ │ -│ [ Pair ] [ Rescan ] [ Discoverable ⊙ ] │ -╰──────────────────────────────────────────────╯ -#+end_example - -Pair does the whole intended thing — pair, then trust, then connect — -because pairing a device means "use it now and reconnect on its own -later" (decision below). Discoverable is a toggle for the inbound case -(pairing a phone TO the laptop), off by default, auto-off with bluez's -discoverable-timeout. Rows sort by RSSI, strongest first; named devices -above unnamed ones. - -Diagnostics tab (mirrors the net panel's shape: one big verb + streaming -evidence rows + tiered repairs behind confirmation): - -#+begin_example -╭──────────────────────────────────────────────╮ -│ [ Devices ] [ Diagnostics ] │ -│ │ -│ [ Get Bluetooth Working ] [ Advanced ▸]│ -│ ┌──────────────────────────────────────────┐ │ -│ │ ✓ Adapter present (hci0) │ │ -│ │ ✓ Not blocked (rfkill clear) │ │ -│ │ ✓ bluetooth.service active │ │ -│ │ ✓ Adapter powered │ │ -│ │ ✗ MX Master 3: paired but unreachable │ │ -│ │ … Re-pair suggested — see below │ │ -│ │ │ │ -│ │ Fix: [ Reconnect ] [ Re-pair device ] │ │ -│ └──────────────────────────────────────────┘ │ -│ power-cycle · restart service · unblock │ ← tiered repairs (confirm) -╰──────────────────────────────────────────────╯ -#+end_example - -The doctor chain, in order, each an evidence row: - -1. Adapter present — =bluetoothctl list= / rfkill has an hci entry. - Absent → hardware/driver verdict, no repair offered. -2. rfkill state — soft-blocked names the likely cause when the - airplane-mode state file says airplane is on ("Blocked by airplane - mode — turn airplane mode off"), otherwise offers Unblock (no root - needed, rfkill group). -3. bluetooth.service — inactive/failed → offer restart (the one bt-priv - verb), evidence quotes the last journal line. -4. Adapter powered — off → offer power on (and note if a boot-time - policy keeps turning it off). -5. Per-device reachability — paired-but-connect-fails distinguishes - "device off/out of range" (RSSI absent in a scan burst) from "bond - corrupt" (connect error string), and only the latter suggests the - re-pair repair (remove + pair + trust + connect, confirmed first — - it's the destructive tier). -6. Audio profile (audio devices only) — device connected but no wpctl - sink/source, or the card stuck in HSP/HFP when A2DP is expected: - evidence names the active profile and offers the repair inline — - "Switch to A2DP" drives =wpctl set-profile <card> <index>= (profile - inventory from =pw-dump= — ground truth 2026-07-02: wpctl can't - enumerate a card's profiles, and the card's =bluez5.profile= prop - reads "off" mid-stream; the card's Profile param and the sink node's - =api.bluez5.profile= are authoritative), verifies the sink came back - in the expected profile, and reports fixed or no-change. In v1 per - Craig (2026-07-02): this failure mode has bitten repeatedly, so it - gets the one-click fix, not just a diagnosis. Connected audio-device - row captions also show the profile when it's the degraded one - ("Connected · mic mode (HSP)") so the state is visible before the - doctor runs. - -Repairs confirm with the net panel's future-tense scope copy ("This will -restart the Bluetooth service. Connected devices will drop and -reconnect."), run on the worker thread, verify after (re-read state, -report "fixed" or "no change"), and never chain silently. - -** Bar module - -=custom/bluetooth= replacing the blueman-applet tray icon: the panel's -glanceable layer, one glyph, state-following like =custom/net=: - -#+begin_example - off / blocked (dim; red slash variant when rfkill-blocked) - on, nothing connected (dim) - connected (white; tooltip lists devices + battery) -#+end_example - -Tooltip carries device names, battery percentages, and the keybind hints -(the module-tooltip convention shipped 2026-07-02). Click opens the -panel (=bt-panel= toggle wrapper); the existing Super+Shift+B bind moves -from blueman-manager to =bt panel=. Low-battery on a connected device -(<15%) adds a red percentage to the glyph text — the mouse dying -mid-meeting is the one state worth surfacing unprompted. - -** UX conformance notes - -Named against the heuristics the panel family follows (Nielsen's ten, -plus the rulesets patterns catalog): - -- Visibility of status: live captions, scan countdown, elapsed ticker on - long ops, verify-after-repair rows. -- Match to the real world: device-kind glyphs + plain state lines; MACs - demoted to tooltips; "Forget" not "Remove bond". -- User control: Esc closes, Rescan is idempotent, scan bursts are - bounded, repairs confirm, running ops show a Stop where stoppable. -- Consistency: interaction grammar is the net panel's — same switcher - layout, same primary-button contract, same confirm copy shape. -- Error prevention: Forget and Re-pair confirm; power-off while devices - are connected states the consequence in the confirm body. -- Recognition over recall: every action is a visible button; no context - menus, no hidden gestures (transient-state-buttons pattern). -- Minimalism: two tabs, one primary action per view, detail behind - tooltips and the Advanced reveal. -- Help users recover: the doctor's evidence rows name the broken link - and carry the repair inline (default-most-common-friction-proportional: - the likely fix is one click, the destructive one is confirmed). - -Tension found with the net panel while writing this (filed as todo.org -tasks per Craig's instruction, 2026-07-02): transient error toasts -auto-dismiss in 4s, and the V2 spec's keyboard-navigation claims -(tab-between-sections, arrow rows, type-to-filter) aren't verifiably -implemented. Both filed against the net panel rather than cloned here; -this panel adopts whatever resolution those tasks land on. - -* Decisions (Craig) [4/4] - -** DONE Pair implies trust + connect? -CLOSED: [2026-07-02 Thu] -Decided (Craig, 2026-07-02): yes — one Pair verb does pair → trust → -connect. A device that shouldn't auto-reconnect gets untrusted later; a -per-device auto-reconnect toggle can ride a later pass. - -** DONE Retire blueman entirely? -CLOSED: [2026-07-02 Thu] -Decided (Craig, 2026-07-02): drop it outright, no bake-in period — the -package leaves archsetup and both machines once phase 2 lands, -bluetoothctl stays as the terminal fallback. Craig's framing: any issue -after retirement is a signal the doctor needs another check or the panel -has a real bug, and it gets fixed there rather than papered over by -keeping blueman around. - -** DONE Battery in the row caption or tooltip only? -CLOSED: [2026-07-02 Thu] -Approved (Craig, 2026-07-02): caption when the device reports it -("Connected · battery 80%"), tooltip otherwise. - -** DONE Scan burst length and auto-rescan? -CLOSED: [2026-07-02 Thu] -Approved (Craig, 2026-07-02): 8s bursts, no auto-repeat — Rescan stays -explicit, matching the net panel's Available view. - -* Review findings [2/2] - -** DONE Empty-state and no-adapter presentation copy undefined :nonblocking: -CLOSED: [2026-07-02 Thu] -The mockups show populated lists; the spec didn't say what an empty Paired -list, an empty post-scan Nearby list, or a machine with no adapter shows -in the panel and on the bar glyph. Dispositioned same pass: clone the -donor — the net panel's in-box overlay message pattern (=show_loading= / -placeholder label) carries the copy. Paired empty: "No paired devices — -switch to Nearby to pair one." Nearby post-scan empty: "Nothing found — -Rescan, or make the device discoverable." No adapter: adapter row reads -"No Bluetooth adapter", Devices controls disable, Diagnostics stays -usable (the doctor's step 1 names the hardware/driver verdict); bar -glyph shows the off/blocked state. Non-blocking; recorded so the -implementer doesn't invent copy mid-build. - -** DONE Logging/redaction carry-over unstated :nonblocking: -CLOSED: [2026-07-02 Thu] -The spec says "the net panel's stack, verbatim" but didn't name whether -the engine adopts net's =eventlog= (structured op log) and =redact= -(sensitive-field scrubbing) modules. Dispositioned same pass: yes, both -carry over — every mutating verb (pair/connect/forget/repair) logs an -eventlog entry, and MACs are the redaction surface (device names stay, -MACs redact in copied reports, mirroring net's report redaction). -Non-blocking; it's the donor default made explicit. - -* Implementation phases - -1. Engine =bt= package: adapter/device/scan probes over fake-bluetoothctl, - status + doctor chain (rfkill, service, powered, reachability, audio - profile probe + A2DP switch repair over fake-wpctl) — pure TDD, no - GTK. =bt status= and =bt doctor= work from a terminal. Shared dupre - css factored to the common asset if the settings panel hasn't already - done it. -2. Panel: PanelModel presenter + Blueprint pages (Devices with - Paired/Nearby, Diagnostics), worker-thread wiring, pairing-dialog - state machine, bt-panel toggle wrapper, AT-SPI smoke. Super+Shift+B - rebind. -3. Bar module =custom/bluetooth= (glyph states, tooltip, low-battery - surface, refresh signal), waybar config + suite coverage; blueman - retirement per the decision. -4. bt-priv one-verb helper + sudoers rule in archsetup; package-list - swap (blueman out per decision, bluez-utils stays); VM test - assertions. -5. archsetup keybind/config defaults so a fresh install lands the panel - wired (waybar module present, bind set, sudoers placed). - -* Review and iteration history - -** 2026-07-02 Thu @ 15:19:58 -0400 — Claude Code (archsetup) — phase 5 builder, spec closed -- *What changed or was recommended:* Phase 5 shipped and the spec flipped - to IMPLEMENTED. No new install code was needed — the waybar module, the - =Super+Shift+B= bind, and the shared panel css all ride the dotfiles - hyprland tier that a fresh install already clones and stows, and sudoers - is covered by the blanket grant. The phase's substance is proof: - =test_desktop.py= gained hyprland-gated assertions for the four stowed - bt bins, the =custom/bluetooth= waybar entry, the =bt-panel= keybind, - and the stowed =panel.css=. -- *Why:* Final phase of the DOING decomposition; with it the todo parent - closed and the lifecycle keyword flipped with a history line. -- *Artifacts:* archsetup =scripts/testing/tests/test_desktop.py=; todo.org - parent DONE + dated phase 5 / test-surface entries; this spec's Status - heading. - -** 2026-07-02 Thu @ 15:16:51 -0400 — Claude Code (archsetup) — phase 4 builder -- *What changed or was recommended:* Phase 4 shipped. Dotfiles =2a026b1=: - the stowed =bt-priv= shim (one verb, verified against the fake-systemctl) - and the sxhkd =Super+Shift+B= bind repointed from blueman-manager to - =st -e bluetoothctl= (terminal fallback per the retirement decision — the - panel is Wayland-only). archsetup: blueman dropped from the - =desktop_environment= package loop; VM assertions added (bluez/bluez-utils - present, blueman absent). blueman also removed live from velox. -- *Why:* Build order per the DOING decomposition. The spec's "sudoers rule" - item resolved as net-priv's did: archsetup already grants the primary - user blanket =NOPASSWD: ALL= (archsetup:1089), so a narrow bt-priv rule - would be dead config — no new sudoers needed, and phase 5's "sudoers - placed" is satisfied by the existing grant. -- *Artifacts:* dotfiles =hyprland/.local/bin/bt-priv=, - =common/.config/sxhkd/sxhkdrc=; archsetup =archsetup= (bluetooth loop), - =scripts/testing/tests/test_packages.py=; dated phase 4 entry under the - todo.org parent. - -** 2026-07-02 Thu @ 15:06:00 -0400 — Claude Code (archsetup) — phase 3 builder -- *What changed or was recommended:* Phase 3 shipped (dotfiles =e372de3=): - the =custom/bluetooth= bar module (state-following glyph, low-battery red - percentage, device+battery tooltip with the keybind hint, signal 10 with - the panel poking it after each reload) and the blueman retirement from the - Hyprland session (exec-once + windowrules removed, applet killed live). - The phase 2 deferred items also closed this pass: both AT-SPI smokes green - (the bt smoke's primary-button assertion fixed for the state-following - label, =c1a8219=), both panels eyeballed correct in dupre, and the - net-panel keyboard claims verified live (archsetup =e80df2b= — false - claims struck from the net spec). -- *Why:* Build order per the DOING decomposition; the Zoom meeting ended, - unblocking the visual work. Phases 4-5 (bt-priv/sudoers/packages, install - defaults — archsetup side) remain. -- *Artifacts:* dotfiles =bluetooth/src/bt/indicator.py=, =waybar-bt=, - waybar config + three css files; dated phase 3 entry under the todo.org - parent. - -** 2026-07-02 Thu @ 14:15:27 -0400 — Claude Code (archsetup) — phase 2 builder -- *What changed or was recommended:* Phase 2 shipped (dotfiles =76b2c05=): - the GTK panel — PanelModel/viewmodel presenter pair (69 tests), Blueprint - pages, pairing pty state machine with default-deny passkey confirms, - manage.py op envelopes shared by CLI and panel (power + discoverable verbs - added), =bt-panel= toggle, Super+Shift+B rebind. The shared dupre css - factoring landed as planned: net's inline =_CSS= became - =themes/dupre/panel.css= with =dupre-*= classes, both panels consume it. - 43 suites green. The AT-SPI smoke (=make test-panel-bt=) is written but - not yet run live — a Zoom meeting occupied the compositor; it runs when - the meeting ends, along with a visual check of both panels. -- *Why:* Build order per the DOING decomposition; phases 3-5 (bar module, - bt-priv/sudoers, install defaults) remain. -- *Artifacts:* dotfiles =bluetooth/src/bt/{panel,viewmodel,pairing,manage, - gui,pages}.py=, =ui/*.blp=, =tests/bt/test_btpanel.py=, the panel smoke; - dated phase 2 entry under the todo.org parent. - -** 2026-07-02 Thu @ 13:31:00 -0400 — Claude Code (archsetup) — phase 1 builder -- *What changed or was recommended:* Phase 1 shipped (dotfiles =eb2230f=): - the =bt= engine package, 101 tests over fakes, live-verified read-only - on velox. Two spec corrections from ground truth: profile inventory - comes from =pw-dump= (wpctl can't enumerate profiles), and the active - profile reads from the card's Profile param / sink's - =api.bluez5.profile= (the card's =bluez5.profile= prop is unreliable). - The shared-css factoring moved into phase 2 — net's css is an inline - string in its =gui.py=, so extracting it belongs with the first second - consumer rather than as a standalone poke at the working net panel. -- *Why:* Build order per the DOING decomposition; corrections keep the - spec honest for the phase 2 implementer. -- *Artifacts:* dotfiles =bluetooth/src/bt/=, =tests/bt/=, the stowed - =bt= shim; dated phase 1 entry under the todo.org parent. - -** 2026-07-02 Thu @ 13:10:00 -0400 — Claude Code (archsetup) — reviewer + responder -- *What changed or was recommended:* Ran the spec-review gate: passed. - All four decisions were already DONE (cookie added to the heading); - the five phases are each a clean single-session stop; CLI verbs are - verified against installed bluez 5.86. Two non-blocking findings - recorded and dispositioned in the same fused pass (empty-state / - no-adapter copy, eventlog + redaction carry-over) — both resolve to - "clone the net-panel donor," now stated explicitly. Flipped DRAFT → - READY → DOING and decomposed the phases into build sub-tasks under the - todo.org parent with :SPEC_ID: bound. -- *Why:* Craig queued the build ("4 first, then 1", 2026-07-02) after - resolving all decisions the same morning; the gate held nothing back, - so review and response fused to keep the speedrun moving. -- *Artifacts:* Findings in =* Review findings [2/2]= above; build parent - in todo.org ("Bluetooth panel + bar module"); net-panel toast fix the - UX-conformance note references landed as dotfiles =0f017d4=. |
