Skip to content

Allow double-tap-only mode for modifier hotkeys#227

Open
tylerlaprade wants to merge 1 commit into
kitlangton:mainfrom
tylerlaprade:modifier-double-tap-only
Open

Allow double-tap-only mode for modifier hotkeys#227
tylerlaprade wants to merge 1 commit into
kitlangton:mainfrom
tylerlaprade:modifier-double-tap-only

Conversation

@tylerlaprade

@tylerlaprade tylerlaprade commented May 18, 2026

Copy link
Copy Markdown

This removes the key+modifier-only restriction from double-tap-only mode, so modifier-only hotkeys like Cmd or Option can use it too.

It also tightens the Settings UI:

  • Hide Use double-tap only unless double-tap lock is enabled.
  • Hide the hold threshold slider while double-tap-only mode is active.

Refs #78. Related: #120.

Verified:

  • Manually tested Cmd as a modifier-only double-tap-only hotkey in the debug app.
  • swift test --build-path /private/tmp/hexcore-swift-test-build
  • xcodebuild -scheme Hex -configuration Debug -derivedDataPath /private/tmp/HexDerivedData -skipMacroValidation CODE_SIGNING_ALLOWED=NO build
image

Summary by CodeRabbit

  • New Features

    • Modifier-only shortcuts can now use double-tap-only mode.
  • Bug Fixes

    • Double-tap-only reliably ignores single taps and preserves correct timing.
    • The double-tap-only toggle and minimum-key-time control are shown only when appropriate, hiding inactive hold controls.
  • Tests

    • Added tests covering double-tap-only behavior for standard and modifier-only shortcuts.
  • Chore

    • Patch release entry added.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 18, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7a025a2c-cfe0-4ee3-b05e-8726a749d4fb

📥 Commits

Reviewing files that changed from the base of the PR and between 196a67d and 47b5982.

📒 Files selected for processing (4)
  • .changeset/72ce3372.md
  • Hex/Features/Settings/HotKeySectionView.swift
  • HexCore/Sources/HexCore/Logic/HotKeyProcessor.swift
  • HexCore/Tests/HexCoreTests/HotKeyProcessorTests.swift
✅ Files skipped from review due to trivial changes (1)
  • .changeset/72ce3372.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • HexCore/Tests/HexCoreTests/HotKeyProcessorTests.swift
  • Hex/Features/Settings/HotKeySectionView.swift
  • HexCore/Sources/HexCore/Logic/HotKeyProcessor.swift

📝 Walkthrough

Walkthrough

Enables double-tap-only for modifier-only and key+modifier hotkeys by revising HotKeyProcessor timing/state tracking, updates HotKeySectionView visibility for related controls, adds unit tests for both patterns, and adds a changeset release note.

Changes

Double-tap-only hotkey support

Layer / File(s) Summary
Double-tap-only state machine and timing logic
HexCore/Sources/HexCore/Logic/HotKeyProcessor.swift
New doubleTapOnlyPressStartedAt timestamp and docs; removed key-requirement for double-tap-only; handleMatchingChord() records first-press time; handleNonmatchingChord() computes first-press→release duration and sets lastTapAt when within doubleTapThreshold; dirty-state and resetToIdle() clear double-tap tracking via clearDoubleTapTracking().
UI controls for double-tap-only modes
Hex/Features/Settings/HotKeySectionView.swift
Use double-tap only toggle visibility now depends on store.hexSettings.doubleTapLockEnabled; Minimum key time slider shown only for modifier-only hotkeys and hidden when double-tap-only is active.
Double-tap-only test coverage for both hotkey patterns
HexCore/Tests/HexCoreTests/HotKeyProcessorTests.swift
Added tests for key+modifier and modifier-only double-tap-only flows covering arming, ignoring slow taps, partial modifier release scenarios, lock arming, and stop conditions.
Release notes documenting user-facing behavior changes
.changeset/72ce3372.md
Patch release entry for hex-app documenting modifier-only double-tap-only support and hiding inactive hold controls.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant HotKeyProcessor
  participant State
  User->>HotKeyProcessor: First press (matching chord)
  HotKeyProcessor->>State: Store doubleTapOnlyPressStartedAt
  HotKeyProcessor-->>User: No recording yet (await second tap)
  User->>HotKeyProcessor: Release first press (non-matching)
  HotKeyProcessor->>State: Compute elapsed since doubleTapOnlyPressStartedAt
  HotKeyProcessor-->>State: Set or clear lastTapAt based on threshold
  User->>HotKeyProcessor: Second press (matching within threshold)
  HotKeyProcessor->>State: Enter doubleTapLock and start recording
  HotKeyProcessor-->>User: Recording active
  User->>HotKeyProcessor: Release or third press
  HotKeyProcessor->>State: Stop recording and reset tracking
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • kitlangton/Hex#175: Introduces the doubleTapLockEnabled gating mechanism that this PR builds upon for UI visibility and state management.

Suggested reviewers

  • kitlangton

