# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Commands ```bash # Development pnpm dev # Start dev server at http://localhost:3000 pnpm build # Production build (uses Turbopack by default) pnpm start # Start production server # Note: If Turbopack fails, use: pnpm build -- --webpack # Database (direct SQL or docker) docker-compose -f docker-compose.dev.yml exec db psql -U postgres -d tia ``` **Note:** Running from `/Users/manohar_air/MyProjects/Tia/tia` directory. ## Architecture ### Tech Stack - **Framework:** Next.js 16 with App Router (src/app/) - **Database:** PostgreSQL 16 with pgvector + Drizzle ORM - **Auth:** Database sessions with httpOnly cookies - **AI:** LiteLLM gateway → MiniMax model (minimax-2.7) - **Storage:** Cloudflare R2 for media uploads - **Styling:** Tailwind CSS v4 ### Project Structure ``` src/ ├── app/ # Next.js App Router pages │ ├── api/ # API routes (auth, logs, ai, growth, etc.) │ ├── page.tsx # Home (Quick Log + AI card) │ ├── ai/ # AI chat page with sidebar │ ├── medical/ # Vaccination tracking │ ├── growth/ # Growth charts │ ├── memories/ # Photo gallery │ ├── menu/ # Navigation menu │ ├── onboarding/ # First-time setup │ ├── settings/ # Settings with theme picker │ ├── login/ # User login (email + password) │ ├── admin/ # Admin panel │ └── admin-login/ # Admin login (separate) ├── ThemeProvider.tsx # Theme context (light/dark/system/time) ├── FamilyProvider.tsx # Family/child context (resolves from session) drizzle/ # Database migrations docs/ # Design docs ``` ### Database - **Migrations:** SQL files in `drizzle/` (not using drizzle-kit push) - **Apply:** `psql` directly or via docker-compose exec - **RLS:** Row-level security for multi-family isolation ### Data Models - **Family:** Parent account container - **Members:** Adults in family (mom, dad, etc.) via `family_members` - **Children:** Baby profiles with birth date - **Sessions:** Login sessions with httpOnly cookies - **Logs:** Feed, sleep, diaper entries with timestamps - **Vaccinations:** IAP schedule tracking - **Growth:** Weight/height over time - **Memories:** Photos with R2 storage - **Chat Sessions:** User conversations with AI (chat_sessions, chat_messages) ### Key Patterns **ThemeProvider:** Wrap app in ThemeProvider from layout.tsx. Use `useTheme()` hook in components. ```typescript import { useTheme } from "./ThemeProvider"; const { theme, toggle, setMode } = useTheme(); // theme: "light" | "dark" // mode: "light" | "dark" | "system" | "time" ``` **FamilyProvider:** Resolves family from database session on login. ```typescript import { useFamily } from "./FamilyProvider"; const { familyId, familyName, child, children, tier, memberCount } = useFamily(); // familyId: string | null // familyName: string | null (from session) // child: Child | null // children: Child[] // tier: "free" | "pro" // memberCount: number (from family_members table) ``` **Offline Queue:** Uses localStorage (`tia_offline_queue`) for failed API calls, retries when online. **Session Validation:** All data API routes must use functions from `@/lib/auth`: ```typescript import { validateSession, requireFamily, requireOwnership } from "@/lib/auth"; // Require family for user data export async function GET(request: Request) { const auth = await requireFamily(); if (!auth.success) { return NextResponse.json({ error: auth.error }, { status: auth.status }); } const familyId = auth.session!.familyId!; // ... route logic } // Require ownership for specific resources const ownership = await requireOwnership(childId, "children", "Child"); if (!ownership.success) { return NextResponse.json({ error: ownership.error }, { status: ownership.status }); } ``` **Chat Sessions:** Stored in localStorage (`tia_chat_sessions`) - shared between home page AI card and /ai page. Database tables: `chat_sessions`, `chat_messages`. **API Routes:** Return standard JSON `{ success: true, items: [...] }` format for lists. **AI Integration:** - Route: `/api/ai` → LiteLLM at `https://llm.manohargupta.com` - Model: `minimax-2.7` - See `/docs/debugging.md` for troubleshooting ## Authentication (Email + Password) 1. User visits `/login` with email + password (or signs up for new account) 2. API `/api/auth/signin` verifies password hash and creates session in `sessions` table 3. Session token stored in **httpOnly cookie** (NOT localStorage!) 4. Password stored with simple hash in `users.password_hash` ### Tables Used - **users:** User accounts (email, name) - **families:** Family accounts (name, tier, limits) - **family_members:** Links users to families (user_id, family_id, role) - **children:** Child profiles (name, birth_date, family_id) - **sessions:** Login sessions (session_token, user_id, expires) ### NEVER use localStorage for: - authentication tokens - family_id after login - Any data that should persist across devices ### localStorage Acceptable For: - Theme preference (user-specific display only) - Temporary cache (offline queue for retry) - Chat sessions local cache (synced from database) ## Admin Panel Access at: `/admin-login` (username: `admin`, password: `admin123`) ### Pages - `/admin` - Dashboard with clickable stat cards - `/admin/families` - Manage families (create, view/add/remove members, set tier) - `/admin/users` - Manage users (add to family, password status, delete) - `/admin/children` - Manage children - `/admin/revenue` - Revenue analytics - `/admin/analytics` - Feature usage - `/admin/support` - Support tickets - `/admin/settings` - Platform settings ## Data Storage Consistency ### RULE: All user data must persist to database, NOT localStorage | Data Type | Storage | API Key | Persists After Refresh | Persists After Logout | |----------|---------|--------|------------------------|-------------------| | Children | Database | `/api/children` | ✅ Yes | ✅ Yes | | Activity Logs | Database | `/api/logs` | ✅ Yes | ✅ Yes | | Vaccinations | Database | `/api/vaccinations` | ✅ Yes | ✅ Yes | | Growth Records | Database | `/api/growth` | ✅ Yes | ✅ Yes | | User Profile | Database | `/api/auth/profile` | ✅ Yes | ✅ Yes | | Memories/Photos | Database + R2 | `/api/upload` | ✅ Yes | ✅ Yes | | Auth Session | Database + Cookie | `/api/auth/signin` | ✅ Yes | ✅ No | | Theme | localStorage | `tia_theme` | ✅ Yes | ✅ Yes | | Chat Sessions | Database | `/api/chat` | ✅ Yes | ✅ Yes | | Offline Queue | localStorage | `tia_offline_queue` | ✅ Yes | ❌ No | ## R2 Storage (Cloudflare) ### Setup 1. **Create bucket** in Cloudflare Dashboard → R2 2. **Create API token** with "Object Read & Write" permissions 3. **Enable Public Development URL** in bucket settings (gives pub-*.r2.dev URL) ### API Endpoint Format The S3 API endpoint is: `https://.r2.cloudflarestorage.com` For your bucket named "tia": - Account ID: `e71f22a2f8614fb3ba6d9b28a264d8ce` - S3 Endpoint: `https://e71f22a2f8614fb3ba6d9b28a264d8ce.r2.cloudflarestorage.com` - Public URL: `https://pub-37a76fd657c94d1dbc521a109c087a11.r2.dev` (no bucket name in path!) ### Code Example ```typescript const client = new S3Client({ region: "auto", endpoint: `https://${accountId}.r2.cloudflarestorage.com`, credentials: { accessKeyId, secretAccessKey }, }); // List objects const command = new ListObjectsV2Command({ Bucket: "tia" }); const res = await client.send(command); // Get presigned upload URL const command = new PutObjectCommand({ Bucket: "tia", Key: key, ContentType }); const url = await getSignedUrl(client, command, { expiresIn: 3600 }); ``` ### Key Learnings 1. **S3 API endpoint** does NOT include bucket name: `https://...r2.cloudflarestorage.com` 2. **Public URL** does NOT include bucket path: `https://pub-...r2.dev` (NOT `/tia/...`) 3. **CORS** needs GET and PUT methods for uploads 4. ListObjects needs bucket name in command, not endpoint ## Environment Variables Set in `.env.local` for development, or in Dokploy dashboard for production. Required: - `DATABASE_URL` - PostgreSQL connection (as `tia_app` role after H2.1) - `DATABASE_URL_SUPERUSER` - Superuser connection (for migrations only) - `LITELLM_URL` - AI gateway URL - `LITELLM_KEY` - AI API key - `R2_ACCOUNT_ID` - Cloudflare R2 account ID - `R2_ACCESS_KEY_ID` - R2 access key - `R2_SECRET_ACCESS_KEY` - R2 secret key - `R2_BUCKET_NAME` - R2 bucket name (e.g., "tia") - `R2_PUBLIC_URL` - Public R2 URL - `CRON_SECRET` - Secret for cron backup endpoint ### Security Patterns All data API routes must validate sessions using `@/lib/auth`: ```typescript import { requireFamily, requireOwnership } from "@/lib/auth"; export async function GET(request: Request) { const auth = await requireFamily(); if (!auth.success) { return NextResponse.json({ error: auth.error }, { status: auth.status }); } // ... route logic } ``` ### Current Security Status (May 2026) - **RLS (Row-Level Security):** DISABLED on family_members and children tables (was blocking INSERTs) - **App-level security:** All routes use `requireFamily()` and `requireOwnership()` checks - **This is secure because:** All API routes validate session before returning data - **To re-enable RLS later:** Add proper INSERT bypass policy, keep RLS for SELECT only AI routes use medical guardrails from `@/lib/ai/medical-triggers`: ```typescript import { detectMedicalIntent } from "@/lib/ai/medical-triggers"; const intent = detectMedicalIntent(query); if (intent.isMedical) { // Redirect to pediatrician } ``` ## Known Issues ### Turbopack Parsing Issue Some files may cause "Unterminated regexp literal" errors when building with Turbopack. If build fails: 1. Try building with Webpack instead: ```bash pnpm build -- --webpack ``` 2. The issue is in SWC parser - avoid patterns like `name: "TT/Td"` in arrays as the `/` can be interpreted as regex 3. Apply fixes one change at a time between builds - cumulative changes can confuse Turbopack's cache