fix(lists): tell capped users they are capped, on every creation path - #224
Merged
Conversation
A user at the free-plan limit reported not being able to create a list. The
quota is working as intended — they hold 5 lists with no subscription, so
assertListQuota throws PLAN_LIMIT before the insert. The bug is what they were
told about it.
Only CreateListModal recognised PLAN_LIMIT and showed the upgrade prompt. The
other paths did not:
- TemplatePickerModal (both built-in and saved templates) showed
"Failed to create list. Please try again." — advice that can never work,
since retrying at the cap fails identically every time.
- Templates.tsx showed NOTHING. Both catch blocks logged to console, cleared
the spinner and returned, so the button simply stopped responding with no
explanation on screen.
- OnboardingFlow showed "Couldn't create list. Try again."
So whether a capped user learned they could upgrade depended entirely on which
button they pressed.
Adds src/lib/planLimit.ts as the single place that knows what the quota error
looks like, and routes every creation path through it. Templates.tsx gains an
error surface it never had, with a link to pricing. Home.tsx is deliberately
untouched: it creates the demo list for brand-new users, who are at zero lists
and cannot hit the cap.
The detector matches on substring, not prefix — Convex wraps mutation errors,
so the marker never arrives at position 0. There is a test for the wrapped form
specifically.
The last test is the one that matters: it asserts every list-creating component
recognises the quota, since the defect was never in the logic but in four call
sites not asking. Confirmed non-vacuous — it fails on main.
Co-Authored-By: Claude Opus 5 (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.
A user reported not being able to create a list. The quota is working correctly — they hold 5 lists with no subscription, so
assertListQuotathrowsPLAN_LIMITbefore the insert. The bug is what they were told about it.Only one of four paths explained the problem
CreateListModalTemplatePickerModal(built-in + saved)Templates.tsxOnboardingFlowWhether a user learned they could upgrade depended on which button they pressed.
"Please try again" is the worst part: retrying at the cap fails identically every time, so it sends someone into a loop that cannot succeed.
Templates.tsxwas worse still — both catch blocks logged to console, cleared the spinner and returned, so the button just stopped working with no on-screen explanation.The fix
src/lib/planLimit.tsbecomes the single place that knows what the quota error looks like, and every creation path routes through it:isPlanLimitError(err)— matches on substring, not prefix. Convex wraps mutation errors ([CONVEX M(lists:createList)] Uncaught Error: PLAN_LIMIT: …), so the marker never arrives at position 0. There's a test for the wrapped form specifically.listCreationErrorMessage(err, fallback)— the actionable cap message, or a generic fallback for genuinely unexpected failures.Templates.tsxgains an error surface it never had, with a link to pricing.Home.tsxis deliberately untouched: it creates the demo list for brand-new users, who are at zero lists and cannot hit the cap.Tests
Four cover the helper — the real server string, the Convex-wrapped form, non-quota errors not being misread, and the cap message never saying "try again".
The fifth is the one that matters: it asserts every list-creating component recognises the quota. The defect was never in the logic, it was in four call sites not asking — so the regression test has to check coverage, not behaviour. Confirmed non-vacuous: it fails on
main.169 tests pass;
tsc -bclean; the 7 remaining eslint errors in these files are pre-existing (8 before this change).Not addressed here
Whether 5 is the right cap, and whether
raedugas@gmail.comshould be granted headroom —bonusListsorreferralProUntilare the existing levers, both writes to production user data.🤖 Generated with Claude Code