@arach/lattices 0.2.1 → 0.6.1

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 (143) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +144 -69
  3. package/apps/mac/Info.plist +43 -0
  4. package/apps/mac/Lattices.app/Contents/Info.plist +43 -0
  5. package/apps/mac/Lattices.app/Contents/MacOS/Lattices +0 -0
  6. package/apps/mac/Lattices.app/Contents/Resources/AppIcon.icns +0 -0
  7. package/apps/mac/Lattices.app/Contents/Resources/docs/assistant-knowledge.md +130 -0
  8. package/apps/mac/Lattices.app/Contents/Resources/tap.wav +0 -0
  9. package/apps/mac/Lattices.app/Contents/_CodeSignature/CodeResources +150 -0
  10. package/apps/mac/Lattices.entitlements +21 -0
  11. package/apps/mac/Resources/Pets/assistant-spark/pet.json +62 -0
  12. package/apps/mac/Resources/Pets/assistant-spark/spritesheet.webp +0 -0
  13. package/apps/mac/Resources/Pets/scout-ranger/pet.json +6 -0
  14. package/apps/mac/Resources/Pets/scout-ranger/spritesheet.webp +0 -0
  15. package/apps/mac/Resources/tap.wav +0 -0
  16. package/assets/AppIcon.icns +0 -0
  17. package/bin/assistant-intelligence.ts +912 -0
  18. package/bin/cli/capture.ts +252 -0
  19. package/bin/cli/daemon.ts +22 -0
  20. package/bin/cli/helpers.ts +105 -0
  21. package/bin/cli/layer.ts +178 -0
  22. package/bin/cli/runs.ts +43 -0
  23. package/bin/cli/search.ts +141 -0
  24. package/bin/cli/session.ts +32 -0
  25. package/bin/client.ts +17 -0
  26. package/bin/cua.ts +26 -0
  27. package/bin/{daemon-client.js → daemon-client.ts} +49 -30
  28. package/bin/handsoff-infer.ts +96 -0
  29. package/bin/handsoff-worker.ts +531 -0
  30. package/bin/infer.ts +424 -0
  31. package/bin/keychain.ts +75 -0
  32. package/bin/lattices-app.ts +655 -0
  33. package/bin/lattices-build +125 -0
  34. package/bin/lattices-build-env.ts +77 -0
  35. package/bin/lattices-dev +362 -0
  36. package/bin/lattices.ts +3260 -0
  37. package/bin/project-twin.ts +645 -0
  38. package/docs/agent-execution-plan.md +562 -0
  39. package/docs/agent-layer-guide.md +207 -0
  40. package/docs/agents.md +233 -0
  41. package/docs/ai-chat-ux-review.md +416 -0
  42. package/docs/api.md +1041 -47
  43. package/docs/app.md +96 -13
  44. package/docs/assistant-knowledge.md +130 -0
  45. package/docs/companion-deck.md +209 -0
  46. package/docs/component-extraction-roadmap.md +392 -0
  47. package/docs/concepts.md +13 -12
  48. package/docs/config.md +83 -10
  49. package/docs/gesture-customization-proposal.md +520 -0
  50. package/docs/handsoff-test-scenarios.md +84 -0
  51. package/docs/hyperspace-grid-snappiness.md +210 -0
  52. package/docs/layers.md +176 -28
  53. package/docs/mouse-gestures.md +244 -0
  54. package/docs/ocr.md +21 -9
  55. package/docs/overview.md +42 -23
  56. package/docs/presentation-execution-review.md +491 -0
  57. package/docs/prompts/hands-off-system.md +382 -0
  58. package/docs/prompts/hands-off-turn.md +30 -0
  59. package/docs/prompts/voice-advisor.md +31 -0
  60. package/docs/prompts/voice-fallback.md +23 -0
  61. package/docs/proposals/LAT-001-gesture-visual-customization.md +522 -0
  62. package/docs/proposals/LAT-002-shared-overlay-canvas.md +353 -0
  63. package/docs/proposals/LAT-003-menu-bar-controller-architecture.md +291 -0
  64. package/docs/proposals/LAT-004-interactive-overlay-actors.md +534 -0
  65. package/docs/proposals/LAT-005-action-runtime-product-spine.md +914 -0
  66. package/docs/proposals/LAT-006-followup-gaps.md +103 -0
  67. package/docs/proposals/LAT-006-runs-and-capture-in-lattices.md +566 -0
  68. package/docs/proposals/LAT-007-unified-app-shell.md +128 -0
  69. package/docs/quickstart.md +8 -12
  70. package/docs/reference/dewey.config.ts +74 -0
  71. package/docs/reference/install-agent.md +79 -0
  72. package/docs/release.md +172 -0
  73. package/docs/repo-structure.md +100 -0
  74. package/docs/terminal-kit.md +87 -0
  75. package/docs/tiling-reference.md +224 -0
  76. package/docs/twins.md +138 -0
  77. package/docs/voice-command-protocol.md +278 -0
  78. package/docs/voice-error-model.md +73 -0
  79. package/docs/voice.md +221 -0
  80. package/package.json +69 -16
  81. package/packages/npm/sdk/cua.d.mts +1 -0
  82. package/packages/npm/sdk/cua.d.ts +188 -0
  83. package/packages/npm/sdk/cua.mjs +376 -0
  84. package/app/Lattices.app/Contents/Info.plist +0 -24
  85. package/app/Package.swift +0 -13
  86. package/app/Sources/ActionRow.swift +0 -61
  87. package/app/Sources/App.swift +0 -10
  88. package/app/Sources/AppDelegate.swift +0 -234
  89. package/app/Sources/AppShellView.swift +0 -62
  90. package/app/Sources/AppTypeClassifier.swift +0 -70
  91. package/app/Sources/AppWindowShell.swift +0 -63
  92. package/app/Sources/CheatSheetHUD.swift +0 -332
  93. package/app/Sources/CommandModeState.swift +0 -1362
  94. package/app/Sources/CommandModeView.swift +0 -1405
  95. package/app/Sources/CommandModeWindow.swift +0 -192
  96. package/app/Sources/CommandPaletteView.swift +0 -307
  97. package/app/Sources/CommandPaletteWindow.swift +0 -134
  98. package/app/Sources/DaemonProtocol.swift +0 -101
  99. package/app/Sources/DaemonServer.swift +0 -414
  100. package/app/Sources/DesktopModel.swift +0 -121
  101. package/app/Sources/DesktopModelTypes.swift +0 -71
  102. package/app/Sources/DiagnosticLog.swift +0 -271
  103. package/app/Sources/EventBus.swift +0 -30
  104. package/app/Sources/HotkeyManager.swift +0 -250
  105. package/app/Sources/HotkeyStore.swift +0 -338
  106. package/app/Sources/InventoryManager.swift +0 -35
  107. package/app/Sources/InventoryPath.swift +0 -43
  108. package/app/Sources/KeyRecorderView.swift +0 -210
  109. package/app/Sources/LatticesApi.swift +0 -1125
  110. package/app/Sources/MainView.swift +0 -467
  111. package/app/Sources/MainWindow.swift +0 -83
  112. package/app/Sources/OcrModel.swift +0 -309
  113. package/app/Sources/OcrStore.swift +0 -295
  114. package/app/Sources/OmniSearchState.swift +0 -283
  115. package/app/Sources/OmniSearchView.swift +0 -288
  116. package/app/Sources/OmniSearchWindow.swift +0 -105
  117. package/app/Sources/OrphanRow.swift +0 -129
  118. package/app/Sources/PaletteCommand.swift +0 -419
  119. package/app/Sources/PermissionChecker.swift +0 -125
  120. package/app/Sources/Preferences.swift +0 -92
  121. package/app/Sources/ProcessModel.swift +0 -199
  122. package/app/Sources/ProcessQuery.swift +0 -151
  123. package/app/Sources/Project.swift +0 -28
  124. package/app/Sources/ProjectRow.swift +0 -368
  125. package/app/Sources/ProjectScanner.swift +0 -121
  126. package/app/Sources/ScreenMapState.swift +0 -2387
  127. package/app/Sources/ScreenMapView.swift +0 -2820
  128. package/app/Sources/ScreenMapWindowController.swift +0 -89
  129. package/app/Sources/SessionManager.swift +0 -72
  130. package/app/Sources/SettingsView.swift +0 -1053
  131. package/app/Sources/SettingsWindow.swift +0 -20
  132. package/app/Sources/TabGroupRow.swift +0 -178
  133. package/app/Sources/Terminal.swift +0 -259
  134. package/app/Sources/TerminalQuery.swift +0 -156
  135. package/app/Sources/TerminalSynthesizer.swift +0 -200
  136. package/app/Sources/Theme.swift +0 -163
  137. package/app/Sources/TilePickerView.swift +0 -209
  138. package/app/Sources/TmuxModel.swift +0 -53
  139. package/app/Sources/TmuxQuery.swift +0 -81
  140. package/app/Sources/WindowTiler.swift +0 -1755
  141. package/app/Sources/WorkspaceManager.swift +0 -434
  142. package/bin/lattices-app.js +0 -221
  143. package/bin/lattices.js +0 -1418
