fix(input-plumber): log + retry the boot-time wake-button reload#206
Merged
Conversation
The overlay wake button (a physical button bound through InputPlumber, e.g. the handheld's Keyboard button → F16) is re-applied on boot by reloadPersistedProfile(). When that reload failed — a stale saved capability after an InputPlumber update, or the composite device not yet enumerated on a cold boot — the button silently reverted to its OS default (opening the on-screen keyboard) with almost no diagnostics: reloadPersistedProfile() had no internal logging and onLoad only warned on failure. - Add step-by-step logging to reloadPersistedProfile(): binding read, IP-up, composite devices found, whether the saved capability is still present in the device (catches post-update capability renames), and LoadProfilePath result. Log when a binding is persisted, too. - onLoad now retries the reload up to 5× (2s apart), mirroring restartInputPlumber(), so a cold boot where IP is up but the pad isn't enumerated yet doesn't drop the binding for the whole session. No-op when nothing is bound. Diagnose a bad boot with: journalctl -u loadout.service -b --no-pager | grep "wake reload" Co-Authored-By: Claude Opus 4.8 <[email protected]>
a5c30c1 to
81dbb89
Compare
The retry loop lived inline in backend.onLoad, where it was awkward to test (real 2s sleeps + onLoad's other side effects). Extract it into `reloadPersistedProfileWithRetry()` in wake-trigger.ts with injectable `reload` + `wait`, and point onLoad at it (behavior unchanged). Add unit tests: success first try (no wait), no-op when nothing is bound, retry-then-succeed (asserts call count, waits, and onRetry attempts), give-up after `attempts` with no trailing wait, and a custom attempts count. Runs in ~80ms — no real delays. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The overlay wake button — a physical button bound through InputPlumber (e.g. the handheld's Keyboard button → F16) — is re-applied on every boot by
reloadPersistedProfile(). When that reload fails, the button silently reverts to its OS default (opening the on-screen keyboard), and there were almost no diagnostics to explain why:reloadPersistedProfile()had no internal logging.onLoadonly logged on failure, and did a single attempt.Two real failure modes hit this:
waitForIppasses) but the composite device isn't enumerated yet →reloadPersistedProfile()returns "Bound device not connected" and the binding is dropped for the whole session.Changes
reloadPersistedProfile()(non-Deck path): binding read, IP-up + device list, whether the saved capability is still present in the device's capabilities (catches post-update renames), and theLoadProfilePathresult. Also log when a binding is persisted at bind time.onLoad: reload is retried up to 5× (2s apart), mirroring the existingrestartInputPlumber()retry, so the cold-boot enumeration race can't silently drop the binding. No-op when nothing is bound (earlyokreturn).Diagnose
Verification
Built and installed on a OneXPlayer APEX (non-Deck). A real cold reboot logs the full happy path and restores the binding:
Typecheck clean; input-plumber lib tests pass (67). The
app.spec.tsxrender tests were already failing onmain(pre-existing, unrelated).🤖 Generated with Claude Code