diff --git a/supacode/App/supacodeApp.swift b/supacode/App/supacodeApp.swift index 8453ac7f..7b20b770 100644 --- a/supacode/App/supacodeApp.swift +++ b/supacode/App/supacodeApp.swift @@ -617,8 +617,7 @@ struct SupacodeApp: App { ) } CommandGroup(after: .appSettings) { - Button("Install Command Line Tool...") { - SettingsWindowManager.shared.show() + Button("Install Command Line Tool") { store.send(.settings(.installCLIButtonTapped)) } .help("Install the prowl command line tool to /usr/local/bin") diff --git a/supacode/Features/App/Reducer/AppFeature.swift b/supacode/Features/App/Reducer/AppFeature.swift index 4747e0db..d252c4b7 100644 --- a/supacode/Features/App/Reducer/AppFeature.swift +++ b/supacode/Features/App/Reducer/AppFeature.swift @@ -472,8 +472,15 @@ struct AppFeature { case .settings(.delegate(.terminalFontSizeChanged)): return .none - case .settings(.delegate(.cliInstallStatusChanged)): - return .none + case .settings(.delegate(.cliInstallCompleted(let result))): + switch result { + case .installed(let path): + return .send(.repositories(.showToast(.success("prowl installed at \(path)")))) + case .uninstalled: + return .send(.repositories(.showToast(.success("prowl command line tool removed")))) + case .failed(let message): + return .send(.repositories(.showToast(.warning("CLI install failed: \(message)")))) + } case .settings(.delegate(.terminalLayoutSnapshotCleared(let success))): if success { diff --git a/supacode/Features/Settings/Reducer/SettingsFeature.swift b/supacode/Features/Settings/Reducer/SettingsFeature.swift index e8f5b7ce..032d7fe1 100644 --- a/supacode/Features/Settings/Reducer/SettingsFeature.swift +++ b/supacode/Features/Settings/Reducer/SettingsFeature.swift @@ -113,12 +113,18 @@ struct SettingsFeature { case openSystemNotificationSettings } + enum CLIInstallResultMessage: Equatable { + case installed(path: String) + case uninstalled + case failed(message: String) + } + @CasePathable enum Delegate: Equatable { case settingsChanged(GlobalSettings) case terminalFontSizeChanged(Float32?) case terminalLayoutSnapshotCleared(success: Bool) - case cliInstallStatusChanged + case cliInstallCompleted(CLIInstallResultMessage) } @Dependency(AnalyticsClient.self) private var analyticsClient @@ -259,7 +265,8 @@ struct SettingsFeature { } } state.cliInstallStatus = cliInstallClient.installationStatus(cliDefaultInstallPath) - return .send(.delegate(.cliInstallStatusChanged)) + let result: CLIInstallResultMessage = path.isEmpty ? .uninstalled : .installed(path: path) + return .send(.delegate(.cliInstallCompleted(result))) case .cliInstallCompleted(.failure(let error)): state.alert = AlertState { @@ -270,7 +277,7 @@ struct SettingsFeature { TextState(error.message) } state.cliInstallStatus = cliInstallClient.installationStatus(cliDefaultInstallPath) - return .send(.delegate(.cliInstallStatusChanged)) + return .send(.delegate(.cliInstallCompleted(.failed(message: error.message)))) case .refreshCLIInstallStatus: state.cliInstallStatus = cliInstallClient.installationStatus(cliDefaultInstallPath) diff --git a/supacodeTests/AppFeatureCLIInstallTests.swift b/supacodeTests/AppFeatureCLIInstallTests.swift index 9455a056..83b5d535 100644 --- a/supacodeTests/AppFeatureCLIInstallTests.swift +++ b/supacodeTests/AppFeatureCLIInstallTests.swift @@ -31,7 +31,7 @@ struct SettingsFeatureCLIInstallTests { } $0.cliInstallStatus = .installed(path: "/usr/local/bin/prowl") } - await store.receive(\.delegate.cliInstallStatusChanged) + await store.receive(\.delegate.cliInstallCompleted) #expect(installed.value == true) } @@ -58,7 +58,7 @@ struct SettingsFeatureCLIInstallTests { TextState("Permission denied") } } - await store.receive(\.delegate.cliInstallStatusChanged) + await store.receive(\.delegate.cliInstallCompleted) } @Test(.dependencies) func uninstallShowsSuccessAlert() async { @@ -84,12 +84,12 @@ struct SettingsFeatureCLIInstallTests { TextState("The prowl command line tool has been removed.") } } - await store.receive(\.delegate.cliInstallStatusChanged) + await store.receive(\.delegate.cliInstallCompleted) #expect(uninstalled.value == true) } - @Test(.dependencies) func commandPaletteInstallRoutesToSettings() async { + @Test(.dependencies) func commandPaletteInstallRoutesToSettingsAndShowsToast() async { let installed = LockIsolated(false) let store = TestStore( initialState: AppFeature.State(settings: SettingsFeature.State()) @@ -101,6 +101,7 @@ struct SettingsFeatureCLIInstallTests { } $0.cliInstallClient.installationStatus = { _ in .installed(path: "/usr/local/bin/prowl") } } + store.exhaustivity = .off await store.send(.commandPalette(.delegate(.installCLI))) await store.receive(\.settings.installCLIButtonTapped) @@ -114,7 +115,10 @@ struct SettingsFeatureCLIInstallTests { } $0.settings.cliInstallStatus = .installed(path: "/usr/local/bin/prowl") } - await store.receive(\.settings.delegate.cliInstallStatusChanged) + await store.receive(\.settings.delegate.cliInstallCompleted) + await store.receive(\.repositories.showToast) { + $0.repositories.statusToast = .success("prowl installed at /usr/local/bin/prowl") + } #expect(installed.value == true) }