@lattices/cli 0.4.5 → 0.4.7

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} +9 -0
  5. package/app/Sources/{AppShellView.swift → AppShell/AppShellView.swift} +10 -1
  6. package/app/Sources/{KeyRecorderView.swift → AppShell/KeyRecorderView.swift} +1 -1
  7. package/app/Sources/{Preferences.swift → AppShell/Preferences.swift} +0 -2
  8. package/app/Sources/{SettingsView.swift → AppShell/SettingsView.swift} +27 -2
  9. package/app/Sources/{HotkeyStore.swift → Core/Actions/HotkeyStore.swift} +15 -2
  10. package/app/Sources/{IntentEngine.swift → Core/Actions/IntentEngine.swift} +44 -26
  11. package/app/Sources/Core/Actions/IntentSchema.swift +94 -0
  12. package/app/Sources/{Intents → Core/Actions/Intents}/LatticeIntent.swift +0 -25
  13. package/app/Sources/{VoiceIntentResolver.swift → Core/Actions/VoiceIntentResolver.swift} +46 -4
  14. package/app/Sources/{DesktopModel.swift → Core/Desktop/DesktopModel.swift} +2 -8
  15. package/app/Sources/Core/Desktop/SessionWindowLocator.swift +139 -0
  16. package/app/Sources/Core/Desktop/WindowPreviewCard.swift +100 -0
  17. package/app/Sources/Core/Desktop/WindowPreviewStore.swift +113 -0
  18. package/app/Sources/Core/Desktop/WindowSelectionStore.swift +76 -0
  19. package/app/Sources/{WindowTiler.swift → Core/Desktop/WindowTiler.swift} +24 -110
  20. package/app/Sources/{CommandModeState.swift → Core/Overlays/CommandMode/CommandModeState.swift} +228 -24
  21. package/app/Sources/{CommandModeView.swift → Core/Overlays/CommandMode/CommandModeView.swift} +601 -59
  22. package/app/Sources/{CommandModeWindow.swift → Core/Overlays/CommandMode/CommandModeWindow.swift} +9 -5
  23. package/app/Sources/Core/Overlays/CommandPalette/CommandPaletteWindow.swift +67 -0
  24. package/app/Sources/{CheatSheetHUD.swift → Core/Overlays/HUD/CheatSheetHUD.swift} +1 -0
  25. package/app/Sources/{HUDRightBar.swift → Core/Overlays/HUD/HUDRightBar.swift} +23 -201
  26. package/app/Sources/{LauncherHUD.swift → Core/Overlays/HUD/LauncherHUD.swift} +12 -26
  27. package/app/Sources/Core/Overlays/OmniSearch/OmniSearchWindow.swift +94 -0
  28. package/app/Sources/Core/Overlays/OverlayPanelShell.swift +241 -0
  29. package/app/Sources/{ScreenMapState.swift → Core/Overlays/ScreenMap/ScreenMapState.swift} +25 -2
  30. package/app/Sources/{ScreenMapView.swift → Core/Overlays/ScreenMap/ScreenMapView.swift} +20 -7
  31. package/app/Sources/{VoiceCommandWindow.swift → Core/Overlays/Voice/VoiceCommandWindow.swift} +46 -74
  32. package/app/Sources/{WorkspaceManager.swift → Core/Workspace/WorkspaceManager.swift} +59 -4
  33. package/docs/component-extraction-roadmap.md +392 -0
  34. package/package.json +3 -1
  35. package/app/Sources/CommandPaletteWindow.swift +0 -134
  36. package/app/Sources/OmniSearchWindow.swift +0 -165
  37. /package/app/Sources/{App.swift → AppShell/App.swift} +0 -0
  38. /package/app/Sources/{AppUpdater.swift → AppShell/AppUpdater.swift} +0 -0
  39. /package/app/Sources/{CliActionLauncher.swift → AppShell/CliActionLauncher.swift} +0 -0
  40. /package/app/Sources/{HomeDashboardView.swift → AppShell/HomeDashboardView.swift} +0 -0
  41. /package/app/Sources/{LatticesRuntime.swift → AppShell/LatticesRuntime.swift} +0 -0
  42. /package/app/Sources/{MainView.swift → AppShell/MainView.swift} +0 -0
  43. /package/app/Sources/{MainWindow.swift → AppShell/MainWindow.swift} +0 -0
  44. /package/app/Sources/{OnboardingView.swift → AppShell/OnboardingView.swift} +0 -0
  45. /package/app/Sources/{SettingsWindow.swift → AppShell/SettingsWindow.swift} +0 -0
  46. /package/app/Sources/{HotkeyManager.swift → Core/Actions/HotkeyManager.swift} +0 -0
  47. /package/app/Sources/{Intents → Core/Actions/Intents}/CreateLayerIntent.swift +0 -0
  48. /package/app/Sources/{Intents → Core/Actions/Intents}/DistributeIntent.swift +0 -0
  49. /package/app/Sources/{Intents → Core/Actions/Intents}/FocusIntent.swift +0 -0
  50. /package/app/Sources/{Intents → Core/Actions/Intents}/HelpIntent.swift +0 -0
  51. /package/app/Sources/{Intents → Core/Actions/Intents}/KillIntent.swift +0 -0
  52. /package/app/Sources/{Intents → Core/Actions/Intents}/LaunchIntent.swift +0 -0
  53. /package/app/Sources/{Intents → Core/Actions/Intents}/ListSessionsIntent.swift +0 -0
  54. /package/app/Sources/{Intents → Core/Actions/Intents}/ListWindowsIntent.swift +0 -0
  55. /package/app/Sources/{Intents → Core/Actions/Intents}/ScanIntent.swift +0 -0
  56. /package/app/Sources/{Intents → Core/Actions/Intents}/SearchIntent.swift +0 -0
  57. /package/app/Sources/{Intents → Core/Actions/Intents}/SwitchLayerIntent.swift +0 -0
  58. /package/app/Sources/{Intents → Core/Actions/Intents}/TileIntent.swift +0 -0
  59. /package/app/Sources/{PaletteCommand.swift → Core/Actions/PaletteCommand.swift} +0 -0
  60. /package/app/Sources/{CompanionActivityLog.swift → Core/Companion/CompanionActivityLog.swift} +0 -0
  61. /package/app/Sources/{CompanionKeyboardController.swift → Core/Companion/CompanionKeyboardController.swift} +0 -0
  62. /package/app/Sources/{LatticesCompanionBridgeServer.swift → Core/Companion/LatticesCompanionBridgeServer.swift} +0 -0
  63. /package/app/Sources/{LatticesCompanionCockpit.swift → Core/Companion/LatticesCompanionCockpit.swift} +0 -0
  64. /package/app/Sources/{LatticesCompanionSecurityCoordinator.swift → Core/Companion/LatticesCompanionSecurityCoordinator.swift} +0 -0
  65. /package/app/Sources/{LatticesCompanionTrackpadController.swift → Core/Companion/LatticesCompanionTrackpadController.swift} +0 -0
  66. /package/app/Sources/{LatticesDeckHost.swift → Core/Companion/LatticesDeckHost.swift} +0 -0
  67. /package/app/Sources/{DaemonProtocol.swift → Core/Daemon/DaemonProtocol.swift} +0 -0
  68. /package/app/Sources/{DaemonServer.swift → Core/Daemon/DaemonServer.swift} +0 -0
  69. /package/app/Sources/{LatticesApi.swift → Core/Daemon/LatticesApi.swift} +0 -0
  70. /package/app/Sources/{AccessibilityTextExtractor.swift → Core/Desktop/AccessibilityTextExtractor.swift} +0 -0
  71. /package/app/Sources/{AppTypeClassifier.swift → Core/Desktop/AppTypeClassifier.swift} +0 -0
  72. /package/app/Sources/{DesktopModelTypes.swift → Core/Desktop/DesktopModelTypes.swift} +0 -0
  73. /package/app/Sources/{InventoryManager.swift → Core/Desktop/InventoryManager.swift} +0 -0
  74. /package/app/Sources/{InventoryPath.swift → Core/Desktop/InventoryPath.swift} +0 -0
  75. /package/app/Sources/{MouseFinder.swift → Core/Desktop/MouseFinder.swift} +0 -0
  76. /package/app/Sources/{OcrModel.swift → Core/Desktop/OcrModel.swift} +0 -0
  77. /package/app/Sources/{OcrStore.swift → Core/Desktop/OcrStore.swift} +0 -0
  78. /package/app/Sources/{PlacementSpec.swift → Core/Desktop/PlacementSpec.swift} +0 -0
  79. /package/app/Sources/{TilePickerView.swift → Core/Desktop/TilePickerView.swift} +0 -0
  80. /package/app/Sources/{WindowDragSnapController.swift → Core/Desktop/WindowDragSnapController.swift} +0 -0
  81. /package/app/Sources/{MouseGestureConfig.swift → Core/Input/MouseGestureConfig.swift} +0 -0
  82. /package/app/Sources/{MouseGestureController.swift → Core/Input/MouseGestureController.swift} +0 -0
  83. /package/app/Sources/{MouseInputDeviceStore.swift → Core/Input/MouseInputDeviceStore.swift} +0 -0
  84. /package/app/Sources/{MouseInputEventViewer.swift → Core/Input/MouseInputEventViewer.swift} +0 -0
  85. /package/app/Sources/{MouseShortcutStore.swift → Core/Input/MouseShortcutStore.swift} +0 -0
  86. /package/app/Sources/{AppWindowShell.swift → Core/Overlays/AppWindowShell.swift} +0 -0
  87. /package/app/Sources/{CommandPaletteView.swift → Core/Overlays/CommandPalette/CommandPaletteView.swift} +0 -0
  88. /package/app/Sources/{HUDBottomBar.swift → Core/Overlays/HUD/HUDBottomBar.swift} +0 -0
  89. /package/app/Sources/{HUDController.swift → Core/Overlays/HUD/HUDController.swift} +0 -0
  90. /package/app/Sources/{HUDLeftBar.swift → Core/Overlays/HUD/HUDLeftBar.swift} +0 -0
  91. /package/app/Sources/{HUDMinimap.swift → Core/Overlays/HUD/HUDMinimap.swift} +0 -0
  92. /package/app/Sources/{HUDState.swift → Core/Overlays/HUD/HUDState.swift} +0 -0
  93. /package/app/Sources/{HUDTopBar.swift → Core/Overlays/HUD/HUDTopBar.swift} +0 -0
  94. /package/app/Sources/{LayerBezel.swift → Core/Overlays/HUD/LayerBezel.swift} +0 -0
  95. /package/app/Sources/{OmniSearchState.swift → Core/Overlays/OmniSearch/OmniSearchState.swift} +0 -0
  96. /package/app/Sources/{OmniSearchView.swift → Core/Overlays/OmniSearch/OmniSearchView.swift} +0 -0
  97. /package/app/Sources/{ScreenMapWindowController.swift → Core/Overlays/ScreenMap/ScreenMapWindowController.swift} +0 -0
  98. /package/app/Sources/{PiAuthNextStepCard.swift → Core/Pi/PiAuthNextStepCard.swift} +0 -0
  99. /package/app/Sources/{PiAuthPromptCard.swift → Core/Pi/PiAuthPromptCard.swift} +0 -0
  100. /package/app/Sources/{PiChatDock.swift → Core/Pi/PiChatDock.swift} +0 -0
  101. /package/app/Sources/{PiChatSession.swift → Core/Pi/PiChatSession.swift} +0 -0
  102. /package/app/Sources/{PiInstallCallout.swift → Core/Pi/PiInstallCallout.swift} +0 -0
  103. /package/app/Sources/{PiProviderSetupCallout.swift → Core/Pi/PiProviderSetupCallout.swift} +0 -0
  104. /package/app/Sources/{PiWorkspaceView.swift → Core/Pi/PiWorkspaceView.swift} +0 -0
  105. /package/app/Sources/{DiagnosticLog.swift → Core/System/DiagnosticLog.swift} +0 -0
  106. /package/app/Sources/{EventBus.swift → Core/System/EventBus.swift} +0 -0
  107. /package/app/Sources/{PermissionChecker.swift → Core/System/PermissionChecker.swift} +0 -0
  108. /package/app/Sources/{ProcessModel.swift → Core/System/ProcessModel.swift} +0 -0
  109. /package/app/Sources/{ProcessQuery.swift → Core/System/ProcessQuery.swift} +0 -0
  110. /package/app/Sources/{SystemTelemetryMonitor.swift → Core/System/SystemTelemetryMonitor.swift} +0 -0
  111. /package/app/Sources/{AdvisorLearningStore.swift → Core/Voice/AdvisorLearningStore.swift} +0 -0
  112. /package/app/Sources/{AgentSession.swift → Core/Voice/AgentSession.swift} +0 -0
  113. /package/app/Sources/{AudioProvider.swift → Core/Voice/AudioProvider.swift} +0 -0
  114. /package/app/Sources/{HandsOffSession.swift → Core/Voice/HandsOffSession.swift} +0 -0
  115. /package/app/Sources/{VoiceChatView.swift → Core/Voice/VoiceChatView.swift} +0 -0
  116. /package/app/Sources/{VoxClient.swift → Core/Voice/VoxClient.swift} +0 -0
  117. /package/app/Sources/{Project.swift → Core/Workspace/Project.swift} +0 -0
  118. /package/app/Sources/{ProjectScanner.swift → Core/Workspace/ProjectScanner.swift} +0 -0
  119. /package/app/Sources/{SessionLayerStore.swift → Core/Workspace/SessionLayerStore.swift} +0 -0
  120. /package/app/Sources/{SessionManager.swift → Core/Workspace/SessionManager.swift} +0 -0
  121. /package/app/Sources/{Terminal.swift → Core/Workspace/Terminal/Terminal.swift} +0 -0
  122. /package/app/Sources/{TerminalQuery.swift → Core/Workspace/Terminal/TerminalQuery.swift} +0 -0
  123. /package/app/Sources/{TerminalSynthesizer.swift → Core/Workspace/Terminal/TerminalSynthesizer.swift} +0 -0
  124. /package/app/Sources/{TmuxModel.swift → Core/Workspace/Tmux/TmuxModel.swift} +0 -0
  125. /package/app/Sources/{TmuxQuery.swift → Core/Workspace/Tmux/TmuxQuery.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
package/app/Info.plist CHANGED
@@ -15,9 +15,9 @@
15
15
  <key>CFBundlePackageType</key>
16
16
  <string>APPL</string>
17
17
  <key>CFBundleVersion</key>
18
- <string>0.4.5</string>
18
+ <string>0.4.7</string>
19
19
  <key>CFBundleShortVersionString</key>
20
- <string>0.4.5</string>
20
+ <string>0.4.7</string>
21
21
  <key>LSMinimumSystemVersion</key>
22
22
  <string>13.0</string>
23
23
  <key>LSUIElement</key>
@@ -15,9 +15,9 @@
15
15
  <key>CFBundlePackageType</key>
16
16
  <string>APPL</string>
17
17
  <key>CFBundleVersion</key>
18
- <string>0.4.5</string>
18
+ <string>0.4.7</string>
19
19
  <key>CFBundleShortVersionString</key>
20
- <string>0.4.5</string>
20
+ <string>0.4.7</string>
21
21
  <key>LSMinimumSystemVersion</key>
22
22
  <string>13.0</string>
23
23
  <key>LSUIElement</key>
@@ -91,6 +91,10 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
91
91
  store.register(action: .unifiedWindow) { ScreenMapWindowController.shared.toggle() }
92
92
  store.register(action: .bezel) { Self.showWorkspaceInspector() }
93
93
  store.register(action: .cheatSheet) { SettingsWindowController.shared.show() }
94
+ store.register(action: .desktopInventory) {
95
+ DiagnosticLog.shared.info("Hotkey: desktopInventory triggered")
96
+ ScreenMapWindowController.shared.showPage(.desktopInventory)
97
+ }
94
98
  store.register(action: .voiceCommand) {
95
99
  DiagnosticLog.shared.info("Hotkey: voiceCommand triggered")
96
100
  VoiceCommandWindow.shared.toggle()
@@ -158,6 +162,11 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
158
162
  }
159
163
  store.register(action: .tileDistribute) { WindowTiler.distributeVisible(reactivateLattices: false) }
160
164
  store.register(action: .tileTypeGrid) { WindowTiler.distributeVisibleByFrontmostType(reactivateLattices: false) }
165
+ store.register(action: .tileOrganize) {
166
+ let appName = DesktopModel.shared.frontmostWindow()?.app
167
+ ?? NSWorkspace.shared.frontmostApplication?.localizedName
168
+ CommandModeWindow.shared.show(launchMode: .organize(appName: appName))
169
+ }
161
170
 
162
171
  // Onboarding on first launch; otherwise just check permissions
163
172
  if !OnboardingWindowController.shared.showIfNeeded() {
@@ -56,6 +56,10 @@ struct AppShellView: View {
56
56
  .background(Palette.bg)
57
57
  .onAppear {
58
58
  commandState.onDismiss = { windowController.activePage = .home }
59
+ syncPageState(windowController.activePage)
60
+ }
61
+ .onChange(of: windowController.activePage) { page in
62
+ syncPageState(page)
59
63
  }
60
64
  }
61
65
 
@@ -116,7 +120,7 @@ struct AppShellView: View {
116
120
  windowController.activePage = page
117
121
  })
118
122
  case .desktopInventory:
119
- CommandModeView(state: commandState)
123
+ CommandModeView(state: commandState, presentation: .embedded)
120
124
  case .pi:
121
125
  PiWorkspaceView()
122
126
  case .settings:
@@ -134,4 +138,9 @@ struct AppShellView: View {
134
138
  )
135
139
  }
