@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.
- package/app/Info.plist +2 -2
- package/app/Lattices.app/Contents/Info.plist +2 -2
- package/app/Lattices.app/Contents/MacOS/Lattices +0 -0
- package/app/Sources/{AppDelegate.swift → AppShell/AppDelegate.swift} +9 -0
- package/app/Sources/{AppShellView.swift → AppShell/AppShellView.swift} +10 -1
- package/app/Sources/{KeyRecorderView.swift → AppShell/KeyRecorderView.swift} +1 -1
- package/app/Sources/{Preferences.swift → AppShell/Preferences.swift} +0 -2
- package/app/Sources/{SettingsView.swift → AppShell/SettingsView.swift} +27 -2
- package/app/Sources/{HotkeyStore.swift → Core/Actions/HotkeyStore.swift} +15 -2
- package/app/Sources/{IntentEngine.swift → Core/Actions/IntentEngine.swift} +44 -26
- package/app/Sources/Core/Actions/IntentSchema.swift +94 -0
- package/app/Sources/{Intents → Core/Actions/Intents}/LatticeIntent.swift +0 -25
- package/app/Sources/{VoiceIntentResolver.swift → Core/Actions/VoiceIntentResolver.swift} +46 -4
- package/app/Sources/{DesktopModel.swift → Core/Desktop/DesktopModel.swift} +2 -8
- package/app/Sources/Core/Desktop/SessionWindowLocator.swift +139 -0
- package/app/Sources/Core/Desktop/WindowPreviewCard.swift +100 -0
- package/app/Sources/Core/Desktop/WindowPreviewStore.swift +113 -0
- package/app/Sources/Core/Desktop/WindowSelectionStore.swift +76 -0
- package/app/Sources/{WindowTiler.swift → Core/Desktop/WindowTiler.swift} +24 -110
- package/app/Sources/{CommandModeState.swift → Core/Overlays/CommandMode/CommandModeState.swift} +228 -24
- package/app/Sources/{CommandModeView.swift → Core/Overlays/CommandMode/CommandModeView.swift} +601 -59
- package/app/Sources/{CommandModeWindow.swift → Core/Overlays/CommandMode/CommandModeWindow.swift} +9 -5
- package/app/Sources/Core/Overlays/CommandPalette/CommandPaletteWindow.swift +67 -0
- package/app/Sources/{CheatSheetHUD.swift → Core/Overlays/HUD/CheatSheetHUD.swift} +1 -0
- package/app/Sources/{HUDRightBar.swift → Core/Overlays/HUD/HUDRightBar.swift} +23 -201
- package/app/Sources/{LauncherHUD.swift → Core/Overlays/HUD/LauncherHUD.swift} +12 -26
- package/app/Sources/Core/Overlays/OmniSearch/OmniSearchWindow.swift +94 -0
- package/app/Sources/Core/Overlays/OverlayPanelShell.swift +241 -0
- package/app/Sources/{ScreenMapState.swift → Core/Overlays/ScreenMap/ScreenMapState.swift} +25 -2
- package/app/Sources/{ScreenMapView.swift → Core/Overlays/ScreenMap/ScreenMapView.swift} +20 -7
- package/app/Sources/{VoiceCommandWindow.swift → Core/Overlays/Voice/VoiceCommandWindow.swift} +46 -74
- package/app/Sources/{WorkspaceManager.swift → Core/Workspace/WorkspaceManager.swift} +59 -4
- package/docs/component-extraction-roadmap.md +392 -0
- package/package.json +3 -1
- package/app/Sources/CommandPaletteWindow.swift +0 -134
- package/app/Sources/OmniSearchWindow.swift +0 -165
- /package/app/Sources/{App.swift → AppShell/App.swift} +0 -0
- /package/app/Sources/{AppUpdater.swift → AppShell/AppUpdater.swift} +0 -0
- /package/app/Sources/{CliActionLauncher.swift → AppShell/CliActionLauncher.swift} +0 -0
- /package/app/Sources/{HomeDashboardView.swift → AppShell/HomeDashboardView.swift} +0 -0
- /package/app/Sources/{LatticesRuntime.swift → AppShell/LatticesRuntime.swift} +0 -0
- /package/app/Sources/{MainView.swift → AppShell/MainView.swift} +0 -0
- /package/app/Sources/{MainWindow.swift → AppShell/MainWindow.swift} +0 -0
- /package/app/Sources/{OnboardingView.swift → AppShell/OnboardingView.swift} +0 -0
- /package/app/Sources/{SettingsWindow.swift → AppShell/SettingsWindow.swift} +0 -0
- /package/app/Sources/{HotkeyManager.swift → Core/Actions/HotkeyManager.swift} +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/CreateLayerIntent.swift +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/DistributeIntent.swift +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/FocusIntent.swift +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/HelpIntent.swift +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/KillIntent.swift +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/LaunchIntent.swift +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/ListSessionsIntent.swift +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/ListWindowsIntent.swift +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/ScanIntent.swift +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/SearchIntent.swift +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/SwitchLayerIntent.swift +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/TileIntent.swift +0 -0
- /package/app/Sources/{PaletteCommand.swift → Core/Actions/PaletteCommand.swift} +0 -0
- /package/app/Sources/{CompanionActivityLog.swift → Core/Companion/CompanionActivityLog.swift} +0 -0
- /package/app/Sources/{CompanionKeyboardController.swift → Core/Companion/CompanionKeyboardController.swift} +0 -0
- /package/app/Sources/{LatticesCompanionBridgeServer.swift → Core/Companion/LatticesCompanionBridgeServer.swift} +0 -0
- /package/app/Sources/{LatticesCompanionCockpit.swift → Core/Companion/LatticesCompanionCockpit.swift} +0 -0
- /package/app/Sources/{LatticesCompanionSecurityCoordinator.swift → Core/Companion/LatticesCompanionSecurityCoordinator.swift} +0 -0
- /package/app/Sources/{LatticesCompanionTrackpadController.swift → Core/Companion/LatticesCompanionTrackpadController.swift} +0 -0
- /package/app/Sources/{LatticesDeckHost.swift → Core/Companion/LatticesDeckHost.swift} +0 -0
- /package/app/Sources/{DaemonProtocol.swift → Core/Daemon/DaemonProtocol.swift} +0 -0
- /package/app/Sources/{DaemonServer.swift → Core/Daemon/DaemonServer.swift} +0 -0
- /package/app/Sources/{LatticesApi.swift → Core/Daemon/LatticesApi.swift} +0 -0
- /package/app/Sources/{AccessibilityTextExtractor.swift → Core/Desktop/AccessibilityTextExtractor.swift} +0 -0
- /package/app/Sources/{AppTypeClassifier.swift → Core/Desktop/AppTypeClassifier.swift} +0 -0
- /package/app/Sources/{DesktopModelTypes.swift → Core/Desktop/DesktopModelTypes.swift} +0 -0
- /package/app/Sources/{InventoryManager.swift → Core/Desktop/InventoryManager.swift} +0 -0
- /package/app/Sources/{InventoryPath.swift → Core/Desktop/InventoryPath.swift} +0 -0
- /package/app/Sources/{MouseFinder.swift → Core/Desktop/MouseFinder.swift} +0 -0
- /package/app/Sources/{OcrModel.swift → Core/Desktop/OcrModel.swift} +0 -0
- /package/app/Sources/{OcrStore.swift → Core/Desktop/OcrStore.swift} +0 -0
- /package/app/Sources/{PlacementSpec.swift → Core/Desktop/PlacementSpec.swift} +0 -0
- /package/app/Sources/{TilePickerView.swift → Core/Desktop/TilePickerView.swift} +0 -0
- /package/app/Sources/{WindowDragSnapController.swift → Core/Desktop/WindowDragSnapController.swift} +0 -0
- /package/app/Sources/{MouseGestureConfig.swift → Core/Input/MouseGestureConfig.swift} +0 -0
- /package/app/Sources/{MouseGestureController.swift → Core/Input/MouseGestureController.swift} +0 -0
- /package/app/Sources/{MouseInputDeviceStore.swift → Core/Input/MouseInputDeviceStore.swift} +0 -0
- /package/app/Sources/{MouseInputEventViewer.swift → Core/Input/MouseInputEventViewer.swift} +0 -0
- /package/app/Sources/{MouseShortcutStore.swift → Core/Input/MouseShortcutStore.swift} +0 -0
- /package/app/Sources/{AppWindowShell.swift → Core/Overlays/AppWindowShell.swift} +0 -0
- /package/app/Sources/{CommandPaletteView.swift → Core/Overlays/CommandPalette/CommandPaletteView.swift} +0 -0
- /package/app/Sources/{HUDBottomBar.swift → Core/Overlays/HUD/HUDBottomBar.swift} +0 -0
- /package/app/Sources/{HUDController.swift → Core/Overlays/HUD/HUDController.swift} +0 -0
- /package/app/Sources/{HUDLeftBar.swift → Core/Overlays/HUD/HUDLeftBar.swift} +0 -0
- /package/app/Sources/{HUDMinimap.swift → Core/Overlays/HUD/HUDMinimap.swift} +0 -0
- /package/app/Sources/{HUDState.swift → Core/Overlays/HUD/HUDState.swift} +0 -0
- /package/app/Sources/{HUDTopBar.swift → Core/Overlays/HUD/HUDTopBar.swift} +0 -0
- /package/app/Sources/{LayerBezel.swift → Core/Overlays/HUD/LayerBezel.swift} +0 -0
- /package/app/Sources/{OmniSearchState.swift → Core/Overlays/OmniSearch/OmniSearchState.swift} +0 -0
- /package/app/Sources/{OmniSearchView.swift → Core/Overlays/OmniSearch/OmniSearchView.swift} +0 -0
- /package/app/Sources/{ScreenMapWindowController.swift → Core/Overlays/ScreenMap/ScreenMapWindowController.swift} +0 -0
- /package/app/Sources/{PiAuthNextStepCard.swift → Core/Pi/PiAuthNextStepCard.swift} +0 -0
- /package/app/Sources/{PiAuthPromptCard.swift → Core/Pi/PiAuthPromptCard.swift} +0 -0
- /package/app/Sources/{PiChatDock.swift → Core/Pi/PiChatDock.swift} +0 -0
- /package/app/Sources/{PiChatSession.swift → Core/Pi/PiChatSession.swift} +0 -0
- /package/app/Sources/{PiInstallCallout.swift → Core/Pi/PiInstallCallout.swift} +0 -0
- /package/app/Sources/{PiProviderSetupCallout.swift → Core/Pi/PiProviderSetupCallout.swift} +0 -0
- /package/app/Sources/{PiWorkspaceView.swift → Core/Pi/PiWorkspaceView.swift} +0 -0
- /package/app/Sources/{DiagnosticLog.swift → Core/System/DiagnosticLog.swift} +0 -0
- /package/app/Sources/{EventBus.swift → Core/System/EventBus.swift} +0 -0
- /package/app/Sources/{PermissionChecker.swift → Core/System/PermissionChecker.swift} +0 -0
- /package/app/Sources/{ProcessModel.swift → Core/System/ProcessModel.swift} +0 -0
- /package/app/Sources/{ProcessQuery.swift → Core/System/ProcessQuery.swift} +0 -0
- /package/app/Sources/{SystemTelemetryMonitor.swift → Core/System/SystemTelemetryMonitor.swift} +0 -0
- /package/app/Sources/{AdvisorLearningStore.swift → Core/Voice/AdvisorLearningStore.swift} +0 -0
- /package/app/Sources/{AgentSession.swift → Core/Voice/AgentSession.swift} +0 -0
- /package/app/Sources/{AudioProvider.swift → Core/Voice/AudioProvider.swift} +0 -0
- /package/app/Sources/{HandsOffSession.swift → Core/Voice/HandsOffSession.swift} +0 -0
- /package/app/Sources/{VoiceChatView.swift → Core/Voice/VoiceChatView.swift} +0 -0
- /package/app/Sources/{VoxClient.swift → Core/Voice/VoxClient.swift} +0 -0
- /package/app/Sources/{Project.swift → Core/Workspace/Project.swift} +0 -0
- /package/app/Sources/{ProjectScanner.swift → Core/Workspace/ProjectScanner.swift} +0 -0
- /package/app/Sources/{SessionLayerStore.swift → Core/Workspace/SessionLayerStore.swift} +0 -0
- /package/app/Sources/{SessionManager.swift → Core/Workspace/SessionManager.swift} +0 -0
- /package/app/Sources/{Terminal.swift → Core/Workspace/Terminal/Terminal.swift} +0 -0
- /package/app/Sources/{TerminalQuery.swift → Core/Workspace/Terminal/TerminalQuery.swift} +0 -0
- /package/app/Sources/{TerminalSynthesizer.swift → Core/Workspace/Terminal/TerminalSynthesizer.swift} +0 -0
- /package/app/Sources/{TmuxModel.swift → Core/Workspace/Tmux/TmuxModel.swift} +0 -0
- /package/app/Sources/{TmuxQuery.swift → Core/Workspace/Tmux/TmuxQuery.swift} +0 -0
- /package/app/Sources/{ActionRow.swift → UI/ActionRow.swift} +0 -0
- /package/app/Sources/{OrphanRow.swift → UI/OrphanRow.swift} +0 -0
- /package/app/Sources/{ProjectRow.swift → UI/ProjectRow.swift} +0 -0
- /package/app/Sources/{TabGroupRow.swift → UI/TabGroupRow.swift} +0 -0
- /package/app/Sources/{Theme.swift → UI/Theme.swift} +0 -0
package/app/Sources/{CommandModeState.swift → Core/Overlays/CommandMode/CommandModeState.swift}
RENAMED
|
@@ -45,6 +45,11 @@ enum DesktopInventoryMode: Equatable {
|
|
|
45
45
|
case screenMap // m → interactive screen map editor
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
enum CommandModeLaunchMode: Equatable {
|
|
49
|
+
case normal
|
|
50
|
+
case organize(appName: String?)
|
|
51
|
+
}
|
|
52
|
+
|
|
48
53
|
// DisplayGeometry, ScreenMapWindowEntry, ScreenMapEditorState, ScreenMapActionLog
|
|
49
54
|
// are defined in ScreenMapState.swift
|
|
50
55
|
// MARK: - Filter Presets
|
|
@@ -91,11 +96,14 @@ final class CommandModeState: ObservableObject {
|
|
|
91
96
|
@Published var inventory = CommandModeInventory(activeLayer: nil, layerCount: 0, items: [])
|
|
92
97
|
@Published var chords: [Chord] = []
|
|
93
98
|
@Published var desktopSnapshot: DesktopInventorySnapshot?
|
|
94
|
-
@Published var selectedWindowIds: Set<UInt32> = []
|
|
99
|
+
@Published var selectedWindowIds: Set<UInt32> = [] {
|
|
100
|
+
didSet { syncSharedSelection() }
|
|
101
|
+
}
|
|
95
102
|
@Published var desktopMode: DesktopInventoryMode = .browsing
|
|
96
103
|
@Published var activePreset: FilterPreset? = nil
|
|
97
104
|
@Published var searchQuery: String = ""
|
|
98
105
|
@Published var isSearching: Bool = false
|
|
106
|
+
@Published var gridPreviewPlacement: PlacementSpec? = nil
|
|
99
107
|
|
|
100
108
|
// MARK: - Marquee Drag State
|
|
101
109
|
@Published var isDragging: Bool = false
|
|
@@ -129,10 +137,43 @@ final class CommandModeState: ObservableObject {
|
|
|
129
137
|
|
|
130
138
|
var onDismiss: (() -> Void)?
|
|
131
139
|
var onPanelResize: ((_ width: CGFloat, _ height: CGFloat) -> Void)?
|
|
140
|
+
private let launchMode: CommandModeLaunchMode
|
|
132
141
|
|
|
133
142
|
/// Tracks the last item navigated to, for consistent Shift+arrow multi-select
|
|
134
143
|
private var cursorWindowId: UInt32?
|
|
135
144
|
|
|
145
|
+
init(launchMode: CommandModeLaunchMode = .normal) {
|
|
146
|
+
self.launchMode = launchMode
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
var isOrganizeFlow: Bool {
|
|
150
|
+
if case .organize = launchMode { return true }
|
|
151
|
+
return false
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
var organizeSeedAppName: String? {
|
|
155
|
+
if case .organize(let appName) = launchMode { return appName }
|
|
156
|
+
return nil
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
var organizeSelectionSummary: String {
|
|
160
|
+
let count = selectedWindowIds.count
|
|
161
|
+
if let appName = organizeSeedAppName, !appName.isEmpty {
|
|
162
|
+
return "\(count) \(appName) window\(count == 1 ? "" : "s") selected"
|
|
163
|
+
}
|
|
164
|
+
return "\(count) window\(count == 1 ? "" : "s") selected"
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
var organizeGuidance: String {
|
|
168
|
+
if selectedWindowIds.count > 1 {
|
|
169
|
+
return "Press D to organize. Cmd-click adds or removes windows. Shift-click extends the selection."
|
|
170
|
+
}
|
|
171
|
+
if selectedWindowIds.count == 1 {
|
|
172
|
+
return "Cmd-click another window to add it, then press D to organize the set."
|
|
173
|
+
}
|
|
174
|
+
return "Click windows to select them. Cmd-click adds or removes windows, and D organizes the selection."
|
|
175
|
+
}
|
|
176
|
+
|
|
136
177
|
// MARK: - Selection Helpers
|
|
137
178
|
|
|
138
179
|
/// Backwards-compat: returns single selected ID (first element)
|
|
@@ -140,6 +181,26 @@ final class CommandModeState: ObservableObject {
|
|
|
140
181
|
selectedWindowIds.first
|
|
141
182
|
}
|
|
142
183
|
|
|
184
|
+
var selectedWindowSummaryText: String {
|
|
185
|
+
let windows = flatWindowList.filter { selectedWindowIds.contains($0.id) }
|
|
186
|
+
let labels = windows.compactMap { $0.appName }.uniquePrefix(3)
|
|
187
|
+
guard !labels.isEmpty else { return "" }
|
|
188
|
+
if windows.count > labels.count {
|
|
189
|
+
return labels.joined(separator: " • ") + " +\(windows.count - labels.count)"
|
|
190
|
+
}
|
|
191
|
+
return labels.joined(separator: " • ")
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
var gridPreviewRegionLabel: String {
|
|
195
|
+
guard let placement = gridPreviewPlacement else { return "Full Screen" }
|
|
196
|
+
switch placement {
|
|
197
|
+
case .tile(let position):
|
|
198
|
+
return position.label
|
|
199
|
+
default:
|
|
200
|
+
return placement.wireValue
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
143
204
|
func isSelected(_ id: UInt32) -> Bool {
|
|
144
205
|
selectedWindowIds.contains(id)
|
|
145
206
|
}
|
|
@@ -344,7 +405,9 @@ final class CommandModeState: ObservableObject {
|
|
|
344
405
|
desktopSnapshot = buildDesktopInventory()
|
|
345
406
|
clearSelection()
|
|
346
407
|
desktopMode = .browsing
|
|
408
|
+
gridPreviewPlacement = nil
|
|
347
409
|
phase = .desktopInventory
|
|
410
|
+
configureLaunchMode()
|
|
348
411
|
// Don't call onPanelResize here — caller handles initial sizing
|
|
349
412
|
}
|
|
350
413
|
|
|
@@ -535,6 +598,7 @@ final class CommandModeState: ObservableObject {
|
|
|
535
598
|
if isSearching && selectedWindowIds.isEmpty { return false }
|
|
536
599
|
if isSearching { deactivateSearch() }
|
|
537
600
|
if !selectedWindowIds.isEmpty {
|
|
601
|
+
gridPreviewPlacement = nil
|
|
538
602
|
desktopMode = .gridPreview
|
|
539
603
|
}
|
|
540
604
|
return true
|
|
@@ -551,6 +615,20 @@ final class CommandModeState: ObservableObject {
|
|
|
551
615
|
}
|
|
552
616
|
return true
|
|
553
617
|
|
|
618
|
+
case 2: // d → distribute selected
|
|
619
|
+
if isSearching && selectedWindowIds.isEmpty { return false }
|
|
620
|
+
if isSearching { deactivateSearch() }
|
|
621
|
+
guard !selectedWindowIds.isEmpty else {
|
|
622
|
+
flash("Select 2+ windows to organize")
|
|
623
|
+
return true
|
|
624
|
+
}
|
|
625
|
+
guard selectedWindowIds.count > 1 else {
|
|
626
|
+
flash("Add another window, then press D to organize")
|
|
627
|
+
return true
|
|
628
|
+
}
|
|
629
|
+
distributeSelected()
|
|
630
|
+
return true
|
|
631
|
+
|
|
554
632
|
case 46: // m → screen map editor (standalone window)
|
|
555
633
|
if isSearching { deactivateSearch() }
|
|
556
634
|
ScreenMapWindowController.shared.show()
|
|
@@ -614,15 +692,54 @@ final class CommandModeState: ObservableObject {
|
|
|
614
692
|
|
|
615
693
|
private func handleGridPreviewKey(_ keyCode: UInt16) -> Bool {
|
|
616
694
|
switch keyCode {
|
|
617
|
-
case 53: // Escape —
|
|
618
|
-
|
|
695
|
+
case 53: // Escape — cancel preview, keep selection
|
|
696
|
+
gridPreviewPlacement = nil
|
|
697
|
+
desktopMode = .browsing
|
|
619
698
|
return true
|
|
620
699
|
|
|
621
700
|
case 36, 1: // Enter or s → apply the layout
|
|
622
|
-
showAndDistributeSelected()
|
|
701
|
+
showAndDistributeSelected(in: gridPreviewPlacement)
|
|
702
|
+
gridPreviewPlacement = nil
|
|
623
703
|
desktopMode = .browsing
|
|
624
704
|
return true
|
|
625
705
|
|
|
706
|
+
case 123:
|
|
707
|
+
gridPreviewPlacement = .tile(.left)
|
|
708
|
+
return true
|
|
709
|
+
case 124:
|
|
710
|
+
gridPreviewPlacement = .tile(.right)
|
|
711
|
+
return true
|
|
712
|
+
case 126:
|
|
713
|
+
gridPreviewPlacement = .tile(.top)
|
|
714
|
+
return true
|
|
715
|
+
case 125:
|
|
716
|
+
gridPreviewPlacement = .tile(.bottom)
|
|
717
|
+
return true
|
|
718
|
+
case 18:
|
|
719
|
+
gridPreviewPlacement = .tile(.topLeft)
|
|
720
|
+
return true
|
|
721
|
+
case 19:
|
|
722
|
+
gridPreviewPlacement = .tile(.topRight)
|
|
723
|
+
return true
|
|
724
|
+
case 20:
|
|
725
|
+
gridPreviewPlacement = .tile(.bottomLeft)
|
|
726
|
+
return true
|
|
727
|
+
case 21:
|
|
728
|
+
gridPreviewPlacement = .tile(.bottomRight)
|
|
729
|
+
return true
|
|
730
|
+
case 23:
|
|
731
|
+
gridPreviewPlacement = .tile(.leftThird)
|
|
732
|
+
return true
|
|
733
|
+
case 22:
|
|
734
|
+
gridPreviewPlacement = .tile(.centerThird)
|
|
735
|
+
return true
|
|
736
|
+
case 26:
|
|
737
|
+
gridPreviewPlacement = .tile(.rightThird)
|
|
738
|
+
return true
|
|
739
|
+
case 8:
|
|
740
|
+
gridPreviewPlacement = .tile(.center)
|
|
741
|
+
return true
|
|
742
|
+
|
|
626
743
|
default:
|
|
627
744
|
return true
|
|
628
745
|
}
|
|
@@ -795,20 +912,8 @@ final class CommandModeState: ObservableObject {
|
|
|
795
912
|
private func tileAllSelected(to position: TilePosition) {
|
|
796
913
|
let windows = flatWindowList.filter { selectedWindowIds.contains($0.id) }
|
|
797
914
|
guard !windows.isEmpty else { return }
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
if windows.count >= 2 && (position == .left || position == .right) {
|
|
801
|
-
distributeSelectedHorizontally()
|
|
802
|
-
return
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
DiagnosticLog.shared.info("Tile all \(windows.count): \(position.rawValue)")
|
|
806
|
-
for win in windows {
|
|
807
|
-
WindowTiler.tileWindowById(wid: win.id, pid: win.pid, to: position)
|
|
808
|
-
}
|
|
809
|
-
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak self] in
|
|
810
|
-
self?.desktopSnapshot = self?.buildDesktopInventory()
|
|
811
|
-
}
|
|
915
|
+
DiagnosticLog.shared.info("Grid selected \(windows.count): \(position.rawValue)")
|
|
916
|
+
showAndDistributeSelected(in: .tile(position))
|
|
812
917
|
}
|
|
813
918
|
|
|
814
919
|
private func distributeSelectedHorizontally() {
|
|
@@ -849,28 +954,36 @@ final class CommandModeState: ObservableObject {
|
|
|
849
954
|
}
|
|
850
955
|
|
|
851
956
|
/// Show all selected windows AND distribute in smart grid — single batch operation
|
|
852
|
-
func showAndDistributeSelected() {
|
|
957
|
+
func showAndDistributeSelected(in placement: PlacementSpec? = nil) {
|
|
853
958
|
let windows = flatWindowList.filter { selectedWindowIds.contains($0.id) }
|
|
854
959
|
guard !windows.isEmpty else { return }
|
|
855
960
|
savePositions(for: windows)
|
|
856
|
-
WindowTiler.batchRaiseAndDistribute(
|
|
961
|
+
WindowTiler.batchRaiseAndDistribute(
|
|
962
|
+
windows: windows.map { (wid: $0.id, pid: $0.pid) },
|
|
963
|
+
region: placement?.fractions
|
|
964
|
+
)
|
|
857
965
|
let shape = WindowTiler.gridShape(for: windows.count)
|
|
858
966
|
let grid = shape.map(String.init).joined(separator: "+")
|
|
859
|
-
|
|
967
|
+
let region = placement.map { " in \(self.regionLabel(for: $0))" } ?? ""
|
|
968
|
+
flash("\(windows.count) windows\(region) [\(grid)]")
|
|
860
969
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak self] in
|
|
861
970
|
self?.desktopSnapshot = self?.buildDesktopInventory()
|
|
862
971
|
}
|
|
863
972
|
}
|
|
864
973
|
|
|
865
974
|
/// Distribute selected in smart grid without raising
|
|
866
|
-
func distributeSelected() {
|
|
975
|
+
func distributeSelected(in placement: PlacementSpec? = nil) {
|
|
867
976
|
let windows = flatWindowList.filter { selectedWindowIds.contains($0.id) }
|
|
868
977
|
guard !windows.isEmpty else { return }
|
|
869
978
|
savePositions(for: windows)
|
|
870
|
-
WindowTiler.batchRaiseAndDistribute(
|
|
979
|
+
WindowTiler.batchRaiseAndDistribute(
|
|
980
|
+
windows: windows.map { (wid: $0.id, pid: $0.pid) },
|
|
981
|
+
region: placement?.fractions
|
|
982
|
+
)
|
|
871
983
|
let shape = WindowTiler.gridShape(for: windows.count)
|
|
872
984
|
let grid = shape.map(String.init).joined(separator: "+")
|
|
873
|
-
|
|
985
|
+
let region = placement.map { " in \(self.regionLabel(for: $0))" } ?? ""
|
|
986
|
+
flash("\(windows.count) windows\(region) [\(grid)]")
|
|
874
987
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak self] in
|
|
875
988
|
self?.desktopSnapshot = self?.buildDesktopInventory()
|
|
876
989
|
}
|
|
@@ -960,6 +1073,84 @@ final class CommandModeState: ObservableObject {
|
|
|
960
1073
|
onDismiss?()
|
|
961
1074
|
}
|
|
962
1075
|
|
|
1076
|
+
private func syncSharedSelection() {
|
|
1077
|
+
guard !selectedWindowIds.isEmpty else {
|
|
1078
|
+
WindowSelectionStore.shared.clear(source: "desktop-inventory")
|
|
1079
|
+
return
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
let summaries = flatWindowList
|
|
1083
|
+
.filter { selectedWindowIds.contains($0.id) }
|
|
1084
|
+
.map {
|
|
1085
|
+
SelectedWindowSummary(
|
|
1086
|
+
wid: $0.id,
|
|
1087
|
+
app: $0.appName ?? "Window",
|
|
1088
|
+
title: $0.title,
|
|
1089
|
+
latticesSession: $0.latticesSession
|
|
1090
|
+
)
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
guard !summaries.isEmpty else { return }
|
|
1094
|
+
WindowSelectionStore.shared.setSelection(summaries, source: "desktop-inventory")
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
private func regionLabel(for placement: PlacementSpec) -> String {
|
|
1098
|
+
switch placement {
|
|
1099
|
+
case .tile(let position):
|
|
1100
|
+
return position.label
|
|
1101
|
+
default:
|
|
1102
|
+
return placement.wireValue
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
private func configureLaunchMode() {
|
|
1107
|
+
switch launchMode {
|
|
1108
|
+
case .normal:
|
|
1109
|
+
return
|
|
1110
|
+
case .organize(let appName):
|
|
1111
|
+
activePreset = .currentSpace
|
|
1112
|
+
seedSelectionForOrganization(appName: appName)
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
private func seedSelectionForOrganization(appName: String?) {
|
|
1117
|
+
let visibleWindows = flatWindowList.filter(\.isOnScreen)
|
|
1118
|
+
let targetApp = appName ?? visibleWindows.first?.appName
|
|
1119
|
+
let initialSelection = visibleWindows.filter { window in
|
|
1120
|
+
guard let name = window.appName, let targetApp else { return false }
|
|
1121
|
+
return name.localizedCaseInsensitiveCompare(targetApp) == .orderedSame
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
if initialSelection.isEmpty {
|
|
1125
|
+
flash("Select windows to organize. Cmd-click adds or removes windows; D distributes.")
|
|
1126
|
+
return
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
selectedWindowIds = Set(initialSelection.map(\.id))
|
|
1130
|
+
cursorWindowId = initialSelection.first?.id
|
|
1131
|
+
|
|
1132
|
+
if let targetApp {
|
|
1133
|
+
if initialSelection.count > 1 {
|
|
1134
|
+
flash("Selected \(initialSelection.count) \(targetApp) windows. Press D to organize.")
|
|
1135
|
+
} else {
|
|
1136
|
+
flash("Selected the \(targetApp) window. Cmd-click more windows, then press D.")
|
|
1137
|
+
}
|
|
1138
|
+
} else if initialSelection.count > 1 {
|
|
1139
|
+
flash("Selected \(initialSelection.count) windows. Press D to organize.")
|
|
1140
|
+
} else {
|
|
1141
|
+
flash("Selected 1 window. Cmd-click more windows, then press D.")
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
DispatchQueue.main.async { [weak self] in
|
|
1145
|
+
guard let self = self else { return }
|
|
1146
|
+
if self.selectedWindowIds.count > 1 {
|
|
1147
|
+
self.highlightAllSelected()
|
|
1148
|
+
} else {
|
|
1149
|
+
self.highlightSelectedWindow()
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
|
|
963
1154
|
// MARK: - Inventory Builder
|
|
964
1155
|
|
|
965
1156
|
private func buildInventory() -> CommandModeInventory {
|
|
@@ -1360,3 +1551,16 @@ final class CommandModeState: ObservableObject {
|
|
|
1360
1551
|
return chords
|
|
1361
1552
|
}
|
|
1362
1553
|
}
|
|
1554
|
+
|
|
1555
|
+
private extension Sequence where Element == String {
|
|
1556
|
+
func uniquePrefix(_ count: Int) -> [String] {
|
|
1557
|
+
var seen = Set<String>()
|
|
1558
|
+
var result: [String] = []
|
|
1559
|
+
for item in self where !seen.contains(item) {
|
|
1560
|
+
seen.insert(item)
|
|
1561
|
+
result.append(item)
|
|
1562
|
+
if result.count == count { break }
|
|
1563
|
+
}
|
|
1564
|
+
return result
|
|
1565
|
+
}
|
|
1566
|
+
}
|