diff --git a/.claude/settings.local.json b/.claude/settings.local.json
new file mode 100644
index 0000000..596566f
--- /dev/null
+++ b/.claude/settings.local.json
@@ -0,0 +1,30 @@
+{
+ "permissions": {
+ "allow": [
+ "Bash(node \"T:/CC-Projekte/.claude/gsd-core/bin/gsd-tools.cjs\" query commit \"docs: initialize project\" --files .planning/PROJECT.md)",
+ "Bash(mkdir -p .planning)",
+ "Bash(node \"T:/CC-Projekte/.claude/gsd-core/bin/gsd-tools.cjs\" query config-new-project '{\"mode\":\"yolo\",\"granularity\":\"standard\",\"parallelization\":true,\"commit_docs\":true,\"model_profile\":\"balanced\",\"workflow\":{\"research\":true,\"plan_check\":true,\"verifier\":true,\"nyquist_validation\":true,\"auto_advance\":false},\"plan_review\":{\"source_grounding\":true},\"ship\":{\"pr_body_sections\":[{\"heading\":\"User Stories & Acceptance Criteria\",\"enabled\":true,\"source\":\"REQUIREMENTS.md ## User Stories || REQUIREMENTS.md ## Acceptance Criteria\",\"fallback\":\"- Acceptance criteria are covered by the linked requirements and verification evidence.\"},{\"heading\":\"Risks & Dependencies\",\"enabled\":true,\"source\":\"PLAN.md ## Risks || PLAN.md ## Dependencies\",\"fallback\":\"- No known high-risk rollout dependencies.\"},{\"heading\":\"Success Metrics & Release Criteria\",\"enabled\":true,\"source\":\"REQUIREMENTS.md ## Definition of Done || VERIFICATION.md ## Release Criteria\",\"fallback\":\"- Release when automated verification and required manual checks pass.\"},{\"heading\":\"Stakeholder Review & Approval\",\"enabled\":false,\"template\":\"- Product owner approval pending for {phase_name}.\"}]}}')",
+ "Bash(node \"T:/CC-Projekte/.claude/gsd-core/bin/gsd-tools.cjs\" query commit \"chore: add project config\" --files .planning/config.json)",
+ "Bash(python3 *)",
+ "Bash(rm .planning/research/write_summary.py)",
+ "Bash(git init *)",
+ "Bash(git add *)",
+ "Bash(git commit -m 'docs: complete project research for OnlyPets *)",
+ "Bash(node \"T:/CC-Projekte/.claude/gsd-core/bin/gsd-tools.cjs\" query commit \"docs: define v1 requirements\" --files .planning/REQUIREMENTS.md)",
+ "Skill(gsd:ui-phase)",
+ "Bash(node \"T:/CC-Projekte/.claude/gsd-core/bin/gsd-tools.cjs\" query state.advance-plan --phase 1 --plan \"01-01\")",
+ "Bash(mkdir -p \"T:/CC-Projekte/OnlyPets/src/app/\\(auth\\)/sign-up/[[...sign-up]]\")",
+ "Bash(mkdir -p \"T:/CC-Projekte/OnlyPets/src/app/\\(auth\\)/log-in/[[...sign-in]]\")",
+ "Bash(npx tsc *)",
+ "Bash(node -e \"const c = require\\('@clerk/nextjs'\\); console.log\\(Object.keys\\(c\\).filter\\(k => k.includes\\('useSign'\\) || k.includes\\('Hook'\\)\\)\\)\")",
+ "Bash(node -e ' *)",
+ "Bash(xargs grep -l \"SignInSignalValue\")",
+ "Bash(npx vitest *)",
+ "Bash(git commit -m ' *)",
+ "Bash(gsd-tools query *)",
+ "Bash(git commit *)",
+ "Bash(Test-Path *)",
+ "Bash(git mv *)"
+ ]
+ }
+}
diff --git a/.gitignore b/.gitignore
index 68d82a4..e584102 100644
--- a/.gitignore
+++ b/.gitignore
@@ -43,3 +43,6 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts
+
+# clerk configuration (can include secrets)
+/.clerk/
diff --git a/.planning/STATE.md b/.planning/STATE.md
index d1cf5d4..c90939c 100644
--- a/.planning/STATE.md
+++ b/.planning/STATE.md
@@ -4,13 +4,13 @@ milestone: v1.0
milestone_name: milestone
status: executing
stopped_at: Phase 1 plans complete (01-01 through 01-04)
-last_updated: "2026-06-05T15:29:08.194Z"
-last_activity: 2026-06-05 — Phase 1 plans created (01-01 through 01-04); ready for execution
+last_updated: "2026-06-06T06:40:22.471Z"
+last_activity: 2026-06-05 -- Phase 1 execution started
progress:
total_phases: 7
completed_phases: 0
total_plans: 4
- completed_plans: 0
+ completed_plans: 1
percent: 0
---
@@ -25,10 +25,10 @@ See: .planning/PROJECT.md (updated 2026-06-05)
## Current Position
-Phase: 1 of 7 (Foundation)
-Plan: 0 of 4 in current phase
+Phase: 1 (Foundation) — EXECUTING
+Plan: 2 of 4
Status: Ready to execute
-Last activity: 2026-06-05 — Phase 1 plans created (01-01 through 01-04); ready for execution
+Last activity: 2026-06-05 -- Phase 1 execution started
Progress: [░░░░░░░░░░] 0%
diff --git a/.planning/config.json b/.planning/config.json
index 167dce2..4c4dac4 100644
--- a/.planning/config.json
+++ b/.planning/config.json
@@ -41,7 +41,8 @@
"post_planning_gaps": true,
"security_enforcement": true,
"security_asvs_level": 1,
- "security_block_on": "high"
+ "security_block_on": "high",
+ "_auto_chain_active": false
},
"ship": {
"pr_body_sections": [
diff --git a/.planning/phases/01-foundation/01-01-SUMMARY.md b/.planning/phases/01-foundation/01-01-SUMMARY.md
new file mode 100644
index 0000000..efa6832
--- /dev/null
+++ b/.planning/phases/01-foundation/01-01-SUMMARY.md
@@ -0,0 +1,204 @@
+---
+phase: "01"
+plan: "01"
+subsystem: foundation
+tags: [scaffold, auth, prisma, trpc, r2, active-pet-context, vitest]
+dependency_graph:
+ requires: []
+ provides:
+ - proxy.ts (Clerk auth enforcement on all non-public routes)
+ - prisma singleton (src/lib/prisma.ts with @prisma/adapter-pg)
+ - Prisma schema (Owner, Species, Breed, Pet models)
+ - getPresignedUploadUrl (src/lib/r2.ts)
+ - getOwnerId / Clerk auth wrapper (src/lib/auth.ts)
+ - createTRPCContext + router + publicProcedure + protectedProcedure (src/trpc/init.ts)
+ - appRouter + AppRouter type (src/trpc/routers/_app.ts)
+ - petsRouter with create/list/byId + ownership assertion (src/trpc/routers/pets.ts)
+ - useTRPC + TRPCReactProvider (src/trpc/client.tsx)
+ - ActivePetProvider + useActivePet (src/context/ActivePetContext.tsx)
+ - Vitest config + 22 Wave 0 test scaffolds
+ affects:
+ - All subsequent plans (depend on these foundational symbols)
+tech_stack:
+ added:
+ - Next.js 16.2.7 (React 19.2.4, TypeScript 6.0.3, Turbopack)
+ - Tailwind CSS v4.3.0 (@import "tailwindcss"; @tailwindcss/postcss)
+ - shadcn/ui (base-nova style; neutral preset; components: button, input, label, textarea, select, avatar, card, dialog, dropdown-menu, sonner, progress, separator, badge, skeleton)
+ - "@clerk/nextjs" 7.4.3 (proxy.ts auth gating)
+ - "@trpc/server" + "@trpc/client" + "@trpc/tanstack-react-query" 11.17.0
+ - "@tanstack/react-query" 5.101.0
+ - "zod" 4.4.3
+ - "prisma" + "@prisma/client" 7.8.0 (Rust-free WASM engine)
+ - "@prisma/extension-accelerate" 3.0.1
+ - "@prisma/adapter-pg" 7.8.0 + "pg" 8.x (Prisma 7 local dev adapter)
+ - "@aws-sdk/client-s3" + "@aws-sdk/s3-request-presigner" 3.1062.0
+ - "sonner" 2.0.7
+ - "lucide-react" 1.17.0
+ - "@upstash/redis" 1.38.0
+ - "vitest" 4.x + "@vitest/coverage-v8" + "@vitejs/plugin-react" + "jsdom"
+ patterns:
+ - Prisma 7 singleton with @prisma/adapter-pg (local dev) and withAccelerate() (Vercel)
+ - Two-layer identity: Clerk userId (owner) + active_pet_id (pet context + localStorage)
+ - Presigned R2 PUT upload (media bytes never through API server)
+ - Clerk proxy.ts pattern (Next.js 16 convention)
+ - tRPC ownership assertion on all pet mutations (T-1-02)
+key_files:
+ created:
+ - proxy.ts
+ - prisma.config.ts
+ - prisma/schema.prisma
+ - prisma/seed.ts
+ - src/lib/prisma.ts
+ - src/lib/r2.ts
+ - src/lib/auth.ts
+ - src/trpc/init.ts
+ - src/trpc/query-client.ts
+ - src/trpc/server.ts
+ - src/trpc/client.tsx
+ - src/trpc/routers/_app.ts
+ - src/trpc/routers/pets.ts
+ - src/app/api/trpc/[trpc]/route.ts
+ - src/context/ActivePetContext.tsx
+ - src/hooks/useActivePet.ts
+ - src/app/(app)/layout.tsx
+ - src/app/(app)/pets/page.tsx
+ - src/app/(app)/onboarding/pet/page.tsx
+ - vitest.config.ts
+ - src/__tests__/auth.test.ts
+ - src/__tests__/pet-profile.test.ts
+ - src/__tests__/media-upload.test.ts
+ - src/__tests__/helpers/prisma-mock.ts
+ - .env.example
+ - package.json
+ - postcss.config.mjs
+ - tsconfig.json
+ - components.json
+ modified:
+ - src/app/layout.tsx (added ClerkProvider + TRPCReactProvider + ActivePetProvider + Toaster)
+ - src/app/globals.css (orange design system: --primary oklch(0.7024 0.1967 41.45); --background oklch(0.9882 0 0))
+decisions:
+ - "Prisma 7 requires @prisma/adapter-pg for local direct connections (url/directUrl no longer in schema.prisma; moved to prisma.config.ts)"
+ - "Prisma 7 singleton: adapter path for local dev, accelerateUrl + withAccelerate() for Vercel/Accelerate"
+ - "proxy.ts at repo root (not src/) per Next.js 16 + Clerk 7.x convention"
+ - "tRPC appRouter.createCaller() used for RSC server-side calling (createCallerFactory removed from @trpc/server top-level in v11)"
+ - "Onboarding pet page uses placeholder speciesId — real species selector wired in Plan 02"
+metrics:
+ duration: "~20 minutes"
+ completed: "2026-06-05T19:18:00Z"
+ tasks_completed: 2
+ tasks_total: 3
+ files_created: 26
+ files_modified: 2
+---
+
+# Phase 01 Plan 01: Walking Skeleton Summary
+
+**One-liner:** Next.js 16 scaffold + Clerk proxy.ts + Prisma 7 schema (Owner/Species/Breed/Pet) + tRPC pets router with ownership assertions + presigned R2 client + ActivePetContext + Vitest Wave 0 test scaffold
+
+## What Was Built
+
+The foundational architecture for OnlyPets is in place. Two tasks completed:
+
+**Task 1 — Scaffold + deps + Vitest scaffold:**
+- `create-next-app` in scaffold-tmp (capital letters prevented scaffolding in root), then moved to project root
+- All pinned runtime deps installed: Clerk 7.4.3, tRPC 11.17.0, Prisma 7.8.0, Zod 4.4.3, AWS SDK, Sonner, Upstash Redis
+- Tailwind v4 verified: `postcss.config.mjs` uses `@tailwindcss/postcss`; `globals.css` uses `@import "tailwindcss"`; no `tailwind.config.ts`
+- shadcn/ui initialized (base-nova style, neutral preset); all Phase 1 components added
+- Orange design system applied: `--primary: oklch(0.7024 0.1967 41.45)` (#F97316 equivalent); `--background: oklch(0.9882 0 0)` (#FAFAFA)
+- Inter font loaded via `next/font/google`
+- Vitest configured with jsdom + @ alias; 22 `it.todo` Wave 0 tests scaffold
+
+**Task 2 — Core wiring:**
+- `proxy.ts` at repo root: Clerk middleware gating all non-public routes; `config.matcher` includes `/(api|trpc)(.*)` and `/__clerk/(.*)`
+- `prisma/schema.prisma`: Owner, Species, Breed, Pet models (Prisma 7 format — no url/directUrl in schema)
+- `prisma.config.ts`: Prisma 7 `defineConfig` with datasource url from `DATABASE_URL` env
+- `src/lib/prisma.ts`: Singleton with `@prisma/adapter-pg` for local dev; `withAccelerate()` for Prisma Accelerate
+- `src/lib/r2.ts` (server-only): S3Client + `getPresignedUploadUrl(key, contentType)` with 300s expiry
+- `src/lib/auth.ts`: `getOwnerId()` Clerk auth wrapper
+- `src/trpc/init.ts`: `createTRPCContext`, `router`, `publicProcedure`, `protectedProcedure` (UNAUTHORIZED on no userId)
+- `src/trpc/routers/pets.ts`: `pets.create` (upserts Owner, creates Pet), `pets.list` (scoped), `pets.byId` (ownership-asserted)
+- `src/trpc/routers/_app.ts`: `appRouter` + `AppRouter` type
+- `src/trpc/client.tsx`: `TRPCReactProvider` + `useTRPC` (via `@trpc/tanstack-react-query`)
+- `src/trpc/server.ts`: `createTRPCCaller` for RSC prefetch (uses `appRouter.createCaller()`)
+- `/api/trpc/[trpc]/route.ts`: tRPC fetch handler (GET + POST)
+- `src/context/ActivePetContext.tsx` + `src/hooks/useActivePet.ts`: localStorage-backed active pet context
+- `prisma/seed.ts`: seeds Dog/Cat/Bird species idempotently
+- `src/app/layout.tsx`: ClerkProvider (orange appearance), TRPCReactProvider, ActivePetProvider, Toaster
+- Skeleton pages: `(app)/layout.tsx`, `(app)/pets/page.tsx` (RSC, reads pets.list), `(app)/onboarding/pet/page.tsx` (form + pets.create mutation)
+
+## Deviations from Plan
+
+### Auto-fixed Issues
+
+**1. [Rule 1 - Bug] Prisma 7 schema.prisma no longer accepts url/directUrl in datasource**
+- **Found during:** Task 2, running `npx prisma validate`
+- **Issue:** Prisma 7 error: "The datasource property `url` is no longer supported in schema files." The research document described Prisma 5/6 schema format. Prisma 7 requires connection URLs to live in `prisma.config.ts` (via `defineConfig`).
+- **Fix:** Removed `url` and `directUrl` from `prisma/schema.prisma`; created `prisma.config.ts` with `defineConfig({ datasource: { url: env("DATABASE_URL") } })`
+- **Files modified:** `prisma/schema.prisma`, created `prisma.config.ts`
+- **Commit:** e0214ea
+
+**2. [Rule 2 - Missing critical functionality] Prisma 7 requires @prisma/adapter-pg for direct DB connections**
+- **Found during:** Task 2, when testing PrismaClient instantiation
+- **Issue:** Prisma 7's new WASM engine requires either `adapter` (for direct connections) or `accelerateUrl` (for Prisma Accelerate). The old `datasourceUrl` constructor option is removed. Without an adapter, the local dev path cannot connect to Supabase.
+- **Fix:** Installed `@prisma/adapter-pg` 7.8.0 and `pg` 8.x (first-party Prisma packages). Singleton uses `PrismaPg(new Pool(...))` for local dev and `accelerateUrl + withAccelerate()` for Vercel.
+- **Files modified:** `src/lib/prisma.ts`, `package.json`
+- **Commit:** e0214ea
+
+**3. [Rule 1 - Bug] Missing jsdom dependency for Vitest**
+- **Found during:** Task 1, running `npx vitest run`
+- **Issue:** Vitest 4.x requires `jsdom` to be installed separately when `environment: "jsdom"` is configured.
+- **Fix:** `npm install -D jsdom`
+- **Files modified:** `package.json`
+- **Commit:** dbc074e
+
+**4. [Rule 1 - Bug] tRPC v11 `createCallerFactory` not exported from `@trpc/server` top-level**
+- **Found during:** Task 2, TypeScript check
+- **Issue:** The tRPC docs example referenced `createCallerFactory` from `@trpc/server` but it's not in the v11 API surface.
+- **Fix:** Use `appRouter.createCaller(ctx)` directly in `src/trpc/server.ts`.
+- **Files modified:** `src/trpc/server.ts`
+- **Commit:** e0214ea
+
+**5. [Rule 1 - Bug] create-next-app refused capital letters in directory name**
+- **Found during:** Task 1, scaffolding attempt
+- **Issue:** `create-next-app` rejects names with capital letters. The project root is `T:/CC-Projekte/OnlyPets` (capital O and P).
+- **Fix:** Scaffolded into `scaffold-tmp` subdirectory then moved all files to project root via PowerShell `Copy-Item -Recurse`.
+- **Files modified:** All scaffold files moved to root
+- **Commit:** dbc074e
+
+## Task 3: Checkpoint (Awaiting Human Action)
+
+Task 3 is a `checkpoint:human-action` requiring the user to:
+1. Set up `.env.local` with real `DATABASE_URL` and `DIRECT_URL` from Supabase
+2. Run `npx prisma db push` to create tables in Supabase
+3. Run `npx prisma db seed` to seed Dog/Cat/Bird species
+4. Verify Species table shows 3 rows in Prisma Studio
+
+## Known Stubs
+
+| File | Location | Stub | Reason |
+|------|----------|------|--------|
+| `src/app/(app)/onboarding/pet/page.tsx` | Line 41 | `speciesId: "placeholder"` | Real species selector not wired until Plan 02. Walking skeleton only needs the create mutation end-to-end path. |
+
+## Self-Check: PASSED
+
+Files verified:
+- proxy.ts: FOUND
+- prisma/schema.prisma: FOUND
+- prisma.config.ts: FOUND
+- src/lib/prisma.ts: FOUND
+- src/lib/r2.ts: FOUND
+- src/lib/auth.ts: FOUND
+- src/trpc/init.ts: FOUND
+- src/trpc/routers/pets.ts: FOUND
+- src/trpc/routers/_app.ts: FOUND
+- src/trpc/client.tsx: FOUND
+- src/context/ActivePetContext.tsx: FOUND
+- src/hooks/useActivePet.ts: FOUND
+- vitest.config.ts: FOUND
+- src/__tests__/pet-profile.test.ts: FOUND
+- src/__tests__/media-upload.test.ts: FOUND
+- .env.example: FOUND
+
+Commits verified:
+- dbc074e: chore(01-01) scaffold
+- e0214ea: feat(01-01) wiring
diff --git a/.planning/phases/01-foundation/01-RESEARCH.md b/.planning/phases/01-foundation/01-RESEARCH.md
new file mode 100644
index 0000000..0e1bbfe
--- /dev/null
+++ b/.planning/phases/01-foundation/01-RESEARCH.md
@@ -0,0 +1,898 @@
+# Phase 1: Foundation - Research
+
+**Researched:** 2026-06-05
+**Domain:** Next.js 16 App Router, Clerk auth, Prisma 7 + Supabase, Cloudflare R2 presigned uploads, two-layer pet identity
+**Confidence:** MEDIUM-HIGH
+
+---
+
+## Summary
+
+Phase 1 establishes the identity layer and media pipeline that every subsequent phase depends on. Three things must be true before any other phase starts: (1) owner authentication works end-to-end, (2) the pet-as-actor identity pattern is encoded correctly into every request context, and (3) media storage bypasses the API server via presigned R2 uploads. None of these can be retrofitted cheaply.
+
+The stack decisions from ecosystem research remain sound but require version updates: the npm `latest` tag now resolves to Next.js **16.2.7** (stable since October 2025), Prisma **7.8.0**, and `@trpc/tanstack-react-query` as the current package name for the tRPC React Query integration. Clerk 7.4.3 explicitly declares `^16.0.10` in its peerDependencies, so compatibility with Next.js 16 is confirmed. The previously flagged blockers (Clerk + Next.js 15 compatibility, tRPC v11 stability, Tailwind v4 migration) are now resolved.
+
+Walking skeleton goal: owner can sign up with Clerk, a pet profile is created in Supabase PostgreSQL via Prisma, the pet's avatar photo is uploaded directly to Cloudflare R2 via a presigned URL, and the active pet is reflected in the navigation — all in a single deployable Next.js 16 application on Vercel.
+
+**Primary recommendation:** Scaffold with `create-next-app@latest` (gives Next.js 16, React 19, TypeScript, Tailwind v4, Turbopack by default), install Clerk + tRPC + Prisma 7, run `npx shadcn@latest init`, configure `proxy.ts` (not `middleware.ts`) for Clerk, and establish the two-layer identity context before writing any pet profile logic.
+
+---
+
+
+## Phase Requirements
+
+| ID | Description | Research Support |
+|----|-------------|------------------|
+| AUTH-01 | Owner can create an account with email and password | Clerk `` component + `clerkMiddleware()` in proxy.ts handles this entirely |
+| AUTH-02 | Owner receives email verification link after signup | Clerk's built-in email verification flow — no custom code needed; configure in Clerk dashboard |
+| AUTH-03 | Owner can reset password via email link | Clerk's built-in forgot-password flow with `` component |
+| AUTH-04 | Owner session persists across browser refresh | Clerk JWT sessions are persisted via httpOnly cookie automatically; active_pet_id stored in localStorage |
+| PET-01 | Owner can create a pet profile with name, breed, species, bio, and avatar photo | Prisma Pet model + tRPC `pets.create` mutation + presigned R2 upload for avatar |
+| PET-02 | Owner can edit their pet's profile details | tRPC `pets.update` mutation with ownership assertion via `active_pet_id` + `owner_id` check |
+| PET-03 | Owner can delete a pet profile | tRPC `pets.delete` mutation with confirm dialog; cascade behavior on DB constraints |
+| PET-04 | Owner can manage multiple pets and switch the active pet without logging out | `active_pet_id` in React Context + localStorage; switcher in nav dropdown; no ownership change |
+| PET-05 | Pet profile includes a structured "adoption origin story" field | `adoption_story` TEXT column in Pet table; rendered as `
+
+---
+
+## Architectural Responsibility Map
+
+| Capability | Primary Tier | Secondary Tier | Rationale |
+|------------|-------------|----------------|-----------|
+| Owner authentication (sign-up, login, session) | Clerk (external) | Next.js App Router (middleware/proxy) | Clerk manages owner identity; Next.js proxy.ts enforces auth on protected routes |
+| Active pet resolution per request | API / Backend (tRPC context) | Frontend Server (RSC) | pet_id ownership must be validated server-side; client sends pet_id, server asserts ownership |
+| Pet profile CRUD | API / Backend (tRPC procedures) | Database (Prisma + PostgreSQL) | All mutations go through tRPC with ownership assertions before any DB write |
+| Avatar photo upload | Browser / Client | CDN / R2 | Client uploads directly to R2 via presigned PUT URL; API handles only metadata write after upload |
+| Pet switcher state | Browser / Client | — | Active pet stored in React Context + localStorage; no server round-trip on switch |
+| Breed/species taxonomy | Database / Storage | API / Backend | Seeded static data in PostgreSQL; served via tRPC query; never hardcoded client-side |
+| Design system / UI components | Browser / Client | — | shadcn/ui components rendered client-side; no server rendering needed for form primitives |
+
+---
+
+## Standard Stack
+
+### Core
+
+| Library | Version | Purpose | Why Standard |
+|---------|---------|---------|--------------|
+| `next` | 16.2.7 | Full-stack React framework | `latest` on npm (2026-06-01); App Router, Turbopack default, React 19.2, proxy.ts |
+| `react` / `react-dom` | 19.2.7 | UI rendering | Ships with Next.js 16; RSC + concurrent features |
+| `typescript` | 6.0.3 | Type safety | Required by all stack members; Next.js 16 first-class TS config |
+| `@clerk/nextjs` | 7.4.3 | Owner authentication | Declares peerDep `^16.0.10` — confirmed Next.js 16 compatible; proxy.ts integration |
+| `tailwindcss` | 4.3.0 | Utility-first CSS | v4 CSS-native engine; `@import "tailwindcss"` replaces v3 directives |
+| `@tailwindcss/postcss` | 4.3.0 | Tailwind v4 PostCSS plugin | Required in v4; replaces `tailwindcss` as postcss plugin |
+| `prisma` | 7.8.0 | ORM / schema + migrations | `latest` on npm (2026-04-22); Rust-free query engine default in v7 |
+| `@prisma/client` | 7.8.0 | Generated type-safe DB client | Paired with `prisma` CLI |
+| `@prisma/extension-accelerate` | 3.0.1 | Serverless connection pooling | Required on Vercel; Prisma-maintained (prismabot@prisma.io) |
+| `@supabase/supabase-js` | 2.107.0 | Supabase client (Realtime + auth helpers) | Official Supabase client for real-time subscriptions (Phase 2+) |
+| `@trpc/server` | 11.17.0 | tRPC server router | v11 stable (2026-04-28); `latest` on npm |
+| `@trpc/client` | 11.17.0 | tRPC client | Paired with server |
+| `@trpc/tanstack-react-query` | 11.17.0 | tRPC React Query bridge | Current v11 package name (supersedes `@trpc/react-query` which also works) |
+| `@tanstack/react-query` | 5.101.0 | Client data fetching + cache | v5 stable; `latest` on npm (2026-06-02) |
+| `zod` | 4.4.3 | Schema validation | Used natively by tRPC procedures; validates all API inputs |
+| `@aws-sdk/client-s3` | 3.1062.0 | S3-compatible R2 client | AWS SDK v3; used to generate presigned PUT URLs server-side |
+| `@aws-sdk/s3-request-presigner` | 3.1062.0 | Presigned URL generation | Paired with client-s3; `getSignedUrl` + `PutObjectCommand` |
+
+### Supporting
+
+| Library | Version | Purpose | When to Use |
+|---------|---------|---------|-------------|
+| `sonner` | 2.0.7 | Toast notifications | shadcn/ui toast uses Sonner under the hood; all success/error feedback |
+| `lucide-react` | 1.17.0 | Icon set | Bundled with shadcn; use for all icons (camera, chevron, etc.) |
+| `@upstash/redis` | 1.38.0 | Serverless Redis client | Not needed in Phase 1; install now for Phase 2 story TTL |
+| `server-only` | (latest) | Guard server-only modules | Prevents tRPC router code from being bundled into client |
+| `client-only` | (latest) | Guard client-only modules | Prevents Clerk browser hooks from running on server |
+
+### Alternatives Considered
+
+| Instead of | Could Use | Tradeoff |
+|------------|-----------|----------|
+| `@trpc/tanstack-react-query` | `@trpc/react-query` | Both resolve to 11.17.0; `@trpc/tanstack-react-query` is the preferred name per tRPC docs |
+| Presigned R2 PUT | Supabase Storage | Supabase Storage lacks image transforms; R2 + Cloudflare Images is the design decision |
+| Prisma 7 | Drizzle ORM | Drizzle is faster and smaller; Prisma 7's Rust-free engine closes the gap; migration tooling still better in Prisma |
+| `middleware.ts` | `proxy.ts` | Next.js 16 deprecates `middleware.ts` in favor of `proxy.ts`; Clerk supports both but recommends `proxy.ts` for 16+ |
+
+**Installation:**
+```bash
+# Scaffold
+npx create-next-app@latest onlypets --typescript --eslint --app --turbopack --src-dir --tailwind
+
+# Auth
+npm install @clerk/nextjs
+
+# tRPC + data fetching
+npm install @trpc/server @trpc/client @trpc/tanstack-react-query @tanstack/react-query zod server-only client-only
+
+# Database
+npm install prisma @prisma/client @prisma/extension-accelerate @supabase/supabase-js
+npx prisma init
+
+# Media upload
+npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner
+
+# Supporting
+npm install sonner @upstash/redis
+
+# shadcn/ui (interactive, copies component source into repo)
+npx shadcn@latest init
+npx shadcn@latest add button input form label textarea select avatar card dialog dropdown-menu toast progress separator badge skeleton
+```
+
+**Version verification (run before pinning):**
+All versions above confirmed via `npm view version` on 2026-06-05.
+
+---
+
+## Package Legitimacy Audit
+
+> slopcheck v0.6.1 was installed but operates against PyPI only — not applicable for npm packages. All packages verified via `npm view` against the npm registry and confirmed with official GitHub repository links.
+
+| Package | Registry | Source Repo | First Published | Disposition |
+|---------|----------|-------------|-----------------|-------------|
+| `next` | npm | github.com/vercel/next.js | 2011 | Approved [VERIFIED: npm registry] |
+| `@clerk/nextjs` | npm | github.com/clerk/javascript | ~2021 | Approved [VERIFIED: npm registry] |
+| `@trpc/server` | npm | github.com/trpc/trpc | ~2021 | Approved [VERIFIED: npm registry] |
+| `@trpc/client` | npm | github.com/trpc/trpc | ~2021 | Approved [VERIFIED: npm registry] |
+| `@trpc/tanstack-react-query` | npm | github.com/trpc/trpc | ~2021 | Approved [VERIFIED: npm registry] |
+| `tailwindcss` | npm | github.com/tailwindlabs/tailwindcss | ~2017 | Approved [VERIFIED: npm registry] |
+| `@tailwindcss/postcss` | npm | github.com/tailwindlabs/tailwindcss | ~2024 | Approved [VERIFIED: npm registry] |
+| `prisma` | npm | github.com/prisma/prisma | ~2019 | Approved [VERIFIED: npm registry] |
+| `@prisma/client` | npm | github.com/prisma/prisma | ~2019 | Approved [VERIFIED: npm registry] |
+| `@prisma/extension-accelerate` | npm | Prisma-maintained (prismabot@prisma.io) | ~2023 | Approved [VERIFIED: npm registry] |
+| `@supabase/supabase-js` | npm | github.com/supabase/supabase-js | ~2020 | Approved [VERIFIED: npm registry] |
+| `@tanstack/react-query` | npm | github.com/TanStack/query | ~2019 | Approved [VERIFIED: npm registry] |
+| `zod` | npm | github.com/colinhacks/zod | ~2020 | Approved [VERIFIED: npm registry] |
+| `@aws-sdk/client-s3` | npm | github.com/aws/aws-sdk-js-v3 | ~2020 | Approved [VERIFIED: npm registry] |
+| `@aws-sdk/s3-request-presigner` | npm | github.com/aws/aws-sdk-js-v3 | ~2020 | Approved [VERIFIED: npm registry] |
+| `sonner` | npm | github.com/emilkowalski/sonner | ~2022 | Approved [VERIFIED: npm registry] |
+| `lucide-react` | npm | github.com/lucide-icons/lucide | ~2020 | Approved [VERIFIED: npm registry] |
+| `@upstash/redis` | npm | github.com/upstash/redis-js | ~2021 | Approved [VERIFIED: npm registry] |
+
+**Packages removed due to slopcheck:** none
+**Packages flagged as suspicious:** none
+
+*slopcheck could not be run against npm (operates against PyPI). All packages are cross-verified against official GitHub repositories via `npm view --json` and have multi-year publication histories from known organizations.*
+
+---
+
+## Architecture Patterns
+
+### System Architecture Diagram
+
+```
+Browser
+ │
+ ├─ [GET /sign-up, /log-in]──► Clerk hosted UI (appearance customized)
+ │ │
+ │ └─ Clerk issues JWT (owner_id in sub)
+ │
+ ├─ [All app routes]──► proxy.ts (clerkMiddleware)
+ │ │ checks JWT, injects auth into request
+ │ │
+ │ └─► Next.js App Router
+ │ │
+ │ ├─ Server Components (RSC)
+ │ │ reads owner_id from auth()
+ │ │ resolves active_pet_id from DB or localStorage hint
+ │ │
+ │ └─ /api/trpc/[trpc] (Route Handler)
+ │ │
+ │ ├─ tRPC context builder
+ │ │ extracts owner_id from Clerk auth()
+ │ │ reads x-active-pet-id header
+ │ │ asserts pet ownership via Prisma
+ │ │
+ │ └─ tRPC procedures
+ │ pets.create / .update / .delete / .list
+ │ media.getPresignedUrl
+ │
+ ├─ [PUT presigned URL]──► Cloudflare R2 (direct — never through API)
+ │ browser uploads file bytes directly to R2
+ │ on success: calls tRPC pets.setAvatar(storageKey)
+ │
+ └─ [Pet avatar CDN URL]──► Cloudflare CDN ──► R2 bucket
+
+Database
+ Supabase PostgreSQL 16
+ owner (owner_id = Clerk user_id)
+ species (id, name) ← seeded: Dog, Cat, Bird
+ breed (id, species_id, name) ← seeded: AKC/CFA/BirdLife data
+ pet (id, owner_id FK→owner, species_id FK, breed_id FK,
+ name, bio, adoption_story, avatar_key, created_at)
+
+ORM
+ Prisma 7 client (singleton in lib/prisma.ts)
+ Prisma Accelerate for connection pooling on Vercel serverless
+```
+
+### Recommended Project Structure
+```
+src/
+├── app/
+│ ├── (auth)/ # Clerk auth route group
+│ │ ├── sign-up/
+│ │ ├── log-in/
+│ │ ├── forgot-password/
+│ │ └── reset-password/
+│ ├── (app)/ # Protected app route group
+│ │ ├── layout.tsx # App shell (sidebar/bottom nav)
+│ │ ├── onboarding/
+│ │ │ └── pet/ # First pet creation flow
+│ │ └── pets/
+│ │ ├── page.tsx # My Pets grid (/pets)
+│ │ └── [petId]/
+│ │ ├── page.tsx # Pet profile view
+│ │ └── edit/page.tsx # Pet profile edit
+│ ├── api/
+│ │ └── trpc/[trpc]/route.ts # tRPC fetch handler
+│ ├── layout.tsx # Root layout (ClerkProvider, fonts)
+│ └── globals.css # @import "tailwindcss"; CSS variables
+│
+├── trpc/
+│ ├── init.ts # initTRPC, context type
+│ ├── query-client.ts # makeQueryClient() factory
+│ ├── server.ts # createCallerFactory for RSC prefetch
+│ ├── client.tsx # TRPCReactProvider, useTRPC hook
+│ └── routers/
+│ ├── _app.ts # Root router
+│ ├── pets.ts # pet CRUD procedures
+│ └── media.ts # getPresignedUrl procedure
+│
+├── lib/
+│ ├── prisma.ts # PrismaClient singleton
+│ ├── r2.ts # S3Client for R2, getPresignedUploadUrl()
+│ └── auth.ts # Clerk auth() wrappers for server context
+│
+├── components/
+│ ├── ui/ # shadcn component copies
+│ ├── pet/
+│ │ ├── PetCard.tsx
+│ │ ├── PetForm.tsx
+│ │ ├── AvatarUpload.tsx
+│ │ └── PetSwitcher.tsx
+│ └── layout/
+│ ├── Sidebar.tsx
+│ └── MobileNav.tsx
+│
+├── context/
+│ └── ActivePetContext.tsx # React Context for active_pet_id
+│
+├── hooks/
+│ └── useActivePet.ts
+│
+└── prisma/
+ ├── schema.prisma
+ ├── migrations/
+ └── seed.ts # Species + breed taxonomy seed
+```
+
+### Pattern 1: Two-Layer Identity — Owner + Active Pet
+
+**What:** Every tRPC procedure that operates on pet data receives `(owner_id from Clerk, pet_id from request header)` and must assert `pet.owner_id === auth.owner_id` before proceeding.
+
+**When to use:** All pet mutation procedures (create, update, delete), any query that scopes to a specific pet.
+
+**Example:**
+```typescript
+// Source: [ASSUMED] — based on tRPC v11 + Clerk auth() documented patterns
+
+// trpc/init.ts
+import { auth } from '@clerk/nextjs/server'
+import { initTRPC, TRPCError } from '@trpc/server'
+import { prisma } from '@/lib/prisma'
+
+export const createTRPCContext = async () => {
+ const { userId } = await auth()
+ return { userId, prisma }
+}
+
+const t = initTRPC.context().create()
+export const router = t.router
+export const publicProcedure = t.procedure
+export const protectedProcedure = t.procedure.use(({ ctx, next }) => {
+ if (!ctx.userId) throw new TRPCError({ code: 'UNAUTHORIZED' })
+ return next({ ctx: { ...ctx, userId: ctx.userId } })
+})
+
+// trpc/routers/pets.ts — ownership assertion
+export const petsRouter = router({
+ update: protectedProcedure
+ .input(z.object({ petId: z.string(), name: z.string(), ... }))
+ .mutation(async ({ ctx, input }) => {
+ const pet = await ctx.prisma.pet.findUnique({ where: { id: input.petId } })
+ if (!pet || pet.ownerId !== ctx.userId) {
+ throw new TRPCError({ code: 'FORBIDDEN' })
+ }
+ return ctx.prisma.pet.update({ where: { id: input.petId }, data: { ... } })
+ }),
+})
+```
+
+### Pattern 2: Presigned R2 PUT Upload
+
+**What:** API generates a short-lived presigned URL; browser PUTs the file directly to R2; on success, browser calls API to record the storage key in the DB.
+
+**When to use:** Every media upload in the app (avatar photos, post images, etc.)
+
+**Example:**
+```typescript
+// Source: [VERIFIED: developers.cloudflare.com/r2/api/s3/presigned-urls/]
+
+// lib/r2.ts
+import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'
+import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
+
+export const r2 = new S3Client({
+ region: 'auto', // required by SDK but not used by R2
+ endpoint: `https://${process.env.CF_ACCOUNT_ID}.r2.cloudflarestorage.com`,
+ credentials: {
+ accessKeyId: process.env.R2_ACCESS_KEY_ID!,
+ secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
+ },
+})
+
+export async function getPresignedUploadUrl(key: string, contentType: string) {
+ return getSignedUrl(
+ r2,
+ new PutObjectCommand({
+ Bucket: process.env.R2_BUCKET_NAME!,
+ Key: key,
+ ContentType: contentType, // must match what browser sends in PUT
+ }),
+ { expiresIn: 300 } // 5 minutes
+ )
+}
+```
+
+### Pattern 3: Clerk proxy.ts Configuration (Next.js 16)
+
+**What:** Next.js 16 renames `middleware.ts` to `proxy.ts`. Clerk 7.x supports both but recommends `proxy.ts` for Next.js 16+.
+
+**When to use:** All projects on Next.js 16. If using Next.js ≤15, use `middleware.ts`.
+
+**Example:**
+```typescript
+// Source: [VERIFIED: clerk.com/docs/references/nextjs/clerk-middleware]
+
+// proxy.ts (at project root, NOT in src/)
+import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
+
+const isPublicRoute = createRouteMatcher([
+ '/sign-in(.*)',
+ '/sign-up(.*)',
+ '/forgot-password(.*)',
+ '/reset-password(.*)',
+])
+
+export default clerkMiddleware(async (auth, request) => {
+ if (!isPublicRoute(request)) {
+ await auth.protect()
+ }
+})
+
+export const config = {
+ matcher: [
+ '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
+ '/(api|trpc)(.*)',
+ '/__clerk/(.*)',
+ ],
+}
+```
+
+### Pattern 4: Prisma Singleton (Next.js / Serverless)
+
+**What:** Prevents multiple PrismaClient instances during hot-reload in dev and across serverless function invocations.
+
+**When to use:** Always in Next.js projects.
+
+**Example:**
+```typescript
+// Source: [CITED: prisma.io/docs/guides/nextjs — PrismaClient singleton pattern]
+
+// lib/prisma.ts
+import { PrismaClient } from '@prisma/client'
+import { withAccelerate } from '@prisma/extension-accelerate'
+
+const createPrismaClient = () =>
+ new PrismaClient().$extends(withAccelerate())
+
+const globalForPrisma = globalThis as unknown as {
+ prisma: ReturnType | undefined
+}
+
+export const prisma = globalForPrisma.prisma ?? createPrismaClient()
+
+if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
+```
+
+### Pattern 5: Clerk Appearance Customization (Orange Primary)
+
+**What:** Override Clerk's default blue primary to match the OnlyPets orange-500 (#F97316) design system.
+
+**When to use:** In `` in `app/layout.tsx`.
+
+**Example:**
+```typescript
+// Source: [CITED: clerk.com/docs/customization/variables]
+
+// app/layout.tsx
+
+ {children}
+
+```
+
+### Pattern 6: Active Pet Context
+
+**What:** Owner's active pet stored in React Context, initialized from localStorage, with no page reload on switch.
+
+**When to use:** App shell layout and all components that display the active pet identity.
+
+**Example:**
+```typescript
+// Source: [ASSUMED] — standard React Context + localStorage pattern
+
+// context/ActivePetContext.tsx
+'use client'
+import { createContext, useContext, useState, useEffect } from 'react'
+
+type ActivePetContextType = {
+ activePetId: string | null
+ setActivePet: (id: string) => void
+}
+
+export const ActivePetContext = createContext(null)
+
+export function ActivePetProvider({ children }: { children: React.ReactNode }) {
+ const [activePetId, setActivePetId] = useState(null)
+
+ useEffect(() => {
+ const stored = localStorage.getItem('active_pet_id')
+ if (stored) setActivePetId(stored)
+ }, [])
+
+ const setActivePet = (id: string) => {
+ setActivePetId(id)
+ localStorage.setItem('active_pet_id', id)
+ }
+
+ return (
+
+ {children}
+
+ )
+}
+
+export const useActivePet = () => {
+ const ctx = useContext(ActivePetContext)
+ if (!ctx) throw new Error('useActivePet must be used inside ActivePetProvider')
+ return ctx
+}
+```
+
+### Anti-Patterns to Avoid
+
+- **Putting `middleware.ts` instead of `proxy.ts` in Next.js 16:** `middleware.ts` is deprecated in Next.js 16. Use `proxy.ts`. The exported function name must be `proxy` or default export.
+- **Routing media through the API server:** Never pass image bytes through a Next.js Route Handler. Generate presigned URLs server-side; client uploads directly to R2.
+- **Passing pet_id in the request body without ownership assertion:** Always cross-check `pet.owner_id === auth().userId` in every tRPC procedure. Never trust a client-supplied pet_id without verifying ownership.
+- **Creating Clerk users for pets:** Pets are `app-domain entities` (rows in the `pet` table), not Clerk users. Never call Clerk's user creation API for a pet.
+- **Multiple PrismaClient instances:** Hot-reload creates new instances. Use the global singleton pattern in `lib/prisma.ts`.
+- **Hardcoding breed lists client-side:** Breed taxonomy is seeded data in PostgreSQL, served via a tRPC query. It must be queryable and filterable by species — a static array cannot be filtered server-side.
+- **Using Tailwind v3 config format with v4:** Tailwind v4 has no `tailwind.config.js`. Configuration goes in CSS via `@theme {}` blocks in `globals.css`. Do NOT generate a `tailwind.config.ts` — `create-next-app` may still do this for v3 installs.
+- **Installing `@trpc/react-query` instead of `@trpc/tanstack-react-query`:** Both exist at 11.17.0 and both work; the tRPC docs now reference `@trpc/tanstack-react-query` as the canonical package. Use `@trpc/tanstack-react-query`.
+
+---
+
+## Don't Hand-Roll
+
+| Problem | Don't Build | Use Instead | Why |
+|---------|-------------|-------------|-----|
+| Email/password auth, session management, email verification | Custom JWT auth, bcrypt password hashing | Clerk `@clerk/nextjs` | PBKDF2 strength, secure httpOnly cookies, email verification flows, OAuth are all solved; hand-rolling auth is a common source of security vulnerabilities |
+| Image upload to S3/R2 | Streaming binary through Next.js API | AWS SDK presigned PUT | API server becomes a bottleneck and CORS-headache; presigned URLs allow client-direct upload with no server load |
+| File size / format validation on upload | Custom MIME parsing on server | Client-side + presigned URL `ContentType` restriction | Client validates before initiating; R2 enforces content-type on the presigned URL |
+| UI components (forms, dialogs, dropdowns) | Custom accessible components | shadcn/ui (Radix UI primitives) | Radix handles focus trapping, keyboard navigation, ARIA — non-trivial to build correctly |
+| TypeScript API types shared between client and server | OpenAPI code-gen, manual type files | tRPC end-to-end type inference | Procedure input/output types propagate automatically; no code-gen step |
+| Breed taxonomy data | Manually typing 200+ breeds | Seed script from curated data (AKC, CFA lists) | Pre-curated lists exist; the schema just needs `(id, species_id, name)` — seed once |
+| Connection pooling for PostgreSQL on serverless | PgBouncer self-hosting | Prisma Accelerate | Vercel serverless functions have no persistent connection; Accelerate handles pooling via a managed proxy |
+
+**Key insight:** Clerk, Prisma, tRPC, and shadcn collectively eliminate the most error-prone boilerplate layers (auth, DB access, API typing, accessible UI). Phase 1's engineering value is in the identity wiring and data model, not in rebuilding solved infrastructure.
+
+---
+
+## Common Pitfalls
+
+### Pitfall 1: `middleware.ts` Instead of `proxy.ts` in Next.js 16
+
+**What goes wrong:** Auth middleware is never executed; all routes are publicly accessible.
+**Why it happens:** Next.js 16 changed the conventional filename from `middleware.ts` to `proxy.ts`. The old `middleware.ts` still works for Edge runtime use cases but is deprecated. Clerk's `clerkMiddleware()` example code now shows `proxy.ts`.
+**How to avoid:** Create `proxy.ts` at the project root (not in `src/`). Export the `clerkMiddleware()` result as the default export.
+**Warning signs:** Visiting protected routes without being logged in doesn't redirect to sign-in; Clerk's `auth()` returns `null` in server components.
+
+### Pitfall 2: Stale `create-next-app` Scaffold Assumptions
+
+**What goes wrong:** Scaffold creates Tailwind v3 config files (`tailwind.config.ts`, old CSS directives) that are incompatible with Tailwind v4.
+**Why it happens:** `create-next-app` behavior depends on its bundled template. With Next.js 16 + Tailwind v4, the scaffold should already use the v4 PostCSS approach, but this needs verification at scaffold time.
+**How to avoid:** After scaffold, verify `postcss.config.mjs` uses `@tailwindcss/postcss` not `tailwindcss`. Verify `globals.css` uses `@import "tailwindcss"` not `@tailwind base; @tailwind components; @tailwind utilities`.
+**Warning signs:** Build errors about `tailwind.config.ts` not being found; or v3 CSS directives present.
+
+### Pitfall 3: Pet Ownership Not Asserted on Mutations
+
+**What goes wrong:** Any owner can delete or edit any pet by passing an arbitrary `petId` — a horizontal privilege escalation vulnerability.
+**Why it happens:** tRPC procedures receive `petId` from the client. Without an ownership check, any authenticated user can mutate any pet.
+**How to avoid:** Every mutation: (1) `protectedProcedure` ensures `userId` exists, (2) `findUnique` fetches the pet, (3) assert `pet.ownerId === ctx.userId`, throw `FORBIDDEN` if not.
+**Warning signs:** No ownership assertion in `pets.update`, `pets.delete`, or `media.setAvatar` procedures.
+
+### Pitfall 4: Presigned URL Content-Type Mismatch
+
+**What goes wrong:** R2 rejects the upload with a 403 SignatureDoesNotMatch error.
+**Why it happens:** The `ContentType` encoded in the presigned URL signature must exactly match the `Content-Type` header the browser sends in the PUT request. If the tRPC procedure hardcodes `image/jpeg` but the browser sends `image/png`, the signature is invalid.
+**How to avoid:** The client specifies the content type when requesting the presigned URL; the API encodes it in the signature. Browser sends the same `Content-Type` header in the PUT.
+**Warning signs:** R2 returns 403 on direct PUT; CORS preflight passes but actual upload fails.
+
+### Pitfall 5: Prisma Version Mismatch with Connection Pooling
+
+**What goes wrong:** `@prisma/extension-accelerate` requires a specific Prisma client version. Version drift between `prisma`, `@prisma/client`, and `@prisma/extension-accelerate` causes runtime type errors.
+**Why it happens:** `prisma` (CLI) and `@prisma/client` must match. Accelerate extension is a separate package with its own peer requirements.
+**How to avoid:** Always install `prisma` and `@prisma/client` at the same version. Run `npx prisma generate` after any version change to regenerate the client.
+**Warning signs:** TypeScript errors about missing `$extends` method; Accelerate extension type errors.
+
+### Pitfall 6: Active Pet ID Not Available Server-Side for RSC
+
+**What goes wrong:** Server Components can't read `localStorage`; RSC that tries to show the active pet displays nothing or the wrong pet.
+**Why it happens:** `localStorage` is browser-only. RSCs run on the server.
+**How to avoid:** The active pet context is for client components only. For RSC that needs to know the active pet, read it from a cookie set by the client on pet switch, or pass it as a prop from a client component boundary. On initial server render, fall back to the owner's first pet.
+**Warning signs:** `localStorage is not defined` runtime error in RSC; hydration mismatch between server and client rendering of the active pet.
+
+### Pitfall 7: Breed Data Without Species Seeding
+
+**What goes wrong:** Pet creation form has no breeds to display; or breeds from other species show up in the selector.
+**Why it happens:** The breed table depends on the species table (FK constraint). If the seed script runs in the wrong order or is missing, breed queries return empty.
+**How to avoid:** Seed script order: (1) species, (2) breeds with species_id FK. Seed must run before any tests or manual testing of the pet creation form.
+**Warning signs:** Empty breed `