Skip to content

tap-hold pending_output (speculative Hold) - #709

Merged
rgoulter merged 6 commits into
masterfrom
pending-output-opaque-ref
Aug 29, 2026
Merged

tap-hold pending_output (speculative Hold)#709
rgoulter merged 6 commits into
masterfrom
pending-output-opaque-ref

Conversation

@rgoulter

@rgoulter rgoulter commented Aug 29, 2026

Copy link
Copy Markdown
Owner

Rewrite of #708: speculative eager Hold for tap-hold (FAK eager_decision='hold' / ZMK hold-while-undecided / QMK Speculative Hold) behind a generic System::pending_output seam.

Ref stays opaque. Guarding of what can be speculated is done in Nickel (keymap codegen), not by pattern-matching composite Ref in Rust.

Approach

  • System::pending_output(&PendingKeyState) -> Option<KeyOutput> is the keymap HID fold point (pressed_keys / aggregate_pressed_modifiers). keymap.rs stays generic.
  • Nickel bakes HID onto tap_hold::Key.hold_output from the hold keyboard leaf. LeftGUI / RightGUI and non-keyboard holds are omitted.
  • tap_hold::System::pending_output returns that stored KeyOutput when Profile.pending_output is Hold.
  • Generated composite System::pending_output delegates to the pending family's method (self.tap_hold.pending_output(pks)). It does not match on Ref.

Stack

  1. core: add System::pending_output hook — defaulted trait method; keymap fold includes it while a pending session is live.
  2. core: tap-hold pending_output profile and hold_outputPendingOutput (NoOutput|Hold), Key.hold_output: Option<KeyOutput>, PendingKeyState copies that HID when the profile is Hold.
  3. ncl: tap-hold pending_output authoring and codegenconfig.tap_hold.pending_output authoring/lowering; bake hold_output; composite delegates pending_output.
  4. test: refresh expected.rs snapshotsncl/scripts/save-test-snapshots.sh.
  5. test: add pending_output integration tests — 7 AAA tests in tests/rust/tap_hold/pending_output.rs (default silent, default timeout hold, Hold+first-tick, Hold+timeout, Hold+quick-release cancel, Hold+HoldOnKeyPress interrupt, GUI refusal).
  6. test: add pending_output cucumber featuretap_hold-config-pending_output.feature (3 scenarios; default Hold in Background).

Verification

  • cargo test --test rust-integration pending_output 7 passed
  • just test-fast 220 passed
  • just ncl::snapshots green
  • just check-quick fmt/clippy/doc green

Linger / same-mod ZMK tap deferred to follow-on.

Supersedes #708.

rgoulter and others added 6 commits August 29, 2026 19:37
Add a defaulted trait method System::pending_output(&PKS) -> Option<KeyOutput>
so Keymap's HID fold can include speculative output without
special-casing tap-hold in keymap.rs. The fold now includes
pending_output in pressed_keys and aggregate_pressed_modifiers
while a pending session is live, using iterator chain rather than
a mutating push.

This is the generic seam for tap-hold Hold-while-undecided;
other families keep the default None (no output while pending).

Model: grok-4.6
Agent: Grok Build
Co-Authored-By: Grok 4.6 <grok-4.6@x.ai>
Co-Authored-By: Grok Build <grok-build@x.ai>
Add PendingOutput (NoOutput|Hold) to Profile, store baked HID on
Key.hold_output, and copy it into PendingKeyState when pending_output
is Hold. tap_hold::System::pending_output returns that stored
KeyOutput; it never inspects composite Ref.

Nickel fills hold_output from the hold keyboard leaf
(non-keyboard and LeftGUI/RightGUI stay None).

Model: grok-4.6
Agent: Grok Build
Co-Authored-By: Grok 4.6 <grok-4.6@x.ai>
Co-Authored-By: Grok Build <grok-build@x.ai>
Add pending_output (NoOutput|Hold) to tap-hold Profile/Config, lower
flat config.tap_hold.pending_output to default_profile, and bake
hold_output on tap-hold Key data from the hold keyboard leaf.
LeftGUI/RightGUI and non-keyboard holds are omitted.

Composite System::pending_output delegates to each pending family's
System::pending_output; it does not match on aggregate Ref.

Model: grok-4.6
Agent: Grok Build
Co-Authored-By: Grok 4.6 <grok-4.6@x.ai>
Co-Authored-By: Grok Build <grok-build@x.ai>
Generated via ncl/scripts/save-test-snapshots.sh after adding
System::pending_output and baking Key.hold_output in tap-hold codegen.

Model: grok-4.6
Agent: Grok Build
Co-Authored-By: Grok 4.6 <grok-4.6@x.ai>
Co-Authored-By: Grok Build <grok-build@x.ai>
Add tests/rust/tap_hold/pending_output.rs with 7 AAA tests: default
silent, default timeout hold, Hold+first-tick, Hold+timeout,
Hold+quick-release cancel, Hold+HoldOnKeyPress interrupt, and GUI
refusal. AAA prose is inside each test body. Register the module
from tests/rust/tap_hold.rs.

Model: grok-4.6
Agent: Grok Build
Co-Authored-By: Grok 4.6 <grok-4.6@x.ai>
Co-Authored-By: Grok Build <grok-build@x.ai>
Add features/keymap/key/tap_hold-config-pending_output.feature
with 3 scenarios (Hold+timeout, Hold+quick-release retracts to tap,
Hold+HoldOnKeyPress). Default Hold is in Background; default silent
is covered in rust integration test. Prose uses NoOutput, no emdash,
semantic linebreaks, and matches FAK eager_decision, ZMK
hold-while-undecided, and QMK Speculative Hold.

Model: grok-4.6
Agent: Grok Build
Co-Authored-By: Grok 4.6 <grok-4.6@x.ai>
Co-Authored-By: Grok Build <grok-build@x.ai>
Comment on lines +215 to +222
224 => 1,
225 => 2,
226 => 4,
227 => 8,
228 => 16,
229 => 32,
230 => 64,
231 => 128,

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Let's use hex values, or otherwise comment this so it's clearer / easier to cross reference.

@rgoulter
rgoulter merged commit d9603ce into master Aug 29, 2026
13 checks passed
@rgoulter
rgoulter deleted the pending-output-opaque-ref branch August 29, 2026 12:46
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