diff --git a/work/projects/ios-subscription-day7-promo-pr.md b/work/projects/ios-subscription-day7-promo-pr.md index 92869a5d..a50cdd3b 100644 --- a/work/projects/ios-subscription-day7-promo-pr.md +++ b/work/projects/ios-subscription-day7-promo-pr.md @@ -17,132 +17,163 @@ CC: N/A **Prerequisites** - Build from `alex/subscription-day7-promo`. - Be an Internal User (add `|| DEBUG` to the `#if` check in `InternalUserStore.swift`). +- Enable both flags in the Internal Settings debug menu: `subscriptionPromoForExistingUsers` and `privacyProOnboardingPromotion`. - The cooldown is currently overridden to **1 minute** from install date for easier manual testing (production will require 7 days). -- "Stop app" = force-quit via Xcode Stop button or app switcher. "Hide app" = home button / swipe up to background without quitting. +- **"Stop app"** = force-quit via Xcode Stop button or app switcher swipe-up. +- **"Hide app"** = press home button / swipe up to background without quitting. +- **Day-7 promo sheet** = the "Did you know?" half-sheet (title: *"Did you know?"*, CTA: *"Try it free!"* or *"Learn More"*, dismiss: *"No thanks"*, close: ✕ icon top-right). --- #### A — Linear onboarding "Search only" path -1. **Stop at SERP Dax dialog; wait ≥1 min; cold launch** - Complete linear onboarding ("Search only" → "Continue" through all steps) → first search triggers SERP Dax dialog → force-quit app → wait ≥1 min → cold launch - → **Promo sheet should appear** (single-tab state: `subscriptionPromotionPending = false`, `hasSeenOnboarding = false`, cooldown passed) +To reach this state: complete linear onboarding → on the *"Want easy access to private AI chat in the address bar?"* screen select **"Search only"** → tap **"Next"** → complete remaining steps. -2. **Stop at SERP Dax dialog; hide app; reopen (foreground)** - Same setup → background app instead of quitting → switch back to DuckDuckGo - → **No promo** (not a cold launch) +Contextual onboarding then starts on the browser. The *"Ready to get started? Try a search!"* dialog appears with search suggestion chips. -3. **Tap "Got It" on SERP Dax dialog; stop app; wait ≥1 min; cold launch** - Complete linear onboarding "Search only" → first search → SERP Dax dialog → tap "Got It" → force-quit → wait ≥1 min → cold launch - → **Promo sheet should appear** (fire tutorial not yet done; `subscriptionPromotionPending = false`) +1. **"Try a Search" dialog visible; stop app; wait ≥1 min; cold launch** + On the *"Ready to get started? Try a search!"* dialog, do not tap any chip → force-quit → wait ≥1 min → cold launch + → **Day-7 promo sheet appears** (`subscriptionPromotionPending = false`, `hasSeenOnboarding = false`, cooldown passed) -4. **Tap "Got It" on SERP dialog; fire icon tutorial shows; stop app; wait ≥1 min; cold launch** - Same as above → do NOT press the fire button → force-quit → cold launch - → **Promo sheet should appear** (not yet in EOJ flow) +2. **"Try a Search" dialog visible; hide app; reopen (foreground)** + Same as above → press home button instead of quitting → switch back to DuckDuckGo + → **No promo sheet** (foregrounding, not a cold launch) -5. **Complete fire button step; contextual subscription promo is pending; stop app; cold launch** - Complete linear "Search only" → SERP dialog → "Got It" → fire tutorial → press fire button → contextual EOJ subscription promo dialog appears → force-quit without interacting → wait ≥1 min → cold launch - → **No promo** (`subscriptionPromotionPending = true`; launch promo blocked to avoid clash with contextual flow) +3. **Select a search chip; "That's DuckDuckGo Search!" appears; tap "Got it!"; "Try Visiting a Site" appears; stop app; cold launch** + On *"Ready to get started? Try a search!"* select a search suggestion chip → SERP loads → *"That's DuckDuckGo Search!"* dialog → tap **"Got it!"** → *"Next, try visiting a site!"* dialog appears → force-quit → wait ≥1 min → cold launch + → **Day-7 promo sheet appears** (fire tutorial not yet done; `subscriptionPromotionPending = false`) -6. **Complete contextual subscription promo via CTA; stop app; cold launch** - Same as #5 but fully complete the contextual subscription promo (tap "Try it Free" / "Learn More") → force-quit → cold launch - → **Promo sheet should appear** (`hasSeenOnboarding = true`; launch promo has not been shown yet) +4. **"Try Visiting a Site" dialog visible; hide app; reopen (foreground)** + Same as above (up to *"Next, try visiting a site!"* dialog visible) → press home button → switch back to DuckDuckGo + → **No promo sheet** (foregrounding, not a cold launch) -7. **Dismiss contextual subscription promo via X; stop app; cold launch** - Same as #5 but dismiss via X button on the contextual subscription promo → force-quit → cold launch - → **Promo sheet should appear** (`hasSeenOnboarding = true`) +5. **"Try Visiting a Site" dialog visible; tap X; stop app; cold launch** + On *"Next, try visiting a site!"* tap the **X** (skip/close) → confirm skip if prompted → force-quit → wait ≥1 min → cold launch + → **Day-7 promo sheet appears** (fire tutorial not started; `subscriptionPromotionPending = false`) + +6. **Complete "Try Visiting a Site"; fire button tutorial shows; stop app; cold launch** + Visit a site from the *"Try Visiting a Site"* step → tracker dialog → **"Got it!"** → fire button is highlighted by tutorial → force-quit without tapping fire → wait ≥1 min → cold launch + → **Day-7 promo sheet appears** (not yet in EOJ flow; `subscriptionPromotionPending = false`) + +7. **Press 🔥 fire button; contextual subscription promo "Oh, before I forget..." appears; stop app; cold launch** + Follow fire tutorial → tap the 🔥 **Fire button** → fire confirmation → contextual subscription promo *"Oh, before I forget..."* appears → force-quit without interacting → wait ≥1 min → cold launch + → **No promo sheet** (`subscriptionPromotionPending = true`; launch promo blocked to avoid clash with contextual flow) + +8. **"Oh, before I forget..." contextual promo visible; hide app; reopen (foreground)** + Same as above (contextual subscription promo visible on screen) → press home button → switch back to DuckDuckGo + → **No promo sheet** (foregrounding; also `subscriptionPromotionPending = true`) + +9. **Tap "Try it free!" / "Learn More" on "Oh, before I forget..."; stop app; cold launch** + On the *"Oh, before I forget..."* contextual promo, tap **"Try it free!"** (or **"Learn More"**) → force-quit → wait ≥1 min → cold launch + → **Day-7 promo sheet appears** (`hasSeenOnboarding = true`; launch promo not yet shown) + +10. **Tap "No thanks" on "Oh, before I forget..."; stop app; cold launch** + On the *"Oh, before I forget..."* contextual promo, tap **"No thanks"** → force-quit → wait ≥1 min → cold launch + → **Day-7 promo sheet appears** (`hasSeenOnboarding = true`) --- #### B — Linear onboarding "Search & Duck.ai" path — search suggestion variant -8. **Select search suggestion on SERP; tap X on SERP Dax dialog; stop app; cold launch** - Complete linear onboarding "Search & Duck.ai" → first search → select a search suggestion → SERP Dax dialog appears → tap X → force-quit → wait ≥1 min → cold launch - → **Promo sheet should appear** (before fire step; `subscriptionPromotionPending = false`) +To reach this state: complete linear onboarding → on the *"Want easy access to private AI chat in the address bar?"* screen select **"Toggle between Search and Duck.ai"** → tap **"Next"** → complete remaining steps. -9. **Select search suggestion; complete SERP dialog + duck.ai dialog; fire pending; stop app; cold launch** - Complete linear "Search & Duck.ai" → SERP suggestion → "Got It" on SERP dialog → duck.ai suggestion dialog → continue → fire tutorial → press fire → contextual EOJ subscription promo appears → force-quit → cold launch - → **No promo** (`subscriptionPromotionPending = true`) +Contextual onboarding starts with *"Ready to get started? Try a search or AI chat!"* dialog showing both search and Duck.ai suggestion chips. -10. **Select search suggestion; complete full contextual onboarding including EOJ promo; stop app; cold launch** - Same as #9 but fully complete EOJ subscription promo (CTA or X) → force-quit → cold launch - → **Promo sheet should appear** (`hasSeenOnboarding = true`) +11. **Select a search chip; "Try Visiting a Site" appears; stop app; cold launch** + On *"Ready to get started? Try a search or AI chat!"* select a **search suggestion chip** → SERP loads → *"That's DuckDuckGo Search!"* → *"Next, try visiting a site!"* dialog → force-quit → wait ≥1 min → cold launch + → **Day-7 promo sheet appears** (before fire step; `subscriptionPromotionPending = false`) + +12. **Select a search chip; complete contextual onboarding to fire step; contextual promo pending; stop app; cold launch** + Select a search chip → complete *"Try Visiting a Site"* → fire tutorial → tap 🔥 Fire button → *"Oh, before I forget..."* appears → force-quit → cold launch + → **No promo sheet** (`subscriptionPromotionPending = true`) + +13. **Select a search chip; complete full contextual onboarding including EOJ promo; stop app; cold launch** + Same as #12 but fully complete the contextual subscription promo (tap **"Try it free!"**, **"Learn More"**, or **"No thanks"**) → force-quit → cold launch + → **Day-7 promo sheet appears** (`hasSeenOnboarding = true`) --- #### C — Linear onboarding "Search & Duck.ai" path — AI suggestion variant -11. **Select AI suggestion on SERP; tap X on duck.ai dialog; stop app; cold launch** - Complete linear "Search & Duck.ai" → first search → select AI/duck.ai suggestion → duck.ai dialog → tap X → force-quit → wait ≥1 min → cold launch - → **Promo sheet should appear** (before fire step) +14. **Select a Duck.ai chip; duck.ai dialog appears; tap X; stop app; cold launch** + On *"Ready to get started? Try a search or AI chat!"* select a **Duck.ai suggestion chip** → Duck.ai opens → contextual duck.ai dialog appears → tap **X** → force-quit → wait ≥1 min → cold launch + → **Day-7 promo sheet appears** (before fire step; `subscriptionPromotionPending = false`) -12. **Select AI suggestion; complete duck.ai dialog; fire pending; stop app; cold launch** - Complete linear "Search & Duck.ai" → AI suggestion → duck.ai contextual dialog → "Got It" → fire tutorial → press fire → EOJ subscription promo appears → force-quit → cold launch - → **No promo** (`subscriptionPromotionPending = true`) +15. **Duck.ai contextual dialog visible; hide app; reopen (foreground)** + Same as above (duck.ai contextual dialog visible) → press home button → switch back to DuckDuckGo + → **No promo sheet** (foregrounding, not a cold launch) -13. **Select AI suggestion; complete full contextual onboarding; stop app; cold launch** - Same as #12 but fully complete EOJ subscription promo → force-quit → cold launch - → **Promo sheet should appear** +16. **Select a Duck.ai chip; complete duck.ai dialogs; fire pending; stop app; cold launch** + Select Duck.ai chip → complete duck.ai contextual dialogs ("Got it!") → fire tutorial → tap 🔥 Fire button → *"Oh, before I forget..."* appears → force-quit → cold launch + → **No promo sheet** (`subscriptionPromotionPending = true`) + +17. **Select a Duck.ai chip; complete full contextual onboarding; stop app; cold launch** + Same as #16 but fully complete the contextual subscription promo → force-quit → cold launch + → **Day-7 promo sheet appears** (`hasSeenOnboarding = true`) --- -#### D — Promo interaction flows (once promo appears) +#### D — Day-7 promo interaction flows (once promo appears) -14. **Tap CTA on day-7 promo; verify destination; relaunch** - Trigger promo (any flow above) → tap "Try it Free" (or "Learn More" if no free trial) → subscription purchase/info page opens → force-quit → cold launch - → Subscription page opens with `funnel_modal_ios__subscriptionnudge` origin; **promo does NOT appear again** on next launch +18. **Tap "Try it free!" / "Learn More" CTA; verify destination; relaunch** + Trigger the day-7 promo (any flow above) → on the *"Did you know?"* sheet tap **"Try it free!"** (or **"Learn More"**) → Privacy Pro subscription page opens → force-quit → cold launch + → Subscription page opened with `funnel_modal_ios__subscriptionnudge` origin; **no promo sheet on next launch** -15. **Dismiss day-7 promo via swipe-down / X; relaunch** - Trigger promo → swipe down or tap X → force-quit → cold launch - → **No promo** (already shown) +19. **Tap "No thanks" (dismiss button); relaunch** + Trigger promo → tap **"No thanks"** → force-quit → cold launch + → **No promo sheet** (already shown) -16. **Background app while promo is showing; reopen** - Trigger promo → background app (don't dismiss) → reopen - → Promo should still be visible (or gracefully dismissed); **no second promo on next cold launch** +20. **Tap ✕ (close icon); relaunch** + Trigger promo → tap the **✕ icon** (top-right) → force-quit → cold launch + → **No promo sheet** (already shown) + +21. **Hide app while promo is visible; reopen; then cold launch** + Trigger promo → press home button without dismissing → switch back to DuckDuckGo → force-quit → cold launch + → Promo was visible on reopen; **no promo sheet on next cold launch** (already shown) --- #### E — Edge cases / negative flows -17. **Cooldown not passed: immediate cold launch after install** - Fresh install → enable flags → complete linear + contextual onboarding immediately → cold launch (< 1 min) - → **No promo** (cooldown not met) +22. **Cooldown not passed: launch immediately after install** + Fresh install → enable flags → complete linear + contextual onboarding → cold launch immediately (< 1 min since install) + → **No promo sheet** (cooldown not met) -18. **`subscriptionPromoForExistingUsers` flag disabled** - Disable `subscriptionPromoForExistingUsers` in debug menu → complete onboarding → wait ≥1 min → cold launch - → **No promo** +23. **`subscriptionPromoForExistingUsers` flag disabled** + Disable `subscriptionPromoForExistingUsers` flag → complete onboarding → wait ≥1 min → cold launch + → **No promo sheet** -19. **`privacyProOnboardingPromotion` flag disabled** +24. **`privacyProOnboardingPromotion` flag disabled** Disable `privacyProOnboardingPromotion` → same setup - → **No promo** + → **No promo sheet** -20. **WinBack-eligible user does not see day-7 promo** - Set up a user who qualifies for the WinBack offer AND the day-7 promo → cold launch - → **WinBack offer appears** (higher priority); day-7 promo stays pending for future launch +25. **WinBack-eligible user: WinBack promo takes priority** + Set up a user qualifying for both WinBack offer and day-7 promo → cold launch + → **WinBack offer sheet appears** (higher priority); day-7 promo stays pending for the next launch after WinBack is dismissed -21. **Reinstaller promo eligible user does not see day-7 promo** - User who skipped linear onboarding (`isDismissed = true`) + reinstaller flag enabled → cold launch - → **Reinstaller promo appears** (higher priority) +26. **Reinstaller-eligible user: reinstaller promo takes priority** + User who skipped linear onboarding (tap X → "Start Browsing") + `subscriptionPromoForReinstallers` enabled → cold launch + → **Reinstaller promo sheet appears** (higher priority) --- -#### F — Other modal prompts not affected by per-provider gate refactor +#### F — Other modal prompts not regressed by per-provider gate refactor -22. **Default Browser prompt still respects onboarding gate** - User who has NOT completed contextual onboarding (`hasSeenOnboarding = false`); no subscription promo pending → cold launch - → **No Default Browser prompt** (default `isEligibleToPresent` requires onboarding complete) - *(Validates per-provider gate applies to other providers correctly)* +27. **Default Browser prompt does not appear before onboarding is complete** + User who has NOT completed contextual onboarding (`hasSeenOnboarding = false`, `subscriptionPromotionPending = false`, `subscriptionPromoForExistingUsers` disabled to prevent that promo) → cold launch + → **No Default Browser prompt** (default `isEligibleToPresent` still requires onboarding complete) + *(Validates the per-provider gate refactor hasn't changed behaviour for other providers)* --- #### G — Upgrade scenario -23. **Upgrade from pre-feature build; onboarding complete; promo shown on first launch** - Install build at commit `0f064b1e66` → complete linear + contextual onboarding (including EOJ fire + subscription promo dialog) → update app to `alex/subscription-day7-promo` with flags enabled → cold launch (≥1 min since original install date) - → **Day-7 promo sheet appears** (`hasSeenOnboarding = true` carried over; `subscriptionPromotionDialogSeen` false on new property; cooldown already passed) +28. **Upgrade from pre-feature build; onboarding complete; promo shown on first launch after update** + Install build at commit `0f064b1e66` → complete linear + contextual onboarding all the way through the *"Oh, before I forget..."* contextual promo → update app to `alex/subscription-day7-promo` with flags enabled → cold launch (install date is ≥1 min ago) + → **Day-7 promo sheet appears** (`hasSeenOnboarding = true` carried over; `subscriptionPromotionDialogSeen` = false since it's a new UserDefaults key; cooldown already passed) -24. **Upgrade from pre-feature build; onboarding incomplete; promo shown on first launch** - Install build at commit `0f064b1e66` → complete linear onboarding only (do not complete contextual dialogs) → update to `alex/subscription-day7-promo` → cold launch (≥1 min since install) +29. **Upgrade from pre-feature build; contextual onboarding incomplete; promo shown on first launch after update** + Install build at commit `0f064b1e66` → complete linear onboarding only (stop before completing contextual dialogs) → update to `alex/subscription-day7-promo` → cold launch (install date ≥1 min ago) → **Day-7 promo sheet appears** (`subscriptionPromotionPending = false` since user never reached fire step; cooldown passed) --- @@ -153,9 +184,9 @@ CC: N/A #### What could go wrong? -- **Promo appears during active contextual onboarding** — would create two overlapping subscription-related sheets. Mitigated by the `isEligibleToPresent` gate checking `subscriptionPromotionPending`; covered by testing flows #5, #9, #12. -- **Promo shown twice** — duplicate impression if `subscriptionPromotionDialogSeen` is not persisted correctly. Mitigated by setting the flag in `markLaunchPromptPresented()` before presenting; covered by flows #14–15. -- **Other modal prompts regressed by gate refactor** — existing providers could break if the per-provider default gate behaves differently from the removed global gate. The default `isEligibleToPresent` returns `isOnboardingComplete`, which is functionally equivalent; covered by flow #22. +- **Promo appears during active contextual onboarding** — would create two overlapping subscription-related sheets. Mitigated by the `isEligibleToPresent` gate checking `subscriptionPromotionPending`; covered by testing flows #7, #12, #16. +- **Promo shown twice** — duplicate impression if `subscriptionPromotionDialogSeen` is not persisted correctly. Mitigated by setting the flag in `markLaunchPromptPresented()` before presenting; covered by flows #18–20. +- **Other modal prompts regressed by gate refactor** — existing providers could break if the per-provider default gate behaves differently from the removed global gate. The default `isEligibleToPresent` returns `isOnboardingComplete`, which is functionally equivalent; covered by flow #27. - **Cooldown bypassed by date manipulation** — low risk for production; the debug override (1-min) must be reverted before shipping. ### Quality Considerations