Ignore URLError.cancelled so pull-to-refresh doesn't show a spurious error; bump to 3.5
Build iOS App / Build .ipa and publish source (push) Successful in 1m44s

Pull-to-refresh cancels the still-in-flight request (URLError.cancelled,
localizedDescription 'cancelled') to run the refresh, which surfaced as an
error dialog. Add an isCancellation helper and skip cancellations in the PR
load and CI-runs paths. Bump to 3.5.
This commit is contained in:
Hermes
2026-08-24 07:18:35 +00:00
parent a045a36125
commit 6ed50ecc4a
2 changed files with 22 additions and 6 deletions
+4 -4
View File
@@ -187,7 +187,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 25;
CURRENT_PROJECT_VERSION = 26;
DEVELOPMENT_TEAM = "";
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "GiteaPR";
@@ -197,7 +197,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 3.4;
MARKETING_VERSION = 3.5;
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 = 25;
CURRENT_PROJECT_VERSION = 26;
DEVELOPMENT_TEAM = "";
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "GiteaPR";
@@ -221,7 +221,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 3.4;
MARKETING_VERSION = 3.5;
PRODUCT_BUNDLE_IDENTIFIER = com.bueso.giteapr;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
+18 -2
View File
@@ -36,6 +36,18 @@ final class PRDetailViewModel: ObservableObject {
}
}
/// A task cancellation is not a real failure — it happens when a refresh
/// (pull-to-refresh / .task / .onAppear) supersedes an in-flight request,
/// and surfaces as URLError.cancelled ("cancelled"). These must not be
/// shown as errors.
private func isCancellation(_ error: Error) -> Bool {
if let urlError = error as? URLError {
return urlError.code == .cancelled
}
let ns = error as NSError
return ns.domain == NSURLErrorDomain && ns.code == NSURLErrorCancelled
}
func load() async {
guard let client = SettingsStore.shared.client() else { return }
isLoading = true
@@ -48,6 +60,7 @@ final class PRDetailViewModel: ObservableObject {
do {
pr = try await client.pullRequest(owner: owner, repo: repo.name, number: prNumber)
} catch {
if isCancellation(error) { return } // pull-to-refresh superseded; not a real error
LogStore.shared.log(.error, "PR \(prNumber): load failed: \(error.localizedDescription)")
errorMessage = error.localizedDescription
return
@@ -79,8 +92,11 @@ final class PRDetailViewModel: ObservableObject {
}
} catch {
// 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)")
// a token-scope 403 on the Actions endpoint is visible. Ignore
// cancellations (refresh superseding an in-flight request).
if !isCancellation(error) {
LogStore.shared.log(.warn, "PR \(prNumber): actions/runs failed: \(error.localizedDescription)")
}
}
}