Skip to content

feat(gui): add a Copy Diagnostics button to the About window#206

Merged
AprilNEA merged 5 commits into
AprilNEA:masterfrom
davidbudnick:feat/diagnostics-report
Jun 12, 2026
Merged

feat(gui): add a Copy Diagnostics button to the About window#206
AprilNEA merged 5 commits into
AprilNEA:masterfrom
davidbudnick:feat/diagnostics-report

Conversation

@davidbudnick

@davidbudnick davidbudnick commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a Copy Diagnostics button to the About window that copies a privacy-filtered, Markdown diagnostics report to the clipboard for pasting into a bug report. It gives enough detail to reproduce environment-specific issues — device connection, capabilities, missing render images, agent/GUI version skew, asset-cache state — without exposing anything that uniquely identifies the user's hardware.

What it collects

  • App — GUI + agent versions (with a mismatch flag), IPC protocol versions, OS + version + arch, locale, accessibility / input-hook / launch-at-login / menu-bar state, build profile, config schema + configured-device count.
  • Assets — source (app bundle vs user cache), index size, redacted cache path, bundle presence — the signals behind "preview image not loading".
  • Devices (per device) — name, kind, firmware codename, connection type, online, battery, capabilities (buttons/pointer/lighting), DPI, model/config key + wpid + model-ids + ext-model, transports, render state (resolved depot vs silhouette), slot.
  • Receivers — model name + VID/PID.

Privacy

Model-level only. Serial numbers, unit IDs, and the Bolt receiver UID are excluded by construction — the report structs have no field to hold them, so a future edit can't leak one by forgetting to redact. The home directory is redacted to ~ in the cache path.

Design

  • The data shape and Markdown rendering live in openlogi-core (diagnostics.rs) — platform-free and unit-tested.
  • A thin GUI adapter (openlogi-gui/src/diagnostics.rs) maps live AppState (device list, agent status, asset resolver) onto it, refining connection type from the device's announced transports.
  • The GUI now retains the full AgentStatus and the raw inventory snapshot (previously discarded after each poll) so the report can include agent version, hook state, and transports.
  • Clipboard write uses cx.write_to_clipboard(ClipboardItem::new_string(...)); the button confirms with a "Copied!" label for ~2s.

Testing

  • 8 unit tests in openlogi-core cover section rendering, version/protocol mismatch flags, empty/offline devices, the no-serial-or-unit-id guarantee, and direct-connection slot rendering.
  • Verified end-to-end on real hardware (built and ran the GUI), confirming the report renders correctly against a live device:
### OpenLogi Diagnostics

**App**
- OpenLogi (GUI): v0.6.6 (release)
- Agent: v0.6.6 (connected)
- IPC protocol: GUI 1 / agent 1
- OS: macOS 26.4.1 (arm64)
- Locale: en-CA (UI: follow system)
- Accessibility: granted · Input hook: installed
- Launch at login: no · Menu bar: yes · Update check: on
- Running from: source build (dev)
- Config: schema 2 · 0 configured device(s) · thumbwheel 14

**Assets**
- Source: user cache · Index: loaded (210 models) · User cache: present
- Cache path: ~/.local/share/openlogi/assets · Bundle assets: absent

**Devices (2)**
- Receiver: Logi Bolt Receiver (VID 046d / PID c548)
- MX Master 3S — mouse (codename: MX Master 3S)
  - Connection: Logi Bolt receiver · Online: yes · Battery: 35% (discharging, good)
  - Capabilities: buttons=yes, pointer=yes, lighting=no
  - Model: 0b034 (model-ids: b034/0000/0000, ext-model: 00)
  - Transports: BTLE
  - Render: mx_master_3s · Slot 2
- G513 — keyboard (codename: G513 RGB MECHANICAL GAMING KEYBOARD)
  - Connection: Wired (USB) · Online: yes · Battery: n/a
  - Capabilities: buttons=yes, pointer=no, lighting=yes
  - Model: 5c33c (model-ids: c33c/0000/0000, ext-model: 05)
  - Transports: USB
  - Render: g513 · direct

Screenshots

Screenshot 2026-06-10 at 4 26 29 PM

@davidbudnick davidbudnick marked this pull request as ready for review June 11, 2026 02:32
@greptile-apps

greptile-apps Bot commented Jun 11, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds a Copy Diagnostics button to the About window that renders a privacy-filtered Markdown report to the clipboard. The data shape and rendering live in a new openlogi-core module (platform-free, unit-tested), with a thin GUI adapter in openlogi-gui that maps live AppState onto it.

  • Core module (diagnostics.rs): defines structs for app info, assets, receivers, and per-device detail; renders Markdown; privacy-by-construction (no serial/unit-id fields exist on the structs). Eight tests cover rendering, mismatch flags, empty inventory, and the no-identifier guarantee.
  • GUI adapter: maps AppState (device list, agent status snapshot, asset resolver) onto the core structs; refines HID++ route into a user-facing connection type; redact_home strips the username from the cache path before copying.
  • About window: button with a generation-counter guard (copied_gen) so rapid clicks don't let a stale 2-second reset timer clear a newer "Copied!" confirmation prematurely.

