5.7 KiB
tags, created, project
| tags | created | project | |||||
|---|---|---|---|---|---|---|---|
|
2026-06-02 | address-ios-onboarding-issues |
iOS Onboarding — Duck.AI Query Experiment Debug Overrides
Temporary overrides to force the onboarding flow to always reset and start at the duck.ai query entry/suggestion-selection screen. All changes are marked TODO: OVERRIDE - Remove before shipping.
Files changed
1. iOS/DuckDuckGo/MainViewController.swift — init (before super.init)
Resets all persisted onboarding flags on every launch so the linear onboarding always triggers from scratch.
// TODO: OVERRIDE - Remove before shipping.
tutorialSettings.hasSeenOnboarding = false
tutorialSettings.hasSkippedOnboarding = false
tutorialSettings.onboardingFlowType = nil // re-evaluates as .default (required for duck.ai query experiment)
let _debugDaxSettings = DefaultDaxDialogsSettings()
_debugDaxSettings.isDismissed = false
_debugDaxSettings.tryAnonymousSearchShown = false
_debugDaxSettings.tryVisitASiteShown = false
_debugDaxSettings.browsingAfterSearchShown = false
_debugDaxSettings.browsingWithTrackersShown = false
_debugDaxSettings.browsingWithoutTrackersShown = false
_debugDaxSettings.browsingMajorTrackingSiteShown = false
_debugDaxSettings.fireButtonEducationShownOrExpired = false
_debugDaxSettings.fireMessageExperimentShown = false
_debugDaxSettings.fireButtonPulseDateShown = nil
_debugDaxSettings.privacyButtonPulseShown = false
_debugDaxSettings.browsingFinalDialogShown = false
_debugDaxSettings.subscriptionPromotionDialogShown = false
_debugDaxSettings.chatPathVisitSiteSeen = false
_debugDaxSettings.isChatFirstPath = false
// Clear any resume checkpoint from a previous partial onboarding run.
// Uses UserDefaults.app.keyedStoring() (concrete type) — `any KeyedStoring` existential
// can't be passed directly in the pre-super.init phase.
OnboardingResumeCheckpointStore.clearAll(in: UserDefaults.app.keyedStoring())
Why onboardingFlowType = nil: The duck.ai query experiment only runs on the .default flow. If onboardingFlowType is left as .duckAI from a previous session the experiment step never inserts.
Why UserDefaults.app.keyedStoring() instead of onboardingResumeStepStore: Swift can't resolve any KeyedStoring existential subscripts in the pre-super.init phase. keyedStoring() returns some KeyedStoring (opaque concrete type) which works. Same backing store.
2. iOS/DuckDuckGo/OnboardingFlow/LinearOnboarding/OnboardingIntroViewModel.swift
a) init — jump directly to the duck.ai query selection step
Inserts .duckAIQuerySelection at the end of introSteps and sets it as currentIntroStep. When onAppear() fires it calls makeInitialViewState() which renders this step immediately, skipping all preceding intro screens.
Appending at the end is critical: after the user selects a query, makeNextViewState() finds no further step and calls completeOnboardingIntro(), which opens duck.ai as expected. Inserting at index 1 (near the start) would advance to the next step (e.g. browser comparison screen) instead.
// TODO: OVERRIDE - Remove before shipping.
if !introSteps.contains(.duckAIQuerySelection) {
introSteps.append(.duckAIQuerySelection)
}
currentIntroStep = .duckAIQuerySelection
b) resolveDuckAIQueryExperimentCohortID() — force Treatment A cohort
func resolveDuckAIQueryExperimentCohortID() -> FeatureFlag.DuckAIQueryExperimentCohort? {
// TODO: OVERRIDE - Remove before shipping.
return .treatmentA
// production code below (unreachable)
guard onboardingManager.currentOnboardingFlow == .default && featureFlagger.isFeatureOn(...) else { return nil }
return featureFlagger.resolveCohort(...)
}
c) insertExperimentStepIfNeeded() — relax isReturningUser guard
The production guard requires isReturningUser: true. Since we reset hasSeenOnboarding = false the device looks like a new user, so the guard would fail. Changed to match any introDialog step.
// Production (commented out):
// guard case .introDialog(isReturningUser: true) = introSteps.first,
// Override:
guard case .renderable(.introDialog(_)) = introSteps.first,
3. iOS/DuckDuckGo/MainViewController+DuckAIExperiment.swift — isDuckAIFireFlowEnabled
The production computed property returns false when the feature flag is off (which it is in debug builds) and onboardingFlowType is .default. This causes the entire fire-onboarding path (controls lock, fire dialog, completion dialog) to be skipped.
var isDuckAIFireFlowEnabled: Bool {
// TODO: OVERRIDE - Remove before shipping.
return true
// production:
// return featureFlagger.isFeatureOn(.onboardingDuckAIQueryTrackersDemoExperiment)
// || onboardingManager.currentOnboardingFlow == .duckAI
}
4. SharedPackages/Onboarding/Sources/Onboarding/OnboardingSuggestedSitesProvider.swift
Replaced default suggested site for testing.
// TODO: OVERRIDE - Remove before shipping.
default: site = "news.ru" // was "ESPN.com"
Removing overrides (checklist)
MainViewController.swiftinit — delete the 30-line flag-reset blockOnboardingIntroViewModel.swiftinit — delete the 4-line step-jump blockOnboardingIntroViewModel.swiftresolveDuckAIQueryExperimentCohortID— deletereturn .treatmentBand the swiftlint commentOnboardingIntroViewModel.swiftinsertExperimentStepIfNeeded— restorecase .introDialog(isReturningUser: true)guard, delete override commentMainViewController+DuckAIExperiment.swiftisDuckAIFireFlowEnabled— deletereturn trueand the swiftlint commentOnboardingSuggestedSitesProvider.swift— restoredefault: site = "ESPN.com"