@arach/lattices 0.1.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 (64) hide show
  1. package/README.md +157 -0
  2. package/app/Lattices.app/Contents/Info.plist +24 -0
  3. package/app/Package.swift +13 -0
  4. package/app/Sources/App.swift +49 -0
  5. package/app/Sources/AppDelegate.swift +104 -0
  6. package/app/Sources/AppShellView.swift +62 -0
  7. package/app/Sources/AppTypeClassifier.swift +70 -0
  8. package/app/Sources/AppWindowShell.swift +63 -0
  9. package/app/Sources/CheatSheetHUD.swift +331 -0
  10. package/app/Sources/CommandModeState.swift +1341 -0
  11. package/app/Sources/CommandModeView.swift +1380 -0
  12. package/app/Sources/CommandModeWindow.swift +192 -0
  13. package/app/Sources/CommandPaletteView.swift +307 -0
  14. package/app/Sources/CommandPaletteWindow.swift +134 -0
  15. package/app/Sources/DaemonProtocol.swift +101 -0
  16. package/app/Sources/DaemonServer.swift +406 -0
  17. package/app/Sources/DesktopModel.swift +121 -0
  18. package/app/Sources/DesktopModelTypes.swift +71 -0
  19. package/app/Sources/DiagnosticLog.swift +253 -0
  20. package/app/Sources/EventBus.swift +29 -0
  21. package/app/Sources/HotkeyManager.swift +249 -0
  22. package/app/Sources/HotkeyStore.swift +330 -0
  23. package/app/Sources/InventoryManager.swift +35 -0
  24. package/app/Sources/InventoryPath.swift +43 -0
  25. package/app/Sources/KeyRecorderView.swift +210 -0
  26. package/app/Sources/LatticesApi.swift +915 -0
  27. package/app/Sources/MainView.swift +507 -0
  28. package/app/Sources/MainWindow.swift +70 -0
  29. package/app/Sources/OrphanRow.swift +129 -0
  30. package/app/Sources/PaletteCommand.swift +409 -0
  31. package/app/Sources/PermissionChecker.swift +115 -0
  32. package/app/Sources/Preferences.swift +48 -0
  33. package/app/Sources/ProcessModel.swift +199 -0
  34. package/app/Sources/ProcessQuery.swift +151 -0
  35. package/app/Sources/Project.swift +28 -0
  36. package/app/Sources/ProjectRow.swift +368 -0
  37. package/app/Sources/ProjectScanner.swift +121 -0
  38. package/app/Sources/ScreenMapState.swift +2397 -0
  39. package/app/Sources/ScreenMapView.swift +2817 -0
  40. package/app/Sources/ScreenMapWindowController.swift +89 -0
  41. package/app/Sources/SessionManager.swift +72 -0
  42. package/app/Sources/SettingsView.swift +641 -0
  43. package/app/Sources/SettingsWindow.swift +20 -0
  44. package/app/Sources/TabGroupRow.swift +178 -0
  45. package/app/Sources/Terminal.swift +259 -0
  46. package/app/Sources/TerminalQuery.swift +156 -0
  47. package/app/Sources/TerminalSynthesizer.swift +200 -0
  48. package/app/Sources/Theme.swift +124 -0
  49. package/app/Sources/TilePickerView.swift +209 -0
  50. package/app/Sources/TmuxModel.swift +53 -0
  51. package/app/Sources/TmuxQuery.swift +81 -0
  52. package/app/Sources/WindowTiler.swift +1752 -0
  53. package/app/Sources/WorkspaceManager.swift +434 -0
  54. package/bin/daemon-client.js +187 -0
  55. package/bin/lattices-app.js +205 -0
  56. package/bin/lattices.js +1295 -0
  57. package/docs/api.md +707 -0
  58. package/docs/app.md +250 -0
  59. package/docs/concepts.md +225 -0
  60. package/docs/config.md +234 -0
  61. package/docs/layers.md +317 -0
  62. package/docs/overview.md +74 -0
  63. package/docs/quickstart.md +82 -0
  64. package/package.json +38 -0
