fix: macOS Swift cleanup

This commit is contained in:
Peter Steinberger
2026-01-04 17:57:16 +01:00
parent 0928e3c866
commit 5eb6b779f5
7 changed files with 81 additions and 48 deletions

View File

@@ -9,7 +9,7 @@ import SwiftUI
final class AppState {
private let isPreview: Bool
private var isInitializing = true
private nonisolated var configWatcher: ConfigFileWatcher?
private var configWatcher: ConfigFileWatcher?
private var suppressVoiceWakeGlobalSync = false
private var voiceWakeGlobalSyncTask: Task<Void, Never>?
@@ -321,6 +321,7 @@ final class AppState {
}
}
@MainActor
deinit {
self.configWatcher?.stop()
}

View File

@@ -11,9 +11,6 @@ final class MacNodeLocationService: NSObject, CLLocationManagerDelegate {
private let manager = CLLocationManager()
private var locationContinuation: CheckedContinuation<CLLocation, Swift.Error>?
private struct UncheckedSendable<T>: @unchecked Sendable {
let value: T
}
override init() {
super.init()
@@ -63,7 +60,7 @@ final class MacNodeLocationService: NSObject, CLLocationManagerDelegate {
}
}
private func withTimeout<T>(
private func withTimeout<T: Sendable>(
timeoutMs: Int,
operation: @escaping () async throws -> T) async throws -> T
{
@@ -71,15 +68,38 @@ final class MacNodeLocationService: NSObject, CLLocationManagerDelegate {
return try await operation()
}
return try await withThrowingTaskGroup(of: UncheckedSendable<T>.self) { group in
group.addTask { try await UncheckedSendable(value: operation()) }
group.addTask {
try await Task.sleep(nanoseconds: UInt64(timeoutMs) * 1_000_000)
throw Error.timeout
return try await withCheckedThrowingContinuation { continuation in
var didFinish = false
func finish(returning value: T) {
guard !didFinish else { return }
didFinish = true
continuation.resume(returning: value)
}
func finish(throwing error: Swift.Error) {
guard !didFinish else { return }
didFinish = true
continuation.resume(throwing: error)
}
let timeoutItem = DispatchWorkItem {
finish(throwing: Error.timeout)
}
DispatchQueue.main.asyncAfter(
deadline: .now() + .milliseconds(timeoutMs),
execute: timeoutItem)
Task { @MainActor in
do {
let value = try await operation()
timeoutItem.cancel()
finish(returning: value)
} catch {
timeoutItem.cancel()
finish(throwing: error)
}
}
let result = try await group.next()!
group.cancelAll()
return result.value
}
}

View File

@@ -86,7 +86,7 @@ extension OnboardingView {
var navigationBar: some View {
let wizardLockIndex = self.wizardPageOrderIndex
HStack(spacing: 20) {
return HStack(spacing: 20) {
ZStack(alignment: .leading) {
Button(action: {}, label: {
Label("Back", systemImage: "chevron.left").labelStyle(.iconOnly)

View File

@@ -1,3 +1,4 @@
import ClawdbotProtocol
import Observation
import SwiftUI

View File

@@ -232,44 +232,52 @@ struct OnboardingWizardStepView: View {
private var selectOptions: some View {
VStack(alignment: .leading, spacing: 8) {
ForEach(self.optionItems) { item in
Button {
self.selectedIndex = item.index
} label: {
HStack(alignment: .top, spacing: 8) {
Image(systemName: self.selectedIndex == item.index ? "largecircle.fill.circle" : "circle")
.foregroundStyle(.accent)
VStack(alignment: .leading, spacing: 2) {
Text(item.option.label)
.foregroundStyle(.primary)
if let hint = item.option.hint, !hint.isEmpty {
Text(hint)
.font(.caption)
.foregroundStyle(.secondary)
}
}
}
}
.buttonStyle(.plain)
ForEach(self.optionItems, id: \.index) { item in
self.selectOptionRow(item)
}
}
}
private var multiselectOptions: some View {
VStack(alignment: .leading, spacing: 8) {
ForEach(self.optionItems) { item in
Toggle(isOn: self.bindingForOption(item)) {
VStack(alignment: .leading, spacing: 2) {
Text(item.option.label)
if let hint = item.option.hint, !hint.isEmpty {
Text(hint)
.font(.caption)
.foregroundStyle(.secondary)
}
ForEach(self.optionItems, id: \.index) { item in
self.multiselectOptionRow(item)
}
}
}
private func selectOptionRow(_ item: WizardOptionItem) -> some View {
Button {
self.selectedIndex = item.index
} label: {
HStack(alignment: .top, spacing: 8) {
Image(systemName: self.selectedIndex == item.index ? "largecircle.fill.circle" : "circle")
.foregroundStyle(Color.accentColor)
VStack(alignment: .leading, spacing: 2) {
Text(item.option.label)
.foregroundStyle(.primary)
if let hint = item.option.hint, !hint.isEmpty {
Text(hint)
.font(.caption)
.foregroundStyle(.secondary)
}
}
}
}
.buttonStyle(.plain)
}
private func multiselectOptionRow(_ item: WizardOptionItem) -> some View {
Toggle(isOn: self.bindingForOption(item)) {
VStack(alignment: .leading, spacing: 2) {
Text(item.option.label)
if let hint = item.option.hint, !hint.isEmpty {
Text(hint)
.font(.caption)
.foregroundStyle(.secondary)
}
}
}
}
private func bindingForOption(_ item: WizardOptionItem) -> Binding<Bool> {

View File

@@ -588,7 +588,9 @@ actor TalkModeRuntime {
let stream = client.streamSynthesize(voiceId: voiceId, request: request)
guard self.isCurrent(input.generation) else { return }
if self.interruptOnSpeech, ! await self.prepareForPlayback(generation: input.generation) { return }
if self.interruptOnSpeech {
guard await self.prepareForPlayback(generation: input.generation) else { return }
}
await MainActor.run { TalkModeController.shared.updatePhase(.speaking) }
self.phase = .speaking
@@ -643,7 +645,9 @@ actor TalkModeRuntime {
private func playSystemVoice(input: TalkPlaybackInput) async throws {
self.ttsLogger.info("talk system voice start chars=\(input.cleanedText.count, privacy: .public)")
if self.interruptOnSpeech, ! await self.prepareForPlayback(generation: input.generation) { return }
if self.interruptOnSpeech {
guard await self.prepareForPlayback(generation: input.generation) else { return }
}
await MainActor.run { TalkModeController.shared.updatePhase(.speaking) }
self.phase = .speaking
await TalkSystemSpeechSynthesizer.shared.stop()
@@ -727,7 +731,6 @@ actor TalkModeRuntime {
}
extension TalkModeRuntime {
// MARK: - Audio playback (MainActor helpers)
@MainActor

View File

@@ -875,7 +875,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?
@@ -900,7 +900,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]?
@@ -929,7 +929,7 @@ public struct WizardStartResult: Codable {
}
}
public struct WizardStatusResult: Codable {
public struct WizardStatusResult: Codable, Sendable {
public let status: AnyCodable
public let error: String?