Files
gart/code/Hex-main/docs/hotkey-semantics.md
T

14 KiB
Executable File

Hex HotKey Semantics

Overview

Hex uses a threshold-based recording system that behaves differently depending on whether your hotkey is modifier-only (e.g., Option) or a regular hotkey (e.g., Cmd+A).

The key insight: Modifier-only hotkeys need protection from accidental triggers (like quick Option taps for special characters), while regular hotkeys are inherently intentional.


Quick Reference

Modifier-Only Hotkeys (e.g., Option, Option+Command)

Timeline: Press Option → START recording

Before 0.3s (< 0.3s):

  • Release → DISCARD (silent)
  • Click → DISCARD (silent)
  • Press A → DISCARD (silent)
  • Add Shift → DISCARD (silent)
  • All actions trigger silent discard

After 0.3s (≥ 0.3s):

  • Release → STOP (transcribe)
  • Click → NOP (ignore, keep recording)
  • Press A → NOP (ignore, keep recording)
  • Add Shift → NOP (ignore, keep recording)
  • ESC → CANCEL (only way to stop)

Key points:

  • < 0.3s: Everything except ESC triggers silent discard (no sound)
  • ≥ 0.3s: Only ESC cancels (with sound), everything else is ignored
  • Recording continues until you release the modifier or press ESC

Regular Hotkeys (e.g., Cmd+A, Option+K)

Timeline: Press Cmd+A → START recording

Before 0.2s (< minimumKeyTime):

  • Release → DISCARD (silent)
  • Click → DISCARD (silent)

Between 0.2s - 1.0s:

  • Press different key (e.g., Cmd+B) → STOP (with sound)
  • Add modifier (e.g., Shift) → STOP (with sound)

After 1.0s (> 1.0s):

  • Press different key → NOP (ignore, keep recording)
  • Add modifier → NOP (ignore, keep recording)
  • Allows typing while recording

Any time:

  • Release → STOP (transcribe if long enough)
  • ESC → CANCEL

Key points:

  • < minimumKeyTime (default 0.2s): Silent discard
  • 0.2s - 1.0s: Different key/modifier triggers stop (with sound)
  • > 1.0s: Everything is ignored (allows typing while recording)
  • Release or ESC always stops

Constants & Thresholds

// For modifier-only hotkeys (system safety)
modifierOnlyMinimumDuration = 0.3s

// For all hotkeys (user-configurable)
minimumKeyTime = 0.2s (default)

// Other thresholds
doubleTapThreshold = 0.3s           // Max time between taps
pressAndHoldCancelThreshold = 1.0s  // For regular hotkeys only

Effective Thresholds

The actual threshold used depends on the hotkey type:

// Modifier-only (e.g., Option)
effectiveThreshold = max(minimumKeyTime, 0.3s)
// User sets 0.1s → uses 0.3s
// User sets 0.5s → uses 0.5s

// Regular (e.g., Cmd+A)
effectiveThreshold = minimumKeyTime
// User sets 0.1s → uses 0.1s
// User sets 0.5s → uses 0.5s

Recording Decision Matrix

When you release the hotkey, should we transcribe the recording?

Modifier-only (Option):

  • Duration < 0.3s → Discard (silent)
  • Duration ≥ 0.3s → Transcribe

Regular (Cmd+A):

  • Duration < 0.2s (or < minimumKeyTime) → Discard (silent)
  • Duration ≥ 0.2s (or ≥ minimumKeyTime) → Transcribe

Note: minimumKeyTime can be adjusted by user, but modifier-only always enforces 0.3s minimum


Detailed Behaviors

1. Modifier-Only: Option

Scenario A: Quick tap (< 0.3s)

User: Hold Option (0.1s) → Release
      ↓
  START ────→ DISCARD (silent)
  
Result: No transcription, no sound
Why: Likely accidental (Option+Click, Option+A for special chars)

Scenario B: Hold and click (< 0.3s)

User: Hold Option (0.25s) → Click mouse
      ↓
  START ────→ DISCARD (silent)
  
Result: No transcription, no sound, click passes through
Why: Option+Click is for duplicating items in macOS

Scenario C: Hold and press A (< 0.3s)

User: Hold Option (0.2s) → Press A
      ↓
  START ────→ DISCARD (silent)
  
Result: No transcription, Option+A passes through to macOS
Why: Option+A might be for special character "å"

Scenario D: Hold longer (≥ 0.3s)

User: Hold Option (0.5s) → Release
      ↓
  START ───────────→ TRANSCRIBE
  
Result: Audio transcribed and pasted

Scenario E: Hold, then click (≥ 0.3s)

User: Hold Option (0.5s) → Click mouse
      ↓
  START ───────────→ (ignored, keeps recording)
  
Result: Recording continues, click passes through
Why: After 0.3s, we assume intentional recording

Scenario F: Hold, then add Shift (≥ 0.3s)

User: Hold Option (0.5s) → Add Shift
      ↓
  START ───────────→ (ignored, keeps recording)
  
Result: Recording continues, Option+Shift passes through
Why: User might be pressing Shift for capital letters while speaking

Scenario G: ESC cancels anytime

User: Hold Option (any duration) → Press ESC
      ↓
  START ───────────→ CANCEL (with sound)
  
Result: Recording cancelled, cancel sound plays
Why: ESC is explicit "I want to cancel" gesture

2. Regular Hotkey: Cmd+A

Scenario A: Quick tap (< 0.2s)

User: Hold Cmd+A (0.1s) → Release
      ↓
  START ────→ DISCARD (silent)

Scenario B: Press different key within 1s

User: Hold Cmd+A (0.5s) → Press Cmd+B
      ↓
  START ───────────→ STOP (with sound)
  
Result: Recording stopped, audio discarded
Why: User is likely using other Cmd shortcuts

Scenario C: Press different key after 1s

User: Hold Cmd+A (1.5s) → Press Cmd+B
      ↓
  START ───────────────────→ (ignored, keeps recording)
  
Result: Recording continues, Cmd+B passes through
Why: After 1s, assume user is transcribing while typing

Scenario D: Add modifier (< 1s)

User: Hold Cmd+A (0.5s) → Add Shift (Cmd+Shift+A)
      ↓
  START ───────────→ STOP (with sound)
  
Result: Recording stopped
Why: Cmd+Shift+A is likely a different command

3. Multi-Modifier: Option+Command

Behaves like single modifier (uses 0.3s threshold):

User: Hold Option+Command (0.25s) → Add Shift
      ↓
  START ────→ DISCARD (silent)

User: Hold Option+Command (0.5s) → Add Shift
      ↓
  START ───────────→ (ignored, keeps recording)

Partial release = full release:

User: Hold Option+Command → Release Command (keep Option)
      ↓
  START ───────────→ STOP
  
Result: Recording stopped and transcribed (if ≥ 0.3s)
Why: Releasing any part of the hotkey = release

Double-Tap Lock

Quick double-tap locks recording on (hands-free mode)

Timeline

0.0s: Tap hotkey ──────────→ START
0.1s: Release ─────────────→ STOP
0.2s: Tap again ───────────→ START
0.3s: Release ─────────────→ LOCK! (Δt = 0.2s < 0.3s)

Now recording is locked on:
- Release doesn't stop it
- Tap hotkey again to stop
- ESC also stops

5.0s: Tap hotkey ──────────→ STOP

Sequence

  1. Tap 1 (t=0.0s) → START recording
  2. Release (t=0.1s) → STOP (normal behavior)
  3. Tap 2 (t=0.2s, within 0.3s window) → START recording again
  4. Release (t=0.3s, Δt=0.2s < 0.3s) → LOCK! (hands-free mode)
  5. Recording continues until:
    • Tap hotkey again → STOP
    • Press ESC → STOP

Rules

  1. Timing window: 2nd tap must be within 0.3s of 1st release
  2. Lock engages: On 2nd release, not 2nd press
  3. Exit lock: Tap hotkey again OR press ESC
  4. Too slow: If 2nd tap > 0.3s after 1st release, treated as new recording

Output Types

  • .discard (silent)

    • Stop recording
    • Discard audio
    • Pass keys through to macOS
  • .stop (with stop sound)

    • Stop recording
    • Transcribe if duration ≥ threshold
  • .cancel (with cancel sound)

    • Stop recording
    • Discard audio
    • Play cancel sound

Key Interception

When does Hex intercept (block) key events from reaching other apps?

Key Interception Rules:

  • Press modifier-only hotkey (Option) → No (passes through)
  • Press regular hotkey (Cmd+A) → Yes (blocked)
  • .discard output → No (passes through) ← CRITICAL!
  • .cancel output → Yes (blocked)
  • Mouse clicks → Never intercepted (always pass through)

Example:

User: Press Option (0.2s) → Press A
      ↓
  START → DISCARD (passes through)
  
Result: Hex discards recording silently
        macOS sees Option+A
        Special character dialog appears ✅

Dirty State

Prevents re-triggering until full release

What triggers dirty?

Modifier-only (Option):

  • Add extra modifier within 0.3s → dirty
  • Press any key within 0.3s → dirty
  • Click mouse within 0.3s → dirty

Regular (Cmd+A):

  • Press different key within 1s → dirty
  • Change modifiers within 1s → dirty

Dirty behavior

User: Hold Option (0.1s) → Add Shift → Release Shift → Press Option again
      ↓
  START → DISCARD (dirty=true) → (ignored) → (ignored)
  
  User must release EVERYTHING (∅) to clear dirty
  
  → Release all keys → Now Option works again

State Flow

CLEAN → [trigger dirty condition] → DIRTY → [full release (all keys)] → CLEAN

  • CLEAN: Accepts hotkey input
  • DIRTY: Ignores all input until full release (all keys released)

Decision Tree

