Skip to content

iOS: Fix duck.ai "You've got this" completion dialog persistence and dismissal in UTI mode#5231

Open
mallexxx wants to merge 1 commit into
mainfrom
alex/ios-fix-duck-ai-eoj-dialog-persistence
Open

iOS: Fix duck.ai "You've got this" completion dialog persistence and dismissal in UTI mode#5231
mallexxx wants to merge 1 commit into
mainfrom
alex/ios-fix-duck-ai-eoj-dialog-persistence

Conversation

@mallexxx
Copy link
Copy Markdown
Contributor

@mallexxx mallexxx commented Jun 2, 2026

Task/Issue URL: https://app.asana.com/1/137249556945/project/1202406491309510/task/1215009192158950
Tech Design URL: N/A
CC: N/A

Description

  • Persist the "You've got this!" completion dialog when the UTI address bar activates or deactivates

Testing Steps

Prerequisites

  1. In iOS/Core/FeatureFlag.swift change .unifiedToggleInput from:
    Config(source: .remoteReleasable(AIChatSubfeature.unifiedToggleInput))
    to:
    Config(defaultValue: .enabled, source: .remoteReleasable(AIChatSubfeature.unifiedToggleInput))
  2. In iOS/DuckDuckGo/OnboardingFlow/LinearOnboarding/OnboardingIntroViewModel.swift in resolveDuckAIQueryExperimentCohortID() add return .treatmentB as the first line to force Treatment B enrollment
  3. Reset iOS simulator (DeviceErase all content and settings…)
  4. In the iOS scheme set App LanguageEnglish and App RegionUnited States

Reaching the completion dialog
5. Fresh install. Go through onboarding to the "Search experience" screen.
6. Toggle to "Search + Duck.ai" → "Next".
7. Select Duck.ai and choose a suggested query option.
8. Complete the onboarding until the "You've got this!" dialog appears with the address bar active.

Address bar activate/deactivate
9. While the dialog is visible, activate and deactivate the address bar multiple times — confirm the dialog remains visible throughout

High-five → Subscription upsell
10. Tap "High five" — confirm the dialog dismisses and the subscription upsell appears

Navigate away while address bar is active → upsell on next new tab
11. Reach the dialog again (steps 5–8), activate the address bar, type a URL and navigate to a page — confirm the dialog is dismissed, then open a new tab and verify the subscription upsell appears

Burn all → upsell on next new tab
12. Reach the dialog again, trigger "Burn all" — confirm the dialog is dismissed and the subscription upsell appears on the next new tab

Open duck.ai → upsell on next new tab
13. Reach the dialog again, open the duck.ai chat — confirm the dialog is dismissed and the subscription upsell appears on the next new tab

Impact and Risks

Impact Level: Low

What could go wrong?

  • dismissDuckAICompletionDialogIfNeededOnEditingEnd is now also called from viewWillDisappear. It is idempotent (guarded by isShowingDuckAICompletionDialog), so double-calls from overlapping triggers are safe.
  • setUnifiedInputContentOverlaySuppressed(true) is now called during UTI completion dialog setup. dismissHostingController already calls setUnifiedInputContentOverlaySuppressed(false) on all teardown paths, so the suppression state is always restored.

Quality Considerations

  • All dismissal paths now consistently call setFinalOnboardingDialogSeen() before checking subscriptionPromotionPending, ensuring the subscription promo state machine behaves the same regardless of how the completion dialog exits.

Made with Cursor


Note

Low Risk
Onboarding-only UI in NewTabPageViewController; dismissal is guarded and overlay suppression is cleared on existing teardown paths.

Overview
Fixes the Duck.ai "You've got this!" completion dialog in Unified Toggle Input (UTI) mode so it stays visible while the address bar opens and closes, and so leaving the new tab page still advances onboarding correctly.

Presentation: UTI completion now matches other Dax overlays: it calls setUnifiedInputContentOverlaySuppressed(true) so the NTP shows through while the bar is active, and hosts a single SwiftUI overlay on the plain contentContainer ancestor (full NTP bounds) instead of inside unifiedInputContentContainer, avoiding nested UIHostingController warnings and the old bar-relative layout.

Lifecycle: viewWillDisappear invokes dismissDuckAICompletionDialogIfNeededOnEditingEnd() so tab switches and navigation tear down the dialog and mark end-of-journey seen. setFinalOnboardingDialogSeen() runs before reading subscriptionPromotionPending in that dismiss path and in notifyDuckAICompletionDismissedIfNeeded(), so the subscription upsell can appear on the next NTP whether the user taps through or navigates away.

Reviewed by Cursor Bugbot for commit 3b454d6. Bugbot is set up for automated code reviews on this repo. Configure here.

…dismissal in UTI mode

Co-authored-by: Cursor <cursoragent@cursor.com>
@mallexxx mallexxx requested a review from alessandroboron June 2, 2026 14:24
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 3b454d6. Configure here.

}
// Dismiss the UTI completion dialog when the NTP leaves the screen (tab switch,
// navigation). The dialog is marked as seen so the subscription promo surfaces cleanly.
dismissDuckAICompletionDialogIfNeededOnEditingEnd()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Safety-net dismissal in viewWillDisappear is dead code

Medium Severity

The new dismissDuckAICompletionDialogIfNeededOnEditingEnd() call at line 159 can never independently fire in viewWillDisappear. The UTI completion dialog's hosting controller is parented to mainVC (line 602), so the pre-existing check at line 154 (hc.parent !== self) is always true and dismissHostingController(didFinishNTPOnboarding: false) runs first, setting isShowingDuckAICompletionDialog = false. The guard inside dismissDuckAICompletionDialogIfNeededOnEditingEnd then always returns early. This means setFinalOnboardingDialogSeen() is never called through this path — the intended safety net for "tab switch, navigation" cleanup doesn't work. In practice, external triggers (onEditingEnd, dismiss()) always precede viewWillDisappear, masking the issue, but the defensive fallback is silently broken.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3b454d6. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant