Skip to content

fix: stale captured events leaking across interactions - #4

Merged
jamescrosswell merged 2 commits into
mainfrom
fix-stale-key-replay
Mar 10, 2026
Merged

jamescrosswell merged 2 commits into
mainfrom
fix-stale-key-replay

Conversation

@jamescrosswell

Copy link
Copy Markdown
Contributor

Problem

When using balanced flavor, it was possible to get spurious keycodes emitted at
the start of a subsequent interaction. For example, with &ok_ltm on Y and
&kp TAB on T (on the app-switch layer), typing normally could occasionally
produce one or more T keycodes followed by the intended Y output.

Root cause: a race between the tapping-term timer and the interrupt key.

With balanced flavor, a hold is resolved when an interrupt key is both pressed
and released. However, if the tapping-term timer fires while the interrupt key
is still held down
, do_hold() is called from the work handler — which sets
undecided_olm = NULL. The interrupt key's release event then arrives after
undecided_olm is NULL, so the position listener lets it bubble freely instead
of capturing it. The result: the interrupt key-down is in the capture buffer but
its key-up is not, and captured_events_count is never reset.

On the next interaction, if a tap resolves, release_captured_events() replays
the stale key-down from the previous interaction — emitting an unexpected keycode
before the intended output.

Fix

Two changes to behavior_os_layer_mod.c:

  1. os_layer_mod_work_handler: call release_captured_events() immediately
    after do_hold() when the timer fires. This mirrors exactly what the
    interrupt-driven path already does in the event listener, and ensures the
    buffer is always cleared when a hold is decided.

  2. on_os_layer_mod_binding_released (HOLD path): reset captured_events_count = 0
    as a defensive safety net for any edge case where an event entered the buffer
    without being replayed.

Tests

  • Added tests/os-layer-mod/balanced-timer-fires-while-interrupt-held: presses
    the ok_ltm key, presses an interrupt key, then waits past tapping-term-ms
    while the interrupt key is still held. Verifies the interrupt key is replayed
    under the active layer (producing T while LALT is held), with no leakage
    into subsequent interactions.

  • Fixed run-tests to run all test suites under tests/ when invoked with
    no arguments, rather than only the oskey/ suite. The os-layer-mod suite was
    previously silently skipped.

@jamescrosswell
jamescrosswell merged commit 92c09b8 into main Mar 10, 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.

1 participant