fix: avoid Swift compiler crash in onboarding wizard

This commit is contained in:
Peter Steinberger
2026-01-03 17:59:37 +00:00
parent 27a8f3d061
commit 0af89022ff
2 changed files with 87 additions and 54 deletions

View File

@@ -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)
}
}
}

View File

@@ -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<Bool> {
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 }