@lattices/cli 0.4.14 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/README.md +5 -7
  2. package/apps/mac/Info.plist +2 -2
  3. package/apps/mac/Lattices.app/Contents/Info.plist +4 -12
  4. package/apps/mac/Lattices.app/Contents/MacOS/Lattices +0 -0
  5. package/bin/lattices-app.ts +110 -17
  6. package/bin/lattices-build +125 -0
  7. package/bin/lattices-dev +89 -16
  8. package/bin/lattices.ts +977 -16
  9. package/docs/agents.md +81 -4
  10. package/docs/ai-chat-ux-review.md +416 -0
  11. package/docs/api.md +135 -3
  12. package/docs/app.md +30 -8
  13. package/docs/config.md +4 -0
  14. package/docs/mouse-gestures.md +60 -1
  15. package/docs/proposals/LAT-004-interactive-overlay-actors.md +1 -1
  16. package/docs/proposals/LAT-005-action-runtime-product-spine.md +914 -0
  17. package/docs/proposals/LAT-006-mira-in-lattices.md +553 -0
  18. package/docs/reference/dewey.config.ts +2 -2
  19. package/docs/release.md +171 -0
  20. package/docs/repo-structure.md +4 -5
  21. package/docs/voice.md +11 -27
  22. package/package.json +9 -10
  23. package/apps/mac/Package.swift +0 -27
  24. package/apps/mac/Sources/AppShell/App.swift +0 -26
  25. package/apps/mac/Sources/AppShell/AppActivationCoordinator.swift +0 -27
  26. package/apps/mac/Sources/AppShell/AppDelegate.swift +0 -189
  27. package/apps/mac/Sources/AppShell/AppServicesBootstrap.swift +0 -25
  28. package/apps/mac/Sources/AppShell/AppShellView.swift +0 -171
  29. package/apps/mac/Sources/AppShell/AppUpdater.swift +0 -305
  30. package/apps/mac/Sources/AppShell/CliActionLauncher.swift +0 -50
  31. package/apps/mac/Sources/AppShell/HomeDashboardView.swift +0 -133
  32. package/apps/mac/Sources/AppShell/HotkeyBootstrap.swift +0 -87
  33. package/apps/mac/Sources/AppShell/KeyRecorderView.swift +0 -210
  34. package/apps/mac/Sources/AppShell/LatticesRuntime.swift +0 -104
  35. package/apps/mac/Sources/AppShell/MainView.swift +0 -847
  36. package/apps/mac/Sources/AppShell/MainWindow.swift +0 -83
  37. package/apps/mac/Sources/AppShell/MenuBarController.swift +0 -177
  38. package/apps/mac/Sources/AppShell/OnboardingView.swift +0 -483
  39. package/apps/mac/Sources/AppShell/PermissionsAssistantView.swift +0 -366
  40. package/apps/mac/Sources/AppShell/PermissionsAssistantWindow.swift +0 -70
  41. package/apps/mac/Sources/AppShell/Preferences.swift +0 -297
  42. package/apps/mac/Sources/AppShell/SettingsView.swift +0 -3163
  43. package/apps/mac/Sources/AppShell/SettingsWindow.swift +0 -34
  44. package/apps/mac/Sources/AppShell/WorkspaceInspectorPresenter.swift +0 -13
  45. package/apps/mac/Sources/Core/Actions/HotkeyManager.swift +0 -256
  46. package/apps/mac/Sources/Core/Actions/HotkeyStore.swift +0 -399
  47. package/apps/mac/Sources/Core/Actions/IntentEngine.swift +0 -988
  48. package/apps/mac/Sources/Core/Actions/IntentSchema.swift +0 -94
  49. package/apps/mac/Sources/Core/Actions/Intents/CreateLayerIntent.swift +0 -54
  50. package/apps/mac/Sources/Core/Actions/Intents/DistributeIntent.swift +0 -56
  51. package/apps/mac/Sources/Core/Actions/Intents/FocusIntent.swift +0 -69
  52. package/apps/mac/Sources/Core/Actions/Intents/HelpIntent.swift +0 -41
  53. package/apps/mac/Sources/Core/Actions/Intents/KillIntent.swift +0 -47
  54. package/apps/mac/Sources/Core/Actions/Intents/LatticeIntent.swift +0 -53
  55. package/apps/mac/Sources/Core/Actions/Intents/LaunchIntent.swift +0 -67
  56. package/apps/mac/Sources/Core/Actions/Intents/ListSessionsIntent.swift +0 -32
  57. package/apps/mac/Sources/Core/Actions/Intents/ListWindowsIntent.swift +0 -30
  58. package/apps/mac/Sources/Core/Actions/Intents/ScanIntent.swift +0 -52
  59. package/apps/mac/Sources/Core/Actions/Intents/SearchIntent.swift +0 -190
  60. package/apps/mac/Sources/Core/Actions/Intents/SwitchLayerIntent.swift +0 -50
  61. package/apps/mac/Sources/Core/Actions/Intents/TileIntent.swift +0 -61
  62. package/apps/mac/Sources/Core/Actions/PaletteCommand.swift +0 -439
  63. package/apps/mac/Sources/Core/Actions/VoiceIntentResolver.swift +0 -713
  64. package/apps/mac/Sources/Core/Companion/CompanionActivityLog.swift +0 -70
  65. package/apps/mac/Sources/Core/Companion/CompanionKeyboardController.swift +0 -141
  66. package/apps/mac/Sources/Core/Companion/LatticesCompanionBridgeServer.swift +0 -454
  67. package/apps/mac/Sources/Core/Companion/LatticesCompanionCockpit.swift +0 -555
  68. package/apps/mac/Sources/Core/Companion/LatticesCompanionSecurityCoordinator.swift +0 -629
  69. package/apps/mac/Sources/Core/Companion/LatticesCompanionTrackpadController.swift +0 -204
  70. package/apps/mac/Sources/Core/Companion/LatticesDeckHost.swift +0 -1463
  71. package/apps/mac/Sources/Core/Daemon/DaemonProtocol.swift +0 -114
  72. package/apps/mac/Sources/Core/Daemon/DaemonServer.swift +0 -427
  73. package/apps/mac/Sources/Core/Daemon/LatticesApi.swift +0 -2965
  74. package/apps/mac/Sources/Core/Desktop/AccessibilityTextExtractor.swift +0 -111
  75. package/apps/mac/Sources/Core/Desktop/AppTypeClassifier.swift +0 -106
  76. package/apps/mac/Sources/Core/Desktop/DesktopModel.swift +0 -331
  77. package/apps/mac/Sources/Core/Desktop/DesktopModelTypes.swift +0 -73
  78. package/apps/mac/Sources/Core/Desktop/InventoryManager.swift +0 -35
  79. package/apps/mac/Sources/Core/Desktop/InventoryPath.swift +0 -43
  80. package/apps/mac/Sources/Core/Desktop/MouseFinder.swift +0 -527
  81. package/apps/mac/Sources/Core/Desktop/OcrModel.swift +0 -467
  82. package/apps/mac/Sources/Core/Desktop/OcrStore.swift +0 -329
  83. package/apps/mac/Sources/Core/Desktop/PlacementSpec.swift +0 -195
  84. package/apps/mac/Sources/Core/Desktop/SessionWindowLocator.swift +0 -139
  85. package/apps/mac/Sources/Core/Desktop/TilePickerView.swift +0 -209
  86. package/apps/mac/Sources/Core/Desktop/WindowCapture.swift +0 -33
  87. package/apps/mac/Sources/Core/Desktop/WindowDragSnapController.swift +0 -429
  88. package/apps/mac/Sources/Core/Desktop/WindowPreviewCard.swift +0 -100
  89. package/apps/mac/Sources/Core/Desktop/WindowPreviewStore.swift +0 -112
  90. package/apps/mac/Sources/Core/Desktop/WindowSelectionStore.swift +0 -76
  91. package/apps/mac/Sources/Core/Desktop/WindowTiler.swift +0 -2222
  92. package/apps/mac/Sources/Core/Input/EventTapBreaker.swift +0 -124
  93. package/apps/mac/Sources/Core/Input/EventTapThread.swift +0 -54
  94. package/apps/mac/Sources/Core/Input/InputCaptureResetCenter.swift +0 -20
  95. package/apps/mac/Sources/Core/Input/KeyboardRemapConfig.swift +0 -69
  96. package/apps/mac/Sources/Core/Input/KeyboardRemapController.swift +0 -346
  97. package/apps/mac/Sources/Core/Input/KeyboardRemapStore.swift +0 -141
  98. package/apps/mac/Sources/Core/Input/MouseGestureConfig.swift +0 -499
  99. package/apps/mac/Sources/Core/Input/MouseGestureController.swift +0 -2583
  100. package/apps/mac/Sources/Core/Input/MouseInputDeviceStore.swift +0 -98
  101. package/apps/mac/Sources/Core/Input/MouseInputEventViewer.swift +0 -272
  102. package/apps/mac/Sources/Core/Input/MouseShortcutStore.swift +0 -170
  103. package/apps/mac/Sources/Core/Input/SecureEventInputMonitor.swift +0 -39
  104. package/apps/mac/Sources/Core/Input/ShapeRecognizer.swift +0 -624
  105. package/apps/mac/Sources/Core/Input/TapBudgetMeter.swift +0 -56
  106. package/apps/mac/Sources/Core/Overlays/AppWindowShell.swift +0 -63
  107. package/apps/mac/Sources/Core/Overlays/CommandMode/CommandModeState.swift +0 -1566
  108. package/apps/mac/Sources/Core/Overlays/CommandMode/CommandModeView.swift +0 -1927
  109. package/apps/mac/Sources/Core/Overlays/CommandMode/CommandModeWindow.swift +0 -196
  110. package/apps/mac/Sources/Core/Overlays/CommandPalette/CommandPaletteView.swift +0 -307
  111. package/apps/mac/Sources/Core/Overlays/CommandPalette/CommandPaletteWindow.swift +0 -67
  112. package/apps/mac/Sources/Core/Overlays/HUD/CheatSheetHUD.swift +0 -576
  113. package/apps/mac/Sources/Core/Overlays/HUD/HUDBottomBar.swift +0 -279
  114. package/apps/mac/Sources/Core/Overlays/HUD/HUDController.swift +0 -1158
  115. package/apps/mac/Sources/Core/Overlays/HUD/HUDLeftBar.swift +0 -849
  116. package/apps/mac/Sources/Core/Overlays/HUD/HUDMinimap.swift +0 -179
  117. package/apps/mac/Sources/Core/Overlays/HUD/HUDRightBar.swift +0 -596
  118. package/apps/mac/Sources/Core/Overlays/HUD/HUDState.swift +0 -367
  119. package/apps/mac/Sources/Core/Overlays/HUD/HUDTopBar.swift +0 -243
  120. package/apps/mac/Sources/Core/Overlays/HUD/LauncherHUD.swift +0 -334
  121. package/apps/mac/Sources/Core/Overlays/HUD/LayerBezel.swift +0 -203
  122. package/apps/mac/Sources/Core/Overlays/OmniSearch/OmniSearchState.swift +0 -280
  123. package/apps/mac/Sources/Core/Overlays/OmniSearch/OmniSearchView.swift +0 -422
  124. package/apps/mac/Sources/Core/Overlays/OmniSearch/OmniSearchWindow.swift +0 -94
  125. package/apps/mac/Sources/Core/Overlays/OverlayPanelShell.swift +0 -241
  126. package/apps/mac/Sources/Core/Overlays/ScreenMap/ScreenMapState.swift +0 -3135
  127. package/apps/mac/Sources/Core/Overlays/ScreenMap/ScreenMapView.swift +0 -3977
  128. package/apps/mac/Sources/Core/Overlays/ScreenMap/ScreenMapWindowController.swift +0 -119
  129. package/apps/mac/Sources/Core/Overlays/ScreenOverlayCanvasController.swift +0 -1217
  130. package/apps/mac/Sources/Core/Overlays/Voice/VoiceCommandWindow.swift +0 -1575
  131. package/apps/mac/Sources/Core/Pi/PiAuthNextStepCard.swift +0 -148
  132. package/apps/mac/Sources/Core/Pi/PiAuthPromptCard.swift +0 -90
  133. package/apps/mac/Sources/Core/Pi/PiChatDock.swift +0 -564
  134. package/apps/mac/Sources/Core/Pi/PiChatSession.swift +0 -1948
  135. package/apps/mac/Sources/Core/Pi/PiInstallCallout.swift +0 -86
  136. package/apps/mac/Sources/Core/Pi/PiProviderSetupCallout.swift +0 -99
  137. package/apps/mac/Sources/Core/Pi/PiWorkspaceView.swift +0 -510
  138. package/apps/mac/Sources/Core/System/Capability.swift +0 -79
  139. package/apps/mac/Sources/Core/System/DiagnosticLog.swift +0 -373
  140. package/apps/mac/Sources/Core/System/EventBus.swift +0 -31
  141. package/apps/mac/Sources/Core/System/PermissionChecker.swift +0 -224
  142. package/apps/mac/Sources/Core/System/ProcessModel.swift +0 -199
  143. package/apps/mac/Sources/Core/System/ProcessQuery.swift +0 -151
  144. package/apps/mac/Sources/Core/System/SystemTelemetryMonitor.swift +0 -273
  145. package/apps/mac/Sources/Core/Voice/AdvisorLearningStore.swift +0 -90
  146. package/apps/mac/Sources/Core/Voice/AgentSession.swift +0 -377
  147. package/apps/mac/Sources/Core/Voice/AudioProvider.swift +0 -555
  148. package/apps/mac/Sources/Core/Voice/HandsOffSession.swift +0 -839
  149. package/apps/mac/Sources/Core/Voice/VoiceChatView.swift +0 -192
  150. package/apps/mac/Sources/Core/Voice/VoxClient.swift +0 -454
  151. package/apps/mac/Sources/Core/Workspace/Project.swift +0 -28
  152. package/apps/mac/Sources/Core/Workspace/ProjectScanner.swift +0 -141
  153. package/apps/mac/Sources/Core/Workspace/SessionLayerStore.swift +0 -285
  154. package/apps/mac/Sources/Core/Workspace/SessionManager.swift +0 -75
  155. package/apps/mac/Sources/Core/Workspace/Terminal/Terminal.swift +0 -259
  156. package/apps/mac/Sources/Core/Workspace/Terminal/TerminalQuery.swift +0 -156
  157. package/apps/mac/Sources/Core/Workspace/Terminal/TerminalSynthesizer.swift +0 -200
  158. package/apps/mac/Sources/Core/Workspace/Tmux/TmuxModel.swift +0 -60
  159. package/apps/mac/Sources/Core/Workspace/Tmux/TmuxQuery.swift +0 -105
  160. package/apps/mac/Sources/Core/Workspace/WorkspaceManager.swift +0 -1027
  161. package/apps/mac/Sources/UI/ActionRow.swift +0 -78
  162. package/apps/mac/Sources/UI/OrphanRow.swift +0 -129
  163. package/apps/mac/Sources/UI/ProjectRow.swift +0 -368
  164. package/apps/mac/Sources/UI/TabGroupRow.swift +0 -178
  165. package/apps/mac/Sources/UI/Theme.swift +0 -164
  166. package/apps/mac/Tests/StageDragTests.swift +0 -333
  167. package/apps/mac/Tests/StageJoinTests.swift +0 -313
  168. package/apps/mac/Tests/StageManagerTests.swift +0 -280
  169. package/apps/mac/Tests/StageTileTests.swift +0 -353
  170. package/swift/Package.swift +0 -20
  171. package/swift/Sources/DeckKit/DeckAction.swift +0 -51
  172. package/swift/Sources/DeckKit/DeckBridgeSecurity.swift +0 -152
  173. package/swift/Sources/DeckKit/DeckCockpit.swift +0 -82
  174. package/swift/Sources/DeckKit/DeckHost.swift +0 -7
  175. package/swift/Sources/DeckKit/DeckManifest.swift +0 -145
  176. package/swift/Sources/DeckKit/DeckRuntimeSnapshot.swift +0 -533
  177. package/swift/Sources/DeckKit/DeckTrackpad.swift +0 -63
  178. package/swift/Sources/DeckKit/DeckValue.swift +0 -93
  179. package/swift/Sources/DeckKit/DeckVoiceError.swift +0 -88
  180. package/swift/Tests/DeckKitTests/DeckKitTests.swift +0 -286
