Commit Graph
104 Commits
Author SHA1 Message Date
adminandClaude Sonnet 4.6 d17d71e55a feat(health): emergency vet with OSM search and map
- EmergencyVet model: name, address, phone, website, lat, lng (1:1 to Pet)
- health router: getEmergencyVet, setEmergencyVet, deleteEmergencyVet
- EmergencyVetSection: Nominatim search (debounced 500ms, OSM attribution),
  result dropdown auto-fills address + coordinates, manual override for all fields
- OSM iframe map (no API key) with "In Karte öffnen" link; shown in edit
  mode as coordinate preview and in view mode after save
- Health card (public): red-accented emergency vet block with phone link,
  website link and OSM map; map hidden on print (print:hidden)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 17:39:46 +02:00
adminandClaude Sonnet 4.6 c9f385a9fa fix(profile): replace bottom sheet with centered dialog for post detail
- Sheet side=bottom was anchored to screen bottom edge
- Default SheetContent X button overlapped PostCard 3-dot menu
- Dialog renders centered, showCloseButton=false removes the X
  (backdrop click / Escape still closes; 3-dots remain the only top-right control)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 17:23:21 +02:00
adminandClaude Sonnet 4.6 b0d6a4d7d1 fix(cron): birthday notifications require 1+ year like adoptedAt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 17:14:47 +02:00
adminandClaude Sonnet 4.6 f6aa825ad1 feat(v2): algorithmic feed toggle, anniversary notifications, welcome card
Algorithmic Feed:
- feed.getFeed gains `mode: chrono | algo` param (default: chrono)
- Algo scoring: reactions*3 + comments*5 + reposts*4 + recency bonus
- FeedList: pill toggle Fuer dich (algo) / Aktuell (chrono), saved to localStorage

Anniversary Notifications:
- Pet schema: birthday Date?, adoptedAt Date?
- NotificationType enum: +BIRTHDAY, +ADOPTION_DAY
- /api/cron/anniversaries: daily cron (08:00 UTC via vercel.json)
- Pet edit form: birthday + adoptedAt date pickers
- Notifications page: Cake/Home icons, German copy, links to health page

New-User Welcome Card:
- WelcomeCard shown at top of feed while localStorage flag unset
- 5 feature tiles: Posts/Videos, Stories, Milestones, DMs, Health
- Dismissable via X or Verstanden; per-pet flag in localStorage

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 17:10:50 +02:00
adminandClaude Sonnet 4.6 80559c3b3f feat(health): health card share link, PDF export, weight chart in card
- ShareDialog: expiry selector (7/14/30/60/90 days, default 14), shows
  expiry date after generation, "Als PDF öffnen / drucken" button
- WeightChart extracted to standalone component (WeightChart.tsx) so it
  can be shared between HealthDashboard and the public health card page
- Health card page: renders WeightChart when 2+ weight entries exist
- AutoPrint client component: auto-triggers window.print() when card is
  opened with ?print=1 (600ms delay for render to settle)
- README: Phase 6 + Phase 7 marked complete with full feature summaries;
  project structure and router list updated

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 06:06:28 +02:00
adminandClaude Sonnet 4.6 b9f786c87f feat(health): add SVG weight trend chart
Renders a smooth bezier line chart above the weight log list when
2+ entries exist. No external library — pure SVG with:
- Orange-500 line + gradient area fill (PawFeed accent)
- Hover tooltip with dark bg, orange weight value, muted date
- Glow ring + dot state change on hover
- Bezier curve interpolation for smooth line
- Responsive via viewBox (maxHeight 190px)
- Y-axis: auto-scaled with 4 grid ticks
- X-axis: up to 5 date labels (DD.MM), always shows first + last
- Trend delta label (neutral, context-dependent for pets)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 05:49:29 +02:00
adminandClaude Sonnet 4.6 f1c798a4b0 feat(07): Phase 7 — Health & Vet Tracking
Schema: WeightLog, VetVisit, Vaccine, HealthCard models
Router: health.ts — CRUD for weight/vet/vaccines + health card token management
Pages:
  - /pets/[petId]/health — owner-only dashboard (weight, vet visits, vaccines)
  - /health-card/[token] — public shareable health card (no auth required)
