@lattices/cli 0.4.5 → 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 (130) hide show
  1. package/app/Info.plist +2 -2
  2. package/app/Lattices.app/Contents/Info.plist +2 -2
  3. package/app/Lattices.app/Contents/MacOS/Lattices +0 -0
  4. package/app/Sources/{AppDelegate.swift → AppShell/AppDelegate.swift} +4 -0
  5. package/app/Sources/{AppShellView.swift → AppShell/AppShellView.swift} +10 -1
  6. package/app/Sources/{HotkeyStore.swift → Core/Actions/HotkeyStore.swift} +2 -1
  7. package/app/Sources/{IntentEngine.swift → Core/Actions/IntentEngine.swift} +44 -26
  8. package/app/Sources/Core/Actions/IntentSchema.swift +94 -0
  9. package/app/Sources/{Intents → Core/Actions/Intents}/LatticeIntent.swift +0 -25
  10. package/app/Sources/{VoiceIntentResolver.swift → Core/Actions/VoiceIntentResolver.swift} +46 -4
  11. package/app/Sources/{DesktopModel.swift → Core/Desktop/DesktopModel.swift} +2 -8
  12. package/app/Sources/Core/Desktop/SessionWindowLocator.swift +139 -0
  13. package/app/Sources/Core/Desktop/WindowPreviewCard.swift +100 -0
  14. package/app/Sources/Core/Desktop/WindowPreviewStore.swift +113 -0
  15. package/app/Sources/Core/Desktop/WindowSelectionStore.swift +76 -0
  16. package/app/Sources/{WindowTiler.swift → Core/Desktop/WindowTiler.swift} +24 -108
  17. package/app/Sources/{CommandModeState.swift → Core/Overlays/CommandMode/CommandModeState.swift} +127 -24
  18. package/app/Sources/{CommandModeView.swift → Core/Overlays/CommandMode/CommandModeView.swift} +488 -55
  19. package/app/Sources/Core/Overlays/CommandPalette/CommandPaletteWindow.swift +67 -0
  20. package/app/Sources/{HUDRightBar.swift → Core/Overlays/HUD/HUDRightBar.swift} +23 -201
  21. package/app/Sources/{LauncherHUD.swift → Core/Overlays/HUD/LauncherHUD.swift} +12 -26
  22. package/app/Sources/Core/Overlays/OmniSearch/OmniSearchWindow.swift +94 -0
  23. package/app/Sources/Core/Overlays/OverlayPanelShell.swift +241 -0
  24. package/app/Sources/{ScreenMapState.swift → Core/Overlays/ScreenMap/ScreenMapState.swift} +25 -1
  25. package/app/Sources/{VoiceCommandWindow.swift → Core/Overlays/Voice/VoiceCommandWindow.swift} +46 -74
  26. package/docs/component-extraction-roadmap.md +392 -0
  27. package/package.json +3 -1
  28. package/app/Sources/CommandPaletteWindow.swift +0 -134
  29. package/app/Sources/OmniSearchWindow.swift +0 -165
  30. /package/app/Sources/{App.swift → AppShell/App.swift} +0 -0
  31. /package/app/Sources/{AppUpdater.swift → AppShell/AppUpdater.swift} +0 -0
  32. /package/app/Sources/{CliActionLauncher.swift → AppShell/CliActionLauncher.swift} +0 -0
  33. /package/app/Sources/{HomeDashboardView.swift → AppShell/HomeDashboardView.swift} +0 -0
  34. /package/app/Sources/{KeyRecorderView.swift → AppShell/KeyRecorderView.swift} +0 -0
  35. /package/app/Sources/{LatticesRuntime.swift → AppShell/LatticesRuntime.swift} +0 -0
  36. /package/app/Sources/{MainView.swift → AppShell/MainView.swift} +0 -0
  37. /package/app/Sources/{MainWindow.swift → AppShell/MainWindow.swift} +0 -0
  38. /package/app/Sources/{OnboardingView.swift → AppShell/OnboardingView.swift} +0 -0
  39. /package/app/Sources/{Preferences.swift → AppShell/Preferences.swift} +0 -0
  40. /package/app/Sources/{SettingsView.swift → AppShell/SettingsView.swift} +0 -0
  41. /package/app/Sources/{SettingsWindow.swift → AppShell/SettingsWindow.swift} +0 -0
  42. /package/app/Sources/{HotkeyManager.swift → Core/Actions/HotkeyManager.swift} +0 -0
  43. /package/app/Sources/{Intents → Core/Actions/Intents}/CreateLayerIntent.swift +0 -0
  44. /package/app/Sources/{Intents → Core/Actions/Intents}/DistributeIntent.swift +0 -0
  45. /package/app/Sources/{Intents → Core/Actions/Intents}/FocusIntent.swift +0 -0
  46. /package/app/Sources/{Intents → Core/Actions/Intents}/HelpIntent.swift +0 -0
  47. /package/app/Sources/{Intents → Core/Actions/Intents}/KillIntent.swift +0 -0
  48. /package/app/Sources/{Intents → Core/Actions/Intents}/LaunchIntent.swift +0 -0
  49. /package/app/Sources/{Intents → Core/Actions/Intents}/ListSessionsIntent.swift +0 -0
  50. /package/app/Sources/{Intents → Core/Actions/Intents}/ListWindowsIntent.swift +0 -0
  51. /package/app/Sources/{Intents → Core/Actions/Intents}/ScanIntent.swift +0 -0
  52. /package/app/Sources/{Intents → Core/Actions/Intents}/SearchIntent.swift +0 -0
  53. /package/app/Sources/{Intents → Core/Actions/Intents}/SwitchLayerIntent.swift +0 -0
  54. /package/app/Sources/{Intents → Core/Actions/Intents}/TileIntent.swift +0 -0
  55. /package/app/Sources/{PaletteCommand.swift → Core/Actions/PaletteCommand.swift} +0 -0
  56. /package/app/Sources/{CompanionActivityLog.swift → Core/Companion/CompanionActivityLog.swift} +0 -0
  57. /package/app/Sources/{CompanionKeyboardController.swift → Core/Companion/CompanionKeyboardController.swift} +0 -0
  58. /package/app/Sources/{LatticesCompanionBridgeServer.swift → Core/Companion/LatticesCompanionBridgeServer.swift} +0 -0
  59. /package/app/Sources/{LatticesCompanionCockpit.swift → Core/Companion/LatticesCompanionCockpit.swift} +0 -0
  60. /package/app/Sources/{LatticesCompanionSecurityCoordinator.swift → Core/Companion/LatticesCompanionSecurityCoordinator.swift} +0 -0
  61. /package/app/Sources/{LatticesCompanionTrackpadController.swift → Core/Companion/LatticesCompanionTrackpadController.swift} +0 -0
  62. /package/app/Sources/{LatticesDeckHost.swift → Core/Companion/LatticesDeckHost.swift} +0 -0
  63. /package/app/Sources/{DaemonProtocol.swift → Core/Daemon/DaemonProtocol.swift} +0 -0
  64. /package/app/Sources/{DaemonServer.swift → Core/Daemon/DaemonServer.swift} +0 -0
  65. /package/app/Sources/{LatticesApi.swift → Core/Daemon/LatticesApi.swift} +0 -0
  66. /package/app/Sources/{AccessibilityTextExtractor.swift → Core/Desktop/AccessibilityTextExtractor.swift} +0 -0
  67. /package/app/Sources/{AppTypeClassifier.swift → Core/Desktop/AppTypeClassifier.swift} +0 -0
  68. /package/app/Sources/{DesktopModelTypes.swift → Core/Desktop/DesktopModelTypes.swift} +0 -0
  69. /package/app/Sources/{InventoryManager.swift → Core/Desktop/InventoryManager.swift} +0 -0
  70. /package/app/Sources/{InventoryPath.swift → Core/Desktop/InventoryPath.swift} +0 -0
  71. /package/app/Sources/{MouseFinder.swift → Core/Desktop/MouseFinder.swift} +0 -0
  72. /package/app/Sources/{OcrModel.swift → Core/Desktop/OcrModel.swift} +0 -0
  73. /package/app/Sources/{OcrStore.swift → Core/Desktop/OcrStore.swift} +0 -0
  74. /package/app/Sources/{PlacementSpec.swift → Core/Desktop/PlacementSpec.swift} +0 -0
  75. /package/app/Sources/{TilePickerView.swift → Core/Desktop/TilePickerView.swift} +0 -0
  76. /package/app/Sources/{WindowDragSnapController.swift → Core/Desktop/WindowDragSnapController.swift} +0 -0
  77. /package/app/Sources/{MouseGestureConfig.swift → Core/Input/MouseGestureConfig.swift} +0 -0
  78. /package/app/Sources/{MouseGestureController.swift → Core/Input/MouseGestureController.swift} +0 -0
  79. /package/app/Sources/{MouseInputDeviceStore.swift → Core/Input/MouseInputDeviceStore.swift} +0 -0
  80. /package/app/Sources/{MouseInputEventViewer.swift → Core/Input/MouseInputEventViewer.swift} +0 -0
  81. /package/app/Sources/{MouseShortcutStore.swift → Core/Input/MouseShortcutStore.swift} +0 -0
  82. /package/app/Sources/{AppWindowShell.swift → Core/Overlays/AppWindowShell.swift} +0 -0
  83. /package/app/Sources/{CommandModeWindow.swift → Core/Overlays/CommandMode/CommandModeWindow.swift} +0 -0
  84. /package/app/Sources/{CommandPaletteView.swift → Core/Overlays/CommandPalette/CommandPaletteView.swift} +0 -0
  85. /package/app/Sources/{CheatSheetHUD.swift → Core/Overlays/HUD/CheatSheetHUD.swift} +0 -0
  86. /package/app/Sources/{HUDBottomBar.swift → Core/Overlays/HUD/HUDBottomBar.swift} +0 -0
  87. /package/app/Sources/{HUDController.swift → Core/Overlays/HUD/HUDController.swift} +0 -0
  88. /package/app/Sources/{HUDLeftBar.swift → Core/Overlays/HUD/HUDLeftBar.swift} +0 -0
  89. /package/app/Sources/{HUDMinimap.swift → Core/Overlays/HUD/HUDMinimap.swift} +0 -0
  90. /package/app/Sources/{HUDState.swift → Core/Overlays/HUD/HUDState.swift} +0 -0
  91. /package/app/Sources/{HUDTopBar.swift → Core/Overlays/HUD/HUDTopBar.swift} +0 -0
  92. /package/app/Sources/{LayerBezel.swift → Core/Overlays/HUD/LayerBezel.swift} +0 -0
  93. /package/app/Sources/{OmniSearchState.swift → Core/Overlays/OmniSearch/OmniSearchState.swift} +0 -0
  94. /package/app/Sources/{OmniSearchView.swift → Core/Overlays/OmniSearch/OmniSearchView.swift} +0 -0
  95. /package/app/Sources/{ScreenMapView.swift → Core/Overlays/ScreenMap/ScreenMapView.swift} +0 -0
  96. /package/app/Sources/{ScreenMapWindowController.swift → Core/Overlays/ScreenMap/ScreenMapWindowController.swift} +0 -0
  97. /package/app/Sources/{PiAuthNextStepCard.swift → Core/Pi/PiAuthNextStepCard.swift} +0 -0
  98. /package/app/Sources/{PiAuthPromptCard.swift → Core/Pi/PiAuthPromptCard.swift} +0 -0
  99. /package/app/Sources/{PiChatDock.swift → Core/Pi/PiChatDock.swift} +0 -0
  100. /package/app/Sources/{PiChatSession.swift → Core/Pi/PiChatSession.swift} +0 -0
  101. /package/app/Sources/{PiInstallCallout.swift → Core/Pi/PiInstallCallout.swift} +0 -0
  102. /package/app/Sources/{PiProviderSetupCallout.swift → Core/Pi/PiProviderSetupCallout.swift} +0 -0
  103. /package/app/Sources/{PiWorkspaceView.swift → Core/Pi/PiWorkspaceView.swift} +0 -0
  104. /package/app/Sources/{DiagnosticLog.swift → Core/System/DiagnosticLog.swift} +0 -0
  105. /package/app/Sources/{EventBus.swift → Core/System/EventBus.swift} +0 -0
  106. /package/app/Sources/{PermissionChecker.swift → Core/System/PermissionChecker.swift} +0 -0
  107. /package/app/Sources/{ProcessModel.swift → Core/System/ProcessModel.swift} +0 -0
  108. /package/app/Sources/{ProcessQuery.swift → Core/System/ProcessQuery.swift} +0 -0
  109. /package/app/Sources/{SystemTelemetryMonitor.swift → Core/System/SystemTelemetryMonitor.swift} +0 -0
  110. /package/app/Sources/{AdvisorLearningStore.swift → Core/Voice/AdvisorLearningStore.swift} +0 -0
  111. /package/app/Sources/{AgentSession.swift → Core/Voice/AgentSession.swift} +0 -0
  112. /package/app/Sources/{AudioProvider.swift → Core/Voice/AudioProvider.swift} +0 -0
  113. /package/app/Sources/{HandsOffSession.swift → Core/Voice/HandsOffSession.swift} +0 -0
  114. /package/app/Sources/{VoiceChatView.swift → Core/Voice/VoiceChatView.swift} +0 -0
  115. /package/app/Sources/{VoxClient.swift → Core/Voice/VoxClient.swift} +0 -0
  116. /package/app/Sources/{Project.swift → Core/Workspace/Project.swift} +0 -0
  117. /package/app/Sources/{ProjectScanner.swift → Core/Workspace/ProjectScanner.swift} +0 -0
  118. /package/app/Sources/{SessionLayerStore.swift → Core/Workspace/SessionLayerStore.swift} +0 -0
  119. /package/app/Sources/{SessionManager.swift → Core/Workspace/SessionManager.swift} +0 -0
  120. /package/app/Sources/{Terminal.swift → Core/Workspace/Terminal/Terminal.swift} +0 -0
  121. /package/app/Sources/{TerminalQuery.swift → Core/Workspace/Terminal/TerminalQuery.swift} +0 -0
  122. /package/app/Sources/{TerminalSynthesizer.swift → Core/Workspace/Terminal/TerminalSynthesizer.swift} +0 -0
  123. /package/app/Sources/{TmuxModel.swift → Core/Workspace/Tmux/TmuxModel.swift} +0 -0
  124. /package/app/Sources/{TmuxQuery.swift → Core/Workspace/Tmux/TmuxQuery.swift} +0 -0
  125. /package/app/Sources/{WorkspaceManager.swift → Core/Workspace/WorkspaceManager.swift} +0 -0
  126. /package/app/Sources/{ActionRow.swift → UI/ActionRow.swift} +0 -0
  127. /package/app/Sources/{OrphanRow.swift → UI/OrphanRow.swift} +0 -0
  128. /package/app/Sources/{ProjectRow.swift → UI/ProjectRow.swift} +0 -0
  129. /package/app/Sources/{TabGroupRow.swift → UI/TabGroupRow.swift} +0 -0
  130. /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