@@ -1,79 +0,0 @@
1
- import AppKit
2
- import Foundation
3
-
4
- /// An OS permission Lattices can ask for. The Permissions Assistant introduces
5
- /// these and only requests the underlying grant when the user explicitly opts
6
- /// in. Product configuration like Pi/provider auth is intentionally excluded —
7
- /// that lives in the Chat/Pi surface, not here.
8
- enum Capability: String, CaseIterable, Identifiable {
9
- case windowControl // macOS Accessibility — tiling, focus, snap
10
- case screenSearch // macOS Screen Recording — OCR / on-screen text search
11
-
12
- var id: String { rawValue }
13
-
14
- var title: String {
15
- switch self {
16
- case .windowControl: return "Tiling & focus"
17
- case .screenSearch: return "Screen text search"
18
- }
19
- }
20
-
21
- var iconName: String {
22
- switch self {
23
- case .windowControl: return "rectangle.3.group"
24
- case .screenSearch: return "text.viewfinder"
25
- }
26
- }
27
-
28
- var requirementLabel: String {
29
- switch self {
30
- case .windowControl: return "Accessibility"
31
- case .screenSearch: return "Screen & System Audio Recording"
32
- }
33
- }
34
-
35
- var pitch: String {
36
- switch self {
37
- case .windowControl:
38
- return "Move, resize, snap, and arrange windows from the menu bar, command palette, and gestures."
39
- case .screenSearch:
40
- return "Index on-screen text with OCR so the omni search can jump to any window by what it shows."
41
- }
42
- }
43
-
44
- var why: String {
45
- switch self {
46
- case .windowControl:
47
- return "macOS Accessibility lets Lattices read window titles and move or resize windows. No keystrokes are recorded."
48
- case .screenSearch:
49
- return "Screen Recording lets Lattices read pixels to OCR what is on-screen. Captures stay on this Mac."
50
- }
51
- }
52
-
53
- /// Optional one-liner shown when the capability is on, summarising current behavior.
54
- var whenGrantedDetail: String {
55
- switch self {
56
- case .windowControl: return "Lattices can move and tile windows."
57
- case .screenSearch: return "OCR can index visible windows for omni search."
58
- }
59
- }
60
-
61
- /// Live status — read directly from the system, never cached.
62
- var isGranted: Bool {
63
- switch self {
64
- case .windowControl: return PermissionChecker.shared.accessibility
65
- case .screenSearch: return PermissionChecker.shared.screenRecording
66
- }
67
- }
68
-
69
- /// All capabilities that are not yet granted.
70
- static var missing: [Capability] {
71
- Capability.allCases.filter { !$0.isGranted }
72
- }
73
-
74
- /// Capabilities that are missing AND have not been dismissed-for-now.
75
- static var visiblyMissing: [Capability] {
76
- let dismissed = Preferences.shared.dismissedCapabilities
77
- return missing.filter { !dismissed.contains($0.rawValue) }
78
- }
79
- }
@@ -1,373 +0,0 @@
1
- import AppKit
2
- import SwiftUI
3
-
4
- // MARK: - Log Store
5
-
6
- final class DiagnosticLog: ObservableObject {
7
- static let shared = DiagnosticLog()
8
-
9
- struct Entry: Identifiable {
10
- let id = UUID()
11
- let time: Date
12
- let message: String
13
- let level: Level
14
-
15
- enum Level: String { case info, success, warning, error }
16
-
17
- var icon: String {
18
- switch level {
19
- case .info: return "›"
20
- case .success: return "✓"
21
- case .warning: return "⚠"
22
- case .error: return "✗"
23
- }
24
- }
25
- }
26
-
27
- @Published var entries: [Entry] = []
28
- private let maxEntries = 80
29
-
30
- // Disk persistence
31
- private let logFile: URL
32
- private let fileHandle: FileHandle?
33
- private let diskQueue = DispatchQueue(label: "com.lattices.log-writer")
34
- private static let timeFmt: DateFormatter = {
35
- let f = DateFormatter()
36
- f.dateFormat = "HH:mm:ss.SSS"
37
- return f
38
- }()
39
-
40
- private init() {
41
- let dir = FileManager.default.homeDirectoryForCurrentUser
42
- .appendingPathComponent(".lattices")
43
- try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
44
-
45
- logFile = dir.appendingPathComponent("lattices.log")
46
-
47
- // Rotate if > 1MB
48
- if let attrs = try? FileManager.default.attributesOfItem(atPath: logFile.path),
49
- let size = attrs[.size] as? UInt64, size > 1_000_000 {
50
- let prev = dir.appendingPathComponent("lattices.log.1")
51
- try? FileManager.default.removeItem(at: prev)
52
- try? FileManager.default.moveItem(at: logFile, to: prev)
53
- }
54
-
55
- // Create file if needed and open for appending
56
- if !FileManager.default.fileExists(atPath: logFile.path) {
57
- FileManager.default.createFile(atPath: logFile.path, contents: nil)
58
- }
59
- fileHandle = try? FileHandle(forWritingTo: logFile)
60
- fileHandle?.seekToEndOfFile()
61
-
62
- // Write session header
63
- let header = "\n──── Lattices launched \(ISO8601DateFormatter().string(from: Date())) ────\n"
64
- if let data = header.data(using: .utf8) {
65
- fileHandle?.write(data)
66
- }
67
- }
68
-
69
- deinit {
70
- fileHandle?.closeFile()
71
- }
72
-
73
- func log(_ message: String, level: Entry.Level = .info) {
74
- let entry = Entry(time: Date(), message: message, level: level)
75
-
76
- // In-memory for UI
77
- DispatchQueue.main.async {
78
- self.entries.append(entry)
79
- if self.entries.count > self.maxEntries {
80
- self.entries.removeFirst(self.entries.count - self.maxEntries)
81
- }
82
- }
83
-
84
- // Disk
85
- diskQueue.async { [weak self] in
86
- let ts = Self.timeFmt.string(from: entry.time)
87
- let line = "\(ts) \(entry.icon) [\(level.rawValue)] \(message)\n"
88
- if let data = line.data(using: .utf8) {
89
- self?.fileHandle?.write(data)
90
- }
91
- }
92
- }
93
-
94
- func info(_ msg: String) { log(msg, level: .info) }
95
- func success(_ msg: String) { log(msg, level: .success) }
96
- func warn(_ msg: String) { log(msg, level: .warning) }
97
- func error(_ msg: String) { log(msg, level: .error) }
98
- func clear() { DispatchQueue.main.async { self.entries.removeAll() } }
99
-
100
- // MARK: - Per-Action Timing
101
-
102
- struct TimedAction {
103
- let label: String
104
- let start: Date
105
- }
106
-
107
- func startTimed(_ label: String) -> TimedAction {
108
- info("▸ \(label)")
109
- return TimedAction(label: label, start: Date())
110
- }
111
-
112
- func finish(_ action: TimedAction) {
113
- let ms = Date().timeIntervalSince(action.start) * 1000
114
- success("▸ \(action.label) — \(String(format: "%.0f", ms))ms")
115
- }
116
- }
117
-
118
- // MARK: - Interaction Feedback
119
-
120
- final class AppFeedback {
121
- static let shared = AppFeedback()
122
-
123
- private lazy var tapSound: NSSound? = {
124
- guard let url = Bundle.main.url(forResource: "tap", withExtension: "wav") else { return nil }
125
- return NSSound(contentsOf: url, byReference: true)
126
- }()
127
-
128
- private init() {}
129
-
130
- @discardableResult
131
- func beginTimed(_ label: String, state: HUDState? = nil, feedback: String? = nil, playSound: Bool = true) -> DiagnosticLog.TimedAction {
132
- if playSound {
133
- playTap()
134
- }
135
- if let feedback, let state {
136
- state.showFeedback(feedback)
137
- }
138
- return DiagnosticLog.shared.startTimed(label)
139
- }
140
-
141
- func finish(_ action: DiagnosticLog.TimedAction, state: HUDState? = nil, feedback: String? = nil) {
142
- if let feedback, let state {
143
- state.showFeedback(feedback)
144
- }
145
- DiagnosticLog.shared.finish(action)
146
- }
147
-
148
- func acknowledge(_ label: String, state: HUDState? = nil, feedback: String? = nil, playSound: Bool = true) {
149
- if playSound {
150
- playTap()
151
- }
152
- if let feedback, let state {
153
- state.showFeedback(feedback)
154
- }
155
- DiagnosticLog.shared.info(label)
156
- }
157
-
158
- private func playTap() {
159
- DispatchQueue.main.async {
160
- self.tapSound?.stop()
161
- self.tapSound?.play()
162
- }
163
- }
164
- }
165
-
166
- // MARK: - Diagnostic Window
167
-
168
- final class DiagnosticWindow {
169
- static let shared = DiagnosticWindow()
170
-
171
- private var window: NSWindow?
172
- private var keyMonitor: Any?
173
- private let log = DiagnosticLog.shared
174
-
175
- var isVisible: Bool { window?.isVisible ?? false }
176
-
177
- func toggle() {
178
- if let w = window, w.isVisible {
179
- dismiss()
180
- } else {
181
- show()
182
- }
183
- }
184
-
185
- func dismiss() {
186
- window?.orderOut(nil)
187
- if let monitor = keyMonitor {
188
- NSEvent.removeMonitor(monitor)
189
- keyMonitor = nil
190
- }
191
- }
192
-
193
- func show() {
194
- if let w = window {
195
- w.orderFrontRegardless()
196
- return
197
- }
198
-
199
- let view = DiagnosticOverlayView()
200
-
201
- let hosting = NSHostingController(rootView: view)
202
- let screen = NSScreen.main
203
- let screenFrame = screen?.visibleFrame ?? NSRect(x: 0, y: 0, width: 1920, height: 1080)
204
- let panelWidth: CGFloat = 480
205
- let panelHeight: CGFloat = max(600, floor(screenFrame.height * 0.55))
206
- hosting.preferredContentSize = NSSize(width: panelWidth, height: panelHeight)
207
-
208
- let w = NSPanel(
209
- contentRect: NSRect(x: 0, y: 0, width: panelWidth, height: panelHeight),
210
- styleMask: [.titled, .closable, .resizable, .utilityWindow, .nonactivatingPanel],
211
- backing: .buffered,
212
- defer: false
213
- )
214
- w.contentViewController = hosting
215
- w.title = "Lattices Diagnostics"
216
- w.titlebarAppearsTransparent = true
217
- w.isMovableByWindowBackground = true
218
- w.level = .floating
219
- w.isOpaque = false
220
- w.backgroundColor = NSColor(red: 0.1, green: 0.1, blue: 0.12, alpha: 1.0)
221
- w.hasShadow = true
222
- w.alphaValue = 1.0
223
- w.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary]
224
-
225
- // Position: right edge, vertically centered
226
- let x = screenFrame.maxX - panelWidth - 12
227
- let y = screenFrame.minY + floor((screenFrame.height - panelHeight) / 2)
228
- w.setFrameOrigin(NSPoint(x: x, y: y))
229
-
230
- w.orderFrontRegardless()
231
- window = w
232
-
233
- // Escape key → dismiss
234
- keyMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { [weak self] event in
235
- guard event.keyCode == 53,
236
- let win = self?.window,
237
- event.window === win || win.isKeyWindow else { return event }
238
- self?.dismiss()
239
- return nil
240
- }
241
-
242
- // Startup log
243
- let diag = DiagnosticLog.shared
244
- diag.info("Diagnostics opened")
245
- diag.info("Terminal: \(Preferences.shared.terminal.rawValue) (\(Preferences.shared.terminal.bundleId))")
246
- diag.info("Installed: \(Terminal.installed.map(\.rawValue).joined(separator: ", "))")
247
-
248
- // Show running sessions
249
- let task = Process()
250
- task.executableURL = URL(fileURLWithPath: TmuxQuery.resolvedPath ?? "/opt/homebrew/bin/tmux")
251
- task.arguments = ["list-sessions", "-F", "#{session_name}"]
252
- let pipe = Pipe()
253
- task.standardOutput = pipe
254
- task.standardError = FileHandle.nullDevice
255
- try? task.run()
256
- task.waitUntilExit()
257
- let sessions = String(data: pipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "none"
258
- diag.info("tmux sessions: \(sessions)")
259
- }
260
- }
261
-
262
- // MARK: - SwiftUI Overlay
263
-
264
- struct DiagnosticOverlayView: View {
265
- @StateObject private var log = DiagnosticLog.shared
266
- @State private var autoScroll = true
267
- @State private var refreshTick = 0
268
-
269
- private static let timeFmt: DateFormatter = {
270
- let f = DateFormatter()
271
- f.dateFormat = "HH:mm:ss.SSS"
272
- return f
273
- }()
274
-
275
- // Fallback timer to catch any missed updates
276
- private let refreshTimer = Timer.publish(every: 0.5, on: .main, in: .common).autoconnect()
277
-
278
- var body: some View {
279
- VStack(spacing: 0) {
280
- // Header
281
- HStack {
282
- Text("DIAGNOSTICS")
283
- .font(.system(size: 10, weight: .bold, design: .monospaced))
284
- .foregroundColor(.green.opacity(0.8))
285
- Spacer()
286
- let _ = refreshTick // force re-render on timer
287
- Text("\(log.entries.count) events")
288
- .font(.system(size: 9, design: .monospaced))
289
- .foregroundColor(.white.opacity(0.4))
290
- Button("Copy") {
291
- let text = log.entries.map { entry in
292
- let t = Self.timeFmt.string(from: entry.time)
293
- return "\(t) \(entry.icon) \(entry.message)"
294
- }.joined(separator: "\n")
295
- NSPasteboard.general.clearContents()
296
- NSPasteboard.general.setString(text, forType: .string)
297
- }
298
- .font(.system(size: 9, design: .monospaced))
299
- .foregroundColor(.white.opacity(0.5))
300
- .buttonStyle(.plain)
301
- Button("Clear") { log.clear() }
302
- .font(.system(size: 9, design: .monospaced))
303
- .foregroundColor(.white.opacity(0.5))
304
- .buttonStyle(.plain)
305
- }
306
- .padding(.horizontal, 10)
307
- .padding(.vertical, 6)
308
- .background(Color.black.opacity(0.3))
309
- .onReceive(refreshTimer) { _ in refreshTick += 1 }
310
-
311
- // Log entries
312
- ScrollViewReader { proxy in
313
- ScrollView {
314
- LazyVStack(alignment: .leading, spacing: 1) {
315
- ForEach(log.entries) { entry in
316
- logRow(entry)
317
- .id(entry.id)
318
- }
319
- }
320
- .padding(.horizontal, 8)
321
- .padding(.vertical, 4)
322
- }
323
- .onChange(of: log.entries.count) { _ in
324
- if autoScroll, let last = log.entries.last {
325
- withAnimation(.easeOut(duration: 0.1)) {
326
- proxy.scrollTo(last.id, anchor: .bottom)
327
- }
328
- }
329
- }
330
- }
331
- }
332
- .frame(minWidth: 420, idealWidth: 480, minHeight: 400, idealHeight: 600)
333
- .background(Color.black.opacity(0.75))
334
- }
335
-
336
- private func logRow(_ entry: DiagnosticLog.Entry) -> some View {
337
- HStack(alignment: .top, spacing: 6) {
338
- Text(Self.timeFmt.string(from: entry.time))
339
- .font(.system(size: 9, design: .monospaced))
340
- .foregroundColor(.white.opacity(0.3))
341
-
342
- Text(entry.icon)
343
- .font(.system(size: 9, design: .monospaced))
344
- .foregroundColor(iconColor(entry.level))
345
- .frame(width: 10)
346
-
347
- Text(entry.message)
348
- .font(.system(size: 10, design: .monospaced))
349
- .foregroundColor(textColor(entry.level))
350
- .lineLimit(3)
351
- .fixedSize(horizontal: false, vertical: true)
352
- }
353
- .padding(.vertical, 1)
354
- }
355
-
356
- private func iconColor(_ level: DiagnosticLog.Entry.Level) -> Color {
357
- switch level {
358
- case .info: return .white.opacity(0.5)
359
- case .success: return .green
360
- case .warning: return .yellow
361
- case .error: return .red
362
- }
363
- }
364
-
365
- private func textColor(_ level: DiagnosticLog.Entry.Level) -> Color {
366
- switch level {
367
- case .info: return .white.opacity(0.7)
368
- case .success: return .green.opacity(0.9)
369
- case .warning: return .yellow.opacity(0.9)
370
- case .error: return .red.opacity(0.9)
371
- }
372
- }
373
- }
@@ -1,31 +0,0 @@
1
- import Foundation
2
-
3
- enum ModelEvent {
4
- case windowsChanged(windows: [WindowEntry], added: [UInt32], removed: [UInt32])
5
- case tmuxChanged(sessions: [TmuxSession])
6
- case layerSwitched(index: Int)
7
- case processesChanged(interesting: [Int])
8
- case ocrScanComplete(windowCount: Int, totalBlocks: Int)
9
- case voiceCommand(text: String, confidence: Double)
10
- }
11
-
12
- final class EventBus {
13
- static let shared = EventBus()
14
- private var handlers: [(ModelEvent) -> Void] = []
15
- private let lock = NSLock()
16
-
17
- func subscribe(_ handler: @escaping (ModelEvent) -> Void) {
18
- lock.lock()
19
- handlers.append(handler)
20
- lock.unlock()
21
- }
22
-
23
- func post(_ event: ModelEvent) {
24
- lock.lock()
25
- let copy = handlers
26
- lock.unlock()
27
- for handler in copy {
28
- handler(event)
29
- }
30
- }
31
- }