--- type: project-doc parent: "[[Tia]]" status: verified tags: [tia, architecture, engineering] --- # 🏗️ Tia — Architecture > Verified from CLAUDE.md + memory files. ## Stack Next.js 16 (App Router, Turbopack) · PostgreSQL 16 + **pgvector** (pgvector/pgvector:pg18 on prod) · **Drizzle ORM** · **LiteLLM** gateway → **MiniMax** (minimax-2.7) · **Cloudflare R2** · **Resend** · Tailwind v4 · **Dokploy** (auto-migrate on deploy). ## Route groups ([[Decision Log#TD-005]]) - `(marketing)/` → `/`, `/pricing`, `/privacy`, `/terms` — **static, no DB/auth imports** - `(app)/` → authenticated app with ThemeProvider + FamilyProvider + BottomNav; dashboard at `/home` ## Auth - User: `/login` → `POST /api/auth/signin` (bcrypt) → session row → httpOnly `tia_session`; routes use `requireFamily()` - Admin: `/admin-login` → `tia_admin_session`; admin layout is a server component calling `verifyAdminSession()` ## Data & context - `FamilyProvider` resolves family/child from session: `{ familyId, child, children, tier, memberCount, updateChildImage }` - Quota logic in `src/lib/quota.ts` (pure fns + queries); usage always derived via `SUM` ([[Decision Log#TD-001]]) ## Migrations (the big gotcha) ([[Decision Log#TD-003]]) - `migrate.mjs` runs before `server.js`; reads all SQL in `drizzle/`, applies any not in `__drizzle_migrations` - **`when` must be `Date.now()`** (> 1779539431897) or Drizzle silently skips it - Hot-apply via `POST /api/debug-migration` (header `x-run-migration: yes`), idempotent steps ## Media (R2) ([[Decision Log#TD-004]]) - 3-step upload: init → server-proxied `PUT /api/upload` → save URL - **Never** direct cross-origin PUT; **never** raw `*.r2.dev` in `` — use `/api/img?key=` proxy ## Observability (admin) `error_events` table + `logError()` + `POST /api/errors`; `/admin/{health,errors,audit,ai}` over `audit_log`, `ai_usage` (p95 via `percentile_cont`). ## Related [[Tia]] · [[Tia - Decisions]] · [[Self-Hosting]] · [[Engineering Overview]]