@arach/lattices 0.2.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 (143) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +172 -86
  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
@@ -1,234 +0,0 @@
1
- import AppKit
2
- import SwiftUI
3
-
4
- /// Manages the NSStatusItem (menu bar icon), left-click popover, and right-click context menu.
5
- /// Replaces the previous SwiftUI MenuBarExtra approach for full click-event control.
6
- class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
7
-
8
- private var statusItem: NSStatusItem!
9
- private var popover: NSPopover?
10
- private var contextMenu: NSMenu!
11
-
12
- /// 3×3 grid icon for the menu bar — L-shape bright, rest dim (template for auto light/dark)
13
- private static let menuBarIcon: NSImage = {
14
- let size: CGFloat = 18
15
- let img = NSImage(size: NSSize(width: size, height: size), flipped: true) { _ in
16
- let pad: CGFloat = 2
17
- let gap: CGFloat = 1.5
18
- let cellSize = (size - 2 * pad - 2 * gap) / 3
19
-
20
- let solidCells: Set<Int> = [0, 3, 6, 7, 8]
21
-
22
- for row in 0..<3 {
23
- for col in 0..<3 {
24
- let idx = row * 3 + col
25
- let x = pad + CGFloat(col) * (cellSize + gap)
26
- let y = pad + CGFloat(row) * (cellSize + gap)
27
- let rect = NSRect(x: x, y: y, width: cellSize, height: cellSize)
28
-
29
- if solidCells.contains(idx) {
30
- NSColor.black.setFill()
31
- } else {
32
- NSColor.black.withAlphaComponent(0.25).setFill()
33
- }
34
- let path = NSBezierPath(roundedRect: rect, xRadius: 0.8, yRadius: 0.8)
35
- path.fill()
36
- }
37
- }
38
- return true
39
- }
40
- img.isTemplate = true
41
- return img
42
- }()
43
-
44
- /// Toggle between .accessory (hidden from Dock/Cmd+Tab) and .regular (visible)
45
- /// based on whether any managed windows are open.
46
- static func updateActivationPolicy() {
47
- let hasVisibleWindow =
48
- CommandModeWindow.shared.isVisible ||
49
- CommandPaletteWindow.shared.isVisible ||
50
- MainWindow.shared.isVisible ||
51
- ScreenMapWindowController.shared.isVisible ||
52
- OmniSearchWindow.shared.isVisible
53
- let desired: NSApplication.ActivationPolicy = hasVisibleWindow ? .regular : .accessory
54
- if NSApp.activationPolicy() != desired {
55
- NSApp.setActivationPolicy(desired)
56
- if desired == .regular {
57
- NSApp.activate(ignoringOtherApps: true)
58
- }
59
- }
60
- }
61
-
62
- func applicationDidFinishLaunching(_ notification: Notification) {
63
- NSApp.setActivationPolicy(.accessory)
64
- NSApp.appearance = NSAppearance(named: .darkAqua)
65
-
66
- // --- Status item ---
67
- statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)
68
- if let button = statusItem.button {
69
- button.image = Self.menuBarIcon
70
- button.action = #selector(statusItemClicked(_:))
71
- button.sendAction(on: [.leftMouseUp, .rightMouseUp])
72
- button.target = self
73
- }
74
-
75
- // --- Context menu (right-click) ---
76
- contextMenu = buildContextMenu()
77
-
78
- // --- Hotkey registration ---
79
- let scanner = ProjectScanner.shared
80
- CommandPaletteWindow.shared.configure(scanner: scanner)
81
-
82
- let store = HotkeyStore.shared
83
- store.register(action: .palette) { CommandPaletteWindow.shared.toggle() }
84
- store.register(action: .screenMap) { ScreenMapWindowController.shared.toggle() }
85
- store.register(action: .bezel) { WindowBezel.showBezelForFrontmostWindow() }
86
- store.register(action: .cheatSheet) { CheatSheetHUD.shared.toggle() }
87
- store.register(action: .desktopInventory) { CommandModeWindow.shared.toggle() }
88
- store.register(action: .omniSearch) { OmniSearchWindow.shared.toggle() }
89
-
90
- // Layer-switching hotkeys
91
- let workspace = WorkspaceManager.shared
92
- let layerCount = (workspace.config?.layers ?? []).count
93
- for (i, action) in HotkeyAction.layerActions.prefix(layerCount).enumerated() {
94
- let index = i
95
- store.register(action: action) { workspace.tileLayer(index: index) }
96
- }
97
-
98
- // Tiling hotkeys
99
- let tileMap: [(HotkeyAction, TilePosition)] = [
100
- (.tileLeft, .left), (.tileRight, .right),
101
- (.tileMaximize, .maximize), (.tileCenter, .center),
102
- (.tileTopLeft, .topLeft), (.tileTopRight, .topRight),
103
- (.tileBottomLeft, .bottomLeft), (.tileBottomRight, .bottomRight),
104
- (.tileTop, .top), (.tileBottom, .bottom),
105
- (.tileLeftThird, .leftThird), (.tileCenterThird, .centerThird),
106
- (.tileRightThird, .rightThird),
107
- ]
108
- for (action, position) in tileMap {
109
- store.register(action: action) { WindowTiler.tileFrontmostViaAX(to: position) }
110
- }
111
- store.register(action: .tileDistribute) { WindowTiler.distributeVisible() }
112
-
113
- // Check macOS permissions (Accessibility, Screen Recording)
114
- PermissionChecker.shared.check()
115
-
116
- // Start daemon services
117
- OcrStore.shared.open()
118
- DesktopModel.shared.start()
119
- OcrModel.shared.start()
120
- TmuxModel.shared.start()
121
- ProcessModel.shared.start()
122
- LatticesApi.setup()
123
- DaemonServer.shared.start()
124
-
125
- // --diagnostics flag: auto-open diagnostics panel on launch
126
- if CommandLine.arguments.contains("--diagnostics") {
127
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
128
- DiagnosticWindow.shared.show()
129
- }
130
- }
131
-
132
- // --screen-map flag: auto-open screen map on launch
133
- if CommandLine.arguments.contains("--screen-map") {
134
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
135
- ScreenMapWindowController.shared.show()
136
- }
137
- }
138
- }
139
-
140
- // MARK: - Status item click handler
141
-
142
- @objc private func statusItemClicked(_ sender: Any?) {
143
- guard let event = NSApp.currentEvent, let button = statusItem.button else { return }
144
-
145
- if event.type == .rightMouseUp {
146
- // Right-click → context menu
147
- contextMenu.popUp(positioning: nil, at: NSPoint(x: 0, y: button.bounds.height + 4), in: button)
148
- } else {
149
- // Left-click → toggle popover
150
- if let shown = popover, shown.isShown {
151
- shown.performClose(sender)
152
- } else {
153
- let p = makePopover()
154
- p.show(relativeTo: button.bounds, of: button, preferredEdge: .minY)
155
- p.contentViewController?.view.window?.makeKey()
156
- }
157
- }
158
- }
159
-
160
- /// Dismiss the popover programmatically (e.g. from the pop-out button).
161
- func dismissPopover() {
162
- popover?.performClose(nil)
163
- }
164
-
165
- /// Create a fresh popover each time so the SwiftUI view tree isn't kept alive
166
- /// when the popover is closed — prevents continuous CPU usage from @Published updates.
167
- private func makePopover() -> NSPopover {
168
- let p = NSPopover()
169
- p.contentViewController = NSHostingController(rootView: MainView(scanner: ProjectScanner.shared))
170
- p.behavior = .transient
171
- p.contentSize = NSSize(width: 380, height: 520)
172
- p.appearance = NSAppearance(named: .darkAqua)
173
- p.delegate = self
174
- popover = p
175
- return p
176
- }
177
-
178
- func popoverDidClose(_ notification: Notification) {
179
- // Tear down the SwiftUI view tree so observed models stop driving re-renders
180
- popover?.contentViewController = nil
181
- popover = nil
182
- }
183
-
184
- // MARK: - Context menu
185
-
186
- private func buildContextMenu() -> NSMenu {
187
- let menu = NSMenu()
188
-
189
- let actions: [(String, String, Selector)] = [
190
- ("Command Palette", "⌘⇧M", #selector(menuCommandPalette)),
191
- ("Screen Map", "", #selector(menuScreenMap)),
192
- ("Desktop Inventory", "", #selector(menuDesktopInventory)),
193
- ("Window Bezel", "", #selector(menuWindowBezel)),
194
- ("Cheat Sheet", "", #selector(menuCheatSheet)),
195
- ("Omni Search", "", #selector(menuOmniSearch)),
196
- ]
197
- for (title, shortcut, action) in actions {
198
- let item = NSMenuItem(title: title, action: action, keyEquivalent: "")
199
- item.target = self
200
- if !shortcut.isEmpty {
201
- // Display-only; the actual hotkey is global
202
- }
203
- menu.addItem(item)
204
- }
205
-
206
- menu.addItem(.separator())
207
-
208
- let settings = NSMenuItem(title: "Settings…", action: #selector(menuSettings), keyEquivalent: ",")
209
- settings.target = self
210
- menu.addItem(settings)
211
-
212
- let diag = NSMenuItem(title: "Diagnostics", action: #selector(menuDiagnostics), keyEquivalent: "")
213
- diag.target = self
214
- menu.addItem(diag)
215
-
216
- menu.addItem(.separator())
217
-
218
- let quit = NSMenuItem(title: "Quit Lattices", action: #selector(menuQuit), keyEquivalent: "q")
219
- quit.target = self
220
- menu.addItem(quit)
221
-
222
- return menu
223
- }
224
-
225
- @objc private func menuCommandPalette() { CommandPaletteWindow.shared.toggle() }
226
- @objc private func menuScreenMap() { ScreenMapWindowController.shared.toggle() }
227
- @objc private func menuDesktopInventory() { CommandModeWindow.shared.toggle() }
228
- @objc private func menuWindowBezel() { WindowBezel.showBezelForFrontmostWindow() }
229
- @objc private func menuCheatSheet() { CheatSheetHUD.shared.toggle() }
230
- @objc private func menuOmniSearch() { OmniSearchWindow.shared.toggle() }
231
- @objc private func menuSettings() { SettingsWindowController.shared.show() }
232
- @objc private func menuDiagnostics() { DiagnosticWindow.shared.toggle() }
233
- @objc private func menuQuit() { NSApp.terminate(nil) }
234
- }
@@ -1,62 +0,0 @@
1
- import SwiftUI
2
-
3
- // MARK: - Navigation Pages
4
-
5
- enum AppPage: String, CaseIterable {
6
- case screenMap
7
- case settings
8
- case docs
9
-
10
- var label: String {
11
- switch self {
12
- case .screenMap: return "Screen Map"
13
- case .settings: return "Settings"
14
- case .docs: return "Docs"
15
- }
16
- }
17
-
18
- var icon: String {
19
- switch self {
20
- case .screenMap: return "rectangle.3.group"
21
- case .settings: return "gearshape"
22
- case .docs: return "book"
23
- }
24
- }
25
- }
26
-
27
- // MARK: - App Shell View
28
-
29
- struct AppShellView: View {
30
- @ObservedObject var controller: ScreenMapController
31
- @ObservedObject var windowController = ScreenMapWindowController.shared
32
-
33
- var body: some View {
34
- contentArea
35
- .background(Palette.bg)
36
- }
37
-
38
- // MARK: - Content Area
39
-
40
- @ViewBuilder
41
- private var contentArea: some View {
42
- switch windowController.activePage {
43
- case .screenMap:
44
- ScreenMapView(controller: controller, onNavigate: { page in
45
- windowController.activePage = page
46
- })
47
- case .settings:
48
- SettingsContentView(
49
- prefs: Preferences.shared,
50
- scanner: ProjectScanner.shared,
51
- onBack: { windowController.activePage = .screenMap; controller.enter() }
52
- )
53
- case .docs:
54
- SettingsContentView(
55
- page: .docs,
56
- prefs: Preferences.shared,
57
- scanner: ProjectScanner.shared,
58
- onBack: { windowController.activePage = .screenMap; controller.enter() }
59
- )
60
- }
61
- }
62
- }
@@ -1,70 +0,0 @@
1
- import Foundation
2
-
3
- enum AppType: String, CaseIterable {
4
- case terminal
5
- case editor
6
- case browser
7
- case chat
8
- case media
9
- case design
10
- case system
11
- case other
12
-
13
- var label: String { rawValue }
14
- }
15
-
16
- enum AppTypeClassifier {
17
- private static let nameMap: [String: AppType] = [
18
- // Terminals
19
- "iTerm2": .terminal, "Terminal": .terminal, "Alacritty": .terminal,
20
- "kitty": .terminal, "Warp": .terminal, "Hyper": .terminal,
21
- "WezTerm": .terminal, "Rio": .terminal, "Ghostty": .terminal,
22
-
23
- // Editors / IDEs
24
- "Xcode": .editor, "Code": .editor, "Visual Studio Code": .editor,
25
- "Cursor": .editor, "Sublime Text": .editor, "TextEdit": .editor,
26
- "Nova": .editor, "BBEdit": .editor, "Zed": .editor,
27
- "IntelliJ IDEA": .editor, "WebStorm": .editor, "PyCharm": .editor,
28
- "CLion": .editor, "GoLand": .editor, "RustRover": .editor,
29
- "Android Studio": .editor, "Fleet": .editor, "Neovide": .editor,
30
-
31
- // Browsers
32
- "Safari": .browser, "Google Chrome": .browser, "Firefox": .browser,
33
- "Arc": .browser, "Brave Browser": .browser, "Microsoft Edge": .browser,
34
- "Orion": .browser, "Vivaldi": .browser, "Opera": .browser,
35
- "Chrome": .browser, "Zen Browser": .browser,
36
-
37
- // Chat / Communication
38
- "Slack": .chat, "Discord": .chat, "Messages": .chat,
39
- "Telegram": .chat, "WhatsApp": .chat, "Signal": .chat,
40
- "Teams": .chat, "Microsoft Teams": .chat, "Zoom": .chat,
41
- "FaceTime": .chat, "Skype": .chat,
42
-
43
- // Media
44
- "Spotify": .media, "Music": .media, "QuickTime Player": .media,
45
- "VLC": .media, "IINA": .media, "Podcasts": .media,
46
- "Photos": .media, "Preview": .media, "mpv": .media,
47
-
48
- // Design
49
- "Figma": .design, "Sketch": .design, "Pixelmator Pro": .design,
50
- "Affinity Designer 2": .design, "Affinity Photo 2": .design,
51
- "Adobe Photoshop": .design, "Adobe Illustrator": .design,
52
- "Blender": .design, "OmniGraffle": .design,
53
-
54
- // System
55
- "Finder": .system, "System Preferences": .system, "System Settings": .system,
56
- "Activity Monitor": .system, "Console": .system, "Disk Utility": .system,
57
- "Keychain Access": .system,
58
- ]
59
-
60
- static func classify(_ appName: String) -> AppType {
61
- if let exact = nameMap[appName] { return exact }
62
- // Substring fallback
63
- let lower = appName.lowercased()
64
- if lower.contains("terminal") || lower.contains("term") { return .terminal }
65
- if lower.contains("code") || lower.contains("studio") || lower.contains("edit") { return .editor }
66
- if lower.contains("chrome") || lower.contains("firefox") || lower.contains("safari") || lower.contains("browser") { return .browser }
67
- if lower.contains("slack") || lower.contains("discord") || lower.contains("chat") || lower.contains("teams") { return .chat }
68
- return .other
69
- }
70
- }
@@ -1,63 +0,0 @@
1
- import AppKit
2
- import SwiftUI
3
-
4
- /// Shared factory for standalone NSWindow chrome.
5
- /// Every managed window (Screen Map, Settings, Diagnostics, etc.) uses this
6
- /// to get consistent title bar styling, dark appearance, and positioning.
7
- struct AppWindowShell {
8
-
9
- struct Config {
10
- var title: String
11
- var titleVisible: Bool = true
12
- var initialSize: NSSize
13
- var minSize: NSSize
14
- var maxSize: NSSize
15
- var miniaturizable: Bool = true
16
- }
17
-
18
- /// Create a styled NSWindow hosting a SwiftUI root view.
19
- static func makeWindow<V: View>(config: Config, rootView: V) -> NSWindow {
20
- let hosting = NSHostingView(rootView: rootView.preferredColorScheme(.dark))
21
- hosting.frame = NSRect(origin: .zero, size: config.initialSize)
22
-
23
- var styleMask: NSWindow.StyleMask = [.titled, .closable, .resizable]
24
- if config.miniaturizable { styleMask.insert(.miniaturizable) }
25
-
26
- let w = NSWindow(
27
- contentRect: NSRect(origin: .zero, size: config.initialSize),
28
- styleMask: styleMask,
29
- backing: .buffered,
30
- defer: false
31
- )
32
- w.contentView = hosting
33
- w.title = config.title
34
- w.titlebarAppearsTransparent = true
35
- w.titleVisibility = config.titleVisible ? .visible : .hidden
36
- w.isReleasedWhenClosed = false
37
- w.backgroundColor = NSColor(red: 0.11, green: 0.11, blue: 0.12, alpha: 1.0)
38
- w.appearance = NSAppearance(named: .darkAqua)
39
- w.minSize = config.minSize
40
- w.maxSize = config.maxSize
41
- return w
42
- }
43
-
44
- /// Center the window on screen, nudged 8% above vertical center.
45
- /// Clamps to 92% screen width / 85% screen height.
46
- static func positionCentered(_ window: NSWindow) {
47
- guard let screen = NSScreen.main else { return }
48
- let frame = screen.visibleFrame
49
- let size = window.frame.size
50
- let w = min(size.width, frame.width * 0.92)
51
- let h = min(size.height, frame.height * 0.85)
52
- let x = frame.midX - w / 2
53
- let y = frame.midY - h / 2 + (frame.height * 0.08)
54
- window.setFrame(NSRect(x: x, y: y, width: w, height: h), display: true)
55
- }
56
-
57
- /// Bring the window to front and update activation policy.
58
- static func present(_ window: NSWindow) {
59
- window.makeKeyAndOrderFront(nil)
60
- NSApp.activate(ignoringOtherApps: true)
61
- AppDelegate.updateActivationPolicy()
62
- }
63
- }