@lattices/cli 0.4.14 → 0.6.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 (181) hide show
  1. package/README.md +5 -7
  2. package/apps/mac/Info.plist +4 -4
  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/proposals/LAT-007-unified-app-shell.md +128 -0
  19. package/docs/reference/dewey.config.ts +2 -2
  20. package/docs/release.md +171 -0
  21. package/docs/repo-structure.md +5 -5
  22. package/docs/voice.md +11 -27
  23. package/package.json +11 -10
  24. package/apps/mac/Package.swift +0 -27
  25. package/apps/mac/Sources/AppShell/App.swift +0 -26
  26. package/apps/mac/Sources/AppShell/AppActivationCoordinator.swift +0 -27
  27. package/apps/mac/Sources/AppShell/AppDelegate.swift +0 -189
  28. package/apps/mac/Sources/AppShell/AppServicesBootstrap.swift +0 -25
  29. package/apps/mac/Sources/AppShell/AppShellView.swift +0 -171
  30. package/apps/mac/Sources/AppShell/AppUpdater.swift +0 -305
  31. package/apps/mac/Sources/AppShell/CliActionLauncher.swift +0 -50
  32. package/apps/mac/Sources/AppShell/HomeDashboardView.swift +0 -133
  33. package/apps/mac/Sources/AppShell/HotkeyBootstrap.swift +0 -87
  34. package/apps/mac/Sources/AppShell/KeyRecorderView.swift +0 -210
  35. package/apps/mac/Sources/AppShell/LatticesRuntime.swift +0 -104
  36. package/apps/mac/Sources/AppShell/MainView.swift +0 -847
  37. package/apps/mac/Sources/AppShell/MainWindow.swift +0 -83
  38. package/apps/mac/Sources/AppShell/MenuBarController.swift +0 -177
  39. package/apps/mac/Sources/AppShell/OnboardingView.swift +0 -483
  40. package/apps/mac/Sources/AppShell/PermissionsAssistantView.swift +0 -366
  41. package/apps/mac/Sources/AppShell/PermissionsAssistantWindow.swift +0 -70
  42. package/apps/mac/Sources/AppShell/Preferences.swift +0 -297
  43. package/apps/mac/Sources/AppShell/SettingsView.swift +0 -3163
  44. package/apps/mac/Sources/AppShell/SettingsWindow.swift +0 -34
  45. package/apps/mac/Sources/AppShell/WorkspaceInspectorPresenter.swift +0 -13
  46. package/apps/mac/Sources/Core/Actions/HotkeyManager.swift +0 -256
  47. package/apps/mac/Sources/Core/Actions/HotkeyStore.swift +0 -399
  48. package/apps/mac/Sources/Core/Actions/IntentEngine.swift +0 -988
  49. package/apps/mac/Sources/Core/Actions/IntentSchema.swift +0 -94
  50. package/apps/mac/Sources/Core/Actions/Intents/CreateLayerIntent.swift +0 -54
  51. package/apps/mac/Sources/Core/Actions/Intents/DistributeIntent.swift +0 -56
  52. package/apps/mac/Sources/Core/Actions/Intents/FocusIntent.swift +0 -69
  53. package/apps/mac/Sources/Core/Actions/Intents/HelpIntent.swift +0 -41
  54. package/apps/mac/Sources/Core/Actions/Intents/KillIntent.swift +0 -47
  55. package/apps/mac/Sources/Core/Actions/Intents/LatticeIntent.swift +0 -53
  56. package/apps/mac/Sources/Core/Actions/Intents/LaunchIntent.swift +0 -67
  57. package/apps/mac/Sources/Core/Actions/Intents/ListSessionsIntent.swift +0 -32
  58. package/apps/mac/Sources/Core/Actions/Intents/ListWindowsIntent.swift +0 -30
  59. package/apps/mac/Sources/Core/Actions/Intents/ScanIntent.swift +0 -52
  60. package/apps/mac/Sources/Core/Actions/Intents/SearchIntent.swift +0 -190
  61. package/apps/mac/Sources/Core/Actions/Intents/SwitchLayerIntent.swift +0 -50
  62. package/apps/mac/Sources/Core/Actions/Intents/TileIntent.swift +0 -61
  63. package/apps/mac/Sources/Core/Actions/PaletteCommand.swift +0 -439
  64. package/apps/mac/Sources/Core/Actions/VoiceIntentResolver.swift +0 -713
  65. package/apps/mac/Sources/Core/Companion/CompanionActivityLog.swift +0 -70
  66. package/apps/mac/Sources/Core/Companion/CompanionKeyboardController.swift +0 -141
  67. package/apps/mac/Sources/Core/Companion/LatticesCompanionBridgeServer.swift +0 -454
  68. package/apps/mac/Sources/Core/Companion/LatticesCompanionCockpit.swift +0 -555
  69. package/apps/mac/Sources/Core/Companion/LatticesCompanionSecurityCoordinator.swift +0 -629
  70. package/apps/mac/Sources/Core/Companion/LatticesCompanionTrackpadController.swift +0 -204
  71. package/apps/mac/Sources/Core/Companion/LatticesDeckHost.swift +0 -1463
  72. package/apps/mac/Sources/Core/Daemon/DaemonProtocol.swift +0 -114
  73. package/apps/mac/Sources/Core/Daemon/DaemonServer.swift +0 -427
  74. package/apps/mac/Sources/Core/Daemon/LatticesApi.swift +0 -2965
  75. package/apps/mac/Sources/Core/Desktop/AccessibilityTextExtractor.swift +0 -111
  76. package/apps/mac/Sources/Core/Desktop/AppTypeClassifier.swift +0 -106
  77. package/apps/mac/Sources/Core/Desktop/DesktopModel.swift +0 -331
  78. package/apps/mac/Sources/Core/Desktop/DesktopModelTypes.swift +0 -73
  79. package/apps/mac/Sources/Core/Desktop/InventoryManager.swift +0 -35
  80. package/apps/mac/Sources/Core/Desktop/InventoryPath.swift +0 -43
  81. package/apps/mac/Sources/Core/Desktop/MouseFinder.swift +0 -527
  82. package/apps/mac/Sources/Core/Desktop/OcrModel.swift +0 -467
  83. package/apps/mac/Sources/Core/Desktop/OcrStore.swift +0 -329
  84. package/apps/mac/Sources/Core/Desktop/PlacementSpec.swift +0 -195
  85. package/apps/mac/Sources/Core/Desktop/SessionWindowLocator.swift +0 -139
  86. package/apps/mac/Sources/Core/Desktop/TilePickerView.swift +0 -209
  87. package/apps/mac/Sources/Core/Desktop/WindowCapture.swift +0 -33
  88. package/apps/mac/Sources/Core/Desktop/WindowDragSnapController.swift +0 -429
  89. package/apps/mac/Sources/Core/Desktop/WindowPreviewCard.swift +0 -100
  90. package/apps/mac/Sources/Core/Desktop/WindowPreviewStore.swift +0 -112
  91. package/apps/mac/Sources/Core/Desktop/WindowSelectionStore.swift +0 -76
  92. package/apps/mac/Sources/Core/Desktop/WindowTiler.swift +0 -2222
  93. package/apps/mac/Sources/Core/Input/EventTapBreaker.swift +0 -124
  94. package/apps/mac/Sources/Core/Input/EventTapThread.swift +0 -54
  95. package/apps/mac/Sources/Core/Input/InputCaptureResetCenter.swift +0 -20
  96. package/apps/mac/Sources/Core/Input/KeyboardRemapConfig.swift +0 -69
  97. package/apps/mac/Sources/Core/Input/KeyboardRemapController.swift +0 -346
  98. package/apps/mac/Sources/Core/Input/KeyboardRemapStore.swift +0 -141
  99. package/apps/mac/Sources/Core/Input/MouseGestureConfig.swift +0 -499
  100. package/apps/mac/Sources/Core/Input/MouseGestureController.swift +0 -2583
  101. package/apps/mac/Sources/Core/Input/MouseInputDeviceStore.swift +0 -98
  102. package/apps/mac/Sources/Core/Input/MouseInputEventViewer.swift +0 -272
  103. package/apps/mac/Sources/Core/Input/MouseShortcutStore.swift +0 -170
  104. package/apps/mac/Sources/Core/Input/SecureEventInputMonitor.swift +0 -39
  105. package/apps/mac/Sources/Core/Input/ShapeRecognizer.swift +0 -624
  106. package/apps/mac/Sources/Core/Input/TapBudgetMeter.swift +0 -56
  107. package/apps/mac/Sources/Core/Overlays/AppWindowShell.swift +0 -63
  108. package/apps/mac/Sources/Core/Overlays/CommandMode/CommandModeState.swift +0 -1566
  109. package/apps/mac/Sources/Core/Overlays/CommandMode/CommandModeView.swift +0 -1927
  110. package/apps/mac/Sources/Core/Overlays/CommandMode/CommandModeWindow.swift +0 -196
  111. package/apps/mac/Sources/Core/Overlays/CommandPalette/CommandPaletteView.swift +0 -307
  112. package/apps/mac/Sources/Core/Overlays/CommandPalette/CommandPaletteWindow.swift +0 -67
  113. package/apps/mac/Sources/Core/Overlays/HUD/CheatSheetHUD.swift +0 -576
  114. package/apps/mac/Sources/Core/Overlays/HUD/HUDBottomBar.swift +0 -279
  115. package/apps/mac/Sources/Core/Overlays/HUD/HUDController.swift +0 -1158
  116. package/apps/mac/Sources/Core/Overlays/HUD/HUDLeftBar.swift +0 -849
  117. package/apps/mac/Sources/Core/Overlays/HUD/HUDMinimap.swift +0 -179
  118. package/apps/mac/Sources/Core/Overlays/HUD/HUDRightBar.swift +0 -596
  119. package/apps/mac/Sources/Core/Overlays/HUD/HUDState.swift +0 -367
  120. package/apps/mac/Sources/Core/Overlays/HUD/HUDTopBar.swift +0 -243
  121. package/apps/mac/Sources/Core/Overlays/HUD/LauncherHUD.swift +0 -334
  122. package/apps/mac/Sources/Core/Overlays/HUD/LayerBezel.swift +0 -203
  123. package/apps/mac/Sources/Core/Overlays/OmniSearch/OmniSearchState.swift +0 -280
  124. package/apps/mac/Sources/Core/Overlays/OmniSearch/OmniSearchView.swift +0 -422
  125. package/apps/mac/Sources/Core/Overlays/OmniSearch/OmniSearchWindow.swift +0 -94
  126. package/apps/mac/Sources/Core/Overlays/OverlayPanelShell.swift +0 -241
  127. package/apps/mac/Sources/Core/Overlays/ScreenMap/ScreenMapState.swift +0 -3135
  128. package/apps/mac/Sources/Core/Overlays/ScreenMap/ScreenMapView.swift +0 -3977
  129. package/apps/mac/Sources/Core/Overlays/ScreenMap/ScreenMapWindowController.swift +0 -119
  130. package/apps/mac/Sources/Core/Overlays/ScreenOverlayCanvasController.swift +0 -1217
  131. package/apps/mac/Sources/Core/Overlays/Voice/VoiceCommandWindow.swift +0 -1575
  132. package/apps/mac/Sources/Core/Pi/PiAuthNextStepCard.swift +0 -148
  133. package/apps/mac/Sources/Core/Pi/PiAuthPromptCard.swift +0 -90
  134. package/apps/mac/Sources/Core/Pi/PiChatDock.swift +0 -564
  135. package/apps/mac/Sources/Core/Pi/PiChatSession.swift +0 -1948
  136. package/apps/mac/Sources/Core/Pi/PiInstallCallout.swift +0 -86
  137. package/apps/mac/Sources/Core/Pi/PiProviderSetupCallout.swift +0 -99
  138. package/apps/mac/Sources/Core/Pi/PiWorkspaceView.swift +0 -510
  139. package/apps/mac/Sources/Core/System/Capability.swift +0 -79
  140. package/apps/mac/Sources/Core/System/DiagnosticLog.swift +0 -373
  141. package/apps/mac/Sources/Core/System/EventBus.swift +0 -31
  142. package/apps/mac/Sources/Core/System/PermissionChecker.swift +0 -224
  143. package/apps/mac/Sources/Core/System/ProcessModel.swift +0 -199
  144. package/apps/mac/Sources/Core/System/ProcessQuery.swift +0 -151
  145. package/apps/mac/Sources/Core/System/SystemTelemetryMonitor.swift +0 -273
  146. package/apps/mac/Sources/Core/Voice/AdvisorLearningStore.swift +0 -90
  147. package/apps/mac/Sources/Core/Voice/AgentSession.swift +0 -377
  148. package/apps/mac/Sources/Core/Voice/AudioProvider.swift +0 -555
  149. package/apps/mac/Sources/Core/Voice/HandsOffSession.swift +0 -839
  150. package/apps/mac/Sources/Core/Voice/VoiceChatView.swift +0 -192
  151. package/apps/mac/Sources/Core/Voice/VoxClient.swift +0 -454
  152. package/apps/mac/Sources/Core/Workspace/Project.swift +0 -28
  153. package/apps/mac/Sources/Core/Workspace/ProjectScanner.swift +0 -141
  154. package/apps/mac/Sources/Core/Workspace/SessionLayerStore.swift +0 -285
  155. package/apps/mac/Sources/Core/Workspace/SessionManager.swift +0 -75
  156. package/apps/mac/Sources/Core/Workspace/Terminal/Terminal.swift +0 -259
  157. package/apps/mac/Sources/Core/Workspace/Terminal/TerminalQuery.swift +0 -156
  158. package/apps/mac/Sources/Core/Workspace/Terminal/TerminalSynthesizer.swift +0 -200
  159. package/apps/mac/Sources/Core/Workspace/Tmux/TmuxModel.swift +0 -60
  160. package/apps/mac/Sources/Core/Workspace/Tmux/TmuxQuery.swift +0 -105
  161. package/apps/mac/Sources/Core/Workspace/WorkspaceManager.swift +0 -1027
  162. package/apps/mac/Sources/UI/ActionRow.swift +0 -78
  163. package/apps/mac/Sources/UI/OrphanRow.swift +0 -129
  164. package/apps/mac/Sources/UI/ProjectRow.swift +0 -368
  165. package/apps/mac/Sources/UI/TabGroupRow.swift +0 -178
  166. package/apps/mac/Sources/UI/Theme.swift +0 -164
  167. package/apps/mac/Tests/StageDragTests.swift +0 -333
  168. package/apps/mac/Tests/StageJoinTests.swift +0 -313
  169. package/apps/mac/Tests/StageManagerTests.swift +0 -280
  170. package/apps/mac/Tests/StageTileTests.swift +0 -353
  171. package/swift/Package.swift +0 -20
  172. package/swift/Sources/DeckKit/DeckAction.swift +0 -51
  173. package/swift/Sources/DeckKit/DeckBridgeSecurity.swift +0 -152
  174. package/swift/Sources/DeckKit/DeckCockpit.swift +0 -82
  175. package/swift/Sources/DeckKit/DeckHost.swift +0 -7
  176. package/swift/Sources/DeckKit/DeckManifest.swift +0 -145
  177. package/swift/Sources/DeckKit/DeckRuntimeSnapshot.swift +0 -533
  178. package/swift/Sources/DeckKit/DeckTrackpad.swift +0 -63
  179. package/swift/Sources/DeckKit/DeckValue.swift +0 -93
  180. package/swift/Sources/DeckKit/DeckVoiceError.swift +0 -88
  181. package/swift/Tests/DeckKitTests/DeckKitTests.swift +0 -286
