Skip to content

Remove the Classroom tier — Tappymaps is not a school product - #45

Merged
mapzimus merged 1 commit into
masterfrom
claude/tappymaps-revamp-audit-4gm8pj
Aug 23, 2026
Merged

Remove the Classroom tier — Tappymaps is not a school product#45
mapzimus merged 1 commit into
masterfrom
claude/tappymaps-revamp-audit-4gm8pj

Conversation

@mapzimus

Copy link
Copy Markdown
Owner

Re-opened from #44, which merged at dd0d331 and captured only the app_admins migration — 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_codes and classroom_lookup() are dropped and has_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/:code errors. 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/:code route — 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_codes and 0 subscriptions at tier classroom. Nobody loses access and no paying customer is affected.

App (index.html, −497 lines)

  • Deleted the Classroom tools + upsell blocks in the Create Upgrade panel, their CSS, and the upgrade modal's Classroom button.
  • Deleted the class-code / worksheet subsystem: code mint / copy / join, the classroom_lookup RPC call, exportWorksheetPack, Modes.ClassJoin and its route, and wireClassroomUI.
  • Collapsed entitlements to one paid tier — dropped appState.classroomUnlocked, isClassroom(), startClassroomCheckout(), updateClassroomUI(). Owner accounts auto-grant pro.
  • Rebuilt /pricing as Free vs Pro (was Pro vs Classroom-for-teachers) and dropped the "Have a class code?" join box.
  • Dropped "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 rather than a migration.

Databasesupabase/migrations/20260822_remove_classroom.sql, already applied to production

  • Dropped classroom_lookup(text) and classroom_codes (taking its two RLS policies with it).
  • Redefined active_subscription_tier() so admins get 'pro', and has_subscription() without the classroom-implies-pro special case.
  • Re-asserted the schema v2 grant posture on both redefined functions.

Verified by JWT impersonation after applying:

max@mapparatus.org     tier=pro     has('pro')=true   has('classroom')=false
mhowe.gis@gmail.com    tier=pro     has('pro')=true   has('classroom')=false
random authenticated   tier=(none)  has('pro')=false  has('classroom')=false

classroom_codes dropped: true    classroom_lookup dropped: true
stray classroom rows: 0          policies mentioning classroom: 0

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 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 chars
  • node --test test/*.test.mjs — 18/18
  • node scripts/api-subscription-test.mjs — 16/16
  • node scripts/checkout-plan-smoke.mjs — pass, including the new negative guards
  • npm run smoke — all checks pass, zero console errors
  • /pricing and the Create Upgrade panel inspected in a real browser

Note for whoever touches billing next

Every write to user_subscriptions must keep setting tier. 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

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
@vercel

vercel Bot commented Aug 23, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
mapparatus Ready Ready Preview Aug 23, 2026 3:30am
tappymaps Ready Ready Preview Aug 23, 2026 3:30am

@mapzimus
mapzimus marked this pull request as ready for review August 23, 2026 10:30
@mapzimus
mapzimus merged commit 5a58051 into master Aug 23, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants