diff --git a/.planning/phases/02-content-core/02-CONTEXT.md b/.planning/phases/02-content-core/02-CONTEXT.md new file mode 100644 index 0000000..087725a --- /dev/null +++ b/.planning/phases/02-content-core/02-CONTEXT.md @@ -0,0 +1,122 @@ +# Phase 2: Content Core - Context + +**Gathered:** 2026-06-06 +**Status:** Ready for planning + + +## Phase Boundary + +Phase 2 delivers the complete content layer for PawFeed: pets can post photos (single + carousel), stories (24h disappearing), and milestone posts (structured types). Pets follow each other, owners see a chronological home feed. Block and report are live before any content is publicly accessible. The home route becomes the feed. + + + + +## Implementation Decisions + +### Post Creation Flow +- **D-01:** Post creation entry point is the center **+** button in the mobile bottom nav and a corresponding button in the desktop sidebar. Tapping/clicking it opens a **bottom sheet** (Sheet component) with three type choices: Photo, Story, Milestone. +- **D-02:** After selecting a type from the sheet, the creation form opens as a **full-screen modal overlay** (Dialog). No URL navigation change — the owner stays on the current page while creating. +- **D-03:** After successfully posting, the modal closes and the owner **lands on the feed (/feed) with the new post at the top**. Feed re-fetches on mutation success. + +### Home & Navigation +- **D-04:** After Phase 2, the **home route (/)** redirects to **/feed**. The feed is the primary landing page after login, replacing the current /pets redirect. +- **D-05:** The **"Home" link** in the sidebar and mobile bottom nav points to **/feed**. Label stays "Home" (universal social app convention). +- **D-06:** Onboarding redirect logic in the app layout stays: if user has zero pets → /onboarding/pet. If user has pets but no follows → show the feed with an empty state ("Follow some pets to see their posts here" + "Find Pets" CTA). + +### Story Expiry +- **D-07:** Stories use **lazy-delete on read**: the `stories` table has an `expires_at` column. All story queries filter `WHERE expires_at > NOW()`. A weekly cleanup job (Supabase `pg_cron` or manual) purges old records. No real-time cron infrastructure needed in Phase 2. +- **D-08:** Story **view analytics are retained for 30 days** even after the story expires. When a story expires, its record stays in the DB with `expired: true` but view records (`story_views` table) are NOT cascade-deleted. A separate cleanup job purges `story_views` older than 30 days. This allows the owner to see historical engagement. + +### Milestone Types +- **D-09:** Predefined milestone types for Phase 2 (no custom types — consistent visual format): + - `birthday` — "🎂 Birthday" + - `adoption_day` — "🐾 Adoption Day" + - `first_outing` — "🌿 First Outing" + - `vet_visit` — "⚕️ Vet Visit" + - `litter` — "🍼 New Litter" (female pets who have delivered a litter; free-text field for litter details) + - Follower milestones (auto-triggered, not user-initiated): `followers_500`, `followers_1k`, `followers_2_5k`, `followers_5k`, `followers_10k`, `followers_25k`, `followers_50k`, `followers_100k`, `followers_250k`, `followers_500k`, `followers_1m` +- **D-10:** **Follower milestones are auto-triggered**: when a pet's follower count crosses a threshold, the system sends the owner an in-app notification ("Max just reached 1,000 followers! 🎉") with a prompt to share the milestone post. The owner chooses whether to post. The milestone post itself is still user-initiated (from the notification or the + button). +- **D-11:** Custom milestone types are **deferred to a later phase**. Predefined list only for now. + +### Claude's Discretion +- Feed pagination cursor implementation details (cursor vs offset, page size) +- Redis sorted set key naming convention for the fan-out feed +- Exact DB schema for `posts`, `stories`, `milestones` tables (beyond the decisions above) +- Story view deduplication logic (track unique viewers, not view count) +- Follow notification batching implementation + + + + +## Canonical References + +**Downstream agents MUST read these before planning or implementing.** + +### Design Contract +- `.planning/phases/02-content-core/02-UI-SPEC.md` — Phase 2 UI design contract: PostCard, StoryRing, MilestoneCard visual specs, all copy strings, spacing exceptions, new shadcn components required + +### Phase 1 Foundation +- `.planning/phases/01-foundation/01-01-SUMMARY.md` — tRPC router patterns, Prisma schema, ActivePetContext +- `.planning/phases/01-foundation/01-04-SUMMARY.md` — Supabase Storage media pipeline (presigned upload → confirmUpload) — MUST reuse for photo posts +- `src/trpc/routers/pets.ts` — ownership assertion pattern (assertPetOwnership) — replicate for all new content mutations +- `src/trpc/routers/media.ts` — presigned upload flow — extend for photo post uploads +- `src/lib/supabase-storage.ts` — `createSignedUploadUrl`, `getPublicUrl` — reuse directly +- `src/lib/avatar-url.ts` — CDN URL construction pattern — replicate for post images + +### Requirements +- `.planning/REQUIREMENTS.md` — Phase 2 requirements: PHOTO-01–04, STORY-01–03, MILE-01–02, SOCL-01–03, FEED-01, SAFE-01–02 + +### Research +- `.planning/research/SUMMARY.md` — Redis fan-out feed architecture, Supabase Realtime, story data model guidance + + + + +## Existing Code Insights + +### Reusable Assets +- `src/trpc/routers/media.ts` + `src/lib/supabase-storage.ts`: Photo post uploads reuse the exact same presigned upload pipeline as avatar uploads. The tRPC `media.getPresignedUrl` + `media.confirmUpload` pattern is the blueprint. +- `src/components/pet/AvatarUpload.tsx`: The five-state upload component (idle/dragging/uploading/complete/error) is the reference implementation for any upload UI in Phase 2. +- `src/context/ActivePetContext.tsx`: All content is posted as the active pet. `useActivePet()` provides the `activePetId` for every mutation. +- `src/trpc/init.ts`: `protectedProcedure` + `assertPetOwnership` pattern — replicate for every content mutation. +- shadcn components already installed: Card, Dialog, Avatar, Badge, Skeleton, Separator, Button, Sheet (new in Phase 2) + +### Established Patterns +- tRPC v11 mutation: `useMutation({ ...trpc.x.y.mutationOptions(), onSuccess/onError })` +- tRPC v11 query: `useQuery({ ...trpc.x.y.queryOptions(), staleTime })` +- Server component data fetching: `createTRPCCaller()` from `src/trpc/server.ts` +- Ownership scope: always scoped by `ctx.userId` from Clerk, never trust client-supplied owner IDs +- Supabase Storage key format: `{type}/{ownerId}/{petId}/{uuid}.{ext}` — use `posts/{ownerId}/{petId}/{uuid}.jpg` for post images +- CDN URL: `getAvatarUrl(key)` → replicate as `getMediaUrl(key)` for post images + +### Integration Points +- New `posts` router and `stories` router slot into `src/trpc/routers/_app.ts` +- New Prisma models (`Post`, `Story`, `MilestonePost`, `Follow`, `Block`, `Report`) extend `prisma/schema.prisma` +- `src/app/(app)/layout.tsx`: add `/feed` to the protected route group; update home redirect logic per D-04 +- `src/components/layout/Sidebar.tsx` + `MobileNav.tsx`: add + button and Home → /feed update per D-05 + + + + +## Specific Ideas + +- Story view analytics retained 30 days (see D-08) — `story_views` table with `viewed_at` timestamp, not cascade-deleted when story expires +- Follower milestone thresholds: 500, 1k, 2.5k, 5k, 10k, 25k, 50k, 100k, 250k, 500k, 1M — seed into a `milestone_thresholds` table or hard-code as a constant array +- Litter milestone: female pets only, free-text field for "how many puppies/kittens/chicks" +- Feed on empty state: "Follow some pets to see their posts here." + "Find Pets" CTA linking to /explore (Phase 3 feature — CTA can be a placeholder button in Phase 2) + + + + +## Deferred Ideas + +- **Native advertising / sponsored posts** — Owner requested native ad blocks in the feed (styled like posts but labeled "Sponsored"). This is a monetization capability that requires its own phase (advertiser accounts, ad targeting, billing, content policy). Deferred to a future Monetization phase (Phase 8+). +- **Custom milestone types** (free-text) — Deferred to post-MVP. Predefined list only in Phase 2. +- **Auto-follower milestone notifications** infrastructure — The notification trigger (when follower count crosses threshold) depends on Phase 4 (Notifications). In Phase 2, we create the milestone post type and seed the thresholds, but the auto-trigger fires in Phase 4. + + + +--- + +*Phase: 02-content-core* +*Context gathered: 2026-06-06* diff --git a/.planning/phases/02-content-core/02-DISCUSSION-LOG.md b/.planning/phases/02-content-core/02-DISCUSSION-LOG.md new file mode 100644 index 0000000..d0c683d --- /dev/null +++ b/.planning/phases/02-content-core/02-DISCUSSION-LOG.md @@ -0,0 +1,125 @@ +# Phase 2: Content Core - Discussion Log + +> **Audit trail only.** Do not use as input to planning, research, or execution agents. +> Decisions are captured in CONTEXT.md — this log preserves the alternatives considered. + +**Date:** 2026-06-06 +**Phase:** 02-content-core +**Areas discussed:** Post creation flow, Home & navigation, Story expiry mechanism, Milestone type list + +--- + +## Post Creation Flow + +| Option | Description | Selected | +|--------|-------------|----------| +| Center + button → bottom sheet | Mobile: + in bottom nav opens choice sheet. Desktop: sidebar button. Instagram-style. | ✓ | +| Dedicated page /posts/new | Clicking + navigates to a full page with type selector. Cleaner URL. | | +| Inline in feed | Composer at top of feed like Facebook/Twitter. | | + +**User's choice:** Center + button → bottom sheet + +--- + +| Option | Description | Selected | +|--------|-------------|----------| +| Full-screen modal overlay | Opens dialog on top of current page. No URL change. | ✓ | +| Navigate to /posts/new?type=photo | URL changes, browser back works. | | + +**User's choice:** Full-screen modal overlay + +--- + +| Option | Description | Selected | +|--------|-------------|----------| +| Back to feed, new post at top | Modal closes, feed refreshes with new post. | ✓ | +| Navigate to pet profile grid | Go to /pets/[petId]. | | +| Stay on current page | Modal closes, success toast only. | | + +**User's choice:** Back to feed, new post at top + +--- + +## Home & Navigation + +| Option | Description | Selected | +|--------|-------------|----------| +| Feed (/feed) — redirect / → /feed | Landing after login = content feed. | ✓ | +| Keep /pets as home | My Pets grid stays first screen. | | +| Context-dependent | No follows → /pets, has follows → /feed | | + +**User's choice:** Feed (/feed) — Home nav link points to /feed + +--- + +| Option | Description | Selected | +|--------|-------------|----------| +| Home → /feed | Home icon takes you to the feed. | ✓ | +| Rename to 'Feed' and link to /feed | More explicit label. | | + +**User's choice:** Home → /feed (label stays "Home") + +--- + +## Story Expiry Mechanism + +| Option | Description | Selected | +|--------|-------------|----------| +| Lazy-delete on read | Filter expired stories in queries, weekly cleanup job. No extra infrastructure. | ✓ | +| Vercel Cron Job | Hourly cleanup endpoint. Requires Vercel Pro. | | +| Supabase Edge Functions + pg_cron | Scheduled Supabase function. Free but adds complexity. | | + +**User's choice:** Lazy-delete on read + +--- + +| Option | Description | Selected | +|--------|-------------|----------| +| Delete views with story | Cascade delete views when story purged. Simple, no GDPR complexity. | | +| Keep view analytics for 30 days | Historical engagement data retained. Separate cleanup job. | ✓ | + +**User's choice:** Keep view analytics for 30 days — `story_views` table not cascade-deleted on story expiry. + +--- + +## Milestone Type List + +**User's choice (multiSelect):** Birthday, Adoption Day, First Outing, Vet Visit + +**Additional types specified by user:** +- Litter milestone (female animals who have delivered a litter) +- Follower milestones: 500, 1k, 2.5k, 5k, 10k, 25k, 50k, 100k, 250k, 500k, 1M — auto-triggered when follower count crosses threshold + +--- + +| Option | Description | Selected | +|--------|-------------|----------| +| Auto-triggered | System sends notification at threshold, owner decides to post. | ✓ | +| Manually posted only | Owner creates milestone post themselves. | | + +**User's choice:** Auto-triggered follower milestones + +--- + +| Option | Description | Selected | +|--------|-------------|----------| +| Yes, free-text custom type | Owner can type any milestone. | | +| Predefined list only | Consistent visual format. Custom types deferred. | ✓ | + +**User's choice:** Predefined list only for Phase 2 + +--- + +## Claude's Discretion + +- Feed pagination cursor implementation details +- Redis sorted set key naming convention +- Exact DB schema column names (beyond the structural decisions) +- Story view deduplication logic +- Follow notification batching + +## Deferred Ideas + +- **Native advertising / sponsored posts** — Feed with native ad blocks styled like posts but labeled "Sponsored". Monetization phase (Phase 8+). +- **Custom milestone types** — Free-text custom types. Post-MVP. +- **Auto-follower milestone notification trigger** — Depends on Phase 4 Notifications infrastructure.