Profile: Health button added for own pets; Edit + Health side by side

Health data is owner-private; the Health Card link is the only public
access point, shareable via token URL (vet, friends, sitters).
Token can be regenerated to revoke access.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 05:07:56 +02:00
adminandClaude Sonnet 4.6 5d396a7bb4 fix: make pet profiles clickable from feed, search, and notifications
- Add pets.getProfile procedure (no ownership check) so any auth'd user can view any pet profile
- Profile page now uses getProfile; Edit link and owner attribution only shown for own pets
- PostCard header: wrap PawRing + pet name in Link to /pets/[petId]
- Search Pets tab + People tab pet rows: wrap avatar/name in Link to /pets/[petId]

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 04:52:45 +02:00
adminandClaude Sonnet 4.6 e2e932804b feat(06): Phase 6 — Direct Messages
- Schema: DmPolicy enum (EVERYONE/FOLLOWERS_ONLY), dmPolicy on Pet, Conversation + Message models
- conversationsRouter: list (inbox), getOrCreate (with DmPolicy + block check), unreadCount
- messagesRouter: list (3s poll, auto mark-read), send (+ MESSAGE notification), markRead
- /messages inbox page with unread badges + last message preview
- /messages/[conversationId] thread with bubble UI, day dividers, Enter to send
- Message button on pet profiles (ProfileActions) → getOrCreate → navigate to thread
- Sidebar: Messages link activated
- Pet edit page: DM Privacy setting (Everyone / Only pets I follow)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 09:45:45 +02:00
adminandClaude Sonnet 4.6 1fc69add4d docs: update README to reflect Phases 1-5 complete
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 08:44:48 +02:00
adminandClaude Sonnet 4.6 da0f9f2bb7 fix: resolve 5 bugs from dev testing
- PostCard: add variant="detail" (object-contain + black bg) for profile sheet — full image in original format
- VideoCard: getByPostId polls Mux API directly when PROCESSING, self-heals without webhooks in dev
- PawRing: gray ring for all-seen stories, orange for unseen; reads viewerPetId from ActivePetContext
- Search: add People tab via new search.byOwner (Clerk username search → owner's pets)
- Notifications: rows now navigate to actor profile (FOLLOW) or pet profile (REACTION/COMMENT)
- Notifications router: include post.petId for navigation target

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 08:41:46 +02:00
adminandClaude Sonnet 4.6 60366691e8 feat(05): Phase 5 — short-form video via Mux
Schema:
- Add VideoPost model (muxUploadId, muxAssetId, muxPlaybackId,
  status, durationSecs, aspectRatio) linked 1-to-1 with Post
- Add VideoStatus enum (PROCESSING/READY/ERROR)
- Add PostType.VIDEO

Backend:
- src/lib/mux.ts — singleton Mux client + webhook secret
- videos router: createUpload (Mux direct upload URL + PROCESSING post),
  getByPostId (status polling), updateCaption
- POST /api/webhooks/mux: verifies signature; handles
  video.upload.asset_created (store muxAssetId),
  video.asset.ready (set READY + playbackId + fan-out to feed),
  video.asset.errored (set ERROR)
- feed.ts + posts.byPetId: include videoPost in all post queries

UI:
- VideoUploadForm: drop zone → XHR PUT to Mux upload URL with
  progress bar; caption field; transitions to "Processing…" on success
- VideoCard: PROCESSING shows spinner; READY renders MuxPlayer
  (HLS, orange accent); polls every 5s until status changes
- PostTypeSheet: "Video Post" option added (between Photo and Milestone)
- PostCard: VIDEO type renders VideoCard instead of image carousel

Env vars: MUX_TOKEN_ID, MUX_TOKEN_SECRET, MUX_WEBHOOK_SECRET,
NEXT_PUBLIC_APP_URL — placeholders added to .env.local

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 13:27:43 +02:00
adminandClaude Sonnet 4.6 08fd0dfb71 feat(04): Phase 4 — in-app notifications
Schema:
- Add NotificationType enum (FOLLOW, REACTION, COMMENT, MESSAGE)
- Add Notification model with recipientPet, actorPet, post, comment
  relations; index on [recipientPetId, read, createdAt desc]

Backend:
- notifications router: list (cursor-paginated), unreadCount,
  markRead, markAllRead
- follows.create: fire-and-forget FOLLOW notification to followee
- reactions.toggle: REACTION notification on create (skip self)
- comments.create: COMMENT notification with commentId (skip self)
  All triggers use .catch(()=>{}) to never fail the main action

UI:
- NotificationBell component with orange badge, polls every 30s
- /notifications page: list with avatar, type icon, text, thumbnail,
  read/unread dot; auto-marks all read on visit
- Sidebar: Notifications link activated with NotificationBell
- MobileNav: Bell replaces Search tab (Search still at /search)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 12:59:23 +02:00
adminandClaude Sonnet 4.6 7fafc6d17f fix(StoryTray): align Add Story circle with PawRing items
PawRing containers are size+RING_PAD*2=72px tall while the Add Story
button was only 56px, causing vertical misalignment. Wrap the button
in a matching 72px container so all tray items share the same height.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 12:44:57 +02:00
adminandClaude Sonnet 4.6 4b2760a08d fix(PawRing): center main pad ring at cy=50 so avatar sits inside it
mainCy=58 shifted the ring below the flex-centered avatar, causing the
image to appear above/half-inside the ring. Setting cy=50 aligns the
ring with the container center; toe beans (cy≈1-10) overflow upward via
SVG overflow:visible, which is the correct paw-print anatomy.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 12:43:27 +02:00
adminandClaude Sonnet 4.6 a9886948b8 fix: empty feed + unclickable profile grid
- feed.getFeed: when Redis is unavailable, fall back to a direct
  Postgres query (followed pets + own posts, cursor-based) instead of
  returning an empty array — feed now works in dev without Upstash
- ProfileTabs: wrap grid thumbnails in <button> + open a bottom Sheet
  with the full PostCard on tap; previously tapping a photo did nothing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 12:38:53 +02:00
adminandClaude Sonnet 4.6 5af2a40bb3 fix: resolve three runtime errors found in dev smoke test
- MultiImageUpload: move onKeysChange out of setImages updater into
  useEffect to fix React "Cannot update a component while rendering"
  warning; same fix applied to handleRemove
- feed.getFeed: wrap getFeedPage in try/catch so Redis failure
  (Upstash not configured) returns empty feed instead of 500
- posts.create + reposts.create: wrap fanOutPost with .catch(() => {})
  so Redis failure never rolls back an already-committed post

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 12:33:14 +02:00
adminandClaude Sonnet 4.6 01b4f18a97 fix: wrap useSearchParams in Suspense on reset-password page
Next.js static rendering requires useSearchParams() to be inside a
Suspense boundary. Splits page into ResetPasswordContent (inner) +
ResetPasswordPage (Suspense wrapper). Caught by npm run build.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 12:21:07 +02:00
adminandClaude Sonnet 4.6 847ac97bde feat(03-06): Hashtag search end-to-end — Phase 3 complete
- hashtag-helpers.ts: extractHashtags (regex+dedup) + saveHashtags (upsert)
- parse-caption.ts: parseCaption → CaptionSegment[] for client rendering
- search.byHashtag GREEN: hashtag.findUnique → postHashtag.findMany + pagination
- posts.create + reposts.create: extract+save hashtags after post creation
- /hashtag/[tag]: infinite scroll post grid + useInView sentinel
- /search Tags tab: byHashtag query wired, 3-col post grid
- PostCard: parseCaption replaces raw caption — hashtags are orange Link to /hashtag/[tag]
- STATE.md: Phase 3 complete, 3/7 phases done

Tests: 67 passing, 0 failing (5 todo), tsc clean

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 12:16:03 +02:00
adminandClaude Sonnet 4.6 59a3be27b0 feat(03-05): Explore + pet search — routers GREEN + pages + nav
- explore.listPets: publicProcedure, speciesId/breedId filter, cursor pagination
- search.pets: protectedProcedure, ILIKE name+breed OR, cursor pagination
- ExploreCard: avatar, breed badge, follower count, FollowButton
- /explore: species tabs + breed filter Select + infinite pet grid
- /search: debounced input, Pets tab with FollowButton rows, Tags stub
- Sidebar: Explore activated, Search added
- MobileNav: Explore activated, Notifications placeholder → Search

Tests: explore 3/3 GREEN, search.pets 2/2 GREEN; 65 total passing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 12:09:24 +02:00
adminandClaude Sonnet 4.6 8507fe9489 feat(03-04): Paw-Back/Repost — reposts router GREEN + RepostCard + PawBackButton
- reposts.create: ownership + repost-of-repost guard + idempotency + fanOutPost
- reposts.delete: ownership + cascade via post.delete
- reposts.isReposted: compound-key lookup, returns boolean
- PawBackButton: optimistic toggle, orange when reposted, invalidates feed
- RepostCard: attribution header + original pet/images/caption, no action row
- PostCard: REPOST type dispatches to RepostCard; stub replaced with PawBackButton
- feed.ts + posts.ts: repost include added to all post hydration queries
- PostCardPost type: OriginalPostContent + repost field

Tests: 3/3 reposts GREEN, 60 total passing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 12:01:58 +02:00
adminandClaude Sonnet 4.6 6fbb99e7c9 feat(03-02): Paw reactions GREEN — toggle/count router + PawButton on PostCard
- reactions.toggle: assertPetOwnership → findUnique(postId_petId) → create or delete, return {reacted}
- reactions.hasReacted, getCount implemented
- feed.ts + posts.byPetId: add _count{reactions,comments} to hydration include
- PawButton: optimistic toggle, filled/outline PawPrint icon, count badge
- PostCard: replace "0 likes/0 comments" placeholder with action row (PawButton + comment chip + Paw-Back stub)
- PostCardPost type: add optional _count field
- 4/4 reactions tests GREEN; 56 passing total (+3 from 53)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 11:43:31 +02:00
adminandClaude Sonnet 4.6 b71edfa758 docs: update README — Phase 3 in progress, plan 1/6 complete
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 11:37:04 +02:00
adminandClaude Sonnet 4.6 73666a3916 feat(03-01): Phase 3 schema foundation — Reaction/Comment/Repost/Hashtag models + router stubs RED
- Prisma: add Reaction, Comment, Repost, Hashtag, PostHashtag models; PostType.REPOST enum
- Back-relations on Post and Pet for all Phase 3 models (named to avoid ambiguous-relation errors)
- 5 tRPC router stubs: reactions, comments, reposts, explore, search — all registered in _app.ts
- Extend prisma-mock.ts with Phase 3 model mocks
- 5 test scaffold files: 14 failing RED + 3 FORBIDDEN passing; 53 Phase 2 tests untouched
- Phase 3 planning: CONTEXT, RESEARCH, UI-SPEC, VALIDATION, 6 PLANs, ROADMAP + STATE updated

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 11:31:48 +02:00
admin 273d412fb4 docs: add README with setup, architecture, progress and debugging notes 2026-06-13 10:25:30 +02:00
admin 3fa62d2ce0 feat(02-06): MilestoneForm + PostTypeSheet wiring + ProfileTabs + profile page — Phase 2 complete 2026-06-13 10:22:35 +02:00
admin 9b02d3dc8b feat(02-06): milestone-meta map + MilestoneCard + PostCard MILESTONE routing — MILE-02 2026-06-13 10:21:49 +02:00
admin 554ca1deae test(02-06): add milestones tests RED → implement milestonesRouter GREEN — MILE-01 2026-06-13 10:21:40 +02:00
admin 9a901e3485 feat(02-05): wire seams — PawRing in PostCard, StoryTray in feed, StoryForm in PostTypeSheet 2026-06-13 10:11:09 +02:00
admin e30531f834 feat(02-05): storiesRouter GREEN + PawRing + StoryViewer + StoryTray + StoryForm 2026-06-13 10:08:23 +02:00
admin 99bff88545 test(02-05): add stories tests RED — STORY-01-03 2026-06-13 10:08:16 +02:00
admin fa9b33ad7e refactor(feed): use PrismaClient type directly in assertPetOwnership 2026-06-13 10:02:17 +02:00
adminandClaude Sonnet 4.6 f7b09902f3 docs(02-04): complete photo posts plan summary
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 12:50:55 +02:00
adminandClaude Sonnet 4.6 e4b619671f feat(02-04): post creation flow — MultiImageUpload, PhotoPostForm, PostTypeSheet, + button nav
MultiImageUpload:
- Per-file: validate type(jpg/png/webp) + size(<10MB) with exact UI-SPEC error copy
- Rejects >10 total: "You can upload up to 10 photos per post."
- posts.getPresignedUrl → XHR PUT with exact Content-Type (Pitfall 4 prevention)
- Reorderable thumbnails with remove button and upload progress bar

PhotoPostForm:
- MultiImageUpload + caption Textarea (max 500, error "Caption must be 500 characters or fewer.")
- "Share Post" CTA (bg-orange-500) → posts.create({ petId: activePetId, imageKeys })
- onSuccess: invalidate feed query, close Dialog, router.push('/feed') (D-03)
- Toasts: "Post shared" / "Couldn't share your post. Please try again."

PostTypeSheet:
- Bottom Sheet titled "Create" with 3 options: Photo Post, Story, Milestone (D-01)
- Photo → full-screen Dialog with PhotoPostForm (D-02, no URL change)
- Story/Milestone: TODO(02-05)/TODO(02-06) seams with placeholder dialogs

Sidebar + MobileNav:
- + button opens PostTypeSheet (D-01)
- MobileNav center + button: was Link to /pets, now triggers PostTypeSheet
- Sidebar: New Post button added above owner identity

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 12:48:56 +02:00
adminandClaude Sonnet 4.6 15f51825d9 feat(02-04): full PostCard — carousel + caption clamp + options menu
- Header: Avatar (story ring seam TODO(02-05)), pet name, timestamp, MoreHorizontal
- Single image: object-cover max-h-[560px]; Carousel: Embla for 2–10 images
- Carousel index indicator top-right; dot indicators bg-muted/bg-foreground (NOT orange)
- Caption line-clamp-3 with more/less toggle (instant, no animation per UI-SPEC)
- Own post options: Edit caption (inline Textarea) + Delete post (AlertDialog)
- Other's post options: Report post (ReportSheet) + Block {name} (BlockDialog)
- updateCaption / delete wired to tRPC mutations with exact UI-SPEC copy
- activePetId === post.pet.id determines own-vs-other ownership

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 12:45:32 +02:00
adminandClaude Sonnet 4.6 75d1e80665 feat(02-04): implement postsRouter GREEN — PHOTO-01–04
- getPresignedUrl: scopes key to posts/{ownerId}/{petId}/, ownership check (T-02-30)
- create: ownership assertion + Post+PostImage nested write + fanOutPost (PHOTO-01)
- create: imageKeys.max(10) enforces carousel limit (PHOTO-02, T-02-32)
- imageKeys.startsWith('posts/') prevents path traversal (T-02-31)
- updateCaption: assertPostOwnership via post→pet→ownerId (PHOTO-03, T-02-34)
- delete: assertPostOwnership + prisma.post.delete, PostImages cascade (PHOTO-04)
- byPetId: cursor-paginated query with images+pet+milestone include

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 12:43:35 +02:00
adminandClaude Sonnet 4.6 5e71010e50 test(02-04): add failing tests for posts router RED
- PHOTO-01: create single photo inserts Post + PostImage + fans out
- PHOTO-02: carousel limit rejects >10 images (Zod already validates)
- PHOTO-03: updateCaption forbidden for non-owner → FORBIDDEN
- PHOTO-04: delete calls prisma.post.delete (PostImages cascade via schema)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 12:42:49 +02:00
admin bdd676f19b docs(02-02): complete follow/block/report plan summary
- SOCL-01/02/03 and SAFE-01/SAFE-02 all implemented and tested
- 21 new tests green (follows: 10, blocks: 7, reports: 7)
- tsc exits 0; vitest 36 passed
2026-06-07 12:39:47 +02:00
admin 7f969a7b33 docs(02-03): complete feed infrastructure plan summary
- FEED-01 implemented: Redis-backed infinite query + FeedList + EmptyFeed + PostCard placeholder
- /feed page created; home redirect (D-04); nav Home→/feed (D-05)
- TDD gate compliance: RED (9219d3a) → GREEN (56f976e) documented
2026-06-07 12:38:40 +02:00
admin 0428cf2a04 feat(02-02): social + safety UI — FollowButton, PetListItem, BlockDialog, ReportSheet, followers/following pages
- FollowButton: optimistic follow/unfollow, revert on error, AlertDialog unfollow confirm (SOCL-01/02)
- PetListItem: avatar + name + species badge + right-aligned FollowButton (SOCL-03)
- BlockDialog: AlertDialog with exact UI-SPEC copy, destructive Block CTA (SAFE-01)
- ReportSheet: 5 RadioGroup reasons, notes Textarea, destructive Submit CTA (SAFE-02)
- followers/page.tsx: RSC shell "{N} Followers" heading + FollowersList client (SOCL-03)
- following/page.tsx: RSC shell "Following {N}" heading + FollowingList client (SOCL-03)
- pets/[petId]/page.tsx: stats row (posts/followers/following) + ProfileActions (SOCL-03)
- ProfileActions: FollowButton + MoreHorizontal options menu + BlockDialog + ReportSheet
- Fix: test prisma cast to `as any` resolving TS2322 type errors (Rule 1 - Bug)
- tsc exits 0; vitest 36 passed
2026-06-07 12:37:45 +02:00
admin e9b286b4df feat(02-03): /feed page + home redirect + nav Home→/feed
- src/app/(app)/feed/page.tsx: RSC shell with StoryTray seam + FeedList, metadata "Home — PawFeed"
- src/app/page.tsx: redirect("/feed") per D-04 (home route → /feed after Phase 2)
- Sidebar.tsx: Home href "/" → "/feed"; active detection handles /feed and / (D-05)
- MobileNav.tsx: Home href "/" → "/feed"; active state updated for /feed (D-05)
- (app)/layout.tsx: unchanged — zero-pets redirect to /onboarding/pet preserved (D-06)
2026-06-07 12:37:04 +02:00
admin fd7dd2d56b feat(02-03): FeedList + SkeletonCard + EmptyFeed + PostCard placeholder
- FeedList: useInfiniteQuery + useInView sentinel, 3 skeleton loading, empty state, end-of-feed copy
- SkeletonCard: animate-pulse 80px header + image + 2 text lines per UI-SPEC
- EmptyFeed: "Your feed is empty" heading + Find Pets CTA to /explore (D-06)
- PostCard: minimal placeholder (header + single image + caption); marked TODO(02-04) for carousel
- feed.test.ts: any-cast for mock prisma (tsc clean on new code; 3 pre-existing errors unrelated)
2026-06-07 12:35:07 +02:00
admin f85230a074 feat(02-02): implement blocksRouter + reportsRouter (GREEN)
- blocks.create: assertPetOwnership + self-block rejection + prisma.block.create (SAFE-01)
- blocks.list: assertPetOwnership + prisma.block.findMany scoped by blockerPetId (T-02-12)
- reports.create: assertPetOwnership + both-null target rejection + prisma.report.create (SAFE-02)
- ReportReason enum: SPAM, HARASSMENT, INAPPROPRIATE, MISINFORMATION, OTHER (T-02-13)
- Blocks authoritative in Postgres only; never Redis (RESEARCH anti-pattern avoided)
2026-06-07 12:33:42 +02:00
admin 65b30c7889 test(02-02): add failing tests for blocks + reports routers (RED)
- SAFE-01: blocks.create inserts row, self-block rejected, ownership assertion, list returns blockedPetIds
- SAFE-01: feed block filter helper test confirms excluded petIds pattern
- SAFE-02: reports.create with targetPostId/targetPetId, both-null rejection, ownership assertion, all 5 enum values
2026-06-07 12:32:52 +02:00
admin 7bdb2989ca feat(02-02): implement followsRouter (GREEN)
- follows.create: assertPetOwnership + self-follow rejection + prisma.follow.create + backfillOnFollow
- follows.delete: assertPetOwnership + prisma.follow.delete by composite key
- follows.isFollowing: prisma.follow.findUnique → boolean
- follows.listFollowers: scoped by followeePetId, includes follower display fields
- follows.listFollowing: scoped by followerPetId, includes followee display fields
- All mutations use protectedProcedure; social graph is pet-to-pet never ownerId (SOCL-01/02/03)
2026-06-07 12:31:50 +02:00
admin 56f976e3c4 feat(02-03): implement feed.getFeed router with Redis + Prisma hydration
- assertPetOwnership enforces petId ownership (T-02-20)
- getFeedPage reads Redis sorted set with cursor pagination
- Block filter applied at Prisma hydration layer (T-02-21)
- Post order restored to Redis score order after Prisma findMany (Pitfall 7)
- FEED-01 tests green
2026-06-07 12:31:36 +02:00
admin 2e1cf45d7a test(02-02): add failing tests for follows router (RED)
- SOCL-01: follow creates row, self-follow rejected, ownership assertion, backfillOnFollow called
- SOCL-02: unfollow removes row, ownership assertion
- isFollowing: returns true/false based on DB state
- SOCL-03: listFollowers scoped by petId, listFollowing scoped by petId
2026-06-07 12:31:13 +02:00
admin 9219d3a907 test(02-03): add failing tests for feed.getFeed FEED-01
- Empty feed: returns [] and null nextCursor for pet with no follows
- Cursor pagination: nextCursor passed through from getFeedPage
- Block filter: blocked pets excluded from hydration
- Ownership: throws FORBIDDEN when petId is not owned by requester
2026-06-07 12:31:03 +02:00
admin fb76e7cd7b docs(02-01): complete foundation scaffold plan summary
- SUMMARY.md for Phase 02 Plan 01 (Foundation Scaffold, Wave 0)
- 8 Prisma models pushed, 7 router stubs registered, 7 test scaffolds created
- 2 auto-fixed deviations documented (ZRANGE offset, vi import)
2026-06-07 12:28:42 +02:00
admin 4c8ca3ab2f fix(02-01): add explicit vi import to 7 test scaffold files for TypeScript compatibility
vi.mock() is vitest-hoisted at runtime but TypeScript requires explicit import.
Adds: import { vi } from "vitest" to all 7 new test scaffolds.
npx tsc --noEmit now exits 0.
2026-06-07 12:27:22 +02:00