From b978cc4e91311637f43abbb684b1ef14937a2d30 Mon Sep 17 00:00:00 2001 From: Tu Nombre Real Date: Sun, 4 Jan 2026 03:13:31 +0100 Subject: [PATCH] feat(macos): add Swift 6 strict concurrency compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prepares the macOS app for Swift 6 strict concurrency mode by: 1. Adding Sendable conformance to WizardNextResult, WizardStartResult, and WizardStatusResult in GatewayModels.swift 2. Adding AnyCodable bridging helpers in OnboardingWizard.swift to handle type conflicts between ClawdisProtocol and local module 3. Making CLLocationManagerDelegate methods nonisolated in: - MacNodeLocationService.swift - PermissionManager.swift (LocationPermissionRequester) Using Task { @MainActor in } pattern to safely access MainActor state from nonisolated protocol requirements. These changes are forward-compatible and don't affect behavior on current Swift versions. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../NodeMode/MacNodeLocationService.swift | 29 ++++++++++------ .../Sources/Clawdis/OnboardingWizard.swift | 34 +++++++++++++------ .../Sources/Clawdis/PermissionManager.swift | 12 ++++--- .../ClawdisProtocol/GatewayModels.swift | 6 ++-- 4 files changed, 53 insertions(+), 28 deletions(-) diff --git a/apps/macos/Sources/Clawdis/NodeMode/MacNodeLocationService.swift b/apps/macos/Sources/Clawdis/NodeMode/MacNodeLocationService.swift index a95d51b13..236e66f35 100644 --- a/apps/macos/Sources/Clawdis/NodeMode/MacNodeLocationService.swift +++ b/apps/macos/Sources/Clawdis/NodeMode/MacNodeLocationService.swift @@ -91,19 +91,26 @@ final class MacNodeLocationService: NSObject, CLLocationManagerDelegate { } } - func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { - guard let cont = self.locationContinuation else { return } - self.locationContinuation = nil - if let latest = locations.last { - cont.resume(returning: latest) - } else { - cont.resume(throwing: Error.unavailable) + // MARK: - CLLocationManagerDelegate (nonisolated for Swift 6 compatibility) + + nonisolated func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { + Task { @MainActor in + guard let cont = self.locationContinuation else { return } + self.locationContinuation = nil + if let latest = locations.last { + cont.resume(returning: latest) + } else { + cont.resume(throwing: Error.unavailable) + } } } - func locationManager(_ manager: CLLocationManager, didFailWithError error: Swift.Error) { - guard let cont = self.locationContinuation else { return } - self.locationContinuation = nil - cont.resume(throwing: error) + nonisolated func locationManager(_ manager: CLLocationManager, didFailWithError error: Swift.Error) { + let errorCopy = error // Capture error for Sendable compliance + Task { @MainActor in + guard let cont = self.locationContinuation else { return } + self.locationContinuation = nil + cont.resume(throwing: errorCopy) + } } } diff --git a/apps/macos/Sources/Clawdis/OnboardingWizard.swift b/apps/macos/Sources/Clawdis/OnboardingWizard.swift index ca0fb2b74..16174d265 100644 --- a/apps/macos/Sources/Clawdis/OnboardingWizard.swift +++ b/apps/macos/Sources/Clawdis/OnboardingWizard.swift @@ -6,6 +6,20 @@ import SwiftUI private let onboardingWizardLogger = Logger(subsystem: "com.clawdis", category: "onboarding.wizard") +// MARK: - Swift 6 AnyCodable Bridging Helpers +// These helpers bridge between ClawdisProtocol.AnyCodable and the local module +// to avoid Swift 6 strict concurrency type conflicts. + +private typealias ProtocolAnyCodable = ClawdisProtocol.AnyCodable + +private func bridgeToProtocol(_ value: Any) -> ProtocolAnyCodable { + ProtocolAnyCodable(value) +} + +private func bridgeDict(_ dict: [String: Any]) -> [String: ProtocolAnyCodable] { + dict.mapValues { ProtocolAnyCodable($0) } +} + @MainActor @Observable final class OnboardingWizardModel { @@ -307,12 +321,12 @@ private struct WizardOptionItem: Identifiable { } private struct WizardOption { - let value: AnyCodable? + let value: ProtocolAnyCodable? let label: String let hint: String? } -private func decodeWizardStep(_ raw: [String: AnyCodable]?) -> WizardStep? { +private func decodeWizardStep(_ raw: [String: ProtocolAnyCodable]?) -> WizardStep? { guard let raw else { return nil } do { let data = try JSONEncoder().encode(raw) @@ -323,7 +337,7 @@ private func decodeWizardStep(_ raw: [String: AnyCodable]?) -> WizardStep? { } } -private func parseWizardOptions(_ raw: [[String: AnyCodable]]?) -> [WizardOption] { +private func parseWizardOptions(_ raw: [[String: ProtocolAnyCodable]]?) -> [WizardOption] { guard let raw else { return [] } return raw.map { entry in let value = entry["value"] @@ -337,7 +351,7 @@ private func wizardStepType(_ step: WizardStep) -> String { (step.type.value as? String) ?? "" } -private func anyCodableString(_ value: AnyCodable?) -> String { +private func anyCodableString(_ value: ProtocolAnyCodable?) -> String { switch value?.value { case let string as String: return string @@ -352,11 +366,11 @@ private func anyCodableString(_ value: AnyCodable?) -> String { } } -private func anyCodableStringValue(_ value: AnyCodable?) -> String? { +private func anyCodableStringValue(_ value: ProtocolAnyCodable?) -> String? { value?.value as? String } -private func anyCodableBool(_ value: AnyCodable?) -> Bool { +private func anyCodableBool(_ value: ProtocolAnyCodable?) -> Bool { switch value?.value { case let bool as Bool: return bool @@ -367,18 +381,18 @@ private func anyCodableBool(_ value: AnyCodable?) -> Bool { } } -private func anyCodableArray(_ value: AnyCodable?) -> [AnyCodable] { +private func anyCodableArray(_ value: ProtocolAnyCodable?) -> [ProtocolAnyCodable] { switch value?.value { - case let arr as [AnyCodable]: + case let arr as [ProtocolAnyCodable]: return arr case let arr as [Any]: - return arr.map { AnyCodable($0) } + return arr.map { ProtocolAnyCodable($0) } default: return [] } } -private func anyCodableEqual(_ lhs: AnyCodable?, _ rhs: AnyCodable?) -> Bool { +private func anyCodableEqual(_ lhs: ProtocolAnyCodable?, _ rhs: ProtocolAnyCodable?) -> Bool { switch (lhs?.value, rhs?.value) { case let (l as String, r as String): return l == r diff --git a/apps/macos/Sources/Clawdis/PermissionManager.swift b/apps/macos/Sources/Clawdis/PermissionManager.swift index ea43844ba..de15195fd 100644 --- a/apps/macos/Sources/Clawdis/PermissionManager.swift +++ b/apps/macos/Sources/Clawdis/PermissionManager.swift @@ -289,10 +289,14 @@ final class LocationPermissionRequester: NSObject, CLLocationManagerDelegate { } } - func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { - guard let cont = self.continuation else { return } - self.continuation = nil - cont.resume(returning: manager.authorizationStatus) + // nonisolated for Swift 6 strict concurrency compatibility + nonisolated func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { + let status = manager.authorizationStatus + Task { @MainActor in + guard let cont = self.continuation else { return } + self.continuation = nil + cont.resume(returning: status) + } } } diff --git a/apps/macos/Sources/ClawdisProtocol/GatewayModels.swift b/apps/macos/Sources/ClawdisProtocol/GatewayModels.swift index 2a10b182a..b968c86f6 100644 --- a/apps/macos/Sources/ClawdisProtocol/GatewayModels.swift +++ b/apps/macos/Sources/ClawdisProtocol/GatewayModels.swift @@ -871,7 +871,7 @@ public struct WizardStep: Codable { } } -public struct WizardNextResult: Codable { +public struct WizardNextResult: Codable, Sendable { public let done: Bool public let step: [String: AnyCodable]? public let status: AnyCodable? @@ -896,7 +896,7 @@ public struct WizardNextResult: Codable { } } -public struct WizardStartResult: Codable { +public struct WizardStartResult: Codable, Sendable { public let sessionid: String public let done: Bool public let step: [String: AnyCodable]? @@ -925,7 +925,7 @@ public struct WizardStartResult: Codable { } } -public struct WizardStatusResult: Codable { +public struct WizardStatusResult: Codable, Sendable { public let status: AnyCodable public let error: String?