@lattices/cli 0.4.14 → 0.6.0

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 (181) hide show
  1. package/README.md +5 -7
  2. package/apps/mac/Info.plist +4 -4
  3. package/apps/mac/Lattices.app/Contents/Info.plist +4 -12
  4. package/apps/mac/Lattices.app/Contents/MacOS/Lattices +0 -0
  5. package/bin/lattices-app.ts +110 -17
  6. package/bin/lattices-build +125 -0
  7. package/bin/lattices-dev +89 -16
  8. package/bin/lattices.ts +977 -16
  9. package/docs/agents.md +81 -4
  10. package/docs/ai-chat-ux-review.md +416 -0
  11. package/docs/api.md +135 -3
  12. package/docs/app.md +30 -8
  13. package/docs/config.md +4 -0
  14. package/docs/mouse-gestures.md +60 -1
  15. package/docs/proposals/LAT-004-interactive-overlay-actors.md +1 -1
  16. package/docs/proposals/LAT-005-action-runtime-product-spine.md +914 -0
  17. package/docs/proposals/LAT-006-mira-in-lattices.md +553 -0
  18. package/docs/proposals/LAT-007-unified-app-shell.md +128 -0
  19. package/docs/reference/dewey.config.ts +2 -2
  20. package/docs/release.md +171 -0
  21. package/docs/repo-structure.md +5 -5
  22. package/docs/voice.md +11 -27
  23. package/package.json +11 -10
  24. package/apps/mac/Package.swift +0 -27
  25. package/apps/mac/Sources/AppShell/App.swift +0 -26
  26. package/apps/mac/Sources/AppShell/AppActivationCoordinator.swift +0 -27
  27. package/apps/mac/Sources/AppShell/AppDelegate.swift +0 -189
  28. package/apps/mac/Sources/AppShell/AppServicesBootstrap.swift +0 -25
  29. package/apps/mac/Sources/AppShell/AppShellView.swift +0 -171
  30. package/apps/mac/Sources/AppShell/AppUpdater.swift +0 -305
  31. package/apps/mac/Sources/AppShell/CliActionLauncher.swift +0 -50
  32. package/apps/mac/Sources/AppShell/HomeDashboardView.swift +0 -133
  33. package/apps/mac/Sources/AppShell/HotkeyBootstrap.swift +0 -87
  34. package/apps/mac/Sources/AppShell/KeyRecorderView.swift +0 -210
  35. package/apps/mac/Sources/AppShell/LatticesRuntime.swift +0 -104
  36. package/apps/mac/Sources/AppShell/MainView.swift +0 -847
  37. package/apps/mac/Sources/AppShell/MainWindow.swift +0 -83
  38. package/apps/mac/Sources/AppShell/MenuBarController.swift +0 -177
  39. package/apps/mac/Sources/AppShell/OnboardingView.swift +0 -483
  40. package/apps/mac/Sources/AppShell/PermissionsAssistantView.swift +0 -366
  41. package/apps/mac/Sources/AppShell/PermissionsAssistantWindow.swift +0 -70
  42. package/apps/mac/Sources/AppShell/Preferences.swift +0 -297
  43. package/apps/mac/Sources/AppShell/SettingsView.swift +0 -3163
  44. package/apps/mac/Sources/AppShell/SettingsWindow.swift +0 -34
  45. package/apps/mac/Sources/AppShell/WorkspaceInspectorPresenter.swift +0 -13
  46. package/apps/mac/Sources/Core/Actions/HotkeyManager.swift +0 -256
  47. package/apps/mac/Sources/Core/Actions/HotkeyStore.swift +0 -399
  48. package/apps/mac/Sources/Core/Actions/IntentEngine.swift +0 -988
  49. package/apps/mac/Sources/Core/Actions/IntentSchema.swift +0 -94
  50. package/apps/mac/Sources/Core/Actions/Intents/CreateLayerIntent.swift +0 -54
  51. package/apps/mac/Sources/Core/Actions/Intents/DistributeIntent.swift +0 -56
  52. package/apps/mac/Sources/Core/Actions/Intents/FocusIntent.swift +0 -69
  53. package/apps/mac/Sources/Core/Actions/Intents/HelpIntent.swift +0 -41
  54. package/apps/mac/Sources/Core/Actions/Intents/KillIntent.swift +0 -47
  55. package/apps/mac/Sources/Core/Actions/Intents/LatticeIntent.swift +0 -53
  56. package/apps/mac/Sources/Core/Actions/Intents/LaunchIntent.swift +0 -67
  57. package/apps/mac/Sources/Core/Actions/Intents/ListSessionsIntent.swift +0 -32
  58. package/apps/mac/Sources/Core/Actions/Intents/ListWindowsIntent.swift +0 -30
  59. package/apps/mac/Sources/Core/Actions/Intents/ScanIntent.swift +0 -52
  60. package/apps/mac/Sources/Core/Actions/Intents/SearchIntent.swift +0 -190
  61. package/apps/mac/Sources/Core/Actions/Intents/SwitchLayerIntent.swift +0 -50
  62. package/apps/mac/Sources/Core/Actions/Intents/TileIntent.swift +0 -61
  63. package/apps/mac/Sources/Core/Actions/PaletteCommand.swift +0 -439
  64. package/apps/mac/Sources/Core/Actions/VoiceIntentResolver.swift +0 -713
  65. package/apps/mac/Sources/Core/Companion/CompanionActivityLog.swift +0 -70
  66. package/apps/mac/Sources/Core/Companion/CompanionKeyboardController.swift +0 -141
  67. package/apps/mac/Sources/Core/Companion/LatticesCompanionBridgeServer.swift +0 -454
  68. package/apps/mac/Sources/Core/Companion/LatticesCompanionCockpit.swift +0 -555
  69. package/apps/mac/Sources/Core/Companion/LatticesCompanionSecurityCoordinator.swift +0 -629
  70. package/apps/mac/Sources/Core/Companion/LatticesCompanionTrackpadController.swift +0 -204
  71. package/apps/mac/Sources/Core/Companion/LatticesDeckHost.swift +0 -1463
  72. package/apps/mac/Sources/Core/Daemon/DaemonProtocol.swift +0 -114
  73. package/apps/mac/Sources/Core/Daemon/DaemonServer.swift +0 -427
  74. package/apps/mac/Sources/Core/Daemon/LatticesApi.swift +0 -2965
  75. package/apps/mac/Sources/Core/Desktop/AccessibilityTextExtractor.swift +0 -111
  76. package/apps/mac/Sources/Core/Desktop/AppTypeClassifier.swift +0 -106
  77. package/apps/mac/Sources/Core/Desktop/DesktopModel.swift +0 -331
  78. package/apps/mac/Sources/Core/Desktop/DesktopModelTypes.swift +0 -73
  79. package/apps/mac/Sources/Core/Desktop/InventoryManager.swift +0 -35
  80. package/apps/mac/Sources/Core/Desktop/InventoryPath.swift +0 -43
  81. package/apps/mac/Sources/Core/Desktop/MouseFinder.swift +0 -527
  82. package/apps/mac/Sources/Core/Desktop/OcrModel.swift +0 -467
  83. package/apps/mac/Sources/Core/Desktop/OcrStore.swift +0 -329
  84. package/apps/mac/Sources/Core/Desktop/PlacementSpec.swift +0 -195
  85. package/apps/mac/Sources/Core/Desktop/SessionWindowLocator.swift +0 -139
  86. package/apps/mac/Sources/Core/Desktop/TilePickerView.swift +0 -209
  87. package/apps/mac/Sources/Core/Desktop/WindowCapture.swift +0 -33
  88. package/apps/mac/Sources/Core/Desktop/WindowDragSnapController.swift +0 -429
  89. package/apps/mac/Sources/Core/Desktop/WindowPreviewCard.swift +0 -100
  90. package/apps/mac/Sources/Core/Desktop/WindowPreviewStore.swift +0 -112
  91. package/apps/mac/Sources/Core/Desktop/WindowSelectionStore.swift +0 -76
  92. package/apps/mac/Sources/Core/Desktop/WindowTiler.swift +0 -2222
  93. package/apps/mac/Sources/Core/Input/EventTapBreaker.swift +0 -124
  94. package/apps/mac/Sources/Core/Input/EventTapThread.swift +0 -54
  95. package/apps/mac/Sources/Core/Input/InputCaptureResetCenter.swift +0 -20
  96. package/apps/mac/Sources/Core/Input/KeyboardRemapConfig.swift +0 -69
  97. package/apps/mac/Sources/Core/Input/KeyboardRemapController.swift +0 -346
  98. package/apps/mac/Sources/Core/Input/KeyboardRemapStore.swift +0 -141
  99. package/apps/mac/Sources/Core/Input/MouseGestureConfig.swift +0 -499
  100. package/apps/mac/Sources/Core/Input/MouseGestureController.swift +0 -2583
  101. package/apps/mac/Sources/Core/Input/MouseInputDeviceStore.swift +0 -98
  102. package/apps/mac/Sources/Core/Input/MouseInputEventViewer.swift +0 -272
  103. package/apps/mac/Sources/Core/Input/MouseShortcutStore.swift +0 -170
  104. package/apps/mac/Sources/Core/Input/SecureEventInputMonitor.swift +0 -39
  105. package/apps/mac/Sources/Core/Input/ShapeRecognizer.swift +0 -624
  106. package/apps/mac/Sources/Core/Input/TapBudgetMeter.swift +0 -56
  107. package/apps/mac/Sources/Core/Overlays/AppWindowShell.swift +0 -63
  108. package/apps/mac/Sources/Core/Overlays/CommandMode/CommandModeState.swift +0 -1566
  109. package/apps/mac/Sources/Core/Overlays/CommandMode/CommandModeView.swift +0 -1927
  110. package/apps/mac/Sources/Core/Overlays/CommandMode/CommandModeWindow.swift +0 -196
  111. package/apps/mac/Sources/Core/Overlays/CommandPalette/CommandPaletteView.swift +0 -307
  112. package/apps/mac/Sources/Core/Overlays/CommandPalette/CommandPaletteWindow.swift +0 -67
  113. package/apps/mac/Sources/Core/Overlays/HUD/CheatSheetHUD.swift +0 -576
  114. package/apps/mac/Sources/Core/Overlays/HUD/HUDBottomBar.swift +0 -279
  115. package/apps/mac/Sources/Core/Overlays/HUD/HUDController.swift +0 -1158
  116. package/apps/mac/Sources/Core/Overlays/HUD/HUDLeftBar.swift +0 -849
  117. package/apps/mac/Sources/Core/Overlays/HUD/HUDMinimap.swift +0 -179
  118. package/apps/mac/Sources/Core/Overlays/HUD/HUDRightBar.swift +0 -596
  119. package/apps/mac/Sources/Core/Overlays/HUD/HUDState.swift +0 -367
  120. package/apps/mac/Sources/Core/Overlays/HUD/HUDTopBar.swift +0 -243
  121. package/apps/mac/Sources/Core/Overlays/HUD/LauncherHUD.swift +0 -334
  122. package/apps/mac/Sources/Core/Overlays/HUD/LayerBezel.swift +0 -203
  123. package/apps/mac/Sources/Core/Overlays/OmniSearch/OmniSearchState.swift +0 -280
  124. package/apps/mac/Sources/Core/Overlays/OmniSearch/OmniSearchView.swift +0 -422
  125. package/apps/mac/Sources/Core/Overlays/OmniSearch/OmniSearchWindow.swift +0 -94
  126. package/apps/mac/Sources/Core/Overlays/OverlayPanelShell.swift +0 -241
  127. package/apps/mac/Sources/Core/Overlays/ScreenMap/ScreenMapState.swift +0 -3135
  128. package/apps/mac/Sources/Core/Overlays/ScreenMap/ScreenMapView.swift +0 -3977
  129. package/apps/mac/Sources/Core/Overlays/ScreenMap/ScreenMapWindowController.swift +0 -119
  130. package/apps/mac/Sources/Core/Overlays/ScreenOverlayCanvasController.swift +0 -1217
  131. package/apps/mac/Sources/Core/Overlays/Voice/VoiceCommandWindow.swift +0 -1575
  132. package/apps/mac/Sources/Core/Pi/PiAuthNextStepCard.swift +0 -148
  133. package/apps/mac/Sources/Core/Pi/PiAuthPromptCard.swift +0 -90
  134. package/apps/mac/Sources/Core/Pi/PiChatDock.swift +0 -564
  135. package/apps/mac/Sources/Core/Pi/PiChatSession.swift +0 -1948
  136. package/apps/mac/Sources/Core/Pi/PiInstallCallout.swift +0 -86
  137. package/apps/mac/Sources/Core/Pi/PiProviderSetupCallout.swift +0 -99
  138. package/apps/mac/Sources/Core/Pi/PiWorkspaceView.swift +0 -510
  139. package/apps/mac/Sources/Core/System/Capability.swift +0 -79
  140. package/apps/mac/Sources/Core/System/DiagnosticLog.swift +0 -373
  141. package/apps/mac/Sources/Core/System/EventBus.swift +0 -31
  142. package/apps/mac/Sources/Core/System/PermissionChecker.swift +0 -224
  143. package/apps/mac/Sources/Core/System/ProcessModel.swift +0 -199
  144. package/apps/mac/Sources/Core/System/ProcessQuery.swift +0 -151
  145. package/apps/mac/Sources/Core/System/SystemTelemetryMonitor.swift +0 -273
  146. package/apps/mac/Sources/Core/Voice/AdvisorLearningStore.swift +0 -90
  147. package/apps/mac/Sources/Core/Voice/AgentSession.swift +0 -377
  148. package/apps/mac/Sources/Core/Voice/AudioProvider.swift +0 -555
  149. package/apps/mac/Sources/Core/Voice/HandsOffSession.swift +0 -839
  150. package/apps/mac/Sources/Core/Voice/VoiceChatView.swift +0 -192
  151. package/apps/mac/Sources/Core/Voice/VoxClient.swift +0 -454
  152. package/apps/mac/Sources/Core/Workspace/Project.swift +0 -28
  153. package/apps/mac/Sources/Core/Workspace/ProjectScanner.swift +0 -141
  154. package/apps/mac/Sources/Core/Workspace/SessionLayerStore.swift +0 -285
  155. package/apps/mac/Sources/Core/Workspace/SessionManager.swift +0 -75
  156. package/apps/mac/Sources/Core/Workspace/Terminal/Terminal.swift +0 -259
  157. package/apps/mac/Sources/Core/Workspace/Terminal/TerminalQuery.swift +0 -156
  158. package/apps/mac/Sources/Core/Workspace/Terminal/TerminalSynthesizer.swift +0 -200
  159. package/apps/mac/Sources/Core/Workspace/Tmux/TmuxModel.swift +0 -60
  160. package/apps/mac/Sources/Core/Workspace/Tmux/TmuxQuery.swift +0 -105
  161. package/apps/mac/Sources/Core/Workspace/WorkspaceManager.swift +0 -1027
  162. package/apps/mac/Sources/UI/ActionRow.swift +0 -78
  163. package/apps/mac/Sources/UI/OrphanRow.swift +0 -129
  164. package/apps/mac/Sources/UI/ProjectRow.swift +0 -368
  165. package/apps/mac/Sources/UI/TabGroupRow.swift +0 -178
  166. package/apps/mac/Sources/UI/Theme.swift +0 -164
  167. package/apps/mac/Tests/StageDragTests.swift +0 -333
  168. package/apps/mac/Tests/StageJoinTests.swift +0 -313
  169. package/apps/mac/Tests/StageManagerTests.swift +0 -280
  170. package/apps/mac/Tests/StageTileTests.swift +0 -353
  171. package/swift/Package.swift +0 -20
  172. package/swift/Sources/DeckKit/DeckAction.swift +0 -51
  173. package/swift/Sources/DeckKit/DeckBridgeSecurity.swift +0 -152
  174. package/swift/Sources/DeckKit/DeckCockpit.swift +0 -82
  175. package/swift/Sources/DeckKit/DeckHost.swift +0 -7
  176. package/swift/Sources/DeckKit/DeckManifest.swift +0 -145
  177. package/swift/Sources/DeckKit/DeckRuntimeSnapshot.swift +0 -533
  178. package/swift/Sources/DeckKit/DeckTrackpad.swift +0 -63
  179. package/swift/Sources/DeckKit/DeckValue.swift +0 -93
  180. package/swift/Sources/DeckKit/DeckVoiceError.swift +0 -88
  181. package/swift/Tests/DeckKitTests/DeckKitTests.swift +0 -286
