@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
@@ -0,0 +1,392 @@
1
+ # Component Extraction Roadmap
2
+
3
+ This note turns a codebase review into an incremental component plan for `lattices`.
4
+
5
+ The goal is not to rewrite the app around a new architecture in one move. The goal is to extract a few reusable primitives that make future features cheaper:
6
+
7
+ - a cmdcmd-style visual window or session switcher
8
+ - one shared action model across hotkeys, palette, voice, daemon, and companion
9
+ - less duplicated window lookup, space lookup, and preview logic
10
+
11
+ ## Why this exists
12
+
13
+ Three pressure points showed up repeatedly:
14
+
15
+ 1. `WindowTiler.swift` acts like several libraries at once.
16
+ 2. action definitions exist in multiple parallel forms.
17
+ 3. overlay and panel shells are repeatedly rebuilt per surface.
18
+
19
+ The result is that new UX surfaces often have to re-solve the same problems:
20
+
21
+ - how to find a target window
22
+ - how to map a user intent to a canonical action
23
+ - how to show a floating interactive surface
24
+ - how to capture and render previews
25
+
26
+ ## Current duplication seams
27
+
28
+ ### 1. Window and session lookup
29
+
30
+ The same session-tagged window matching idea appears in multiple places:
31
+
32
+ - `DesktopModel.windowForSession(...)`
33
+ - tag parsing during desktop polling
34
+ - CG window lookup paths in `WindowTiler`
35
+ - AX window lookup paths in `WindowTiler`
36
+
37
+ This is the strongest candidate for a single reusable locator.
38
+
39
+ ### 2. Space topology and window membership
40
+
41
+ Display-space maps, current-space discovery, and window-space membership are rebuilt in several flows instead of being queried from one read model.
42
+
43
+ This makes space-aware features harder than they need to be:
44
+
45
+ - move window to space
46
+ - present window on the current space
47
+ - show where a session already lives
48
+ - build a visual desktop map
49
+
50
+ ### 3. Window presentation and motion
51
+
52
+ `tile`, `present`, `batchMoveAndRaiseWindows`, and related paths all contain their own versions of:
53
+
54
+ - resolve target
55
+ - move or resize
56
+ - raise
57
+ - activate app
58
+ - mark interaction
59
+
60
+ That sequencing should be owned by one operation layer.
61
+
62
+ ### 4. Preview capture and preview rendering
63
+
64
+ The codebase already has useful preview pieces, but they live in separate pockets:
65
+
66
+ - `WindowPreviewStore` in `HUDRightBar.swift`
67
+ - preview placeholder and preview card variants in HUD
68
+ - separate preview capture in `ScreenMapState.swift`
69
+
70
+ This is a strong signal that preview should become its own reusable subsystem.
71
+
72
+ ### 5. Action definitions
73
+
74
+ Action and intent metadata currently live in several places:
75
+
76
+ - `HotkeyStore.swift`
77
+ - `PaletteCommand.swift`
78
+ - `IntentEngine.swift`
79
+ - `Intents/LatticeIntent.swift`
80
+ - `LatticesApi.swift`
81
+
82
+ The sharpest duplication is that `IntentEngine.swift` and `Intents/LatticeIntent.swift` each define their own intent schema.
83
+
84
+ ### 6. Overlay and panel shells
85
+
86
+ There is already a useful shared primitive for normal app windows in `AppWindowShell.swift`, but overlay surfaces still rebuild similar shell code:
87
+
88
+ - `CommandPaletteWindow.swift`
89
+ - `OmniSearchWindow.swift`
90
+ - `VoiceCommandWindow.swift`
91
+ - `LauncherHUD.swift`
92
+
93
+ The repeated shell concerns are:
94
+
95
+ - `NSPanel` setup
96
+ - blur and rounded-mask container setup
97
+ - screen placement
98
+ - activation and dismissal behavior
99
+ - event monitor lifecycle
100
+
101
+ ## Proposed reusable components
102
+
103
+ This is the target component map.
104
+
105
+ ### Desktop substrate
106
+
107
+ #### `SessionWindowLocator`
108
+
109
+ Responsibility:
110
+
111
+ - resolve a lattices session, title tag, app target, or explicit window id into a canonical window target
112
+ - try fast cache lookup first
113
+ - fall back through CG and AX in one place
114
+
115
+ Why:
116
+
117
+ - removes repeated session-tag matching logic
118
+ - gives palette, daemon, voice, HUD, and future switchers the same targeting rules
119
+
120
+ #### `SpaceTopologySnapshot`
121
+
122
+ Responsibility:
123
+
124
+ - expose a single read model for displays, spaces, current space, and window-to-space membership
125
+
126
+ Why:
127
+
128
+ - prevents repeated recomputation of display-space facts
129
+ - makes space-aware UIs easier to build
130
+
131
+ #### `WindowPresenter`
132
+
133
+ Responsibility:
134
+
135
+ - own the canonical move, resize, raise, activate, and interaction-marking flow
136
+ - support both single-window and batched operations
137
+
138
+ Why:
139
+
140
+ - centralizes the side-effect sequence
141
+ - makes future planners and higher-level actions less fragile
142
+
143
+ #### `WindowPreviewProvider`
144
+
145
+ Responsibility:
146
+
147
+ - capture, cache, and serve still previews or live previews for windows
148
+ - separate capture policy from UI rendering
149
+
150
+ Why:
151
+
152
+ - avoids HUD and Screen Map each inventing preview behavior
153
+ - directly supports a visual selector or session fan-out
154
+
155
+ ### Action substrate
156
+
157
+ #### `ActionRegistry`
158
+
159
+ Responsibility:
160
+
161
+ - define canonical verbs once
162
+ - own parameter metadata, user-facing labels, phrase templates, and execution hooks
163
+
164
+ Minimal shape:
165
+
166
+ ```swift
167
+ enum ActionID: String {
168
+ case openPalette
169
+ case openSearch
170
+ case focusWindow
171
+ case placeWindow
172
+ case launchProject
173
+ case switchLayer
174
+ case killSession
175
+ case refreshProjects
176
+ }
177
+
178
+ struct ActionParam {
179
+ let name: String
180
+ let type: ActionParamType
181
+ let required: Bool
182
+ let values: [String]?
183
+ }
184
+
185
+ struct ActionDef {
186
+ let id: ActionID
187
+ let title: String
188
+ let params: [ActionParam]
189
+ let hotkey: HotkeyMeta?
190
+ let palette: PaletteMeta?
191
+ let phrases: [String]
192
+ let run: (ActionContext) throws -> JSON
193
+ }
194
+ ```
195
+
196
+ Why:
197
+
198
+ - one action identity across hotkeys, palette, voice, daemon, and companion
199
+ - palette rows become runtime bindings of a verb to a target, not bespoke actions
200
+
201
+ #### `ActionContext`
202
+
203
+ Responsibility:
204
+
205
+ - carry structured arguments plus source information like `hotkey`, `palette`, `voice-local`, `daemon`, or `companion`
206
+
207
+ Why:
208
+
209
+ - makes execution and logging more consistent
210
+
211
+ ### Overlay substrate
212
+
213
+ #### `OverlayPanelShell`
214
+
215
+ Responsibility:
216
+
217
+ - build a reusable floating `NSPanel` shell from configuration
218
+ - own blur or plain background, corner radius, window level, collection behavior, and hosting setup
219
+
220
+ Why:
221
+
222
+ - extracts the shared Spotlight-style panel construction path
223
+
224
+ #### `OverlayPlacement`
225
+
226
+ Responsibility:
227
+
228
+ - centralize placement policies like centered, spotlight offset, top-center, or mouse-screen placement
229
+
230
+ Why:
231
+
232
+ - removes repeated `visibleFrame` math
233
+
234
+ #### `OverlayLifecycleController`
235
+
236
+ Responsibility:
237
+
238
+ - own local event monitors, Escape dismissal, deactivate behavior, and cleanup
239
+
240
+ Why:
241
+
242
+ - reduces panel-specific lifecycle glue
243
+
244
+ ### UI primitives
245
+
246
+ #### `WindowPreviewCard`
247
+
248
+ Responsibility:
249
+
250
+ - render a window preview, loading state, and unavailable state consistently
251
+
252
+ Why:
253
+
254
+ - low-risk first UI extraction
255
+ - immediately reduces duplicated HUD preview rendering
256
+
257
+ ## Recommended extraction order
258
+
259
+ The sequence below favors leverage without taking unnecessary risk.
260
+
261
+ ### Slice 1: `WindowPreviewCard`
262
+
263
+ Extract the repeated preview body and placeholder logic from HUD into a shared SwiftUI component.
264
+
265
+ Why first:
266
+
267
+ - UI-only
268
+ - already duplicated
269
+ - does not disturb CGS, AX, or window mutation paths
270
+
271
+ ### Slice 2: `OverlayPanelShell`
272
+
273
+ Extract the shared panel-construction path from `CommandPaletteWindow` and `OmniSearchWindow`.
274
+
275
+ Why second:
276
+
277
+ - those two surfaces are the cleanest near-duplicates
278
+ - builds a reusable shell for a future visual selector
279
+
280
+ ### Slice 3: unify intent schema
281
+
282
+ Remove the parallel intent-definition structures by expanding or reusing the types in `Intents/LatticeIntent.swift` and pointing them at existing execution handlers.
283
+
284
+ Why third:
285
+
286
+ - high leverage
287
+ - removes one entire duplicate definition system
288
+ - proves the registry shape before migrating hotkeys or palette
289
+
290
+ ### Slice 4: `SessionWindowLocator`
291
+
292
+ Centralize session-tagged lookup across DesktopModel and WindowTiler.
293
+
294
+ Why fourth:
295
+
296
+ - strongest desktop duplication seam
297
+ - unlocks cleaner action execution and better future switcher targeting
298
+
299
+ ### Slice 5: `SpaceTopologySnapshot`
300
+
301
+ Create one query layer for display and space topology.
302
+
303
+ Why fifth:
304
+
305
+ - stabilizes space-aware features before touching more motion logic
306
+
307
+ ### Slice 6: `WindowPresenter`
308
+
309
+ Unify move, resize, raise, activate, and interaction-marking flows.
310
+
311
+ Why sixth:
312
+
313
+ - this is higher risk because it sits directly on side effects
314
+ - it is safer after lookup and topology are centralized
315
+
316
+ ### Slice 7: `WindowPreviewProvider`
317
+
318
+ Lift preview capture and caching out of HUD-specific code and reconcile it with Screen Map preview capture.
319
+
320
+ Why seventh:
321
+
322
+ - more useful after overlay shell and preview card exist
323
+ - becomes the substrate for a visual window or session chooser
324
+
325
+ ## Features this should unlock
326
+
327
+ Once the components above exist, the app can add new surfaces with much less bespoke code.
328
+
329
+ ### cmdcmd-style visual switcher
330
+
331
+ Use:
332
+
333
+ - `OverlayPanelShell`
334
+ - `OverlayPlacement`
335
+ - `SessionWindowLocator`
336
+ - `WindowPreviewProvider`
337
+ - `WindowPresenter`
338
+
339
+ Possible behavior:
340
+
341
+ - fan out lattices sessions or all windows
342
+ - show live or cached previews
343
+ - focus, tile, move to space, or close from one surface
344
+
345
+ ### Shared action surfaces
346
+
347
+ Use:
348
+
349
+ - `ActionRegistry`
350
+ - `ActionContext`
351
+
352
+ Possible behavior:
353
+
354
+ - define `placeWindow` once
355
+ - trigger it from voice, hotkey, palette, daemon, or companion
356
+ - keep labels and phrases aligned across surfaces
357
+
358
+ ### Stronger planning and preview
359
+
360
+ Use:
361
+
362
+ - `ActionRegistry`
363
+ - `WindowPresenter`
364
+ - `SpaceTopologySnapshot`
365
+
366
+ Possible behavior:
367
+
368
+ - preview a multi-window action before applying it
369
+ - build transactional-feeling UI around batched movement
370
+
371
+ ## Things not to do yet
372
+
373
+ - do not rewrite `WindowTiler.swift` in one shot
374
+ - do not migrate every overlay surface onto one abstraction immediately
375
+ - do not force the palette to become purely registry-generated before the action model is proven
376
+
377
+ The safer path is:
378
+
379
+ 1. extract small reusable pieces
380
+ 2. move one production surface onto them
381
+ 3. verify behavior
382
+ 4. repeat
383
+
384
+ ## Summary
385
+
386
+ The strongest architectural opportunity here is not one big framework. It is three small substrates:
387
+
388
+ - desktop targeting and motion
389
+ - action definition and routing
390
+ - overlay panel construction
391
+
392
+ If those become reusable, `lattices` gets a much cleaner path to new features without making every new surface solve the same desktop and execution problems again.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lattices/cli",
3
- "version": "0.4.5",
3
+ "version": "0.4.7",
4
4
  "description": "Agentic window manager for macOS — programmable workspace, smart layouts, managed tmux sessions, and a 35+-method agent API",
5
5
  "bin": {
6
6
  "lattices": "./bin/lattices.ts",
@@ -28,6 +28,8 @@
28
28
  },
29
29
  "scripts": {
30
30
  "dev": "bun --cwd docs-site dev",
31
+ "test:e2e": "node --experimental-strip-types --test test/e2e-daemon.test.mjs",
32
+ "test:e2e:voice": "node --experimental-strip-types test/eval-voice.js",
31
33
  "typecheck": "tsc --noEmit",
32
34
  "build:app-bundle": "bash ./bin/lattices-dev build",
33
35
  "prepack": "bash ./bin/lattices-dev build"
@@ -1,134 +0,0 @@
1
- import AppKit
2
- import SwiftUI
3
-
4
- /// NSPanel subclass that accepts key events even without a titlebar
5
- private class KeyablePanel: NSPanel {
6
- override var canBecomeKey: Bool { true }
7
- override var canBecomeMain: Bool { true }
8
- }
9
-
10
- final class CommandPaletteWindow {
11
- static let shared = CommandPaletteWindow()
12
-
13
- private var panel: NSPanel?
14
- private var scanner: ProjectScanner?
15
-
16
- func configure(scanner: ProjectScanner) {
17
- self.scanner = scanner
18
- }
19
-
20
- var isVisible: Bool { panel?.isVisible ?? false }
21
-
22
- func toggle() {
23
- if let p = panel, p.isVisible {
24
- dismiss()
25
- } else {
26
- show()
27
- }
28
- }
29
-
30
- func show() {
31
- // Always rebuild for fresh command state
32
- dismiss()
33
-
34
- guard let scanner = scanner else { return }
35
-
36
- // Ensure projects are up to date (full scan if list is empty,
37
- // e.g. palette opened via hotkey before main popover appeared)
38
- if scanner.projects.isEmpty {
39
- scanner.scan()
40
- } else {
41
- scanner.refreshStatus()
42
- }
43
-
44
- let commands = CommandBuilder.build(scanner: scanner)
45
- let view = CommandPaletteView(commands: commands) { [weak self] in
46
- self?.dismiss()
47
- }
48
- .preferredColorScheme(.dark)
49
-
50
- let hosting = NSHostingView(rootView: view)
51
- hosting.translatesAutoresizingMaskIntoConstraints = false
52
-
53
- let panel = KeyablePanel(
54
- contentRect: NSRect(x: 0, y: 0, width: 540, height: 440),
55
- styleMask: [.nonactivatingPanel],
56
- backing: .buffered,
57
- defer: false
58
- )
59
-
60
- panel.isOpaque = false
61
- panel.backgroundColor = .clear
62
- panel.hasShadow = true
63
- panel.level = .floating
64
- panel.isMovableByWindowBackground = true
65
- panel.hidesOnDeactivate = true
66
- panel.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary]
67
- panel.isReleasedWhenClosed = false
68
-
69
- // Use NSVisualEffectView as contentView with a maskImage to communicate
70
- // the rounded shape to the window server (layer.cornerRadius only clips
71
- // at the view level — the window backing store stays rectangular)
72
- let cornerRadius: CGFloat = 14
73
-
74
- let effectView = NSVisualEffectView()
75
- effectView.blendingMode = .behindWindow
76
- effectView.material = .popover
77
- effectView.state = .active
78
- effectView.wantsLayer = true
79
- effectView.maskImage = Self.maskImage(cornerRadius: cornerRadius)
80
-
81
- panel.contentView = effectView
82
-
83
- effectView.addSubview(hosting)
84
- NSLayoutConstraint.activate([
85
- hosting.leadingAnchor.constraint(equalTo: effectView.leadingAnchor),
86
- hosting.trailingAnchor.constraint(equalTo: effectView.trailingAnchor),
87
- hosting.topAnchor.constraint(equalTo: effectView.topAnchor),
88
- hosting.bottomAnchor.constraint(equalTo: effectView.bottomAnchor),
89
- ])
90
-
91
- // Center horizontally, slightly above vertical center (Spotlight-style)
92
- if let screen = NSScreen.main {
93
- let screenFrame = screen.visibleFrame
94
- let x = screenFrame.midX - 270
95
- let y = screenFrame.midY - 220 + (screenFrame.height * 0.1)
96
- panel.setFrameOrigin(NSPoint(x: x, y: y))
97
- }
98
-
99
- panel.makeKeyAndOrderFront(nil)
100
- NSApp.activate(ignoringOtherApps: true)
101
-
102
- self.panel = panel
103
- AppDelegate.updateActivationPolicy()
104
- }
105
-
106
- func dismiss() {
107
- panel?.orderOut(nil)
108
- panel = nil
109
- AppDelegate.updateActivationPolicy()
110
- }
111
-
112
- /// Stretchable mask image for rounded corners — capInsets preserve the
113
- /// corner arcs while the center stretches to any window size
114
- private static func maskImage(cornerRadius: CGFloat) -> NSImage {
115
- let edgeLength = 2.0 * cornerRadius + 1.0
116
- let maskImage = NSImage(
117
- size: NSSize(width: edgeLength, height: edgeLength),
118
- flipped: false
119
- ) { rect in
120
- let path = NSBezierPath(roundedRect: rect, xRadius: cornerRadius, yRadius: cornerRadius)
121
- NSColor.black.set()
122
- path.fill()
123
- return true
124
- }
125
- maskImage.capInsets = NSEdgeInsets(
126
- top: cornerRadius,
127
- left: cornerRadius,
128
- bottom: cornerRadius,
129
- right: cornerRadius
130
- )
131
- maskImage.resizingMode = .stretch
132
- return maskImage
133
- }
134
- }
@@ -1,165 +0,0 @@
1
- import AppKit
2
- import SwiftUI
3
-
4
- private final class OmniSearchPanel: NSPanel {
5
- override var canBecomeKey: Bool { true }
6
- override var canBecomeMain: Bool { true }
7
-
8
- override func sendEvent(_ event: NSEvent) {
9
- if event.type == .leftMouseDown || event.type == .rightMouseDown {
10
- if !NSApp.isActive {
11
- NSApp.activate(ignoringOtherApps: true)
12
- }
13
- if !isKeyWindow {
14
- makeKey()
15
- }
16
- }
17
- super.sendEvent(event)
18
- }
19
- }
20
-
21
- private final class OmniSearchHostingView<Content: View>: NSHostingView<Content> {
22
- override func acceptsFirstMouse(for event: NSEvent?) -> Bool { true }
23
- override var focusRingType: NSFocusRingType { get { .none } set {} }
24
- }
25
-
26
- final class OmniSearchWindow {
27
- static let shared = OmniSearchWindow()
28
-
29
- private var panel: NSPanel?
30
- private var keyMonitor: Any?
31
- private var state: OmniSearchState?
32
-
33
- var isVisible: Bool { panel?.isVisible ?? false }
34
-
35
- func toggle() {
36
- if isVisible {
37
- dismiss()
38
- } else {
39
- show()
40
- }
41
- }
42
-
43
- func show() {
44
- if let p = panel, p.isVisible {
45
- p.makeKeyAndOrderFront(nil)
46
- NSApp.activate(ignoringOtherApps: true)
47
- return
48
- }
49
-
50
- // Fresh state each time
51
- let searchState = OmniSearchState()
52
- state = searchState
53
-
54
- let view = OmniSearchView(state: searchState) { [weak self] in
55
- self?.dismiss()
56
- }
57
- .preferredColorScheme(.dark)
58
-
59
- let hosting = OmniSearchHostingView(rootView: view)
60
- hosting.translatesAutoresizingMaskIntoConstraints = false
61
-
62
- let p = OmniSearchPanel(
63
- contentRect: NSRect(x: 0, y: 0, width: 520, height: 480),
64
- styleMask: [.titled, .closable, .resizable, .utilityWindow, .nonactivatingPanel],
65
- backing: .buffered,
66
- defer: false
67
- )
68
- p.title = "Search"
69
- p.titlebarAppearsTransparent = true
70
- p.titleVisibility = .hidden
71
- p.isMovableByWindowBackground = true
72
- p.level = .floating
73
- p.isOpaque = false
74
- p.backgroundColor = NSColor(red: 0.11, green: 0.11, blue: 0.12, alpha: 1.0)
75
- p.hasShadow = true
76
- p.appearance = NSAppearance(named: .darkAqua)
77
- p.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary]
78
- p.minSize = NSSize(width: 400, height: 300)
79
- p.maxSize = NSSize(width: 700, height: 700)
80
- p.hidesOnDeactivate = false
81
- p.becomesKeyOnlyIfNeeded = false
82
-
83
- let effectView = NSVisualEffectView()
84
- effectView.blendingMode = .behindWindow
85
- effectView.material = .popover
86
- effectView.state = .active
87
- effectView.wantsLayer = true
88
- effectView.maskImage = Self.maskImage(cornerRadius: 14)
89
- p.contentView = effectView
90
-
91
- effectView.addSubview(hosting)
92
- NSLayoutConstraint.activate([
93
- hosting.leadingAnchor.constraint(equalTo: effectView.leadingAnchor),
94
- hosting.trailingAnchor.constraint(equalTo: effectView.trailingAnchor),
95
- hosting.topAnchor.constraint(equalTo: effectView.topAnchor),
96
- hosting.bottomAnchor.constraint(equalTo: effectView.bottomAnchor),
97
- ])
98
-
99
- // Center on screen
100
- if let screen = NSScreen.main {
101
- let visibleFrame = screen.visibleFrame
102
- let x = visibleFrame.midX - 260
103
- let y = visibleFrame.midY + 60 // slightly above center
104
- p.setFrameOrigin(NSPoint(x: x, y: y))
105
- }
106
-
107
- p.makeKeyAndOrderFront(nil)
108
- NSApp.activate(ignoringOtherApps: true)
109
- panel = p
110
-
111
- // Key monitor: Escape → dismiss, arrow keys → navigate, Enter → activate
112
- keyMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { [weak self] event in
113
- guard self?.panel?.isKeyWindow == true else { return event }
114
-
115
- switch event.keyCode {
116
- case 53: // Escape
117
- self?.dismiss()
118
- return nil
119
- case 125: // ↓
120
- self?.state?.moveSelection(1)
121
- return nil
122
- case 126: // ↑
123
- self?.state?.moveSelection(-1)
124
- return nil
125
- case 36: // Enter
126
- self?.state?.activateSelected()
127
- self?.dismiss()
128
- return nil
129
- default:
130
- return event
131
- }
132
- }
133
- }
134
-
135
- func dismiss() {
136
- panel?.orderOut(nil)
137
- panel = nil
138
- state = nil
139
- if let monitor = keyMonitor {
140
- NSEvent.removeMonitor(monitor)
141
- keyMonitor = nil
142
- }
143
- }
144
-
145
- private static func maskImage(cornerRadius: CGFloat) -> NSImage {
146
- let edgeLength = 2.0 * cornerRadius + 1.0
147
- let maskImage = NSImage(
148
- size: NSSize(width: edgeLength, height: edgeLength),
149
- flipped: false
150
- ) { rect in
151
- let path = NSBezierPath(roundedRect: rect, xRadius: cornerRadius, yRadius: cornerRadius)
152
- NSColor.black.set()
153
- path.fill()
154
- return true
155
- }
156
- maskImage.capInsets = NSEdgeInsets(
157
- top: cornerRadius,
158
- left: cornerRadius,
159
- bottom: cornerRadius,
160
- right: cornerRadius
161
- )
162
- maskImage.resizingMode = .stretch
163
- return maskImage
164
- }
165
- }
File without changes