native macOS codings agent orchestrator
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #155 from onevcat/fix/cli-install-feedback-from-command-palette

fix: show CLI install feedback via toolbar toast for all entry points

authored by

Wei Wang and committed by
GitHub
f127b81d 134ffe54

+66 -48
+2 -3
supacode/App/supacodeApp.swift
··· 617 617 ) 618 618 } 619 619 CommandGroup(after: .appSettings) { 620 - Button("Install Command Line Tool...") { 621 - SettingsWindowManager.shared.show() 622 - store.send(.settings(.installCLIButtonTapped)) 620 + Button("Install Command Line Tool") { 621 + store.send(.settings(.installCLIButtonTapped(showAlert: false))) 623 622 } 624 623 .help("Install the prowl command line tool to /usr/local/bin") 625 624 }
+10 -3
supacode/Features/App/Reducer/AppFeature.swift
··· 472 472 case .settings(.delegate(.terminalFontSizeChanged)): 473 473 return .none 474 474 475 - case .settings(.delegate(.cliInstallStatusChanged)): 476 - return .none 475 + case .settings(.delegate(.cliInstallCompleted(let result))): 476 + switch result { 477 + case .installed(let path): 478 + return .send(.repositories(.showToast(.success("prowl installed at \(path)")))) 479 + case .uninstalled: 480 + return .send(.repositories(.showToast(.success("prowl command line tool removed")))) 481 + case .failed(let message): 482 + return .send(.repositories(.showToast(.warning("CLI install failed: \(message)")))) 483 + } 477 484 478 485 case .settings(.delegate(.terminalLayoutSnapshotCleared(let success))): 479 486 if success { ··· 836 843 return .send(.repositories(.refreshWorktrees)) 837 844 838 845 case .commandPalette(.delegate(.installCLI)): 839 - return .send(.settings(.installCLIButtonTapped)) 846 + return .send(.settings(.installCLIButtonTapped(showAlert: false))) 840 847 841 848 case .commandPalette(.delegate(.ghosttyCommand(let action))): 842 849 guard let worktree = state.repositories.selectedTerminalWorktree else {
+38 -25
supacode/Features/Settings/Reducer/SettingsFeature.swift
··· 28 28 var terminalFontSize: Float32? 29 29 var keybindingUserOverrides: KeybindingUserOverrideStore 30 30 var cliInstallStatus: CLIInstallStatus = .notInstalled 31 + var cliInstallShowAlert: Bool = true 31 32 var selection: SettingsSection? = .general 32 33 var repositorySettings: RepositorySettingsFeature.State? 33 34 @Presents var alert: AlertState<Alert>? ··· 97 98 case setCommandFinishedNotificationThreshold(String) 98 99 case setTerminalFontSize(Float32?) 99 100 case clearTerminalLayoutSnapshotButtonTapped 100 - case installCLIButtonTapped 101 + case installCLIButtonTapped(showAlert: Bool = true) 101 102 case uninstallCLIButtonTapped 102 103 case cliInstallCompleted(Result<String, CLIInstallError>) 103 104 case refreshCLIInstallStatus ··· 113 114 case openSystemNotificationSettings 114 115 } 115 116 117 + enum CLIInstallResultMessage: Equatable { 118 + case installed(path: String) 119 + case uninstalled 120 + case failed(message: String) 121 + } 122 + 116 123 @CasePathable 117 124 enum Delegate: Equatable { 118 125 case settingsChanged(GlobalSettings) 119 126 case terminalFontSizeChanged(Float32?) 120 127 case terminalLayoutSnapshotCleared(success: Bool) 121 - case cliInstallStatusChanged 128 + case cliInstallCompleted(CLIInstallResultMessage) 122 129 } 123 130 124 131 @Dependency(AnalyticsClient.self) private var analyticsClient ··· 213 220 await send(.delegate(.terminalLayoutSnapshotCleared(success: success))) 214 221 } 215 222 216 - case .installCLIButtonTapped: 223 + case .installCLIButtonTapped(let showAlert): 224 + state.cliInstallShowAlert = showAlert 217 225 let installPath = cliDefaultInstallPath 218 226 return .run { [cliInstallClient] send in 219 227 do { ··· 241 249 } 242 250 243 251 case .cliInstallCompleted(.success(let path)): 244 - if path.isEmpty { 245 - state.alert = AlertState { 246 - TextState("Command Line Tool Uninstalled") 247 - } actions: { 248 - ButtonState(action: .dismiss) { TextState("OK") } 249 - } message: { 250 - TextState("The prowl command line tool has been removed.") 252 + if state.cliInstallShowAlert { 253 + if path.isEmpty { 254 + state.alert = AlertState { 255 + TextState("Command Line Tool Uninstalled") 256 + } actions: { 257 + ButtonState(action: .dismiss) { TextState("OK") } 258 + } message: { 259 + TextState("The prowl command line tool has been removed.") 260 + } 261 + } else { 262 + state.alert = AlertState { 263 + TextState("Command Line Tool Installed") 264 + } actions: { 265 + ButtonState(action: .dismiss) { TextState("OK") } 266 + } message: { 267 + TextState("The prowl command is now available at \(path).") 268 + } 251 269 } 252 - } else { 270 + } 271 + state.cliInstallStatus = cliInstallClient.installationStatus(cliDefaultInstallPath) 272 + let result: CLIInstallResultMessage = path.isEmpty ? .uninstalled : .installed(path: path) 273 + return .send(.delegate(.cliInstallCompleted(result))) 274 + 275 + case .cliInstallCompleted(.failure(let error)): 276 + if state.cliInstallShowAlert { 253 277 state.alert = AlertState { 254 - TextState("Command Line Tool Installed") 278 + TextState("Command Line Tool Error") 255 279 } actions: { 256 280 ButtonState(action: .dismiss) { TextState("OK") } 257 281 } message: { 258 - TextState("The prowl command is now available at \(path).") 282 + TextState(error.message) 259 283 } 260 284 } 261 285 state.cliInstallStatus = cliInstallClient.installationStatus(cliDefaultInstallPath) 262 - return .send(.delegate(.cliInstallStatusChanged)) 263 - 264 - case .cliInstallCompleted(.failure(let error)): 265 - state.alert = AlertState { 266 - TextState("Command Line Tool Error") 267 - } actions: { 268 - ButtonState(action: .dismiss) { TextState("OK") } 269 - } message: { 270 - TextState(error.message) 271 - } 272 - state.cliInstallStatus = cliInstallClient.installationStatus(cliDefaultInstallPath) 273 - return .send(.delegate(.cliInstallStatusChanged)) 286 + return .send(.delegate(.cliInstallCompleted(.failed(message: error.message)))) 274 287 275 288 case .refreshCLIInstallStatus: 276 289 state.cliInstallStatus = cliInstallClient.installationStatus(cliDefaultInstallPath)
+2 -2
supacode/Features/Settings/Views/AdvancedSettingsView.swift
··· 38 38 switch store.cliInstallStatus { 39 39 case .notInstalled: 40 40 Button("Install") { 41 - store.send(.installCLIButtonTapped) 41 + store.send(.installCLIButtonTapped()) 42 42 } 43 43 .help("Install prowl command line tool to /usr/local/bin") 44 44 .buttonStyle(.bordered) ··· 50 50 .buttonStyle(.bordered) 51 51 case .installedDifferentSource: 52 52 Button("Reinstall") { 53 - store.send(.installCLIButtonTapped) 53 + store.send(.installCLIButtonTapped()) 54 54 } 55 55 .help("Replace the existing prowl command with the version bundled in this app") 56 56 .buttonStyle(.bordered)
+14 -15
supacodeTests/AppFeatureCLIInstallTests.swift
··· 20 20 $0.cliInstallClient.installationStatus = { _ in .installed(path: "/usr/local/bin/prowl") } 21 21 } 22 22 23 - await store.send(.installCLIButtonTapped) 23 + await store.send(.installCLIButtonTapped()) 24 24 await store.receive(\.cliInstallCompleted.success) { 25 25 $0.alert = AlertState { 26 26 TextState("Command Line Tool Installed") ··· 31 31 } 32 32 $0.cliInstallStatus = .installed(path: "/usr/local/bin/prowl") 33 33 } 34 - await store.receive(\.delegate.cliInstallStatusChanged) 34 + await store.receive(\.delegate.cliInstallCompleted) 35 35 36 36 #expect(installed.value == true) 37 37 } ··· 48 48 $0.cliInstallClient.installationStatus = { _ in .notInstalled } 49 49 } 50 50 51 - await store.send(.installCLIButtonTapped) 51 + await store.send(.installCLIButtonTapped()) 52 52 await store.receive(\.cliInstallCompleted.failure) { 53 53 $0.alert = AlertState { 54 54 TextState("Command Line Tool Error") ··· 58 58 TextState("Permission denied") 59 59 } 60 60 } 61 - await store.receive(\.delegate.cliInstallStatusChanged) 61 + await store.receive(\.delegate.cliInstallCompleted) 62 62 } 63 63 64 64 @Test(.dependencies) func uninstallShowsSuccessAlert() async { ··· 84 84 TextState("The prowl command line tool has been removed.") 85 85 } 86 86 } 87 - await store.receive(\.delegate.cliInstallStatusChanged) 87 + await store.receive(\.delegate.cliInstallCompleted) 88 88 89 89 #expect(uninstalled.value == true) 90 90 } 91 91 92 - @Test(.dependencies) func commandPaletteInstallRoutesToSettings() async { 92 + @Test(.dependencies) func commandPaletteInstallRoutesToSettingsAndShowsToast() async { 93 93 let installed = LockIsolated(false) 94 94 let store = TestStore( 95 95 initialState: AppFeature.State(settings: SettingsFeature.State()) ··· 101 101 } 102 102 $0.cliInstallClient.installationStatus = { _ in .installed(path: "/usr/local/bin/prowl") } 103 103 } 104 + store.exhaustivity = .off 104 105 105 106 await store.send(.commandPalette(.delegate(.installCLI))) 106 - await store.receive(\.settings.installCLIButtonTapped) 107 + await store.receive(\.settings.installCLIButtonTapped) { 108 + $0.settings.cliInstallShowAlert = false 109 + } 107 110 await store.receive(\.settings.cliInstallCompleted.success) { 108 - $0.settings.alert = AlertState { 109 - TextState("Command Line Tool Installed") 110 - } actions: { 111 - ButtonState(action: .dismiss) { TextState("OK") } 112 - } message: { 113 - TextState("The prowl command is now available at /usr/local/bin/prowl.") 114 - } 115 111 $0.settings.cliInstallStatus = .installed(path: "/usr/local/bin/prowl") 116 112 } 117 - await store.receive(\.settings.delegate.cliInstallStatusChanged) 113 + await store.receive(\.settings.delegate.cliInstallCompleted) 114 + await store.receive(\.repositories.showToast) { 115 + $0.repositories.statusToast = .success("prowl installed at /usr/local/bin/prowl") 116 + } 118 117 119 118 #expect(installed.value == true) 120 119 }