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

578 lines
14 KiB
Markdown
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
```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 ✅