+ }
@@ -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
 
@@ -0,0 +1,94 @@
1
+ import AppKit
2
+ import SwiftUI
3
+
4
+ final class OmniSearchWindow {
5
+ static let shared = OmniSearchWindow()
6
+
7
+ private var panel: NSPanel?
8
+ private var keyMonitor: Any?
9
+ private var state: OmniSearchState?
10
+
11
+ var isVisible: Bool { panel?.isVisible ?? false }
12
+
13
+ func toggle() {
14
+ if isVisible {
15
+ dismiss()
16
+ } else {
17
+ show()
18
+ }
19
+ }
20
+
21
+ func show() {
22
+ if let p = panel, p.isVisible {
23
+ p.makeKeyAndOrderFront(nil)
24
+ NSApp.activate(ignoringOtherApps: true)
25
+ return
26
+ }
27
+
28
+ // Fresh state each time
29
+ let searchState = OmniSearchState()
30
+ state = searchState
31
+
32
+ let view = OmniSearchView(state: searchState) { [weak self] in
33
+ self?.dismiss()
34
+ }
35
+ .preferredColorScheme(.dark)
36
+
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
54
+ )
55
+ p.hidesOnDeactivate = false
56
+ p.becomesKeyOnlyIfNeeded = false
57
+ OverlayPanelShell.position(p, placement: .centered(yOffsetRatio: 0.125))
58
+ OverlayPanelShell.present(p)
59
+ panel = p
60
+
61
+ // Key monitor: Escape → dismiss, arrow keys → navigate, Enter → activate
62
+ keyMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { [weak self] event in
63
+ guard self?.panel?.isKeyWindow == true else { return event }
64
+
65
+ switch event.keyCode {
66
+ case 53: // Escape
67
+ self?.dismiss()
68
+ return nil
69
+ case 125: // ↓
70
+ self?.state?.moveSelection(1)
71
+ return nil
72
+ case 126: // ↑
73
+ self?.state?.moveSelection(-1)
74
+ return nil
75
+ case 36: // Enter
76
+ self?.state?.activateSelected()
77
+ self?.dismiss()
78
+ return nil
79
+ default:
80
+ return event
81
+ }
82
+ }
83
+ }
84
+
85
+ func dismiss() {
86
+ panel?.orderOut(nil)
87
+ panel = nil
88
+ state = nil
89
+ if let monitor = keyMonitor {
90
+ NSEvent.removeMonitor(monitor)
91
+ keyMonitor = nil
92
+ }
93
+ }
94
+ }