Skip to content

fix(shortcut): keep toggle parity when presses arrive mid-pipeline - #1910

Merged
cjpais merged 5 commits into
cjpais:mainfrom
xilec:fix/coordinator-busy-press-parity
Aug 25, 2026
Merged

fix(shortcut): keep toggle parity when presses arrive mid-pipeline#1910
cjpais merged 5 commits into
cjpais:mainfrom
xilec:fix/coordinator-busy-press-parity

Conversation

@xilec

@xilec xilec commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Before Submitting This PR

  • I have searched existing issues and pull requests, including closed ones, to ensure this is not a duplicate
  • I have read CONTRIBUTING.md

Human Written Description

I found this bug while working on #1909, but split these fixes into a separate PR to keep that one small and ease review.

Rapid re-triggers of push-to-talk via external toggle triggers (e.g. SIGUSR2 sent on both key press and release) could wedge the recording state: the overlay would sit in "recording" with the button long released, waiting for input.

Two places in the transcription coordinator dropped toggle edges:

  1. A press arriving while the previous transcription is still processing was ignored. Toggle triggers flip state on every edge, so a dropped press desyncs the parity: the next edge then starts a recording nobody will stop. Now a busy press is remembered (or forgotten, if the key was already released) and started when the pipeline drains — a quick tap inside the busy window nets to no-op, an odd press starts exactly one recording.

  2. The 30 ms debounce also swallowed external triggers. Two edges faster than 30 ms (a scripted double-tap) lost the second one — same desync. External triggers (signals, CLI flags) are deliberate edges from the user's own integration and are now exempt; physical key repeats still debounce.

The second commit is a behavior-neutral refactor that moves all transition logic into a pure CoordinatorState the tests drive directly, replacing a hand-written test simulator that could drift from production.

Known remaining limitation

Cancel mid-hold with directionless toggle triggers can still desync parity (the release edge arrives at idle and toggles a fresh recording). A proper fix requires direction-carrying triggers (explicit press/release API) — invasive changes that need to be discussed with the maintainer before implementation, so they are out of scope for this PR.

Related Issues/Discussions

Community Feedback

Bug fix found while dogfooding push-to-talk via external triggers (triggerhappy -> SIGUSR2 on both key edges); no prior discussion.

Testing

Screenshots/Videos

AI Assistance

  • AI was used (please describe below)

If AI was used:

  • Tools used: Kimi Code CLI
  • How extensively: AI did the debugging and drafted the fix and tests under my direction; I reviewed and iterated on the approach.

xilec added 2 commits August 13, 2026 19:40
…xternal triggers

Toggle-style triggers (SIGUSR2, CLI flags, pedals that signal on both
edges) flip state on every edge. A press arriving while the previous
pipeline was still processing used to be dropped, desyncing parity: the
next edge then started a recording nobody would stop, leaving the
overlay waiting for input with the button long released.

The coordinator now remembers at most one press while the pipeline is
busy: a second toggle press (or a PTT release) during the same busy
window cancels it, preserving parity, and a remembered press starts
recording as soon as the pipeline drains. External triggers bypass the
30 ms keyboard debounce, which could eat one edge of a quick external
tap and break parity the same way.
…ransport loop

The coordinator thread mixed transport (mpsc/recv_timeout), state, decisions
and effect execution, so sequence tests had to mirror the transitions in a
hand-written simulator that covered only the cjpais#1539 auto-repeat path and could
silently drift from production behavior.

All transition logic now lives in a pure CoordinatorState (on_input /
on_grace_expired / on_cancel / on_processing_finished) returning Effect
values; the thread only transports commands and executes effects, reporting
start failures back via on_start_result so the optimistic Recording
transition rolls back when recording fails to begin. Tests drive the real
machine directly and additionally cover the busy-pipeline remember/forget and
cancel-drain sequences.
@cjpais
cjpais merged commit c6fa60d into cjpais:main Aug 25, 2026
2 checks passed
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