summaryrefslogtreecommitdiff
path: root/DomainDig/DomainDigIntents.swift
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-07-22 21:43:42 -0500
committerChristian Cleberg <[email protected]>2026-07-22 23:23:11 -0500
commit872583eca8b4e7ae6ef85917604d4a5257b9d125 (patch)
tree0df88e0621e195f6adf5d0c67615f2099759a530 /DomainDig/DomainDigIntents.swift
parentd3af0e7d51c2bac5de801bfbbed96a105c442e64 (diff)
downloaddomain-dig-872583eca8b4e7ae6ef85917604d4a5257b9d125.tar.gz
domain-dig-872583eca8b4e7ae6ef85917604d4a5257b9d125.tar.bz2
domain-dig-872583eca8b4e7ae6ef85917604d4a5257b9d125.zip
fix: adopt Swift 6 language mode; resolve all concurrency issues (#27)
All three product targets (app, widget, share extension) now build under SWIFT_VERSION = 6.0 with zero errors and zero warnings. The UITests target stays on 5.0: XCTestCase's nonisolated setUp/init overrides conflict with the target's MainActor default isolation under 6, and test tooling is not shipping code. The original seven diagnostics, plus the layers Swift 6 mode surfaced once those cleared: - SMTPChannel is an actor. It was implicitly MainActor while running its receive loop on a background queue, so parsedLines/lineWaiters/ receiveBuffer were declared main-actor-protected and mutated off it — concurrent mutation while resuming a CheckedContinuation can double-resume, which traps. The actor serialises all state; Network callbacks hop in via Task. The start() continuation also gains an OSAllocatedUnfairLock resume-once guard: the state handler can fire .ready and later .failed, and resuming twice was a pre-existing trap of the same family. - CachedLookupResult is nonisolated (a value pair built inside actor LookupRuntime cannot have a MainActor-bound memberwise init) with conditional Sendable — opting out of MainActor isolation also opted out of the implicit Sendable that globally-isolated types get. - PortScanService.printableBanner is nonisolated: a pure transformation called from the connection's queue. - SweepActivityController stores the activity's Sendable id instead of the non-Sendable Activity, re-resolving via Activity.activities inside each fire-and-forget task, so nothing non-Sendable crosses isolation. - App Intents' static title/description/openAppWhenRun become lets (get-only protocol requirements; static var is shared mutable global state), and the summary helpers are @MainActor to match the model properties they read and the perform() implementations that call them. - ExternalDataService's ISO8601DateFormatter is nonisolated(unsafe), citing Apple's documented thread-safety, rather than risking a parser behaviour change by switching APIs with no test coverage. - TaskMetricsDelegate.metrics is nonisolated(unsafe): written on the session's delegate queue, read only after the request completes, and URLSession guarantees didFinishCollecting precedes task completion. - The share extension extracts the host via async/withCheckedContinuation instead of sending a non-Sendable completion into loadItem's @Sendable handler; Task inherits the view controller's MainActor so the manual DispatchQueue.main hop goes too. Validated: clean Swift 6 build of all product targets, and the full enforced 11-test audit suite green on the floor runtime — Swift 6's runtime isolation checks ran the app through every screen without a trap.
Diffstat (limited to 'DomainDig/DomainDigIntents.swift')
-rw-r--r--DomainDig/DomainDigIntents.swift20
1 files changed, 11 insertions, 9 deletions
diff --git a/DomainDig/DomainDigIntents.swift b/DomainDig/DomainDigIntents.swift
index 8b46aa7..70b9e67 100644
--- a/DomainDig/DomainDigIntents.swift
+++ b/DomainDig/DomainDigIntents.swift
@@ -6,13 +6,13 @@ import Foundation
/// `DomainReportBuilder`) and returns a concise summary. Usable from
/// Shortcuts, Spotlight, the Action button, and Siri.
struct InspectDomainIntent: AppIntent {
- static var title: LocalizedStringResource = "Inspect Domain"
- static var description = IntentDescription(
+ static let title: LocalizedStringResource = "Inspect Domain"
+ static let description = IntentDescription(
"Run a DomainDig inspection and return a summary of availability, risk, TLS, email security, and certificate health."
)
// Read-only inspection; no need to foreground the app.
- static var openAppWhenRun = false
+ static let openAppWhenRun = false
@Parameter(
title: "Domain",
@@ -44,6 +44,7 @@ struct InspectDomainIntent: AppIntent {
}
/// Multi-line summary suitable for a returned Shortcuts text value.
+ @MainActor
static func summaryText(for report: DomainReport) -> String {
let dnssec: String
switch report.dns.dnssecSigned {
@@ -68,6 +69,7 @@ struct InspectDomainIntent: AppIntent {
}
/// Short spoken/dialog line for Siri and the Shortcuts result banner.
+ @MainActor
static func spokenSummary(for report: DomainReport) -> String {
"\(report.domain) is \(report.availability.rawValue). Risk \(report.riskAssessment.level.title.lowercased()), health \(report.health.title.lowercased())."
}
@@ -89,12 +91,12 @@ enum InspectDomainError: Error, CustomLocalizedStringResourceConvertible {
/// existing view-model path (premium limits, monitoring, history linking,
/// cloud-sync recording, and the paywall when over the free limit).
struct AddToWatchlistIntent: AppIntent {
- static var title: LocalizedStringResource = "Add Domain to Watchlist"
- static var description = IntentDescription(
+ static let title: LocalizedStringResource = "Add Domain to Watchlist"
+ static let description = IntentDescription(
"Open DomainDig and add a domain to your watchlist."
)
- static var openAppWhenRun = true
+ static let openAppWhenRun = true
@Parameter(
title: "Domain",
@@ -128,12 +130,12 @@ struct AddToWatchlistIntent: AppIntent {
/// through the existing view-model batch path (`refreshAllTrackedDomains`), which
/// enforces the batch feature gate and surfaces the paywall when needed.
struct RunSweepIntent: AppIntent {
- static var title: LocalizedStringResource = "Run Watchlist Sweep"
- static var description = IntentDescription(
+ static let title: LocalizedStringResource = "Run Watchlist Sweep"
+ static let description = IntentDescription(
"Open DomainDig and re-inspect every domain on your watchlist."
)
- static var openAppWhenRun = true
+ static let openAppWhenRun = true
@MainActor
func perform() async throws -> some IntentResult {