@lattices/cli 0.4.2 → 0.4.6

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 (146) hide show
  1. package/README.md +3 -0
  2. package/app/Info.plist +2 -2
  3. package/app/Lattices.app/Contents/Info.plist +2 -2
  4. package/app/Lattices.app/Contents/MacOS/Lattices +0 -0
  5. package/app/Package.swift +6 -0
  6. package/app/Sources/AppShell/App.swift +20 -0
  7. package/app/Sources/{AppDelegate.swift → AppShell/AppDelegate.swift} +94 -34
  8. package/app/Sources/{AppShellView.swift → AppShell/AppShellView.swift} +12 -1
  9. package/app/Sources/AppShell/AppUpdater.swift +92 -0
  10. package/app/Sources/AppShell/CliActionLauncher.swift +50 -0
  11. package/app/Sources/{HomeDashboardView.swift → AppShell/HomeDashboardView.swift} +18 -10
  12. package/app/Sources/AppShell/LatticesRuntime.swift +61 -0
  13. package/app/Sources/{MainView.swift → AppShell/MainView.swift} +351 -191
  14. package/app/Sources/{OnboardingView.swift → AppShell/OnboardingView.swift} +30 -16
  15. package/app/Sources/{Preferences.swift → AppShell/Preferences.swift} +78 -0
  16. package/app/Sources/{SettingsView.swift → AppShell/SettingsView.swift} +869 -152
  17. package/app/Sources/{HotkeyStore.swift → Core/Actions/HotkeyStore.swift} +9 -5
  18. package/app/Sources/{IntentEngine.swift → Core/Actions/IntentEngine.swift} +51 -27
  19. package/app/Sources/Core/Actions/IntentSchema.swift +94 -0
  20. package/app/Sources/{Intents → Core/Actions/Intents}/LatticeIntent.swift +0 -25
  21. package/app/Sources/{PaletteCommand.swift → Core/Actions/PaletteCommand.swift} +26 -6
  22. package/app/Sources/{VoiceIntentResolver.swift → Core/Actions/VoiceIntentResolver.swift} +46 -4
  23. package/app/Sources/Core/Companion/CompanionActivityLog.swift +70 -0
  24. package/app/Sources/Core/Companion/CompanionKeyboardController.swift +141 -0
  25. package/app/Sources/Core/Companion/LatticesCompanionBridgeServer.swift +438 -0
  26. package/app/Sources/Core/Companion/LatticesCompanionCockpit.swift +555 -0
  27. package/app/Sources/Core/Companion/LatticesCompanionSecurityCoordinator.swift +594 -0
  28. package/app/Sources/Core/Companion/LatticesCompanionTrackpadController.swift +204 -0
  29. package/app/Sources/Core/Companion/LatticesDeckHost.swift +1463 -0
  30. package/app/Sources/{LatticesApi.swift → Core/Daemon/LatticesApi.swift} +125 -4
  31. package/app/Sources/{AppTypeClassifier.swift → Core/Desktop/AppTypeClassifier.swift} +36 -0
  32. package/app/Sources/{DesktopModel.swift → Core/Desktop/DesktopModel.swift} +6 -8
  33. package/app/Sources/Core/Desktop/MouseFinder.swift +527 -0
  34. package/app/Sources/Core/Desktop/SessionWindowLocator.swift +139 -0
  35. package/app/Sources/Core/Desktop/WindowDragSnapController.swift +628 -0
  36. package/app/Sources/Core/Desktop/WindowPreviewCard.swift +100 -0
  37. package/app/Sources/Core/Desktop/WindowPreviewStore.swift +113 -0
  38. package/app/Sources/Core/Desktop/WindowSelectionStore.swift +76 -0
  39. package/app/Sources/{WindowTiler.swift → Core/Desktop/WindowTiler.swift} +351 -172
  40. package/app/Sources/Core/Input/MouseGestureConfig.swift +364 -0
  41. package/app/Sources/Core/Input/MouseGestureController.swift +1203 -0
  42. package/app/Sources/Core/Input/MouseInputDeviceStore.swift +98 -0
  43. package/app/Sources/Core/Input/MouseInputEventViewer.swift +272 -0
  44. package/app/Sources/Core/Input/MouseShortcutStore.swift +107 -0
  45. package/app/Sources/{CommandModeState.swift → Core/Overlays/CommandMode/CommandModeState.swift} +127 -24
  46. package/app/Sources/{CommandModeView.swift → Core/Overlays/CommandMode/CommandModeView.swift} +492 -79
  47. package/app/Sources/Core/Overlays/CommandPalette/CommandPaletteWindow.swift +67 -0
  48. package/app/Sources/{CheatSheetHUD.swift → Core/Overlays/HUD/CheatSheetHUD.swift} +1 -0
  49. package/app/Sources/{HUDRightBar.swift → Core/Overlays/HUD/HUDRightBar.swift} +23 -201
  50. package/app/Sources/{LauncherHUD.swift → Core/Overlays/HUD/LauncherHUD.swift} +12 -26
  51. package/app/Sources/{OmniSearchView.swift → Core/Overlays/OmniSearch/OmniSearchView.swift} +136 -2
  52. package/app/Sources/{OmniSearchWindow.swift → Core/Overlays/OmniSearch/OmniSearchWindow.swift} +21 -32
  53. package/app/Sources/Core/Overlays/OverlayPanelShell.swift +241 -0
  54. package/app/Sources/{ScreenMapState.swift → Core/Overlays/ScreenMap/ScreenMapState.swift} +116 -32
  55. package/app/Sources/{ScreenMapView.swift → Core/Overlays/ScreenMap/ScreenMapView.swift} +510 -524
  56. package/app/Sources/{ScreenMapWindowController.swift → Core/Overlays/ScreenMap/ScreenMapWindowController.swift} +12 -4
  57. package/app/Sources/{VoiceCommandWindow.swift → Core/Overlays/Voice/VoiceCommandWindow.swift} +46 -53
  58. package/app/Sources/Core/Pi/PiAuthNextStepCard.swift +148 -0
  59. package/app/Sources/Core/Pi/PiAuthPromptCard.swift +90 -0
  60. package/app/Sources/{PiChatDock.swift → Core/Pi/PiChatDock.swift} +137 -74
  61. package/app/Sources/{PiChatSession.swift → Core/Pi/PiChatSession.swift} +608 -108
  62. package/app/Sources/Core/Pi/PiInstallCallout.swift +86 -0
  63. package/app/Sources/Core/Pi/PiProviderSetupCallout.swift +99 -0
  64. package/app/Sources/{PiWorkspaceView.swift → Core/Pi/PiWorkspaceView.swift} +174 -77
  65. package/app/Sources/{PermissionChecker.swift → Core/System/PermissionChecker.swift} +76 -2
  66. package/app/Sources/Core/System/SystemTelemetryMonitor.swift +273 -0
  67. package/app/Sources/{HandsOffSession.swift → Core/Voice/HandsOffSession.swift} +15 -4
  68. package/app/Sources/{WorkspaceManager.swift → Core/Workspace/WorkspaceManager.swift} +288 -0
  69. package/bin/assistant-intelligence.ts +874 -0
  70. package/bin/handsoff-infer.ts +16 -209
  71. package/bin/handsoff-worker.ts +45 -258
  72. package/bin/lattices-app.ts +62 -0
  73. package/bin/lattices-dev +4 -0
  74. package/bin/lattices.ts +125 -14
  75. package/docs/agents.md +14 -0
  76. package/docs/api.md +55 -0
  77. package/docs/app.md +3 -0
  78. package/docs/companion-deck.md +180 -0
  79. package/docs/component-extraction-roadmap.md +392 -0
  80. package/docs/config.md +25 -0
  81. package/docs/tiling-reference.md +55 -0
  82. package/docs/voice-error-model.md +73 -0
  83. package/package.json +4 -1
  84. package/app/Sources/App.swift +0 -10
  85. package/app/Sources/CommandPaletteWindow.swift +0 -134
  86. package/app/Sources/MouseFinder.swift +0 -222
  87. /package/app/Sources/{KeyRecorderView.swift → AppShell/KeyRecorderView.swift} +0 -0
  88. /package/app/Sources/{MainWindow.swift → AppShell/MainWindow.swift} +0 -0
  89. /package/app/Sources/{SettingsWindow.swift → AppShell/SettingsWindow.swift} +0 -0
  90. /package/app/Sources/{HotkeyManager.swift → Core/Actions/HotkeyManager.swift} +0 -0
  91. /package/app/Sources/{Intents → Core/Actions/Intents}/CreateLayerIntent.swift +0 -0
  92. /package/app/Sources/{Intents → Core/Actions/Intents}/DistributeIntent.swift +0 -0
  93. /package/app/Sources/{Intents → Core/Actions/Intents}/FocusIntent.swift +0 -0
  94. /package/app/Sources/{Intents → Core/Actions/Intents}/HelpIntent.swift +0 -0
  95. /package/app/Sources/{Intents → Core/Actions/Intents}/KillIntent.swift +0 -0
  96. /package/app/Sources/{Intents → Core/Actions/Intents}/LaunchIntent.swift +0 -0
  97. /package/app/Sources/{Intents → Core/Actions/Intents}/ListSessionsIntent.swift +0 -0
  98. /package/app/Sources/{Intents → Core/Actions/Intents}/ListWindowsIntent.swift +0 -0
  99. /package/app/Sources/{Intents → Core/Actions/Intents}/ScanIntent.swift +0 -0
  100. /package/app/Sources/{Intents → Core/Actions/Intents}/SearchIntent.swift +0 -0
  101. /package/app/Sources/{Intents → Core/Actions/Intents}/SwitchLayerIntent.swift +0 -0
  102. /package/app/Sources/{Intents → Core/Actions/Intents}/TileIntent.swift +0 -0
  103. /package/app/Sources/{DaemonProtocol.swift → Core/Daemon/DaemonProtocol.swift} +0 -0
  104. /package/app/Sources/{DaemonServer.swift → Core/Daemon/DaemonServer.swift} +0 -0
  105. /package/app/Sources/{AccessibilityTextExtractor.swift → Core/Desktop/AccessibilityTextExtractor.swift} +0 -0
  106. /package/app/Sources/{DesktopModelTypes.swift → Core/Desktop/DesktopModelTypes.swift} +0 -0
  107. /package/app/Sources/{InventoryManager.swift → Core/Desktop/InventoryManager.swift} +0 -0
  108. /package/app/Sources/{InventoryPath.swift → Core/Desktop/InventoryPath.swift} +0 -0
  109. /package/app/Sources/{OcrModel.swift → Core/Desktop/OcrModel.swift} +0 -0
  110. /package/app/Sources/{OcrStore.swift → Core/Desktop/OcrStore.swift} +0 -0
  111. /package/app/Sources/{PlacementSpec.swift → Core/Desktop/PlacementSpec.swift} +0 -0
  112. /package/app/Sources/{TilePickerView.swift → Core/Desktop/TilePickerView.swift} +0 -0
  113. /package/app/Sources/{AppWindowShell.swift → Core/Overlays/AppWindowShell.swift} +0 -0
  114. /package/app/Sources/{CommandModeWindow.swift → Core/Overlays/CommandMode/CommandModeWindow.swift} +0 -0
  115. /package/app/Sources/{CommandPaletteView.swift → Core/Overlays/CommandPalette/CommandPaletteView.swift} +0 -0
  116. /package/app/Sources/{HUDBottomBar.swift → Core/Overlays/HUD/HUDBottomBar.swift} +0 -0
  117. /package/app/Sources/{HUDController.swift → Core/Overlays/HUD/HUDController.swift} +0 -0
  118. /package/app/Sources/{HUDLeftBar.swift → Core/Overlays/HUD/HUDLeftBar.swift} +0 -0
  119. /package/app/Sources/{HUDMinimap.swift → Core/Overlays/HUD/HUDMinimap.swift} +0 -0
  120. /package/app/Sources/{HUDState.swift → Core/Overlays/HUD/HUDState.swift} +0 -0
  121. /package/app/Sources/{HUDTopBar.swift → Core/Overlays/HUD/HUDTopBar.swift} +0 -0
  122. /package/app/Sources/{LayerBezel.swift → Core/Overlays/HUD/LayerBezel.swift} +0 -0
  123. /package/app/Sources/{OmniSearchState.swift → Core/Overlays/OmniSearch/OmniSearchState.swift} +0 -0
  124. /package/app/Sources/{DiagnosticLog.swift → Core/System/DiagnosticLog.swift} +0 -0
  125. /package/app/Sources/{EventBus.swift → Core/System/EventBus.swift} +0 -0
  126. /package/app/Sources/{ProcessModel.swift → Core/System/ProcessModel.swift} +0 -0
  127. /package/app/Sources/{ProcessQuery.swift → Core/System/ProcessQuery.swift} +0 -0
  128. /package/app/Sources/{AdvisorLearningStore.swift → Core/Voice/AdvisorLearningStore.swift} +0 -0
  129. /package/app/Sources/{AgentSession.swift → Core/Voice/AgentSession.swift} +0 -0
  130. /package/app/Sources/{AudioProvider.swift → Core/Voice/AudioProvider.swift} +0 -0
  131. /package/app/Sources/{VoiceChatView.swift → Core/Voice/VoiceChatView.swift} +0 -0
  132. /package/app/Sources/{VoxClient.swift → Core/Voice/VoxClient.swift} +0 -0
  133. /package/app/Sources/{Project.swift → Core/Workspace/Project.swift} +0 -0
  134. /package/app/Sources/{ProjectScanner.swift → Core/Workspace/ProjectScanner.swift} +0 -0
  135. /package/app/Sources/{SessionLayerStore.swift → Core/Workspace/SessionLayerStore.swift} +0 -0
  136. /package/app/Sources/{SessionManager.swift → Core/Workspace/SessionManager.swift} +0 -0
  137. /package/app/Sources/{Terminal.swift → Core/Workspace/Terminal/Terminal.swift} +0 -0
  138. /package/app/Sources/{TerminalQuery.swift → Core/Workspace/Terminal/TerminalQuery.swift} +0 -0
  139. /package/app/Sources/{TerminalSynthesizer.swift → Core/Workspace/Terminal/TerminalSynthesizer.swift} +0 -0
  140. /package/app/Sources/{TmuxModel.swift → Core/Workspace/Tmux/TmuxModel.swift} +0 -0
  141. /package/app/Sources/{TmuxQuery.swift → Core/Workspace/Tmux/TmuxQuery.swift} +0 -0
  142. /package/app/Sources/{ActionRow.swift → UI/ActionRow.swift} +0 -0
  143. /package/app/Sources/{OrphanRow.swift → UI/OrphanRow.swift} +0 -0
  144. /package/app/Sources/{ProjectRow.swift → UI/ProjectRow.swift} +0 -0
  145. /package/app/Sources/{TabGroupRow.swift → UI/TabGroupRow.swift} +0 -0
  146. /package/app/Sources/{Theme.swift → UI/Theme.swift} +0 -0
