docs(01-03): complete pet profile CRUD plan summary

- SUMMARY.md with all task commits, deviations, known stubs
- Self-check: all 11 files found, all 5 commits verified
This commit is contained in:
admin committed 2026-06-06 10:23:03 +02:00
1 parent 8824391613
commit b0cca2deb6
1 file changed
+207
@@ -0,0 +1,207 @@
---
phase: "01"
plan: "03"
subsystem: pets
tags: [trpc, prisma, react-hook-form, pet-crud, species, breeds, seed, vitest, zod]
dependency_graph:
requires:
- proxy.ts (Clerk auth — from Plan 01)
- petsRouter create/list/byId (from Plan 01)
- createTRPCCaller + protectedProcedure (from Plan 01)
- ActivePetProvider + useActivePet (from Plan 01)
- prisma schema Owner/Species/Breed/Pet (from Plan 01)
- prisma seed.ts species upsert (from Plan 01)
- useTRPC + TRPCReactProvider (from Plan 01)
- App shell layout + Sidebar + MobileNav (from Plan 02)
- onboarding/pet stub page (from Plan 02)
provides:
- pets.update (ownership-asserted, Zod limits)
- pets.delete (ownership-asserted)
- pets.listSpecies (protectedProcedure)
- pets.listBreeds (filtered by speciesId, protectedProcedure)
- Breed seed: 20 dog breeds + 15 cat breeds + 10 bird breeds (45 total)
- PetForm component (create/edit, DB-driven species/breed, char counters, AvatarUploadZone stub)
- PetCard component (active orange ring, species badge)
- /pets page (My Pets grid + Add Pet CTA)
- /pets/[petId] page (pet profile view, owner subordination)
- /pets/[petId]/edit page (PetForm edit mode + delete dialog)
- /onboarding/pet page (wired PetForm replace stub)
- PetGrid client island (active pet selection)
- 11 passing pet-profile unit tests (PET-01 through PET-05 + FORBIDDEN assertions)
affects:
- Plan 04 (avatar upload — AvatarUploadZone stub wired here, upload logic comes next)
- All feed/content plans (pet identity layer is complete; pet.id is valid for FK references)
tech_stack:
added:
- react-hook-form (^7.x) + @hookform/resolvers (^3.x) — form state + Zod resolver
patterns:
- tRPC v11 @trpc/tanstack-react-query: useTRPC().proc.queryOptions() + useQuery(); useTRPC().proc.mutationOptions() + useMutation() — NOT .useQuery()/.useMutation() directly on proxy
- Server page + client island split for pages needing both SSR data and client interactivity (PetGrid)
- buttonVariants({}) on Link elements — base-nova Button lacks asChild support
- tRPC caller in tests: explicit CallerCtx type to avoid union type inference error
- Breed seed: idempotent upsert with @@unique([speciesId, name]) composite key
key_files:
created:
- src/components/pet/PetForm.tsx
- src/components/pet/PetCard.tsx
- src/app/(app)/pets/PetGrid.tsx
- src/app/(app)/pets/[petId]/page.tsx
- src/app/(app)/pets/[petId]/edit/page.tsx
modified:
- src/trpc/routers/pets.ts (added update, delete, listSpecies, listBreeds)
- prisma/seed.ts (extended with 45 breed upserts)
- src/app/(app)/pets/page.tsx (replaced skeleton with PetGrid)
- src/app/onboarding/pet/page.tsx (replaced stub with PetForm)
- src/__tests__/pet-profile.test.ts (replaced 22 todos with 11 real tests)
- package.json + package-lock.json (react-hook-form + @hookform/resolvers)
key_decisions:
- "tRPC v11 useTRPC() returns TRPCOptionsProxy — use queryOptions()/mutationOptions() with useQuery()/useMutation() from @tanstack/react-query, not .useQuery()/.useMutation() on the proxy directly"
- "base-nova Button (base-ui) lacks asChild — use buttonVariants() class on Link elements for navigation buttons (same pattern as Plan 02)"
- "Server page + client island split: server RSC fetches pets.list, PetGrid client island handles active pet context"
- "AvatarUploadZone is a stub in Plan 03 — upload disabled in create mode; Plan 04 wires actual R2 upload"
- "tRPC test caller: explicit CallerCtx type annotation avoids TS union type error on createCaller param"
- "currentUser() from @clerk/nextjs/server used on pet profile page for owner username (Owner model has no username column)"
requirements-completed: [PET-01, PET-02, PET-03, PET-04, PET-05]
# Metrics
duration: ~40min
completed: "2026-06-06"
---
# Phase 01 Plan 03: Pet Profile CRUD Summary
**Full pet CRUD via tRPC (update/delete/listSpecies/listBreeds) + PetForm/PetCard components + My Pets grid + pet profile/edit pages + 45-breed seed + 11 passing unit tests**
## Performance
- **Duration:** ~40 minutes
- **Started:** 2026-06-06T10:00:00Z
- **Completed:** 2026-06-06T10:20:00Z
- **Tasks:** 4 (Tasks 1-4 complete; Task 5 is checkpoint:human-action awaiting user verification)
- **Files modified:** 11
## Accomplishments
- Complete pet CRUD lifecycle: create, view, edit, delete with ownership assertion on every mutation
- Species (Dog/Cat/Bird) and breed taxonomy (45 breeds) seeded idempotently and served via protected tRPC queries
- PetForm component wires species/breed from DB (not hardcoded), enforces Zod char limits, supports both create and edit modes
- Pet profile page with "Managed by @owner" attribution in muted caption (Owner Identity Subordination per UI-SPEC)
- Delete dialog with exact UI-SPEC copy: "Delete pet profile?", "Yes, delete", "Keep pet"
- 11 unit tests covering PET-01 through PET-05 with FORBIDDEN ownership assertions; all passing with mocked Prisma
## Task Commits
Each task was committed atomically:
1. **Task 1: Extend tRPC pets router + breed seed** — `69553b1` (feat)
2. **Task 2: PetForm + PetCard components** — `63d8c06` (feat)
3. **Task 3: Pet profile pages** — `95dfec7` (feat)
4. **Task 4: Pet-profile unit tests** — `f996459` (test)
5. **Task 4 fix: tRPC caller type fix** — `8824391` (fix)
## Files Created/Modified
- `src/trpc/routers/pets.ts` — Added update, delete, listSpecies, listBreeds with assertPetOwnership
- `prisma/seed.ts` — Extended with 20 dog + 15 cat + 10 bird breed upserts
- `src/components/pet/PetForm.tsx` — Reusable create/edit form; species/breed from DB; char counters; AvatarUploadZone stub
- `src/components/pet/PetCard.tsx` — Pet card with active orange ring, species badge, hover shadow
- `src/app/(app)/pets/page.tsx` — Server RSC + PetGrid client island
- `src/app/(app)/pets/PetGrid.tsx` — Client island: active pet selection, empty state CTA
- `src/app/(app)/pets/[petId]/page.tsx` — Pet profile view: avatar, name, species, owner attribution
- `src/app/(app)/pets/[petId]/edit/page.tsx` — Edit form + delete confirmation dialog (UI-SPEC copy)
- `src/app/onboarding/pet/page.tsx` — Replaced stub with PetForm mode=create
- `src/__tests__/pet-profile.test.ts` — 11 real tests replacing 22 todos
- `package.json` — Added react-hook-form + @hookform/resolvers
## Decisions Made
- tRPC v11 proxy API: `useTRPC()` returns `TRPCOptionsProxy`, not hook-decorated procedures. Must use `.queryOptions()` + `useQuery()` and `.mutationOptions()` + `useMutation()` from TanStack Query.
- base-nova Button lacks `asChild` prop (same issue as Plan 02). Used `buttonVariants()` on `<Link>` for styled navigation links.
- Server + client island split: RSC page fetches data server-side (SEO-friendly, avoids loading flicker), `PetGrid` client component handles active pet state from `useActivePet()`.
- Owner username for pet profile attribution uses `currentUser()` from Clerk — the Owner Prisma model only has `id` (Clerk userId). No username stored in DB; Clerk is the source of truth.
- `AvatarUploadZone` is a stub showing idle state with disabled upload in create mode; Plan 04 wires R2 presigned upload.
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 1 - Bug] tRPC v11 useTRPC() proxy does not have .useQuery()/.useMutation() on procedures**
- **Found during:** Task 2 (PetForm component implementation)
- **Issue:** Plan instructions described using `trpc.pets.listSpecies.useQuery()` and `trpc.pets.create.useMutation()` — this is the tRPC v10 `@trpc/react-query` API. In tRPC v11 with `@trpc/tanstack-react-query`, the `useTRPC()` hook returns a `TRPCOptionsProxy` that exposes `.queryOptions()` and `.mutationOptions()`, not hook-decorated procedures. TypeScript errors on `.useQuery` and `.useMutation` not existing.
- **Fix:** Rewrote PetForm to use `useQuery({ ...trpc.pets.listSpecies.queryOptions() })` and `useMutation({ ...trpc.pets.create.mutationOptions() })` from `@tanstack/react-query`. Same pattern used in edit page for pets.delete.
- **Files modified:** `src/components/pet/PetForm.tsx`, `src/app/(app)/pets/[petId]/edit/page.tsx`
- **Verification:** `npx tsc --noEmit` exits 0 (excluding pre-existing .next/dev cache error)
- **Committed in:** 63d8c06 (Task 2), 95dfec7 (Task 3)
**2. [Rule 1 - Bug] base-nova Button lacks asChild prop — cannot wrap Link**
- **Found during:** Task 3 (pet profile pages)
- **Issue:** Plan specified `<Button asChild><Link href="...">` pattern. base-nova's Button uses `@base-ui/react/button` which does not support `asChild`. Same issue fixed in Plan 02.
- **Fix:** Used `buttonVariants({ variant: "outline" })` CSS class on `<Link>` elements directly (same approach as Plan 02 Sidebar).
- **Files modified:** `src/app/(app)/pets/page.tsx`, `src/app/(app)/pets/[petId]/page.tsx`
- **Verification:** `npx tsc --noEmit` exits 0
- **Committed in:** 95dfec7 (Task 3)
**3. [Rule 1 - Bug] tRPC createCaller context type causes TypeScript union type error in tests**
- **Found during:** Task 4 (unit tests)
- **Issue:** `petsRouter.createCaller` accepts `Context | (() => MaybePromise<Context>)`. TypeScript inferred the parameter as a union type, so accessing `.prisma` on it caused `TS2339: Property 'prisma' does not exist on type '...union...'`.
- **Fix:** Declared explicit `type CallerCtx = { userId: string | null; prisma: PrismaClient }` and typed the mock context against it before passing to `createCaller`.
- **Files modified:** `src/__tests__/pet-profile.test.ts`
- **Verification:** `npx tsc --noEmit` exits 0; all 11 tests pass
- **Committed in:** 8824391 (fix commit)
---
**Total deviations:** 3 auto-fixed (all Rule 1 — API behavior differences between plan instructions and actual library version)
**Impact on plan:** All fixes address tRPC v11 / base-nova API differences from plan-time assumptions. No scope creep.
## Known Stubs
| File | Location | Stub | Reason |
|------|----------|------|--------|
| `src/components/pet/PetForm.tsx` | `AvatarUploadZone` function | Renders idle upload UI; upload disabled in create mode | Plan 04 wires actual R2 presigned upload |
| `src/app/(app)/pets/[petId]/page.tsx` | Posts section | "No posts yet" placeholder | Phase 2 adds the posts grid |
## Threat Surface Scan
No new trust boundaries beyond what the plan's threat model covers. All mutations (update, delete) go through `assertPetOwnership` before any DB write (T-3-01). listSpecies/listBreeds are `protectedProcedure` (T-3-04). Pet profile page uses `notFound()` on ownership violation (T-3-03).
## Issues Encountered
- `.next/dev/types/routes.d.ts` is empty (stale Next.js dev build cache from prior sessions). Causes a single TypeScript error in `.next/dev/types/validator.ts`. Pre-existing issue — not caused by Plan 03 changes. All application code is clean. Will self-resolve when `npm run dev` regenerates the routes file.
## User Setup Required
Before verifying the checkpoint, the user must have `npx prisma db push` completed (from Plan 01 checkpoint) and the seed run. The new breed seed is additive — run `npx prisma db seed` again to populate the 45 breed rows.
## Next Phase Readiness
- Pet CRUD complete: create, view, edit, delete all wired end-to-end
- Breed taxonomy seeded: species selector and breed selector are functional with real DB data
- AvatarUploadZone stub in place: Plan 04 can wire the upload without changing PetForm's interface
- Active pet context and sidebar pet switcher work with real Pet rows
- All 5 PET requirements (PET-01 through PET-05) are implemented and unit-tested
## Self-Check: PASSED
Files verified:
- src/trpc/routers/pets.ts: FOUND (update, delete, listSpecies, listBreeds present)
- prisma/seed.ts: FOUND (Labrador Retriever present)
- src/components/pet/PetForm.tsx: FOUND
- src/components/pet/PetCard.tsx: FOUND
- src/app/(app)/pets/page.tsx: FOUND
- src/app/(app)/pets/PetGrid.tsx: FOUND
- src/app/(app)/pets/[petId]/page.tsx: FOUND
- src/app/(app)/pets/[petId]/edit/page.tsx: FOUND
- src/app/onboarding/pet/page.tsx: FOUND (PetForm wired, no TODO stub)
- src/__tests__/pet-profile.test.ts: FOUND (11 real tests)
Commits verified:
- 69553b1: feat(01-03) pets router + breed seed
- 63d8c06: feat(01-03) PetForm + PetCard
- 95dfec7: feat(01-03) pet profile pages
- f996459: test(01-03) unit tests
- 8824391: fix(01-03) type fix
---
*Phase: 01-foundation*
*Completed: 2026-06-06*