Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ These are absolute rules — never violate them:
- **Turbo navigation pre-hide**: `turbo:before-render` listener in pre-hide.ts hides key elements in incoming body BEFORE Turbo renders. Prevents GitHub PAT flash on partial page transition.
- **OpenAI pre-hide scope**: preHideCSS must NOT include `td.api-key-token .api-key-token-value` — these are truncated previews (sk-...QngA) that never match full patterns, causing permanent hidden state.
- **Toast duration**: 25 seconds (changed from 10s for better visibility during demos).
- **Sequential paste pre-fetch**: `SequentialPasteEngine` pre-fetches ALL key values from Keychain before starting the paste sequence. This ensures Keychain auth dialogs (if any) appear upfront, not between ⌘V→Tab→⌘V steps. Phase 2 writes directly to `NSPasteboard`, bypassing `ClipboardEngine.copyToClipboard`.
- **CGEvent modifier isolation**: When simulating ⌘V followed by Tab, the Tab `CGEvent` must explicitly set `flags = []` to clear residual Command modifier. Without this, the system interprets Tab as ⌘Tab (app switcher). Use `CGEventSource(stateID: .combinedSessionState)` for isolated event sources.
- **Paste key shortcut**: Changed from `⌃⌥[1-9]` to `⌃⌥⌘[1-9]` to avoid conflicts with system/app shortcuts. All ⌃⌥⌘ shortcuts are consistent: D (demo), V (capture), [1-9] (paste).

## Documentation

Expand Down
6 changes: 3 additions & 3 deletions docs/01-product-spec/implementation-status.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
| ClipboardEngine | ✅ | copy + autoClear + detectKeys 完成 |
| MaskingCoordinator | ✅ | isDemoMode / activeContext / pattern 匹配完成 |
| IPCServer (WebSocket) | ✅ | handshake / state_changed / pattern_cache_sync / toggle_demo_mode / nmh clientType |
| HotkeyManager | ✅ | `⌃⌥⌘D` toggle、`⌃⌥Space` hold 偵測、`⌃⌥[1-9]` paste、flagsChanged 監聽 |
| HotkeyManager | ✅ | `⌃⌥⌘D` toggle、`⌃⌥Space` hold 偵測、`⌃⌥[1-9]` paste、flagsChanged 監聽 |
| Floating Toolbox (HUD) | ✅ | NSPanel 浮動視窗、hold-to-search、Scheme B 鎖定、↑↓ 導航 |
| ToolboxState (ViewModel) | ✅ | 搜尋過濾、選取狀態、release/confirm/dismiss 邏輯 |
| FloatingToolboxController | ✅ | NSPanel 管理、游標定位、鎖定模式 makeKey |
Expand All @@ -33,7 +33,7 @@
| 功能 | 對應 Spec 章節 | 優先順序 | 備註 |
|------|--------------|---------|---------|
| `⌃⌥⌘V` capture clipboard | Spec §4.4 | 中 | HotkeyManager 端尚未掛載 ClipboardEngine.detectKeys |
| Linked Key Groups (sequential paste) | Spec §6.3 | | `LinkedGroup`/`GroupEntry` 結構未建立 |
| ~~Linked Key Groups (sequential paste)~~ | Spec §6.3 | ~~中~~ | `LinkedGroup`/`GroupEntry`/`SequentialPasteEngine` 完成、Settings UI 群組管理(CRUD)、`request_paste_group` IPC handler |
| Shortcut conflict detection | Spec §4.4 | 低 | |
| Import / Export vault | Spec §9.1 | 低 | |

Expand Down Expand Up @@ -129,7 +129,7 @@
### Phase 2: 剪貼簿 + 快捷鍵 ✅
6. ~~ClipboardEngine~~ ✅
7. ~~HotkeyManager(hold 偵測 + flagsChanged + 字元轉發)~~ ✅
8. ~~Floating Toolbox HUD(NSPanel + hold-to-search + Scheme B 鎖定 + `⌃⌥[1-9]` paste)~~ ✅
8. ~~Floating Toolbox HUD(NSPanel + hold-to-search + Scheme B 鎖定 + `⌃⌥[1-9]` paste)~~ ✅

### Phase 3: IPC + VS Code Extension ✅
9. ~~IPCServer~~ ✅
Expand Down
2 changes: 1 addition & 1 deletion docs/01-product-spec/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
- macOS Menu Bar App (Swift/SwiftUI),Key 儲存基於 Keychain
- 展示模式切換與視覺指示
- 浮動工具箱,支援按住搜尋與方案 B 鎖定
- 鍵盤快捷鍵:`⌃⌥Space`、`⌃⌥⌘D`、`⌃⌥[1-9]`、`⌃⌥⌘V`
- 鍵盤快捷鍵:`⌃⌥Space`、`⌃⌥⌘D`、`⌃⌥[1-9]`、`⌃⌥⌘V`
- VS Code Extension,使用 Decoration API 遮蔽
- 雙層 Key 階層與手動 Key 輸入
- 基本關聯 Key 群組(順序貼上)
Expand Down
2 changes: 1 addition & 1 deletion docs/02-technical-architecture/swift-core-modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ DemoSafeApp (SwiftUI)
|--------|------|------|
| `⌃⌥Space` | `toggleToolbox()` | 顯示/隱藏浮動工具箱 |
| `⌃⌥⌘D` | `toggleDemoMode()` | 切換展示/一般模式 |
| `⌃⌥[1-9]` | `pasteKeyByIndex()` | 依快捷鍵索引貼上金鑰 |
| `⌃⌥[1-9]` | `pasteKeyByIndex()` | 依快捷鍵索引貼上金鑰 |
| `⌃⌥⌘V` | `captureClipboard()` | 掃描並儲存當前剪貼簿內容 |

### 按住偵測邏輯
Expand Down
2 changes: 1 addition & 1 deletion docs/04-ui-ux/floating-toolbox.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
| 搜尋/過濾 | 繼續按住 + 輸入字元 | Key 清單即時過濾 |
| 快速貼上(單一結果) | 僅剩 1 個結果時放開 | Key 貼入,工具箱消失 |
| 從多個結果中選擇 | 放開 → 工具箱鎖定;用 `↑↓` + `Enter` | 按 Enter 貼入 Key,Esc 取消 |
| 依編號直接貼上 | 按 `⌃⌥[1-9]` | 立即貼入第 N 個 Key,無需工具箱 |
| 依編號直接貼上 | 按 `⌃⌥[1-9]` | 立即貼入第 N 個 Key,無需工具箱 |

## 鎖定行為(方案 B)

Expand Down
2 changes: 1 addition & 1 deletion docs/04-ui-ux/keyboard-shortcuts.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
|------|-----------|---------|
| 切換浮動工具箱 | `⌃⌥Space` | 左手組合鍵,與應用程式衝突極少 |
| 切換展示模式 | `⌃⌥⌘D` | 三個修飾鍵防止意外觸發 |
| 直接貼上第 N 個 Key | `⌃⌥[1-9]` | 已知 Key 位置的最快路徑 |
| 直接貼上第 N 個 Key | `⌃⌥[1-9]` | 已知 Key 位置的最快路徑 |
| 快速擷取剪貼簿內容 | `⌃⌥⌘V` | 貼上 + 自動解析入管理器 |

## 自訂設定
Expand Down
17 changes: 10 additions & 7 deletions docs/04-ui-ux/smart-key-extraction.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,29 +136,32 @@ struct LinkedGroup: Codable, Identifiable {
let id: UUID
var label: String // 如 "AWS Production"
var entries: [GroupEntry] // 有序的 Key 列表
var pasteMode: PasteMode // .sequential 或 .fieldSelect
var pasteMode: PasteMode // .sequential 或 .selectField
var createdAt: Date
}

struct GroupEntry: Codable {
let keyId: UUID
let fieldLabel: String // 如 "Access Key ID"、"Secret Key"
var fieldLabel: String // 如 "Access Key ID"、"Secret Key"
var sortOrder: Int
}

enum PasteMode: String, Codable {
case sequential // 按 Tab 自動依序貼入
case fieldSelect // 顯示選單讓使用者選擇
case selectField // 使用者選擇要貼上的欄位
case sequential // 按 Tab 自動依序貼入
}
```

### 順序貼上模擬

```
使用者觸發 LinkedGroup paste(⌃⌥[N] 對應群組)
使用者觸發 LinkedGroup paste(⌃⌥[N] 對應群組)
1. 貼上 entries[0].value(Access Key ID)
SequentialPasteEngine 預先從 Keychain 取出所有 key(避免中途跳認證窗)
1. 寫入 entries[0].value 到剪貼簿 → 模擬 ⌘V
2. 模擬 Tab 鍵
3. 貼上 entries[1].value(Secret Key)
3. 寫入 entries[1].value 到剪貼簿 → 模擬 ⌘V
完成,兩個欄位同時填入
```
6 changes: 3 additions & 3 deletions docs/en/01-product-spec/implementation-status.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
| ClipboardEngine | ✅ | copy + autoClear + detectKeys completed |
| MaskingCoordinator | ✅ | isDemoMode / activeContext / pattern matching completed |
| IPCServer (WebSocket) | ✅ | handshake / state_changed / pattern_cache_sync / toggle_demo_mode / nmh clientType |
| HotkeyManager | ✅ | `⌃⌥⌘D` toggle, `⌃⌥Space` hold detection, `⌃⌥[1-9]` paste, flagsChanged listener |
| HotkeyManager | ✅ | `⌃⌥⌘D` toggle, `⌃⌥Space` hold detection, `⌃⌥[1-9]` paste, flagsChanged listener |
| Floating Toolbox (HUD) | ✅ | NSPanel floating window, hold-to-search, Scheme B lock, ↑↓ navigation |
| ToolboxState (ViewModel) | ✅ | Search filtering, selection state, release/confirm/dismiss logic |
| FloatingToolboxController | ✅ | NSPanel management, cursor positioning, locked mode makeKey |
Expand All @@ -33,7 +33,7 @@
| Feature | Spec Section | Priority | Notes |
|---------|-------------|----------|---------|
| `⌃⌥⌘V` capture clipboard | Spec §4.4 | Medium | HotkeyManager not yet wired to ClipboardEngine.detectKeys |
| Linked Key Groups (sequential paste) | Spec §6.3 | Medium | `LinkedGroup`/`GroupEntry` structs not yet created |
| ~~Linked Key Groups (sequential paste)~~ | Spec §6.3 | ~~Medium~~ | `LinkedGroup`/`GroupEntry`/`SequentialPasteEngine` complete, Settings UI group management (CRUD), `request_paste_group` IPC handler |
| Shortcut conflict detection | Spec §4.4 | Low | |
| Import / Export vault | Spec §9.1 | Low | |

Expand Down Expand Up @@ -129,7 +129,7 @@ Per Spec §9 Roadmap:
### Phase 2: Clipboard + Hotkeys ✅
6. ~~ClipboardEngine~~ ✅
7. ~~HotkeyManager (hold detection + flagsChanged + keystroke forwarding)~~ ✅
8. ~~Floating Toolbox HUD (NSPanel + hold-to-search + Scheme B lock + `⌃⌥[1-9]` paste)~~ ✅
8. ~~Floating Toolbox HUD (NSPanel + hold-to-search + Scheme B lock + `⌃⌥[1-9]` paste)~~ ✅

### Phase 3: IPC + VS Code Extension ✅
9. ~~IPCServer~~ ✅
Expand Down
2 changes: 1 addition & 1 deletion docs/en/01-product-spec/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ The minimum viable product focuses on the **VS Code + Menu Bar** experience:
- macOS Menu Bar App (Swift/SwiftUI), Keychain-based key storage
- Demo mode toggle with visual indicator
- Floating toolbox with hold-to-search and Scheme B lock
- Keyboard shortcuts: `⌃⌥Space`, `⌃⌥⌘D`, `⌃⌥[1-9]`, `⌃⌥⌘V`
- Keyboard shortcuts: `⌃⌥Space`, `⌃⌥⌘D`, `⌃⌥[1-9]`, `⌃⌥⌘V`
- VS Code Extension with Decoration API masking
- Two-tier key hierarchy and manual key entry
- Basic linked key groups (sequential paste)
Expand Down
2 changes: 1 addition & 1 deletion docs/en/02-technical-architecture/swift-core-modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ Manages global keyboard shortcuts using `CGEvent.tapCreate` for system-level hot
|--------|--------|-------------|
| `⌃⌥Space` | `toggleToolbox()` | Show/hide floating toolbox |
| `⌃⌥⌘D` | `toggleDemoMode()` | Toggle demo/normal mode |
| `⌃⌥[1-9]` | `pasteKeyByIndex()` | Paste key by shortcut index |
| `⌃⌥[1-9]` | `pasteKeyByIndex()` | Paste key by shortcut index |
| `⌃⌥⌘V` | `captureClipboard()` | Scan and save current clipboard contents |

### Hold-to-Detect Logic
Expand Down
2 changes: 1 addition & 1 deletion docs/en/04-ui-ux/floating-toolbox.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Inspired by the macOS `⌘Tab` App Switcher.
| Search/filter | Continue holding + type characters | Key list filters in real-time |
| Quick paste (single result) | Release when only 1 result remains | Key is pasted, toolbox disappears |
| Select from multiple results | Release → toolbox locks; use `↑↓` + `Enter` | Press Enter to paste Key, Esc to cancel |
| Paste directly by number | Press `⌃⌥[1-9]` | Immediately paste the Nth Key, no toolbox needed |
| Paste directly by number | Press `⌃⌥[1-9]` | Immediately paste the Nth Key, no toolbox needed |

## Lock Behavior (Plan B)

Expand Down
2 changes: 1 addition & 1 deletion docs/en/04-ui-ux/keyboard-shortcuts.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
|--------|-----------------|------------------|
| Toggle floating toolbox | `⌃⌥Space` | Left-hand key combination, very few conflicts with applications |
| Toggle demo mode | `⌃⌥⌘D` | Three modifier keys to prevent accidental triggering |
| Paste Nth Key directly | `⌃⌥[1-9]` | Fastest path when Key position is known |
| Paste Nth Key directly | `⌃⌥[1-9]` | Fastest path when Key position is known |
| Quick extract clipboard content | `⌃⌥⌘V` | Paste + auto-parse into manager |

## Custom Settings
Expand Down
26 changes: 21 additions & 5 deletions packages/swift-core/DemoSafe/App/AppState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ final class AppState: ObservableObject {
let maskingCoordinator: MaskingCoordinator
let hotkeyManager: HotkeyManager
let ipcServer: IPCServer
let sequentialPasteEngine: SequentialPasteEngine
let toolboxState: ToolboxState
let toolboxController: FloatingToolboxController

Expand All @@ -28,7 +29,8 @@ final class AppState: ObservableObject {
self.vaultManager = VaultManager(keychainService: keychainService)
self.maskingCoordinator = MaskingCoordinator(vaultManager: vaultManager)
self.clipboardEngine = ClipboardEngine(keychainService: keychainService, maskingCoordinator: maskingCoordinator)
self.ipcServer = IPCServer(maskingCoordinator: maskingCoordinator, clipboardEngine: clipboardEngine, vaultManager: vaultManager)
self.sequentialPasteEngine = SequentialPasteEngine(clipboardEngine: clipboardEngine, keychainService: keychainService)
self.ipcServer = IPCServer(maskingCoordinator: maskingCoordinator, clipboardEngine: clipboardEngine, vaultManager: vaultManager, keychainService: keychainService)
self.hotkeyManager = HotkeyManager(maskingCoordinator: maskingCoordinator)
self.toolboxState = ToolboxState(vaultManager: vaultManager)
self.toolboxController = FloatingToolboxController()
Expand All @@ -50,8 +52,6 @@ final class AppState: ObservableObject {
seedDefaultContextModes()
}

// Test key seeding removed — Active Key Capture handles key storage via Chrome Extension.

// Wire settings window controller
SettingsWindowController.shared.setAppState(self)

Expand Down Expand Up @@ -180,12 +180,28 @@ final class AppState: ObservableObject {
self?.toggleDemoMode()
}

// Paste key by index
// Paste key by index (group-aware)
hotkeyManager.onPasteKeyByIndex = { [weak self] index in
guard let self else { return }
let allKeys = self.vaultManager.getAllKeys()
guard index >= 1, index <= allKeys.count else { return }
self.copyKey(keyId: allKeys[index - 1].id)
let key = allKeys[index - 1]

// If key belongs to a sequential group, paste the entire group
if let groupId = key.linkedGroupId,
let group = self.vaultManager.getLinkedGroup(groupId: groupId),
group.pasteMode == .sequential {
let autoClear = self.activeContext?.clipboardClearSeconds
Task {
do {
try await self.sequentialPasteEngine.pasteGroupSequentially(group, autoClearSeconds: autoClear)
} catch {
logger.error("Sequential paste failed: \(error.localizedDescription)")
}
}
} else {
self.copyKey(keyId: key.id)
}
}
}

Expand Down
18 changes: 15 additions & 3 deletions packages/swift-core/DemoSafe/Models/LinkedGroup.swift
Original file line number Diff line number Diff line change
@@ -1,15 +1,27 @@
import Foundation

/// A single entry within a LinkedGroup, representing one key with its field label.
struct GroupEntry: Codable {
let keyId: UUID
var fieldLabel: String // e.g. "Access Key ID", "Secret Key"
var sortOrder: Int
}

/// An ordered group of keys for batch paste operations.
struct LinkedGroup: Codable, Identifiable {
let id: UUID
var label: String
var keyIds: [UUID]
var entries: [GroupEntry]
var pasteMode: PasteMode
var createdAt: Date

/// Key IDs in sort order, for convenience.
var sortedKeyIds: [UUID] {
entries.sorted { $0.sortOrder < $1.sortOrder }.map(\.keyId)
}
}

enum PasteMode: String, Codable {
case selectField // MVP: user selects which field to paste
case sequential // Future: paste keys in order
case selectField // User selects which field to paste
case sequential // Paste keys in order with Tab between fields
}
Loading
Loading