Skip to content

feat(app): occasional plans reminder on app open (flag paywall-reask, off by default) - #21754

Open
YinkaAdepoju wants to merge 3 commits into
BasedHardware:mainfrom
YinkaAdepoju:yinka/paywall-reask
Open

YinkaAdepoju wants to merge 3 commits into
BasedHardware:mainfrom
YinkaAdepoju:yinka/paywall-reask

Conversation

@YinkaAdepoju

@YinkaAdepoju YinkaAdepoju commented Oct 15, 2026 •

Copy link
Copy Markdown

What changed and why

Free users only see the plans today when they hit a limit or open Settings. This adds an experiment that, on app open, occasionally shows the existing plans screen to free users. It is off by default.

  • Infrequent by design: at most once a day, with growing gaps between showings, and never on the day someone signs up. Any other plans screen they see resets the timer.
  • Never interrupts: it waits in PromptQueue behind recordings, calls and other prompts, and is skipped for paid users or if the user upgraded in the meantime.
  • Behind the feature flag paywall-reask, with a control group that sees no change. The flag is off, so nothing changes for anyone until it is turned on. If the flag can't be read or anything fails, nothing is shown.
  • Reuses the existing paywall (PaywallRouter.openVariant, falling back to the plans sheet). No new screen or copy. New analytics source: app_open_reask.

Details: app/lib/services/experiments/paywall_reask.md. Flag registered in config/feature-flags.yaml and backend/docs/feature-flag-registry.md.

Split out of #21711 to keep that PR focused.

Try it (debug builds)

flutter run --flavor dev --dart-define=OMI_PAYWALL_REASK=test --dart-define=OMI_PAYWALL_REASK_NOW=true

OMI_PAYWALL_REASK=test|control picks the group; OMI_PAYWALL_REASK_NOW=true makes it show on every launch / resume. Both overrides only work in debug builds.

Product invariants affected

none

How it was verified

  • flutter analyze on all touched Dart files: no issues.
  • scripts/check_event_registry.py (regenerated events.g.dart) and scripts/check_feature_flag_registry.py: OK.
  • make preflight: passes.
  • Not yet tried on a device or simulator with this branch.

Tests

  • app/test/unit/paywall_reask_test.dart (new): timing, the once-a-day limit, never on the first visit, other paywalls reset the timer, the control group sees nothing, an unread flag or a signed-out user does nothing.
  • Existing paywall_router_test.dart, paywall_experiment_test.dart, outline_paywall_page_test.dart still pass.

UI evidence

UI-Evidence: none -- no new screen, it reuses the existing paywall

Failure class (fixes)

Failure-Class: none

🤖 Generated with Claude Code

…reask)

Free readers get the plans again on app open 2, 3, 5 and then every 7 days
after the last paywall they saw anywhere, behind PostHog flag paywall-reask
(control exposed on the same schedule, shows nothing). Queued in PromptQueue,
opened through PaywallRouter so paywall-variant still picks the design.
Pre-registration: app/lib/services/experiments/paywall_reask.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@YinkaAdepoju
YinkaAdepoju marked this pull request as ready for review October 15, 2026 18:35
Yinka and others added 2 commits October 15, 2026 14:40
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@YinkaAdepoju YinkaAdepoju changed the title feat(app): paywall re-ask on app open for free readers (flag paywall-reask) feat(app): occasional plans reminder on app open (flag paywall-reask, off by default) Oct 15, 2026
@Git-on-my-level

Copy link
Copy Markdown
Collaborator

Thanks @YinkaAdepoju — this is a well-scoped, well-documented experiment PR. I went through each changed file:

  • app/lib/services/experiments/paywall_reask.dart — the scheduler is genuinely fail-closed (no identity, unread flag, or analytics error → nothing shown, no exposure recorded), and the schedule advances for control and test at the same moments, so the exposure comparison stays honest. Debug overrides are correctly kDebugMode-gated.
  • app/lib/pages/home/paywall_reask_prompt.dart — nice use of PromptQueue with canShowNow re-checking free status at show time; the re-entrancy guard covers the two call sites. (Minor: the module-level _checking flag is global state; fine here, just worth knowing.)
  • app/lib/pages/home/page.dart — the two queuePaywallReask call sites (launch + resume from background) match the PR description.
  • app/lib/pages/paywall/paywall_router.dart — PaywallReask.noteShown() placed before the control early-return, so every paywall surface (variant page, plans sheet, settings) resets the clock. Correct insertion point.
  • app/test/unit/paywall_reask_test.dart — real behavioral coverage: first-visit, once-a-day cap, gap widening, clock reset, control parity, unread flag, no identity.
  • contracts/analytics/events.json + app/lib/utils/analytics/registry/events.g.dart — app_open_reask added consistently in both the contract and the regenerated enums.
  • config/feature-flags.yaml, backend/docs/feature-flag-registry.md, app/lib/services/experiments/paywall_reask.md, app/changelog/unreleased/20261015-paywall-reask.json — flag registered off/inactive with a real pre-registration (metrics, guardrails, decision rule, kill switch) and a changelog fragment. I verified all the APIs the new code calls (PromptQueue.enqueue, PaywallRouter.openVariant, UsagePage params, prefs methods) exist on current main.

Before this moves forward, a few things for a maintainer:

  1. Product decision: whether Omi should occasionally re-show the paywall on app open is a monetization/UX call — the flag rides on top of paywall-variant (design picked at show time) and interacts with the open feat(app): redesigned Home behind the home-redesign A/B flag (recording pill, upgrade in place) #21711 home-redesign sequence, so the growth/flag owner should sign off on the timing and on the flag ownership listed as yinka.
  2. Verification: no CI check runs are visible on the head SHA, and the description notes it hasn't been tried on a device/simulator yet. Once CI runs on this branch, a quick simulator pass with the debug defines (OMI_PAYWALL_REASK=test OMI_PAYWALL_REASK_NOW=true) — especially the PromptQueue interplay while a recording is active — would be good to see.

No security, dependency, or workflow concerns found. Leaving the product call to a human maintainer.


by AI on behalf of David — if you need David’s attention urgently, please @Git-on-my-level and escalate with need human response.

@Git-on-my-level Git-on-my-level added feature-fit-review Needs review of product/feature direction with Omi mission/vision positive-signal Automation verified a genuine fix/quality contribution flutter flutter work labels Oct 15, 2026

@kodjima33 kodjima33 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

feat(app) new paywall-reask experiment, flag off by default - feature, approve-only.

This branch has not been deployed

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

Labels

feature-fit-review Needs review of product/feature direction with Omi mission/vision flutter flutter work positive-signal Automation verified a genuine fix/quality contribution

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants