@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.
- package/LICENSE +21 -0
- package/README.md +144 -69
- package/apps/mac/Info.plist +43 -0
- package/apps/mac/Lattices.app/Contents/Info.plist +43 -0
- package/apps/mac/Lattices.app/Contents/MacOS/Lattices +0 -0
- package/apps/mac/Lattices.app/Contents/Resources/AppIcon.icns +0 -0
- package/apps/mac/Lattices.app/Contents/Resources/docs/assistant-knowledge.md +130 -0
- package/apps/mac/Lattices.app/Contents/Resources/tap.wav +0 -0
- package/apps/mac/Lattices.app/Contents/_CodeSignature/CodeResources +150 -0
- package/apps/mac/Lattices.entitlements +21 -0
- package/apps/mac/Resources/Pets/assistant-spark/pet.json +62 -0
- package/apps/mac/Resources/Pets/assistant-spark/spritesheet.webp +0 -0
- package/apps/mac/Resources/Pets/scout-ranger/pet.json +6 -0
- package/apps/mac/Resources/Pets/scout-ranger/spritesheet.webp +0 -0
- package/apps/mac/Resources/tap.wav +0 -0
- package/assets/AppIcon.icns +0 -0
- package/bin/assistant-intelligence.ts +912 -0
- package/bin/cli/capture.ts +252 -0
- package/bin/cli/daemon.ts +22 -0
- package/bin/cli/helpers.ts +105 -0
- package/bin/cli/layer.ts +178 -0
- package/bin/cli/runs.ts +43 -0
- package/bin/cli/search.ts +141 -0
- package/bin/cli/session.ts +32 -0
- package/bin/client.ts +17 -0
- package/bin/cua.ts +26 -0
- package/bin/{daemon-client.js → daemon-client.ts} +49 -30
- package/bin/handsoff-infer.ts +96 -0
- package/bin/handsoff-worker.ts +531 -0
- package/bin/infer.ts +424 -0
- package/bin/keychain.ts +75 -0
- package/bin/lattices-app.ts +655 -0
- package/bin/lattices-build +125 -0
- package/bin/lattices-build-env.ts +77 -0
- package/bin/lattices-dev +362 -0
- package/bin/lattices.ts +3260 -0
- package/bin/project-twin.ts +645 -0
- package/docs/agent-execution-plan.md +562 -0
- package/docs/agent-layer-guide.md +207 -0
- package/docs/agents.md +233 -0
- package/docs/ai-chat-ux-review.md +416 -0
- package/docs/api.md +1041 -47
- package/docs/app.md +96 -13
- package/docs/assistant-knowledge.md +130 -0
- package/docs/companion-deck.md +209 -0
- package/docs/component-extraction-roadmap.md +392 -0
- package/docs/concepts.md +13 -12
- package/docs/config.md +83 -10
- package/docs/gesture-customization-proposal.md +520 -0
- package/docs/handsoff-test-scenarios.md +84 -0
- package/docs/hyperspace-grid-snappiness.md +210 -0
- package/docs/layers.md +176 -28
- package/docs/mouse-gestures.md +244 -0
- package/docs/ocr.md +21 -9
- package/docs/overview.md +42 -23
- package/docs/presentation-execution-review.md +491 -0
- package/docs/prompts/hands-off-system.md +382 -0
- package/docs/prompts/hands-off-turn.md +30 -0
- package/docs/prompts/voice-advisor.md +31 -0
- package/docs/prompts/voice-fallback.md +23 -0
- package/docs/proposals/LAT-001-gesture-visual-customization.md +522 -0
- package/docs/proposals/LAT-002-shared-overlay-canvas.md +353 -0
- package/docs/proposals/LAT-003-menu-bar-controller-architecture.md +291 -0
- package/docs/proposals/LAT-004-interactive-overlay-actors.md +534 -0
- package/docs/proposals/LAT-005-action-runtime-product-spine.md +914 -0
- package/docs/proposals/LAT-006-followup-gaps.md +103 -0
- package/docs/proposals/LAT-006-runs-and-capture-in-lattices.md +566 -0
- package/docs/proposals/LAT-007-unified-app-shell.md +128 -0
- package/docs/quickstart.md +8 -12
- package/docs/reference/dewey.config.ts +74 -0
- package/docs/reference/install-agent.md +79 -0
- package/docs/release.md +172 -0
- package/docs/repo-structure.md +100 -0
- package/docs/terminal-kit.md +87 -0
- package/docs/tiling-reference.md +224 -0
- package/docs/twins.md +138 -0
- package/docs/voice-command-protocol.md +278 -0
- package/docs/voice-error-model.md +73 -0
- package/docs/voice.md +221 -0
- package/package.json +69 -16
- package/packages/npm/sdk/cua.d.mts +1 -0
- package/packages/npm/sdk/cua.d.ts +188 -0
- package/packages/npm/sdk/cua.mjs +376 -0
- package/app/Lattices.app/Contents/Info.plist +0 -24
- package/app/Package.swift +0 -13
- package/app/Sources/ActionRow.swift +0 -61
- package/app/Sources/App.swift +0 -10
- package/app/Sources/AppDelegate.swift +0 -234
- package/app/Sources/AppShellView.swift +0 -62
- package/app/Sources/AppTypeClassifier.swift +0 -70
- package/app/Sources/AppWindowShell.swift +0 -63
- package/app/Sources/CheatSheetHUD.swift +0 -332
- package/app/Sources/CommandModeState.swift +0 -1362
- package/app/Sources/CommandModeView.swift +0 -1405
- package/app/Sources/CommandModeWindow.swift +0 -192
- package/app/Sources/CommandPaletteView.swift +0 -307
- package/app/Sources/CommandPaletteWindow.swift +0 -134
- package/app/Sources/DaemonProtocol.swift +0 -101
- package/app/Sources/DaemonServer.swift +0 -414
- package/app/Sources/DesktopModel.swift +0 -121
- package/app/Sources/DesktopModelTypes.swift +0 -71
- package/app/Sources/DiagnosticLog.swift +0 -271
- package/app/Sources/EventBus.swift +0 -30
- package/app/Sources/HotkeyManager.swift +0 -250
- package/app/Sources/HotkeyStore.swift +0 -338
- package/app/Sources/InventoryManager.swift +0 -35
- package/app/Sources/InventoryPath.swift +0 -43
- package/app/Sources/KeyRecorderView.swift +0 -210
- package/app/Sources/LatticesApi.swift +0 -1125
- package/app/Sources/MainView.swift +0 -467
- package/app/Sources/MainWindow.swift +0 -83
- package/app/Sources/OcrModel.swift +0 -309
- package/app/Sources/OcrStore.swift +0 -295
- package/app/Sources/OmniSearchState.swift +0 -283
- package/app/Sources/OmniSearchView.swift +0 -288
- package/app/Sources/OmniSearchWindow.swift +0 -105
- package/app/Sources/OrphanRow.swift +0 -129
- package/app/Sources/PaletteCommand.swift +0 -419
- package/app/Sources/PermissionChecker.swift +0 -125
- package/app/Sources/Preferences.swift +0 -92
- package/app/Sources/ProcessModel.swift +0 -199
- package/app/Sources/ProcessQuery.swift +0 -151
- package/app/Sources/Project.swift +0 -28
- package/app/Sources/ProjectRow.swift +0 -368
- package/app/Sources/ProjectScanner.swift +0 -121
- package/app/Sources/ScreenMapState.swift +0 -2387
- package/app/Sources/ScreenMapView.swift +0 -2820
- package/app/Sources/ScreenMapWindowController.swift +0 -89
- package/app/Sources/SessionManager.swift +0 -72
- package/app/Sources/SettingsView.swift +0 -1053
- package/app/Sources/SettingsWindow.swift +0 -20
- package/app/Sources/TabGroupRow.swift +0 -178
- package/app/Sources/Terminal.swift +0 -259
- package/app/Sources/TerminalQuery.swift +0 -156
- package/app/Sources/TerminalSynthesizer.swift +0 -200
- package/app/Sources/Theme.swift +0 -163
- package/app/Sources/TilePickerView.swift +0 -209
- package/app/Sources/TmuxModel.swift +0 -53
- package/app/Sources/TmuxQuery.swift +0 -81
- package/app/Sources/WindowTiler.swift +0 -1755
- package/app/Sources/WorkspaceManager.swift +0 -434
- package/bin/lattices-app.js +0 -221
- 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`
|