@@ -1,847 +0,0 @@
1
- import SwiftUI
2
-
3
- enum MainViewLayout {
4
- case popover
5
- case embedded
6
- }
7
-
8
- struct MainView: View {
9
- @ObservedObject var scanner: ProjectScanner
10
- var layout: MainViewLayout = .popover
11
- @StateObject private var prefs = Preferences.shared
12
- @StateObject private var permChecker = PermissionChecker.shared
13
- @ObservedObject private var workspace = WorkspaceManager.shared
14
- @StateObject private var inventory = InventoryManager.shared
15
- @State private var searchText = ""
16
- @State private var hasCheckedSetup = false
17
- @State private var tmuxBannerDismissed = false
18
- @ObservedObject private var tmuxModel = TmuxModel.shared
19
- @State private var orphanSectionCollapsed = true
20
- private let embeddedProjectColumns = Array(
21
- repeating: GridItem(.flexible(minimum: 0, maximum: .infinity), spacing: 10, alignment: .top),
22
- count: 3
23
- )
24
- private let embeddedProjectCardHeight: CGFloat = 94
25
- private let embeddedProjectGridSpacing: CGFloat = 10
26
- private var filtered: [Project] {
27
- if searchText.isEmpty { return scanner.projects }
28
- return scanner.projects.filter {
29
- $0.name.localizedCaseInsensitiveContains(searchText)
30
- }
31
- }
32
-
33
- private var filteredOrphans: [TmuxSession] {
34
- if searchText.isEmpty { return inventory.orphans }
35
- return inventory.orphans.filter {
36
- $0.name.localizedCaseInsensitiveContains(searchText)
37
- }
38
- }
39
-
40
- private var needsSetup: Bool { prefs.scanRoot.isEmpty }
41
- private var runningCount: Int { scanner.projects.filter(\.isRunning).count }
42
- private var hasVisibleGroups: Bool {
43
- guard let groups = workspace.config?.groups else { return false }
44
- return !groups.isEmpty && searchText.isEmpty
45
- }
46
- private var embeddedProjectGridHeight: CGFloat {
47
- guard !filtered.isEmpty else { return 0 }
48
- let rowCount = min(Int(ceil(Double(filtered.count) / 3.0)), 3)
49
- return CGFloat(rowCount) * embeddedProjectCardHeight
50
- + CGFloat(max(0, rowCount - 1)) * embeddedProjectGridSpacing
51
- }
52
-
53
- var body: some View {
54
- VStack(spacing: 0) {
55
- mainContent
56
- }
57
- .frame(
58
- minWidth: layout == .popover ? 380 : 0,
59
- idealWidth: layout == .popover ? 380 : nil,
60
- maxWidth: .infinity,
61
- maxHeight: .infinity,
62
- alignment: .top
63
- )
64
- .background(PanelBackground())
65
- .preferredColorScheme(.dark)
66
- .onAppear {
67
- if needsSetup && !hasCheckedSetup {
68
- hasCheckedSetup = true
69
- SettingsWindowController.shared.show()
70
- }
71
- runRefresh()
72
- }
73
- .onReceive(NotificationCenter.default.publisher(for: .latticesPopoverWillShow)) { _ in
74
- guard layout == .popover else { return }
75
- runRefresh()
76
- }
77
- }
78
-
79
- private func runRefresh() {
80
- let tTotal = DiagnosticLog.shared.startTimed("MainView.refresh (total)")
81
- scanner.updateRoot(prefs.scanRoot)
82
-
83
- let tScan = DiagnosticLog.shared.startTimed("ProjectScanner.scan")
84
- scanner.scan()
85
- DiagnosticLog.shared.finish(tScan)
86
-
87
- let tInv = DiagnosticLog.shared.startTimed("InventoryManager.refresh")
88
- inventory.refresh()
89
- DiagnosticLog.shared.finish(tInv)
90
-
91
- let tPerm = DiagnosticLog.shared.startTimed("PermissionChecker.check")
92
- permChecker.check()
93
- DiagnosticLog.shared.finish(tPerm)
94
-
95
- DiagnosticLog.shared.finish(tTotal)
96
- }
97
-
98
- private var visiblyMissingCapabilities: [Capability] {
99
- Capability.allCases.filter { !$0.isGranted && !prefs.isCapabilityDismissed($0.rawValue) }
100
- }
101
-
102
- private var mainContent: some View {
103
- VStack(spacing: 0) {
104
- if layout == .popover {
105
- HStack {
106
- Text("Lattices")
107
- .font(Typo.mono(14))
108
- .foregroundColor(Palette.text)
109
- buildChannelBadge
110
-
111
- Spacer()
112
-
113
- headerButton(icon: "house") {
114
- MenuBarController.shared.dismissPopover()
115
- ScreenMapWindowController.shared.showPage(.home)
116
- }
117
- headerButton(icon: "rectangle.3.group") {
118
- MenuBarController.shared.dismissPopover()
119
- ScreenMapWindowController.shared.showPage(.screenMap)
120
- }
121
- headerButton(icon: "magnifyingglass") {
122
- MenuBarController.shared.dismissPopover()
123
- ScreenMapWindowController.shared.showPage(.desktopInventory)
124
- }
125
- headerButton(icon: "command") {
126
- MenuBarController.shared.dismissPopover()
127
- CommandPaletteWindow.shared.toggle()
128
- }
129
- headerButton(icon: "arrow.clockwise") { scanner.scan(); inventory.refresh() }
130
- }
131
- .padding(.horizontal, 18)
132
- .padding(.top, 18)
133
- .padding(.bottom, 12)
134
- } else {
135
- // Search
136
- HStack(spacing: 8) {
137
- Image(systemName: "magnifyingglass")
138
- .foregroundColor(Palette.textMuted)
139
- .font(.system(size: 11))
140
- TextField("Search projects...", text: $searchText)
141
- .textFieldStyle(.plain)
142
- .font(Typo.body(13))
143
- .foregroundColor(Palette.text)
144
- if !searchText.isEmpty {
145
- Button { searchText = "" } label: {
146
- Image(systemName: "xmark.circle.fill")
147
- .foregroundColor(Palette.textMuted)
148
- .font(.system(size: 11))
149
- }
150
- .buttonStyle(.plain)
151
- }
152
- }
153
- .padding(.horizontal, 12)
154
- .padding(.vertical, 8)
155
- .background(
156
- RoundedRectangle(cornerRadius: 4)
157
- .fill(Palette.surface)
158
- )
159
- .padding(.horizontal, 14)
160
- .padding(.bottom, 10)
161
- }
162
-
163
- // Permission banner — only when something is missing AND not snoozed
164
- if !visiblyMissingCapabilities.isEmpty {
165
- permissionBanner
166
- }
167
-
168
- // tmux not-found banner
169
- if !tmuxModel.isAvailable && !tmuxBannerDismissed {
170
- tmuxBanner
171
- }
172
-
173
- if layout == .popover {
174
- Rectangle()
175
- .fill(Palette.border)
176
- .frame(height: 0.5)
177
-
178
- actionsSection
179
-
180
- Rectangle()
181
- .fill(Palette.border)
182
- .frame(height: 0.5)
183
-
184
- bottomBar
185
- } else {
186
- Rectangle()
187
- .fill(Palette.border)
188
- .frame(height: 0.5)
189
-
190
- if filtered.isEmpty && !hasVisibleGroups {
191
- Spacer()
192
- emptyState
193
- Spacer()
194
- } else {
195
- embeddedProjectsSection
196
- }
197
-
198
- Rectangle()
199
- .fill(Palette.border)
200
- .frame(height: 0.5)
201
-
202
- // Actions footer
203
- actionsSection
204
-
205
- Rectangle()
206
- .fill(Palette.border)
207
- .frame(height: 0.5)
208
-
209
- // Bottom bar
210
- bottomBar
211
- }
212
- }
213
- }
214
-
215
- private var embeddedProjectsSection: some View {
216
- VStack(spacing: 0) {
217
- if hasVisibleGroups, let groups = workspace.config?.groups {
218
- LazyVStack(spacing: 4) {
219
- ForEach(groups) { group in
220
- TabGroupRow(group: group, workspace: workspace)
221
- }
222
- }
223
- .padding(.horizontal, 10)
224
- .padding(.top, 8)
225
- .padding(.bottom, filtered.isEmpty ? 8 : 6)
226
- }
227
-
228
- if !filtered.isEmpty {
229
- VStack(spacing: 0) {
230
- HStack(spacing: 8) {
231
- Text(searchText.isEmpty ? "Projects" : "Matches")
232
- .font(Typo.monoBold(10))
233
- .foregroundColor(Palette.textMuted)
234
-
235
- if searchText.isEmpty {
236
- Text("\(runningCount) live")
237
- .font(Typo.mono(9))
238
- .foregroundColor(Palette.running.opacity(0.8))
239
- }
240
-
241
- Spacer()
242
-
243
- Text("\(filtered.count)")
244
- .font(Typo.mono(9))
245
- .foregroundColor(Palette.textMuted)
246
- }
247
- .padding(.horizontal, 14)
248
- .padding(.top, 10)
249
- .padding(.bottom, 8)
250
-
251
- ScrollView(.vertical, showsIndicators: filtered.count > 9) {
252
- LazyVGrid(columns: embeddedProjectColumns, spacing: embeddedProjectGridSpacing) {
253
- ForEach(filtered) { project in
254
- HomeProjectCard(
255
- project: project,
256
- onLaunch: { launchProject(project) },
257
- onDetach: { detachProject(project) },
258
- onKill: { killProject(project) },
259
- onSync: { syncProject(project) },
260
- onRestart: { paneName in restartProject(project, paneName: paneName) }
261
- )
262
- .frame(height: embeddedProjectCardHeight)
263
- }
264
- }
265
- .padding(.horizontal, 14)
266
- .padding(.bottom, 10)
267
- }
268
- .frame(maxHeight: embeddedProjectGridHeight)
269
- }
270
- }
271
-
272
- if !filteredOrphans.isEmpty {
273
- orphanSection
274
- .padding(.horizontal, 10)
275
- .padding(.bottom, 8)
276
- }
277
- }
278
- }
279
-
280
- // MARK: - Orphan section
281
-
282
- private var orphanSection: some View {
283
- VStack(spacing: 4) {
284
- Rectangle()
285
- .fill(Palette.border)
286
- .frame(height: 0.5)
287
- .padding(.vertical, 4)
288
-
289
- // Section header
290
- Button {
291
- withAnimation(.easeOut(duration: 0.15)) { orphanSectionCollapsed.toggle() }
292
- } label: {
293
- HStack(spacing: 6) {
294
- Image(systemName: orphanSectionCollapsed ? "chevron.right" : "chevron.down")
295
- .font(.system(size: 9, weight: .semibold))
296
- .foregroundColor(Palette.textMuted)
297
-
298
- Text("Unmanaged Sessions")
299
- .font(Typo.caption(10))
300
- .foregroundColor(Palette.textMuted)
301
-
302
- Text("\(filteredOrphans.count)")
303
- .font(Typo.mono(9))
304
- .foregroundColor(Palette.detach)
305
- .padding(.horizontal, 5)
306
- .padding(.vertical, 1)
307
- .background(
308
- RoundedRectangle(cornerRadius: 3)
309
- .fill(Palette.detach.opacity(0.12))
310
- )
311
-
312
- Spacer()
313
- }
314
- }
315
- .buttonStyle(.plain)
316
- .padding(.horizontal, 4)
317
-
318
- if !orphanSectionCollapsed {
319
- ForEach(filteredOrphans) { session in
320
- OrphanRow(
321
- session: session,
322
- onAttach: {
323
- let terminal = Preferences.shared.terminal
324
- terminal.focusOrAttach(session: session.name)
325
- },
326
- onKill: {
327
- SessionManager.killByName(session.name)
328
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
329
- inventory.refresh()
330
- }
331
- }
332
- )
333
- }
334
- }
335
- }
336
- }
337
-
338
- // MARK: - Actions footer
339
-
340
- private var actionsSection: some View {
341
- VStack(spacing: 0) {
342
- HStack {
343
- Text("Quick Actions")
344
- .font(Typo.monoBold(10))
345
- .foregroundColor(Palette.textMuted)
346
-
347
- Spacer()
348
-
349
- Button("Help & shortcuts") {
350
- SettingsWindowController.shared.show()
351
- }
352
- .buttonStyle(.plain)
353
- .font(Typo.mono(9))
354
- .foregroundColor(Palette.textMuted)
355
- }
356
- .padding(.horizontal, 14)
357
- .padding(.top, 10)
358
- .padding(.bottom, 6)
359
-
360
- ActionRow(
361
- label: "Home",
362
- detail: "Workspace overview and project launcher",
363
- hotkeyTokens: hotkeyTokens(.unifiedWindow),
364
- icon: "house",
365
- accentColor: Palette.text
366
- ) {
367
- ScreenMapWindowController.shared.showPage(.home)
368
- }
369
- ActionRow(
370
- label: "Layout",
371
- detail: "Arrange windows and layers",
372
- hotkeyTokens: [],
373
- icon: "rectangle.3.group",
374
- accentColor: Palette.running
375
- ) {
376
- ScreenMapWindowController.shared.showPage(.screenMap)
377
- }
378
- ActionRow(
379
- label: "Search",
380
- detail: "Windows, projects, sessions, processes, and OCR",
381
- hotkeyTokens: hotkeyTokens(.omniSearch),
382
- icon: "magnifyingglass",
383
- accentColor: AudioLayer.shared.isListening ? Palette.running : Palette.textDim
384
- ) {
385
- ScreenMapWindowController.shared.showPage(.desktopInventory)
386
- }
387
- ActionRow(
388
- label: "Command Palette",
389
- detail: "Launch, attach, and control projects",
390
- hotkeyTokens: hotkeyTokens(.palette),
391
- icon: "command",
392
- accentColor: Palette.running
393
- ) {
394
- CommandPaletteWindow.shared.toggle()
395
- }
396
- }
397
- .padding(.vertical, 4)
398
- .background(Palette.surface.opacity(0.4))
399
- }
400
-
401
- private var bottomBar: some View {
402
- HStack(spacing: 16) {
403
- bottomBarButton(icon: "gearshape", label: "Settings") {
404
- SettingsWindowController.shared.show()
405
- }
406
-
407
- if !permChecker.allGranted {
408
- Button {
409
- PermissionsAssistantWindowController.shared.show()
410
- } label: {
411
- HStack(spacing: 4) {
412
- Circle()
413
- .fill(Palette.detach)
414
- .frame(width: 5, height: 5)
415
- Text("Permissions")
416
- .font(Typo.mono(9))
417
- .foregroundColor(Palette.textMuted)
418
- }
419
- }
420
- .buttonStyle(.plain)
421
- }
422
-
423
- Spacer()
424
-
425
- bottomBarButton(icon: "power", label: "Quit", color: Palette.kill) {
426
- NSApp.terminate(nil)
427
- }
428
- }
429
- .padding(.horizontal, 16)
430
- .padding(.vertical, 8)
431
- .background(Palette.bg)
432
- }
433
-
434
- private func bottomBarButton(icon: String, label: String, color: Color = Palette.textMuted, action: @escaping () -> Void) -> some View {
435
- Button(action: action) {
436
- HStack(spacing: 4) {
437
- Image(systemName: icon)
438
- .font(.system(size: 10, weight: .medium))
439
- Text(label)
440
- .font(Typo.mono(9))
441
- }
442
- .foregroundColor(color)
443
- }
444
- .buttonStyle(.plain)
445
- }
446
-
447
- private func hotkeyTokens(_ action: HotkeyAction) -> [String] {
448
- guard let binding = HotkeyStore.shared.bindings[action],
449
- let key = binding.displayParts.last else { return [] }
450
-
451
- let modifiers = Set(binding.displayParts.dropLast())
452
- if modifiers == Set(["Ctrl", "Option", "Shift", "Cmd"]) {
453
- return ["Hyper", shortenHotkeyToken(key)]
454
- }
455
-
456
- return binding.displayParts.map(shortenHotkeyToken)
457
- }
458
-
459
- private func shortenHotkeyToken(_ token: String) -> String {
460
- switch token {
461
- case "Cmd": return "⌘"
462
- case "Shift": return "⇧"
463
- case "Option": return "⌥"
464
- case "Ctrl": return "⌃"
465
- case "Return": return "↩"
466
- case "Escape": return "Esc"
467
- case "Space": return "Space"
468
- default: return token
469
- }
470
- }
471
-
472
- // MARK: - Empty state
473
-
474
- private var emptyState: some View {
475
- VStack(spacing: 14) {
476
- Image(systemName: "terminal")
477
- .font(.system(size: 28, weight: .light))
478
- .foregroundColor(Palette.textMuted)
479
-
480
- Text("No projects yet")
481
- .font(Typo.heading(14))
482
- .foregroundColor(Palette.textDim)
483
-
484
- Text("Choose a repo and we’ll hand off to the CLI\nin your terminal.")
485
- .font(Typo.mono(11))
486
- .foregroundColor(Palette.textMuted)
487
- .multilineTextAlignment(.center)
488
- .lineSpacing(3)
489
-
490
- HStack(spacing: 10) {
491
- Button(action: CliActionLauncher.initializeProjectInTerminal) {
492
- Text("Initialize Project")
493
- .angularButton(Palette.running)
494
- }
495
- .buttonStyle(.plain)
496
-
497
- Button(action: CliActionLauncher.launchProjectInTerminal) {
498
- Text("Launch Project")
499
- .angularButton(.white, filled: false)
500
- }
501
- .buttonStyle(.plain)
502
- }
503
-
504
- Text("Initialize runs lattices init && lattices start in the folder you choose.")
505
- .font(Typo.mono(9))
506
- .foregroundColor(Palette.textMuted)
507
- .multilineTextAlignment(.center)
508
- .lineSpacing(2)
509
- }
510
- .padding(.horizontal, 20)
511
- }
512
-
513
- // MARK: - Permission banner
514
-
515
- private var permissionBanner: some View {
516
- let missing = visiblyMissingCapabilities
517
-
518
- return VStack(alignment: .leading, spacing: 8) {
519
- HStack {
520
- Image(systemName: "sparkles")
521
- .font(.system(size: 10))
522
- .foregroundColor(Palette.detach)
523
- Text("OPTIONAL CAPABILITIES")
524
- .font(Typo.monoBold(10))
525
- .foregroundColor(Palette.detach)
526
- Spacer()
527
- Button {
528
- for cap in missing { prefs.dismissCapability(cap.rawValue) }
529
- } label: {
530
- Text("Maybe later")
531
- .font(Typo.mono(9))
532
- .foregroundColor(Palette.textMuted)
533
- }
534
- .buttonStyle(.plain)
535
- }
536
-
537
- Text(bannerSummary(missing))
538
- .font(Typo.mono(10))
539
- .foregroundColor(Palette.text)
540
- .fixedSize(horizontal: false, vertical: true)
541
-
542
- HStack(spacing: 8) {
543
- ForEach(missing) { cap in
544
- capabilityChip(cap)
545
- }
546
- Spacer(minLength: 0)
547
- }
548
-
549
- Button {
550
- PermissionsAssistantWindowController.shared.show(focus: missing.first)
551
- } label: {
552
- HStack(spacing: 6) {
553
- Image(systemName: "slider.horizontal.3")
554
- .font(.system(size: 10, weight: .semibold))
555
- Text("Open Permissions Assistant")
556
- .font(Typo.monoBold(10))
557
- }
558
- .foregroundColor(.white)
559
- .padding(.horizontal, 10)
560
- .padding(.vertical, 6)
561
- .background(
562
- RoundedRectangle(cornerRadius: 4)
563
- .fill(Palette.detach.opacity(0.18))
564
- .overlay(
565
- RoundedRectangle(cornerRadius: 4)
566
- .strokeBorder(Palette.detach.opacity(0.45), lineWidth: 0.5)
567
- )
568
- )
569
- }
570
- .buttonStyle(.plain)
571
- }
572
- .padding(12)
573
- .background(
574
- RoundedRectangle(cornerRadius: 5)
575
- .fill(Palette.detach.opacity(0.08))
576
- .overlay(
577
- RoundedRectangle(cornerRadius: 5)
578
- .strokeBorder(Palette.detach.opacity(0.20), lineWidth: 0.5)
579
- )
580
- )
581
- .padding(.horizontal, 14)
582
- .padding(.bottom, 10)
583
- }
584
-
585
- private func bannerSummary(_ missing: [Capability]) -> String {
586
- switch missing.count {
587
- case 0: return ""
588
- case 1: return "\(missing[0].title) is off. Lattices works without it."
589
- default: return "\(missing.count) capabilities are off. Turn on whichever you want; the rest of the app works without them."
590
- }
591
- }
592
-
593
- private func capabilityChip(_ cap: Capability) -> some View {
594
- Button {
595
- PermissionsAssistantWindowController.shared.show(focus: cap)
596
- } label: {
597
- HStack(spacing: 5) {
598
- Image(systemName: cap.iconName)
599
- .font(.system(size: 9, weight: .semibold))
600
- Text(cap.title)
601
- .font(Typo.mono(9))
602
- }
603
- .foregroundColor(Palette.textDim)
604
- .padding(.horizontal, 7)
605
- .padding(.vertical, 3)
606
- .background(
607
- Capsule()
608
- .fill(Palette.surface)
609
- .overlay(
610
- Capsule().strokeBorder(Palette.border, lineWidth: 0.5)
611
- )
612
- )
613
- }
614
- .buttonStyle(.plain)
615
- }
616
-
617
- private var buildChannelBadge: some View {
618
- let tint = LatticesRuntime.isDevBuild ? Palette.detach : Palette.running
619
-
620
- return Text(LatticesRuntime.buildChannelLabel)
621
- .font(Typo.monoBold(9))
622
- .foregroundColor(tint)
623
- .padding(.horizontal, 6)
624
- .padding(.vertical, 3)
625
- .background(
626
- Capsule()
627
- .fill(tint.opacity(0.12))
628
- .overlay(
629
- Capsule()
630
- .strokeBorder(tint.opacity(0.28), lineWidth: 0.5)
631
- )
632
- )
633
- }
634
-
635
- // MARK: - tmux banner
636
-
637
- private var tmuxBanner: some View {
638
- VStack(alignment: .leading, spacing: 6) {
639
- HStack {
640
- Image(systemName: "terminal")
641
- .font(.system(size: 10))
642
- .foregroundColor(Palette.detach)
643
- Text("TMUX NOT FOUND")
644
- .font(Typo.monoBold(10))
645
- .foregroundColor(Palette.detach)
646
- Spacer()
647
- Button { tmuxBannerDismissed = true } label: {
648
- Image(systemName: "xmark")
649
- .font(.system(size: 8, weight: .bold))
650
- .foregroundColor(Palette.textMuted)
651
- }
652
- .buttonStyle(.plain)
653
- }
654
-
655
- Text("Session management requires tmux. Install it with Homebrew:")
656
- .font(Typo.mono(10))
657
- .foregroundColor(Palette.text)
658
-
659
- Button {
660
- let task = Process()
661
- task.executableURL = URL(fileURLWithPath: "/bin/zsh")
662
- task.arguments = ["-lc", "brew install tmux"]
663
- task.standardOutput = FileHandle.nullDevice
664
- task.standardError = FileHandle.nullDevice
665
- try? task.run()
666
- } label: {
667
- HStack(spacing: 6) {
668
- Image(systemName: "arrow.down.circle")
669
- .font(.system(size: 10))
670
- Text("brew install tmux")
671
- .font(Typo.monoBold(10))
672
- }
673
- .padding(.vertical, 4)
674
- .padding(.horizontal, 8)
675
- .background(
676
- RoundedRectangle(cornerRadius: 4)
677
- .fill(Palette.detach.opacity(0.06))
678
- )
679
- }
680
- .buttonStyle(.plain)
681
-
682
- Text("Window tiling, search, and OCR work without tmux.")
683
- .font(Typo.mono(9))
684
- .foregroundColor(Palette.textMuted)
685
- }
686
- .padding(12)
687
- .background(
688
- RoundedRectangle(cornerRadius: 5)
689
- .fill(Palette.detach.opacity(0.08))
690
- .overlay(
691
- RoundedRectangle(cornerRadius: 5)
692
- .strokeBorder(Palette.detach.opacity(0.20), lineWidth: 0.5)
693
- )
694
- )
695
- .padding(.horizontal, 14)
696
- .padding(.bottom, 10)
697
- }
698
-
699
- // MARK: - Helpers
700
-
701
- private func headerButton(icon: String, action: @escaping () -> Void) -> some View {
702
- Button(action: action) {
703
- Image(systemName: icon)
704
- .font(.system(size: 12, weight: .medium))
705
- .foregroundColor(Palette.textDim)
706
- .frame(width: 28, height: 28)
707
- }
708
- .buttonStyle(.plain)
709
- }
710
-
711
- private func launchProject(_ project: Project) {
712
- SessionManager.launch(project: project)
713
- }
714
-
715
- private func detachProject(_ project: Project) {
716
- SessionManager.detach(project: project)
717
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
718
- scanner.refreshStatus()
719
- }
720
- }
721
-
722
- private func killProject(_ project: Project) {
723
- SessionManager.kill(project: project)
724
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
725
- scanner.refreshStatus()
726
- }
727
- }
728
-
729
- private func syncProject(_ project: Project) {
730
- SessionManager.sync(project: project)
731
- DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
732
- scanner.refreshStatus()
733
- }
734
- }
735
-
736
- private func restartProject(_ project: Project, paneName: String?) {
737
- SessionManager.restart(project: project, paneName: paneName)
738
- }
739
- }
740
-
741
- private struct HomeProjectCard: View {
742
- let project: Project
743
- let onLaunch: () -> Void
744
- let onDetach: () -> Void
745
- let onKill: () -> Void
746
- let onSync: () -> Void
747
- let onRestart: (String?) -> Void
748
-
749
- @State private var isHovered = false
750
-
751
- private var summaryText: String {
752
- if !project.paneSummary.isEmpty { return project.paneSummary }
753
- if let cmd = project.devCommand, !cmd.isEmpty { return cmd }
754
- return project.hasConfig
755
- ? "\(project.paneCount) pane\(project.paneCount == 1 ? "" : "s")"
756
- : "No config"
757
- }
758
-
759
- var body: some View {
760
- VStack(alignment: .leading, spacing: 10) {
761
- HStack(alignment: .top, spacing: 8) {
762
- Circle()
763
- .fill(project.isRunning ? Palette.running : Palette.borderLit)
764
- .frame(width: 7, height: 7)
765
- .padding(.top, 4)
766
-
767
- VStack(alignment: .leading, spacing: 4) {
768
- Text(project.name)
769
- .font(Typo.heading(13))
770
- .foregroundColor(Palette.text)
771
- .lineLimit(1)
772
-
773
- Text(summaryText)
774
- .font(Typo.mono(10))
775
- .foregroundColor(Palette.textMuted)
776
- .lineLimit(2)
777
- .fixedSize(horizontal: false, vertical: true)
778
- }
779
-
780
- Spacer(minLength: 0)
781
-
782
- if project.isRunning {
783
- Text("LIVE")
784
- .font(Typo.monoBold(8))
785
- .foregroundColor(Palette.running)
786
- .padding(.horizontal, 6)
787
- .padding(.vertical, 3)
788
- .background(
789
- Capsule()
790
- .fill(Palette.running.opacity(0.10))
791
- )
792
- }
793
- }
794
-
795
- Spacer(minLength: 0)
796
-
797
- HStack(spacing: 6) {
798
- if project.isRunning {
799
- Button(action: onDetach) {
800
- Image(systemName: "rectangle.portrait.and.arrow.right")
801
- .font(.system(size: 10, weight: .medium))
802
- .angularButton(Palette.detach, filled: false)
803
- }
804
- .buttonStyle(.plain)
805
- }
806
-
807
- Spacer(minLength: 0)
808
-
809
- Button(action: onLaunch) {
810
- Text(project.isRunning ? "Attach" : "Launch")
811
- .angularButton(project.isRunning ? Palette.running : Palette.launch)
812
- }
813
- .buttonStyle(.plain)
814
- }
815
- }
816
- .padding(12)
817
- .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
818
- .glassCard(hovered: isHovered)
819
- .contentShape(Rectangle())
820
- .onHover { isHovered = $0 }
821
- .contextMenu {
822
- if project.isRunning {
823
- Button("Attach") { onLaunch() }
824
- Button("Detach") { onDetach() }
825
- Button {
826
- WindowTiler.navigateToWindow(
827
- session: project.sessionName,
828
- terminal: Preferences.shared.terminal
829
- )
830
- } label: {
831
- Label("Go to Window", systemImage: "macwindow")
832
- }
833
- Divider()
834
- Button("Sync Session") { onSync() }
835
- Menu("Restart Pane") {
836
- ForEach(project.paneNames, id: \.self) { name in
837
- Button(name) { onRestart(name) }
838
- }
839
- }
840
- Divider()
841
- Button("Kill Session") { onKill() }
842
- } else {
843
- Button("Launch") { onLaunch() }
844
- }
845
- }
846
- }
847
- }