@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,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
- }