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
admin and Claude 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
admin and Claude 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
admin and Claude 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
admin and Claude 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
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
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
admin
c74fc0d750
feat(02-01): 7 test scaffolds + prisma-mock extension + react-intersection-observer + 7 shadcn components
...
- Extend src/__tests__/helpers/prisma-mock.ts with post, postImage, milestone, story, storyView, follow, block, report mocks
- Create 7 test scaffold files (21 it.todo stubs covering PHOTO-01..04, STORY-01..03, MILE-01, SOCL-01..03, FEED-01, SAFE-01..02)
- feed.test.ts mocks @/lib/redis — no real Upstash calls in tests
- Install react-intersection-observer ^10.0.3 (slopcheck [OK] per RESEARCH)
- Add shadcn components: carousel, sheet, alert-dialog, scroll-area, tabs, popover, radio-group
- All 7 test files load and run without import errors (21 todo, 0 failures)
2026-06-07 12:26:08 +02:00
admin
c435404b2d
feat(02-01): shared libs + 7 tRPC router stubs registered in _app.ts
...
- src/lib/redis.ts: @upstash/redis singleton (server-only)
- src/lib/feed-helpers.ts: feedKey, fanOutPost, backfillOnFollow, getFeedPage, trimFeed, FEED_RETENTION_MS
- src/lib/media-url.ts: getMediaUrl (mirrors getAvatarUrl signature, client-safe)
- src/lib/milestone-thresholds.ts: FOLLOWER_MILESTONE_THRESHOLDS constant (D-09, A3)
- Router stubs: posts, stories, milestones, follows, blocks, reports, feed (all NOT_IMPLEMENTED)
- All 9 routers registered in _app.ts (pets, media + 7 new)
- feed.getFeed input includes cursor: z.number().nullish() per Pitfall 6
- Rule 1 fix: @upstash/redis zrange requires {offset, count} not just count
- npx tsc --noEmit exits 0
2026-06-07 12:23:10 +02:00
admin
eeab28e8cb
chore: rename project from OnlyPets to PawFeed (domain: pawfeed.org)
2026-06-06 11:51:45 +02:00
admin
95e42f5474
fix(01-04): replace all R2/api-avatar URLs with Supabase Storage CDN via getAvatarUrl()
2026-06-06 11:38:37 +02:00
admin
e3c279d6a4
feat(01-04): switch media storage from Cloudflare R2 to Supabase Storage
2026-06-06 11:32:17 +02:00
admin
62b7cdaf1e
test(01-04): implement 7 media upload unit tests covering security and validation
...
- Test 1: getPresignedUrl generates URL with correct contentType and avatars/-scoped key
- Test 2: getPresignedUrl rejects non-owner with FORBIDDEN (T-4-01)
- Test 3: getPresignedUrl rejects image/gif (Zod enum validation, T-4-02)
- Test 4: getPresignedUrl rejects 11MB file (T-4-06 fileSize limit)
- Test 5: confirmUpload writes avatarKey to DB with correct update call
- Test 6: confirmUpload rejects path traversal key (T-4-05)
- Bonus: confirmUpload rejects non-owner with FORBIDDEN (T-4-01)
- Mock getPresignedUploadUrl via vi.mock — no real R2 calls
2026-06-06 11:11:40 +02:00
admin
fb8bb5c8fe
feat(01-04): wire CDN avatar into profile, edit page, sidebar, and mobile nav
...
- pets/[petId]/page.tsx: replace /api/avatar stub with NEXT_PUBLIC_R2_PUBLIC_URL CDN URL
- pets/[petId]/edit/page.tsx: pass initialAvatarKey to PetForm so AvatarUpload shows current photo
- Sidebar.tsx: add avatarKey to Pet type; show AvatarImage from CDN for active pet and dropdown list
- MobileNav.tsx: same CDN avatar pattern for mobile pet switcher
- next.config.ts: add images.remotePatterns for R2 CDN hostname (T-4-04 safeguard)
2026-06-06 11:10:28 +02:00
admin
1bd1a89c68
feat(01-04): AvatarUpload five-state component + wire into PetForm
...
- Create AvatarUpload.tsx: idle/dragging/uploading/error/idle-with-avatar states
- XHR PUT to R2 presigned URL with progress tracking (Content-Type must match presignedUrl)
- Two-step upload: getPresignedUrl -> XHR PUT -> confirmUpload -> CDN URL display
- Client-side validation: size <= 10MB, type jpeg/png/webp
- Disabled state in create mode (no petId) with tooltip
- Drag and drop: dragOver/dragLeave/onDrop handlers; orange border in dragging state
- Wire AvatarUpload into PetForm replacing AvatarUploadZone stub
- Add initialAvatarKey prop and currentAvatarKey state to PetForm
- onUploadComplete invalidates React Query cache (pets.byId + pets.list)
2026-06-06 11:08:56 +02:00
admin
485877a541
feat(01-04): tRPC media router with getPresignedUrl and confirmUpload
...
- Create src/trpc/routers/media.ts with server-only import (T-4-03)
- getPresignedUrl: ownership assertion, contentType enum (jpeg/png/webp), fileSize <= 10MB, UUID-scoped R2 key
- confirmUpload: ownership assertion, key.startsWith('avatars/') validation (T-4-05)
- Register media: mediaRouter in _app.ts appRouter
- Fix pre-existing TS error in PetForm.tsx: setSelectedSpeciesId(val ?? '') (Rule 1 bug)
2026-06-06 11:07:09 +02:00
admin
3d0e2e6961
fix(01-03): pet card click navigates to profile page
2026-06-06 10:49:13 +02:00
admin
d7eb00a5a3
feat(01-03): auto-capitalize first letter of pet name on input
2026-06-06 10:45:56 +02:00
admin
a0fdc0b9c4
fix(01-03): resolve species/breed name directly from list in trigger — bypass Radix SelectValue bug
2026-06-06 10:42:22 +02:00
admin
9d51003ece
fix(01-03): use empty string instead of undefined for Select value — prevents controlled/uncontrolled switch
2026-06-06 10:38:47 +02:00
admin
c4588098ca
fix(01-03): defer species/breed Select render until data loads — fixes CUID showing in trigger
2026-06-06 10:35:31 +02:00
admin
8824391613
fix(01-03): fix tRPC caller context type in pet-profile tests
...
- Type CallerCtx explicitly to avoid union type inference error
- petsRouter.createCaller accepts Context | (() => Context); explicit type avoids TS error
2026-06-06 10:20:00 +02:00
admin
f996459133
test(01-03): implement pet-profile unit tests (PET-01 through PET-05)
...
- PET-01: create stores pet with ownerId, validates name<=30, upserts owner FK
- PET-02: update FORBIDDEN for non-owner, succeeds for owner
- PET-03: delete by owner succeeds ({ success: true }); FORBIDDEN for non-owner
- PET-05: adoptionStory persists to DB; validates max 500 chars
- Ownership assertion: FORBIDDEN tested for both update and delete (T-3-01)
- All 11 tests passing; no real DB calls (mocked via createMockPrisma)
2026-06-06 10:18:52 +02:00
admin
95dfec7bdf
feat(01-03): add pet profile pages (My Pets grid, profile view, edit, onboarding)
...
- My Pets: server-fetched list + PetGrid client island for active pet selection
- Pet profile view: avatar/name/species badge, "Managed by @owner" subordination
- Edit pet: PetForm in edit mode + delete confirmation dialog (UI-SPEC copy)
- Delete dialog: "Delete pet profile?", "Yes, delete", "Keep pet" per UI-SPEC
- Onboarding page: replaced stub with PetForm mode=create, redirects to /pets/[petId]
- Used buttonVariants for Link-as-button (base-nova lacks asChild)
2026-06-06 10:17:34 +02:00
admin
63d8c06b48
feat(01-03): add PetForm and PetCard components
...
- PetForm: create/edit mode, species/breed from DB (never hardcoded)
- Species selector wired to listSpecies tRPC query
- Breed selector wired to listBreeds, resets on species change
- Bio (maxLength=150) + adoption story (maxLength=500) with char counters
- Pet name maxLength=30 with Zod validation via react-hook-form
- AvatarUploadZone stub (Plan 04 wires upload; disabled in create mode)
- PetCard: avatar with active orange ring, species badge, hover shadow
2026-06-06 10:11:54 +02:00
admin
69553b103b
feat(01-03): extend pets router (update/delete/listSpecies/listBreeds) + breed seed
...
- Add assertPetOwnership helper (T-3-01: ownership check before every mutation)
- Add pets.update with Zod limits (name<=30, bio<=150, adoptionStory<=500)
- Add pets.delete with ownership assertion
- Add pets.listSpecies (protectedProcedure — T-3-04 scraping prevention)
- Add pets.listBreeds filtered by speciesId
- Extend seed with 45 breeds: 20 dog, 15 cat, 10 bird (idempotent upserts)
- Install react-hook-form + @hookform/resolvers for PetForm (Task 2)
2026-06-06 10:07:04 +02:00
admin
5f781fc20e
fix(01-02): rename src/middleware.ts to src/proxy.ts — Next.js 16 convention
2026-06-06 10:01:29 +02:00
admin
32226739b7
fix(01-02): move onboarding/pet outside (app) group — breaks redirect loop
2026-06-06 09:59:01 +02:00
admin
d6fc3e5ccf
fix(01-02): move middleware.ts to src/ — required when project uses src/ directory
2026-06-06 09:46:58 +02:00
admin
177403276b
fix(01-02): update layout comment to reference middleware.ts
2026-06-06 09:43:00 +02:00
admin
e7d300f9c2
feat(01-02): protected app shell with sidebar, mobile nav, onboarding redirect
...
- (app)/layout.tsx: server component with auth() check + pets.list + redirect to /onboarding/pet if no pets
- Sidebar.tsx: 240px desktop nav (lg:), pet switcher with active state, nav links with orange-500 active indicator
- MobileNav.tsx: fixed bottom bar (h-16, lg:hidden), pet switcher above tabs, orange-500 center post button
- onboarding/pet/page.tsx: stub with Plan 03 placeholder (replaces walking skeleton client form)
- Deviation: base-nova shadcn uses @base-ui/react/menu (no asChild support) — used onClick+useRouter
- Deviation: Clerk 7 UserButton no longer accepts afterSignOutUrl
2026-06-06 08:57:38 +02:00
admin
325eaa9a25
feat(01-02): auth layout and Clerk-rendered auth pages
...
- (auth)/layout.tsx: centered 448px container, items-center, bg-background
- sign-up/[[...sign-up]]/page.tsx: Clerk <SignUp> with orange-500 appearance, redirects to /onboarding/pet
- log-in/[[...sign-in]]/page.tsx: Clerk <SignIn> with orange-500 appearance, redirects to /pets
- forgot-password/page.tsx: two-step Clerk 7 reset flow (create identifier + resetPasswordEmailCode.sendCode)
- reset-password/page.tsx: Clerk 7 resetPasswordEmailCode.verifyCode + submitPassword + finalize
- verify-email/page.tsx: static server component + ResendEmailButton client island
- Deviation: Clerk 7 SignInFutureResource API used (no isLoaded/setActive/attemptFirstFactor)
2026-06-06 08:52:23 +02:00
admin
e0214ea0f8
feat(01-01): wire Clerk proxy.ts, Prisma 7 schema + singleton, tRPC stack, R2 client, ActivePetContext
...
- Create proxy.ts at repo root with clerkMiddleware + createRouteMatcher (public allowlist: sign-up, log-in, forgot-password, reset-password, verify-email)
- Create prisma/schema.prisma: Owner, Species, Breed, Pet models with ownership index and FK constraints
- Create prisma.config.ts with Prisma 7 defineConfig (datasource url via env)
- Create src/lib/prisma.ts: Prisma 7 singleton using @prisma/adapter-pg for local dev, withAccelerate() for Prisma Accelerate on Vercel
- Create src/lib/r2.ts (server-only): S3Client + getPresignedUploadUrl with contentType enforcement and 300s expiry
- Create src/lib/auth.ts: getOwnerId() Clerk auth wrapper
- Create src/trpc/init.ts: createTRPCContext, router, publicProcedure, protectedProcedure (throws UNAUTHORIZED)
- Create src/trpc/routers/pets.ts: pets.create (upserts Owner, creates Pet with ownerId=ctx.userId), pets.list (scoped), pets.byId (ownership-asserted)
- Create src/trpc/routers/_app.ts: appRouter + AppRouter type export
- Create src/trpc/query-client.ts, server.ts (RSC caller), client.tsx (TRPCReactProvider + useTRPC)
- Create src/app/api/trpc/[trpc]/route.ts: tRPC fetch handler (GET + POST)
- Create src/context/ActivePetContext.tsx + src/hooks/useActivePet.ts: localStorage-backed active pet context
- Create prisma/seed.ts: seeds Dog/Cat/Bird species idempotently via upsert
- Update src/app/layout.tsx: wrap children in ClerkProvider (orange primary), TRPCReactProvider, ActivePetProvider, Toaster
- Install @prisma/adapter-pg + pg for Prisma 7 local dev (Rule 2: required for Prisma 7 engine compatibility)
- [Rule 1 - Bug] Prisma 7 no longer supports url/directUrl in schema.prisma; moved to prisma.config.ts with defineConfig
2026-06-05 19:18:16 +02:00
admin
dbc074e9ee
chore(01-01): scaffold Next.js 16 + install full stack + Vitest test scaffold
...
- Create-next-app Next.js 16.2.7 with TypeScript 6.0.3, React 19, Tailwind v4, Turbopack
- Install all pinned runtime deps: Clerk 7.4.3, tRPC 11.17.0, Prisma 7.8.0, Zod 4.4.3, AWS SDK 3.1062.0, Sonner 2.0.7, Upstash Redis 1.38.0
- Configure Tailwind v4 (postcss.config.mjs uses @tailwindcss/postcss; globals.css uses @import "tailwindcss")
- Initialize shadcn/ui with default preset; add all Phase 1 components (button, input, form, label, textarea, select, avatar, card, dialog, dropdown-menu, sonner, progress, separator, badge, skeleton)
- Set orange-500 design system: --primary: oklch(0.7024 0.1967 41.45); --background: oklch(0.9882 0 0)
- Load Inter via next/font/google in root layout
- Create vitest.config.ts with jsdom environment and @ path alias
- Create Wave 0 test scaffolds (22 it.todo tests): auth, pet-profile, media-upload + prisma-mock helper
- Create .env.example listing all required service env vars
2026-06-05 19:10:46 +02:00