From 0af89022ff0976e8dfc910da7067c347e8e03e2f Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 3 Jan 2026 17:59:37 +0000 Subject: [PATCH] fix: avoid Swift compiler crash in onboarding wizard --- .../Clawdis/OnboardingView+Wizard.swift | 105 ++++++++++++------ .../Sources/Clawdis/OnboardingWizard.swift | 36 +++--- 2 files changed, 87 insertions(+), 54 deletions(-) diff --git a/apps/macos/Sources/Clawdis/OnboardingView+Wizard.swift b/apps/macos/Sources/Clawdis/OnboardingView+Wizard.swift index 477f033d5..a43cb74c6 100644 --- a/apps/macos/Sources/Clawdis/OnboardingView+Wizard.swift +++ b/apps/macos/Sources/Clawdis/OnboardingView+Wizard.swift @@ -1,3 +1,4 @@ +import Observation import SwiftUI extension OnboardingView { @@ -13,43 +14,10 @@ extension OnboardingView { .frame(maxWidth: 520) self.onboardingCard(spacing: 14, padding: 16) { - if let error = self.onboardingWizard.errorMessage { - Text("Wizard error") - .font(.headline) - Text(error) - .font(.subheadline) - .foregroundStyle(.secondary) - .fixedSize(horizontal: false, vertical: true) - Button("Retry") { - self.onboardingWizard.reset() - Task { - await self.onboardingWizard.startIfNeeded( - mode: self.state.connectionMode, - workspace: self.workspacePath.isEmpty ? nil : self.workspacePath) - } - } - .buttonStyle(.borderedProminent) - } else if self.onboardingWizard.isStarting { - HStack(spacing: 8) { - ProgressView() - Text("Starting wizard…") - .foregroundStyle(.secondary) - } - } else if let step = self.onboardingWizard.currentStep { - OnboardingWizardStepView( - step: step, - isSubmitting: self.onboardingWizard.isSubmitting) - { value in - Task { await self.onboardingWizard.submit(step: step, value: value) } - } - .id(step.id) - } else if self.onboardingWizard.isComplete { - Text("Wizard complete. Continue to the next step.") - .font(.headline) - } else { - Text("Waiting for wizard…") - .foregroundStyle(.secondary) - } + OnboardingWizardCardContent( + wizard: self.onboardingWizard, + mode: self.state.connectionMode, + workspacePath: self.workspacePath) } } .task { @@ -60,3 +28,66 @@ extension OnboardingView { } } } + +private struct OnboardingWizardCardContent: View { + @Bindable var wizard: OnboardingWizardModel + let mode: AppState.ConnectionMode + let workspacePath: String + + private enum CardState { + case error(String) + case starting + case step(WizardStep) + case complete + case waiting + } + + private var state: CardState { + if let error = wizard.errorMessage { return .error(error) } + if wizard.isStarting { return .starting } + if let step = wizard.currentStep { return .step(step) } + if wizard.isComplete { return .complete } + return .waiting + } + + var body: some View { + switch state { + case .error(let error): + Text("Wizard error") + .font(.headline) + Text(error) + .font(.subheadline) + .foregroundStyle(.secondary) + .fixedSize(horizontal: false, vertical: true) + Button("Retry") { + wizard.reset() + Task { + await wizard.startIfNeeded( + mode: mode, + workspace: workspacePath.isEmpty ? nil : workspacePath) + } + } + .buttonStyle(.borderedProminent) + case .starting: + HStack(spacing: 8) { + ProgressView() + Text("Starting wizard…") + .foregroundStyle(.secondary) + } + case .step(let step): + OnboardingWizardStepView( + step: step, + isSubmitting: wizard.isSubmitting) + { value in + Task { await wizard.submit(step: step, value: value) } + } + .id(step.id) + case .complete: + Text("Wizard complete. Continue to the next step.") + .font(.headline) + case .waiting: + Text("Waiting for wizard…") + .foregroundStyle(.secondary) + } + } +} diff --git a/apps/macos/Sources/Clawdis/OnboardingWizard.swift b/apps/macos/Sources/Clawdis/OnboardingWizard.swift index 4ac805619..ca0fb2b74 100644 --- a/apps/macos/Sources/Clawdis/OnboardingWizard.swift +++ b/apps/macos/Sources/Clawdis/OnboardingWizard.swift @@ -196,20 +196,18 @@ struct OnboardingWizardStepView: View { .frame(maxWidth: .infinity, alignment: .leading) } + @ViewBuilder private var textField: some View { let isSensitive = step.sensitive == true if isSensitive { - return AnyView( - SecureField(step.placeholder ?? "", text: $textValue) - .textFieldStyle(.roundedBorder) - .frame(maxWidth: 360) - ) - } - return AnyView( + SecureField(step.placeholder ?? "", text: $textValue) + .textFieldStyle(.roundedBorder) + .frame(maxWidth: 360) + } else { TextField(step.placeholder ?? "", text: $textValue) .textFieldStyle(.roundedBorder) .frame(maxWidth: 360) - ) + } } private var selectOptions: some View { @@ -240,15 +238,7 @@ struct OnboardingWizardStepView: View { private var multiselectOptions: some View { VStack(alignment: .leading, spacing: 8) { ForEach(optionItems) { item in - Toggle(isOn: Binding(get: { - selectedIndices.contains(item.index) - }, set: { newValue in - if newValue { - selectedIndices.insert(item.index) - } else { - selectedIndices.remove(item.index) - } - })) { + Toggle(isOn: bindingForOption(item)) { VStack(alignment: .leading, spacing: 2) { Text(item.option.label) if let hint = item.option.hint, !hint.isEmpty { @@ -262,6 +252,18 @@ struct OnboardingWizardStepView: View { } } + private func bindingForOption(_ item: WizardOptionItem) -> Binding { + Binding(get: { + selectedIndices.contains(item.index) + }, set: { newValue in + if newValue { + selectedIndices.insert(item.index) + } else { + selectedIndices.remove(item.index) + } + }) + } + private var isBlocked: Bool { let type = wizardStepType(step) if type == "select" { return optionItems.isEmpty }