From 5190a47f984b001ffdb75e599b37c1c640c58dc0 Mon Sep 17 00:00:00 2001 From: Aaron Levin Date: Sun, 15 Feb 2026 16:40:41 -0700 Subject: [PATCH] macOS: defer microphone/screen-recording permission asks to dashboard tasks Remove SpeechPermissionStepView (microphone) and ScreenPermissionStepView (screen recording) from the default onboarding flow so hatch + first conversation no longer include privileged permission requests. Accessibility permission is retained as it may be needed for basic functionality. The permission views are preserved for later reuse by dashboard tasks. Flow reduced from 8 steps (0-7 + interview) to 6 steps (0-5 + interview): 0: WakeUp 1: Naming 2: APIKey 3: FnKey 4: Accessibility 5: Alive Also fixes pre-existing merge conflict in DynamicPageSurfaceView.swift and argument ordering in SurfaceContainerView.swift. Co-Authored-By: Claude Opus 4.6 --- .../Onboarding/Hatch/EggSceneView.swift | 8 ++++---- .../Hatch/OnboardingStageImage.swift | 12 +++++------ .../Onboarding/OnboardingFlowView.swift | 12 ++++------- .../Onboarding/OnboardingProgressDots.swift | 4 ++-- .../Features/Onboarding/OnboardingState.swift | 20 +++++++++---------- .../Surfaces/DynamicPageSurfaceView.swift | 4 ---- .../Surfaces/SurfaceContainerView.swift | 4 ++-- 7 files changed, 28 insertions(+), 36 deletions(-) diff --git a/clients/macos/vellum-assistant/Features/Onboarding/Hatch/EggSceneView.swift b/clients/macos/vellum-assistant/Features/Onboarding/Hatch/EggSceneView.swift index c2f01e79eb8..2bd4c5d7be7 100644 --- a/clients/macos/vellum-assistant/Features/Onboarding/Hatch/EggSceneView.swift +++ b/clients/macos/vellum-assistant/Features/Onboarding/Hatch/EggSceneView.swift @@ -20,8 +20,8 @@ struct EggSceneView: View { if progress > 0 { scene.setCrackProgress(progress, animated: false) } - // Resume full hatch if restored at step 7 (Alive) - if state.currentStep == 7 { + // Resume full hatch if restored at step 5 (Alive) + if state.currentStep == 5 { scene.triggerFullHatch() } } @@ -29,9 +29,9 @@ struct EggSceneView: View { scene.setCrackProgress(newValue, animated: true) } .onChange(of: state.currentStep) { old, new in - if (4...6).contains(new) && new > old { + if (3...4).contains(new) && new > old { scene.triggerDramaticCrack(for: new) - } else if new == 7 { + } else if new == 5 { scene.triggerFullHatch() } } diff --git a/clients/macos/vellum-assistant/Features/Onboarding/Hatch/OnboardingStageImage.swift b/clients/macos/vellum-assistant/Features/Onboarding/Hatch/OnboardingStageImage.swift index eedb2a09a1a..f9579c968d5 100644 --- a/clients/macos/vellum-assistant/Features/Onboarding/Hatch/OnboardingStageImage.swift +++ b/clients/macos/vellum-assistant/Features/Onboarding/Hatch/OnboardingStageImage.swift @@ -14,14 +14,14 @@ struct OnboardingStageImage: View { @State private var displayedStage: Int = 1 @State private var isTransitioning = false - /// Maps onboarding step (0-7) to stage image number (1-5). + /// Maps onboarding step (0-5) to stage image number (1-5). private var stageNumber: Int { switch currentStep { - case 0, 1, 2: return 1 - case 3: return 2 - case 4, 5: return 3 - case 6: return 4 - default: return 5 + case 0, 1: return 1 + case 2: return 2 + case 3: return 3 + case 4: return 4 + default: return 5 } } diff --git a/clients/macos/vellum-assistant/Features/Onboarding/OnboardingFlowView.swift b/clients/macos/vellum-assistant/Features/Onboarding/OnboardingFlowView.swift index cb0ab5ce860..7d1935f645c 100644 --- a/clients/macos/vellum-assistant/Features/Onboarding/OnboardingFlowView.swift +++ b/clients/macos/vellum-assistant/Features/Onboarding/OnboardingFlowView.swift @@ -23,8 +23,8 @@ struct OnboardingFlowView: View { .opacity(0.25) .allowsHitTesting(false) - if state.currentStep <= 7 { - // Vertical card layout (steps 0-7) + if state.currentStep <= 5 { + // Vertical card layout (steps 0-5) VStack(spacing: 0) { // TOP: Meadow background + stage image ZStack { @@ -50,12 +50,8 @@ struct OnboardingFlowView: View { case 3: FnKeyStepView(state: state) case 4: - SpeechPermissionStepView(state: state) - case 5: AccessibilityPermissionStepView(state: state) - case 6: - ScreenPermissionStepView(state: state) - case 7: + case 5: AliveStepView( state: state, onComplete: onComplete, @@ -98,7 +94,7 @@ struct OnboardingFlowView: View { .shadow(color: .black.opacity(0.4), radius: 24, y: 12) .position(x: geometry.size.width / 2, y: geometry.size.height / 2) } else { - // Step 8: Interview — manages its own layout + // Step 6: Interview — manages its own layout InterviewStepView( state: state, daemonClient: daemonClient, diff --git a/clients/macos/vellum-assistant/Features/Onboarding/OnboardingProgressDots.swift b/clients/macos/vellum-assistant/Features/Onboarding/OnboardingProgressDots.swift index 48c042c36a1..585b157b673 100644 --- a/clients/macos/vellum-assistant/Features/Onboarding/OnboardingProgressDots.swift +++ b/clients/macos/vellum-assistant/Features/Onboarding/OnboardingProgressDots.swift @@ -3,7 +3,7 @@ import SwiftUI /// Five cumulative progress dots for the onboarding flow. /// -/// By default maps 8 steps (0-7) to 5 dots. Pass a custom `totalSteps` +/// By default maps 6 steps (0-5) to 5 dots. Pass a custom `totalSteps` /// for flows with a different number of steps (e.g. `totalSteps: 5` for /// the first-meeting flow). struct OnboardingProgressDots: View { @@ -12,7 +12,7 @@ struct OnboardingProgressDots: View { private let totalDots = 5 - init(currentStep: Int, totalSteps: Int = 8) { + init(currentStep: Int, totalSteps: Int = 6) { self.currentStep = currentStep self.totalSteps = totalSteps } diff --git a/clients/macos/vellum-assistant/Features/Onboarding/OnboardingState.swift b/clients/macos/vellum-assistant/Features/Onboarding/OnboardingState.swift index 2668e8866a1..6e0165b2007 100644 --- a/clients/macos/vellum-assistant/Features/Onboarding/OnboardingState.swift +++ b/clients/macos/vellum-assistant/Features/Onboarding/OnboardingState.swift @@ -42,8 +42,10 @@ final class OnboardingState { var observationDurationMinutes: Int = 5 var observationInsights: [String] = [] + /// Only checks permissions that are requested during onboarding. + /// Microphone and screen recording are deferred to dashboard tasks. var anyPermissionDenied: Bool { - !speechGranted || !accessibilityGranted || !screenGranted + !accessibilityGranted } /// Continuous crack progress (0.0–1.0) derived from step and permission state. @@ -55,12 +57,10 @@ final class OnboardingState { switch currentStep { case 0: return hasHatched ? 0.15 : 0.0 case 1: return 0.20 - case 2: return 0.30 - case 3: return 0.40 - case 4: return speechGranted ? 0.55 : 0.45 - case 5: return accessibilityGranted ? 0.75 : 0.60 - case 6: return screenGranted ? 0.95 : 0.80 - case 7: return 1.0 + case 2: return 0.40 + case 3: return 0.55 + case 4: return accessibilityGranted ? 0.80 : 0.65 + case 5: return 1.0 default: return 1.0 } } @@ -86,9 +86,9 @@ final class OnboardingState { firstMeetingCrackProgress = CGFloat(UserDefaults.standard.double(forKey: "onboarding.firstMeetingCrackProgress")) // Clamp restored step to the variant's maximum to prevent out-of-range - // rendering (e.g. a step saved from the 8-step default flow would be - // invalid for the 5-step first-meeting flow). - let maxStep = onboardingVariant == .firstMeeting ? 4 : 7 + // rendering (e.g. a step saved from a previous flow version would be + // invalid for the current flow). + let maxStep = onboardingVariant == .firstMeeting ? 4 : 5 if currentStep > maxStep { currentStep = maxStep } diff --git a/clients/macos/vellum-assistant/Features/Surfaces/DynamicPageSurfaceView.swift b/clients/macos/vellum-assistant/Features/Surfaces/DynamicPageSurfaceView.swift index ca6f53fa7b6..012b757be92 100644 --- a/clients/macos/vellum-assistant/Features/Surfaces/DynamicPageSurfaceView.swift +++ b/clients/macos/vellum-assistant/Features/Surfaces/DynamicPageSurfaceView.swift @@ -78,11 +78,7 @@ struct DynamicPageSurfaceView: NSViewRepresentable { } func makeCoordinator() -> Coordinator { -<<<<<<< HEAD - Coordinator(onAction: onAction, onDataRequest: onDataRequest, onPageChanged: onPageChanged, onSnapshotCaptured: onSnapshotCaptured, currentHTML: data.html, sandboxMode: sandboxMode) -======= Coordinator(onAction: onAction, onDataRequest: onDataRequest, onPageChanged: onPageChanged, onSnapshotCaptured: onSnapshotCaptured, onLinkOpen: onLinkOpen, currentHTML: data.html, sandboxMode: sandboxMode) ->>>>>>> 96cab3e0 (feat: add vellum.openLink JS bridge and coordinator handler) } func makeNSView(context: Context) -> WKWebView { diff --git a/clients/macos/vellum-assistant/Features/Surfaces/SurfaceContainerView.swift b/clients/macos/vellum-assistant/Features/Surfaces/SurfaceContainerView.swift index 38d7a63bb17..812cfd1987c 100644 --- a/clients/macos/vellum-assistant/Features/Surfaces/SurfaceContainerView.swift +++ b/clients/macos/vellum-assistant/Features/Surfaces/SurfaceContainerView.swift @@ -78,8 +78,8 @@ struct SurfaceContainerView: View { appId: viewModel.appId, onDataRequest: viewModel.onDataRequest, onCoordinatorReady: viewModel.onCoordinatorReady, - sandboxMode: viewModel.sandboxMode, - onLinkOpen: viewModel.onLinkOpen + onLinkOpen: viewModel.onLinkOpen, + sandboxMode: viewModel.sandboxMode ) .frame(maxWidth: .infinity, maxHeight: .infinity) case .fileUpload(let data):