Skip to content

refactor: land the standards-audit findings in batches#381

Open
AprilNEA wants to merge 17 commits into
masterfrom
refactor/standards-audit
Open

refactor: land the standards-audit findings in batches#381
AprilNEA wants to merge 17 commits into
masterfrom
refactor/standards-audit

Conversation

@AprilNEA

@AprilNEA AprilNEA commented Jul 13, 2026

Copy link
Copy Markdown
Owner

Summary

Lands the findings of a workspace-wide audit against the newly codified agent-guide standards (AGENTS.md, #379): silent wire-value drops, boundary-validation gaps, hand-rolled logic with mature in-tree replacements, and module-structure debt. Seventeen focused commits, one batch each, ordered fixes-first — intended for rebase-merge (every commit is release-quality conventional).

Changes

Behavior fixes

  • hidpp: receiver/feature listeners no longer silently drop events carrying unknown wire values. Unifying/Bolt 0x41 connection events fold an unrecognised kind nibble to Unknown (num_enum(default)) instead of hiding the device — on the Unifying path arrival notifications are the only device source. Bolt 0x54 unknown pairing-error codes carry the raw byte (catch_all Other(u8)) instead of degenerating into a session timeout; same for the 0x4e press type. The 0x1d4b reconnection broadcast decodes infallibly. smartshift_enhanced reports UnsupportedResponse for an unknown wheel mode instead of silently claiming Ratchet, matching 0x2110/hires_wheel.
  • hid, agent-core: the Bolt passkey is decoded once at the notification boundary (NUL-trimmed, digit-validated, numeric value alongside); a malformed passkey fails the pairing session explicitly instead of rendering an all-Left click sequence via parse().unwrap_or(0). Out-of-range DPI writes are rejected as OutOfRange (awaitable path) or logged-and-skipped (fire-and-forget path) instead of clamping u16::MAX onto the device.
  • core, gui, agent, cli: the lighting color is a validated Rgb newtype serialized as the same hex string — the two divergent hand-rolled parsers (agent fell back to black, GUI to white) are gone. The pinned bytes in wire_format.rs are unchanged, proving IPC compatibility.

Dependency replacements

  • core: config persistence via atomic-write-file (adds the directory fsync and randomized temp name the hand-rolled writer lacked; 0600 preserved). $HOME resolution via the etcetera-backed paths::home_dir().
  • assets, gui (tests): hand-rolled env::temp_dir()+pid test dirs → tempfile::tempdir().

Structure (pure code motion)

  • inject: the three inline platform backends move to inject/{macos,linux,windows}.rs (1691 → 682 lines), matching the hook crate's layout.
  • gui: app.rs splits along its three seams into app/{home,detail,widgets}.rs (1909 → 683); asset/mod.rsasset.rs, windows/mod.rswindows.rs per the module-layout rule; the ipc_client pacing module and the asset-sync orchestration get their own files; Load/LazyDeviceDatastate/load.rs; duplicated file_url collapsed; NaN-tolerant sorts → total_cmp.
  • core: swipe machinery → binding/swipe.rs; cmd/diag/mod.rsdiag.rs (cli).

Docs & hygiene

  • core: #![deny(missing_docs)] (parity with openlogi-hid) after documenting 140 flagged items.
  • Every remaining lint suppression carries a reason; the last section-banner comments are gone; the updater fails fast on an impossible invalid embedded version.

Testing

  • devenv tasks run openlogi:check (fmt --check, workspace clippy -D warnings, full workspace tests incl. doc-tests) — green on the final tree; each commit was additionally verified with targeted clippy + tests before landing.
  • cargo clippy --target x86_64-pc-windows-gnu cross-lint on the touched cfg-shared crates (core, inject) — clean; Linux paths rely on CI as the authoritative check.
  • Wire compatibility: the bincode golden tests (wire_format.rs) pass with unchanged pinned bytes; no PROTOCOL_VERSION bump needed.
  • Not runtime-tested on hardware: receiver event tolerance (unknown-kind devices, unknown pairing errors) and the passkey failure path need a physical Bolt/Unifying setup; the GUI was not exercised beyond its unit tests. Suggested spot-check: pair a device (passkey flow), toggle lighting color/brightness, and confirm devices still enumerate after sleep.

Follow-ups (tracked separately, out of scope here)

AprilNEA added 17 commits July 13, 2026 09:34
…ners

Every allow/expect in the workspace now states its reason inline (the
four stragglers had it in a loose comment or not at all), and the one
remaining dashed section banner is reduced to plain prose.
CARGO_PKG_VERSION is cargo-provided and always valid semver; falling
back to 0.0.0 would silently make every release look like an upgrade.
Panic with a reasoned expect instead of hiding the impossible case.
Expose the etcetera-backed home_dir from openlogi-core and use it for
the launchd plist location instead of reading $HOME by hand — one home
resolution policy for the whole workspace.
The receiver and feature listeners run in a void callback with no error
channel, so every unrecognised byte used to degenerate into a silent
return — losing whole events over an identity-only field:

- Unifying/Bolt 0x41 device-connection: an unknown kind nibble dropped
  the event entirely; on the Unifying path arrival notifications are
  the only device source, so the device never appeared at all. Kind now
  folds to Unknown via num_enum(default) (identity-only by policy).
- Pairing-info register reads no longer fail the whole read over the
  kind nibble either, matching the listener policy.
- Bolt 0x54 pairing status: an unknown error code turned a failure into
  a session timeout; PairingError now carries the raw code in a
  num_enum(catch_all) Other variant. Same treatment for the 0x4e
  passkey press type.
- Bolt 0x4d passkey: trim the NUL padding before decoding.
- 0x1d4b WirelessDeviceStatus broadcast: the (re)connection signal was
  dropped if any of its three bytes was unrecognised; all fields now
  decode infallibly with catch_all Other variants.
- smartshift_enhanced: an unknown wheel mode silently reported Ratchet;
  it is now Hidpp20Error::UnsupportedResponse, matching 0x2110 and
  hires_wheel (the pinned fallback test asserts the error instead).

openlogi-hid's discovery kind mapping is simplified to the now-
infallible From.
A corrupt Bolt passkey notification used to flow through from_utf8_lossy
and parse().unwrap_or(0), rendering an all-Left click sequence that can
never authenticate — with no diagnostic anywhere. The 0x4d decoder now
NUL-trims and validates ASCII digits once, producing the digits and
their numeric value together; a malformed payload fails the pairing
session explicitly (surfaced over IPC as the generic transport-failure
message, so the wire format is unchanged).

The DPI write path clamped an out-of-range u32 to u16::MAX and wrote
that to the device. The awaitable path now rejects it as the same
OutOfRange feature error the device itself would return; the
fire-and-forget path logs and skips the write.
The lighting color was a raw String parsed independently in two places
with divergent silent fallbacks: the agent's parse_hex fell back to
black (lights off) while the GUI's fell back to white and didn't strip
a '#' prefix. A hand-edited config degraded differently on each side of
the IPC with no diagnostic.

openlogi-core now owns a validated Rgb newtype (exactly 6 hex digits)
that serializes as the same hex string the field used to hold, so TOML
files and the bincode wire format are unchanged — the pinned wire bytes
in wire_format.rs prove it. Config load folds an unparseable value to
white with the same documented per-field tolerance as the brightness
clamp (failing the load would discard the whole user config). Every
consumer — agent write path, GUI swatches and keyboard glow, CLI diag —
now uses the typed components; both hand-rolled hex parsers are gone.
The hand-rolled temp-file writer used a fixed .toml.tmp name (a
concurrent-save collision surface) and never fsynced the directory, so
the rename could vanish on a crash. atomic-write-file — already the
asset cache's writer — covers both; the 0600 mode on every save is
preserved.
inject.rs simulated three whole backends as inline cfg-gated modules
(~1700 lines in one file). Each moves verbatim to inject/{macos,linux,
windows}.rs, matching the hook crate's layout; shared code and the
public API are unchanged.
asset/mod.rs, windows/mod.rs, and cmd/diag/mod.rs all carry real logic
(the asset resolver, the window registry, the diag device-selection
policy) — per the module-layout rule they are foo.rs files with their
children in the sibling directory. Pure renames.
The ipc_client's inline pacing module (with its own tests) becomes a
file; the duplicated file_url helper collapses to one copy; two
one-line single-caller wrappers in app.rs are inlined; the hotspot
geometry sorts with total_cmp instead of NaN-tolerant unwrap_or.
The hand-rolled env::temp_dir()+pid directories leaked on assertion
failure; tempfile::tempdir cleans up on drop, as the core and xtask
tests already do.
…oad.rs

The lazy load-state types have no AppState dependency; state.rs keeps
the state itself.
SyncOutcome/AssetControl/run_asset_sync are a self-contained subsystem;
main.rs keeps startup wiring only.
app.rs interleaved the home gallery, the device-detail tabs, and the
shared card/label primitives in one ~1900-line file. Each concern moves
to its own module under app/; AppView, routing, and the capability-
gated DetailTab logic stay in app.rs. Pure code motion.
detect_swipe and SwipeAccumulator are input-processing runtime, not
part of the Action vocabulary the rest of binding.rs defines.
The hand-rolled env::temp_dir()+pid directories leaked on assertion
failure; tempfile::tempdir cleans up on drop.
Brings openlogi-core to parity with openlogi-hid, which already
enforces rustdoc coverage at compile time.
@greptile-apps

greptile-apps Bot commented Jul 13, 2026

Copy link
Copy Markdown

Greptile Summary

This PR lands a broad standards-audit refactor across HID handling, config, GUI structure, and docs. The main changes are:

  • Tolerant decoding for several HID++ receiver events and pairing notifications.
  • Validated lighting colors and stricter DPI/passkey boundary handling.
  • Atomic config writes and safer temp-directory use in tests.
  • Large GUI, inject, binding, and state module splits.
  • Added public documentation for openlogi-core.

Confidence Score: 4/5

The changed config and pairing boundaries need fixes before merging.

  • Hash-prefixed lighting colors can be silently rewritten as white.
  • A Bolt keyboard-like pairing session can be shown the pointer click flow when the auth byte is unexpected.
  • The larger module moves and wire-format changes otherwise look consistent from the inspected diff and context.

crates/openlogi-core/src/config.rs; crates/openlogi-hid/src/pairing.rs

Important Files Changed

Filename Overview
crates/openlogi-core/src/config.rs Lighting color config now uses Rgb, but hash-prefixed hand-edited values silently fall back to white.
crates/openlogi-core/src/color.rs Adds strict six-digit RGB parsing with string-compatible serialization.
crates/openlogi-hid/src/pairing.rs Passkey rendering now uses decoded numeric values, but unexpected auth bits can route keyboard-like pairing to the pointer flow.
crates/openlogi-hid/src/pairing/notification.rs Passkey notifications are NUL-trimmed, digit-validated, and malformed payloads fail the pairing session.
crates/openlogi-hidpp/src/receiver/bolt.rs Unknown Bolt kinds, pairing errors, and passkey press types now decode without dropping events.
crates/openlogi-hidpp/src/receiver/unifying.rs Unknown Unifying kind nibbles now fold to Unknown instead of suppressing connection events.
crates/openlogi-agent-core/src/hardware.rs DPI writes now reject values outside the HID++ u16 field and lighting uses Rgb components directly.
crates/openlogi-gui/src/components/lighting_panel.rs Lighting palette swatches now use typed Rgb values.
crates/openlogi-core/src/binding/swipe.rs Swipe gesture logic was moved into a submodule with its tests preserved.
crates/openlogi-inject/src/inject.rs Platform input injection backends were split into cfg-gated submodules.

Fix All in Codex Fix All in Claude Code

Reviews (1): Last reviewed commit: "docs(core): document the remaining publi..." | Re-trigger Greptile

Comment on lines +290 to +292
Ok(String::deserialize(deserializer)?
.parse()
.unwrap_or(Rgb::WHITE))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Hash-Prefixed Colors Become White

When config.toml contains a common hand-edited value like color = "#ff00aa", this deserializes the raw string directly and Rgb::from_str rejects it because it is not exactly six characters. The load then silently stores Rgb::WHITE, so the next save overwrites the user's chosen lighting color with white.

Fix in Codex Fix in Claude Code

Comment on lines 399 to 404
let method = match pairing_auth {
Some(auth) if auth & 0x01 != 0 => PasskeyMethod::Keyboard(passkey),
Some(auth) if auth & 0x01 != 0 => PasskeyMethod::Keyboard(digits),
_ => PasskeyMethod::Pointer {
clicks: passkey_to_clicks(&passkey),
passkey,
clicks: passkey_to_clicks(value),
passkey: digits,
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Keyboard Passkeys Become Clicks

When a keyboard-like Bolt device reaches this path without auth bit 0x01 set, the session can still have been opened with keyboard entropy, but this branch falls back to the pointer method and renders only the 10-bit click sequence. The user is shown the wrong authentication flow, so the receiver waits for the keyboard passkey and pairing times out or fails.

Fix in Codex Fix in Claude Code

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.

1 participant