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

YI
1d ago
+291-4

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