@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,376 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { createConnection } from "node:net";
3
+
4
+ import { z } from "zod";
5
+
6
+ const DAEMON_HOST = "127.0.0.1";
7
+ const DAEMON_PORT = 9399;
8
+
9
+ export const computerTreatmentSchema = z.enum([
10
+ "observe",
11
+ "stage",
12
+ "present",
13
+ "execute",
14
+ ]);
15
+
16
+ export const computerClickTransportSchema = z.enum([
17
+ "auto",
18
+ "ax",
19
+ "accessibility",
20
+ "pointer",
21
+ "mouse",
22
+ "hardware",
23
+ ]);
24
+
25
+ export const cursorStyleSchema = z.enum(["spotlight", "pulse", "marker"]);
26
+ export const cursorShapeSchema = z.enum([
27
+ "arrow",
28
+ "chevron",
29
+ "facet",
30
+ "shard",
31
+ "wedge",
32
+ "prism",
33
+ "notch",
34
+ "needle",
35
+ "petal",
36
+ "kite",
37
+ ]);
38
+ export const cursorSizeSchema = z.enum(["tiny", "small", "regular", "large"]);
39
+ export const cursorTrailSchema = z.enum(["none", "thread", "ribbon", "spark", "comet", "route"]);
40
+ export const cursorMotionSchema = z.enum([
41
+ "glide",
42
+ "snap",
43
+ "float",
44
+ "rush",
45
+ "crawl",
46
+ "accelerate",
47
+ "teleport",
48
+ "spring",
49
+ "magnet",
50
+ "slingshot",
51
+ ]);
52
+ export const cursorTrajectorySchema = z.enum(["straight", "soft", "arc", "swoop", "overshoot"]);
53
+ export const cursorGlowSchema = z.enum(["none", "soft", "halo", "comet"]);
54
+ export const cursorIdleSchema = z.enum([
55
+ "still",
56
+ "breathe",
57
+ "wiggle",
58
+ "orbit",
59
+ "hover",
60
+ "nod",
61
+ "drift",
62
+ "shimmer",
63
+ "blink",
64
+ "tremble",
65
+ ]);
66
+ export const cursorEdgeSchema = z.enum([
67
+ "none",
68
+ "pulse",
69
+ "ripple",
70
+ "tick",
71
+ "reticle",
72
+ "blink",
73
+ "spark",
74
+ "underline",
75
+ "echo",
76
+ "scan",
77
+ "pin",
78
+ ]);
79
+ export const cursorSoundSchema = z.enum(["none", "tick", "click", "engage", "chime"]);
80
+ export const captionPlacementSchema = z.enum([
81
+ "top-left",
82
+ "top-right",
83
+ "bottom-left",
84
+ "bottom-right",
85
+ "top-center",
86
+ "top",
87
+ "center",
88
+ "middle",
89
+ "near-cursor",
90
+ "cursor",
91
+ ]);
92
+
93
+ const ratioSchema = z.number().min(0).max(1);
94
+ const pointSchema = z.number().finite();
95
+
96
+ const windowTargetSchema = z.object({
97
+ wid: z.number().int().positive().optional(),
98
+ app: z.string().min(1).optional(),
99
+ title: z.string().optional(),
100
+ });
101
+
102
+ const actionBaseSchema = z.object({
103
+ treatment: computerTreatmentSchema.optional(),
104
+ dryRun: z.boolean().optional(),
105
+ capture: z.boolean().optional(),
106
+ source: z.string().optional(),
107
+ });
108
+
109
+ const pointTargetSchema = z.object({
110
+ x: pointSchema.optional(),
111
+ y: pointSchema.optional(),
112
+ xRatio: ratioSchema.optional(),
113
+ yRatio: ratioSchema.optional(),
114
+ });
115
+
116
+ const cursorAppearanceSchema = z.object({
117
+ style: cursorStyleSchema.optional(),
118
+ appearance: cursorStyleSchema.optional(),
119
+ shape: cursorShapeSchema.optional(),
120
+ angleDeg: z.number().finite().optional(),
121
+ size: cursorSizeSchema.optional(),
122
+ color: z.string().min(1).optional(),
123
+ durationMs: z.number().int().positive().optional(),
124
+ label: z.string().optional(),
125
+ caption: z.string().optional(),
126
+ captionTitle: z.string().optional(),
127
+ captionBody: z.string().optional(),
128
+ captionDetail: z.string().optional(),
129
+ captionTags: z.string().optional(),
130
+ captionMode: z.enum(["auto", "selection"]).optional(),
131
+ captionEyebrow: z.string().optional(),
132
+ captionLeadMs: z.number().finite().nonnegative().optional(),
133
+ captionSound: cursorSoundSchema.optional(),
134
+ captionPlacement: captionPlacementSchema.optional(),
135
+ captionMargin: z.number().finite().nonnegative().optional(),
136
+ captionX: z.number().finite().optional(),
137
+ captionY: z.number().finite().optional(),
138
+ captionXRatio: ratioSchema.optional(),
139
+ captionYRatio: ratioSchema.optional(),
140
+ captionLeftRatio: ratioSchema.optional(),
141
+ captionTopRatio: ratioSchema.optional(),
142
+ sound: cursorSoundSchema.optional(),
143
+ sfx: cursorSoundSchema.optional(),
144
+ showCaption: z.boolean().optional(),
145
+ captionSelections: z.boolean().optional(),
146
+ treatmentLabel: z.string().optional(),
147
+ variant: z.string().optional(),
148
+ trail: cursorTrailSchema.optional(),
149
+ pathStyle: cursorTrailSchema.optional(),
150
+ motion: cursorMotionSchema.optional(),
151
+ trajectory: cursorTrajectorySchema.optional(),
152
+ glow: cursorGlowSchema.optional(),
153
+ bloom: cursorGlowSchema.optional(),
154
+ idle: cursorIdleSchema.optional(),
155
+ settle: cursorIdleSchema.optional(),
156
+ presence: cursorIdleSchema.optional(),
157
+ edge: cursorEdgeSchema.optional(),
158
+ edgeEffect: cursorEdgeSchema.optional(),
159
+ arrival: cursorEdgeSchema.optional(),
160
+ typewriter: z.boolean().optional(),
161
+ typing: z.boolean().optional(),
162
+ typeIntervalMs: z.number().finite().positive().optional(),
163
+ typingIntervalMs: z.number().finite().positive().optional(),
164
+ });
165
+
166
+ export const computerClickParamsSchema = windowTargetSchema
167
+ .merge(pointTargetSchema)
168
+ .merge(actionBaseSchema)
169
+ .merge(cursorAppearanceSchema.pick({ label: true }))
170
+ .extend({
171
+ button: z.enum(["left", "right", "secondary", "context"]).optional(),
172
+ transport: computerClickTransportSchema.optional(),
173
+ axLabel: z.string().min(1).optional(),
174
+ targetText: z.string().min(1).optional(),
175
+ noFocus: z.boolean().optional(),
176
+ });
177
+
178
+ export const computerMagicCursorParamsSchema = windowTargetSchema
179
+ .merge(pointTargetSchema)
180
+ .merge(actionBaseSchema)
181
+ .merge(cursorAppearanceSchema)
182
+ .extend({
183
+ text: z.string().optional(),
184
+ append: z.boolean().optional(),
185
+ fromX: pointSchema.optional(),
186
+ fromY: pointSchema.optional(),
187
+ fromXRatio: ratioSchema.optional(),
188
+ fromYRatio: ratioSchema.optional(),
189
+ });
190
+
191
+ export function createCuaClient(options = {}) {
192
+ const defaultTimeoutMs = options.defaultTimeoutMs ?? 30_000;
193
+
194
+ async function call(method, params, timeoutMs = defaultTimeoutMs) {
195
+ return daemonCall(method, params, timeoutMs);
196
+ }
197
+
198
+ return {
199
+ click(params) {
200
+ return call("computer.click", computerClickParamsSchema.parse(params));
201
+ },
202
+ magicCursor(params) {
203
+ return call("computer.magicCursor", computerMagicCursorParamsSchema.parse(params));
204
+ },
205
+ };
206
+ }
207
+
208
+ export const cua = createCuaClient();
209
+
210
+ export function click(params) {
211
+ return cua.click(params);
212
+ }
213
+
214
+ export function magicCursor(params) {
215
+ return cua.magicCursor(params);
216
+ }
217
+
218
+ async function daemonCall(method, params = null, timeoutMs = 3000) {
219
+ const id = randomBytes(4).toString("hex");
220
+ const request = JSON.stringify({ id, method, params: params ?? null });
221
+
222
+ return new Promise((resolve, reject) => {
223
+ const socket = createConnection({ host: DAEMON_HOST, port: DAEMON_PORT });
224
+ let settled = false;
225
+ let buffer = Buffer.alloc(0);
226
+ let upgraded = false;
227
+
228
+ const timer = setTimeout(() => {
229
+ if (!settled) {
230
+ settled = true;
231
+ socket.destroy();
232
+ reject(new Error("Daemon request timed out"));
233
+ }
234
+ }, timeoutMs);
235
+
236
+ const cleanup = () => {
237
+ clearTimeout(timer);
238
+ socket.destroy();
239
+ };
240
+
241
+ socket.on("error", (err) => {
242
+ if (!settled) {
243
+ settled = true;
244
+ cleanup();
245
+ reject(err);
246
+ }
247
+ });
248
+
249
+ socket.on("connect", () => {
250
+ const key = randomBytes(16).toString("base64");
251
+ const upgrade = [
252
+ "GET / HTTP/1.1",
253
+ `Host: ${DAEMON_HOST}:${DAEMON_PORT}`,
254
+ "Upgrade: websocket",
255
+ "Connection: Upgrade",
256
+ `Sec-WebSocket-Key: ${key}`,
257
+ "Sec-WebSocket-Version: 13",
258
+ "",
259
+ "",
260
+ ].join("\r\n");
261
+ socket.write(upgrade);
262
+ });
263
+
264
+ socket.on("data", (chunk) => {
265
+ buffer = Buffer.concat([buffer, chunk]);
266
+
267
+ if (!upgraded) {
268
+ const headerEnd = buffer.indexOf("\r\n\r\n");
269
+ if (headerEnd === -1) return;
270
+ const header = buffer.subarray(0, headerEnd).toString();
271
+ if (!header.includes("101")) {
272
+ settled = true;
273
+ cleanup();
274
+ reject(new Error("WebSocket upgrade failed"));
275
+ return;
276
+ }
277
+ upgraded = true;
278
+ buffer = buffer.subarray(headerEnd + 4);
279
+ sendFrame(socket, request);
280
+ }
281
+
282
+ while (true) {
283
+ const result = parseFrame(buffer);
284
+ if (!result) break;
285
+ buffer = result.rest;
286
+
287
+ try {
288
+ const parsed = JSON.parse(result.payload);
289
+ if (parsed.event || parsed.id !== id) continue;
290
+ if (!settled) {
291
+ settled = true;
292
+ cleanup();
293
+ if (parsed.error) {
294
+ reject(new Error(parsed.error));
295
+ } else {
296
+ resolve(parsed.result);
297
+ }
298
+ }
299
+ return;
300
+ } catch {
301
+ if (!settled) {
302
+ settled = true;
303
+ cleanup();
304
+ reject(new Error("Invalid JSON response from daemon"));
305
+ }
306
+ return;
307
+ }
308
+ }
309
+ });
310
+ });
311
+ }
312
+
313
+ function sendFrame(socket, text) {
314
+ const payload = Buffer.from(text, "utf8");
315
+ const mask = randomBytes(4);
316
+ const len = payload.length;
317
+
318
+ let header;
319
+ if (len < 126) {
320
+ header = Buffer.alloc(2);
321
+ header[0] = 0x81;
322
+ header[1] = 0x80 | len;
323
+ } else if (len < 65536) {
324
+ header = Buffer.alloc(4);
325
+ header[0] = 0x81;
326
+ header[1] = 0x80 | 126;
327
+ header.writeUInt16BE(len, 2);
328
+ } else {
329
+ header = Buffer.alloc(10);
330
+ header[0] = 0x81;
331
+ header[1] = 0x80 | 127;
332
+ header.writeBigUInt64BE(BigInt(len), 2);
333
+ }
334
+
335
+ const masked = Buffer.alloc(payload.length);
336
+ for (let i = 0; i < payload.length; i++) {
337
+ masked[i] = payload[i] ^ mask[i % 4];
338
+ }
339
+
340
+ socket.write(Buffer.concat([header, mask, masked]));
341
+ }
342
+
343
+ function parseFrame(buf) {
344
+ if (buf.length < 2) return null;
345
+
346
+ const isMasked = (buf[1] & 0x80) !== 0;
347
+ let payloadLen = buf[1] & 0x7f;
348
+ let offset = 2;
349
+
350
+ if (payloadLen === 126) {
351
+ if (buf.length < 4) return null;
352
+ payloadLen = buf.readUInt16BE(2);
353
+ offset = 4;
354
+ } else if (payloadLen === 127) {
355
+ if (buf.length < 10) return null;
356
+ payloadLen = Number(buf.readBigUInt64BE(2));
357
+ offset = 10;
358
+ }
359
+
360
+ if (isMasked) offset += 4;
361
+ if (buf.length < offset + payloadLen) return null;
362
+
363
+ let payload = buf.subarray(offset, offset + payloadLen);
364
+ if (isMasked) {
365
+ const maskKey = buf.subarray(offset - 4, offset);
366
+ payload = Buffer.alloc(payloadLen);
367
+ for (let i = 0; i < payloadLen; i++) {
368
+ payload[i] = buf[offset + i] ^ maskKey[i % 4];
369
+ }
370
+ }
371
+
372
+ return {
373
+ payload: payload.toString("utf8"),
374
+ rest: buf.subarray(offset + payloadLen),
375
+ };
376
+ }
@@ -1,24 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
- <plist version="1.0">
4
- <dict>
5
- <key>CFBundleIdentifier</key>
6
- <string>com.arach.lattices</string>
7
- <key>CFBundleName</key>
8
- <string>Lattices</string>
9
- <key>CFBundleExecutable</key>
10
- <string>Lattices</string>
11
- <key>CFBundleIconFile</key>
12
- <string>AppIcon</string>
13
- <key>CFBundlePackageType</key>
14
- <string>APPL</string>
15
- <key>CFBundleVersion</key>
16
- <string>1</string>
17
- <key>CFBundleShortVersionString</key>
18
- <string>0.1.0</string>
19
- <key>LSUIElement</key>
20
- <true/>
21
- <key>NSSupportsAutomaticTermination</key>
22
- <true/>
23
- </dict>
24
- </plist>
package/app/Package.swift DELETED
@@ -1,13 +0,0 @@
1
- // swift-tools-version: 5.9
2
- import PackageDescription
3
-
4
- let package = Package(
5
- name: "Lattices",
6
- platforms: [.macOS(.v13)],
7
- targets: [
8
- .executableTarget(
9
- name: "Lattices",
10
- path: "Sources"
11
- )
12
- ]
13
- )
@@ -1,61 +0,0 @@
1
- import SwiftUI
2
-
3
- /// A single action row with shortcut badge, label, optional icon, and hotkey hint.
4
- struct ActionRow: View {
5
- let shortcut: String
6
- let label: String
7
- var hotkey: String? = nil
8
- var icon: String? = nil
9
- var accentColor: Color = Palette.textDim
10
- var action: () -> Void
11
-
12
- @State private var isHovered = false
13
-
14
- var body: some View {
15
- Button(action: action) {
16
- HStack(spacing: 10) {
17
- // Shortcut badge
18
- Text(shortcut)
19
- .font(Typo.monoBold(10))
20
- .foregroundColor(accentColor)
21
- .frame(width: 18, height: 18)
22
- .background(
23
- RoundedRectangle(cornerRadius: 4)
24
- .fill(accentColor.opacity(0.12))
25
- )
26
-
27
- // Icon
28
- if let icon {
29
- Image(systemName: icon)
30
- .font(.system(size: 11, weight: .medium))
31
- .foregroundColor(isHovered ? Palette.text : Palette.textDim)
32
- .frame(width: 14)
33
- }
34
-
35
- // Label
36
- Text(label)
37
- .font(Typo.mono(12))
38
- .foregroundColor(isHovered ? Palette.text : Palette.textDim)
39
- .lineLimit(1)
40
-
41
- Spacer()
42
-
43
- // Hotkey
44
- if let hotkey {
45
- Text(hotkey)
46
- .font(Typo.mono(10))
47
- .foregroundColor(Palette.textMuted)
48
- }
49
- }
50
- .padding(.horizontal, 10)
51
- .padding(.vertical, 6)
52
- .background(
53
- RoundedRectangle(cornerRadius: 5)
54
- .fill(isHovered ? Palette.surfaceHov : Color.clear)
55
- )
56
- .contentShape(Rectangle())
57
- }
58
- .buttonStyle(.plain)
59
- .onHover { isHovered = $0 }
60
- }
61
- }
@@ -1,10 +0,0 @@
1
- import SwiftUI
2
-
3
- @main
4
- struct LatticesApp: App {
5
- @NSApplicationDelegateAdaptor(AppDelegate.self) var delegate
6
-
7
- var body: some Scene {
8
- Settings { EmptyView() }
9
- }
10
- }