Skip to content

Latest commit

 

History

History
94 lines (74 loc) · 8.63 KB

File metadata and controls

94 lines (74 loc) · 8.63 KB

CLAUDE.md

Last Updated: 2026-03-26

Project Context

ThinkHaven - Decision accelerator for structured AI sessions

  • Tech Stack: Next.js 15.5, React 19, TypeScript, Supabase, Stripe, Anthropic Claude
  • Architecture: Monorepo with Next.js app in apps/web/
  • Deployment: Vercel project thinkhaven (https://thinkhaven.co)
  • Documented Solutions: docs/solutions/ contains searchable solution notes for past bugs, architecture patterns, conventions, and workflow issues, organized by category with YAML frontmatter (module, problem_type, tags). Relevant when implementing or debugging in documented areas.

Essential Commands

All commands run from apps/web/:

npm run dev              # Dev server (localhost:3000, Turbopack)
npm run build            # Production build
npm run lint             # ESLint
npm test                 # Unit tests (Vitest, watch mode)
npm run test:run         # Unit tests (once)
npm run test:e2e         # E2E tests (Playwright, 7 smoke tests)
npm run test:prod        # Smoke tests against production (15 tests)

Migrations: apps/web/supabase/migrations/ (001 → 017, sequential, never skip)

Route Architecture

Protected (/app/* - requires auth):

  • /app - Dashboard
  • /app/new - New session
  • /app/session/[id] - Active session workspace
  • /app/account - Account settings

Public: / (landing), /try (guest, 10 msg limit), /demo, /assessment

Legacy redirects: /dashboard/app, /bmad/app/new, /workspace/[id]/app/session/[id], /account/app/account

Environment Variables

See .env.example. Required: NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY, ANTHROPIC_API_KEY, STRIPE_SECRET_KEY, NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY, STRIPE_WEBHOOK_SECRET, NEXT_PUBLIC_APP_URL. Optional: OPENROUTER_API_KEY (server-only fallback for guest synthesis when Anthropic is unavailable; OPENROUTER_MODEL and AI_PROVIDER=openrouter tune it — streaming/tool chat paths are not covered).

Model selection is per-workload via lib/ai/model-config.ts (modelFor(workload)), NOT a single global. Cost-preservation posture: defaults are synthesis/board/chat→claude-sonnet-4-6, util→claude-haiku-4-5 — NO frontier model (Fable 5 / Opus 4.8) is ever a default; they are opt-in only via ANTHROPIC_MODEL_{SYNTHESIS,BOARD,CHAT,UTIL}. ANTHROPIC_MODEL is a global kill-switch (forces every workload onto one model). Synthesis falls back to OpenRouter on Anthropic 402/429/5xx. Frontier models (Fable 5, Opus 4.8/4.7) 400 on temperature — use samplingFor(model, t) to build the sampling slice, never hardcode temperature in a messages.create() call. Reasoning effort: synthesis/board send output_config.effort: "high" via effortConfigFor(model, workload) (model-gated — stripped on models that don't support effort); tune with ANTHROPIC_EFFORT_{SYNTHESIS,BOARD} (low|medium|high|max).

Testing

  • Unit tests: **/*.test.{ts,tsx}, setup in tests/setup.ts
  • E2E: tests/e2e/smoke/health.spec.ts - 7 public route smoke tests, all passing in CI
  • Prod: tests/e2e/smoke/beta-checklist.spec.ts - 9 production verification tests (npm run test:prod)
  • Config: vitest.config.ts, playwright.config.ts, playwright.prod.config.ts (production)
  • Unit suite is fully green (61 files / 528 tests pass, 2 files + 41 tests intentionally skipped). Keep it that way — failures are regressions now, not baseline noise.

Configuration

  • next.config.ts - Next.js config
  • tailwind.config.cjs - Tailwind (CommonJS format)
  • eslint.config.mjs - Linting rules

Common Pitfalls

  1. Middleware active (middleware.ts) - Validates JWTs via supabase.auth.getUser() and refreshes tokens. Runs in Edge Runtime; avoid Node-only APIs in middleware context.
  2. Credit deduction - ALWAYS use deduct_credit_transaction() for atomicity, never manual UPDATE
  3. File-system routing - Every route needs a page.tsx file
  4. Migration order - Sequential (001 → 017), never skip
  5. Stripe webhooks - Verify signatures with stripe-service.ts.constructWebhookEvent()
  6. Tldraw v4 - Use getSnapshot(store) / loadSnapshot(store, data), NOT instance methods
  7. Agentic tool loop - Max 5 rounds per message (MAX_TOOL_ROUNDS in /api/chat/stream/route.ts)
  8. Tool results - Use ToolExecutor.formatResultsForClaude() for Claude's expected tool_result format
  9. Session ops - Use session-primitives.ts functions, not direct Supabase calls
  10. Supabase server client - createClient() returns null when env vars missing; callers must null-check
  11. Next.js env files - Only reads .env, .env.local, .env.production (NOT .env.test)
  12. SSG safety - lib/supabase/client.ts exports a no-op Proxy for SSG; use for client components
  13. Monorepo git paths - Always use absolute paths or run git commands from repo root, not apps/web/
  14. Claude Code Review CI - anthropics/claude-code-action@v1 has known SDK crash (issue #911). continue-on-error: true is set. Job may show failed but workflow passes.
  15. Never use text-secondary for text - shadcn maps --secondary to parchment (a background color), producing invisible text on cream/parchment. Use text-muted-foreground (slate-blue) for secondary text, or text-ink-light for warm secondary text.
  16. Hooks must re-throw or return errors - If a hook catches an error and only sets state, callers can't detect failure. Always re-throw after setting error state, or return a success/failure indicator. Silent swallowing creates impossible UI states.
  17. IDOR checks on every session-scoped handler - When adding ownership verification to API routes, audit ALL handlers in the file, not just the ones that triggered the fix. Partial coverage is worse than none (false sense of security).
  18. React ErrorBoundaries don't catch event handlers - Errors in onClick, onSubmit, etc. are not caught by ErrorBoundary. Wrap event handler bodies in try/catch or use error state for recovery.
  19. Design system source of truth - The actual design system lives in globals.css (CSS variables, component classes) and tailwind.config.cjs (Tailwind tokens). Do NOT trust docs/design/MASTER.md if it reappears — it's auto-generated by ui-ux-pro-max with wrong colors/fonts.
  20. Rate limiting - Use RateLimiter.createLimitResponse(resetTime) from lib/security/rate-limiter.ts for 429 responses. Never hand-roll the response. Admin bypass check (isAdminEmail) must run BEFORE rate limit check so admins aren't blocked.
  21. Lazy module-level init for env vars - Never use IIFEs or top-level const x = fn() that reads env vars at import time. Vercel builds don't have runtime env vars, causing build crashes. Use lazy getter functions instead (see getAppUrl() in stripe-service.ts).
  22. Branded error pages exist - app/error.tsx (runtime errors) and app/not-found.tsx (404s) are in place. Use them as patterns for sub-route-group error handling. Both use the design system (cream/terracotta/ink).
  23. In-memory rate limiter is per-instance - RateLimiter uses a static Map, so state isn't shared across Vercel serverless instances. Acceptable for beta; needs Redis/database-backed solution at scale.
  24. Client components cannot import server-only modules - 'use client' files cannot transitively import modules that use next/headers or other server-only APIs. session-primitives.ts imports supabase/server.ts, so client components must not import from it. Shared types/constants go in a separate client-safe file (e.g., pathway-labels.ts). This broke a Vercel build.
  25. Heavy libraries must be dynamically imported - Mermaid (~250KB gzip), Excalidraw (~500KB), and similar large packages must use next/dynamic with ssr: false. Never import at module scope in components rendered on every page. Gate behind user action or lazy-load on first use.
  26. Sanitize dangerouslySetInnerHTML from third-party libs - Any dangerouslySetInnerHTML with output from mermaid, markdown renderers, or similar must be sanitized with DOMPurify. Mermaid's securityLevel: 'strict' has had bypasses (CVE-2023-20052). Defense in depth.
  27. Radix Dialog for all new modals - Hand-rolled modals lack focus trap, Escape handling, and aria-modal. @radix-ui/react-dialog is installed. Use it for all new modals. Existing hand-rolled modals (SignupPromptModal, ExportDialog, BranchDialog) are tracked for migration.

Production Deployment

  • Vercel: Auto-deploys on push to main
  • Manual: cd apps/web && vercel --prod
  • Env vars: Set via Vercel dashboard (Settings -> Environment Variables)
  • URL: https://thinkhaven.co