Remove the Classroom tier — Tappymaps is not a school product - #45
Merged
Conversation
The $12/mo Classroom tier (class codes, printable worksheet packs, a
/class/:code route) framed Tappymaps as a school tool. It isn't one, so
the tier comes out in full rather than being hidden behind a flag.
Verified before removing anything destructive: production held 0 rows in
classroom_codes and 0 subscriptions at tier 'classroom'. Nobody loses
access and no paying customer is affected.
App (index.html, -497 lines):
- Delete the Classroom tools + upsell blocks in the Create Upgrade panel,
their CSS, and the upgrade modal's Classroom button.
- Delete the whole class-code/worksheet subsystem: code mint/copy/join,
the classroom_lookup RPC call, exportWorksheetPack, Modes.ClassJoin and
its /class/:code route, and wireClassroomUI.
- Collapse entitlements to one paid tier: drop appState.classroomUnlocked,
isClassroom(), startClassroomCheckout() and updateClassroomUI(); owner
accounts now auto-grant 'pro' instead of 'classroom'.
- Rebuild /pricing as Free vs Pro (was Pro vs Classroom-for-teachers) and
drop the "Have a class code?" join box. Retitle the page meta.
- Drop "high-school math teacher" from the About bio.
Billing handlers:
- create-checkout no longer resolves a 'classroom' plan or reads
STRIPE_CLASSROOM_MONTHLY_PRICE_ID.
- verify-subscription drops isClassroom from its payload; tier is pro|free.
- webhook's tierForPrice always resolves 'pro'. The indirection stays so a
future second tier is a one-line change, not a migration.
Database (20260822_remove_classroom.sql, applied to production):
- Drop classroom_lookup(text) and classroom_codes (taking its two RLS
policies with it).
- Redefine active_subscription_tier() so admins get 'pro', and
has_subscription() without the classroom-implies-pro special case.
- Re-assert the schema v2 grant posture on both redefined functions.
Verified by JWT impersonation after applying: both owner accounts resolve
to 'pro', a random authenticated user to null, and has_subscription
('classroom') is false for everyone. classroom_codes and classroom_lookup
are gone; 0 policies still reference the tier.
Guardrails: checkout-plan-smoke now asserts that classroom/teacher/
worksheet/class-code wording cannot reappear in index.html or the checkout
handler, and CLAUDE.md records the constraint so it is not rebuilt later.
Map subject matter is untouched — a Census "Bachelor's Degree+" dataset is
ordinary demographic data, not school framing.
Checks: validate (Block 1 unchanged at 34,471 chars), 18/18 unit, 16/16
API, checkout-plan-smoke, extract-data --check, and the full browser smoke
suite all pass with zero console errors. /pricing and the Upgrade panel
inspected in a real browser.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BmhvochBpXL4rpuccjAQx9
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
mapzimus
marked this pull request as ready for review
August 23, 2026 10:30
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.
Re-opened from #44, which merged at
dd0d331and captured only theapp_adminsmigration — this commit was pushed to the same branch but landed outside the merge snapshot. Same thing happened between #43 and #44.Important
This is time-sensitive. The database half of this change is already applied to production:
classroom_codesandclassroom_lookup()are dropped andhas_subscription('classroom')returns false for everyone. Until this merges, the live site still shows a Classroom tier that the database no longer backs — "Get Classroom" would run a real Stripe checkout for a tier that grants nothing, and/class/:codeerrors. Merging this closes that gap.What this removes
Tappymaps is not a school product. The $12/mo Classroom tier — class codes, printable worksheet packs, a
/class/:coderoute — framed it as one, so it comes out in full rather than hiding behind a flag.Checked before removing anything destructive: production held 0 rows in
classroom_codesand 0 subscriptions at tierclassroom. Nobody loses access and no paying customer is affected.App (
index.html, −497 lines)classroom_lookupRPC call,exportWorksheetPack,Modes.ClassJoinand its route, andwireClassroomUI.appState.classroomUnlocked,isClassroom(),startClassroomCheckout(),updateClassroomUI(). Owner accounts auto-grantpro./pricingas Free vs Pro (was Pro vs Classroom-for-teachers) and dropped the "Have a class code?" join box.Billing handlers
create-checkoutno longer resolves aclassroomplan or readsSTRIPE_CLASSROOM_MONTHLY_PRICE_ID.verify-subscriptiondropsisClassroomfrom its payload;tierispro | free.webhook'stierForPricealways resolves'pro'. The indirection stays so a future second tier is a one-line change rather than a migration.Database —
supabase/migrations/20260822_remove_classroom.sql, already applied to productionclassroom_lookup(text)andclassroom_codes(taking its two RLS policies with it).active_subscription_tier()so admins get'pro', andhas_subscription()without the classroom-implies-pro special case.Verified by JWT impersonation after applying:
Guardrails
checkout-plan-smokenow asserts that classroom / teacher / worksheet / class-code wording cannot reappear inindex.htmlor the checkout handler, andCLAUDE.mdrecords the constraint so a later session does not rebuild it.Map subject matter is deliberately untouched: a Census
Bachelor's Degree+dataset, or a GeoDraft backlog category like "high school graduation rate", is ordinary demographic data rather than school framing. None of those backlog categories are wired into the app today.Verification
Re-run on the rebased base (
007221d):npm run validate— both blocks pass; Block 1 unchanged at 34,471 charsnode --test test/*.test.mjs— 18/18node scripts/api-subscription-test.mjs— 16/16node scripts/checkout-plan-smoke.mjs— pass, including the new negative guardsnpm run smoke— all checks pass, zero console errors/pricingand the Create Upgrade panel inspected in a real browserNote for whoever touches billing next
Every write to
user_subscriptionsmust keep settingtier. The database cannot read Stripe price IDs, so a row written without it locks a paying customer out at the RLS layer even though checkout succeeded.Generated by Claude Code