found.md (iOS-Feedback): Push kam an, aber die Sprechblase zeigte nichts.
MessagesHeaderButton zeigt jetzt conversations.unreadCount im selben Badge
wie die Glocke (gemeinsame UnreadBadge-Komponente), aktualisiert alle 30 s;
Lesen eines Chats invalidiert den Zaehler bereits.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
found.md: Gewichtsdaten wurden nur als Liste angezeigt. Neues WeightChart
(react-native-svg, Port des Web-Diagramms) ab 2 Eintraegen ueber der Liste:
x-Achse zeitproportional (unregelmaessige Wiegetermine), runde y-Ticks
(1/2/5 x 10^n), Datumslabels nach Pixelabstand, Antippen waehlt die
naechste Messung (Wert + Datum im Kopf), Veraenderung seit erster Messung.
Linienfarbe als neues Token chartLine (#ea580c), mit dem Dataviz-Validator
gegen helle und dunkle Flaechen geprueft. Geometrie rein in
src/lib/weight-chart.ts mit Tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
found.md (iOS-Feedback): Hunde-/Katzen-Rassenlisten sind lang. OptionPicker
zeigt ab 10 Eintraegen oben ein Suchfeld, das ohne Gross-/Kleinschreibung
und Akzente filtert (filterOptionsByName). Bei offener Tastatur rueckt die
Liste nach oben, damit sie nicht verdeckt wird. Wirkt in Tier anlegen,
Profil bearbeiten und Explore-Filter.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Profil bearbeiten (Geburtstag/Adoptionstag), Gesundheit, neuer Beitrag,
neuer Meilenstein, Tier anlegen/Onboarding und Einladen hatten keinen
Tastatur-Ausgleich: das fokussierte Feld lag unter der Tastatur.
automaticallyAdjustKeyboardInsets setzt den unteren Inset und scrollt
das aktive Feld in den sichtbaren Bereich (RN Fabric ScrollView),
keyboardShouldPersistTaps="handled" laesst Feldwechsel ohne Zuklappen zu.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Der Test in src/__tests__ importierte aus mobile/, das .dockerignore und
die Web-tsconfig ausschliessen -> next build brach beim Typecheck ab.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Datum (found.md, iOS-Feedback): neue DateField-Komponente mit Ziffern-
tastatur und automatisch eingefügten Punkten (TT.MM.JJJJ) für Geburtstag,
Adoptionstag, Gewicht, Tierarzt und Impfungen. Parser akzeptiert weiter
"-" und "/" sowie einstellige Tage/Monate. Wheel-Picker bleibt in
PENDING-NATIVE-BUILD.md (natives Modul).
Impf-Erinnerung (found.md, iOS-Feedback): täglicher Cron
/api/cron/vaccine-reminders erinnert einmalig 30 Tage vor
Vaccine.nextDueDate per Mitteilung (VACCINE_DUE) + Push mit Impfname und
Datum. Schema: NotificationType.VACCINE_DUE, Notification.vaccineId
(Cascade), Vaccine.reminderSentAt (Claim gegen Doppelversand, wird bei
Fehler wieder freigegeben). Web + App zeigen die Mitteilung an, Tippen
öffnet die Gesundheitsseite. App: Schnellauswahl +1/+3 Jahre für den
Folgetermin plus Hinweis auf die Erinnerung.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fehlen nach dem Apple-Login noch Pflichtangaben (z. B. Vor-/Nachname,
die Apple nur bei der allerersten Autorisierung schickt), zeigt der
Button jetzt eine Fehlermeldung und meldet die Felder an Sentry, statt
kommentarlos nichts zu tun.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Statt nur Spinner: „Upload wird vorbereitet …“, dann Balken + Prozent aus
expo-file-system File.upload onProgress (nativ seit 57.0.7 im 1.0.6-Build,
daher OTA). Vorschau ist während des Uploads nicht mehr antippbar.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merged feature/mobile-video-stories (Gitea #37) auf den seit 02.10.
umgebauten Story-Viewer: Galerie-Picker nimmt Videos bis 30 s, Upload
direkt zu Mux, Viewer spielt READY-Videos ab (Fortschritt aus der echten
Wiedergabe, Weiter bei playToEnd) und zeigt PROCESSING/ERROR. Offene
Kommentar-/Zuschauer-Sheets pausieren das Video wie den Foto-Timer.
Nur JS — expo-video ist seit dem 1.0.6-Build nativ enthalten, also OTA.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Pet.isFoundingMember: in pets.create per bewachter SQL-Anweisung für die
ersten 250 Tiere gesetzt, nie entzogen; Abzeichen (Spross) in Feed und
Profil, Web + App, unabhängig vom Empfehlungsprogramm
- Kurator "PawFeed Team": folgt jedem neuen Tier und schickt eine
Begrüßungs-Direktnachricht (DE/EN nach Sprache beim Anlegen), im
Hintergrund — blockiert das Onboarding nicht
- CuratorSettings + Admin-Seite /p/[secret]/curator: Schalter, Texte mit
Pflicht-Platzhalter {name} und Vorschau, Zähler der Gründungsmitglieder
- prisma/backfill_founding_members.sql, prisma/enable_rls_curator.sql
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- stories.listActive: der Owner bekommt seine Storys der letzten 7 Tage mit
isExpired, alle anderen weiterhin nur die aktiven 24 Stunden
- stories.hasActiveStory: optional includeOwnRecent → grauer Ring
(expiredOnly) nur in der Story-Leiste, nicht an Profilbildern im Feed
- Viewer: Hinweis "Abgelaufen – nur für dich sichtbar", Kommentare bei
abgelaufenen Storys nur lesbar, Pfote nur als Zähler; Löschen und
Zuschauerliste bleiben
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- /api/cron/cleanup-stories (CRON_SECRET): löscht Storys älter als 7 Tage
samt Aufrufen, Pfoten, Kommentaren, Supabase-Bild und Mux-Asset; max. 200
pro Lauf, Fehler einzelner Storys brechen den Lauf nicht ab
- gemeinsame Funktion deleteStoryWithMedia (src/lib/story-cleanup.ts), auch
von stories.delete genutzt
- Datenschutzerklärung Abschnitt 6: Storys spätestens nach 7 Tagen gelöscht
- Datenexport: Story-Block heißt "stories" statt "activeStories"
- CLAUDE.md: Cron-Jobs laufen über die NAS-Crontab + docker/run-cron.sh
(der alte Hinweis "nichts ruft trim-feeds auf" war veraltet)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- stories.delete: nur Owner; Aufrufe, Story (Pfoten/Kommentare per Cascade),
danach Bild in Supabase Storage bzw. Mux-Asset best-effort
- Web: Papierkorb neben der Zuschauerzahl + Rückfrage, Timer pausiert,
gelöscht wird die beim Antippen gemerkte Story
- App: gleicher Papierkorb mit nativer Rückfrage, Timer pausiert
- found.md: Nutzer-Feedback "Story löschbar" erledigt, 7-Tage-Wunsch mit
Befund als offener Punkt
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- App-Viewer: Pfote, Kommentar-Sheet (schreiben, eigene löschen) und für die
eigene Story die Zuschauerliste; Timer pausiert, solange ein Sheet offen ist
- stories.listActive: optionales viewerPetId → reactedByViewer, damit die
Pfote gefüllt startet (abwärtskompatibel für alte App-Builds)
- Web-StoryPawButton nutzt reactedByViewer ebenfalls und wird pro Story
neu initialisiert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Die Leiste zeigte nur Storys gefolgter Tiere — das eigene aktive Tier war
nie enthalten, man konnte die eigene Story nach dem Posten nicht ansehen.
Neuer Ring direkt nach der "+"-Kachel, solange stories.hasActiveStory für
das aktive Tier true ist.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- App: post-new.tsx sortierte HEIC/HEIF still aus, obwohl resizeForUpload()
ohnehin in JPEG umwandelt — jetzt erlaubt, plus Hinweis bei übersprungenen
Fotos
- Web: neues src/lib/heic-to-jpeg.ts wandelt HEIC vor dem Upload im Browser
in JPEG um (Safari kann HEIC nativ dekodieren); eingebaut in Beiträge,
Storys, Meilensteine und Tierbild, accept um HEIC ergänzt. Browser ohne
HEIC-Dekodierung bekommen eine klare Meldung statt "falsches Format".
- Server-Whitelist (JPEG/PNG/WebP) bleibt unverändert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Proxy-Sperre für /sign-up, INVITE_REQUIRED, set-invite/consume-invite,
invites-Router, Admin-Invite-Seite und -Prozeduren, Startcodes in
pets.create entfernt — Registrierung ist offen
- /join?code=… leitet auf /r/<code> um, /invite auf /einladen; alte geteilte
Links laufen nicht ins Leere
- InviteCode-Tabelle bleibt nur als Historie (Kontolöschung räumt sie weiter auf)
- Datenschutzerklärung 4b (Empfehlungsprogramm, Herkunftsangabe, pf_ref-Cookie)
und Nutzungsbedingungen 3b (Programmbedingungen, Bestandsschutz, Missbrauch)
- CLAUDE.md: Abschnitt "Referral program" statt "Invite system"
Build, Typecheck und alle Tests grün.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Bildschirm "Freunde einladen" (/invite, aus den Einstellungen erreichbar):
eigener Code mit nativem Teilen-Dialog, Rangleiter mit Fortschritt,
eingeladene Tiere, nachträgliche Code-Eingabe im Einlösefenster
- Onboarding-Schritt "Wie hast du von PawFeed erfahren?" mit Code-Eingabe,
einmal pro Owner (referrals.onboardingState)
- Rang-Abzeichen in PetIdentityBadges (Feed und Profil), Profilring in
Rangfarbe, "Eingeladen von …" im Tierprofil
- Benachrichtigungen REFERRAL_JOINED / REFERRAL_RANK_UP
- Keine neuen nativen Module (React Native Share) — per OTA auslieferbar
Typecheck grün; nicht auf einem Gerät getestet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Build 2 was rejected in TestFlight beta review (missing demo credentials)
and cannot be resubmitted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Also includes the user's own pending found.md edits (new entries, iOS
section header, trailing-whitespace cleanup).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The middle toe beans reach y = -9 in the 0–100 viewBox. Web draws the SVG
with overflow: visible; react-native-svg clips at the viewBox, cutting the
paw off. Extend the viewBox upward and grow the box to match; the "add
story" tile uses the same box so it lines up with the rings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Android is edge-to-edge, and stack screens (no tab bar below) had no
bottom inset on their scroll containers — the first post on a pet profile
sat in the gesture area and couldn't be tapped (found.md). New
useBottomSafePadding hook adds insets.bottom to the contentContainerStyle
of pet profile, followers, following, health, milestones, edit pet,
settings, search, messages list and delete account.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Block (Guideline 1.2, UGC): useBlockPet hook (native confirm Alert,
blocks.create). PostCard "..." on other pets' posts now offers
Report / Block; pet profile gets a header "..." with Report pet / Block
(hidden for any of the owner's own pets), navigating back after a block.
Feed, comments and DMs already enforce blocks server-side; explore and
search don't yet (server follow-up).
- Delete account (Guideline 5.1.1(v)): new delete-account screen reached
from a quiet link below Log out in Settings. Mirrors web's
DeleteAccountDialog (warning list + type a pet name) and calls the same
POST /api/account/delete with the Clerk bearer token, then clears the
query cache and signs out.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ArachnophobiaOptIn, UploadConsent, OnboardingGuidelines and AvatarStep
still used the static light-palette tokens in module-level
StyleSheet.create, so titles and checkbox labels rendered dark-on-dark.
Switched them to useTheme() + makeStyles(colors) like the other screens.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
iOS labels the back button with the previous screen's title, which was
the untitled (tabs) route group. Use headerBackButtonDisplayMode
"minimal" (chevron only) for all stack screens.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- eas.json: new `preview-simulator` profile (extends preview, ios.simulator)
for EAS cloud simulator builds without an Apple Developer account
- app.json: iOS deployment target 17.0 via expo-build-properties
(@clerk/expo's ClerkKit SPM package requires iOS 17 — pod install failed
on the default 16.4), iPhone-only (supportsTablet: false),
usesAppleSignIn, ITSAppUsesNonExemptEncryption: false
- AppleSignInButton: native Apple sheet via Clerk's useSignInWithApple
(App Store Guideline 4.8, since Google login is offered); same
legal-consent handling as GoogleSignInButton; iOS-only
- Not yet testable end-to-end: needs Apple Developer account, the
Sign in with Apple capability on the App ID and Apple enabled in Clerk
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Multiple users reported "Unmatched Route pawfeed://sso-callback" after
Google sign-in. Root cause: Android's custom tab delivers the OAuth
redirect intent to the app twice — once caught correctly by
expo-web-browser's openAuthSessionAsync() session, but also as a plain
deep link seen by expo-router's own Linking listener. With no matching
route, expo-router briefly showed its built-in Unmatched Route error
screen instead of letting the real sign-in flow (GoogleSignInButton ->
setActive -> router.replace) take over.
Adds app/sso-callback.tsx as a themed loading spinner so expo-router has
a valid screen for that window; no auth logic needed there since
useProtectedRoute() in app/_layout.tsx already redirects based on
Clerk's session state. JS-only change, OTA-deployable, no native rebuild
needed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Extends the gallery picker to accept videos, uploads them via Mux direct
upload (same flow web already uses), and adds PROCESSING/READY/ERROR
handling to the story viewer with real playback-driven progress instead
of a fixed timer.
Fixes a video-viewer bug found during device testing: play() was called
before the player reached readyToPlay, leaving duration at 0 and firing
playToEnd immediately — the viewer closed within ~1s instead of playing.
Now waits for statusChange to readyToPlay before calling play().
Also fixes the story-creation "Story teilen" button sitting flush against
gesture navigation on devices without a bottom safe-area inset applied.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Google SSO broke live with 'redirect url ... does not match an
authorized redirect URI ... pawfeed://sso-callback', despite the SSO
code (99575dd) being unchanged. Root cause was outside the repo
entirely: Clerk's per-instance authorized-redirect-URL allowlist is
server-side config (CLERK_SECRET_KEY-authenticated Backend API), not
tracked in git, and only had a stale org.pawfeed.app2 entry - the
current pawfeed:// scheme's callback was never registered.
Fixed live via POST https://api.clerk.com/v1/redirect_urls (no app
deploy needed), verified through the Google consent screen on an
emulator and end-to-end by the user on a real device.
Documented in both mobile/found.md (full incident) and README.md's
Known Issues table (the reusable gotcha: check Clerk's redirect-URL
allowlist before debugging app code when SSO breaks unexpectedly).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds the merge commit (30c57b6), emulator end-to-end verification, and
OTA update group reference to the Settings-header-icon found.md entry.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Closes Gitea #36. Plan: ~/.claude/plans/mobile-settings-tab-extraction.md.
Extracts the 5 footer sections pets.tsx had grown (Darstellung, Sprache,
Tier-Vorschlaege opt-out, Rechtliches+Version/OTA, Abmelden) into their
own files under src/components/settings/, and a new app/(app)/settings.tsx
screen reached via a gear icon in every tab's header, next to Messages -
not a 5th bottom tab. Chosen over a tab (see the two mockups in the plan
folder) because Settings is a low-frequency destination for a typical
user; the header icon still reaches it in one tap from anywhere without
spending permanent bottom-nav space on it.
settings.tsx lives outside (tabs)/, registered as a plain Stack.Screen
(app/(app)/_layout.tsx) matching search.tsx's pattern - normal back-arrow
push, no presentation:modal, no tab bar visible while it's open.
SettingsHeaderButton is wired into (tabs)/_layout.tsx's global headerRight
default plus the 3 tabs (index/explore/pets) that override it.
pets.tsx is pet-list-only again. FooterErrorBoundary now wraps the whole
Settings screen instead of only the logout button, closing a gap a prior
code review found (AppearanceSection/LegalSection were rendering
unprotected).
Code-reviewed before commit (WORKFLOW.md step 6) - fixed two of three
findings: extracted the section/sectionTitle style duplicated across
AppearanceSection/LanguageSection/PetSuggestionsSection into a shared
sectionTitleStyle.ts, and reduced every header icon's hitSlop 12->8 (rows
can now hold 3 icons with gap:16; 12+12=24 overlapped the gap, 8+8=16
exactly meets it - see the comment on headerButtons in (tabs)/_layout.tsx).
Left the pets.* i18n namespace as-is on the moved strings (documented in
settings.tsx's doc comment) - pets.language* is shared with the pre-login
LanguageSwitcher, so a partial rename would misdescribe those call sites.
Typecheck + lint clean across all touched/new files, i18n key parity
verified (de/en).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Meine Tiere tab footer now shows 'Version X.X.X - OTA <short-id>' (or
'Build (kein OTA)' when running the embedded/non-OTA bundle) via
expo-updates' Updates.isEmbeddedLaunch/updateId. Directly answers the
support question 'did the update actually arrive on this device?' by
comparing the shown short id against the 'Android update ID' an
'eas update' publish prints - the exact ambiguity that cost a multi-day
debugging session earlier (see .claude/handoffs/2026-09-12-ota-update-
mystery-expo-dev-client-root-cause.md).
Code-reviewed before commit (per WORKFLOW.md step 6): fixed two of four
findings - wrapped the expo-updates reads in a try/catch (LegalSection
sits outside FooterErrorBoundary, so an unguarded throw here would have
taken down the whole footer) and moved the hardcoded 'OTA ' prefix into
i18n (pets.otaId). The web-shim-always-unknown finding is a documented
dead branch (this app ships no web build - eas update only ever runs
--platform android). No test coverage added - no RN test infrastructure
exists in mobile/ yet (pre-existing gap, tracked in WORKFLOW.md).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Same root cause as post-new.tsx/story-new.tsx: search.tsx imported the
static light-only colors/typography instead of useTheme(). Bigger
refactor than the other two since TabButton/PetRow/Loading/EmptyState
are separate top-level components, not closures inside the screen -
they now receive styles/colors as props from the theme-aware
makeStyles(colors) built in SearchScreen, rather than importing the
static tokens themselves.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>