@@ -0,0 +1,67 @@
1
+ import AppKit
2
+ import SwiftUI
3
+
4
+ final class CommandPaletteWindow {
5
+ static let shared = CommandPaletteWindow()
6
+
7
+ private var panel: NSPanel?
8
+ private var scanner: ProjectScanner?
9
+
10
+ func configure(scanner: ProjectScanner) {
11
+ self.scanner = scanner
12
+ }
13
+
14
+ var isVisible: Bool { panel?.isVisible ?? false }
15
+
16
+ func toggle() {
17
+ if let p = panel, p.isVisible {
18
+ dismiss()
19
+ } else {
20
+ show()
21
+ }
22
+ }
23
+
24
+ func show() {
25
+ // Always rebuild for fresh command state
26
+ dismiss()
27
+
28
+ guard let scanner = scanner else { return }
29
+
30
+ // Ensure projects are up to date (full scan if list is empty,
31
+ // e.g. palette opened via hotkey before main popover appeared)
32
+ if scanner.projects.isEmpty {
33
+ scanner.scan()
34
+ } else {
35
+ scanner.refreshStatus()
36
+ }
37
+
38
+ let commands = CommandBuilder.build(scanner: scanner)
39
+ let view = CommandPaletteView(commands: commands) { [weak self] in
40
+ self?.dismiss()
41
+ }
42
+ .preferredColorScheme(.dark)
43
+
44
+ let panel = OverlayPanelShell.makePanel(
45
+ config: .init(
46
+ size: NSSize(width: 540, height: 440),
47
+ styleMask: [.nonactivatingPanel],
48
+ background: .material(.popover),
49
+ cornerRadius: 14,
50
+ hidesOnDeactivate: true,
51
+ isMovableByWindowBackground: true
52
+ ),
53
+ rootView: view
54
+ )
55
+ OverlayPanelShell.position(panel, placement: .centered(yOffsetRatio: 0.1))
56
+ OverlayPanelShell.present(panel)
57
+
58
+ self.panel = panel
59
+ AppDelegate.updateActivationPolicy()
60
+ }
61
+
62
+ func dismiss() {
63
+ panel?.orderOut(nil)
64
+ panel = nil
65
+ AppDelegate.updateActivationPolicy()
66
+ }
67
+ }
@@ -254,6 +254,7 @@ struct CheatSheetView: View {
254
254
  // Center + Distribute
255
255
  shortcutRow(action: .tileCenter)
256
256
  shortcutRow(action: .tileDistribute)
257
+ shortcutRow(action: .tileTypeGrid)
257
258
 
258
259
  // Hovered shortcut detail
259
260
  if let hovered = hoveredAction, let binding = hotkeyStore.bindings[hovered] {
@@ -168,34 +168,11 @@ struct HUDRightBar: View {
168
168
  .foregroundColor(Palette.textDim)
169
169
  }
170
170
 
171
- ZStack {
172
- RoundedRectangle(cornerRadius: 10)
173
- .fill(Palette.surface.opacity(0.8))
174
- .overlay(
175
- RoundedRectangle(cornerRadius: 10)
176
- .strokeBorder(Palette.border, lineWidth: 0.5)
177
- )
178
-
179
- if let image = previewModel.image(for: window.wid) {
180
- Image(nsImage: image)
181
- .resizable()
182
- .aspectRatio(contentMode: .fit)
183
- .clipShape(RoundedRectangle(cornerRadius: 8))
184
- .padding(8)
185
- } else if previewModel.isLoading(window.wid) {
186
- previewPlaceholder(
187
- icon: "photo",
188
- title: "Capturing preview",
189
- subtitle: window.app
190
- )
191
- } else {
192
- previewPlaceholder(
193
- icon: "eye.slash",
194
- title: "Preview unavailable",
195
- subtitle: window.app
196
- )
197
- }
198
- }
171
+ WindowPreviewCard(
172
+ image: previewModel.image(for: window.wid),
173
+ isLoading: previewModel.isLoading(window.wid),
174
+ appName: window.app
175
+ )
199
176
  .frame(maxWidth: .infinity)
200
177
  .frame(height: 190)
201
178
  .clipped()
@@ -207,22 +184,6 @@ struct HUDRightBar: View {
207
184
  }
208
185
  }
209
186
 
210
- private func previewPlaceholder(icon: String, title: String, subtitle: String) -> some View {
211
- VStack(spacing: 8) {
212
- Image(systemName: icon)
213
- .font(.system(size: 18, weight: .medium))
214
- .foregroundColor(Palette.textMuted.opacity(0.7))
215
- Text(title)
216
- .font(Typo.monoBold(10))
217
- .foregroundColor(Palette.textMuted)
218
- Text(subtitle)
219
- .font(Typo.mono(9))
220
- .foregroundColor(Palette.textDim)
221
- .lineLimit(1)
222
- }
223
- .padding(16)
224
- }
225
-
226
187
  private func projectPreviewWindow(_ project: Project) -> WindowEntry? {
227
188
  guard project.isRunning else { return nil }
228
189
  return desktop.windowForSession(project.sessionName)
@@ -487,39 +448,25 @@ struct HUDHoverPreviewView: View {
487
448
  }
488
449
 
489
450
  ZStack {
490
- RoundedRectangle(cornerRadius: 12)
491
- .fill(Palette.bg.opacity(0.96))
492
- .overlay(
493
- RoundedRectangle(cornerRadius: 12)
494
- .strokeBorder(Palette.border, lineWidth: 0.5)
495
- )
496
-
497
- if let renderedImage {
498
- Image(nsImage: renderedImage)
499
- .resizable()
500
- .aspectRatio(contentMode: .fit)
501
- .clipShape(RoundedRectangle(cornerRadius: 9))
502
- .padding(10)
503
- .id(renderedWindowID ?? window.wid)
504
- .transition(.opacity)
505
- .opacity(isHoldingPreviousPreview(for: window) ? 0.88 : 1)
506
- } else if previewModel.isLoading(window.wid) {
507
- previewPlaceholder(
508
- icon: "photo",
509
- title: "Capturing preview",
510
- subtitle: window.app
511
- )
512
- } else {
513
- previewPlaceholder(
514
- icon: "eye.slash",
515
- title: "Preview unavailable",
516
- subtitle: window.app
517
- )
518
- }
519
-
520
- if isHoldingPreviousPreview(for: window) {
521
- loadingOverlay(label: "Loading next preview")
451
+ WindowPreviewCard(
452
+ image: renderedImage,
453
+ isLoading: previewModel.isLoading(window.wid),
454
+ appName: window.app,
455
+ style: WindowPreviewCardStyle(
456
+ containerCornerRadius: 12,
457
+ imageCornerRadius: 9,
458
+ imagePadding: 10,
459
+ background: Palette.bg.opacity(0.96),
460
+ border: Palette.border
461
+ ),
462
+ holdingPreviousPreview: isHoldingPreviousPreview(for: window)
463
+ ) {
464
+ if isHoldingPreviousPreview(for: window) {
465
+ loadingOverlay(label: "Loading next preview")
466
+ }
522
467
  }
468
+ .id(renderedWindowID ?? window.wid)
469
+ .transition(.opacity)
523
470
  }
524
471
  .frame(height: 190)
525
472
  }
@@ -591,22 +538,6 @@ struct HUDHoverPreviewView: View {
591
538
  }
592
539
  }
593
540
 
594
- private func previewPlaceholder(icon: String, title: String, subtitle: String) -> some View {
595
- VStack(spacing: 8) {
596
- Image(systemName: icon)
597
- .font(.system(size: 18, weight: .medium))
598
- .foregroundColor(Palette.textMuted.opacity(0.7))
599
- Text(title)
600
- .font(Typo.monoBold(10))
601
- .foregroundColor(Palette.textMuted)
602
- Text(subtitle)
603
- .font(Typo.mono(9))
604
- .foregroundColor(Palette.textDim)
605
- .lineLimit(1)
606
- }
607
- .padding(16)
608
- }
609
-
610
541
  private func loadingOverlay(label: String) -> some View {
611
542
  VStack {
612
543
  Spacer()
@@ -663,112 +594,3 @@ struct HUDHoverPreviewView: View {
663
594
  }
664
595
  }
665
596
  }
666
-
667
- final class WindowPreviewStore: ObservableObject {
668
- static let shared = WindowPreviewStore()
669
-
670
- @Published private var images: [UInt32: NSImage] = [:]
671
- @Published private var loading: Set<UInt32> = []
672
-
673
- private var lastAttemptAt: [UInt32: Date] = [:]
674
- private var accessOrder: [UInt32] = [] // LRU: oldest first
675
- private let maxCached = 15
676
- private let queue = DispatchQueue(label: "com.arach.lattices.hud-preview", qos: .userInitiated)
677
- private let previewMaxSize = NSSize(width: 360, height: 190)
678
-
679
- func image(for wid: UInt32) -> NSImage? {
680
- if images[wid] != nil { touchLRU(wid) }
681
- return images[wid]
682
- }
683
-
684
- private func touchLRU(_ wid: UInt32) {
685
- accessOrder.removeAll { $0 == wid }
686
- accessOrder.append(wid)
687
- }
688
-
689
- private func evictIfNeeded() {
690
- while images.count > maxCached, let oldest = accessOrder.first {
691
- accessOrder.removeFirst()
692
- images.removeValue(forKey: oldest)
693
- lastAttemptAt.removeValue(forKey: oldest)
694
- }
695
- }
696
-
697
- func hasSettled(_ wid: UInt32) -> Bool {
698
- images[wid] != nil || (lastAttemptAt[wid] != nil && !loading.contains(wid))
699
- }
700
-
701
- func isLoading(_ wid: UInt32) -> Bool {
702
- loading.contains(wid)
703
- }
704
-
705
- func prewarm(windows: [WindowEntry], limit: Int = 4) {
706
- for window in windows.prefix(limit) {
707
- load(window: window)
708
- }
709
- }
710
-
711
- func load(window: WindowEntry) {
712
- if images[window.wid] != nil || loading.contains(window.wid) {
713
- return
714
- }
715
-
716
- let now = Date()
717
- if let lastAttemptAt = lastAttemptAt[window.wid], now.timeIntervalSince(lastAttemptAt) < 1.0 {
718
- return
719
- }
720
- lastAttemptAt[window.wid] = now
721
-
722
- loading.insert(window.wid)
723
- let wid = window.wid
724
- let frame = window.frame
725
- let startedAt = Date()
726
-
727
- queue.async { [weak self] in
728
- guard let self else { return }
729
-
730
- let cgImage = CGWindowListCreateImage(
731
- .null,
732
- .optionIncludingWindow,
733
- CGWindowID(wid),
734
- [.boundsIgnoreFraming, .nominalResolution]
735
- )
736
-
737
- let image = cgImage.map {
738
- NSImage(
739
- cgImage: $0,
740
- size: self.previewSize(for: frame)
741
- )
742
- }
743
-
744
- DispatchQueue.main.async {
745
- self.loading.remove(wid)
746
- let elapsedMs = Int(Date().timeIntervalSince(startedAt) * 1000)
747
- if let image {
748
- self.images[wid] = image
749
- self.touchLRU(wid)
750
- self.evictIfNeeded()
751
- if elapsedMs >= 80 {
752
- DiagnosticLog.shared.info("HUDPreview: captured wid=\(wid) in \(elapsedMs)ms")
753
- }
754
- } else {
755
- DiagnosticLog.shared.info("HUDPreview: capture unavailable wid=\(wid) after \(elapsedMs)ms")
756
- }
757
- }
758
- }
759
- }
760
-
761
- private func previewSize(for frame: WindowFrame) -> NSSize {
762
- let width = max(CGFloat(frame.w), CGFloat(1))
763
- let height = max(CGFloat(frame.h), CGFloat(1))
764
- let scale = min(
765
- previewMaxSize.width / width,
766
- previewMaxSize.height / height,
767
- CGFloat(1)
768
- )
769
- return NSSize(
770
- width: max(CGFloat(1), width * scale),
771
- height: max(CGFloat(1), height * scale)
772
- )
773
- }
774
- }
@@ -25,33 +25,20 @@ final class LauncherHUD {
25
25
  let view = LauncherView(dismiss: { [weak self] in self?.dismiss() })
26
26
  .preferredColorScheme(.dark)
27
27
 
28
- let hosting = NSHostingView(rootView: view)
29
-
30
- let p = NSPanel(
31
- contentRect: NSRect(x: 0, y: 0, width: 420, height: 480),
32
- styleMask: [.borderless, .nonactivatingPanel],
33
- backing: .buffered,
34
- defer: false
28
+ let p = OverlayPanelShell.makePanel(
29
+ config: .init(
30
+ size: NSSize(width: 420, height: 480),
31
+ styleMask: [.borderless, .nonactivatingPanel],
32
+ background: .clear,
33
+ hidesOnDeactivate: false,
34
+ isMovableByWindowBackground: false
35
+ ),
36
+ rootView: view
35
37
  )
36
- p.isOpaque = false
37
- p.backgroundColor = .clear
38
- p.level = .floating
39
- p.hasShadow = true
40
- p.hidesOnDeactivate = false
41
- p.isReleasedWhenClosed = false
42
- p.isMovableByWindowBackground = false
43
- p.contentView = hosting
44
-
45
- // Center on mouse screen
46
- let mouseLocation = NSEvent.mouseLocation
47
- let screen = NSScreen.screens.first(where: { $0.frame.contains(mouseLocation) }) ?? NSScreen.main ?? NSScreen.screens.first!
48
- let screenFrame = screen.visibleFrame
49
- let x = screenFrame.midX - 210
50
- let y = screenFrame.midY - 240 + (screenFrame.height * 0.08)
51
- p.setFrameOrigin(NSPoint(x: x, y: y))
38
+ OverlayPanelShell.position(p, placement: .mouseScreenCentered(yOffsetRatio: 0.08))
52
39
 
53
40
  p.alphaValue = 0
54
- p.orderFrontRegardless()
41
+ OverlayPanelShell.present(p, activate: false, makeKey: false, orderFrontRegardless: true)
55
42
 
56
43
  NSAnimationContext.runAnimationGroup { ctx in
57
44
  ctx.duration = 0.12
@@ -86,7 +73,6 @@ final class LauncherHUD {
86
73
  globalMonitor = NSEvent.addGlobalMonitorForEvents(matching: [.leftMouseDown, .rightMouseDown]) { [weak self] event in
87
74
  // Don't dismiss if clicking inside the panel
88
75
  guard let panel = self?.panel else { return }
89
- let loc = event.locationInWindow
90
76
  if !panel.frame.contains(NSEvent.mouseLocation) {
91
77
  self?.dismiss()
92
78
  }
@@ -115,7 +101,7 @@ struct LauncherView: View {
115
101
  let q = query.lowercased()
116
102
  return scanner.projects.filter {
117
103
  $0.name.lowercased().contains(q) ||
118
- ($0.paneSummary ?? "").lowercased().contains(q)
104
+ $0.paneSummary.lowercased().contains(q)
119
105
  }
120
106
  }
121
107
 
@@ -3,7 +3,10 @@ import SwiftUI
3
3
  struct OmniSearchView: View {
4
4
  @ObservedObject var state: OmniSearchState
5
5
  var onDismiss: () -> Void
6
+ var isEmbedded: Bool = false
6
7
 
8
+ @ObservedObject private var ocrModel = OcrModel.shared
9
+ @State private var expandedOcrWindow: UInt32?
7
10
  @FocusState private var searchFocused: Bool
8
11
 
9
12
  var body: some View {
@@ -46,8 +49,22 @@ struct OmniSearchView: View {
46
49
  resultsView
47
50
  }
48
51
  }
49
- .frame(minWidth: 520, idealWidth: 520, maxWidth: 700, minHeight: 360, idealHeight: 480, maxHeight: 600)
50
- .background(PanelBackground())
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
+ }
51
68
  .preferredColorScheme(.dark)
52
69
  .onAppear {
53
70
  searchFocused = true
@@ -234,6 +251,10 @@ struct OmniSearchView: View {
234
251
  }
235
252
  .padding(.horizontal, 14)
236
253
  }
254
+
255
+ if !recentOcrResults.isEmpty {
256
+ ocrResultsSection
257
+ }
237
258
  } else {
238
259
  Text("Loading...")
239
260
  .font(Typo.mono(11))
@@ -245,6 +266,105 @@ struct OmniSearchView: View {
245
266
  }
246
267
  }
247
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
+
248
368
  private func summarySection<Content: View>(
249
369
  _ title: String,
250
370
  icon: String,
@@ -285,4 +405,18 @@ struct OmniSearchView: View {
285
405
  if seconds < 3600 { return "\(seconds / 60)m ago" }
286
406
  return "\(seconds / 3600)h ago"
287
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
+ }
288
422
  }
@@ -34,39 +34,28 @@ final class OmniSearchWindow {
34
34
  }
35
35
  .preferredColorScheme(.dark)
36
36
 
37
- let hosting = NSHostingController(rootView: view)
38
- hosting.preferredContentSize = NSSize(width: 520, height: 480)
39
-
40
- let p = NSPanel(
41
- contentRect: NSRect(x: 0, y: 0, width: 520, height: 480),
42
- styleMask: [.titled, .closable, .resizable, .utilityWindow, .nonactivatingPanel],
43
- backing: .buffered,
44
- defer: false
37
+ let p = OverlayPanelShell.makePanel(
38
+ config: .init(
39
+ size: NSSize(width: 520, height: 480),
40
+ styleMask: [.titled, .closable, .resizable, .utilityWindow, .nonactivatingPanel],
41
+ title: "Search",
42
+ titleVisible: .hidden,
43
+ titlebarAppearsTransparent: true,
44
+ background: .material(.popover),
45
+ cornerRadius: 14,
46
+ hidesOnDeactivate: false,
47
+ isMovableByWindowBackground: true,
48
+ minSize: NSSize(width: 400, height: 300),
49
+ maxSize: NSSize(width: 700, height: 700),
50
+ activatesOnMouseDown: true,
51
+ appearance: NSAppearance(named: .darkAqua)
52
+ ),
53
+ rootView: view
45
54
  )
46
- p.contentViewController = hosting
47
- p.title = "Omni Search"
48
- p.titlebarAppearsTransparent = true
49
- p.titleVisibility = .hidden
50
- p.isMovableByWindowBackground = true
51
- p.level = .floating
52
- p.isOpaque = false
53
- p.backgroundColor = NSColor(red: 0.11, green: 0.11, blue: 0.12, alpha: 1.0)
54
- p.hasShadow = true
55
- p.appearance = NSAppearance(named: .darkAqua)
56
- p.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary]
57
- p.minSize = NSSize(width: 400, height: 300)
58
- p.maxSize = NSSize(width: 700, height: 700)
59
-
60
- // Center on screen
61
- if let screen = NSScreen.main {
62
- let visibleFrame = screen.visibleFrame
63
- let x = visibleFrame.midX - 260
64
- let y = visibleFrame.midY + 60 // slightly above center
65
- p.setFrameOrigin(NSPoint(x: x, y: y))
66
- }
67
-
68
- p.makeKeyAndOrderFront(nil)
69
- NSApp.activate(ignoringOtherApps: true)
55
+ p.hidesOnDeactivate = false
56
+ p.becomesKeyOnlyIfNeeded = false
57
+ OverlayPanelShell.position(p, placement: .centered(yOffsetRatio: 0.125))
58
+ OverlayPanelShell.present(p)
70
59
  panel = p
71
60
 
72
61
  // Key monitor: Escape → dismiss, arrow keys → navigate, Enter → activate