diff --git a/GiteaPR.xcodeproj/project.pbxproj b/GiteaPR.xcodeproj/project.pbxproj index fc3b3c8..7851c56 100644 --- a/GiteaPR.xcodeproj/project.pbxproj +++ b/GiteaPR.xcodeproj/project.pbxproj @@ -187,7 +187,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 4; DEVELOPMENT_TEAM = ""; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_CFBundleDisplayName = "GiteaPR"; @@ -197,7 +197,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2; + MARKETING_VERSION = 1.3; PRODUCT_BUNDLE_IDENTIFIER = com.bueso.giteapr; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -211,7 +211,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 4; DEVELOPMENT_TEAM = ""; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_CFBundleDisplayName = "GiteaPR"; @@ -221,7 +221,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2; + MARKETING_VERSION = 1.3; PRODUCT_BUNDLE_IDENTIFIER = com.bueso.giteapr; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; diff --git a/GiteaPR/Detail/PRDetailViewModel.swift b/GiteaPR/Detail/PRDetailViewModel.swift index e2a43d9..1c69a55 100644 --- a/GiteaPR/Detail/PRDetailViewModel.swift +++ b/GiteaPR/Detail/PRDetailViewModel.swift @@ -41,23 +41,28 @@ final class PRDetailViewModel: ObservableObject { isLoading = true errorMessage = nil defer { isLoading = false } + // Fetch each piece independently. A single sub-request returning 403 + // (e.g. a token scope the repo's Actions needs) must NOT wipe the whole + // detail screen — catch it, log it, keep the rest. Only a failure to + // load the PR itself is fatal. do { - async let prT = client.pullRequest(owner: owner, repo: repo.name, number: prNumber) - async let commentsT = client.comments(owner: owner, repo: repo.name, number: prNumber) - async let reviewsT = client.reviews(owner: owner, repo: repo.name, number: prNumber) - async let filesT = client.changedFiles(owner: owner, repo: repo.name, number: prNumber) - async let commitsT = client.commits(owner: owner, repo: repo.name, number: prNumber) - pr = try await prT - comments = try await commentsT - reviews = try await reviewsT - files = try await filesT - commits = try await commitsT - // CI runs depend on the head SHA from the PR. - if let sha = pr?.head?.sha, !sha.isEmpty { - await loadRuns(client: client, sha: sha) - } + pr = try await client.pullRequest(owner: owner, repo: repo.name, number: prNumber) } catch { + LogStore.shared.log(.error, "PR \(prNumber): load failed: \(error.localizedDescription)") errorMessage = error.localizedDescription + return + } + async let commentsT = client.comments(owner: owner, repo: repo.name, number: prNumber) + async let reviewsT = client.reviews(owner: owner, repo: repo.name, number: prNumber) + async let filesT = client.changedFiles(owner: owner, repo: repo.name, number: prNumber) + async let commitsT = client.commits(owner: owner, repo: repo.name, number: prNumber) + comments = (try? await commentsT) ?? [] + reviews = (try? await reviewsT) ?? [] + files = (try? await filesT) ?? [] + commits = (try? await commitsT) ?? [] + // CI runs depend on the head SHA from the PR. + if let sha = pr?.head?.sha, !sha.isEmpty { + await loadRuns(client: client, sha: sha) } } @@ -73,7 +78,9 @@ final class PRDetailViewModel: ObservableObject { jobs[run.id] = jobList } } catch { - // CI is best-effort; don't fail the whole detail view. + // CI is best-effort; don't fail the whole detail view, but log it so + // a token-scope 403 on the Actions endpoint is visible. + LogStore.shared.log(.warn, "PR \(prNumber): actions/runs failed: \(error.localizedDescription)") } }