@lattices/cli 0.4.2 → 0.4.6

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 (146) hide show
  1. package/README.md +3 -0
  2. package/app/Info.plist +2 -2
  3. package/app/Lattices.app/Contents/Info.plist +2 -2
  4. package/app/Lattices.app/Contents/MacOS/Lattices +0 -0
  5. package/app/Package.swift +6 -0
  6. package/app/Sources/AppShell/App.swift +20 -0
  7. package/app/Sources/{AppDelegate.swift → AppShell/AppDelegate.swift} +94 -34
  8. package/app/Sources/{AppShellView.swift → AppShell/AppShellView.swift} +12 -1
  9. package/app/Sources/AppShell/AppUpdater.swift +92 -0
  10. package/app/Sources/AppShell/CliActionLauncher.swift +50 -0
  11. package/app/Sources/{HomeDashboardView.swift → AppShell/HomeDashboardView.swift} +18 -10
  12. package/app/Sources/AppShell/LatticesRuntime.swift +61 -0
  13. package/app/Sources/{MainView.swift → AppShell/MainView.swift} +351 -191
  14. package/app/Sources/{OnboardingView.swift → AppShell/OnboardingView.swift} +30 -16
  15. package/app/Sources/{Preferences.swift → AppShell/Preferences.swift} +78 -0
  16. package/app/Sources/{SettingsView.swift → AppShell/SettingsView.swift} +869 -152
  17. package/app/Sources/{HotkeyStore.swift → Core/Actions/HotkeyStore.swift} +9 -5
  18. package/app/Sources/{IntentEngine.swift → Core/Actions/IntentEngine.swift} +51 -27
  19. package/app/Sources/Core/Actions/IntentSchema.swift +94 -0
  20. package/app/Sources/{Intents → Core/Actions/Intents}/LatticeIntent.swift +0 -25
  21. package/app/Sources/{PaletteCommand.swift → Core/Actions/PaletteCommand.swift} +26 -6
  22. package/app/Sources/{VoiceIntentResolver.swift → Core/Actions/VoiceIntentResolver.swift} +46 -4
  23. package/app/Sources/Core/Companion/CompanionActivityLog.swift +70 -0
  24. package/app/Sources/Core/Companion/CompanionKeyboardController.swift +141 -0
  25. package/app/Sources/Core/Companion/LatticesCompanionBridgeServer.swift +438 -0
  26. package/app/Sources/Core/Companion/LatticesCompanionCockpit.swift +555 -0
  27. package/app/Sources/Core/Companion/LatticesCompanionSecurityCoordinator.swift +594 -0
  28. package/app/Sources/Core/Companion/LatticesCompanionTrackpadController.swift +204 -0
  29. package/app/Sources/Core/Companion/LatticesDeckHost.swift +1463 -0
  30. package/app/Sources/{LatticesApi.swift → Core/Daemon/LatticesApi.swift} +125 -4
  31. package/app/Sources/{AppTypeClassifier.swift → Core/Desktop/AppTypeClassifier.swift} +36 -0
  32. package/app/Sources/{DesktopModel.swift → Core/Desktop/DesktopModel.swift} +6 -8
  33. package/app/Sources/Core/Desktop/MouseFinder.swift +527 -0
  34. package/app/Sources/Core/Desktop/SessionWindowLocator.swift +139 -0
  35. package/app/Sources/Core/Desktop/WindowDragSnapController.swift +628 -0
  36. package/app/Sources/Core/Desktop/WindowPreviewCard.swift +100 -0
  37. package/app/Sources/Core/Desktop/WindowPreviewStore.swift +113 -0
  38. package/app/Sources/Core/Desktop/WindowSelectionStore.swift +76 -0
  39. package/app/Sources/{WindowTiler.swift → Core/Desktop/WindowTiler.swift} +351 -172
  40. package/app/Sources/Core/Input/MouseGestureConfig.swift +364 -0
  41. package/app/Sources/Core/Input/MouseGestureController.swift +1203 -0
  42. package/app/Sources/Core/Input/MouseInputDeviceStore.swift +98 -0
  43. package/app/Sources/Core/Input/MouseInputEventViewer.swift +272 -0
  44. package/app/Sources/Core/Input/MouseShortcutStore.swift +107 -0
  45. package/app/Sources/{CommandModeState.swift → Core/Overlays/CommandMode/CommandModeState.swift} +127 -24
  46. package/app/Sources/{CommandModeView.swift → Core/Overlays/CommandMode/CommandModeView.swift} +492 -79
  47. package/app/Sources/Core/Overlays/CommandPalette/CommandPaletteWindow.swift +67 -0
  48. package/app/Sources/{CheatSheetHUD.swift → Core/Overlays/HUD/CheatSheetHUD.swift} +1 -0
  49. package/app/Sources/{HUDRightBar.swift → Core/Overlays/HUD/HUDRightBar.swift} +23 -201
  50. package/app/Sources/{LauncherHUD.swift → Core/Overlays/HUD/LauncherHUD.swift} +12 -26
  51. package/app/Sources/{OmniSearchView.swift → Core/Overlays/OmniSearch/OmniSearchView.swift} +136 -2
  52. package/app/Sources/{OmniSearchWindow.swift → Core/Overlays/OmniSearch/OmniSearchWindow.swift} +21 -32
  53. package/app/Sources/Core/Overlays/OverlayPanelShell.swift +241 -0
  54. package/app/Sources/{ScreenMapState.swift → Core/Overlays/ScreenMap/ScreenMapState.swift} +116 -32
  55. package/app/Sources/{ScreenMapView.swift → Core/Overlays/ScreenMap/ScreenMapView.swift} +510 -524
  56. package/app/Sources/{ScreenMapWindowController.swift → Core/Overlays/ScreenMap/ScreenMapWindowController.swift} +12 -4
  57. package/app/Sources/{VoiceCommandWindow.swift → Core/Overlays/Voice/VoiceCommandWindow.swift} +46 -53
  58. package/app/Sources/Core/Pi/PiAuthNextStepCard.swift +148 -0
  59. package/app/Sources/Core/Pi/PiAuthPromptCard.swift +90 -0
  60. package/app/Sources/{PiChatDock.swift → Core/Pi/PiChatDock.swift} +137 -74
  61. package/app/Sources/{PiChatSession.swift → Core/Pi/PiChatSession.swift} +608 -108
  62. package/app/Sources/Core/Pi/PiInstallCallout.swift +86 -0
  63. package/app/Sources/Core/Pi/PiProviderSetupCallout.swift +99 -0
  64. package/app/Sources/{PiWorkspaceView.swift → Core/Pi/PiWorkspaceView.swift} +174 -77
  65. package/app/Sources/{PermissionChecker.swift → Core/System/PermissionChecker.swift} +76 -2
  66. package/app/Sources/Core/System/SystemTelemetryMonitor.swift +273 -0
  67. package/app/Sources/{HandsOffSession.swift → Core/Voice/HandsOffSession.swift} +15 -4
  68. package/app/Sources/{WorkspaceManager.swift → Core/Workspace/WorkspaceManager.swift} +288 -0
  69. package/bin/assistant-intelligence.ts +874 -0
  70. package/bin/handsoff-infer.ts +16 -209
  71. package/bin/handsoff-worker.ts +45 -258
  72. package/bin/lattices-app.ts +62 -0
  73. package/bin/lattices-dev +4 -0
  74. package/bin/lattices.ts +125 -14
  75. package/docs/agents.md +14 -0
  76. package/docs/api.md +55 -0
  77. package/docs/app.md +3 -0
  78. package/docs/companion-deck.md +180 -0
  79. package/docs/component-extraction-roadmap.md +392 -0
  80. package/docs/config.md +25 -0
  81. package/docs/tiling-reference.md +55 -0
  82. package/docs/voice-error-model.md +73 -0
  83. package/package.json +4 -1
  84. package/app/Sources/App.swift +0 -10
  85. package/app/Sources/CommandPaletteWindow.swift +0 -134
  86. package/app/Sources/MouseFinder.swift +0 -222
  87. /package/app/Sources/{KeyRecorderView.swift → AppShell/KeyRecorderView.swift} +0 -0
  88. /package/app/Sources/{MainWindow.swift → AppShell/MainWindow.swift} +0 -0
  89. /package/app/Sources/{SettingsWindow.swift → AppShell/SettingsWindow.swift} +0 -0
  90. /package/app/Sources/{HotkeyManager.swift → Core/Actions/HotkeyManager.swift} +0 -0
  91. /package/app/Sources/{Intents → Core/Actions/Intents}/CreateLayerIntent.swift +0 -0
  92. /package/app/Sources/{Intents → Core/Actions/Intents}/DistributeIntent.swift +0 -0
  93. /package/app/Sources/{Intents → Core/Actions/Intents}/FocusIntent.swift +0 -0
  94. /package/app/Sources/{Intents → Core/Actions/Intents}/HelpIntent.swift +0 -0
  95. /package/app/Sources/{Intents → Core/Actions/Intents}/KillIntent.swift +0 -0
  96. /package/app/Sources/{Intents → Core/Actions/Intents}/LaunchIntent.swift +0 -0
  97. /package/app/Sources/{Intents → Core/Actions/Intents}/ListSessionsIntent.swift +0 -0
  98. /package/app/Sources/{Intents → Core/Actions/Intents}/ListWindowsIntent.swift +0 -0
  99. /package/app/Sources/{Intents → Core/Actions/Intents}/ScanIntent.swift +0 -0
  100. /package/app/Sources/{Intents → Core/Actions/Intents}/SearchIntent.swift +0 -0
  101. /package/app/Sources/{Intents → Core/Actions/Intents}/SwitchLayerIntent.swift +0 -0
  102. /package/app/Sources/{Intents → Core/Actions/Intents}/TileIntent.swift +0 -0
  103. /package/app/Sources/{DaemonProtocol.swift → Core/Daemon/DaemonProtocol.swift} +0 -0
  104. /package/app/Sources/{DaemonServer.swift → Core/Daemon/DaemonServer.swift} +0 -0
  105. /package/app/Sources/{AccessibilityTextExtractor.swift → Core/Desktop/AccessibilityTextExtractor.swift} +0 -0
  106. /package/app/Sources/{DesktopModelTypes.swift → Core/Desktop/DesktopModelTypes.swift} +0 -0
  107. /package/app/Sources/{InventoryManager.swift → Core/Desktop/InventoryManager.swift} +0 -0
  108. /package/app/Sources/{InventoryPath.swift → Core/Desktop/InventoryPath.swift} +0 -0
  109. /package/app/Sources/{OcrModel.swift → Core/Desktop/OcrModel.swift} +0 -0
  110. /package/app/Sources/{OcrStore.swift → Core/Desktop/OcrStore.swift} +0 -0
  111. /package/app/Sources/{PlacementSpec.swift → Core/Desktop/PlacementSpec.swift} +0 -0
  112. /package/app/Sources/{TilePickerView.swift → Core/Desktop/TilePickerView.swift} +0 -0
  113. /package/app/Sources/{AppWindowShell.swift → Core/Overlays/AppWindowShell.swift} +0 -0
  114. /package/app/Sources/{CommandModeWindow.swift → Core/Overlays/CommandMode/CommandModeWindow.swift} +0 -0
  115. /package/app/Sources/{CommandPaletteView.swift → Core/Overlays/CommandPalette/CommandPaletteView.swift} +0 -0
  116. /package/app/Sources/{HUDBottomBar.swift → Core/Overlays/HUD/HUDBottomBar.swift} +0 -0
  117. /package/app/Sources/{HUDController.swift → Core/Overlays/HUD/HUDController.swift} +0 -0
  118. /package/app/Sources/{HUDLeftBar.swift → Core/Overlays/HUD/HUDLeftBar.swift} +0 -0
  119. /package/app/Sources/{HUDMinimap.swift → Core/Overlays/HUD/HUDMinimap.swift} +0 -0
  120. /package/app/Sources/{HUDState.swift → Core/Overlays/HUD/HUDState.swift} +0 -0
  121. /package/app/Sources/{HUDTopBar.swift → Core/Overlays/HUD/HUDTopBar.swift} +0 -0
  122. /package/app/Sources/{LayerBezel.swift → Core/Overlays/HUD/LayerBezel.swift} +0 -0
  123. /package/app/Sources/{OmniSearchState.swift → Core/Overlays/OmniSearch/OmniSearchState.swift} +0 -0
  124. /package/app/Sources/{DiagnosticLog.swift → Core/System/DiagnosticLog.swift} +0 -0
  125. /package/app/Sources/{EventBus.swift → Core/System/EventBus.swift} +0 -0
  126. /package/app/Sources/{ProcessModel.swift → Core/System/ProcessModel.swift} +0 -0
  127. /package/app/Sources/{ProcessQuery.swift → Core/System/ProcessQuery.swift} +0 -0
  128. /package/app/Sources/{AdvisorLearningStore.swift → Core/Voice/AdvisorLearningStore.swift} +0 -0
  129. /package/app/Sources/{AgentSession.swift → Core/Voice/AgentSession.swift} +0 -0
  130. /package/app/Sources/{AudioProvider.swift → Core/Voice/AudioProvider.swift} +0 -0
  131. /package/app/Sources/{VoiceChatView.swift → Core/Voice/VoiceChatView.swift} +0 -0
  132. /package/app/Sources/{VoxClient.swift → Core/Voice/VoxClient.swift} +0 -0
  133. /package/app/Sources/{Project.swift → Core/Workspace/Project.swift} +0 -0
  134. /package/app/Sources/{ProjectScanner.swift → Core/Workspace/ProjectScanner.swift} +0 -0
  135. /package/app/Sources/{SessionLayerStore.swift → Core/Workspace/SessionLayerStore.swift} +0 -0
  136. /package/app/Sources/{SessionManager.swift → Core/Workspace/SessionManager.swift} +0 -0
  137. /package/app/Sources/{Terminal.swift → Core/Workspace/Terminal/Terminal.swift} +0 -0
  138. /package/app/Sources/{TerminalQuery.swift → Core/Workspace/Terminal/TerminalQuery.swift} +0 -0
  139. /package/app/Sources/{TerminalSynthesizer.swift → Core/Workspace/Terminal/TerminalSynthesizer.swift} +0 -0
  140. /package/app/Sources/{TmuxModel.swift → Core/Workspace/Tmux/TmuxModel.swift} +0 -0
  141. /package/app/Sources/{TmuxQuery.swift → Core/Workspace/Tmux/TmuxQuery.swift} +0 -0
  142. /package/app/Sources/{ActionRow.swift → UI/ActionRow.swift} +0 -0
  143. /package/app/Sources/{OrphanRow.swift → UI/OrphanRow.swift} +0 -0
  144. /package/app/Sources/{ProjectRow.swift → UI/ProjectRow.swift} +0 -0
  145. /package/app/Sources/{TabGroupRow.swift → UI/TabGroupRow.swift} +0 -0
  146. /package/app/Sources/{Theme.swift → UI/Theme.swift} +0 -0
