|
| 1 | +# AGENTS.md |
| 2 | + |
| 3 | +*Last Updated: 2026-03-26* |
| 4 | + |
| 5 | +## Project Context |
| 6 | +**ThinkHaven** - Decision accelerator for structured AI sessions |
| 7 | +- **Tech Stack**: Next.js 15.5, React 19, TypeScript, Supabase, Stripe, Anthropic Codex |
| 8 | +- **Architecture**: Monorepo with Next.js app in `apps/web/` |
| 9 | +- **Deployment**: Vercel project `thinkhaven` (https://thinkhaven.co) |
| 10 | + |
| 11 | +## Essential Commands |
| 12 | + |
| 13 | +All commands run from `apps/web/`: |
| 14 | +```bash |
| 15 | +npm run dev # Dev server (localhost:3000, Turbopack) |
| 16 | +npm run build # Production build |
| 17 | +npm run lint # ESLint |
| 18 | +npm test # Unit tests (Vitest, watch mode) |
| 19 | +npm run test:run # Unit tests (once) |
| 20 | +npm run test:e2e # E2E tests (Playwright, 7 smoke tests) |
| 21 | +npm run test:prod # Smoke tests against production (15 tests) |
| 22 | +``` |
| 23 | + |
| 24 | +Migrations: `apps/web/supabase/migrations/` (001 → 017, sequential, never skip) |
| 25 | + |
| 26 | +## Route Architecture |
| 27 | + |
| 28 | +**Protected** (`/app/*` - requires auth): |
| 29 | +- `/app` - Dashboard |
| 30 | +- `/app/new` - New session |
| 31 | +- `/app/session/[id]` - Active session workspace |
| 32 | +- `/app/account` - Account settings |
| 33 | + |
| 34 | +**Public**: `/` (landing), `/try` (guest, 10 msg limit), `/demo`, `/assessment` |
| 35 | + |
| 36 | +**Legacy redirects**: `/dashboard` → `/app`, `/bmad` → `/app/new`, `/workspace/[id]` → `/app/session/[id]`, `/account` → `/app/account` |
| 37 | + |
| 38 | +## Environment Variables |
| 39 | + |
| 40 | +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` |
| 41 | + |
| 42 | +## Testing |
| 43 | + |
| 44 | +- Unit tests: `**/*.test.{ts,tsx}`, setup in `tests/setup.ts` |
| 45 | +- E2E: `tests/e2e/smoke/health.spec.ts` - 7 public route smoke tests, all passing in CI |
| 46 | +- Prod: `tests/e2e/smoke/beta-checklist.spec.ts` - 9 production verification tests (`npm run test:prod`) |
| 47 | +- Config: `vitest.config.ts`, `playwright.config.ts`, `playwright.prod.config.ts` (production) |
| 48 | +- 45/71 unit test files fail (pre-existing, not regressions). mary-persona: 67/67 pass. |
| 49 | + |
| 50 | +## Configuration |
| 51 | + |
| 52 | +- `next.config.ts` - Next.js config |
| 53 | +- `tailwind.config.cjs` - Tailwind (CommonJS format) |
| 54 | +- `eslint.config.mjs` - Linting rules |
| 55 | + |
| 56 | +## Common Pitfalls |
| 57 | + |
| 58 | +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. |
| 59 | +2. **Credit deduction** - ALWAYS use `deduct_credit_transaction()` for atomicity, never manual UPDATE |
| 60 | +3. **File-system routing** - Every route needs a `page.tsx` file |
| 61 | +4. **Migration order** - Sequential (001 → 017), never skip |
| 62 | +5. **Stripe webhooks** - Verify signatures with `stripe-service.ts.constructWebhookEvent()` |
| 63 | +6. **Tldraw v4** - Use `getSnapshot(store)` / `loadSnapshot(store, data)`, NOT instance methods |
| 64 | +7. **Agentic tool loop** - Max 5 rounds per message (`MAX_TOOL_ROUNDS` in `/api/chat/stream/route.ts`) |
| 65 | +8. **Tool results** - Use `ToolExecutor.formatResultsForClaude()` for Codex's expected `tool_result` format |
| 66 | +9. **Session ops** - Use `session-primitives.ts` functions, not direct Supabase calls |
| 67 | +10. **Supabase server client** - `createClient()` returns null when env vars missing; callers must null-check |
| 68 | +11. **Next.js env files** - Only reads `.env`, `.env.local`, `.env.production` (NOT `.env.test`) |
| 69 | +12. **SSG safety** - `lib/supabase/client.ts` exports a no-op Proxy for SSG; use for client components |
| 70 | +13. **Monorepo git paths** - Always use absolute paths or run git commands from repo root, not `apps/web/` |
| 71 | +14. **Codex Review CI** - `anthropics/Codex-action@v1` has known SDK crash (issue #911). `continue-on-error: true` is set. Job may show failed but workflow passes. |
| 72 | +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. |
| 73 | +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. |
| 74 | +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). |
| 75 | +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. |
| 76 | +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. |
| 77 | +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. |
| 78 | +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`). |
| 79 | +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). |
| 80 | +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. |
| 81 | +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. |
| 82 | +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. |
| 83 | +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. |
| 84 | +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. |
| 85 | + |
| 86 | +## Production Deployment |
| 87 | + |
| 88 | +- **Vercel**: Auto-deploys on push to main |
| 89 | +- **Manual**: `cd apps/web && vercel --prod` |
| 90 | +- **Env vars**: Set via Vercel dashboard (Settings -> Environment Variables) |
| 91 | +- **URL**: https://thinkhaven.co |
0 commit comments