136
140
  }
141
+
142
+ private func syncPageState(_ page: AppPage) {
143
+ if page == .screenMap { controller.enter() }
144
+ if page == .desktopInventory { commandState.enter() }
145
+ }
137
146
  }
@@ -34,7 +34,7 @@ struct KeyRecorderView: View {
34
34
  .frame(minWidth: 80, alignment: .leading)
35
35
  } else if let binding = binding {
36
36
  HStack(spacing: 4) {
37
- ForEach(binding.displayParts, id: \.self) { part in
37
+ ForEach(binding.compactDisplayParts, id: \.self) { part in
38
38
  keyBadge(part)
39
39
  }
40
40
  }
@@ -37,7 +37,6 @@ class Preferences: ObservableObject {
37
37
  @Published var companionCockpitLayout: LatticesCompanionCockpitLayout {
38
38
  didSet { persistCompanionCockpitLayout() }
39
39
  }
40
-
41
40
  @Published var mouseGesturesEnabled: Bool {
42
41
  didSet { UserDefaults.standard.set(mouseGesturesEnabled, forKey: "mouseGestures.enabled") }
43
42
  }
@@ -163,7 +162,6 @@ class Preferences: ObservableObject {
163
162
  }
164
163
 
165
164
  self.companionCockpitLayout = Self.loadCompanionCockpitLayout()
166
-
167
165
  if UserDefaults.standard.object(forKey: "mouseGestures.enabled") != nil {
168
166
  self.mouseGesturesEnabled = UserDefaults.standard.bool(forKey: "mouseGestures.enabled")
169
167
  } else {
@@ -57,6 +57,7 @@ struct SettingsContentView: View {
57
57
  @ObservedObject var prefs: Preferences
58
58
  @ObservedObject var scanner: ProjectScanner
59
59
  @ObservedObject var hotkeyStore: HotkeyStore = .shared
60
+ @ObservedObject var workspaceManager: WorkspaceManager = .shared
60
61
  @ObservedObject var appUpdater: AppUpdater = .shared
61
62
  @ObservedObject var mouseShortcutStore: MouseShortcutStore = .shared
62
63
  var onBack: (() -> Void)? = nil
@@ -85,6 +86,13 @@ struct SettingsContentView: View {
85
86
  page == .docs ? "Docs" : selectedTab.title
86
87
  }
87
88
 
89
+ private var snapModifierBinding: Binding<SnapModifierKey> {
90
+ Binding(
91
+ get: { workspaceManager.snapZonesConfig.modifier ?? .command },
92
+ set: { workspaceManager.updateSnapModifier($0) }
93
+ )
94
+ }
95
+
88
96
  private var backBar: some View {
89
97
  VStack(spacing: 0) {
90
98
  HStack(spacing: 8) {
@@ -459,13 +467,28 @@ struct SettingsContentView: View {
459
467
  .labelsHidden()
460
468
  }
461
469
 
462
- Text("Hold the configured snap modifier while dragging to reveal landing targets and a live preview, then release it to go back to a free drag. Default: Command.")
470
+ HStack {
471
+ Text("Snap modifier")
472
+ .font(Typo.mono(10))
473
+ .foregroundColor(Palette.textDim)
474
+ Spacer()
475
+ Picker("", selection: snapModifierBinding) {
476
+ ForEach(SnapModifierKey.allCases) { modifier in
477
+ Text(modifier.shortLabel).tag(modifier)
478
+ }
479
+ }
480
+ .pickerStyle(.segmented)
481
+ .labelsHidden()
482
+ .frame(width: 220)
483
+ }
484
+
485
+ Text("Dragging stays normal until you hold \(snapModifierBinding.wrappedValue.label). While that key is down, Lattices reveals snap targets and a live preview for the window you’re moving.")
463
486
  .font(Typo.caption(9))
464
487
  .foregroundColor(Palette.textMuted.opacity(0.7))
465
488
 
466
489
  cardDivider
467
490
 
468
- Text("Agent-editable rules live in ~/.lattices/snap-zones.json. Changes are picked up on the next drag.")
491
+ Text("Advanced landing-zone rules still live in ~/.lattices/snap-zones.json. Modifier changes here take effect on the next drag.")
469
492
  .font(Typo.caption(9))
470
493
  .foregroundColor(Palette.textMuted.opacity(0.7))
471
494
  }
@@ -1541,6 +1564,8 @@ struct SettingsContentView: View {
1541
1564
  .foregroundColor(Palette.textMuted)
1542
1565
  .fixedSize(horizontal: false, vertical: true)
1543
1566
  }
1567
+
1568
+ compactKeyRecorder(action: .tileOrganize)
1544
1569
  }
1545
1570
  }
1546
1571
 
@@ -31,7 +31,7 @@ enum HotkeyAction: String, CaseIterable, Codable {
31
31
  // Tiling
32
32
  case tileLeft, tileRight, tileMaximize, tileCenter
33
33
  case tileTopLeft, tileTopRight, tileBottomLeft, tileBottomRight
34
- case tileTop, tileBottom, tileDistribute, tileTypeGrid
34
+ case tileTop, tileBottom, tileDistribute, tileTypeGrid, tileOrganize
35
35
  case tileLeftThird, tileCenterThird, tileRightThird
36
36
 
37
37
  var label: String {
@@ -40,7 +40,7 @@ enum HotkeyAction: String, CaseIterable, Codable {
40
40
  case .screenMap: return "Screen Map"
41
41
  case .bezel: return "Window Bezel"
42
42
  case .cheatSheet: return "Cheat Sheet"
43
- case .desktopInventory: return "Search"
43
+ case .desktopInventory: return "Window Selector"
44
44
  case .omniSearch: return "Search"
45
45
  case .voiceCommand: return "Voice Command"
46
46
  case .handsOff: return "Hands-Off Mode"
@@ -71,6 +71,7 @@ enum HotkeyAction: String, CaseIterable, Codable {
71
71
  case .tileBottom: return "Bottom Half"
72
72
  case .tileDistribute: return "Distribute"
73
73
  case .tileTypeGrid: return "Grid Type"
74
+ case .tileOrganize: return "Organize Windows"
74
75
  case .tileLeftThird: return "Left Third"
75
76
  case .tileCenterThird: return "Center Third"
76
77
  case .tileRightThird: return "Right Third"
@@ -127,6 +128,7 @@ enum HotkeyAction: String, CaseIterable, Codable {
127
128
  case .tileLeftThird: return 311
128
129
  case .tileCenterThird: return 312
129
130
  case .tileRightThird: return 313
131
+ case .tileOrganize: return 315
130
132
  }
131
133
  }
132
134
 
@@ -142,6 +144,15 @@ struct KeyBinding: Codable, Equatable {
142
144
  let carbonModifiers: UInt32
143
145
  var displayParts: [String]
144
146
 
147
+ var compactDisplayParts: [String] {
148
+ guard let key = displayParts.last else { return [] }
149
+ let modifiers = Set(displayParts.dropLast())
150
+ if modifiers == Set(["Ctrl", "Option", "Shift", "Cmd"]) {
151
+ return ["Hyper", key]
152
+ }
153
+ return displayParts
154
+ }
155
+
145
156
  static func carbonModifiers(from flags: NSEvent.ModifierFlags) -> UInt32 {
146
157
  var mods: UInt32 = 0
147
158
  if flags.contains(.command) { mods |= UInt32(cmdKey) }
@@ -231,6 +242,7 @@ class HotkeyStore: ObservableObject {
231
242
  bind(.unifiedWindow, 18, hyper) // Hyper+1 (Workspace Home)
232
243
  bind(.bezel, 19, hyper) // Hyper+2
233
244
  bind(.hud, 20, hyper) // Hyper+3 (HUD overlay)
245
+ bind(.desktopInventory, 5, hyper) // Hyper+G
234
246
  bind(.voiceCommand, 21, hyper) // Hyper+4 (moved from Hyper+3)
235
247
  let cmdCtrl = UInt32(cmdKey | controlKey)
236
248
  bind(.handsOff, 46, cmdCtrl) // Ctrl+Cmd+M
@@ -265,6 +277,7 @@ class HotkeyStore: ObservableObject {
265
277
  bind(.tileLeftThird, 18, ctrlOpt) // Ctrl+Opt+1
266
278
  bind(.tileCenterThird, 19, ctrlOpt) // Ctrl+Opt+2
267
279
  bind(.tileRightThird, 20, ctrlOpt) // Ctrl+Opt+3
280
+ bind(.tileOrganize, 31, ctrlOpt) // Ctrl+Opt+O
268
281
 
269
282
  return d
270
283
  }()
@@ -1,31 +1,5 @@
1
1
  import AppKit
2
2
 
3
- // MARK: - Intent Definition
4
-
5
- struct IntentDef {
6
- let name: String
7
- let description: String
8
- let examples: [String] // Example phrases that map to this intent
9
- let slots: [IntentSlot] // Named parameters extracted from the utterance
10
- let handler: (IntentRequest) throws -> JSON
11
- }
12
-
13
- struct IntentSlot {
14
- let name: String
15
- let type: String // "string", "int", "position", "query"
16
- let required: Bool
17
- let description: String
18
- let enumValues: [String]? // For constrained slots like tile positions
19
- }
20
-
21
- struct IntentRequest {
22
- let intent: String
23
- let slots: [String: JSON]
24
- let rawText: String? // Original transcription, for fallback matching
25
- let confidence: Double? // Transcription confidence from voice service
26
- let source: String? // "vox", "siri", "cli", etc.
27
- }
28
-
29
3
  // MARK: - Intent Engine
30
4
 
31
5
  final class IntentEngine {
@@ -133,6 +107,8 @@ final class IntentEngine {
133
107
  description: "Target window ID", enumValues: nil),
134
108
  IntentSlot(name: "session", type: "string", required: false,
135
109
  description: "Target session name", enumValues: nil),
110
+ IntentSlot(name: "selection", type: "bool", required: false,
111
+ description: "Apply to the active multi-window selection instead of a single window", enumValues: nil),
136
112
  ],
137
113
  handler: { req in
138
114
  guard let posStr = req.slots["position"]?.stringValue else {
@@ -176,6 +152,34 @@ final class IntentEngine {
176
152
  throw IntentError.targetNotFound("No window found for app '\(app)'")
177
153
  }
178
154
 
155
+ if req.slots["selection"]?.boolValue == true {
156
+ let selectionIds = WindowSelectionStore.shared.windowIds
157
+ guard !selectionIds.isEmpty else {
158
+ throw IntentError.targetNotFound("No active window selection")
159
+ }
160
+
161
+ if selectionIds.count == 1,
162
+ let wid = selectionIds.first,
163
+ let entry = DesktopModel.shared.windows[wid] {
164
+ tileEntry(entry)
165
+ return .object([
166
+ "ok": .bool(true),
167
+ "target": .string("selection"),
168
+ "wid": .int(Int(wid)),
169
+ "position": .string(posStr)
170
+ ])
171
+ }
172
+
173
+ return try LatticesApi.shared.dispatch(
174
+ method: "space.optimize",
175
+ params: .object([
176
+ "scope": .string("selection"),
177
+ "windowIds": .array(selectionIds.map { .int(Int($0)) }),
178
+ "region": .string(posStr)
179
+ ])
180
+ )
181
+ }
182
+
179
183
  // Default: tile frontmost window
180
184
  DispatchQueue.main.async {
181
185
  WindowTiler.tileFrontmostViaAX(to: placement)
@@ -401,6 +405,8 @@ final class IntentEngine {
401
405
  description: "Constrain the grid to a screen region. Uses tile position names.",
402
406
  enumValues: ["left", "right", "top", "bottom", "top-left", "top-right", "bottom-left", "bottom-right",
403
407
  "left-third", "center-third", "right-third"]),
408
+ IntentSlot(name: "selection", type: "bool", required: false,
409
+ description: "Use the active selected windows instead of all visible windows", enumValues: nil),
404
410
  ],
405
411
  handler: { req in
406
412
  var params: [String: JSON] = [:]
@@ -413,6 +419,18 @@ final class IntentEngine {
413
419
  if let region = req.slots["region"]?.stringValue {
414
420
  params["region"] = .string(region)
415
421
  }
422
+ if req.slots["selection"]?.boolValue == true {
423
+ let selectionIds = WindowSelectionStore.shared.windowIds
424
+ guard !selectionIds.isEmpty else {
425
+ throw IntentError.targetNotFound("No active window selection")
426
+ }
427
+ params["scope"] = .string("selection")
428
+ params["windowIds"] = .array(selectionIds.map { .int(Int($0)) })
429
+ return try LatticesApi.shared.dispatch(
430
+ method: "space.optimize",
431
+ params: .object(params)
432
+ )
433
+ }
416
434
  return try LatticesApi.shared.dispatch(
417
435
  method: "layout.distribute",
418
436
  params: params.isEmpty ? nil : .object(params)
@@ -0,0 +1,94 @@
1
+ import Foundation
2
+
3
+ struct IntentDef {
4
+ let name: String
5
+ let description: String
6
+ let examples: [String]
7
+ let slots: [IntentSlot]
8
+ let handler: (IntentRequest) throws -> JSON
9
+ }
10
+
11
+ struct IntentSlot {
12
+ let name: String
13
+ let type: String
14
+ let required: Bool
15
+ let description: String
16
+ let enumValues: [String]?
17
+ let defaultValue: JSON?
18
+
19
+ init(
20
+ name: String,
21
+ type: String,
22
+ required: Bool,
23
+ description: String,
24
+ enumValues: [String]? = nil,
25
+ defaultValue: JSON? = nil
26
+ ) {
27
+ self.name = name
28
+ self.type = type
29
+ self.required = required
30
+ self.description = description
31
+ self.enumValues = enumValues
32
+ self.defaultValue = defaultValue
33
+ }
34
+ }
35
+
36
+ struct IntentRequest {
37
+ let intent: String
38
+ let slots: [String: JSON]
39
+ let rawText: String?
40
+ let confidence: Double?
41
+ let source: String?
42
+ }
43
+
44
+ enum SlotType {
45
+ case string
46
+ case int
47
+ case bool
48
+ case position
49
+ case query
50
+ case app
51
+ case session
52
+ case layer
53
+ case enumerated([String])
54
+
55
+ var typeLabel: String {
56
+ switch self {
57
+ case .string: return "string"
58
+ case .int: return "int"
59
+ case .bool: return "bool"
60
+ case .position: return "position"
61
+ case .query: return "query"
62
+ case .app: return "app"
63
+ case .session: return "session"
64
+ case .layer: return "layer"
65
+ case .enumerated: return "string"
66
+ }
67
+ }
68
+
69
+ var enumValues: [String]? {
70
+ guard case .enumerated(let values) = self else { return nil }
71
+ return values
72
+ }
73
+ }
74
+
75
+ typealias SlotDef = IntentSlot
76
+
77
+ extension IntentSlot {
78
+ init(
79
+ name: String,
80
+ type: SlotType,
81
+ required: Bool = true,
82
+ description: String = "",
83
+ defaultValue: JSON? = nil
84
+ ) {
85
+ self.init(
86
+ name: name,
87
+ type: type.typeLabel,
88
+ required: required,
89
+ description: description,
90
+ enumValues: type.enumValues,
91
+ defaultValue: defaultValue
92
+ )
93
+ }
94
+ }
@@ -12,31 +12,6 @@ protocol LatticeIntent {
12
12
  func perform(slots: [String: JSON]) throws -> JSON
13
13
  }
14
14
 
15
- // MARK: - Slot Definition
16
-
17
- enum SlotType {
18
- case string // Free-form text
19
- case position // Tile position (left, right, maximize, etc.)
20
- case app // Running app name
21
- case session // Active tmux session
22
- case layer // Layer name
23
- case enumerated([String]) // Fixed set of values
24
- }
25
-
26
- struct SlotDef {
27
- let name: String
28
- let type: SlotType
29
- let required: Bool
30
- let defaultValue: JSON?
31
-
32
- init(name: String, type: SlotType, required: Bool = true, defaultValue: JSON? = nil) {
33
- self.name = name
34
- self.type = type
35
- self.required = required
36
- self.defaultValue = defaultValue
37
- }
38
- }
39
-
40
15
  // MARK: - Compiled Phrase Template
41
16
 
42
17
  struct CompiledPhrase {
@@ -141,10 +141,43 @@ final class VoiceIntentResolver {
141
141
  private func extractSlots(for intentName: String, input: String) -> ExtractedSlots {
142
142
  switch intentName {
143
143
  case "tile_window":
144
- guard let position = resolvePosition(in: input) else {
144
+ var slots: [String: JSON] = [:]
145
+ var boost = 0.0
146
+
147
+ if let position = resolvePosition(in: input) {
148
+ slots["position"] = .string(position)
149
+ boost += 0.28
150
+ } else {
145
151
  return ExtractedSlots(slots: [:], boost: 0)
146
152
  }
147
- return ExtractedSlots(slots: ["position": .string(position)], boost: 0.28)
153
+
154
+ if refersToSelection(in: input) {
155
+ slots["selection"] = .bool(true)
156
+ boost += 0.08
157
+ }
158
+
159
+ return ExtractedSlots(slots: slots, boost: boost)
160
+
161
+ case "distribute":
162
+ var slots: [String: JSON] = [:]
163
+ var boost = 0.0
164
+
165
+ if let region = resolvePosition(in: input) {
166
+ slots["region"] = .string(region)
167
+ boost += 0.18
168
+ }
169
+
170
+ if let app = detectKnownApp(in: input) {
171
+ slots["app"] = .string(app)
172
+ boost += 0.14
173
+ }
174
+
175
+ if refersToSelection(in: input) {
176
+ slots["selection"] = .bool(true)
177
+ boost += 0.12
178
+ }
179
+
180
+ return ExtractedSlots(slots: slots, boost: boost)
148
181
 
149
182
  case "focus":
150
183
  if let app = detectKnownApp(in: input) ?? extractEntity(in: input, prefixes: focusPrefixes) {
@@ -426,6 +459,15 @@ final class VoiceIntentResolver {
426
459
  return nil
427
460
  }
428
461
 
462
+ private func refersToSelection(in input: String) -> Bool {
463
+ let markers = [
464
+ "grid that", "grid these", "grid those",
465
+ "tile that", "tile these", "tile those",
466
+ "selected windows", "selection", "selected", "these windows", "those windows", "them"
467
+ ]
468
+ return markers.contains(where: input.contains)
469
+ }
470
+
429
471
  private func detectKnownApp(in input: String) -> String? {
430
472
  for app in knownApps() {
431
473
  let lower = app.lowercased()
@@ -601,7 +643,7 @@ final class VoiceIntentResolver {
601
643
  "search": ["find", "search", "look for", "where is", "where d", "locate", "lost", "show me all", "windows"],
602
644
  "list_windows": ["what s open", "list windows", "which windows", "what do i have open"],
603
645
  "list_sessions": ["list sessions", "what s running", "which projects", "show my sessions"],
604
- "distribute": ["distribute", "spread", "organize", "arrange", "tidy", "clean up", "grid"],
646
+ "distribute": ["distribute", "spread", "organize", "arrange", "tidy", "clean up", "grid", "selected", "selection"],
605
647
  "create_layer": ["create layer", "save layout", "snapshot", "remember this layout"],
606
648
  "kill": ["kill", "stop", "shut down", "close", "terminate", "end"],
607
649
  "scan": ["scan", "rescan", "ocr", "read the screen", "what s on my screen", "screen text"],
@@ -662,7 +704,7 @@ final class VoiceIntentResolver {
662
704
  "search": ["where d my slack go", "pull up everything with dewey in it", "show me all the chrome windows", "dewey"],
663
705
  "list_windows": ["what do i have open", "what windows do i have"],
664
706
  "list_sessions": ["show me my sessions", "which projects are active"],
665
- "distribute": ["tidy up", "line everything up", "clean up the windows"],
707
+ "distribute": ["tidy up", "line everything up", "clean up the windows", "grid that in the bottom half", "arrange the selected windows"],
666
708
  "create_layer": ["snapshot this", "remember this layout"],
667
709
  "kill": ["close the dewey session", "stop my session"],
668
710
  "scan": ["what s on my screen", "read the screen", "give me a fresh scan"],
@@ -106,8 +106,7 @@ final class DesktopModel: ObservableObject {
106
106
  }
107
107
 
108
108
  func windowForSession(_ session: String) -> WindowEntry? {
109
- let tag = Terminal.windowTag(for: session)
110
- return windows.values.first { $0.title.contains(tag) }
109
+ SessionWindowLocator.cachedWindow(forSession: session, in: windows)
111
110
  }
112
111
 
113
112
  /// Assign a layer tag to a window (in-memory only)
@@ -205,12 +204,7 @@ final class DesktopModel: ObservableObject {
205
204
 
206
205
  let spaceIds = WindowTiler.getSpacesForWindow(wid)
207
206
 
208
- // Extract lattices session tag from title: [lattices:session-name]
209
- var latticesSession: String?
210
- if let range = title.range(of: #"\[lattices:([^\]]+)\]"#, options: .regularExpression) {
211
- let match = String(title[range])
212
- latticesSession = String(match.dropFirst(9).dropLast(1)) // drop "[lattices:" and "]"
213
- }
207
+ let latticesSession = SessionWindowLocator.extractSessionName(from: title)
214
208
 
215
209
  var entry = WindowEntry(
216
210
  wid: wid,