Mike 2.1 Phase 1: design system, Vercel AI Gateway, Stripe paywall
From the PR description
Summary
Phase 1 of the Mike 2.1 rebrand and commercial-SaaS transition. Forks the original mike-2.0 prototype into a paid product targeting Middle East law firms.
Twelve commits, one per logical batch, each independently revertable. Full audit trail in docs/mike-2.1-run-log.md.
What changed
Design (Mike 2.1 "warm paper + ink" system)
- New Tailwind v4
@themeblock inglobals.css- near-black accent (#0A0A0A), hairline#E5E7EBborders, no shadows - 4 shadcn primitives rebuilt as ink pills:
Button,Input,Badge,DropdownMenu - Login + signup pages migrated; signup now requires firm name (B2B onboarding)
- App shell split into 64px
IconRail+ 224pxSecondaryNav, Playbooks nav added not-found.tsxmigrated off legacyfont-eb-garamond/ gray scale
AI provider (Vercel AI Gateway)
- All direct provider SDK calls (Anthropic, Gemini, OpenAI) replaced with Vercel AI Gateway via AI SDK v6
- User-facing API key UI (
/account/models) deleted - customers pay subscription, never touch tokens - Per-request
x-user-id/x-org-idheaders for cost attribution - 12 new gateway tests
Billing (Stripe + paywall)
subscriptionstable migration (backend/migrations/001_add_subscriptions.sql) with RLS + auto-trial trigger on signup (14 days, 1M tokens)- Stripe SDK + webhook handler (
checkout.session.completed,customer.subscription.{created,updated,deleted}) requireActiveSubscriptionmiddleware on chat / projectChat / tabular routes - returns 402 with reason on expired trial or quota exhaustion- Token usage tracked fire-and-forget after each stream completes (never blocks hot path)
Billing UI
- Public
/pricingpage - Starter ($99) / Professional ($249) / Enterprise ($499+) with FAQ /account/billingwith token progress bar + Stripe portal buttonTrialBannerin app chrome - shows "Trial ends in N days" + "Upgrade" link- 402 frontend interceptor redirects to
/pricing?reason=...
Dev experience
NEXT_PUBLIC_DEV_AUTH_BYPASS/DEV_AUTH_BYPASSenv flags - skip Supabase auth and paywall locally- Hard-gated by
NODE_ENV !== 'production'- production deploys ignore the flag entirely - Amber "Dev mode" banner shown when active
Cleanup
- 396 LOC of dead code removed (old
AppSidebar,CreditsExhaustedModal,getApiKeyStatus) - New
Playbookspage (empty state, Phase 2 will wire creation) - Pricing-card borders normalized to the universal
border border-borderhairline
Stats
- 12 commits · 60+ files changed
- Backend: 25/25 tests pass ·
tscclean - Frontend: 20/20 tests pass · Next.js build clean
Reviewer notes
- Critical pre-deploy step: after applying
001_add_subscriptions.sql, backfill subscription rows for existing users - they predate the auto-provision trigger and will 402 on first AI call. One-liner indocs/mike-2.1-deploy-checklist.md§3.3. - Env vars required:
AI_GATEWAY_API_KEY,STRIPE_SECRET_KEY,STRIPE_WEBHOOK_SECRET,STRIPE_PRICE_STARTER/PROFESSIONAL/ENTERPRISE. All documented inbackend/.env.example. - Stripe products must be created manually in the Stripe dashboard before deploy - price IDs flow through env vars.
- AI Gateway streaming was NOT smoke-tested against the live endpoint (no key available in this environment). Unit tests cover the call shape end-to-end via mocks. Manual curl smoke recommended before promoting to production.
- Open follow-ups (parked in run log):
ApiKeyMissingModal/modelAvailabilitystill imported by chat/tabular components but backend never triggers them; token counter resets only oncheckout.session.completed(needsinvoice.paidfor monthly cycle); no seat enforcement yet; mobile chrome hides both nav columns; several feature components still useborder-2instead of hairline tokens.
Revert any individual batch
```bash
git log --oneline main..HEAD # see all 12 commits
git revert
Test plan
- Apply migration
001_add_subscriptions.sqlto a Supabase test project - Set Stripe test-mode env vars + create test products
-
cd backend && npm test→ expect 25/25 pass -
cd frontend && npm test→ expect 20/20 pass - Frontend dev with
NEXT_PUBLIC_DEV_AUTH_BYPASS=1- verify dev banner + all routes render - Hit
POST /chatwith no subscription → 402 with{reason: 'no_subscription'} - Sign up new user → check
subscriptionsrow created withtier='trial' - Stripe webhook test event → verify subscription row updates
- Live AI Gateway smoke (manual curl with real
AI_GATEWAY_API_KEY)
Our analysis
Fork Mike 2.0 into a paid SaaS with gateway-routed LLMs and Stripe paywall — read the full analysis →
Phase 1 UI follow-ups: /playbooks stub, pricing border normalization, not-found token migration — read the full analysis →
Dead-code sweep after Mike 2.1 + gateway migrations — read the full analysis →
NODE_ENV-gated auth + paywall bypass for local previews — read the full analysis →
Stripe subscriptions, paywall middleware, and billing UI — read the full analysis →
Auth pages migrated to Mike 2.1 with B2B-required organisation field — read the full analysis →
Chrome shell split: 64px IconRail + 224px SecondaryNav — read the full analysis →
LLM traffic routed through Vercel AI Gateway; customer-supplied keys removed — read the full analysis →
Mike 2.1 design token system + primitive redesigns — read the full analysis →
Think the analysis missed something the PR description covers?
Commits in this PR (12)
| SHA | Subject | Author | Date | |
|---|---|---|---|---|
3da5dd95 | docs(mike-2.1): add design tokens and frontend inventory specs | z | 2026-05-13 | ↗ GitHub |
commit bodyBatch 0a + 0b - pre-implementation reference docs. - docs/mike-2.1-design-tokens.md: bespoke unprefixed token family (B:* nitro and F:* shadcn .pen kits are reference only, the unprefixed tokens are production). - docs/mike-2.0-frontend-inventory.md: catalog of existing primitives + auth pages + chrome shell that Phase 1 migrates. Agent IDs: a431cf5ab43329570 (tokens), ad8271abf2f042093 (inventory). | ||||
7b4dccea | feat(design): apply Mike 2.1 tokens and redesign 4 shadcn primitives | z | 2026-05-13 | ↗ GitHub |
commit bodyBatch 1 - replace globals.css @theme with bespoke Mike 2.1 token
family, rebuild Button/Input/Badge/Dropdown against the new tokens,
add vitest + 3 smoke tests.
- frontend/src/app/globals.css: bespoke unprefixed tokens (production
family). --color-blue/--color-azure preserved as faded blue for
legacy .usc-section / .cfr-section CSS in legal-doc viewer.
--font-eb-garamond next/font wiring kept for legacy .font-eb-garamond
utility.
- frontend/src/components/ui/{button,input,badge,dropdown-menu}.tsx:
rebuilt against Mike 2.1 tokens.
- frontend/vitest.config.ts + vitest.setup.ts: test runner config.
- frontend/src/components/ui/__tests__/primitives.smoke.test.tsx:
3 smoke tests. All pass.
Agent ID: a4c766b6e2d6a274e (general-purpose).
| ||||
3c8ffa8f | feat(auth): migrate login and signup to Mike 2.1 chrome | z | 2026-05-13 | ↗ GitHub |
commit bodyBatch 2 - port login + signup pages from .pen frames (S9ZQTA/tbg1h/gtQ5F login, V3Q1xu signup) to Mike 2.1 design. - Login + signup forms rebuilt against new tokens + primitives. - site-logo updated to Mike 2.1 mark. - Organisation field made REQUIRED (B2B product). - Confirm-password field preserved (functionality preservation). - WorkOS-SSO and magic-link buttons NOT lifted (no wired primitive). - 2 smoke tests in app/__tests__/auth-pages.smoke.test.tsx. Agent ID: a98a89fd5a4635549 (general-purpose). | ||||
571af72d | feat(chrome): split AppSidebar into 64px IconRail and 224px SecondaryNav | z | 2026-05-13 | ↗ GitHub |
commit bodyBatch 3 - replace the legacy collapsible AppSidebar with the Mike 2.1 two-column nav shell (IconRail 64w + SecondaryNav 224w). - frontend/src/components/chrome/icon-rail.tsx: top-level icon column. - frontend/src/components/chrome/secondary-nav.tsx: contextual nav, includes a new Playbooks entry. - frontend/src/app/(pages)/layout.tsx: switch to new chrome. - frontend/src/components/chrome/__tests__/chrome.smoke.test.tsx. .pen frame references: wF9tL, x72bX, FdYC1, yEGYQ, QxArF, CzlFI. Notes: - AppSidebar.tsx left on disk (removed in batch 7 cleanup commit). - Mobile (<md) currently hides both columns; no drawer yet. - TrialBanner mount in this layout lands in batch 6 (billing UI). Agent ID: a36447602e8db8572 (general-purpose). | ||||
07a46ceb | feat(llm): replace direct provider SDKs with Vercel AI Gateway | z | 2026-05-13 | ↗ GitHub |
commit bodyBatch 4 - route all LLM traffic through the Vercel AI Gateway via
AI SDK v6. Tokens are now paid by the platform, not the customer.
Backend:
- New gateway adapter at backend/src/lib/llm/gateway.ts (uses
ai@^6.0.182, "provider/model" strings auto-route via the bundled
gateway provider).
- 12 unit tests in backend/src/lib/llm/__tests__/gateway.test.ts.
- Deleted backend/src/lib/llm/{claude,gemini,openai,tools}.ts and
backend/src/lib/userApiKeys.ts - the per-provider SDKs and the
customer-key vault are gone.
- backend/src/lib/llm/index.ts now re-exports from gateway and routes
streamChatWithTools / completeText through it.
- backend/src/lib/llm/types.ts: added StreamChatAttribution
({ userId?, orgId? }) for gateway cost-slicing; UserApiKeys kept
as @deprecated alias so existing call-sites still typecheck.
- backend/src/lib/userSettings.ts: drop per-user api_keys lookup.
- backend/src/lib/chatTools.ts: runLLMStream now forwards attribution
instead of apiKeys and surfaces aggregate usage.totalTokens.
- backend/src/routes/{chat,projectChat,tabular}.ts: attribution
({ userId }) replaces apiKeys; legacy queryGemini/title helpers
refactored to take userId.
- backend/src/routes/user.ts: deleted GET/PUT /user/api-keys.
- backend/.env.example: replaced GEMINI_/ANTHROPIC_/OPENAI_API_KEY
and USER_API_KEYS_ENCRYPTION_SECRET with AI_GATEWAY_API_KEY.
Frontend:
- Deleted frontend/src/app/(pages)/account/models/page.tsx - the
user-facing API key UI no longer applies.
- frontend/src/app/(pages)/account/layout.tsx: removed the
"Models & API Keys" tab (Billing tab arrives in batch 6).
Intermediate state notes:
- frontend mikeApi.ts still exports getApiKeyStatus + saveApiKey
against now-404 routes; UserProfileContext still imports them.
Both are cleaned up in batches 6/8.
- public.user_api_keys table is not dropped here - orphaned but
harmless; can be removed in a follow-up migration.
Agent ID: a5511de9cafb2355a (vercel:ai-architect).
| ||||
ba00445b | feat(billing): add Stripe subscriptions and paywall middleware | z | 2026-05-13 | ↗ GitHub |
commit bodyBatch 5 - gate AI chat endpoints behind an active subscription
(or active 14-day trial) and record token usage against the
subscription's monthly limit.
Database:
- backend/migrations/001_add_subscriptions.sql: idempotent
(CREATE IF NOT EXISTS), RLS-enabled, auto-provisions a trial
row on every new auth.users insert via trigger.
- backend/schema.sql: mirror of the migration appended.
Backend:
- backend/src/lib/stripe.ts: Stripe SDK wiring.
- backend/src/lib/billing/usage.ts: recordTokenUsage() fire-and-
forget bookkeeping after stream completion (never blocks the
hot path, never throws).
- backend/src/routes/billing.ts: checkout/portal/webhook endpoints.
Handles checkout.session.completed and
customer.subscription.{created,updated,deleted}.
- backend/src/middleware/requireActiveSubscription.ts: paywall.
- backend/src/index.ts: mount raw-body parser ahead of express.json
for the Stripe webhook signature check; gate POST /chat,
/projects/:id/chat, /tabular-review/:id/chat, and
/tabular-review/:id/generate with requireAuth + paywall.
- backend/.env.example: STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET,
and STRIPE_PRICE_{STARTER,PROFESSIONAL,ENTERPRISE}.
- backend/package.json: stripe@^22.1.1 + vitest test runner.
- routes/{chat,projectChat,tabular}.ts: void recordTokenUsage(...)
after each successful stream.
Frontend:
- frontend/src/lib/billing.ts: tier metadata + helpers.
- frontend/src/app/lib/mikeApi.ts: HTTP 402 interceptor redirects
the browser to /pricing?reason=... on paywall rejection.
Tests:
- 6 middleware tests + 4 webhook tests (10 new tests total).
Agent ID: aa83ddc67d4dfac03 (general-purpose).
| ||||
d3956d04 | feat(billing-ui): pricing page, billing settings, and trial banner | z | 2026-05-13 | ↗ GitHub |
commit bodyBatch 6 - surface the new subscription state in the product: public pricing page, in-app billing settings tab, and a top-of- main trial countdown banner. - frontend/src/app/pricing/page.tsx: public /pricing route, lands logged-out users redirected from a 402. Enterprise CTA stubs out to mailto:sales@mike.ai (placeholder). - frontend/src/app/(pages)/account/billing/page.tsx: in-app billing settings - current plan, trial status, token usage, manage button (Stripe customer portal). - frontend/src/components/chrome/trial-banner.tsx: countdown banner for trialing users, mounted at top of <main> in (pages)/layout.tsx. - frontend/src/app/(pages)/account/layout.tsx: re-add the "Billing" tab (replaces the "Models & API Keys" tab removed in batch 4). - frontend/src/app/lib/mikeApi.ts: SubscriptionTier/Status/Info types + subscription field on UserProfile. - frontend/src/contexts/UserProfileContext.tsx: thread subscription through the profile context. - backend/src/routes/user.ts: return the subscription block on the /user/profile response. - 5 new tests (3 pricing + 2 billing). Agent ID: ad65cb0ceb7e70da6 (general-purpose). | ||||
9974612f | chore: remove dead code (AppSidebar, CreditsExhaustedModal, getApiKeyStatus) | z | 2026-05-13 | ↗ GitHub |
commit bodyBatch 7 - refactor-cleaner sweep. Three call-site-less files removed after the Mike 2.1 chrome migration: - frontend/src/app/components/shared/AppSidebar.tsx (311 LOC): superseded by IconRail + SecondaryNav in batch 3. - frontend/src/app/components/modals/credits-exhausted-modal.tsx (81 LOC): credit-system modal, no remaining importers after the subscription/paywall flow in batches 5-6. - frontend/src/app/lib/mikeApi.ts getApiKeyStatus() (4 LOC): hits a route that no longer exists since batch 4. Left alone (still has active importers): ApiKeyMissingModal, modelAvailability helpers. Agent ID: aca933ce4c3383624 (refactor-cleaner). | ||||
2034b855 | docs(mike-2.1): add deploy checklist and run log | z | 2026-05-13 | ↗ GitHub |
commit bodyBatch 8 - final smoke + paper trail. The deploy checklist verifies backend + frontend builds, paywall coverage, presence of all generated files, and lists 9 known follow-ups parked for the Phase 1 hotfix backlog. The run log is the per-batch audit trail referenced above with the SHAs of commits 1-8. Agent ID: a6969272e17743cb6 (general-purpose). | ||||
85348e4c | feat(dev): add NODE_ENV-gated auth + paywall bypass for local previews | z | 2026-05-13 | ↗ GitHub |
commit bodyAdds NEXT_PUBLIC_DEV_AUTH_BYPASS (frontend) and DEV_AUTH_BYPASS (backend) flags. Both require non-production NODE_ENV. When on: - Frontend skips Supabase auth, injects stub user + Professional-tier subscription, and shows an amber "dev mode" banner. - Backend skips JWT verification and paywall middleware. Production deployments ignore these flags by NODE_ENV check. | ||||
3d1b1b69 | docs: record batch 10 commit SHA in run log | z | 2026-05-13 | ↗ GitHub |
82d36cca | fix(ui): add /playbooks page, normalize pricing borders, migrate not-found to Mike 2.1 tokens | z | 2026-05-14 | ↗ GitHub |
commit body- Add stub /playbooks route with empty state so the IconRail nav entry resolves (resolves deploy-checklist follow-up #6). - Normalize all three pricing cards to the universal hairline `border border-border`; drop `border-2 border-border-active` on the Professional card. "Most popular" is now signaled only by the badge. - Migrate not-found.tsx off legacy tokens (font-eb-garamond, text-gray-500, raw bg-gray-900 anchor) to bg-bg-canvas / foreground / muted-foreground + Button asChild + next/link. | ||||
Capture this PR into my fork
Download a Markdown prompt that tells Claude how to port every
commit in this PR into your working tree. Run it via
claude -p < capture-pull-1.md from
inside the repo you want the changes in.