@@ -0,0 +1,353 @@
1
+ # LAT-002: Shared Per-Screen Overlay Canvas
2
+
3
+ ## Status
4
+
5
+ Partially implemented.
6
+
7
+ The shared canvas primitive exists in `ScreenOverlayCanvasController`, and drag-snap zones now publish `snapZones` layers into it. Mouse gesture visuals, passive hotkey hints, and broader overlay consolidation are still pending.
8
+
9
+ This document proposes a shared, click-through, per-screen overlay canvas for Lattices. The canvas would provide one persistent visual layer per display that features can draw into without each feature creating its own overlay window stack.
10
+
11
+ ## Summary
12
+
13
+ Lattices already has several overlay-like systems:
14
+
15
+ - `HUDController` prebuilds HUD panels, keeps them ordered, and toggles visibility with alpha.
16
+ - `WindowDragSnapController` creates full-screen snap-zone panels per screen as needed.
17
+ - `MouseGestureController` creates transient gesture overlay panels for gesture trails and results.
18
+ - `MouseFinder`, `WindowTiler`, and Screen Map have their own small overlay and highlight mechanisms.
19
+
20
+ These are useful, but they are still separate surfaces. Each one owns its own window lifecycle, z-order behavior, coordinate mapping, visibility rules, and cleanup. That makes every new ambient visual feature slightly more expensive than it should be.
21
+
22
+ The proposed primitive is a `ScreenOverlayCanvas`: one transparent, click-through, always-on-top window per screen, owned by a central controller. Features publish lightweight visual layers into that canvas. The canvas stays passive by default and never becomes the place where input semantics or actions are decided.
23
+
24
+ ## Why Now
25
+
26
+ Recent exploration of Clicky's macOS overlay approach highlighted a simpler mental model: create one full-screen transparent window per display, make it click-through, and draw small dynamic UI elements inside it. The visible object can be tiny and contextual, but the rendering surface is stable and screen-sized.
27
+
28
+ That model maps well to several Lattices ideas:
29
+
30
+ - snap drag target areas
31
+ - passive hotkey information
32
+ - layer and workspace hints
33
+ - mouse gesture trails
34
+ - focus and target highlights
35
+ - command-mode affordances
36
+ - window-map annotations
37
+
38
+ Lattices already has the hard parts in pieces. The value is in unifying the canvas primitive so features stop reinventing overlay windows.
39
+
40
+ ## Current State
41
+
42
+ ### HUD
43
+
44
+ `apps/mac/Sources/Core/Overlays/HUD/HUDController.swift` already uses a warm, persistent model:
45
+
46
+ 1. Panels are prebuilt.
47
+ 2. Panels stay ordered in the window server.
48
+ 3. Hidden state is mostly `alphaValue = 0`.
49
+ 4. Showing the HUD is an immediate alpha flip plus focus handling.
50
+ 5. Data refresh happens after first paint.
51
+
52
+ This makes the HUD feel fast. But the HUD is a set of segmented panels: top, bottom, left, right, preview, and minimap panels. It is interactive and cockpit-like, not a general screen canvas.
53
+
54
+ ### Drag Snap
55
+
56
+ `apps/mac/Sources/Core/Desktop/WindowDragSnapController.swift` creates `WindowSnapOverlayPanel` instances keyed by screen. Each panel covers a whole screen, ignores mouse events, joins all Spaces, and draws snap zones into an `NSView`.
57
+
58
+ This is very close to the proposed canvas, but it is owned by drag snap and only exists for that feature's model.
59
+
60
+ ### Mouse Gestures
61
+
62
+ `apps/mac/Sources/Core/Input/MouseGestureController.swift` creates a `MouseGestureOverlay` for a gesture session. It draws path feedback, recognition feedback, and completion labels. It is intentionally close to the input/action fast path, but its rendering should remain decorative.
63
+
64
+ ### Overlay Shells
65
+
66
+ `apps/mac/Sources/Core/Overlays/OverlayPanelShell.swift` helps normal panel-based surfaces, but it is optimized for discrete overlay panels, not full-screen passive visual layers.
67
+
68
+ ## Proposal
69
+
70
+ Create a shared `ScreenOverlayCanvasController` responsible for one click-through overlay window per `NSScreen`.
71
+
72
+ The controller should provide:
73
+
74
+ - stable full-screen transparent windows, one per display
75
+ - local coordinate mapping for each screen
76
+ - feature-scoped visual layer registration
77
+ - cheap show/hide/update calls
78
+ - deterministic z-order within the canvas
79
+ - automatic screen-change reconciliation
80
+ - explicit cleanup when features end
81
+
82
+ The overlay windows should be:
83
+
84
+ - borderless
85
+ - transparent
86
+ - non-activating
87
+ - click-through
88
+ - not key or main windows
89
+ - able to join all Spaces
90
+ - available in fullscreen auxiliary contexts where practical
91
+ - high enough level for passive visual feedback, but configurable if a surface needs a lower level
92
+
93
+ Conceptually:
94
+
95
+ ```swift
96
+ @MainActor
97
+ final class ScreenOverlayCanvasController {
98
+ static let shared = ScreenOverlayCanvasController()
99
+
100
+ func warmUp()
101
+ func reconcileScreens()
102
+ func publishLayer(_ layer: ScreenOverlayLayerSnapshot)
103
+ func removeLayer(id: ScreenOverlayLayerID)
104
+ func removeLayers(owner: ScreenOverlayOwner)
105
+ }
106
+ ```
107
+
108
+ The first implementation can be AppKit-first: an `NSPanel` or `NSWindow` per screen hosting a custom `NSView` that draws layer snapshots. SwiftUI hosting can come later for feature surfaces that benefit from it, but the lowest-level canvas should stay simple.
109
+
110
+ ## Non-Goals
111
+
112
+ This proposal does not make the shared canvas interactive.
113
+
114
+ Clickable UI should remain in separate panels or app windows. The shared canvas is for passive visual information and transient affordances. This keeps it safe: it should never steal clicks, focus, keyboard input, or app activation.
115
+
116
+ This proposal also does not move action dispatch into the canvas. Features decide behavior elsewhere and publish visual state into the canvas.
117
+
118
+ ## Layer Model
119
+
120
+ Layers should be declarative snapshots, not live feature-owned views by default.
121
+
122
+ Good examples:
123
+
124
+ - `snapZones`: trigger rects, hovered zone, preview rect
125
+ - `gestureTrail`: path points, recognized shape, result label
126
+ - `focusHighlight`: screen rect, color, pulse style, timeout
127
+ - `hotkeyHints`: compact labels and positions
128
+ - `layerStatus`: current layer, running sessions, stale sessions
129
+
130
+ Each snapshot should include:
131
+
132
+ - stable layer id
133
+ - owner
134
+ - target screen id, or all screens
135
+ - z-index within the canvas
136
+ - visibility/opacity
137
+ - payload enum
138
+ - optional expiry
139
+
140
+ Example shape:
141
+
142
+ ```swift
143
+ struct ScreenOverlayLayerSnapshot: Equatable {
144
+ let id: ScreenOverlayLayerID
145
+ let owner: ScreenOverlayOwner
146
+ let screen: ScreenOverlayScreenTarget
147
+ let zIndex: Int
148
+ let opacity: CGFloat
149
+ let payload: ScreenOverlayPayload
150
+ let expiresAt: Date?
151
+ }
152
+
153
+ enum ScreenOverlayPayload: Equatable {
154
+ case snapZones(SnapZoneOverlayPayload)
155
+ case gestureTrail(GestureTrailOverlayPayload)
156
+ case highlight(HighlightOverlayPayload)
157
+ case hotkeyHints(HotkeyHintOverlayPayload)
158
+ }
159
+ ```
160
+
161
+ This is intentionally plain. The canvas can render it synchronously without asking feature controllers for more state.
162
+
163
+ ## Coordinate Rules
164
+
165
+ The canvas should make coordinate handling boring:
166
+
167
+ - external feature APIs accept global AppKit screen coordinates unless explicitly documented otherwise
168
+ - each screen view receives local coordinates derived from its `NSScreen.frame`
169
+ - Y-axis conversion is centralized
170
+ - screen identity uses `NSScreenNumber` when available
171
+ - all features use the same screen id helper
172
+
173
+ This matters because Lattices currently touches AppKit, CoreGraphics, AX, and ScreenCaptureKit coordinate systems. A shared canvas should reduce the number of local conversions.
174
+
175
+ ## Snap Drag Migration
176
+
177
+ Snap drag is the best first adopter.
178
+
179
+ Today, `WindowDragSnapController` already computes:
180
+
181
+ - trigger rects
182
+ - visible label rects
183
+ - preview rects
184
+ - hovered zone
185
+ - screen grouping
186
+
187
+ Instead of owning `WindowSnapOverlayPanel`, it can publish a `snapZones` layer:
188
+
189
+ 1. Drag begins and modifier mode is active.
190
+ 2. Controller resolves zones as it does today.
191
+ 3. Controller publishes `snapZones` snapshots grouped by screen.
192
+ 4. Mouse movement updates the hovered zone and preview rect.
193
+ 5. Mouse up removes the layer and dispatches tiling as today.
194
+
195
+ The drag-snap action path should not change.
196
+
197
+ ## HUD Relationship
198
+
199
+ The HUD should not be moved wholesale onto the shared canvas.
200
+
201
+ The HUD is interactive: search, selection, keyboard focus, previews, sidebars. It should remain panel-based. But the shared canvas can support HUD-adjacent passive information:
202
+
203
+ - pre-HUD hotkey hints
204
+ - layer status while a modifier is held
205
+ - ambient screen map outlines
206
+ - tile target previews before full HUD activation
207
+ - “what will happen if I drop here” hints
208
+
209
+ Think of the canvas as the quiet always-ready visual substrate. The HUD remains the cockpit.
210
+
211
+ ## Mouse Gesture Relationship
212
+
213
+ Mouse gesture recognition and event suppression must remain outside the canvas.
214
+
215
+ The gesture controller can publish trail snapshots and completion markers to the canvas, but:
216
+
217
+ - event tap callbacks still decide pass-through vs swallow
218
+ - shape recognition stays in the gesture pipeline
219
+ - action dispatch stays native and immediate
220
+ - visual renderer failure cannot affect action behavior
221
+
222
+ This dovetails with `LAT-001`: gesture visuals become consumers of semantic snapshots, not owners of recognition state.
223
+
224
+ ## Hotkey Overlay Mode
225
+
226
+ A high-value use case is a passive hotkey layer.
227
+
228
+ When the user holds a configured modifier chord, Lattices could fade in lightweight context:
229
+
230
+ - current layer
231
+ - active project/session names
232
+ - snap zones
233
+ - mouse gesture affordances
234
+ - focused window identity
235
+ - available drop targets
236
+ - voice or command-mode status
237
+
238
+ This would feel different from opening the HUD. It is not a modal command surface. It is a heads-up layer that answers, “what can I do right now?”
239
+
240
+ If the user releases the chord, the layer fades away. If the user continues into an action, the relevant feature can take over and update its layer.
241
+
242
+ ## Window Level
243
+
244
+ The canvas should probably start at a high but conservative level.
245
+
246
+ Existing snap overlays use `CGWindowLevelForKey(.maximumWindow)`. Clicky uses `.screenSaver`. Both work, but they should not become an accidental default for every future surface.
247
+
248
+ The canvas controller should centralize this decision and document it. A first version can use the level already proven by snap overlays, then adjust after testing with:
249
+
250
+ - Terminal.app
251
+ - iTerm2
252
+ - browser fullscreen
253
+ - Stage Manager
254
+ - fullscreen Spaces
255
+ - mission control edge cases
256
+
257
+ ## Performance Rules
258
+
259
+ The canvas should be safe to keep warm:
260
+
261
+ - no polling when no layers are visible
262
+ - no timers inside the canvas unless a visible layer requires animation
263
+ - no filesystem reads during drawing
264
+ - no network access
265
+ - no feature callbacks during draw
266
+ - coalesce rapid updates per run loop
267
+ - keep payloads small and value-like
268
+
269
+ Animations should be bounded and cancelable. Expiring layers should clean themselves up even if a feature forgets to remove them.
270
+
271
+ ## Failure Model
272
+
273
+ Overlay failure should be boring.
274
+
275
+ If the canvas cannot create a window, the feature should still run without visuals.
276
+
277
+ If one screen fails, other screens should continue.
278
+
279
+ If a payload cannot be rendered, the canvas should skip that layer and log a diagnostic.
280
+
281
+ If screen topology changes, the controller should reconcile windows and drop or remap stale screen-targeted layers.
282
+
283
+ The shared canvas must not:
284
+
285
+ - block input
286
+ - steal focus
287
+ - leave opaque windows stuck on screen
288
+ - make actions depend on rendering
289
+ - crash if a feature publishes malformed or stale visual state
290
+
291
+ ## Implementation Plan
292
+
293
+ ### Phase 1: Canvas Primitive
294
+
295
+ - Add `ScreenOverlayCanvasController`.
296
+ - Add a simple `ScreenOverlayWindow` or `ScreenOverlayPanel`.
297
+ - Add screen identity and coordinate helpers.
298
+ - Add a minimal `ScreenOverlayView` that can render `highlight` and `snapZones` payloads.
299
+ - Warm up windows at app launch, hidden and click-through.
300
+
301
+ ### Phase 2: Drag Snap Adoption
302
+
303
+ - Replace `WindowSnapOverlayPanel` ownership with canvas snapshots.
304
+ - Keep existing snap-zone geometry and tiling behavior.
305
+ - Verify multi-monitor drag, fullscreen Spaces, and modifier toggling.
306
+ - Remove old snap overlay panel code after parity.
307
+
308
+ ### Phase 3: Gesture Visual Adoption
309
+
310
+ - Publish gesture path snapshots from `MouseGestureController`.
311
+ - Move native gesture trail drawing into the canvas renderer.
312
+ - Keep recognition/action dispatch untouched.
313
+ - Align with `LAT-001` renderer hooks.
314
+
315
+ ### Phase 4: Passive Hotkey Layer
316
+
317
+ - Add a read-only modifier watcher for a passive “show context” chord.
318
+ - Publish layer/session/window hints into the canvas.
319
+ - Fade in/out quickly without activating Lattices.
320
+ - Keep interactive HUD activation separate.
321
+
322
+ ### Phase 5: Consolidation
323
+
324
+ - Identify duplicate overlay/highlight windows that can become canvas payloads.
325
+ - Keep interactive panels on `OverlayPanelShell`.
326
+ - Document which surfaces should use the canvas and which should remain separate.
327
+
328
+ ## Acceptance Criteria
329
+
330
+ - One shared controller owns screen-sized click-through overlay windows.
331
+ - Snap zones render through the shared canvas with no behavior regression.
332
+ - Canvas rendering survives multiple monitors and screen changes.
333
+ - Visual layers can be added/removed by owner id.
334
+ - Hidden canvas windows do not intercept clicks or focus.
335
+ - Drag snap still tiles correctly when visual rendering is disabled.
336
+ - Mouse gestures can publish visual feedback without action dispatch depending on it.
337
+ - Diagnostics make canvas creation, screen reconciliation, and render failures understandable.
338
+
339
+ ## Open Questions
340
+
341
+ - Should the base window level match current snap overlays or use a lower default?
342
+ - Should the canvas use one `NSView` renderer first, or host SwiftUI layers from the start?
343
+ - How should expiry be modeled: controller-owned timers, per-layer deadlines, or both?
344
+ - Should passive hotkey overlays live in the app config or user workspace config?
345
+ - How much of `MouseFinder` and window highlight feedback should migrate to the canvas?
346
+
347
+ ## References
348
+
349
+ - `apps/mac/Sources/Core/Overlays/HUD/HUDController.swift`
350
+ - `apps/mac/Sources/Core/Desktop/WindowDragSnapController.swift`
351
+ - `apps/mac/Sources/Core/Input/MouseGestureController.swift`
352
+ - `apps/mac/Sources/Core/Overlays/OverlayPanelShell.swift`
353
+ - `docs/proposals/LAT-001-gesture-visual-customization.md`
@@ -0,0 +1,291 @@
1
+ # LAT-003: Menu Bar Controller Architecture
2
+
3
+ ## Status
4
+
5
+ Mostly implemented.
6
+
7
+ The menu bar, hotkey registration, daemon/service startup, workspace-inspector presentation, and activation-policy ownership have been extracted from `AppDelegate`. The optional `MenuBarPanelShell` remains deferred until a concrete menu-bar-adjacent panel needs it.
8
+
9
+ This document records a narrow architecture proposal for the Lattices menu bar surface. The product direction is deliberately conservative: keep the existing menu bar UX, which is already strong, and extract the architectural lessons that make future menu-adjacent surfaces easier to build.
10
+
11
+ ## Summary
12
+
13
+ Lattices currently uses `NSStatusItem` directly from `AppDelegate`, with a cached `NSPopover`, a right-click context menu, prewarmed SwiftUI content, global hotkey setup, service startup, daemon boot, and onboarding checks all coordinated from the same launch object.
14
+
15
+ That works, and the visible menu bar experience should remain intact.
16
+
17
+ The improvement proposed here is structural:
18
+
19
+ - split menu bar ownership out of `AppDelegate`
20
+ - keep the current `NSPopover` for the main projects surface
21
+ - use custom `NSPanel` only for menu-bar-adjacent surfaces that need exact focus, positioning, or dismissal behavior
22
+ - keep activation policy updates centralized
23
+ - make app startup easier to reason about by moving hotkeys and services into small bootstrap components
24
+
25
+ The result should be less launch-time sprawl, not a new visual design.
26
+
27
+ ## Why Now
28
+
29
+ Recent review of Clicky's menu bar implementation highlighted a useful pattern:
30
+
31
+ - `NSStatusItem` owns the menu bar icon
32
+ - a custom borderless `NSPanel` owns the transient control panel
33
+ - click-outside dismissal is explicit
34
+ - panel focus behavior is explicitly defined
35
+ - the app avoids standard popover chrome when it needs full control
36
+
37
+ Lattices does not need to copy that wholesale. Our `NSPopover` is a good fit for the main menu bar projects view, and it already gives us useful system behavior.
38
+
39
+ The lesson is more about ownership. Clicky's menu bar controller has one job. Lattices' `AppDelegate` currently has many.
40
+
41
+ ## Current State
42
+
43
+ The relevant code is in `apps/mac/Sources/AppShell/AppDelegate.swift`.
44
+
45
+ Today, `AppDelegate` owns:
46
+
47
+ - app activation policy decisions
48
+ - status item creation
49
+ - menu bar icon drawing
50
+ - left-click popover toggling
51
+ - right-click context menu construction
52
+ - popover prewarming
53
+ - global hotkey registration
54
+ - command palette configuration
55
+ - drag snap, mouse gesture, keyboard remap startup
56
+ - layer hotkey registration
57
+ - tiling hotkey registration
58
+ - onboarding and permission checks
59
+ - daemon/model service startup
60
+ - updater checks
61
+ - debug launch flags
62
+
63
+ This is not a correctness problem by itself, but it makes the app entry point harder to change. Any new launch-time surface adds one more responsibility to a file that already coordinates too much.
64
+
65
+ ## Non-Goals
66
+
67
+ This proposal does not redesign the menu bar UI.
68
+
69
+ This proposal does not replace the main menu bar `NSPopover` with a custom `NSPanel` by default.
70
+
71
+ This proposal does not change command palette, HUD, Screen Map, or daemon behavior.
72
+
73
+ This proposal does not alter the app's existing visual identity.
74
+
75
+ ## Proposed Components
76
+
77
+ ### 1. `MenuBarController`
78
+
79
+ Owns only menu bar behavior:
80
+
81
+ - create and retain `NSStatusItem`
82
+ - draw or load the status item icon
83
+ - route left-click and right-click behavior
84
+ - own the cached projects popover
85
+ - own the context menu
86
+ - expose `dismissPopover()`
87
+ - publish menu bar visibility state needed for activation policy
88
+
89
+ The existing popover behavior should move here almost unchanged:
90
+
91
+ - lazy `NSPopover` creation
92
+ - SwiftUI content prewarm
93
+ - `.transient` behavior
94
+ - dark appearance
95
+ - `latticesPopoverWillShow` notification
96
+
97
+ This keeps the UX stable while making ownership clearer.
98
+
99
+ ### 2. `AppActivationCoordinator`
100
+
101
+ Owns activation policy decisions.
102
+
103
+ Today `AppDelegate.updateActivationPolicy()` checks a list of surfaces directly. That should become an explicit coordinator that asks registered surfaces whether they are visible.
104
+
105
+ Conceptually:
106
+
107
+ ```swift
108
+ protocol AppVisibleSurface {
109
+ var isVisibleForActivationPolicy: Bool { get }
110
+ }
111
+
112
+ final class AppActivationCoordinator {
113
+ static let shared = AppActivationCoordinator()
114
+
115
+ func register(_ surface: AppVisibleSurface)
116
+ func refresh()
117
+ }
118
+ ```
119
+
120
+ This avoids long hard-coded conditionals in `AppDelegate` as new windows are added.
121
+
122
+ ### 3. `HotkeyBootstrap`
123
+
124
+ Owns global hotkey registration.
125
+
126
+ This includes:
127
+
128
+ - command palette
129
+ - unified window or Screen Map
130
+ - HUD
131
+ - voice command
132
+ - Hands Off
133
+ - mouse finder
134
+ - session layers
135
+ - tiling commands
136
+ - organize mode
137
+ - OmniSearch
138
+
139
+ The point is not to abstract the hotkeys away. The point is to isolate the big registration block into a component whose only job is binding actions to the existing controllers.
140
+
141
+ ### 4. `AppServicesBootstrap`
142
+
143
+ Owns daemon and model startup:
144
+
145
+ - `OcrStore`
146
+ - `DesktopModel`
147
+ - `OcrModel`
148
+ - `TmuxModel`
149
+ - `ProcessModel`
150
+ - `LatticesApi`
151
+ - `DaemonServer`
152
+ - companion bridge
153
+ - agent pool
154
+
155
+ This keeps service startup grouped and timed without mixing it into menu bar construction.
156
+
157
+ ### 5. Optional `MenuBarPanelShell`
158
+
159
+ Keep `NSPopover` for the current project list.
160
+
161
+ Add a small helper only if we introduce menu-bar-adjacent panels that need custom behavior:
162
+
163
+ - borderless `NSPanel`
164
+ - explicit click-outside dismissal
165
+ - non-activating or keyable variants
166
+ - manual positioning below the status item
167
+ - exact sizing around SwiftUI content
168
+
169
+ This helper should not replace `OverlayPanelShell`. It is specific to status-item anchored panels.
170
+
171
+ ## Popover vs Panel Rule
172
+
173
+ Use the cached `NSPopover` when the surface is:
174
+
175
+ - anchored to the menu bar icon
176
+ - mostly standard transient menu behavior
177
+ - comfortable with popover sizing and dismissal semantics
178
+ - part of the existing projects/menu surface
179
+
180
+ Use a custom `NSPanel` when the surface needs:
181
+
182
+ - non-standard chrome
183
+ - unusual animation
184
+ - explicit click-outside rules
185
+ - custom focus behavior
186
+ - independent window level
187
+ - tighter control over multi-Space behavior
188
+
189
+ This is the main lesson from Clicky: use `NSPanel` where control matters. Do not replace a good `NSPopover` just to be clever.
190
+
191
+ ## Suggested File Shape
192
+
193
+ ```text
194
+ apps/mac/Sources/AppShell/
195
+ AppDelegate.swift
196
+ MenuBarController.swift
197
+ AppActivationCoordinator.swift
198
+ HotkeyBootstrap.swift
199
+ AppServicesBootstrap.swift
200
+ MenuBarPanelShell.swift # optional, only when needed
201
+ ```
202
+
203
+ `AppDelegate` should become a launch conductor:
204
+
205
+ 1. set activation policy and appearance
206
+ 2. create `MenuBarController`
207
+ 3. register hotkeys
208
+ 4. start input controllers
209
+ 5. run onboarding or permission checks
210
+ 6. start daemon services
211
+ 7. process debug flags
212
+
213
+ It should not own the details of each of those systems.
214
+
215
+ ## Migration Plan
216
+
217
+ ### Phase 1: Extract `MenuBarController`
218
+
219
+ - Move status item creation.
220
+ - Move menu icon creation.
221
+ - Move context menu construction.
222
+ - Move cached popover creation.
223
+ - Keep existing public behavior and notifications.
224
+ - Route `MainView` dismissal through `MenuBarController` instead of reaching into `AppDelegate` if practical.
225
+
226
+ ### Phase 2: Extract Hotkey Registration
227
+
228
+ - Move the global hotkey registration block into `HotkeyBootstrap`.
229
+ - Keep action closures identical.
230
+ - Keep controller singletons unchanged.
231
+ - Add small helper methods only where they improve readability.
232
+
233
+ ### Phase 3: Extract Service Startup
234
+
235
+ - Move daemon/model startup into `AppServicesBootstrap`.
236
+ - Preserve existing startup order.
237
+ - Preserve diagnostic timing.
238
+ - Keep companion bridge preference behavior unchanged.
239
+
240
+ ### Phase 4: Activation Policy Cleanup
241
+
242
+ - Introduce `AppActivationCoordinator`.
243
+ - Register visible surfaces.
244
+ - Remove the long hard-coded visibility conditional from `AppDelegate`.
245
+ - Keep the current `.accessory` to `.regular` behavior.
246
+
247
+ ### Phase 5: Add `MenuBarPanelShell` Only If Needed
248
+
249
+ - Do not add this during initial extraction unless a concrete menu-bar panel needs it.
250
+ - If added, keep it separate from the main popover.
251
+ - Document why that surface needs panel behavior.
252
+
253
+ ## Acceptance Criteria
254
+
255
+ - Main menu bar popover looks and behaves the same.
256
+ - Right-click menu behaves the same.
257
+ - First popover open remains prewarmed and fast.
258
+ - App activation policy behavior is unchanged.
259
+ - Hotkeys continue to register and fire as before.
260
+ - Daemon and model startup order is unchanged.
261
+ - `AppDelegate` is materially smaller and easier to scan.
262
+ - No new custom panel replaces the existing popover without a concrete need.
263
+
264
+ ## Risks
265
+
266
+ The main risk is accidental behavior drift around activation policy and popover focus. The migration should be done in small steps and tested manually after each extraction.
267
+
268
+ The second risk is over-abstraction. These components should be plain controllers, not a framework. The goal is named ownership, not ceremony.
269
+
270
+ ## Testing Notes
271
+
272
+ Manual testing should cover:
273
+
274
+ - left-click menu bar toggle
275
+ - right-click context menu
276
+ - popover dismiss by outside click
277
+ - popover dismiss from project actions
278
+ - command palette hotkey
279
+ - HUD hotkey
280
+ - Screen Map hotkey
281
+ - voice command hotkey
282
+ - app activation policy returning to accessory mode when surfaces close
283
+ - launch with `--diagnostics`
284
+ - launch with `--screen-map`
285
+
286
+ ## References
287
+
288
+ - `apps/mac/Sources/AppShell/AppDelegate.swift`
289
+ - `apps/mac/Sources/AppShell/MainView.swift`
290
+ - `apps/mac/Sources/Core/Overlays/OverlayPanelShell.swift`
291
+ - `/Users/art/dev/ext/clicky/leanring-buddy/MenuBarPanelManager.swift`