aboutsummaryrefslogtreecommitdiff
path: root/docs/design/2026-07-02-bluetooth-panel-spec.org
diff options
context:
space:
mode:
Diffstat (limited to 'docs/design/2026-07-02-bluetooth-panel-spec.org')
-rw-r--r--docs/design/2026-07-02-bluetooth-panel-spec.org470
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=.