@@ -0,0 +1,331 @@
1
+ import AppKit
2
+ import SwiftUI
3
+
4
+ // MARK: - CheatSheetHUD (singleton window controller)
5
+
6
+ final class CheatSheetHUD {
7
+ static let shared = CheatSheetHUD()
8
+
9
+ private var panel: NSPanel?
10
+ private var localMonitor: Any?
11
+ private var globalMonitor: Any?
12
+
13
+ var isVisible: Bool { panel?.isVisible ?? false }
14
+
15
+ func toggle() {
16
+ if isVisible {
17
+ dismiss()
18
+ } else {
19
+ show()
20
+ }
21
+ }
22
+
23
+ func show() {
24
+ guard panel == nil else { return }
25
+
26
+ let view = CheatSheetView()
27
+ .preferredColorScheme(.dark)
28
+
29
+ let hosting = NSHostingView(rootView: view)
30
+
31
+ let p = NSPanel(
32
+ contentRect: NSRect(x: 0, y: 0, width: 520, height: 420),
33
+ styleMask: [.borderless, .nonactivatingPanel],
34
+ backing: .buffered,
35
+ defer: false
36
+ )
37
+ p.isOpaque = false
38
+ p.backgroundColor = .clear
39
+ p.level = .floating
40
+ p.hasShadow = true
41
+ p.hidesOnDeactivate = false
42
+ p.isReleasedWhenClosed = false
43
+ p.isMovableByWindowBackground = false
44
+ p.contentView = hosting
45
+
46
+ // Center on the screen containing the mouse cursor
47
+ let mouseLocation = NSEvent.mouseLocation
48
+ let screen = NSScreen.screens.first(where: { $0.frame.contains(mouseLocation) }) ?? NSScreen.main ?? NSScreen.screens.first!
49
+ let screenFrame = screen.visibleFrame
50
+ let x = screenFrame.midX - 260
51
+ let y = screenFrame.midY - 210
52
+ p.setFrameOrigin(NSPoint(x: x, y: y))
53
+
54
+ p.alphaValue = 0
55
+ p.orderFrontRegardless()
56
+
57
+ NSAnimationContext.runAnimationGroup { ctx in
58
+ ctx.duration = 0.15
59
+ p.animator().alphaValue = 1.0
60
+ }
61
+
62
+ self.panel = p
63
+ installMonitors()
64
+ }
65
+
66
+ func dismiss() {
67
+ guard let p = panel else { return }
68
+ removeMonitors()
69
+
70
+ NSAnimationContext.runAnimationGroup({ ctx in
71
+ ctx.duration = 0.2
72
+ p.animator().alphaValue = 0
73
+ }) { [weak self] in
74
+ p.orderOut(nil)
75
+ self?.panel = nil
76
+ }
77
+ }
78
+
79
+ // MARK: - Event monitors
80
+
81
+ private func installMonitors() {
82
+ // Escape key dismisses (global — panel is non-activating so keys go to frontmost app)
83
+ localMonitor = NSEvent.addGlobalMonitorForEvents(matching: .keyDown) { [weak self] event in
84
+ if event.keyCode == 53 { // Escape
85
+ self?.dismiss()
86
+ }
87
+ }
88
+
89
+ // Click outside dismisses
90
+ globalMonitor = NSEvent.addGlobalMonitorForEvents(matching: [.leftMouseDown, .rightMouseDown]) { [weak self] _ in
91
+ self?.dismiss()
92
+ }
93
+ }
94
+
95
+ private func removeMonitors() {
96
+ if let m = localMonitor { NSEvent.removeMonitor(m); localMonitor = nil }
97
+ if let m = globalMonitor { NSEvent.removeMonitor(m); globalMonitor = nil }
98
+ }
99
+
100
+ }
101
+
102
+ // MARK: - CheatSheetView
103
+
104
+ struct CheatSheetView: View {
105
+ @ObservedObject private var hotkeyStore = HotkeyStore.shared
106
+
107
+ var body: some View {
108
+ VStack(spacing: 0) {
109
+ // Title
110
+ HStack {
111
+ Text("KEYBOARD SHORTCUTS")
112
+ .font(Typo.pixel(14))
113
+ .foregroundColor(Palette.textDim)
114
+ .tracking(1)
115
+ Spacer()
116
+ }
117
+ .padding(.horizontal, 20)
118
+ .padding(.top, 16)
119
+ .padding(.bottom, 10)
120
+
121
+ Rectangle().fill(Palette.border).frame(height: 0.5)
122
+
123
+ // Two-column body
124
+ HStack(alignment: .top, spacing: 20) {
125
+ // Left column: Tiling
126
+ tilingColumn
127
+
128
+ Rectangle().fill(Palette.border).frame(width: 0.5)
129
+
130
+ // Right column: App + tmux
131
+ VStack(alignment: .leading, spacing: 16) {
132
+ appColumn
133
+ Rectangle().fill(Palette.border).frame(height: 0.5)
134
+ tmuxColumn
135
+ }
136
+ .frame(maxWidth: .infinity, alignment: .leading)
137
+ }
138
+ .padding(.horizontal, 20)
139
+ .padding(.vertical, 14)
140
+
141
+ Spacer(minLength: 0)
142
+
143
+ Rectangle().fill(Palette.border).frame(height: 0.5)
144
+
145
+ // Footer
146
+ HStack {
147
+ Spacer()
148
+ Text("Press ESC to dismiss")
149
+ .font(Typo.caption(10))
150
+ .foregroundColor(Palette.textMuted)
151
+ Spacer()
152
+ }
153
+ .padding(.vertical, 8)
154
+ }
155
+ .frame(width: 520, height: 420)
156
+ .background(
157
+ RoundedRectangle(cornerRadius: 12)
158
+ .fill(Palette.bg)
159
+ .overlay(
160
+ RoundedRectangle(cornerRadius: 12)
161
+ .strokeBorder(Palette.borderLit, lineWidth: 0.5)
162
+ )
163
+ )
164
+ .clipShape(RoundedRectangle(cornerRadius: 12))
165
+ }
166
+
167
+ // MARK: - Tiling Column
168
+
169
+ private var tilingColumn: some View {
170
+ VStack(alignment: .leading, spacing: 10) {
171
+ columnHeader("Tiling")
172
+
173
+ // 3x3 grid
174
+ VStack(spacing: 2) {
175
+ HStack(spacing: 2) {
176
+ tileCell(action: .tileTopLeft, label: "TL")
177
+ tileCell(action: .tileTop, label: "Top")
178
+ tileCell(action: .tileTopRight, label: "TR")
179
+ }
180
+ HStack(spacing: 2) {
181
+ tileCell(action: .tileLeft, label: "Left")
182
+ tileCell(action: .tileMaximize, label: "Max")
183
+ tileCell(action: .tileRight, label: "Right")
184
+ }
185
+ HStack(spacing: 2) {
186
+ tileCell(action: .tileBottomLeft, label: "BL")
187
+ tileCell(action: .tileBottom, label: "Bot")
188
+ tileCell(action: .tileBottomRight, label: "BR")
189
+ }
190
+ }
191
+ .padding(6)
192
+ .background(
193
+ RoundedRectangle(cornerRadius: 6)
194
+ .fill(Color.black.opacity(0.25))
195
+ .overlay(
196
+ RoundedRectangle(cornerRadius: 6)
197
+ .strokeBorder(Palette.border, lineWidth: 0.5)
198
+ )
199
+ )
200
+
201
+ // Thirds row
202
+ HStack(spacing: 2) {
203
+ tileCell(action: .tileLeftThird, label: "\u{2153}L")
204
+ tileCell(action: .tileCenterThird, label: "\u{2153}C")
205
+ tileCell(action: .tileRightThird, label: "\u{2153}R")
206
+ }
207
+ .padding(6)
208
+ .background(
209
+ RoundedRectangle(cornerRadius: 6)
210
+ .fill(Color.black.opacity(0.25))
211
+ .overlay(
212
+ RoundedRectangle(cornerRadius: 6)
213
+ .strokeBorder(Palette.border, lineWidth: 0.5)
214
+ )
215
+ )
216
+
217
+ // Center + Distribute
218
+ shortcutRow(action: .tileCenter)
219
+ shortcutRow(action: .tileDistribute)
220
+ }
221
+ .frame(maxWidth: .infinity, alignment: .leading)
222
+ }
223
+
224
+ // MARK: - App Column
225
+
226
+ private var appColumn: some View {
227
+ VStack(alignment: .leading, spacing: 8) {
228
+ columnHeader("App")
229
+
230
+ shortcutRow(action: .palette)
231
+ shortcutRow(action: .screenMap)
232
+ shortcutRow(action: .bezel)
233
+ shortcutRow(action: .cheatSheet)
234
+ }
235
+ }
236
+
237
+ // MARK: - tmux Column
238
+
239
+ private var tmuxColumn: some View {
240
+ VStack(alignment: .leading, spacing: 6) {
241
+ columnHeader("Inside tmux")
242
+
243
+ tmuxRow("Detach", keys: ["Ctrl+B", "D"])
244
+ tmuxRow("Kill pane", keys: ["Ctrl+B", "X"])
245
+ tmuxRow("Pane left", keys: ["Ctrl+B", "\u{2190}"])
246
+ tmuxRow("Pane right", keys: ["Ctrl+B", "\u{2192}"])
247
+ tmuxRow("Zoom toggle", keys: ["Ctrl+B", "Z"])
248
+ tmuxRow("Scroll mode", keys: ["Ctrl+B", "["])
249
+ }
250
+ }
251
+
252
+ // MARK: - Shared components
253
+
254
+ private func columnHeader(_ title: String) -> some View {
255
+ Text(title.uppercased())
256
+ .font(Typo.pixel(12))
257
+ .foregroundColor(Palette.textDim)
258
+ .tracking(1)
259
+ }
260
+
261
+ private func tileCell(action: HotkeyAction, label: String) -> some View {
262
+ let binding = hotkeyStore.bindings[action]
263
+ let badgeText = binding?.displayParts.last ?? ""
264
+
265
+ return VStack(spacing: 3) {
266
+ Text(label)
267
+ .font(Typo.caption(9))
268
+ .foregroundColor(Palette.textDim)
269
+ Text(badgeText)
270
+ .font(Typo.geistMonoBold(9))
271
+ .foregroundColor(Palette.text)
272
+ }
273
+ .frame(maxWidth: .infinity)
274
+ .frame(height: 38)
275
+ .background(
276
+ RoundedRectangle(cornerRadius: 4)
277
+ .fill(Palette.surface)
278
+ .overlay(
279
+ RoundedRectangle(cornerRadius: 4)
280
+ .strokeBorder(Palette.border, lineWidth: 0.5)
281
+ )
282
+ )
283
+ }
284
+
285
+ private func shortcutRow(action: HotkeyAction) -> some View {
286
+ let binding = hotkeyStore.bindings[action]
287
+ return HStack(spacing: 8) {
288
+ if let parts = binding?.displayParts {
289
+ HStack(spacing: 3) {
290
+ ForEach(parts, id: \.self) { part in
291
+ keyBadge(part)
292
+ }
293
+ }
294
+ }
295
+ Text(action.label)
296
+ .font(Typo.caption(11))
297
+ .foregroundColor(Palette.textDim)
298
+ Spacer()
299
+ }
300
+ }
301
+
302
+ private func tmuxRow(_ label: String, keys: [String]) -> some View {
303
+ HStack(spacing: 8) {
304
+ HStack(spacing: 3) {
305
+ ForEach(keys, id: \.self) { key in
306
+ keyBadge(key)
307
+ }
308
+ }
309
+ Text(label)
310
+ .font(Typo.caption(11))
311
+ .foregroundColor(Palette.textDim)
312
+ Spacer()
313
+ }
314
+ }
315
+
316
+ private func keyBadge(_ key: String) -> some View {
317
+ Text(key)
318
+ .font(Typo.geistMonoBold(10))
319
+ .foregroundColor(Palette.text)
320
+ .padding(.horizontal, 6)
321
+ .padding(.vertical, 3)
322
+ .background(
323
+ RoundedRectangle(cornerRadius: 3)
324
+ .fill(Palette.surface)
325
+ .overlay(
326
+ RoundedRectangle(cornerRadius: 3)
327
+ .strokeBorder(Palette.border, lineWidth: 0.5)
328
+ )
329
+ )
330
+ }
331
+ }