@@ -1,14 +1,14 @@
1
1
  import AppKit
2
2
  import SwiftUI
3
3
 
4
- /// Manages the unified app window (Screen Map + Settings).
4
+ /// Manages the unified app window (Home + Layout + Search + Settings).
5
5
  /// Singleton with show/close/toggle, plus showPage() for navigation.
6
6
  final class ScreenMapWindowController: ObservableObject {
7
7
  static let shared = ScreenMapWindowController()
8
8
 
9
9
  private var window: NSWindow?
10
10
  private var controller: ScreenMapController?
11
- @Published var activePage: AppPage = .screenMap
11
+ @Published var activePage: AppPage = .home
12
12
 
13
13
  var isVisible: Bool { window?.isVisible ?? false }
14
14
 
@@ -23,7 +23,7 @@ final class ScreenMapWindowController: ObservableObject {
23
23
  }
24
24
  }
25
25
 
26
- /// Show the window on the current page (defaults to Screen Map).
26
+ /// Show the window on the current page (defaults to Home).
27
27
  func show() {
28
28
  if let existing = window {
29
29
  if activePage == .screenMap {
@@ -78,6 +78,14 @@ final class ScreenMapWindowController: ObservableObject {
78
78
  show()
79
79
  }
80
80
 
81
+ func showScreenMapOverview() {
82
+ activePage = .screenMap
83
+ show()
84
+ DispatchQueue.main.async { [weak self] in
85
+ self?.controller?.focusViewportPreset(.overview)
86
+ }
87
+ }
88
+
81
89
  /// Open screen map focused on a specific window.
82
90
  func showWindow(wid: UInt32) {
83
91
  activePage = .screenMap
@@ -105,7 +113,7 @@ final class ScreenMapWindowController: ObservableObject {
105
113
  window?.orderOut(nil)
106
114
  window = nil
107
115
  controller = nil
108
- activePage = .screenMap
116
+ activePage = .home
109
117
  AppDelegate.updateActivationPolicy()
110
118
  }
111
119
  }
@@ -2,37 +2,12 @@ import AppKit
2
2
  import Combine
3
3
  import SwiftUI
4
4
 
5
- // MARK: - Panel subclass (handles keyDown when focused)
6
-
7
- final class VoicePanel: NSPanel {
8
- var onKeyDown: ((NSEvent) -> Void)?
9
- var onFlagsChanged: ((NSEvent) -> Void)?
10
-
11
- override var canBecomeKey: Bool { true }
12
-
13
- override func keyDown(with event: NSEvent) {
14
- if let handler = onKeyDown {
15
- handler(event)
16
- } else {
17
- super.keyDown(with: event)
18
- }
19
- }
20
-
21
- override func flagsChanged(with event: NSEvent) {
22
- if let handler = onFlagsChanged {
23
- handler(event)
24
- } else {
25
- super.flagsChanged(with: event)
26
- }
27
- }
28
- }
29
-
30
5
  // MARK: - Window Controller
31
6
 
32
7
  final class VoiceCommandWindow {
33
8
  static let shared = VoiceCommandWindow()
34
9
 
35
- private(set) var panel: VoicePanel?
10
+ private(set) var panel: OverlayPanel?
36
11
  private var keyMonitor: Any?
37
12
  private var state: VoiceCommandState?
38
13
 
@@ -48,7 +23,7 @@ final class VoiceCommandWindow {
48
23
 
49
24
  func show() {
50
25
  // If panel exists but is hidden, just re-show it
51
- if let p = panel, let s = state {
26
+ if let p = panel, state != nil {
52
27
  p.alphaValue = 0
53
28
  p.orderFrontRegardless()
54
29
  NSAnimationContext.runAnimationGroup { ctx in
@@ -69,38 +44,32 @@ final class VoiceCommandWindow {
69
44
  .preferredColorScheme(.dark)
70
45
 
71
46
  let mouseLocation = NSEvent.mouseLocation
72
- let screen = NSScreen.screens.first(where: { $0.frame.contains(mouseLocation) }) ?? NSScreen.main ?? NSScreen.screens.first!
47
+ let screen = NSScreen.screens.first(where: { $0.frame.contains(mouseLocation) })
48
+ ?? NSScreen.main
49
+ ?? NSScreen.screens.first!
73
50
  let visible = screen.visibleFrame
74
-
75
51
  let panelWidth: CGFloat = min(900, visible.width - 80)
76
52
  let panelHeight: CGFloat = min(560, visible.height - 80)
77
53
 
78
- let p = VoicePanel(
79
- contentRect: NSRect(x: 0, y: 0, width: panelWidth, height: panelHeight),
80
- styleMask: [.titled, .nonactivatingPanel],
81
- backing: .buffered,
82
- defer: false
54
+ let p = OverlayPanelShell.makePanel(
55
+ config: .init(
56
+ size: NSSize(width: panelWidth, height: panelHeight),
57
+ styleMask: [.titled, .nonactivatingPanel],
58
+ titleVisible: .hidden,
59
+ titlebarAppearsTransparent: true,
60
+ background: .clear,
61
+ hidesOnDeactivate: false,
62
+ isMovableByWindowBackground: true,
63
+ activatesOnMouseDown: true,
64
+ onKeyDown: { [weak self] event in self?.handleKey(event) },
65
+ onFlagsChanged: { [weak self] event in self?.handleFlags(event) }
66
+ ),
67
+ rootView: view
83
68
  )
84
- p.onKeyDown = { [weak self] event in self?.handleKey(event) }
85
- p.onFlagsChanged = { [weak self] event in self?.handleFlags(event) }
86
- p.titlebarAppearsTransparent = true
87
- p.titleVisibility = .hidden
88
- p.isOpaque = false
89
- p.backgroundColor = .clear
90
- p.level = .floating
91
- p.hasShadow = true
92
- p.hidesOnDeactivate = false
93
- p.isReleasedWhenClosed = false
94
- p.isMovableByWindowBackground = true
95
- p.contentView = NSHostingView(rootView: view)
96
-
97
- // Position: top-center of screen
98
- let x = visible.midX - panelWidth / 2
99
- let y = visible.maxY - panelHeight - 40
100
- p.setFrameOrigin(NSPoint(x: x, y: y))
69
+ OverlayPanelShell.position(p, placement: .topCenter(margin: 40))
101
70
 
102
71
  p.alphaValue = 0
103
- p.orderFrontRegardless()
72
+ OverlayPanelShell.present(p, activate: true, makeKey: true, orderFrontRegardless: true)
104
73
 
105
74
  NSAnimationContext.runAnimationGroup { ctx in
106
75
  ctx.duration = 0.15
@@ -538,6 +507,7 @@ final class VoiceCommandState: ObservableObject {
538
507
 
539
508
  struct VoiceCommandView: View {
540
509
  @ObservedObject var state: VoiceCommandState
510
+ @ObservedObject private var activeSelection = WindowSelectionStore.shared
541
511
  let onDismiss: () -> Void
542
512
 
543
513
  private let docsURL = "https://lattices.dev/docs/voice"
@@ -862,6 +832,30 @@ struct VoiceCommandView: View {
862
832
  VStack(alignment: .leading, spacing: 14) {
863
833
  // Zero-height spacer forces VStack to fill ScrollView width
864
834
  Color.clear.frame(maxWidth: .infinity, maxHeight: 0)
835
+ if activeSelection.isActive {
836
+ commandSection("selection") {
837
+ VStack(alignment: .leading, spacing: 6) {
838
+ HStack(spacing: 6) {
839
+ Text("\(activeSelection.count) window\(activeSelection.count == 1 ? "" : "s")")
840
+ .font(Typo.geistMonoBold(11))
841
+ .foregroundColor(Palette.running)
842
+ if let source = activeSelection.sourceLabel {
843
+ Text(source)
844
+ .font(Typo.geistMono(10))
845
+ .foregroundColor(Palette.textMuted)
846
+ }
847
+ }
848
+ Text(activeSelection.summary(maxItems: 4))
849
+ .font(Typo.geistMono(11))
850
+ .foregroundColor(Palette.textDim)
851
+ .lineLimit(3)
852
+ Text("Try: grid that in the bottom half")
853
+ .font(Typo.geistMono(10))
854
+ .foregroundColor(Palette.textMuted)
855
+ }
856
+ }
857
+ }
858
+
865
859
  // Partial transcript (while listening)
866
860
  if state.phase == .listening, !state.partialText.isEmpty {
867
861
  commandSection("hearing...") {
@@ -1591,4 +1585,3 @@ final class DragDividerNSView: NSView {
1591
1585
  return expanded.contains(point) ? self : nil
1592
1586
  }
1593
1587
  }
1594
-
@@ -0,0 +1,148 @@
1
+ import SwiftUI
2
+
3
+ struct PiAuthNextStepCard: View {
4
+ @ObservedObject var session: PiChatSession
5
+ let compact: Bool
6
+
7
+ var body: some View {
8
+ VStack(alignment: .leading, spacing: compact ? 10 : 12) {
9
+ HStack(spacing: 8) {
10
+ Circle()
11
+ .fill(Palette.running)
12
+ .frame(width: compact ? 6 : 7, height: compact ? 6 : 7)
13
+
14
+ Text(session.authStepLabel)
15
+ .font(Typo.geistMonoBold(compact ? 9 : 10))
16
+ .foregroundColor(Palette.running.opacity(0.95))
17
+ }
18
+
19
+ Text(session.authStepTitle)
20
+ .font(Typo.geistMonoBold(compact ? 11 : 13))
21
+ .foregroundColor(Palette.text)
22
+
23
+ Text(session.authStepDescription)
24
+ .font(Typo.mono(compact ? 10 : 11))
25
+ .foregroundColor(Palette.textDim)
26
+ .fixedSize(horizontal: false, vertical: true)
27
+
28
+ if let code = session.authVerificationCode {
29
+ VStack(alignment: .leading, spacing: 6) {
30
+ HStack(spacing: 6) {
31
+ Text("CODE READY")
32
+ .font(Typo.geistMonoBold(compact ? 8 : 9))
33
+ .foregroundColor(Palette.textMuted)
34
+
35
+ if session.authVerificationCodeCopied {
36
+ statusCapsule("COPIED")
37
+ }
38
+ }
39
+
40
+ Text(code)
41
+ .font(Typo.geistMonoBold(compact ? 14 : 16))
42
+ .foregroundColor(Palette.text)
43
+ .textSelection(.enabled)
44
+ .frame(maxWidth: .infinity, alignment: .leading)
45
+ .padding(.horizontal, compact ? 10 : 12)
46
+ .padding(.vertical, compact ? 9 : 10)
47
+ .background(
48
+ RoundedRectangle(cornerRadius: compact ? 6 : 8)
49
+ .fill(Color.white.opacity(0.04))
50
+ .overlay(
51
+ RoundedRectangle(cornerRadius: compact ? 6 : 8)
52
+ .strokeBorder(Palette.borderLit.opacity(0.5), lineWidth: 0.5)
53
+ )
54
+ )
55
+ }
56
+ }
57
+
58
+ primaryActionButton(
59
+ session.latestAuthURL == nil
60
+ ? "OPENING BROWSER..."
61
+ : (session.authVerificationCode != nil ? "OPEN PAGE AGAIN" : "OPEN BROWSER AGAIN"),
62
+ tint: Palette.running,
63
+ disabled: session.latestAuthURL == nil
64
+ ) {
65
+ session.reopenLatestAuthURL()
66
+ }
67
+
68
+ HStack(spacing: 8) {
69
+ if session.authVerificationCode != nil {
70
+ secondaryActionButton("COPY AGAIN", tint: Palette.text) {
71
+ session.copyAuthVerificationCode()
72
+ }
73
+ }
74
+
75
+ Spacer(minLength: 0)
76
+
77
+ secondaryActionButton("CANCEL", tint: Palette.textMuted) {
78
+ session.cancelAuthFlow()
79
+ }
80
+ }
81
+ }
82
+ .padding(.horizontal, compact ? 10 : 16)
83
+ .padding(.vertical, compact ? 10 : 14)
84
+ .background(
85
+ RoundedRectangle(cornerRadius: compact ? 6 : 8)
86
+ .fill(Palette.running.opacity(0.06))
87
+ .overlay(
88
+ RoundedRectangle(cornerRadius: compact ? 6 : 8)
89
+ .strokeBorder(Palette.running.opacity(0.22), lineWidth: 0.5)
90
+ )
91
+ )
92
+ }
93
+
94
+ private func primaryActionButton(_ label: String, tint: Color, disabled: Bool = false, action: @escaping () -> Void) -> some View {
95
+ Button(action: action) {
96
+ Text(label)
97
+ .font(Typo.geistMonoBold(compact ? 10 : 11))
98
+ .foregroundColor(disabled ? Palette.textMuted : tint)
99
+ .frame(maxWidth: .infinity)
100
+ .padding(.horizontal, compact ? 10 : 12)
101
+ .padding(.vertical, compact ? 8 : 10)
102
+ .background(
103
+ RoundedRectangle(cornerRadius: compact ? 6 : 8)
104
+ .fill(tint.opacity(disabled ? 0.05 : 0.12))
105
+ .overlay(
106
+ RoundedRectangle(cornerRadius: compact ? 6 : 8)
107
+ .strokeBorder((disabled ? Palette.border : tint.opacity(0.35)), lineWidth: 0.5)
108
+ )
109
+ )
110
+ }
111
+ .buttonStyle(.plain)
112
+ .opacity(disabled ? 0.7 : 1)
113
+ .disabled(disabled)
114
+ }
115
+
116
+ private func secondaryActionButton(_ label: String, tint: Color, action: @escaping () -> Void) -> some View {
117
+ Button(label, action: action)
118
+ .buttonStyle(.plain)
119
+ .font(Typo.geistMonoBold(compact ? 9 : 10))
120
+ .foregroundColor(tint)
121
+ .padding(.horizontal, compact ? 8 : 10)
122
+ .padding(.vertical, compact ? 5 : 6)
123
+ .background(
124
+ Capsule()
125
+ .fill(Color.white.opacity(0.03))
126
+ .overlay(
127
+ Capsule()
128
+ .strokeBorder(Palette.border, lineWidth: 0.5)
129
+ )
130
+ )
131
+ }
132
+
133
+ private func statusCapsule(_ text: String) -> some View {
134
+ Text(text)
135
+ .font(Typo.geistMonoBold(compact ? 8 : 9))
136
+ .foregroundColor(Palette.running.opacity(0.95))
137
+ .padding(.horizontal, compact ? 6 : 7)
138
+ .padding(.vertical, compact ? 3 : 4)
139
+ .background(
140
+ Capsule()
141
+ .fill(Palette.running.opacity(0.12))
142
+ .overlay(
143
+ Capsule()
144
+ .strokeBorder(Palette.running.opacity(0.28), lineWidth: 0.5)
145
+ )
146
+ )
147
+ }
148
+ }
@@ -0,0 +1,90 @@
1
+ import SwiftUI
2
+
3
+ struct PiAuthPromptCard: View {
4
+ @ObservedObject var session: PiChatSession
5
+ let prompt: PiAuthPrompt
6
+ let compact: Bool
7
+ var focus: FocusState<Bool>.Binding
8
+
9
+ var body: some View {
10
+ VStack(alignment: .leading, spacing: compact ? 10 : 12) {
11
+ HStack(spacing: 8) {
12
+ Circle()
13
+ .fill(Palette.detach)
14
+ .frame(width: compact ? 6 : 7, height: compact ? 6 : 7)
15
+
16
+ Text("STEP 1")
17
+ .font(Typo.geistMonoBold(compact ? 9 : 10))
18
+ .foregroundColor(Palette.detach.opacity(0.95))
19
+ }
20
+
21
+ Text("One quick question")
22
+ .font(Typo.geistMonoBold(compact ? 11 : 13))
23
+ .foregroundColor(Palette.text)
24
+
25
+ Text(prompt.message)
26
+ .font(Typo.mono(compact ? 10 : 11))
27
+ .foregroundColor(Palette.textDim)
28
+ .fixedSize(horizontal: false, vertical: true)
29
+
30
+ HStack(spacing: 8) {
31
+ TextField(prompt.placeholder ?? "Type your answer", text: $session.authPromptInput)
32
+ .textFieldStyle(.plain)
33
+ .font(Typo.mono(compact ? 11 : 12))
34
+ .foregroundColor(Palette.text)
35
+ .focused(focus)
36
+
37
+ actionButton("CONTINUE", tint: Palette.detach, disabled: !session.canSubmitAuthPrompt) {
38
+ session.submitAuthPrompt()
39
+ }
40
+ }
41
+ .padding(.horizontal, compact ? 10 : 12)
42
+ .padding(.vertical, compact ? 8 : 10)
43
+ .background(
44
+ RoundedRectangle(cornerRadius: compact ? 6 : 8)
45
+ .fill(Color.white.opacity(0.04))
46
+ .overlay(
47
+ RoundedRectangle(cornerRadius: compact ? 6 : 8)
48
+ .strokeBorder(Palette.border.opacity(0.85), lineWidth: 0.5)
49
+ )
50
+ )
51
+
52
+ HStack(spacing: 8) {
53
+ Spacer(minLength: 0)
54
+
55
+ actionButton("CANCEL", tint: Palette.textMuted) {
56
+ session.cancelAuthFlow()
57
+ }
58
+ }
59
+ }
60
+ .padding(.horizontal, compact ? 10 : 16)
61
+ .padding(.vertical, compact ? 10 : 14)
62
+ .background(
63
+ RoundedRectangle(cornerRadius: compact ? 6 : 8)
64
+ .fill(Palette.detach.opacity(0.06))
65
+ .overlay(
66
+ RoundedRectangle(cornerRadius: compact ? 6 : 8)
67
+ .strokeBorder(Palette.detach.opacity(0.22), lineWidth: 0.5)
68
+ )
69
+ )
70
+ }
71
+
72
+ private func actionButton(_ label: String, tint: Color, disabled: Bool = false, action: @escaping () -> Void) -> some View {
73
+ Button(label, action: action)
74
+ .buttonStyle(.plain)
75
+ .font(Typo.geistMonoBold(compact ? 9 : 10))
76
+ .foregroundColor(disabled ? Palette.textMuted : tint)
77
+ .padding(.horizontal, compact ? 8 : 10)
78
+ .padding(.vertical, compact ? 5 : 6)
79
+ .background(
80
+ Capsule()
81
+ .fill(Color.white.opacity(0.03))
82
+ .overlay(
83
+ Capsule()
84
+ .strokeBorder(Palette.border, lineWidth: 0.5)
85
+ )
86
+ )
87
+ .opacity(disabled ? 0.65 : 1)
88
+ .disabled(disabled)
89
+ }
90
+ }