132 lines
5.7 KiB
Markdown
132 lines
5.7 KiB
Markdown
---
|
|
tags:
|
|
- ios
|
|
- onboarding
|
|
- duck-ai
|
|
- override
|
|
- debug
|
|
created: '2026-06-02'
|
|
project: 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.
|
|
|
|
```swift
|
|
// 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.
|
|
|
|
```swift
|
|
// TODO: OVERRIDE - Remove before shipping.
|
|
if !introSteps.contains(.duckAIQuerySelection) {
|
|
introSteps.append(.duckAIQuerySelection)
|
|
}
|
|
currentIntroStep = .duckAIQuerySelection
|
|
```
|
|
|
|
#### b) `resolveDuckAIQueryExperimentCohortID()` — force Treatment A cohort
|
|
|
|
```swift
|
|
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.
|
|
|
|
```swift
|
|
// 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.
|
|
|
|
```swift
|
|
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.
|
|
|
|
```swift
|
|
// TODO: OVERRIDE - Remove before shipping.
|
|
default: site = "news.ru" // was "ESPN.com"
|
|
```
|
|
|
|
---
|
|
|
|
## Removing overrides (checklist)
|
|
|
|
- [ ] `MainViewController.swift` init — delete the 30-line flag-reset block
|
|
- [ ] `OnboardingIntroViewModel.swift` init — delete the 4-line step-jump block
|
|
- [ ] `OnboardingIntroViewModel.swift` `resolveDuckAIQueryExperimentCohortID` — delete `return .treatmentB` and the swiftlint comment
|
|
- [ ] `OnboardingIntroViewModel.swift` `insertExperimentStepIfNeeded` — restore `case .introDialog(isReturningUser: true)` guard, delete override comment
|
|
- [ ] `MainViewController+DuckAIExperiment.swift` `isDuckAIFireFlowEnabled` — delete `return true` and the swiftlint comment
|
|
- [ ] `OnboardingSuggestedSitesProvider.swift` — restore `default: site = "ESPN.com"`
|