From ed1d2f69c3cf2ba83a2a673d9306dce3b3e64697 Mon Sep 17 00:00:00 2001 From: onevcat Date: Sun, 19 Apr 2026 21:16:18 +0900 Subject: [PATCH] Separate code host capability and tidy pull request action reducer Split supportsPullRequests into a dedicated supportsCodeHost capability so non-GitHub git remotes can still surface the open-on-code-host action without implying pull request support. Align the palette accessibility label to Title Case and flatten the pullRequestAction reducer by handling openOnCodeHost before unwrapping the pull request, removing redundant inner guards on the pullRequest optional. --- supacode/Commands/WorktreeCommands.swift | 2 +- supacode/Domain/Repository.swift | 3 ++ .../Reducer/CommandPaletteFeature.swift | 13 +++-- .../Views/CommandPaletteOverlayView.swift | 2 +- ...epositoriesFeature+GithubIntegration.swift | 48 +++++++++---------- 5 files changed, 38 insertions(+), 30 deletions(-) diff --git a/supacode/Commands/WorktreeCommands.swift b/supacode/Commands/WorktreeCommands.swift index 52680963..bfef7f4a 100644 --- a/supacode/Commands/WorktreeCommands.swift +++ b/supacode/Commands/WorktreeCommands.swift @@ -134,7 +134,7 @@ struct WorktreeCommands: Commands { guard let selectedWorktreeID = repositories.selectedWorktreeID else { return nil } guard let repositoryID = repositories.repositoryID(containing: selectedWorktreeID), - repositories.repositories[id: repositoryID]?.capabilities.supportsPullRequests == true + repositories.repositories[id: repositoryID]?.capabilities.supportsCodeHost == true else { return nil } diff --git a/supacode/Domain/Repository.swift b/supacode/Domain/Repository.swift index 0df9e26b..5292cb98 100644 --- a/supacode/Domain/Repository.swift +++ b/supacode/Domain/Repository.swift @@ -11,6 +11,7 @@ nonisolated struct Repository: Identifiable, Hashable, Sendable { let supportsWorktrees: Bool let supportsBranchOperations: Bool let supportsPullRequests: Bool + let supportsCodeHost: Bool let supportsDiff: Bool let supportsGitStatus: Bool let supportsRunnableFolderActions: Bool @@ -20,6 +21,7 @@ nonisolated struct Repository: Identifiable, Hashable, Sendable { supportsWorktrees: true, supportsBranchOperations: true, supportsPullRequests: true, + supportsCodeHost: true, supportsDiff: true, supportsGitStatus: true, supportsRunnableFolderActions: true, @@ -30,6 +32,7 @@ nonisolated struct Repository: Identifiable, Hashable, Sendable { supportsWorktrees: false, supportsBranchOperations: false, supportsPullRequests: false, + supportsCodeHost: false, supportsDiff: false, supportsGitStatus: false, supportsRunnableFolderActions: true, diff --git a/supacode/Features/CommandPalette/Reducer/CommandPaletteFeature.swift b/supacode/Features/CommandPalette/Reducer/CommandPaletteFeature.swift index b2bfff9e..f5a6621a 100644 --- a/supacode/Features/CommandPalette/Reducer/CommandPaletteFeature.swift +++ b/supacode/Features/CommandPalette/Reducer/CommandPaletteFeature.swift @@ -277,14 +277,17 @@ private func selectedCodeHostItems( guard let selectedWorktreeID = repositories.selectedWorktreeID, let repositoryID = repositories.repositoryID(containing: selectedWorktreeID), - let repository = repositories.repositories[id: repositoryID], - repository.capabilities.supportsPullRequests + let repository = repositories.repositories[id: repositoryID] else { return [] } let pullRequest = repositories.worktreeInfo(for: selectedWorktreeID)?.pullRequest - if let pullRequest, pullRequest.number > 0, pullRequest.state.uppercased() != "CLOSED" { + if repository.capabilities.supportsPullRequests, + let pullRequest, + pullRequest.number > 0, + pullRequest.state.uppercased() != "CLOSED" + { return pullRequestItems( pullRequest: pullRequest, worktreeID: selectedWorktreeID, @@ -292,6 +295,10 @@ private func selectedCodeHostItems( ) } + guard repository.capabilities.supportsCodeHost else { + return [] + } + return [ CommandPaletteItem( id: CommandPaletteItemID.pullRequestOpen(repositoryID), diff --git a/supacode/Features/CommandPalette/Views/CommandPaletteOverlayView.swift b/supacode/Features/CommandPalette/Views/CommandPaletteOverlayView.swift index 298e775d..7c33d820 100644 --- a/supacode/Features/CommandPalette/Views/CommandPaletteOverlayView.swift +++ b/supacode/Features/CommandPalette/Views/CommandPaletteOverlayView.swift @@ -520,7 +520,7 @@ private struct CommandPaletteRowView: View { case .archiveWorktree: base = "Archive \(row.title)" case .openPullRequest: - base = "Open on code host" + base = "Open on Code Host" case .markPullRequestReady: base = "Mark pull request ready for review" case .mergePullRequest: diff --git a/supacode/Features/Repositories/Reducer/RepositoriesFeature+GithubIntegration.swift b/supacode/Features/Repositories/Reducer/RepositoriesFeature+GithubIntegration.swift index 64965a79..0c9d88f8 100644 --- a/supacode/Features/Repositories/Reducer/RepositoriesFeature+GithubIntegration.swift +++ b/supacode/Features/Repositories/Reducer/RepositoriesFeature+GithubIntegration.swift @@ -237,28 +237,11 @@ extension RepositoriesFeature { } let repoRoot = worktree.repositoryRootURL let worktreeRoot = worktree.workingDirectory - let pullRequest = state.worktreeInfo(for: worktreeID)?.pullRequest - if action != .openOnCodeHost, pullRequest == nil { - return .send( - .presentAlert( - title: "Pull request not available", - message: "Prowl could not find a pull request for this worktree." - ) - ) - } - let pullRequestRefresh = WorktreeInfoWatcherClient.Event.repositoryPullRequestRefresh( - repositoryRootURL: repoRoot, - worktreeIDs: repository.worktrees.map(\.id) - ) - let branchName = pullRequest?.headRefName ?? worktree.name - let failingCheckDetailsURL = (pullRequest?.statusCheckRollup?.checks ?? []).first { - $0.checkState == .failure && $0.detailsUrl != nil - }?.detailsUrl - switch action { - case .openOnCodeHost: + let optionalPullRequest = state.worktreeInfo(for: worktreeID)?.pullRequest + if case .openOnCodeHost = action { let gitClient = gitClient let openURLClient = openURLClient - let pullRequestURL = pullRequest.flatMap { Self.validWebURL($0.url) } + let pullRequestURL = optionalPullRequest.flatMap { Self.validWebURL($0.url) } return .run { send in if let pullRequestURL { await openURLClient.open(pullRequestURL) @@ -275,6 +258,26 @@ extension RepositoriesFeature { } await openURLClient.open(repositoryURL) } + } + guard let pullRequest = optionalPullRequest else { + return .send( + .presentAlert( + title: "Pull request not available", + message: "Prowl could not find a pull request for this worktree." + ) + ) + } + let pullRequestRefresh = WorktreeInfoWatcherClient.Event.repositoryPullRequestRefresh( + repositoryRootURL: repoRoot, + worktreeIDs: repository.worktrees.map(\.id) + ) + let branchName = pullRequest.headRefName ?? worktree.name + let failingCheckDetailsURL = (pullRequest.statusCheckRollup?.checks ?? []).first { + $0.checkState == .failure && $0.detailsUrl != nil + }?.detailsUrl + switch action { + case .openOnCodeHost: + return .none case .copyFailingJobURL: guard let failingCheckDetailsURL, !failingCheckDetailsURL.isEmpty else { @@ -310,7 +313,6 @@ extension RepositoriesFeature { let githubCLI = githubCLI let githubIntegration = githubIntegration return .run { send in - guard let pullRequest else { return } guard await githubIntegration.isAvailable() else { await send( .presentAlert( @@ -340,7 +342,6 @@ extension RepositoriesFeature { let githubCLI = githubCLI let githubIntegration = githubIntegration return .run { send in - guard let pullRequest else { return } guard await githubIntegration.isAvailable() else { await send( .presentAlert( @@ -374,7 +375,6 @@ extension RepositoriesFeature { let githubCLI = githubCLI let githubIntegration = githubIntegration return .run { send in - guard let pullRequest else { return } guard await githubIntegration.isAvailable() else { await send( .presentAlert( @@ -405,7 +405,6 @@ extension RepositoriesFeature { let githubCLI = githubCLI let githubIntegration = githubIntegration return .run { send in - guard pullRequest != nil else { return } guard await githubIntegration.isAvailable() else { await send( .presentAlert( @@ -483,7 +482,6 @@ extension RepositoriesFeature { let githubCLI = githubCLI let githubIntegration = githubIntegration return .run { send in - guard pullRequest != nil else { return } guard await githubIntegration.isAvailable() else { await send( .presentAlert( -- 2.51.2