Pro plan status & upgrade checklist
Where the paid plan surfaces today, what the minimal-dashboard cleanup removed, and everything to touch when Pro features ship.
Pro features are planned, not launched. The billing infrastructure (Stripe subscription + lifetime purchase, entitlements, webhooks) is fully built and tested — see billing for the machinery. This page records the current Pro surface after the minimal-dashboard cleanup (September 2026) and everything that needs updating when Pro ships.
What the minimal-dashboard cleanup removed
The app topbar was simplified to only the minimal-dashboard and theme toggles:
- Topbar Free/Pro plan badge — removed from
AppShell(src/components/app/app-shell.tsx), including the admin role badge that showed there on admin pages. isProprop — dropped fromAppShelland all its call sites (dashboard, pro, account, admin routes).LangSwitchin the topbar — removed. The component file stays: the marketing nav, footer and auth card still use it.- Plan info remains visible elsewhere (inventory below); the dashboard "logged in as" line is now the main user-facing plan badge.
Badge'spro/freevariants are generic color variants, also used for non-plan semantics (goal kinds, budget table, filter counts, admin tables). Do not remove the variants — only plan-specific usages were affected.
Where Pro still surfaces
| Touchpoint | File | Behavior |
|---|---|---|
| Sidebar "Pro" nav item + badge | src/components/app/app-shell.tsx | Links to /app/pro; badge shows on the expanded rail |
| Pro demo page | src/routes/{-$locale}/app/pro.tsx | hasProAccess(user.role, ent) gate — free users get a blurred preview + upgrade CTA |
| Account → current plan | src/features/billing/components/manage-subscription.tsx | Plan badge + status; pro → Stripe portal button, free → upgrade link; lifetime variant |
| Dashboard "logged in as" line | src/routes/{-$locale}/app/index.tsx | Plan badge in full mode only (hidden when minimal dashboard is on) |
| Pricing page | src/features/billing/components/pricing-table.tsx | Pro card "coming soon"; CTA opens the waitlist dialog, not checkout |
| Payment-failed banner | src/features/billing/components/payment-failed-banner.tsx | In-app dunning signal, driven by ent.paymentFailed → Stripe Customer Portal |
| Admin tables | src/features/admin/components/user-table.tsx, user-detail-drawer.tsx, src/routes/{-$locale}/admin/feedback.tsx | Plan column / badges; feedback isPro derived via hasProAccess (see feedback) |
Gating truth: server-side gates are requireProPlan / hasProAccess (src/features/billing/middleware.ts + entitlement.ts) — admins outrank the paywall. Billing UI (plan badges, upgrade buttons) uses ent.plan so it reflects the real subscription, not the role.
Dormant pipeline (built, tested, zero callers)
The purchase pipeline is wired end-to-end but nothing invokes it yet:
src/features/billing/plans.ts— plan definitions ($9/mo, $90/yr). Dormant: the pricing page pivoted to a waitlist.src/features/billing/actions.ts—checkout(subscription mode) andcheckoutLifetimehave no callers.- Webhook handling is live and covers the subscription lifecycle, lifetime grant/refund, and dunning (event table in billing).
- The
pro-activatedemail template exists, wired viahooks.ts(see email).
Price mismatch to resolve at launch:
plans.tsdefines $9/mo / $90/yr while the pricing-table copy pitches $199 personal / $499 team. Reconcile before taking real payments.
Launch checklist
When Pro features ship, update:
- Pricing page — switch the Pro CTA from the waitlist dialog to the real
checkoutserver fn; reconcile prices between the pricing-table copy andplans.ts. - Lifetime — wire
checkoutLifetimeif you offer a one-time buy (the webhook side is already implemented). - Feature gating — gate new Pro features server-side with
requireProPlan/hasProAccess; useent.planonly for billing UI. - Stripe setup — configure
STRIPE_SECRET_KEY,STRIPE_WEBHOOK_SECRETand price IDs (see environment and deploy); create the products/prices and webhook events in the Stripe Dashboard. - Badge spots — decide where plan badges belong now. The topbar no longer shows one; the dashboard "logged in as" line does. Re-add a topbar badge consciously if you want it back.
- i18n — extend the
billing.*keys in bothsrc/features/i18n/dictionaries/en.tsandzh.ts(structurally identical;zhis typed againsten). - Emails —
pro-activatedfires viahooks.ts(onProActivated); add deactivation / payment-failed hooks as needed. - Tests — extend
billing.workers.test.ts/entitlement.node.test.tsfor any new transitions. - Docs — update this page and billing whenever the Pro status changes.