@@ -1,280 +0,0 @@
1
- import AppKit
2
- import Combine
3
- import Foundation
4
-
5
- // MARK: - Result Types
6
-
7
- enum OmniResultKind: String {
8
- case window
9
- case project
10
- case session
11
- case process
12
- case ocrContent
13
- }
14
-
15
- struct OmniResult: Identifiable {
16
- let id = UUID()
17
- let kind: OmniResultKind
18
- let title: String
19
- let subtitle: String
20
- let icon: String
21
- let score: Int // higher = better match
22
- let action: () -> Void
23
-
24
- /// Group label for display
25
- var groupLabel: String {
26
- switch kind {
27
- case .window: return "Windows"
28
- case .project: return "Projects"
29
- case .session: return "Sessions"
30
- case .process: return "Processes"
31
- case .ocrContent: return "Screen Text"
32
- }
33
- }
34
- }
35
-
36
- // MARK: - Activity Summary
37
-
38
- struct ActivitySummary {
39
- struct AppWindowCount: Identifiable {
40
- let id: String
41
- let appName: String
42
- let count: Int
43
- }
44
-
45
- struct SessionInfo: Identifiable {
46
- let id: String
47
- let name: String
48
- let paneCount: Int
49
- let attached: Bool
50
- }
51
-
52
- let windowsByApp: [AppWindowCount]
53
- let totalWindows: Int
54
- let sessions: [SessionInfo]
55
- let interestingProcesses: [ProcessEntry]
56
- let lastOcrScan: Date?
57
- let ocrWindowCount: Int
58
- }
59
-
60
- // MARK: - State
61
-
62
- final class OmniSearchState: ObservableObject {
63
- @Published var query: String = ""
64
- @Published var results: [OmniResult] = []
65
- @Published var selectedIndex: Int = 0
66
- @Published var activitySummary: ActivitySummary?
67
-
68
- private var cancellables = Set<AnyCancellable>()
69
- private var debounceTimer: AnyCancellable?
70
-
71
- init() {
72
- // Single-char queries fire immediately with a lightweight search;
73
- // longer queries debounce 150ms and run the full search.
74
- debounceTimer = $query
75
- .removeDuplicates()
76
- .sink { [weak self] q in
77
- guard let self else { return }
78
- self.fullSearchTask?.cancel()
79
- if q.isEmpty {
80
- self.results = []
81
- self.refreshSummary()
82
- } else if q.count == 1 {
83
- self.quickSearch(q)
84
- } else {
85
- self.fullSearchTask = DispatchWorkItem { [weak self] in
86
- self?.search(q)
87
- }
88
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.15, execute: self.fullSearchTask!)
89
- }
90
- }
91
-
92
- refreshSummary()
93
- }
94
-
95
- private var fullSearchTask: DispatchWorkItem?
96
-
97
- // MARK: - Quick search (first character — window index only, no terminal/OCR/projects)
98
-
99
- private func quickSearch(_ query: String) {
100
- let q = query.lowercased()
101
- let desktop = DesktopModel.shared
102
- var all: [OmniResult] = []
103
-
104
- for entry in desktop.allWindows() {
105
- var score = 0
106
- if entry.title.lowercased().contains(q) { score += 3 }
107
- if entry.app.lowercased().contains(q) { score += 2 }
108
- if entry.latticesSession?.lowercased().contains(q) == true { score += 3 }
109
- guard score > 0 else { continue }
110
-
111
- let wid = entry.wid
112
- let pid = entry.pid
113
- all.append(OmniResult(
114
- kind: .window,
115
- title: entry.app,
116
- subtitle: entry.title.isEmpty ? "Window \(wid)" : entry.title,
117
- icon: "macwindow",
118
- score: score
119
- ) {
120
- WindowTiler.focusWindow(wid: wid, pid: pid)
121
- })
122
- }
123
-
124
- all.sort { $0.score > $1.score }
125
- results = Array(all.prefix(12))
126
- selectedIndex = 0
127
- }
128
-
129
- // MARK: - Search (delegates to unified lattices.search API)
130
-
131
- private func search(_ query: String) {
132
- let q = query.lowercased()
133
- var all: [OmniResult] = []
134
-
135
- // ── Daemon search: windows, terminals, OCR — single source of truth ──
136
- // This is synchronous on the daemon's in-process API, not a network call.
137
- if let json = try? LatticesApi.shared.dispatch(
138
- method: "lattices.search",
139
- params: .object(["query": .string(q)])
140
- ), case .array(let hits) = json {
141
- let desktop = DesktopModel.shared
142
- for hit in hits {
143
- guard let wid = hit["wid"]?.uint32Value else { continue }
144
- let app = hit["app"]?.stringValue ?? ""
145
- let title = hit["title"]?.stringValue ?? ""
146
- let score = hit["score"]?.intValue ?? 0
147
- let pid = desktop.windows[wid]?.pid ?? 0
148
- let sources = (hit["matchSources"]?.arrayValue ?? []).compactMap(\.stringValue)
149
-
150
- // Determine kind from match sources
151
- let hasOcr = sources.contains("ocr")
152
- let hasTerminal = !Set(sources).isDisjoint(with: ["cwd", "tab", "tmux", "process"])
153
- let kind: OmniResultKind = hasOcr ? .ocrContent : hasTerminal ? .session : .window
154
-
155
- let icon: String
156
- let subtitle: String
157
- switch kind {
158
- case .ocrContent:
159
- icon = "doc.text.magnifyingglass"
160
- subtitle = hit["ocrSnippet"]?.stringValue ?? title
161
- case .session:
162
- icon = "terminal"
163
- let tabs = hit["terminalTabs"]?.arrayValue ?? []
164
- let cwds = tabs.compactMap { $0["cwd"]?.stringValue }
165
- subtitle = cwds.first ?? title
166
- default:
167
- icon = "macwindow"
168
- subtitle = title.isEmpty ? "Window \(wid)" : title
169
- }
170
-
171
- all.append(OmniResult(
172
- kind: kind,
173
- title: app,
174
- subtitle: subtitle,
175
- icon: icon,
176
- score: score
177
- ) {
178
- WindowTiler.focusWindow(wid: wid, pid: pid)
179
- })
180
- }
181
- }
182
-
183
- // ── Projects: local-only (not window-centric, so not in daemon search) ──
184
- for project in ProjectScanner.shared.projects {
185
- let score = scoreProjectMatch(q, name: project.name, path: project.path)
186
- if score > 0 {
187
- let proj = project
188
- all.append(OmniResult(
189
- kind: .project,
190
- title: project.name,
191
- subtitle: project.path,
192
- icon: "folder",
193
- score: score
194
- ) {
195
- SessionManager.launch(project: proj)
196
- })
197
- }
198
- }
199
-
200
- all.sort { $0.score > $1.score }
201
- results = all
202
- selectedIndex = 0
203
- }
204
-
205
- // MARK: - Project scoring (local — projects aren't windows)
206
-
207
- private func scoreProjectMatch(_ query: String, name: String, path: String) -> Int {
208
- let lowerName = name.lowercased()
209
- let lowerPath = path.lowercased()
210
- if lowerName == query { return 100 }
211
- if lowerName.hasPrefix(query) { return 80 }
212
- if lowerName.contains(query) { return 60 }
213
- if lowerPath.contains(query) { return 40 }
214
- return 0
215
- }
216
-
217
- // MARK: - Navigation
218
-
219
- func moveSelection(_ delta: Int) {
220
- guard !results.isEmpty else { return }
221
- selectedIndex = max(0, min(results.count - 1, selectedIndex + delta))
222
- }
223
-
224
- func activateSelected() {
225
- guard selectedIndex >= 0, selectedIndex < results.count else { return }
226
- results[selectedIndex].action()
227
- }
228
-
229
- // MARK: - Activity Summary
230
-
231
- func refreshSummary() {
232
- let desktop = DesktopModel.shared
233
- let windows = desktop.allWindows()
234
-
235
- // Group by app
236
- var appCounts: [String: Int] = [:]
237
- for win in windows {
238
- appCounts[win.app, default: 0] += 1
239
- }
240
- let windowsByApp = appCounts
241
- .sorted { $0.value > $1.value }
242
- .map { ActivitySummary.AppWindowCount(id: $0.key, appName: $0.key, count: $0.value) }
243
-
244
- // Sessions
245
- let sessions = TmuxModel.shared.sessions.map {
246
- ActivitySummary.SessionInfo(
247
- id: $0.id,
248
- name: $0.name,
249
- paneCount: $0.panes.count,
250
- attached: $0.attached
251
- )
252
- }
253
-
254
- // Processes
255
- let procs = ProcessModel.shared.interesting
256
-
257
- // OCR info
258
- let ocrResults = OcrModel.shared.results
259
- let lastScan: Date? = ocrResults.values.map(\.timestamp).max()
260
-
261
- activitySummary = ActivitySummary(
262
- windowsByApp: windowsByApp,
263
- totalWindows: windows.count,
264
- sessions: sessions,
265
- interestingProcesses: procs,
266
- lastOcrScan: lastScan,
267
- ocrWindowCount: ocrResults.count
268
- )
269
- }
270
-
271
- /// Grouped results for display
272
- var groupedResults: [(String, [OmniResult])] {
273
- let groups = Dictionary(grouping: results) { $0.groupLabel }
274
- let order: [String] = ["Windows", "Projects", "Sessions", "Processes", "Screen Text"]
275
- return order.compactMap { key in
276
- guard let items = groups[key], !items.isEmpty else { return nil }
277
- return (key, items)
278
- }
279
- }
280
- }
@@ -1,422 +0,0 @@
1
- import SwiftUI
2
-
3
- struct OmniSearchView: View {
4
- @ObservedObject var state: OmniSearchState
5
- var onDismiss: () -> Void
6
- var isEmbedded: Bool = false
7
-
8
- @ObservedObject private var ocrModel = OcrModel.shared
9
- @State private var expandedOcrWindow: UInt32?
10
- @FocusState private var searchFocused: Bool
11
-
12
- var body: some View {
13
- VStack(spacing: 0) {
14
- // Search field
15
- HStack(spacing: 8) {
16
- Image(systemName: "magnifyingglass")
17
- .foregroundColor(Palette.textMuted)
18
- .font(.system(size: 13))
19
-
20
- TextField("Search windows, projects, sessions...", text: $state.query)
21
- .textFieldStyle(.plain)
22
- .font(Typo.mono(14))
23
- .foregroundColor(Palette.text)
24
- .focused($searchFocused)
25
-
26
- if !state.query.isEmpty {
27
- Button { state.query = "" } label: {
28
- Image(systemName: "xmark.circle.fill")
29
- .foregroundColor(Palette.textMuted)
30
- .font(.system(size: 12))
31
- }
32
- .buttonStyle(.plain)
33
- }
34
- }
35
- .padding(.horizontal, 14)
36
- .padding(.vertical, 12)
37
- .background(Palette.surface)
38
-
39
- Rectangle()
40
- .fill(Palette.border)
41
- .frame(height: 0.5)
42
-
43
- // Content
44
- if state.query.isEmpty {
45
- summaryView
46
- } else if state.results.isEmpty {
47
- emptyResults
48
- } else {
49
- resultsView
50
- }
51
- }
52
- .frame(
53
- minWidth: isEmbedded ? 0 : 520,
54
- idealWidth: isEmbedded ? nil : 520,
55
- maxWidth: isEmbedded ? .infinity : 700,
56
- minHeight: isEmbedded ? 0 : 360,
57
- idealHeight: isEmbedded ? nil : 480,
58
- maxHeight: isEmbedded ? .infinity : 600,
59
- alignment: .top
60
- )
61
- .background {
62
- if isEmbedded {
63
- Palette.bg
64
- } else {
65
- PanelBackground()
66
- }
67
- }
68
- .preferredColorScheme(.dark)
69
- .onAppear {
70
- searchFocused = true
71
- state.refreshSummary()
72
- }
73
- }
74
-
75
- // MARK: - Results
76
-
77
- private var resultsView: some View {
78
- ScrollViewReader { proxy in
79
- ScrollView {
80
- LazyVStack(alignment: .leading, spacing: 2) {
81
- var flatIndex = 0
82
- ForEach(state.groupedResults, id: \.0) { group, items in
83
- // Group header
84
- Text(group.uppercased())
85
- .font(Typo.caption(9))
86
- .foregroundColor(Palette.textMuted)
87
- .padding(.horizontal, 14)
88
- .padding(.top, 8)
89
- .padding(.bottom, 2)
90
-
91
- ForEach(items) { item in
92
- let idx = flatIndex
93
- let _ = { flatIndex += 1 }()
94
- resultRow(item, index: idx)
95
- .id(item.id)
96
- }
97
- }
98
- }
99
- .padding(.vertical, 4)
100
- }
101
- .onChange(of: state.selectedIndex) { newVal in
102
- if newVal < state.results.count {
103
- let item = state.results[newVal]
104
- withAnimation(.easeOut(duration: 0.1)) {
105
- proxy.scrollTo(item.id, anchor: .center)
106
- }
107
- }
108
- }
109
- }
110
- }
111
-
112
- private func resultRow(_ item: OmniResult, index: Int) -> some View {
113
- let isSelected = index == state.selectedIndex
114
- return Button {
115
- item.action()
116
- onDismiss()
117
- } label: {
118
- HStack(spacing: 10) {
119
- Image(systemName: item.icon)
120
- .font(.system(size: 11, weight: .medium))
121
- .foregroundColor(isSelected ? Palette.text : Palette.textDim)
122
- .frame(width: 16)
123
-
124
- VStack(alignment: .leading, spacing: 1) {
125
- Text(item.title)
126
- .font(Typo.mono(12))
127
- .foregroundColor(isSelected ? Palette.text : Palette.textDim)
128
- .lineLimit(1)
129
-
130
- Text(item.subtitle)
131
- .font(Typo.mono(10))
132
- .foregroundColor(Palette.textMuted)
133
- .lineLimit(1)
134
- }
135
-
136
- Spacer()
137
-
138
- Text(item.kind.rawValue)
139
- .font(Typo.mono(9))
140
- .foregroundColor(Palette.textMuted)
141
- .padding(.horizontal, 5)
142
- .padding(.vertical, 2)
143
- .background(
144
- RoundedRectangle(cornerRadius: 3)
145
- .fill(Palette.surface)
146
- )
147
- }
148
- .padding(.horizontal, 14)
149
- .padding(.vertical, 6)
150
- .background(
151
- RoundedRectangle(cornerRadius: 5)
152
- .fill(isSelected ? Palette.surfaceHov : Color.clear)
153
- )
154
- .contentShape(Rectangle())
155
- }
156
- .buttonStyle(.plain)
157
- }
158
-
159
- // MARK: - Empty Results
160
-
161
- private var emptyResults: some View {
162
- VStack(spacing: 12) {
163
- Spacer()
164
- Image(systemName: "magnifyingglass")
165
- .font(.system(size: 24, weight: .light))
166
- .foregroundColor(Palette.textMuted)
167
- Text("No results for \"\(state.query)\"")
168
- .font(Typo.mono(12))
169
- .foregroundColor(Palette.textDim)
170
- Spacer()
171
- }
172
- }
173
-
174
- // MARK: - Activity Summary
175
-
176
- private var summaryView: some View {
177
- ScrollView {
178
- VStack(alignment: .leading, spacing: 14) {
179
- if let summary = state.activitySummary {
180
- // Windows by app
181
- summarySection("WINDOWS", icon: "macwindow", count: summary.totalWindows) {
182
- ForEach(summary.windowsByApp) { app in
183
- HStack {
184
- Text(app.appName)
185
- .font(Typo.mono(11))
186
- .foregroundColor(Palette.textDim)
187
- .lineLimit(1)
188
- Spacer()
189
- Text("\(app.count)")
190
- .font(Typo.monoBold(11))
191
- .foregroundColor(Palette.text)
192
- }
193
- }
194
- }
195
-
196
- // Sessions
197
- if !summary.sessions.isEmpty {
198
- summarySection("TMUX SESSIONS", icon: "terminal", count: summary.sessions.count) {
199
- ForEach(summary.sessions) { session in
200
- HStack {
201
- Circle()
202
- .fill(session.attached ? Palette.running : Palette.textMuted)
203
- .frame(width: 6, height: 6)
204
- Text(session.name)
205
- .font(Typo.mono(11))
206
- .foregroundColor(Palette.textDim)
207
- Spacer()
208
- Text("\(session.paneCount) panes")
209
- .font(Typo.mono(10))
210
- .foregroundColor(Palette.textMuted)
211
- }
212
- }
213
- }
214
- }
215
-
216
- // Processes
217
- if !summary.interestingProcesses.isEmpty {
218
- summarySection("PROCESSES", icon: "gearshape", count: summary.interestingProcesses.count) {
219
- ForEach(Array(summary.interestingProcesses.prefix(10).enumerated()), id: \.offset) { _, proc in
220
- HStack {
221
- Text(proc.comm)
222
- .font(Typo.monoBold(11))
223
- .foregroundColor(Palette.textDim)
224
- if let cwd = proc.cwd {
225
- Text(cwd.replacingOccurrences(of: NSHomeDirectory(), with: "~"))
226
- .font(Typo.mono(10))
227
- .foregroundColor(Palette.textMuted)
228
- .lineLimit(1)
229
- }
230
- Spacer()
231
- }
232
- }
233
- }
234
- }
235
-
236
- // OCR info
237
- if summary.ocrWindowCount > 0 {
238
- HStack(spacing: 6) {
239
- Image(systemName: "doc.text.magnifyingglass")
240
- .font(.system(size: 10))
241
- .foregroundColor(Palette.textMuted)
242
- Text("OCR: \(summary.ocrWindowCount) windows scanned")
243
- .font(Typo.mono(10))
244
- .foregroundColor(Palette.textMuted)
245
- if let t = summary.lastOcrScan {
246
- Spacer()
247
- Text(relativeTime(t))
248
- .font(Typo.mono(9))
249
- .foregroundColor(Palette.textMuted)
250
- }
251
- }
252
- .padding(.horizontal, 14)
253
- }
254
-
255
- if !recentOcrResults.isEmpty {
256
- ocrResultsSection
257
- }
258
- } else {
259
- Text("Loading...")
260
- .font(Typo.mono(11))
261
- .foregroundColor(Palette.textMuted)
262
- .padding(14)
263
- }
264
- }
265
- .padding(.vertical, 10)
266
- }
267
- }
268
-
269
- private var recentOcrResults: [OcrWindowResult] {
270
- Array(ocrModel.results.values.sorted { $0.timestamp > $1.timestamp }.prefix(10))
271
- }
272
-
273
- private var ocrResultsSection: some View {
274
- summarySection("SCREEN TEXT", icon: "doc.text.magnifyingglass", count: ocrModel.results.count) {
275
- ForEach(recentOcrResults, id: \.wid) { result in
276
- ocrResultRow(result)
277
- }
278
- }
279
- }
280
-
281
- private func ocrResultRow(_ result: OcrWindowResult) -> some View {
282
- let isExpanded = expandedOcrWindow == result.wid
283
- let title = result.title.isEmpty ? "Untitled" : result.title
284
- let preview = compactPreview(result.fullText)
285
-
286
- return VStack(alignment: .leading, spacing: 5) {
287
- Button {
288
- withAnimation(.easeOut(duration: 0.12)) {
289
- expandedOcrWindow = isExpanded ? nil : result.wid
290
- }
291
- } label: {
292
- VStack(alignment: .leading, spacing: 4) {
293
- HStack(spacing: 7) {
294
- Image(systemName: isExpanded ? "chevron.down" : "chevron.right")
295
- .font(.system(size: 8, weight: .semibold))
296
- .foregroundColor(Palette.textMuted)
297
- .frame(width: 9)
298
-
299
- Text(result.app)
300
- .font(Typo.monoBold(11))
301
- .foregroundColor(Palette.textDim)
302
- .lineLimit(1)
303
-
304
- Text(sourceLabel(result.source))
305
- .font(Typo.mono(8))
306
- .foregroundColor(Palette.textMuted)
307
- .padding(.horizontal, 4)
308
- .padding(.vertical, 1)
309
- .background(
310
- RoundedRectangle(cornerRadius: 3)
311
- .fill(Palette.surface.opacity(0.8))
312
- )
313
-
314
- Spacer()
315
-
316
- Text(relativeTime(result.timestamp))
317
- .font(Typo.mono(9))
318
- .foregroundColor(Palette.textMuted)
319
- }
320
-
321
- Text(title)
322
- .font(Typo.mono(10))
323
- .foregroundColor(Palette.textMuted)
324
- .lineLimit(1)
325
-
326
- if !isExpanded && !preview.isEmpty {
327
- Text(preview)
328
- .font(Typo.mono(9))
329
- .foregroundColor(Palette.textMuted.opacity(0.75))
330
- .lineLimit(2)
331
- }
332
- }
333
- .padding(8)
334
- .background(
335
- RoundedRectangle(cornerRadius: 5)
336
- .fill(Palette.surface.opacity(isExpanded ? 0.72 : 0.38))
337
- .overlay(
338
- RoundedRectangle(cornerRadius: 5)
339
- .strokeBorder(Color.white.opacity(isExpanded ? 0.10 : 0.05), lineWidth: 0.5)
340
- )
341
- )
342
- .contentShape(Rectangle())
343
- }
344
- .buttonStyle(.plain)
345
-
346
- if isExpanded {
347
- ScrollView {
348
- Text(result.fullText.isEmpty ? "No text captured." : result.fullText)
349
- .font(Typo.mono(10))
350
- .foregroundColor(Palette.textDim)
351
- .textSelection(.enabled)
352
- .frame(maxWidth: .infinity, alignment: .leading)
353
- .padding(8)
354
- }
355
- .frame(maxHeight: 140)
356
- .background(
357
- RoundedRectangle(cornerRadius: 5)
358
- .fill(Color.black.opacity(0.22))
359
- .overlay(
360
- RoundedRectangle(cornerRadius: 5)
361
- .strokeBorder(Color.white.opacity(0.06), lineWidth: 0.5)
362
- )
363
- )
364
- }
365
- }
366
- }
367
-
368
- private func summarySection<Content: View>(
369
- _ title: String,
370
- icon: String,
371
- count: Int,
372
- @ViewBuilder content: () -> Content
373
- ) -> some View {
374
- VStack(alignment: .leading, spacing: 6) {
375
- HStack(spacing: 6) {
376
- Image(systemName: icon)
377
- .font(.system(size: 10, weight: .medium))
378
- .foregroundColor(Palette.textMuted)
379
- Text(title)
380
- .font(Typo.caption(9))
381
- .foregroundColor(Palette.textMuted)
382
- Text("\(count)")
383
- .font(Typo.monoBold(9))
384
- .foregroundColor(Palette.running)
385
- .padding(.horizontal, 4)
386
- .padding(.vertical, 1)
387
- .background(
388
- RoundedRectangle(cornerRadius: 3)
389
- .fill(Palette.running.opacity(0.12))
390
- )
391
- Spacer()
392
- }
393
- .padding(.horizontal, 14)
394
-
395
- VStack(spacing: 3) {
396
- content()
397
- }
398
- .padding(.horizontal, 14)
399
- }
400
- }
401
-
402
- private func relativeTime(_ date: Date) -> String {
403
- let seconds = Int(Date().timeIntervalSince(date))
404
- if seconds < 60 { return "\(seconds)s ago" }
405
- if seconds < 3600 { return "\(seconds / 60)m ago" }
406
- return "\(seconds / 3600)h ago"
407
- }
408
-
409
- private func sourceLabel(_ source: TextSource) -> String {
410
- switch source {
411
- case .accessibility: return "AX"
412
- case .ocr: return "OCR"
413
- }
414
- }
415
-
416
- private func compactPreview(_ text: String) -> String {
417
- text
418
- .components(separatedBy: .whitespacesAndNewlines)
419
- .filter { !$0.isEmpty }
420
- .joined(separator: " ")
421
- }
422
- }