UpgradePromptCard: primary destination → Pro Annual ('2 months free' framing) across 5 surfaces#50
Merged
Conversation
Flips UpgradePromptCard's primary CTA from generic monthly Pro to a
Pro-Annual-anchored CTA ("Get Pro — $7.50/mo billed yearly · 2 months
free") per the 2026-05-13 pricing playbook. A secondary "Or pay
monthly — $9/mo" link sits directly under the primary so mid-cycle
users still have a frictionless monthly path.
Primary navigates to /app/billing?frequency=yearly&plan=pro; secondary
to ?frequency=monthly&plan=pro. Frequencies + plan are appended via a
new buildCtaHref() helper so the destination logic lives in one place.
P1's /auth/me experiment variant continues to override the PRIMARY CTA
label only — the secondary link is intentionally static so the A/B
test doesn't run on two surfaces at once. When P1 overrides the label,
the destination still points at the yearly frequency so the experiment
measures copy, not destination.
5 surfaces covered: vault_prod, provision_twin, family_bindings,
quota_wall, custom_domain. Each priceLine now carries the "2 months
free" framing. private_deploy gets the same treatment for free since
it reads from the same UPGRADE_COPY map.
Tests: 23 unit + 14 surface (was 15 + 9). Full dashboard suite 395
pass / 3 skip / 0 fail. Build clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Flips the primary upgrade CTA on every UpgradePromptCard surface from
generic monthly Pro → Pro Annual ("Get Pro — $7.50/mo billed yearly
· 2 months free"), per the 2026-05-13 pricing playbook (highest-LTV
product, anchored on effective per-month price). A small "Or pay
monthly — $9/mo" text link sits directly under the primary CTA so
mid-cycle users still have a frictionless monthly path.
/app/billing?frequency=yearly&plan=pro/app/billing?frequency=monthly&plan=probuildCtaHref()helperso the query-param logic lives in one place.
Surfaces touched (5 + private_deploy comes along for free)
vault_prod · provision_twin · family_bindings · quota_wall ·
custom_domain · private_deploy. Each
priceLinenow reads "$7.50/mobilled yearly · 2 months free".
P1 experiment composition preserved
The
experiments.upgrade_cta.labelpayload from/auth/mecontinuesto override the primary CTA label only — the secondary "Or pay
monthly" link is intentionally static so the A/B test doesn't run on
two surfaces at once. Crucially, when P1 overrides the label, the
destination still points at yearly so the experiment measures
copy, not destination. Asserted in
UpgradePromptCard.test.tsxwith:Test plan
npm testgreen — 395 pass / 3 skip / 0 fail (was 395 baseline; +13 new asserts)npm run buildclean — TypeScript + Vite production build OKUpgradePromptCard.test.tsx: 23 tests (was 15) — primary, secondary, P1 composition, link targetUpgradePromptSurfaces.test.tsx: 14 tests (was 9) — 5-surface regression block + updated P1 composition testsprivate_deploysurface)Coordination note
BillingPage redesign is in a parallel PR — this PR only touches the
5 in-context upgrade prompt surfaces. BillingPage doesn't yet read
the
?frequency=URL param (it reads from localStorage), so thelanding experience for users who click these CTAs is forward-compat:
the URL carries the right intent, BillingPage will start honouring
it when the parallel PR lands.
🤖 Generated with Claude Code