Processing order:

  1. Is ESC pressed?

    • YES → CANCEL (exit)
  2. Are we dirty?

    • YES → Ignore input (unless full release)
  3. Does chord match hotkey exactly?

    • NO → Check if recording active
      • Recording active → Handle based on elapsed time
      • Not recording → Ignore input
    • YES → Continue to step 4
  4. Is recording active?

    • NO → START recording
    • YES → Check hotkey type and elapsed time
  5. If recording active:

    • Modifier-only?
      • YES → Check elapsed < max(0.3s, minimumKeyTime)
        • YES → DISCARD or (ignore if ≥0.3s)
        • NO → (ignore)
    • Regular hotkey?
      • Check elapsed < 1s
        • YES → Check elapsed < minimumKeyTime
          • YES → DISCARD
          • NO → STOP
        • NO → (ignore)
  6. Final action:

    • START or STOP (depending on current state)

State Machine

States:

  • IDLE

    • Transition: chord matches hotkey → PRESS & HOLD
  • PRESS & HOLD (recording)

    • On release (normal):
      • Check elapsed time
      • If < 0.3s (modifier-only) or < minimumKeyTime (regular) → DISCARD
      • If ≥ threshold → Check last tap timing
        • If Δt < 0.3s → LOCK
        • Otherwise → STOP → IDLE
    • On other input:
      • Check elapsed time
      • If < 0.3s → DISCARD → IDLE
      • If ≥ 0.3s → (ignore, keep recording)
  • LOCK (hands-free recording)

    • Transition: Tap hotkey again OR press ESC → STOP → IDLE

Examples with Full Explanations

Example 1: Quick Option tap for special character

Goal: Type "å" (Option+A)

Timeline:
  t=0.0s  Press Option          → START recording
  t=0.15s Press A               → DISCARD (< 0.3s)
                                  Option+A passes to macOS
  
macOS sees: Option+A
Result: Special character dialog appears ✅
Hex: Silent discard, no transcription

Why this works:

  • Recording starts immediately (responsive)
  • But discarded if < 0.3s (safety)
  • Keys pass through (Option+A reaches macOS)

Example 2: Intentional voice recording with Option

Goal: Record a voice note

Timeline:
  t=0.0s  Press Option          → START recording
  t=0.5s  Still holding...      (recording audio)
  t=2.0s  Release Option        → STOP, TRANSCRIBE
  
Result: Audio transcribed and pasted ✅

Example 3: Option-click to duplicate

Goal: Duplicate file in Finder

Timeline:
  t=0.0s  Press Option          → START recording
  t=0.2s  Click file            → DISCARD (< 0.3s)
                                  Click passes through
  
Finder sees: Option+Click
Result: File duplicated ✅
Hex: Silent discard

Example 4: Recording while typing

Goal: Dictate code comments while typing

Timeline:
  t=0.0s  Press Option          → START recording
  t=0.5s  Still talking...      (recording)
  t=2.0s  Press Cmd+Tab         → IGNORED (> 0.3s)
                                  Cmd+Tab passes through
  t=3.0s  Type some code        → IGNORED
  t=5.0s  Release Option        → STOP, TRANSCRIBE
  
Result: Audio transcribed ✅
        Cmd+Tab worked ✅
        Typing worked ✅

Why: After 0.3s, Hex assumes you're intentionally recording and ignores other input (except ESC).


Example 5: Accidental recording cancellation

Goal: Cancel accidental recording

Timeline:
  t=0.0s  Press Option          → START recording
  t=1.0s  "Oh no, accident!"
  t=1.5s  Press ESC             → CANCEL (with sound)
  
Result: Recording cancelled ✅
        Cancel sound plays

Summary Table

Modifier-only (Option):

  • Time < 0.3s:

    • Release → Discard
    • Click → Discard
    • Press key → Discard
    • Add modifier → Discard
  • Time ≥ 0.3s:

    • Release → Transcribe
    • Click → Ignore (keep recording)
    • Press key → Ignore (keep recording)
    • Add modifier → Ignore (keep recording)
    • ESC → Cancel

Regular (Cmd+A):

  • Time < minimumKeyTime:

    • Release → Discard
  • Time: minimumKeyTime - 1s:

    • Other key → Stop
    • Add modifier → Stop
  • Time > 1s:

    • Other key → Ignore (keep recording)
    • Add modifier → Ignore (keep recording)
  • Any time:

    • Release → Transcribe (if long enough)
    • ESC → Cancel

Implementation Files

  • Core Logic: HexCore/Sources/HexCore/Logic/HotKeyProcessor.swift
  • Recording Decision: HexCore/Sources/HexCore/Logic/RecordingDecision.swift
  • Feature Integration: Hex/Features/Transcription/TranscriptionFeature.swift
  • Tests: HexCore/Tests/HexCoreTests/HotKeyProcessorTests.swift

Document Version: 2.0
Last Updated: 2025-11-14
Total Tests: 46 passing ✅