feat(macos): add Swift 6 strict concurrency compatibility

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 <noreply@anthropic.com>
This commit is contained in:
Tu Nombre Real
2026-01-04 03:13:31 +01:00
committed by Peter Steinberger
parent 72a9e58777
commit b978cc4e91
4 changed files with 53 additions and 28 deletions

View File

@@ -91,7 +91,10 @@ final class MacNodeLocationService: NSObject, CLLocationManagerDelegate {
} }
} }
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { // 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 } guard let cont = self.locationContinuation else { return }
self.locationContinuation = nil self.locationContinuation = nil
if let latest = locations.last { if let latest = locations.last {
@@ -100,10 +103,14 @@ final class MacNodeLocationService: NSObject, CLLocationManagerDelegate {
cont.resume(throwing: Error.unavailable) cont.resume(throwing: Error.unavailable)
} }
} }
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Swift.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 } guard let cont = self.locationContinuation else { return }
self.locationContinuation = nil self.locationContinuation = nil
cont.resume(throwing: error) cont.resume(throwing: errorCopy)
}
} }
} }

View File

@@ -6,6 +6,20 @@ import SwiftUI
private let onboardingWizardLogger = Logger(subsystem: "com.clawdis", category: "onboarding.wizard") 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 @MainActor
@Observable @Observable
final class OnboardingWizardModel { final class OnboardingWizardModel {
@@ -307,12 +321,12 @@ private struct WizardOptionItem: Identifiable {
} }
private struct WizardOption { private struct WizardOption {
let value: AnyCodable? let value: ProtocolAnyCodable?
let label: String let label: String
let hint: String? let hint: String?
} }
private func decodeWizardStep(_ raw: [String: AnyCodable]?) -> WizardStep? { private func decodeWizardStep(_ raw: [String: ProtocolAnyCodable]?) -> WizardStep? {
guard let raw else { return nil } guard let raw else { return nil }
do { do {
let data = try JSONEncoder().encode(raw) 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 [] } guard let raw else { return [] }
return raw.map { entry in return raw.map { entry in
let value = entry["value"] let value = entry["value"]
@@ -337,7 +351,7 @@ private func wizardStepType(_ step: WizardStep) -> String {
(step.type.value as? String) ?? "" (step.type.value as? String) ?? ""
} }
private func anyCodableString(_ value: AnyCodable?) -> String { private func anyCodableString(_ value: ProtocolAnyCodable?) -> String {
switch value?.value { switch value?.value {
case let string as String: case let string as String:
return 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 value?.value as? String
} }
private func anyCodableBool(_ value: AnyCodable?) -> Bool { private func anyCodableBool(_ value: ProtocolAnyCodable?) -> Bool {
switch value?.value { switch value?.value {
case let bool as Bool: case let bool as Bool:
return 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 { switch value?.value {
case let arr as [AnyCodable]: case let arr as [ProtocolAnyCodable]:
return arr return arr
case let arr as [Any]: case let arr as [Any]:
return arr.map { AnyCodable($0) } return arr.map { ProtocolAnyCodable($0) }
default: default:
return [] return []
} }
} }
private func anyCodableEqual(_ lhs: AnyCodable?, _ rhs: AnyCodable?) -> Bool { private func anyCodableEqual(_ lhs: ProtocolAnyCodable?, _ rhs: ProtocolAnyCodable?) -> Bool {
switch (lhs?.value, rhs?.value) { switch (lhs?.value, rhs?.value) {
case let (l as String, r as String): case let (l as String, r as String):
return l == r return l == r

View File

@@ -289,10 +289,14 @@ final class LocationPermissionRequester: NSObject, CLLocationManagerDelegate {
} }
} }
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { // 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 } guard let cont = self.continuation else { return }
self.continuation = nil self.continuation = nil
cont.resume(returning: manager.authorizationStatus) cont.resume(returning: status)
}
} }
} }

View File

@@ -871,7 +871,7 @@ public struct WizardStep: Codable {
} }
} }
public struct WizardNextResult: Codable { public struct WizardNextResult: Codable, Sendable {
public let done: Bool public let done: Bool
public let step: [String: AnyCodable]? public let step: [String: AnyCodable]?
public let status: 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 sessionid: String
public let done: Bool public let done: Bool
public let step: [String: AnyCodable]? 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 status: AnyCodable
public let error: String? public let error: String?