fix: hotplug-driven inventory and phantom device-card suppression#346
fix: hotplug-driven inventory and phantom device-card suppression#346davidbudnick wants to merge 6 commits into
Conversation
Greptile SummaryThis PR replaces the pure-polling HID inventory watcher with a hotplug-driven model: OS hotplug events now wake the watcher early (with a 400 ms settle delay), cutting first-appearance latency from ≤2 s to ~1 s. It also adds a two-pass boot-race recovery (a confirming re-apply one tick after first sighting) and suppresses phantom offline device cards for receivers that are absent or for same-model duplicates distinguished only by the flaky extended-model byte.
Confidence Score: 5/5Safe to merge — the hotplug wiring, burst-drain logic, boot-race recovery, and phantom-card suppression are all well-reasoned and backed by unit tests covering the key edge cases. The three changed subsystems each have targeted tests. The watch_hotplug() call is correctly wrapped in rt.block_on() to prevent panics on Linux. The plan_reapply followup logic correctly distinguishes first-sightings from offline-to-online transitions, does not re-queue the confirming write a second time, and drains the followup set when a device goes offline. The receiver-gate and PID-dedup logic in append_offline_known is correct and covers the extended-model-byte dedup case that motivated the PR. No files require special attention. Real hardware hotplug testing on Linux (udev path) is still pending per the PR notes, which is worth completing before removing the draft status. Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant OS as OS HID layer
participant HB as HidBackend (LazyLock)
participant HP as hotplug.rs watch_hotplug()
participant IW as inventory watcher thread
participant ORC as Orchestrator
participant GUI as GUI state/devices.rs
OS->>HB: device connected
HB-->>HP: DeviceEvent::Connected
HP-->>IW: HotplugEvent::Connected (stream)
Note over IW: tokio::select! woken early
IW->>IW: "sleep(HOTPLUG_SETTLE=400ms)"
IW->>IW: poll_once drain burst events
IW->>HB: enumerator.enumerate()
HB-->>IW: "Vec<DeviceInventory>"
IW-->>ORC: InventoryEvent::Snapshot
ORC->>ORC: plan_reapply(prev, next, followup, reapply_all)
Note over ORC: first-sighting: reapply now + queue followup
ORC->>ORC: reapply_volatile_settings(device)
Note over ORC: next tick: confirming re-apply fires, followup drains
ORC->>GUI: updated device list via RwLock
GUI->>GUI: build_device_list()
GUI->>GUI: append_offline_known() with receiver gate + PID dedup
Note over GUI: phantom cards suppressed
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant OS as OS HID layer
participant HB as HidBackend (LazyLock)
participant HP as hotplug.rs watch_hotplug()
participant IW as inventory watcher thread
participant ORC as Orchestrator
participant GUI as GUI state/devices.rs
OS->>HB: device connected
HB-->>HP: DeviceEvent::Connected
HP-->>IW: HotplugEvent::Connected (stream)
Note over IW: tokio::select! woken early
IW->>IW: "sleep(HOTPLUG_SETTLE=400ms)"
IW->>IW: poll_once drain burst events
IW->>HB: enumerator.enumerate()
HB-->>IW: "Vec<DeviceInventory>"
IW-->>ORC: InventoryEvent::Snapshot
ORC->>ORC: plan_reapply(prev, next, followup, reapply_all)
Note over ORC: first-sighting: reapply now + queue followup
ORC->>ORC: reapply_volatile_settings(device)
Note over ORC: next tick: confirming re-apply fires, followup drains
ORC->>GUI: updated device list via RwLock
GUI->>GUI: build_device_list()
GUI->>GUI: append_offline_known() with receiver gate + PID dedup
Note over GUI: phantom cards suppressed
Reviews (3): Last reviewed commit: "Merge branch 'master' into fix/device-li..." | Re-trigger Greptile |
Context
Stale device cards pile up when the same models are used at two locations (work + home G513 / MX Master 3S), and a just-plugged keyboard waits seconds for its configured lighting. Root causes: persisted
known_identitiesresurrect unreachable devices as placeholders with model-key dedup defeated by the flaky extended-model byte (0b034vs2b034, #271/#280), and the inventory watcher is a pure 2 s poll — the "async-hid has no listener API" rationale predates async-hid 0.5'swatch().Demo
cargo test --workspacepasses; clippy/fmt clean.Changes
hotplug.rs,transport.rs(openlogi-hid)watchers/inventory.rsorchestrator.rsstate/devices.rsNotes