@lattices/cli 0.5.0 → 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 (46) hide show
  1. package/README.md +14 -5
  2. package/apps/mac/Info.plist +4 -2
  3. package/apps/mac/Lattices.app/Contents/Info.plist +4 -2
  4. package/apps/mac/Lattices.app/Contents/MacOS/Lattices +0 -0
  5. package/apps/mac/Lattices.app/Contents/Resources/docs/assistant-knowledge.md +130 -0
  6. package/apps/mac/Lattices.app/Contents/_CodeSignature/CodeResources +11 -0
  7. package/apps/mac/Lattices.entitlements +6 -0
  8. package/bin/assistant-intelligence.ts +41 -3
  9. package/bin/cli/capture.ts +252 -0
  10. package/bin/cli/daemon.ts +22 -0
  11. package/bin/cli/helpers.ts +105 -0
  12. package/bin/cli/layer.ts +178 -0
  13. package/bin/cli/runs.ts +43 -0
  14. package/bin/cli/search.ts +141 -0
  15. package/bin/cli/session.ts +32 -0
  16. package/bin/client.ts +2 -1
  17. package/bin/cua.ts +26 -0
  18. package/bin/infer.ts +22 -4
  19. package/bin/keychain.ts +75 -0
  20. package/bin/lattices-app.ts +111 -12
  21. package/bin/lattices-build-env.ts +77 -0
  22. package/bin/lattices-dev +29 -2
  23. package/bin/lattices.ts +729 -769
  24. package/docs/api.md +496 -3
  25. package/docs/app.md +5 -4
  26. package/docs/assistant-knowledge.md +130 -0
  27. package/docs/config.md +5 -0
  28. package/docs/hyperspace-grid-snappiness.md +210 -0
  29. package/docs/layers.md +53 -0
  30. package/docs/mouse-gestures.md +40 -3
  31. package/docs/ocr.md +3 -0
  32. package/docs/prompts/hands-off-system.md +9 -1
  33. package/docs/proposals/LAT-006-followup-gaps.md +103 -0
  34. package/docs/proposals/{LAT-006-mira-in-lattices.md → LAT-006-runs-and-capture-in-lattices.md} +83 -70
  35. package/docs/proposals/LAT-007-unified-app-shell.md +128 -0
  36. package/docs/quickstart.md +3 -1
  37. package/docs/reference/dewey.config.ts +1 -1
  38. package/docs/release.md +4 -3
  39. package/docs/repo-structure.md +1 -0
  40. package/docs/terminal-kit.md +87 -0
  41. package/docs/tiling-reference.md +5 -3
  42. package/docs/voice.md +3 -3
  43. package/package.json +29 -5
  44. package/packages/npm/sdk/cua.d.mts +1 -0
  45. package/packages/npm/sdk/cua.d.ts +188 -0
  46. package/packages/npm/sdk/cua.mjs +376 -0