Confidence Score: 5/5

Safe to merge. All changed paths are additive (new module + button), no existing behaviour is altered, and the change is well-tested.

The feature is entirely additive — a new button and new modules with no changes to existing rendering, IPC, or device management logic. The core diagnostics module has eight unit tests, the timer race condition is already guarded with the generation counter, and the OS-version lookup was correctly replaced with the non-blocking NSProcessInfo API. The one correctness wrinkle (redact_home using string strip_prefix instead of Path::strip_prefix) is a low-impact edge case in a user-visible string that has no effect on functionality.

No files require special attention.

Important Files Changed

Filename Overview
crates/openlogi-core/src/diagnostics.rs New core diagnostics module: data structs and Markdown renderer. Privacy-by-construction (no serial/unit-id fields). Eight unit tests cover section rendering, mismatch flags, empty inventory, and the no-identifier guarantee.
crates/openlogi-gui/src/diagnostics.rs GUI adapter mapping live AppState onto the core report. Creates a fresh AssetResolver (with index I/O) on each click — acceptable for a user-triggered action. redact_home uses string-level prefix matching instead of Path::strip_prefix, which can produce garbled output if HOME is a string prefix of another path component.
crates/openlogi-gui/src/platform/os.rs New module: reads OS version via NSProcessInfo.operatingSystemVersion() — non-blocking, no child process spawned. Non-macOS arm returns None.
crates/openlogi-gui/src/state.rs Adds last_status and last_inventory fields to AppState, along with accessor methods and store_agent_snapshot. Snapshot is stored unconditionally on every IPC poll (not only when health == Ready), which is appropriate for diagnostics purposes.
crates/openlogi-gui/src/windows/about.rs Adds the Copy Diagnostics button with generation-counter guard (copied_gen) to prevent stale reset timers from clobbering a newer confirmation window after rapid clicks.
crates/openlogi-gui/src/main.rs Adds store_agent_snapshot call after each IPC poll to persist the raw inventory and agent status for the diagnostics report.
crates/openlogi-gui/src/asset/mod.rs Adds index_loaded() and index_entry_count() accessor methods to AssetResolver for use by the diagnostics adapter.

Sequence Diagram

sequenceDiagram
    participant User
    participant AboutView
    participant Collector as "diagnostics::collect"
    participant Resolver as "AssetResolver"
    participant State as "AppState"
    participant Clipboard

    User->>AboutView: click Copy Diagnostics
    AboutView->>Collector: collect(cx)
    Collector->>Resolver: new() — reads index from disk
    Resolver-->>Collector: resolver
    Collector->>State: try_global — last_status, last_inventory, device_list
    State-->>Collector: state snapshot
    Collector-->>AboutView: DiagnosticsReport
    AboutView->>AboutView: to_markdown()
    AboutView->>Clipboard: write_to_clipboard
    AboutView->>AboutView: "copied=true, copied_gen++"
    Note over AboutView: label flips to Copied!
    AboutView->>AboutView: spawn 2s timer with generation
    AboutView->>AboutView: "timer fires — if gen matches, copied=false"
    Note over AboutView: label reverts to Copy Diagnostics
Loading

Reviews (3): Last reviewed commit: "Merge master into feat/diagnostics-repor..." | Re-trigger Greptile

Comment thread crates/openlogi-gui/src/windows/about.rs
Comment thread crates/openlogi-gui/src/platform/os.rs Outdated
Comment thread crates/openlogi-core/src/diagnostics.rs
davidbudnick and others added 2 commits June 10, 2026 21:52
Resolve the IPC poll conflict against the startup-states rework and
adapt the diagnostics adapter to master:

- call store_agent_snapshot before the status snapshot moves into
  AgentLink::Ready; the inventory merge keeps the InventoryHealth gate
  (AprilNEA#213/AprilNEA#215/AprilNEA#220)
- read accessibility from the retained AgentStatus; the
  AppState.accessibility_granted field no longer exists
- map DeviceRoute::Unifying (AprilNEA#181) to a new
  ConnectionKind::UnifyingReceiver
@AprilNEA AprilNEA merged commit 73e90a8 into AprilNEA:master Jun 12, 2026
8 checks passed
@aprilnea aprilnea Bot mentioned this pull request Jun 12, 2026
@aprilnea aprilnea Bot mentioned this pull request Jun 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants