From 76d36033b640c00a432139ef891d1ef71178beab Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Thu, 9 Jul 2026 16:16:33 +0600 Subject: [PATCH] [2026-07-09] eagle: work/projects/ios-subscription-day7-promo-pixels.md --- .../ios-subscription-day7-promo-pixels.md | 120 +++++++++--------- 1 file changed, 61 insertions(+), 59 deletions(-) diff --git a/work/projects/ios-subscription-day7-promo-pixels.md b/work/projects/ios-subscription-day7-promo-pixels.md index a853ff8a..638c59d8 100644 --- a/work/projects/ios-subscription-day7-promo-pixels.md +++ b/work/projects/ios-subscription-day7-promo-pixels.md @@ -1,79 +1,81 @@ -# Pixel Definitions: iOS Day-7 Subscription Promo - -Defined in: `iOS/PixelDefinitions/pixels/definitions/subscription.json5` -Fired from: `SubscriptionPromoExistingUserCoordinator` -Android equivalents: `m_privacy-pro_skipped_onboarding_promotion_*` (same modal, different audience) +# Pixel Definition: iOS Day-7 Subscription Promo Half-Sheet --- -## Pixels +## Background + +- **Project:** [iOS: Show subscription half-sheet promo to new installers who don't see it during onboarding](https://app.asana.com/1/137249556945/project/1202406491309510/task/1214798984829406) +- **Android counterpart:** [Grow active subscriptions by increasing paywall impressions with a day 7 promo half-sheet](https://app.asana.com/1/137249556945/project/72649045549333/task/1210994036696114) — the iOS implementation follows the same approach and target audience as Android +- **Origin request:** [Request origin for iOS subscription promo half-sheet entry point](https://app.asana.com/1/137249556945/task/1214800683792174) — origin value `funnel_modal_ios__subscriptionnudge` (Android equivalent: `funnel_modal_android__subscriptionnudge`) +- We are adding a new half-sheet modal for existing users (7+ days since install) who have never seen any subscription offer. The modal reuses the pre-existing `SubscriptionPromoLaunchView` UI. +- Existing subscription promo pixels already cover related audiences: + - `m_privacy-pro_onboarding_promotion_impression/tap/dismiss` — users who see the promo during onboarding + - `m_privacy-pro_skipped_onboarding_promotion_impression/tap/dismiss` — reinstallers who skipped onboarding (7+ days) + +## Objective + +- Define three new pixels (impression, tap, dismiss) for the day-7 existing-user promo half-sheet. +- Mirror the parameter shape of the existing `m_privacy-pro_skipped_onboarding_promotion_*` pixels to allow cross-audience comparison. +- These are permanent pixels (no expiry) — the feature is not an experiment. +- The origin value `funnel_modal_ios__subscriptionnudge` flows downstream automatically to `m_privacy-pro_offer_screen_impression` and `m_subscribe` without additional work. + +## Testing the feature + +- Feature is implemented in: `iOS/DuckDuckGo/SubscriptionPromo/SubscriptionPromoExistingUserCoordinator.swift` +- Test paths: + - Install app, complete or skip onboarding + - Wait 1 minute (1-min override active — revert to 7 days before shipping) + - Background and re-foreground app → promo half-sheet should appear + - Verify `m_privacy-pro_existing_user_promotion_impression` fires on display + - Tap CTA → verify `m_privacy-pro_existing_user_promotion_tap` fires and subscription flow opens with origin `funnel_modal_ios__subscriptionnudge` + - Re-install and dismiss instead → verify `m_privacy-pro_existing_user_promotion_dismiss` fires + +--- + +## Final pixels (for triage) ### `m_privacy-pro_existing_user_promotion_impression` -| Field | Value | -|---|---| -| Trigger | The day-7 promo half-sheet is shown to an existing user (7+ days since install) who has never seen any subscription offer | -| Fired from | `SubscriptionPromoExistingUserCoordinator.markLaunchPromptPresented()` | -| Frequency | Once per install (guarded by `subscriptionPromotionDialogShown`) | - -**Parameters** - -| Key | Type | Description | -|---|---|---| -| `appVersion` | string | App version (added by default) | -| `ru` | boolean | Whether the user is a returning user (reinstall) | -| `free_trial` | boolean | Whether the user is eligible for a free trial | - -**Suffixes:** `_ios`, `_phone` / `_tablet` - ---- +- **Trigger:** The day-7 promo half-sheet is shown to an existing user (7+ days since install) who has never seen any subscription offer. +- **Frequency:** Once per install (guarded by `daxDialogsSettings.subscriptionPromotionDialogShown`). +- **Fired from:** `SubscriptionPromoExistingUserCoordinator.markLaunchPromptPresented()` +- **Parameters:** + - `appVersion` — app version (added by default) + - `ru` — `true` / `false` — whether the user is a returning user (reinstall) + - `free_trial` — `true` / `false` — whether the user is eligible for a free trial at time of display +- **Suffixes:** `_ios`, `_phone` / `_tablet` ### `m_privacy-pro_existing_user_promotion_tap` -| Field | Value | -|---|---| -| Trigger | User taps the CTA button ("Try It Free" or "Learn More") on the promo modal | -| Fired from | `SubscriptionPromoExistingUserCoordinator.handleCTAAction()` | - -**Parameters** — same as impression: `appVersion`, `ru`, `free_trial` - -**Suffixes:** `_ios`, `_phone` / `_tablet` - ---- +- **Trigger:** User taps the CTA button ("Try It Free" or "Learn More") on the promo modal. +- **Frequency:** Standard (can fire once per impression, since the modal is shown at most once). +- **Fired from:** `SubscriptionPromoExistingUserCoordinator.handleCTAAction()` +- **Parameters:** same as impression — `appVersion`, `ru`, `free_trial` +- **Suffixes:** `_ios`, `_phone` / `_tablet` ### `m_privacy-pro_existing_user_promotion_dismiss` -| Field | Value | -|---|---| -| Trigger | User dismisses the promo modal (taps close or swipes down) | -| Fired from | `SubscriptionPromoExistingUserCoordinator.handleDismissAction()` | - -**Parameters** — same as impression: `appVersion`, `ru`, `free_trial` - -**Suffixes:** `_ios`, `_phone` / `_tablet` +- **Trigger:** User dismisses the promo modal (taps X or swipes down) without tapping the CTA. +- **Frequency:** Standard. +- **Fired from:** `SubscriptionPromoExistingUserCoordinator.handleDismissAction()` and `UIAdaptivePresentationControllerDelegate` (swipe-to-dismiss) +- **Parameters:** same as impression — `appVersion`, `ru`, `free_trial` +- **Suffixes:** `_ios`, `_phone` / `_tablet` --- -## Funnel Origin +## Behavior rules -When the user taps the CTA, the subscription flow is opened with origin `funnel_modal_ios__subscriptionnudge`. - -This origin flows through to two downstream pixels automatically: -- `m_privacy-pro_offer_screen_impression` (param: `origin`) -- `m_subscribe` (param: `origin`) — the subscription purchase success pixel - -Origin defined in: `iOS/DuckDuckGo/Subscription/SubscriptionFunnelOrigin.swift` -Origin request subtask: https://app.asana.com/1/137249556945/task/1214800683792174 -Android equivalent origin: `funnel_modal_android__subscriptionnudge` +- Impression and tap/dismiss are mutually exclusive per install — the modal is shown at most once. +- `ru` and `free_trial` are evaluated at time of display, same as in the skipped-onboarding promo. +- The dismiss pixel fires on both explicit close (X button) and swipe-to-dismiss; neither path fires the tap pixel. +- If the user taps CTA, tap fires but dismiss does not. --- -## Relation to Existing Promo Pixels +## Notes and status -All three subscription promo half-sheets use the same parameter shape (`ru`, `free_trial`) to allow cross-audience comparison: - -| Pixel prefix | Audience | Origin | -|---|---|---| -| `m_privacy-pro_onboarding_promotion_*` | Users who see the promo during onboarding | `funnel_onboarding_ios` | -| `m_privacy-pro_skipped_onboarding_promotion_*` | Reinstallers who skipped onboarding, 7+ days | `funnel_modal_ios__skippedonboardingupsell` | -| `m_privacy-pro_existing_user_promotion_*` | Normal installers who never saw a subscription offer, 7+ days | `funnel_modal_ios__subscriptionnudge` | +- Pixel definitions are committed to `iOS/PixelDefinitions/pixels/definitions/subscription.json5`. +- These pixels are structurally identical to `m_privacy-pro_skipped_onboarding_promotion_*` (same parameters, same suffixes) — no new parameter types are introduced. +- Android equivalent pixels: the Android implementation fires analogous `m_privacy-pro_skipped_onboarding_promotion_*` pixels (Android reused the existing reinstaller promo pixel names for the day-7 promo since it shipped first). +- Origin `funnel_modal_ios__subscriptionnudge` needs formal approval via the [Origin Proposal form](https://form.asana.com/?k=Wy3aY17vglr2bihpUHoyBA&d=137249556945) — tracked in the origin request subtask above. +- Privacy triage is required for the new pixels — see `ios-subscription-day7-promo-privacy-triage.md`.