refactor(plans): render every surface from one plan catalogue - #22
Merged
Conversation
Plan data was maintained by hand in three places: the pricing cards, the portal's own list, and the billing terms as prose. They drifted three times in a single day — the portal priced Business at $30 instead of $25, showed Teams as "Coming soon" while the site sold it, and the terms promised a Teams trial that did not exist yet. shared/plans.ts is now the single source for prices, savings, descriptions, features, trials and which plans bill per seat. All three surfaces derive from it, keeping only what is genuinely local: the landing cards keep their highlight flag and CTA targets, the portal keeps its checkout behaviour. Two mechanical details, both found by building rather than by type-checking: Turbopack does not resolve tsconfig paths pointing outside the app root, so each app widens turbopack.root to the repository and spells out the @shared alias. `tsc --noEmit` passed against the shared module while `next build` failed with "Can't resolve '@shared/plans'"; only a real build proves this. Vitest does not read tsconfig paths either and needs the same alias. The portal had no CI at all, which is why its copy drifted unnoticed — Vercel deploy checks do not type-check. It gains a workflow running tsc, landing gains a tsc step alongside its tests, and both now trigger on changes to shared/. Six tests pin the invariants that matter commercially: annual never exceeds monthly, per-seat plans match the server's list, Business cannot undercut Teams at the shared seat floor, only Pro's trial skips the credit card, and Business carries no trial at all. Feature lists are unified to the fuller landing copy, so the portal now also shows cross-device sessions on Pro and workspace restore on Free.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
portal/pnpm-workspace.yaml still carried the literal placeholder "set this to true or false" for sharp and unrs-resolver. pnpm 11 treats that as unresolved and fails the install with ERR_PNPM_IGNORED_BUILDS, so the new portal CI job could not get past `pnpm install --frozen-lockfile`. The same error silently aborts `pnpm exec` locally, which is how it stayed invisible: Vercel tolerates it, and nothing else in CI ever installed this package. landing/pnpm-workspace.yaml already answers both with true; this matches it.
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.
Plan data was maintained by hand in three places — the pricing cards, the portal's own list, and the billing terms as prose. They drifted three separate times today:
shared/plans.tsis now the single source for prices, savings, descriptions, features, trials and which plans bill per seat. All three surfaces derive from it and keep only what is genuinely local: the landing cards keep their highlight flag and CTA targets, the portal keeps its checkout behaviour.Two mechanical details, both found by building rather than type-checking
Turbopack does not resolve tsconfig paths pointing outside the app root.
tsc --noEmitpassed happily against@shared/planswhilenext buildfailed withModule not found: Can't resolve '@shared/plans'. Each app now widensturbopack.rootto the repository and spells out the alias. Both apps are verified with a realnext build:Vitest does not read tsconfig paths either and needs the same alias in
vitest.config.mts.The portal had no CI
That is why its copy drifted unnoticed:
landing.ymlonly triggers onlanding/**, and Vercel's deploy checks do not type-check. The portal gains a workflow runningtsc --noEmit, landing gains atscstep alongside its tests, and both now trigger on changes toshared/— otherwise editing the catalogue would run neither.Tests
Six new tests pin the invariants that matter commercially, not just structurally:
vitest run— 41 passed across 2 files.Deliberate behaviour change
Feature lists are unified to the fuller landing copy, so the portal now also shows "Cross-device sessions" on Pro and "Persistent sessions & workspace restore" on Free. The two lists had silently diverged; this picks the more complete one rather than the portal's.
Not included
PLAN_FEATURESin the desktop client is a fourth copy, in a different repository. It carries only the feature matrix rather than prices, so sharing it would mean publishing a package for modest benefit. The server stays authoritative for the seat floor and variant ids — those are enforcement, and duplicating them into a display module would invite the opposite bug.