diff --git a/.claude/handoffs/2026-08-11-gdpr-token-grid-targeting-landingpage.md b/.claude/handoffs/2026-08-11-gdpr-token-grid-targeting-landingpage.md new file mode 100644 index 0000000..5c1ddde --- /dev/null +++ b/.claude/handoffs/2026-08-11-gdpr-token-grid-targeting-landingpage.md @@ -0,0 +1,89 @@ +# Session Handoff: GDPR-Export-Token, Posts-Grid, Ad-Targeting, OG-Bild + Landingpage + +**Datum:** 2026-08-11 +**Projekt:** T:\CC-Projekte\OnlyPets (PawFeed) +**Session-Dauer:** ganzer Tag, sehr umfangreich (vier Gitea-Issues bearbeitet, mehrere Live-Bugs beim Deploy gefunden+gefixt) + +## Aktueller Stand + +**Task:** Abarbeitung der bei der letzten Session offen gebliebenen Gitea-Issues #4, #6 (Follow-up), #7, #9 +**Phase:** Abgeschlossen — #6-Follow-up, #7, #9 fertig, deployed, live verifiziert, in Gitea kommentiert (#6 und #7 zusätzlich geschlossen). #4 ist inhaltlich fertig (OG-Bild + volle Landingpage + i18n), aber noch offen in Gitea — sollte in der nächsten Session geschlossen werden, wenn der Nutzer zufrieden ist. +**Fortschritt:** Alle vier bearbeiteten Themen sind live auf pawfeed.org. Nur noch #5 (CSP-Research) und #8 (Dark-Mode) offen — wurden diese Session nicht angefasst. + +## Was wir gemacht haben + +Reihenfolge der Bearbeitung, jeweils committed, gepusht (Gitea) und via SSH deployed (`git pull && cd docker && ./start.sh`): + +1. **Gitea #6 Follow-up — GDPR-Export-Link über pawfeed.org statt Supabase:** Nutzer wollte, dass der DSGVO-Auskunfts-Link von `pawfeed.org` kommt statt direkt auf eine Supabase-Signed-URL zu zeigen (Variante 2: eigenes DB-Token-Modell). Neues `DataExportToken`-Prisma-Modell, `data-export-storage.ts` in `uploadExport()`/`downloadExport()` aufgeteilt (kein Signed-URL-Mechanismus mehr), neue Route `/api/gdpr-export/[token]` streamt die Datei serverseitig durch. **Live-Bug gefunden:** die neue Route wurde von der Clerk-Middleware auf `/sign-in` umgeleitet — behoben durch Ergänzung in `src/proxy.ts`'s public-Route-Matcher. Testmails mehrfach verschickt und verifiziert (inkl. SMTP-Diagnose mit vollem nodemailer-Response-Log, da eine erste Mail "nicht ankam" — SMTP-seitig war alles sauber, lag am Spam-Ordner/Timing). + +2. **Gitea #7 — Admin-Dashboard Posts-Grid:** `/p/[secret]/posts` von Listen- auf 3-Spalten-Grid-Ansicht umgestellt, Caption unter jeder Karte, Icon-Platzhalter für Posts ohne Medium, Video-Posts zeigen Mux-Thumbnail + Play-Icon (`admin.listPosts` selektiert jetzt zusätzlich `videoPost.muxPlaybackId`). **Live-Bug gefunden:** Hochformat-Fotos wurden nicht quadratisch zugeschnitten (Karte wuchs mit dem Bildseitenverhältnis), weil dem `aspect-square`-Container `overflow-hidden` fehlte — Querformat-Fotos verdeckten den Bug beim ersten Test. Per JS-Messung verifiziert (alle Karten exakt 740×740px nach dem Fix). + +3. **Gitea #9 — Ad-Targeting nach Tierart/Rasse:** `Advertisement.species` existierte im Schema, war aber komplett tote Infrastruktur (Admin-UI hatte kein Feld dafür, `getActiveAds` filterte nie danach — jede Anzeige ging an jedes Tier). Neues `breeds`-Feld ergänzt, Zielgruppen-UI im Ad-Dialog (Species-Checkboxen, je aufklappbar in eine Breed-Liste), `admin.getActiveAds` filtert jetzt nach dem tatsächlich betrachtenden Tier (`petId`-Input, vorher parameterlos). Unit-Tests für die Filterlogik ergänzt (`admin.test.ts`). Live im Admin-Panel getestet (Cat+Siamese gesetzt, Badge erschien, danach zurückgesetzt). + +4. **Gitea #4 — OG-Bild + öffentliche Landingpage (mehrteilig, größter Block):** + - Erst code-generiertes OG-Bild via `next/og`'s `ImageResponse` (1200×630, Paw-Wasserzeichen aus CSS-Kreisen). + - Nutzer hat eigenes, verfeinertes SVG-Design geliefert (`C:\Users\Daniel\Desktop\og-preview.svg`, später `og-redesign.png` mit 2 Korrekturen) — zu komplex für 1:1-Portierung nach Satori/JSX (eingebettetes Rasterbild + vektorisierte Textpfade), daher als statisches `src/app/opengraph-image.png` + `opengraph-image.alt.txt` übernommen (Next.js-Dateikonvention). + - **Live-Bug gefunden (Facebook Sharing Debugger: "og:image sollte explizit angegeben werden"):** `pawfeed.org/` hatte gar keine öffentliche Seite — jeder Besucher ohne Session (auch Crawler) wurde von der Clerk-Middleware sofort auf `/sign-in` umgeleitet, bevor Next.js die Metadaten rendern konnte. Nutzer hat sich für eine **echte öffentliche Landingpage** entschieden (nicht nur Crawler-Sonderbehandlung). + - `src/app/page.tsx` neu gebaut: `auth()`-Check statt hartem `.protect()` in der Middleware, eingeloggte Nutzer → weiterhin sofort `/feed`, ausgeloggte → Hero-Landingpage (warmer Orange-Gradient, Paw-Wasserzeichen, CTAs). + - Nutzer-Feedback "es fehlen wichtige Elemente" → Header mit Sprachumschalter, App-Preview (echter Feed-Screenshot in Browser-Chrome-Mockup), 4er-Feature-Grid, Footer mit Impressum/Datenschutz/AGB ergänzt. + - **Flaggen-Emoji-Bug:** 🇬🇧/🇩🇪 rendern auf Windows standardmäßig nur als "GB"/"DE"-Buchstaben (kein Color-Flag-Font) — durch Inline-SVG-Flaggen ersetzt (`src/components/i18n/FlagIcons.tsx`), betrifft den Sprachumschalter jetzt überall in der App, nicht nur die Landingpage. + - App-Preview-Screenshot zweimal überarbeitet: erst per `sharp` Sidebar+Feed zusammengesetzt (Lücke im rohen Screenshot durch zentriertes Layout), dann komplett durch einen vom Nutzer selbst gelieferten saubereren Screenshot ersetzt (`public/app-preview-feed.png`, natürliche Bildgröße 872×926 statt beschnittenem 16:10-Frame). + - **i18n:** Landingpage war komplett hartcodiert Englisch — neuer `"Landing"`-Namespace in `messages/en.json`/`messages/de.json`, `src/app/page.tsx` nutzt jetzt `getTranslations("Landing")`. Live verifiziert (NEXT_LOCALE-Cookie de/en liefert korrekt übersetzten Text). + +## Entscheidungen + +- **DataExportToken statt Supabase-Signed-URL** — volle Kontrolle über Ablaufzeit/Widerruf, Storage-Backend bleibt internes Implementierungsdetail, nutzt dasselbe Muster wie `/health-card/[token]`. User-Entscheidung (Variante 2 von zwei vorgestellten Optionen). +- **Breed-Targeting narrowed WITHIN Species, nicht als unabhängiger Filter** — `ad.breeds` schränkt nur innerhalb bereits gecheckter `ad.species` ein; leere Breeds = jede Rasse der gewählten Art. Vermeidet inkonsistente UI-Zustände (Breed ohne zugehörige Species checkbox). +- **OG-Bild als statisches PNG statt code-generiert** — sobald der Nutzer ein eigenes, design-verfeinertes SVG lieferte, war die Portierung nach Satori/JSX unpraktikabel (eingebettetes Rasterbild + Vektor-Textpfade). Next.js unterstützt beide Ansätze gleichwertig über dieselbe Dateikonvention. +- **Echte Landingpage statt Crawler-User-Agent-Sonderbehandlung** — User-Entscheidung nach Vorstellung beider Optionen; ändert auch das Produkterlebnis für echte ausgeloggte Besucher, nicht nur für Bots. +- **Flaggen als Inline-SVG statt Emoji** — Windows rendert Flaggen-Emoji standardmäßig als Buchstabencode, kein Color-Flag-Font-Fallback. Betrifft potenziell einen großen Teil der Nutzerbasis, daher global im `LanguageSwitcher` gefixt, nicht nur auf der Landingpage. + +## Code-Änderungen (Commits `5e2a11e` → `59981f9` auf `main`, alles gepusht + deployed) + +``` +5e2a11e feat(admin): route GDPR export downloads through pawfeed.org instead of Supabase (Gitea #6 follow-up) +8351382 fix(proxy): allow unauthenticated access to /api/gdpr-export/[token] +ed6842b feat(admin): redesign Posts panel as a 3-column media grid (Gitea #7) +f638239 fix(admin): clip portrait thumbnails to the square grid cell (Gitea #7) +efdf407 feat(admin): add species/breed ad targeting (Gitea #9) +e47eed3 feat(seo): add a generated 1200x630 OG image (Gitea #4) +747d9e1 fix(proxy): allow unauthenticated access to /opengraph-image +2e5bacb feat(seo): swap the generated OG image for the designer-refined one (Gitea #4) +68f0c8b fix(seo): swap in the corrected OG image (2 fixes from designer review) +07a1cb7 feat(landing): add a public landing page at / (Gitea #4 follow-up) +1eb27df feat(landing): add header, app preview, feature overview, and footer +0da56a2 fix(landing): replace flag emoji with SVG chips, tighten app-preview crop +f899ed5 feat(landing): translate landing page copy (Landing i18n namespace) +59981f9 fix(landing): swap in the user's new app-preview screenshot +``` + +**Neue Dateien:** `src/app/api/gdpr-export/[token]/route.ts`, `src/app/opengraph-image.png` (+ `.alt.txt`), `src/components/i18n/FlagIcons.tsx`, `public/app-preview-feed.png` + +**Schema-Änderungen (per `db push` auf die geteilte Prod-DB angewendet):** `DataExportToken`-Modell (neu), `Owner.dataExportTokens`-Relation, `Advertisement.breeds` + +**Wichtige Dateien für den Wiedereinstieg:** +- `src/app/page.tsx` — Landingpage, `getTranslations("Landing")` +- `src/proxy.ts` — public-Route-Matcher (jetzt inkl. `/`, `/api/gdpr-export/(.*)`, `/opengraph-image`) +- `src/trpc/routers/admin.ts` — `getActiveAds` (jetzt `petId`-Input + Filterlogik), `exportUserData`, `listPosts` +- `messages/en.json` / `messages/de.json` — `"Landing"`-Namespace + +## Blocker / offene Risiken + +- Keine aktuell blockierenden Probleme. Alles live und verifiziert. +- **Wiederkehrendes Muster diese Session:** Jede neue öffentliche Route (`/api/gdpr-export/[token]`, `/opengraph-image`) und sogar die Root-Route `/` wurden zunächst von der Clerk-Middleware abgefangen, weil sie nicht im `isPublicRoute`-Matcher standen. Bei jeder zukünftigen neuen Route, die ohne Login erreichbar sein soll, **zuerst `src/proxy.ts` prüfen**, nicht erst beim Live-Test entdecken. +- Ein vorbestehender Flaky-Test (`legal.publish` in `src/__tests__/legal.test.ts`) schlägt gelegentlich fehl, wenn die volle Suite läuft (Env-Var-Leck zwischen Testdateien), läuft aber isoliert immer grün — keine Regression durch diese Session, aber noch nicht behoben. + +## Context zum Merken + +- **GateGuard-Fact-Forcing-Hook** verlangt vor praktisch jeder Edit/Write/ersten-Bash-Aktion pro Datei eine kurze Fakten-Aussage — kostet viel Overhead diese Session, lässt sich nur mit `ECC_GATEGUARD=off` umgehen. +- **Diese Session war extrem kostenintensiv** (>$115 laut Cost-Warnungen) — viele Deploy-Zyklen (11 SSH-Deploys), mehrere Browser-Automatisierungsrunden für visuelle Verifikation, mehrfache Bildbearbeitung (sharp-Kompositing für Screenshots). Für ähnliche visuelle/Design-Arbeit künftig überlegen, ob weniger granulare Zwischenverifikation reicht. +- **`sharp` ist transitiv in node_modules vorhanden** (über Next.js' optionale Image-Optimierung), auch ohne direkten `package.json`-Eintrag — nützlich für zukünftige Bildbearbeitung ohne neue Dependency. +- **Deploy-Workflow weiterhin Routine:** lokal committen+pushen (Gitea) → `ssh daniel@192.168.1.222 "cd /Dockers/PawFeed && git pull && cd docker && ./start.sh"` → Health-Check. +- Landingpage-Copy ist aktuell recht knapp (Hero + 4 Features + Footer) — falls der Nutzer mehr Marketing-Tiefe will (Testimonials, Screenshots weiterer Features wie Health-Tracking/Stories), wäre das ein eigener, klar abgegrenzter Folge-Task. + +## Nächste Schritte + +1. [ ] **Gitea #4 schließen**, sobald der Nutzer mit der finalen Landingpage zufrieden ist (aktuell noch offen, obwohl inhaltlich fertig) +2. [ ] **Gitea #5 — CSP-Research** (Clerk-Workaround für den ClerkProvider-Crash recherchieren, dann Report-Only-Ansatz) +3. [ ] **Gitea #8 — Dark-Mode** (optional, einstellbar, persistent pro Nutzer) +4. [ ] Optional: den vorbestehenden `legal.publish`-Flaky-Test isolieren und fixen (Env-Var-Leck zwischen Testdateien)