Skip to content

feat(activation): onboard unactivated first-cycle Pro users#5564

Merged
koala73 merged 5 commits into
mainfrom
codex/pro-activation-first-cycle-backfill
Jul 25, 2026
Merged

feat(activation): onboard unactivated first-cycle Pro users#5564
koala73 merged 5 commits into
mainfrom
codex/pro-activation-first-cycle-backfill

Conversation

@koala73

@koala73 koala73 commented Jul 24, 2026

Copy link
Copy Markdown
Owner

Summary

  • extend the onboarding introduced in feat(activation): day-0 pro activation onboarding interstitial #5534 to every active Pro subscriber still in their first billing cycle, even when the checkout marker is absent
  • suppress the markerless flow when verified delivery, API, MCP, or local widget functionality is already active
  • atomically claim and confirm one presentation per subscription across devices, with account-safe storage, auth-transition recovery, and bounded retries

Verification

  • frontend and API typechecks
  • 139 focused Convex billing tests
  • 119 focused activation/controller/widget tests
  • 18 Chromium Pro activation regressions
  • full data suite: 16,315 non-skipped assertions passed after generating the required dashboard build artifact; 6 skipped
  • repository pre-push gate

Follow-up to #5534.

@vercel

vercel Bot commented Jul 24, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
worldmonitor Ready Ready Preview, Comment Jul 25, 2026 6:07am

Request Review

koala73 added 2 commits July 24, 2026 22:39
…t gaps

- widget-store: legacy widgets stored before the `tier` field existed no
  longer get silently dropped by the resilient (non-strict) loadWidgets()
  path; they're normalized to 'basic' as before. Strict activation-eligibility
  reads still reject them as malformed.
- billing: remove the byte-for-byte duplicate isProActivationCoveringAt and
  use the now-exported subscriptionHelpers.isCoveringAt instead, so the
  activation-boundary coverage rule can't silently drift from the canonical
  entitlement-reconciliation rule.
- tests: cover the previously-untested controller not-eligible/retry
  branching, the confirmProActivationPresentation double-confirm idempotency
  path, and three decideActivationMount markerless-cohort branches
  (desktop, entitlement-not-loaded, subscription-not-loaded).

Found during code review of #5564.
…ry exhaustion

- mount(): wrap the body in try/finally so `this.mounting` releases on every
  exit path (including a thrown exception), and collapse the two near-
  identical auth-generation-mismatch blocks into one abortIfStale() helper.
- openFlow(): thread a staleness check through so a destroy-triggered
  generation bump (e.g. a same-document app re-init while the interstitial's
  open flow is still in flight) now closes any interstitial that got opened,
  instead of leaving it orphaned on screen wired to a dead context. Also fixes
  a related gap where that path queued a follow-up evaluate() without
  checking ctx.isDestroyed, unlike the sibling check earlier in mount().
- scheduleFlowRetry(): when the bounded 5-attempt backoff schedule exhausts,
  fall back to the same passive entitlement/subscription listeners used
  elsewhere in this file (armRetry()) instead of permanently resolving the
  tab. A later external signal can still retrigger onboarding within the same
  session; a fresh trigger gets its own full backoff schedule.

Found during code review of #5564.
…s, and tests

- reuse canonical withTimeout in the activation flow instead of bespoke deadline wrappers
- normalize legacy pre-tier widgets to 'basic' in loadWidgetsStrict so the markerless strict read resolves instead of retrying forever
- swallow the synchronous subscribe replay in armRetry so the backoff-exhaustion fallback cannot retrigger the retry loop
- update stale pro-activation-state module header (markerless path, injected-storage helpers)
- pin rule.enabled conjunct, same-nonce re-claim, and confirm transient-failure retry with tests
- dedupe the in-memory storage stub in activation-state tests
@koala73
koala73 merged commit e8fc599 into main Jul 25, 2026
26 checks passed
koala73 added a commit that referenced this pull request Jul 25, 2026
…cript (#5584)

* feat(activation): persist wizard outcome + activation-lift analysis script

Neither the Umami funnel side (no userId, joinable to nothing) nor the
Convex side (no outcome record) could answer "did the Pro Activation
wizard improve feature adoption" -- see #5582 for the full investigation,
including the #5565 Umami outage that blacked out ~4 days spanning most
of #5534's launch window.

- convex/schema.ts: extend proActivationPresentations with confirmedSteps/
  skippedSteps/failedSteps/exitedAt -- the durable per-subscription outcome
  record, independent of Umami.
- convex/payments/billing.ts: new recordProActivationOutcome mutation,
  ownership + nonce checked like confirmProActivationPresentation.
  Best-effort by design (no-ops on a stale/mismatched presentation row).
- src/services/billing.ts: client wrapper, swallows errors so a failed
  write never blocks the subscriber's exit.
- src/services/pro-activation-state.ts: buildActivationOutcomeBuckets, a
  pure helper (alongside the existing summarizeActivationExit) that buckets
  step results by outcome -- unit-testable without a DOM.
- src/components/ProActivationInterstitial.ts: call the new mutation from
  onExit, gated to the markerless cohort (the only flow with a presentation
  row to attach outcomes to).
- scripts/report-activation-lift.mjs: read-only report comparing feature
  creation (notificationChannels/alertRules/userApiKeys/mcpProTokens) after
  wizard exit, engaged vs. presented-only-and-skipped-everything. Not a
  randomized control -- both groups were equally shown the wizard, so this
  isolates an engagement effect, not the wizard's existence. Enforces a
  30-sample floor per group before printing a rate comparison.

Verified against real (read-only) production Convex data: the new table/
fields don't exist yet since #5564 hasn't merged, and the script reports
that state cleanly rather than erroring.

* fix(activation): harden lift outcome reporting
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