# 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 ```swift // 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: ```swift // 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 ✅