"🐰
A double-tap dance in binary beat,
Single presses fade, but paired ones greet,
Modifiers alone now join the show,
Timing tracked so recordings flow,
Hops of joy for code that's neat!"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: enabling double-tap-only mode for modifier-only hotkeys, which aligns with the PR's primary objective.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@tylerlaprade tylerlaprade force-pushed the modifier-double-tap-only branch from 48a015e to 196a67d Compare May 18, 2026 19:48
@tylerlaprade tylerlaprade changed the title Fix modifier-only double-tap hotkeys Allow double-tap-only mode for modifier hotkeys May 18, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
HexCore/Tests/HexCoreTests/HotKeyProcessorTests.swift (1)

372-384: ⚡ Quick win

Assert lock state where the test claims “starts recording in lock mode.”

doubleTapOnly_modifierOnly_requiresSecondPress documents lock behavior but does not assert expectedState, which leaves a small regression gap.

Suggested test assertion update
-                ScenarioStep(time: 0.2, key: nil, modifiers: [.command], expectedOutput: .startRecording, expectedIsMatched: true),
+                ScenarioStep(time: 0.2, key: nil, modifiers: [.command], expectedOutput: .startRecording, expectedIsMatched: true, expectedState: .doubleTapLock),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@HexCore/Tests/HexCoreTests/HotKeyProcessorTests.swift` around lines 372 -
384, The test doubleTapOnly_modifierOnly_requiresSecondPress documents that the
second press "starts recording in lock mode" but doesn't assert it; update the
third ScenarioStep passed to runScenario (the one at time 0.2) to include the
expectedState asserting the lock state (e.g. expectedState: .locked or whatever
enum/case your tests use for the recording-locked state) so the test verifies
the processor entered lock mode when startRecording is emitted; keep earlier
steps' expectedState as the idle/non-locked state as appropriate.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@HexCore/Tests/HexCoreTests/HotKeyProcessorTests.swift`:
- Line 336: Several ScenarioStep initializers in HotKeyProcessorTests.swift have
trailing commas in their collection literals (e.g., ScenarioStep(time: 0.2, key:
nil, modifiers: [], expectedOutput: nil, expectedIsMatched: false),) which
triggers SwiftLint trailing_comma warnings; edit each offending literal
(instances of ScenarioStep in the test arrays) to remove the final comma after
the last argument so the initializer calls end without a trailing comma, and
repeat this fix for all occurrences mentioned (the other ScenarioStep lines in
the file).

---

Nitpick comments:
In `@HexCore/Tests/HexCoreTests/HotKeyProcessorTests.swift`:
- Around line 372-384: The test doubleTapOnly_modifierOnly_requiresSecondPress
documents that the second press "starts recording in lock mode" but doesn't
assert it; update the third ScenarioStep passed to runScenario (the one at time
0.2) to include the expectedState asserting the lock state (e.g. expectedState:
.locked or whatever enum/case your tests use for the recording-locked state) so
the test verifies the processor entered lock mode when startRecording is
emitted; keep earlier steps' expectedState as the idle/non-locked state as
appropriate.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 595aa80e-add6-471b-9d6f-b43d4091e9bd

📥 Commits

Reviewing files that changed from the base of the PR and between 48a015e and 196a67d.

📒 Files selected for processing (4)
  • .changeset/72ce3372.md
  • Hex/Features/Settings/HotKeySectionView.swift
  • HexCore/Sources/HexCore/Logic/HotKeyProcessor.swift
  • HexCore/Tests/HexCoreTests/HotKeyProcessorTests.swift
✅ Files skipped from review due to trivial changes (1)
  • .changeset/72ce3372.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • Hex/Features/Settings/HotKeySectionView.swift
  • HexCore/Sources/HexCore/Logic/HotKeyProcessor.swift

steps: [
ScenarioStep(time: 0.0, key: .a, modifiers: [.command], expectedOutput: nil, expectedIsMatched: false),
ScenarioStep(time: 0.1, key: nil, modifiers: [.command], expectedOutput: nil, expectedIsMatched: false),
ScenarioStep(time: 0.2, key: nil, modifiers: [], expectedOutput: nil, expectedIsMatched: false),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove trailing commas in collection literals to clear SwiftLint warnings.

These lines currently trigger trailing_comma warnings and should be cleaned up to keep test lint output clean.

Also applies to: 349-349, 365-365, 383-383, 400-400, 418-418, 432-432, 445-445, 460-460

🧰 Tools
🪛 SwiftLint (0.63.2)

[Warning] 336-336: Collection literals should not have trailing commas

(trailing_comma)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@HexCore/Tests/HexCoreTests/HotKeyProcessorTests.swift` at line 336, Several
ScenarioStep initializers in HotKeyProcessorTests.swift have trailing commas in
their collection literals (e.g., ScenarioStep(time: 0.2, key: nil, modifiers:
[], expectedOutput: nil, expectedIsMatched: false),) which triggers SwiftLint
trailing_comma warnings; edit each offending literal (instances of ScenarioStep
in the test arrays) to remove the final comma after the last argument so the
initializer calls end without a trailing comma, and repeat this fix for all
occurrences mentioned (the other ScenarioStep lines in the file).

Modifier-only hotkeys can now use the existing double-tap-only mode. The settings UI also hides hotkey controls that do not apply to the current mode.

Refs kitlangton#78, kitlangton#120.
@tylerlaprade tylerlaprade force-pushed the modifier-double-tap-only branch from 196a67d to 47b5982 Compare May 18, 2026 19:53
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