@@ -0,0 +1,130 @@
1
+ ---
2
+ type: Assistant Knowledge Base
3
+ title: Lattices — Assistant Knowledge
4
+ description: Orientation + capability map the in-app Workspace Assistant uses to explain Lattices and point to the right feature or doc
5
+ audience: assistant
6
+ ---
7
+
8
+ > You are reading the Workspace Assistant's knowledge base. It summarizes what
9
+ > Lattices can do and links to the deeper docs. Treat the **structured context**
10
+ > in your prompt (current settings, file paths, CLI commands) as ground truth for
11
+ > *this user's* configuration; treat this file as ground truth for *how Lattices
12
+ > works*. When a question goes deeper than this summary, name the relevant doc
13
+ > (see [References](#references)) instead of guessing.
14
+
15
+ ## What Lattices is
16
+
17
+ Lattices is an **agentic window manager for macOS** — a programmable workspace
18
+ that pairs a native menu bar app with managed tmux sessions and a scriptable
19
+ agent API. Three layers, one product:
20
+
21
+ 1. **Programmable workspace** — a CLI and a WebSocket agent API (`ws://127.0.0.1:9399`,
22
+ 35+ methods, real-time events) that let scripts and AI agents observe and drive
23
+ the desktop the same way a person does.
24
+ 2. **Smart layout manager** — the menu bar app tracks every window across all
25
+ monitors: tiling, switchable layers, snap zones, and screen-text indexing.
26
+ 3. **Managed tmux sessions** — declare a dev environment in `.lattices.json`;
27
+ Lattices builds it, runs it, and keeps it alive across reboots.
28
+
29
+ Requirements: macOS 26+, Node 18+; tmux only for session management.
30
+ See [Overview](/docs/overview) and [Concepts](/docs/concepts).
31
+
32
+ ## Capability map
33
+
34
+ Each area below is a one-paragraph summary plus the doc to cite for detail.
35
+
36
+ ### Window tiling & placement
37
+ Snap windows to preset positions — halves, quarters, thirds, maximize, center —
38
+ from the command palette or `lattices tile <position>`. There is also a grid
39
+ placement primitive: compact `CxR:c,r` starts at 1 for command entry, while
40
+ canonical `grid:CxR:c,r` starts at 0 for APIs. → [Tiling reference](/docs/tiling-reference), positions in [Configuration](/docs/config).
41
+
42
+ ### Workspace layers & tab groups
43
+ Group projects into named **layers** you can switch between, and tab-group related
44
+ windows. `workspace.json` layers launch/focus/tile projects. Studio layers are
45
+ rule-backed live window sets persisted in `~/.lattices/layers.json`; their clauses
46
+ support app/title/session exact, substring, regex, Space, visibility, and exclusion
47
+ matches. → [Layers](/docs/layers).
48
+
49
+ ### Command palette & menu bar app
50
+ The palette (**Cmd+Shift+M**) is the app's primary surface: launch projects, tile,
51
+ sync, restart, open settings — all searchable. → [Menu Bar App](/docs/app).
52
+
53
+ ### tmux sessions (`.lattices.json`)
54
+ Declare panes, commands, and layout per project. `lattices start` builds/attaches a
55
+ persistent session named `<basename>-<hash>`; `lattices sync` reconciles a running
56
+ session to its config. **Ensure** re-runs exited commands on reattach; **prefill**
57
+ types them and waits. → [Concepts](/docs/concepts), [Configuration](/docs/config).
58
+
59
+ ### Screen OCR & search
60
+ The app reads on-screen text via the Accessibility API (~60s) and Apple Vision OCR
61
+ on background windows (~2h), indexing everything with FTS5. Search across titles,
62
+ app names, session tags, and OCR with `lattices search <query>` (add `--deep` or
63
+ `--all` to inspect terminal tabs by cwd). → [Screen OCR & Search](/docs/ocr).
64
+
65
+ ### Voice commands
66
+ Natural-language voice control for window management ("put the browser on the
67
+ right", "switch to the backend layer"). → [Voice Commands](/docs/voice).
68
+
69
+ ### Mouse gestures
70
+ Hold a mouse button, draw a direction or shape, release — runs the matched action.
71
+ Configured via `mouseGestures.enabled` plus `~/.lattices/mouse-shortcuts.json`.
72
+ → [Mouse Gestures](/docs/mouse-gestures).
73
+
74
+ ### Agent API & CLI
75
+ Agents connect over WebSocket and get the same control as a person: list
76
+ windows/projects, launch sessions, tile, switch layers, read screen text, and
77
+ subscribe to events (`windows.changed`, `tmux.changed`, `layer.switched`).
78
+ → [Agent Guide](/docs/agents), [Agent API](/docs/api).
79
+
80
+ ### Project twins
81
+ Pi-backed project "twins" for mediated, persistent agent execution scoped to a
82
+ project. → [Project Twins](/docs/twins).
83
+
84
+ ## Key shortcuts
85
+
86
+ | Shortcut | Action |
87
+ |----------|--------|
88
+ | **Cmd+Shift+M** | Open the command palette |
89
+ | `lattices tile <position>` | Tile the focused window (CLI) |
90
+ | `lattices layer [name\|index]` | Switch workspace layer (CLI) |
91
+ | **Ctrl+B** then `D` / `Z` / arrows | tmux: detach / zoom / move pane (inside a session) |
92
+
93
+ Tiling and grid hotkeys are user-configurable — for the live set, point the user to
94
+ Settings or the [Tiling reference](/docs/tiling-reference) rather than asserting one.
95
+
96
+ ## CLI quick reference
97
+
98
+ `lattices` · `lattices init` · `lattices sync` · `lattices start` ·
99
+ `lattices restart [pane]` · `lattices tile <position>` · `lattices group [id]` ·
100
+ `lattices layer [name|index]` · `lattices windows --json` ·
101
+ `lattices search <query> [--deep|--all] [--json] [--wid]` · `lattices place <query> [position]` ·
102
+ `lattices app restart`. Full flags: [Configuration](/docs/config).
103
+
104
+ ## Config & file locations
105
+
106
+ - **Per project:** `.lattices.json` in the project root (panes, commands, layout, ensure/prefill).
107
+ - **User config (`~/.lattices/`):** `workspace.json`, `layers.json`, `mouse-shortcuts.json`,
108
+ `snap-zones.json`, `clusters.json`, `ocr.db`, `lattices.log`.
109
+ - **Defaults domain:** `dev.lattices.app` (read/write app settings via `defaults`).
110
+
111
+ The exact current values and paths for *this* machine arrive in your structured
112
+ context — prefer those over the generic paths above when answering.
113
+
114
+ ## References
115
+
116
+ | Topic | Doc |
117
+ |-------|-----|
118
+ | What it is / who it's for | [Overview](/docs/overview) |
119
+ | Install & first run | [Quickstart](/docs/quickstart) |
120
+ | Architecture, glossary, internals | [Concepts](/docs/concepts) |
121
+ | `.lattices.json`, CLI, tile positions | [Configuration](/docs/config) |
122
+ | Command palette, tiling, sessions | [Menu Bar App](/docs/app) |
123
+ | Tiling & grid placement | [Tiling reference](/docs/tiling-reference) |
124
+ | Layers & tab groups | [Layers](/docs/layers) |
125
+ | Screen OCR & full-text search | [Screen OCR & Search](/docs/ocr) |
126
+ | Voice control | [Voice Commands](/docs/voice) |
127
+ | Mouse gestures | [Mouse Gestures](/docs/mouse-gestures) |
128
+ | Agent contracts (voice/CLI/daemon) | [Agent Guide](/docs/agents) |
129
+ | WebSocket RPC method reference | [Agent API](/docs/api) |
130
+ | Project twins | [Project Twins](/docs/twins) |
package/docs/config.md CHANGED
@@ -152,6 +152,7 @@ Run `lattices init` in your project directory to generate a starter
152
152
  | `lattices hud sync` | Publish all registered HUD actors |
153
153
  | `lattices search <query>` | Search windows by title, app, session, OCR |
154
154
  | `lattices search <q> --deep` | Deep search: index + live terminal inspection |
155
+ | `lattices search <q> --all` | Same as `--deep` (all search sources) |
155
156
  | `lattices search <q> --wid` | Print matching window IDs only (pipeable) |
156
157
  | `lattices place <query> [pos]` | Deep search + focus + tile (default: bottom-right)|
157
158
  | `lattices focus <session>` | Focus a session's window and switch Spaces |
@@ -281,6 +282,10 @@ Aliases: `left-half`/`left`, `right-half`/`right`, `top-half`/`top`,
281
282
  Tiling respects the menu bar and dock. It uses the visible desktop
282
283
  area, not the full screen.
283
284
 
285
+ For arbitrary cells, use compact `CxR:c,r` with 1-indexed coordinates
286
+ from the top-left, or canonical `grid:CxR:c,r` with 0-indexed coordinates.
287
+ Example: `lattices tile 4x4:1,2`.
288
+
284
289
  ### Smart app tiling
285
290
 
286
291
  Use `lattices tile family` when you want lattices to arrange a whole
@@ -0,0 +1,210 @@
1
+ # Hyper+G in-place grid — snappiness & satisfaction brief
2
+
3
+ Context: `relayoutGroup()` already switched from sequential `RealWindowAnimator.setFrameRobust`
4
+ to `WindowTiler.batchMoveAndRaiseWindows` (SLS freeze + one AX pass/app), with UI refresh
5
+ deferred and a tap on commit. This is the "make it even better" pass.
6
+
7
+ Code anchors:
8
+ - Grid path: `WindowMotionMode.swift:1100 relayoutGroup()` → `:1138 distributeGroup()` (G = keycode 5, `:833`)
9
+ - Batch move: `WindowTiler.swift:1753 batchMoveAndRaiseWindows` (freeze `:1767`, AX enum `:1776`, activate `:1818`, unfreeze `:1825`)
10
+ - Sound: `DiagnosticLog.swift:162 AppFeedback.playTap`
11
+ - Optional anim: `RealWindowAnimator.swift` (Timer 60fps, 0.28s — NOT on the batch path)
12
+ - Chrome flash: `WindowTiler.swift:54 WindowHighlight.flash` (single-window only)
13
+ - No haptics anywhere in the codebase yet → green field.
14
+
15
+ ---
16
+
17
+ ## The governing principle
18
+
19
+ Perceived latency is bound to the **earliest** feedback the brain receives, not the moment
20
+ pixels finish moving. The AX move is 40–120ms of work we can't fully erase. So the play is:
21
+ **acknowledge on key-down (haptic + sound + overlay), move under cover, let the windows catch
22
+ up.** Every quick win below is a variant of "fire feedback before the slow thing finishes."
23
+
24
+ ---
25
+
26
+ ## Quick wins (ship this week, low risk)
27
+
28
+ ### Q1 — Haptic on key-down (biggest bang, ~10 lines)
29
+ There is zero haptic feedback today. `NSHapticFeedbackManager.defaultPerformer.perform(.alignment, performanceTime: .now)`
30
+ is *exactly* the Loop/Magnet "snap" feel on trackpad/Force Touch. Fire it the instant `G` is
31
+ matched in `keyDown` (`:833`), **before** `distributeGroup()` runs. Costs nothing, lands on the
32
+ keypress, and the windows snapping ~80ms later reads as "instant + tactile."
33
+
34
+ ### Q2 — Decouple + pre-warm the tap sound
35
+ `playTap` does `DispatchQueue.main.async { stop(); play() }`. Three problems:
36
+ - the async hop delays the sound ~1 runloop turn past the keypress,
37
+ - `stop()` then `play()` adds a tiny dead gap,
38
+ - NSSound's *first* play in a session stutters (codec spin-up).
39
+
40
+ Fixes: (a) prime the sound once on motion-mode entry by playing `tap.wav` at volume 0; (b) fire
41
+ it synchronously on key-down (same site as the haptic), not after the move; (c) consider a
42
+ prepared `AVAudioPlayer` (`prepareToPlay()`) or `AudioServicesPlaySystemSound` for sub-frame
43
+ latency. Net: the *thunk* and the *tap* (haptic) coincide with the keypress, not the landing.
44
+
45
+ ### Q3 — Shrink the SLS freeze window
46
+ `batchMoveAndRaiseWindows` does the slow `kAXWindowsAttribute` enumeration **inside** the
47
+ `SLSDisableUpdate` freeze (`:1776`). The screen is frozen while we do AX queries. Resolve the
48
+ AX elements *before* the freeze, freeze only around the set-frame writes, unfreeze immediately.
49
+ Bonus: `relayoutGroup` already resolves each element via `recordOriginal`'s `ax(for: m)` (`:1113`)
50
+ and then the batch path re-resolves the whole window list — pass the resolved elements in and
51
+ skip the second AX pass entirely. Shorter freeze = less black-flash, snappier reveal.
52
+
53
+ ### Q4 — Stop activating every app
54
+ `batchMoveAndRaiseWindows` calls `app.activate()` once per pid (`:1818`). With windows from N
55
+ apps that's N activations → focus churn, Space flicker, and the overlay can lose key. AX
56
+ `kAXRaiseAction` (already issued at `:1806`) reorders without activating. Activate **only** the
57
+ app that should end up frontmost (the last-picked / aimed window), once. Raise the rest.
58
+
59
+ ### Q5 — Overlay "snap-pop" on the selection chrome
60
+ The real move is a teleport, so add the *sense* of motion in the cheap overlay layer (no AX
61
+ cost). When the grid lands, give each selection border a tiny scale overshoot (1.0→1.04→1.0,
62
+ ~120ms, Core Animation) + a one-shot green edge flash converging on the slot. This is the
63
+ Arc/Raycast "it clicked into place" pop. Overlay-only, runs off the AX path, can't add input
64
+ latency.
65
+
66
+ ---
67
+
68
+ ## Bigger bets (more design, higher ceiling)
69
+
70
+ ### B1 — Ghost-slot anticipation (kills perceived latency outright)
71
+ On `G`, **instantly** paint the target grid as outlined ghost slots (you already compute
72
+ `balancedGrid` rects → `tileFrame`). Animate the slots filling (or the picked windows' chrome
73
+ sliding toward their slots) over ~140ms while the real `batchMove` runs underneath. The user
74
+ sees motion begin on the same frame as the keypress; the real windows arrive under the
75
+ animation. This fully decouples felt-speed from AX latency and is the single highest-ceiling
76
+ change. The ghost overlay is also the natural home for Q5's pop.
77
+
78
+ ### B2 — Precompute on selection, commit = pure writes
79
+ Every pluck/unpluck currently recomputes `balancedGrid` + `tileFrame` and re-resolves AX. Cache
80
+ (a) the AX element per picked wid and (b) the target frame map, updated incrementally as the
81
+ selection changes. By the time `G` fires, commit is nothing but the frozen set-frame loop —
82
+ nothing to compute, nothing to resolve, minimal freeze.
83
+
84
+ ### B3 — Staggered cascade fill (the "deliberate fast" feel)
85
+ Real windows must move simultaneously (per-window AX animation = jank — see "avoid"). But the
86
+ *overlay* tiles/borders can land on a micro-stagger in reading order (~10–14ms/cell). The eye
87
+ reads a fast left-to-right cascade as more intentional and premium than a flat simultaneous
88
+ snap, while the actual work stays a single batch. Pure overlay timing.
89
+
90
+ ---
91
+
92
+ ## What to avoid (these ADD latency or jank)
93
+
94
+ - **Don't animate many real windows via per-tick AX writes.** `RealWindowAnimator`'s 0.28s
95
+ Timer loop is fine for one window; across a group it's 60fps × N AX position+size writes —
96
+ AX is slow and serializes, so it stutters and feels *slower* than a clean teleport. Keep the
97
+ batch teleport; animate the overlay, not the windows.
98
+ - **Don't do AX enumeration / `DesktopModel.poll()` inside the freeze or on the commit path.**
99
+ Poll/rebuild are already deferred (`refreshAfterGridMove`, `:1125`) — keep it that way; don't
100
+ let new chrome work creep back onto the hot path.
101
+ - **Don't async-dispatch or `stop()`-then-`play()` the commit sound** (see Q2) — both push the
102
+ thunk past the keypress.
103
+ - **Don't `app.activate()` per app** (Q4) — multi-app activation is the main source of flicker
104
+ and overlay focus loss.
105
+ - **Avoid Timer-driven overlay animation.** Use Core Animation / `CADisplayLink`; `Timer` at
106
+ 1/60 drifts and can hitch under load.
107
+ - **Don't add dwell.** `WindowHighlight.flash` defaults to a 0.9s dwell + 0.3s fade — fine for a
108
+ one-shot locate, wrong for a snap pop. Snap feedback wants ~120–180ms total. Long fades read
109
+ as lag, not polish.
110
+
111
+ ---
112
+
113
+ ## Recommended ship order
114
+
115
+ 1. **Q1 + Q2** together — one small change at the `G` key site: haptic + synchronous pre-warmed
116
+ sound on key-down. Instant tactile upgrade, ~20 lines, no risk.
117
+ 2. **Q3 + Q4** — tighten `batchMoveAndRaiseWindows` (pre-resolve AX, shorter freeze, single
118
+ activate). Real latency reduction.
119
+ 3. **Q5** — overlay snap-pop on the chrome. First "ooh" moment.
120
+ 4. **B1** — ghost-slot anticipation once Q5's overlay exists to build on. This is the headliner.
121
+
122
+ ---
123
+
124
+ ## Implementation sketches
125
+
126
+ ### Sketch 1 — Haptic + tactile commit on key-down (Q1+Q2)
127
+ At `WindowMotionMode.swift:833`, before dispatching the grid:
128
+ ```swift
129
+ case 5: // G
130
+ if exposed { gatherInPlace() }
131
+ else {
132
+ AppFeedback.shared.commitTactile() // haptic + sound, synchronous, NOW
133
+ distributeGroup() // remove the playTapSound() from relayoutGroup
134
+ }
135
+ return
136
+ ```
137
+ New in `AppFeedback`:
138
+ ```swift
139
+ func warmUp() { // call on motion-mode entry
140
+ tapSound?.volume = 0; tapSound?.play(); tapSound?.stop(); tapSound?.volume = 1
141
+ }
142
+ func commitTactile() { // called on the keypress, on main
143
+ NSHapticFeedbackManager.defaultPerformer.perform(.alignment, performanceTime: .now)
144
+ tapSound?.currentTime = 0
145
+ tapSound?.play() // no async hop, no stop() gap
146
+ }
147
+ ```
148
+ Remove `AppFeedback.shared.playTapSound()` from `relayoutGroup()` (`:1120`) so the feedback is
149
+ owned by the keypress, not the move completion.
150
+
151
+ ### Sketch 2 — Pre-resolved, minimal-freeze batch (Q3+Q4)
152
+ New overload that trusts caller-resolved elements and freezes only the writes:
153
+ ```swift
154
+ static func batchSetFrames(_ moves: [(el: AXUIElement, pid: Int32, frame: CGRect, raise: Bool)],
155
+ frontmostPid: Int32?) {
156
+ // app -> enhanced-UI off, BEFORE freeze
157
+ let pids = Set(moves.map(\.pid))
158
+ let appRefs = Dictionary(uniqueKeysWithValues: pids.map { ($0, AXUIElementCreateApplication($0)) })
159
+ appRefs.values.forEach { AXUIElementSetAttributeValue($0, "AXEnhancedUserInterface" as CFString, false as CFTypeRef) }
160
+
161
+ let cid = _SLSMainConnectionID?()
162
+ if let cid { _ = _SLSDisableUpdate?(cid) } // freeze ONLY the writes
163
+ for m in moves {
164
+ setFrameTriplet(m.el, m.frame) // size→pos→size
165
+ if m.raise { AXUIElementPerformAction(m.el, kAXRaiseAction as CFString) }
166
+ }
167
+ if let cid { _ = _SLSReenableUpdate?(cid) } // unfreeze immediately
168
+
169
+ appRefs.values.forEach { AXUIElementSetAttributeValue($0, "AXEnhancedUserInterface" as CFString, true as CFTypeRef) }
170
+ if let frontmostPid { NSRunningApplication(processIdentifier: frontmostPid)?.activate() } // ONE activate
171
+ }
172
+ ```
173
+ `relayoutGroup` already has `el` in hand from `recordOriginal` — collect it into `moves` and
174
+ call this; no second `kAXWindows` enumeration, freeze shrinks to just the set-frame loop.
175
+
176
+ ### Sketch 3 — Overlay snap-pop on selection chrome (Q5)
177
+ On grid landing, per slot, run a layer animation on the existing selection border view (overlay,
178
+ not the window):
179
+ ```swift
180
+ let pop = CAKeyframeAnimation(keyPath: "transform.scale")
181
+ pop.values = [1.0, 1.045, 1.0]; pop.keyTimes = [0, 0.45, 1]
182
+ pop.duration = 0.13; pop.timingFunction = CAMediaTimingFunction(name: .easeOut)
183
+ borderLayer.add(pop, forKey: "snapPop")
184
+ // + a one-shot edge tint that fades over the same 0.13s
185
+ ```
186
+ No dwell, no AX. Fire it from `refreshAfterGridMove` (`:1125`) so it rides the deferred turn.
187
+
188
+ ### Sketch 4 — Ghost-slot anticipation (B1)
189
+ On `G`, before the move, draw target slots in the motion overlay and animate the picked windows'
190
+ *chrome* (or ghost rects) from current → slot, while `batchSetFrames` runs underneath:
191
+ ```swift
192
+ func previewGrid(_ rects: [CGRect]) { // rects already from balancedGrid → tileFrame
193
+ for (i, r) in rects.enumerated() {
194
+ let slot = ghostLayer(at: currentChromeFrame(i))
195
+ slot.frame = currentChromeFrame(i)
196
+ CATransaction.begin()
197
+ CATransaction.setAnimationDuration(0.14)
198
+ slot.frame = r // slides to target as the real window teleports under it
199
+ CATransaction.commit()
200
+ }
201
+ }
202
+ ```
203
+ Sequence: keypress → `commitTactile()` (Sketch 1) + `previewGrid()` same frame → `batchSetFrames()`
204
+ → ghosts fade as real windows land → `refreshAfterGridMove`. Felt latency ≈ 0; the move hides
205
+ under 140ms of overlay motion.
206
+
207
+ ### Sketch 5 — Staggered cascade (B3, optional flourish)
208
+ In `previewGrid`/snap-pop, offset each cell's animation `beginTime` by `index * 0.012` in
209
+ row-major order. Overlay-only; the real batch stays simultaneous. Toggle behind a setting if you
210
+ want a "calm" vs "playful" feel.
package/docs/layers.md CHANGED
@@ -315,6 +315,59 @@ event is broadcast to all connected clients.
315
315
 
316
316
  More methods in the [Agent API reference](/docs/api).
317
317
 
318
+ ## Rule-backed Studio layers
319
+
320
+ Studio layers are live window rules stored in `~/.lattices/layers.json`.
321
+ They are separate from `workspace.json` launch-and-tile layers: Studio
322
+ layers do not launch projects. They resolve matching desktop windows,
323
+ then recall or scope those windows in Studio and Screen Map.
324
+
325
+ Each layer has a `match` array. A window joins the layer when it matches
326
+ any clause in that array. Inside one clause, every present positive field
327
+ must match, and every clause in `not` must fail.
328
+
329
+ ```json
330
+ [
331
+ {
332
+ "id": "review",
333
+ "name": "Review",
334
+ "match": [
335
+ {
336
+ "appEquals": "Google Chrome",
337
+ "titleRegex": "(GitHub|Pull Request)",
338
+ "not": [
339
+ { "titleContains": "Actions" }
340
+ ]
341
+ },
342
+ {
343
+ "sessionContains": "lattices",
344
+ "isOnScreen": true
345
+ }
346
+ ]
347
+ }
348
+ ]
349
+ ```
350
+
351
+ Supported clause fields:
352
+
353
+ | Field | Match |
354
+ |-------|-------|
355
+ | `app` | App name contains this string |
356
+ | `appEquals` | App name exactly equals this string |
357
+ | `appRegex` | App name matches this regular expression |
358
+ | `titleContains` | Window title contains this string |
359
+ | `titleEquals` | Window title exactly equals this string |
360
+ | `titleRegex` | Window title matches this regular expression |
361
+ | `session` | Parsed lattices tmux session exactly equals this string |
362
+ | `sessionContains` | Parsed lattices tmux session contains this string |
363
+ | `isOnScreen` | Window is, or is not, visible on the current Space |
364
+ | `spaceId` | Window belongs to this macOS Space id |
365
+ | `not` | Exclusion clauses; any match rejects the window |
366
+
367
+ `app` and `titleContains` are the original substring fields, so older
368
+ `layers.json` files continue to work. New layers created from plucked
369
+ windows use `appEquals` by default to avoid accidental substring matches.
370
+
318
371
  ### Layer bar
319
372
 
320
373
  When a workspace config is loaded, a layer bar appears between the
@@ -12,8 +12,9 @@ Mouse gestures are a user-level shortcut system for the macOS app. Hold a
12
12
  configured mouse button, draw a direction or shape, then release to run the
13
13
  matched action.
14
14
 
15
- The app code owns the recognizer, action dispatcher, and JSON schema. Your
16
- actual gesture mappings live in:
15
+ The app code owns the recognizer, action dispatcher, and JSON schema. The
16
+ gesture mappings are data, so agents can define or change hotkeys without
17
+ adding a named action in Swift. Your actual gesture mappings live in:
17
18
 
18
19
  ```bash
19
20
  ~/.lattices/mouse-shortcuts.json
@@ -36,6 +37,16 @@ Do not add personal shortcuts by changing `MouseGestureConfig.swift`. Add them
36
37
  to the user JSON file instead. The Settings UI can open that file from
37
38
  **Settings -> Shortcuts -> Mouse Gestures -> Configure...**.
38
39
 
40
+ Agents should usually write these rules through the daemon:
41
+
42
+ ```js
43
+ await daemonCall('mouse.shortcuts.upsert', { rule })
44
+ ```
45
+
46
+ The daemon persists the rule, snapshots the old config, and refreshes the
47
+ running app immediately. If a tool edits `~/.lattices/mouse-shortcuts.json`
48
+ directly, call `mouse.shortcuts.reload` afterward; no app restart is required.
49
+
39
50
  ## Lightweight History
40
51
 
41
52
  Lattices keeps local snapshots of your mouse shortcut config before risky
@@ -100,6 +111,32 @@ The circle recognizer is intentionally generous: it looks for broad circular
100
111
  motion, a reasonably closed path, and enough turn coverage rather than a
101
112
  geometrically perfect circle.
102
113
 
114
+ ## Default: Dictation Gesture
115
+
116
+ New default configs use a plain shortcut action for dictation. The middle-button
117
+ up gesture sends the Lattices voice command hotkey, currently Hyper+D:
118
+
119
+ ```json
120
+ {
121
+ "id": "dictation",
122
+ "enabled": true,
123
+ "device": "any",
124
+ "trigger": {
125
+ "button": "middle",
126
+ "kind": "drag",
127
+ "direction": "up"
128
+ },
129
+ "action": {
130
+ "type": "shortcut.send",
131
+ "shortcut": {
132
+ "key": "d",
133
+ "keyCode": 2,
134
+ "modifiers": ["control", "option", "shift", "command"]
135
+ }
136
+ }
137
+ }
138
+ ```
139
+
103
140
  ## Default: Screenshot Area Gesture
104
141
 
105
142
  New default configs include a back-button circle gesture for the macOS
@@ -139,7 +176,7 @@ Supported action types include:
139
176
  | `space.previous` | Switch to the previous macOS Space |
140
177
  | `space.next` | Switch to the next macOS Space |
141
178
  | `screenmap.toggle` | Open the Screen Map overview |
142
- | `dictation.start` | Start dictation |
179
+ | `dictation.start` | Legacy compatibility alias that presses the configured Voice Command hotkey |
143
180
  | `shortcut.send` | Send a keyboard shortcut |
144
181
  | `app.activate` | Activate an app by name |
145
182
 
package/docs/ocr.md CHANGED
@@ -65,6 +65,9 @@ lattices search "error"
65
65
  # Deep search — also inspects terminal tabs and processes
66
66
  lattices search "myproject" --deep
67
67
 
68
+ # Same as --deep (all search sources)
69
+ lattices search "myproject" --all
70
+
68
71
  # Search + focus + tile the top result
69
72
  lattices place "myproject" left
70
73
 
@@ -62,6 +62,11 @@ User: "put my terminals in a grid on the right"
62
62
  {"actions": [{"intent": "distribute", "slots": {"app": "iTerm2", "region": "right"}}], "spoken": "Gridding your terminals on the right half."}
63
63
  ```
64
64
 
65
+ User: "put chrome in the top-right cell of a 4x4 grid"
66
+ ```json
67
+ {"actions": [{"intent": "tile_window", "slots": {"wid": 12345, "position": "grid:4x4:3,0"}}], "spoken": "Chrome to the top-right of a 4×4."}
68
+ ```
69
+
65
70
  User: "organize my chrome windows on the left"
66
71
  ```json
67
72
  {"actions": [{"intent": "distribute", "slots": {"app": "Google Chrome", "region": "left"}}], "spoken": "Arranging your Chrome windows on the left."}
@@ -174,8 +179,11 @@ Grid-based tiling. Every position is a cell in a cols×rows grid.
174
179
  **3x2 (sixths):** top-left-third, top-center-third, top-right-third, bottom-left-third, bottom-center-third, bottom-right-third
175
180
  **4x1 (fourths):** first-fourth, second-fourth, third-fourth, last-fourth
176
181
  **4x2 (eighths):** top-first-fourth, top-second-fourth, top-third-fourth, top-last-fourth, bottom-first-fourth, bottom-second-fourth, bottom-third-fourth, bottom-last-fourth
182
+ **4x4 (sixteenths):** no named cells — address them directly with `grid:4x4:C,R` (top-left `grid:4x4:0,0`, top-right `grid:4x4:3,0`, bottom-left `grid:4x4:0,3`, bottom-right `grid:4x4:3,3`, near-center `grid:4x4:1,1`)
183
+
184
+ For arbitrary grids, use the syntax `grid:CxR:C,R` where C=columns, R=rows, then col,row (0-indexed). Example: `grid:5x3:2,1` = center cell of a 5×3 grid; `grid:4x4:3,0` = top-right of a 4×4 (each cell a sixteenth of the screen).
177
185
 
178
- For arbitrary grids, use the syntax `grid:CxR:C,R` where C=columns, R=rows, then col,row (0-indexed). Example: `grid:5x3:2,1` = center cell of a 5×3 grid.
186
+ To span a window across a rectangular block of cells, address two inclusive corners: `grid:CxR:c0,r0-c1,r1`. Example: `grid:4x4:0,0-1,1` = the top-left 2×2 block (a quarter) of a 4×4 grid; `grid:4x4:0,0-3,1` = the top half. Order doesn't matter — the corners are normalized.
179
187
 
180
188
  When the user says "quarter" they mean a 2×2 cell (top-left, top-right, etc.), not a 4×1 fourth.
181
189
  When they say "third" they usually mean a 3×1 column, but "top third" means the 3×2 row.
@@ -0,0 +1,103 @@
1
+ # LAT-006 Follow-up: Next Use Cases & Gaps (Load / Voice / Type / Click)
2
+
3
+ Assessment date: 2026-06-17. Scope: current working tree on `main` (uncommitted LAT-006
4
+ runs/capture/computer-use slice). Focus: root-cause product/API shape, not workarounds.
5
+
6
+ ## What exists today (grounded)
7
+
8
+ A real **observe → act → capture → trace** layer landed (LAT-006 Phase 2 + a computer-use
9
+ extension), wired end to end:
10
+
11
+ - **Daemon** (`apps/mac/Sources/Core/Daemon/LatticesApi.swift`):
12
+ - `runs.create | runs.list | runs.get | runs.artifacts`
13
+ - `capture.screenshotWindow`
14
+ - `computer.prepare | computer.focusWindow | computer.typeText | computer.showCursor | computer.demoTerminal`
15
+ - `settings.cursorAppearance.get | .set`
16
+ - **Controllers**: `Core/Actions/ComputerUseController.swift`, `Core/Capture/CaptureController.swift`,
17
+ `Core/Runs/RunStore.swift` + `RunModels.swift` (persists to `~/Library/Application Support/Lattices/Runs/`, `runs.json` index).
18
+ - **CLI** (`bin/lattices.ts`): `lattices computer|capture|runs|terminals`.
19
+ - **Palette** (`PaletteCommand.swift`): "Screenshot Current Window", "Review Runs" under a new `.run` category → `ScreenMapWindowController.showPage(.runs)` + `RunsReviewView.swift`.
20
+ - **Safety model**: `ComputerTreatment` = observe/stage/present/execute. Typing only targets
21
+ scored *safe* terminals (avoids claude/codex/vim, requires idle shell for Enter), with a transport
22
+ ladder: tmux `send-keys` → iTerm session `write text` → pasteboard/key-events (active tab only).
23
+
24
+ This is a strong foundation. The gaps below are about **shape**, not patching.
25
+
26
+ ## Gaps by focus area (root-cause)
27
+
28
+ ### 1. Loading apps — not a first-class, composable operation
29
+ App/project launch lives only inside the voice `launch` intent → `session.launch`
30
+ (`Intents/LaunchIntent.swift`), with a brittle fallback (capitalize first letter, `NSWorkspace`
31
+ name match; no bundle-id resolution). Root cause: **there is no `apps.launch` daemon verb and no
32
+ "wait until window exists" precondition.** So launching can't be wrapped as a Run, can't be composed
33
+ with capture/type/click, and the computer-use layer can't open a target app before acting on it.
34
+ → Add `apps.launch` (name/bundleId/project) returning a `RunSession` with surfaces, plus a shared
35
+ `waitForWindow` primitive that `computer.focusWindow/typeText/click` reuse as a precondition.
36
+
37
+ ### 2. Voice/talk flows — the new capabilities are invisible to voice
38
+ `IntentEngine.swift` vocabulary stops at workspace control: tile_window, focus, launch,
39
+ switch_layer, search, list_*, distribute, create_layer, kill, scan, swap, hide, highlight,
40
+ move_to_display, find_mouse, summon_mouse, undo. Root cause: **none of `computer.*`, `capture.*`,
41
+ or `runs.*` is registered as an intent.** Voice can move/observe windows but cannot drive the proof
42
+ loop (screenshot, type, review a run). The slot/dispatch plumbing already exists, so this is additive.
43
+ → Register `screenshot` → `capture.screenshotWindow`; `type` → `computer.typeText` (default
44
+ `treatment=stage`, require explicit confirm before execute — matches the HandsOff "don't act on
45
+ questions" preference); `show_cursor`/`click` → cursor/click methods; `review_run` → runs page.
46
+
47
+ ### 3. Typing — solid, but terminal-only
48
+ Strongest area. The transport ladder + safety scoring is the right shape. Root-cause limits:
49
+ - Typing targets **terminals exclusively** (`TerminalCandidate` / `ProcessModel.synthesizeTerminals`).
50
+ There is no "type into the focused text field of app X" (browser URL bar, native field).
51
+ - The pasteboard path saves/restores the clipboard but requires the tab already active; iTerm is
52
+ explicitly excluded from the keyboard transport, so non-tmux iTerm has no fallback.
53
+ → Add an AX-based `computer.typeInto` that resolves the focused element / a target text field, keeping
54
+ the terminal path as the specialized *safe* case. This generalizes typing without weakening the
55
+ terminal safety model.
56
+
57
+ ### 4. Clicking around — the verb does not exist
58
+ **There is no click action.** `computer.showCursor` only renders a visual marker; it posts no mouse
59
+ event. The only `CGEvent` mouse code (`Core/Input/MouseGestureController.swift`) *recognizes* gestures
60
+ — it does not synthesize targeted clicks. So the "act" half of the loop for non-terminal targets
61
+ (buttons, links, menu items) is entirely missing. This is the single biggest missing primitive.
62
+ → Add `computer.click` (+ `computer.moveCursor`) that resolves a target (coordinate, AX element, or
63
+ window+role) and posts left/right down/up via `CGEvent`, wrapped in a Run with before/after capture,
64
+ gated by the same treatment model (observe/stage/present/execute).
65
+
66
+ ### 5. Cross-cutting: the proof loop is open at both ends
67
+ - **Recording not implemented.** Proposal lists `capture.recordWindow/recordRegion` + an AppKit
68
+ `--recording-probe` (Phase 3); only screenshots exist (`WindowCapture` uses `SCStream` for stills).
69
+ Also missing: `runs.start`, `runs.stop`.
70
+ - **No verify step.** `typeText` captures before/after screenshots but never asserts the text landed.
71
+ An OCR/AX diff (reusing the existing `ocr.search`) would close observe → act → **verify** and feed
72
+ LAT-005 receipts.
73
+
74
+ ## Suggested implementation order (highest leverage first)
75
+
76
+ 1. **`computer.click` + `computer.moveCursor`** — fills the only missing computer-use verb; reuse
77
+ treatment model + Run wrapper + before/after capture.
78
+ 2. **Voice intents over existing `computer.*`/`capture.*`/`runs.*`** — additive, low risk; stage-by-default
79
+ for any execute, explicit confirm gate.
80
+ 3. **`apps.launch` (Run-wrapped) + `waitForWindow` precondition** — unblocks "load app, then act".
81
+ 4. **`computer.typeInto` (AX focused-element)** — generalize typing beyond terminals.
82
+ 5. **LAT-006 Phase 3 recording probe + `runs.stop`**, then an **OCR/AX verify** step on type/click runs.
83
+
84
+ ## Testing steps
85
+
86
+ - CLI dogfood (per project convention — test via `lattices`, not raw daemon):
87
+ `lattices computer prepare`, `lattices computer type --text "ls" --dry-run`, `lattices capture window`,
88
+ `lattices runs list`, `lattices runs <id> --json`.
89
+ - Treatment matrix: observe/stage/present/execute each produce a Run with correct artifacts and never
90
+ over-act (stage/observe must not focus or type).
91
+ - Safety regression: `computer.typeText` refuses claude/codex/vim targets; refuses Enter on non-idle shells.
92
+ - Click (new): coordinate click + AX-element click on a known button → before/after artifacts in the run
93
+ dir; assert no click when `treatment != execute`.
94
+ - Voice: "screenshot this window" / "type ls in my terminal" resolve to the right daemon method; execute
95
+ paths require confirm.
96
+ - Persistence: confirm `~/Library/Application Support/Lattices/Runs/` + `runs.json` survive an app restart.
97
+
98
+ ## Owner / next move
99
+
100
+ This is an answer, not a handoff — no other agent needs waking. The clearest single next step that
101
+ unblocks the most use cases is **`computer.click`** (closes the "act" gap for non-terminal targets),
102
+ immediately followed by **exposing the existing computer/capture/run methods to voice**. Both are owned
103
+ by the Lattices macOS app. Recording + verify are the right Phase-3 follow-ups once click + voice land.