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>
38 KiB
TODO:
- Fehler: Explore ohne Filterfunktion
- Benachrichtigung - Button klein Alles Gelesen markieren.
- Head-Bereich verdeckt Handy-Infos nicht lesbarer Bereich Uhrzeit, WIFI Battery - Bitte GoogleRichtlinen beachten
- Dark-Mode ? wo ?
TODO Neu:
- Explore Filter ist jetzt vorhanden, der Rassen-Dropdown, schiebt die Vorhanden elemente nach unten statt darüber zu lappen.
- Explore Seite ist nur Halb im Darkmode
- Benachrichtiungsseite nicht wirklich im Darkmode
TODO Neu2:
- Sprache ist Standard Englisch auch schon im Login-Screen, hier wäre bei der ersten Einwahl in die App, eine Prüfung der Systemsprache wichtig damit direkt Englisch oder Deutsch ausgewählt wird. → Ursache: expo-localization (Geräte-Spracherkennung) ist als natives Modul noch nicht in den Dev-Client eingebunden, daher greift aktuell immer der Englisch-Fallback. Löst sich mit dem geplanten nativen Rebuild von selbst.
- Im Feed habe ich als Nutzer keine Möglich einen Beitrag zu Reporten! Das wäre wichtig. Report-Funktion ist in der Webansicht vollfunktionsfähig und wird im Admin-Bereich getrackt. → Melden-Button (⋮) auf jedem Beitrag, 5 Report-Gründe, live getestet (Übermittlung + Speicherung bestätigt).
- Bilder lassen sich nicht Vergrößern mit 2 Fingern. → Pinch-to-Zoom im Bild-Viewer ergänzt (ohne neue native Abhängigkeit). Über adb nicht als echte Zwei-Finger-Geste simulierbar, daher nur Code+Typecheck geprüft, nicht live.
- Innerhal des Tierprofils + Gesundheitskarte ist der DarkMode nicht umgesetzt → Tierprofil, Profil bearbeiten, Follower/Folgt sowie die komplette Gesundheitsseite (inkl. Einwilligungs-Screen) sind jetzt dark-mode-fähig, live bestätigt.
- Gesundheitsdaten werden nicht gespeichert, sowie kein Zurückbutton sobald man fertig ist mit der bearbeitung der daten. → Zurück-Pfeil war nur unsichtbar (schwarz auf schwarzem Header im Dark Mode), jetzt auf allen Unterseiten sichtbar. Speicherproblem lag am Datumsformat (nächster Punkt) plus fehlender Fehlermeldung bei ungültiger Eingabe — beides behoben, End-to-End getestet.
- Datum wird Korrekt dargestellt, die eingabe ist aber, aktuelle ansicht JJJJ-MM-DD // korrekt wäre DD-MM-JJJJ → Eingabefelder akzeptieren jetzt TT-MM-JJJJ, werden intern korrekt umgewandelt; ungültige Eingaben zeigen eine Fehlermeldung.
- Gewichtsdaten werden nicht als Diagramm angezeigt (feature) → Echtes Feature, kein Bug — noch offen, Rückmeldung ob/wann gewünscht.
- Technische Frage, was wird beim Feed abgerufen? Zum vergleich in der Web umgebung wird mein feed(followerbezogen) und der trend (könnte dir auch gefallen) seperat eingeblendet, wird in der App eine kombination daraus gemacht ? falls ja super. → Nein: Mobile Feed zeigt ausschließlich den chronologischen Follower-Feed (feed.getFeed, mode "chrono"). Web's separater "Trending"-Tab (explore.getTrending, "könnte dir auch gefallen") ist in der App noch gar nicht gebaut — bekannte Lücke, bereits in der README dokumentiert.
- unter meine Tiere, bitte Impressum datenschutz und agbs als link einbinden. sowie eine Versionsnummer der App → Links + "Version 1.0.0" unterhalb der Sprachauswahl ergänzt, live getestet (Impressum öffnet im Browser).
- Symbole für Deutsch Englisch und Automatisch im Meine tiere menü ändern. auf flaggen z.b. → 🇩🇪 / 🇬🇧 / 🌐 statt Icon, live bestätigt.
- Benachrichtigungen trennen zwischen den einzelnen Benachrichtigungen für mehr optik → Sichtbare Trennlinie zwischen den Einträgen ergänzt, live bestätigt.
- Benachrichtigungen sind nicht anklickbar, sollten aber zu dem jeweiligen Post / Pet gehen. → Tap markiert als gelesen und navigiert zum Ziel (Post-Kommentare bzw. Tierprofil je nach Typ), live bestätigt.
- Drei Punkte, menü im Feed, falls es der eigene Beitrag ist, lösch funktion hinzufügen, statt Report. → Bei eigenem Beitrag öffnet "⋮" jetzt einen Lösch-Bestätigungsdialog statt der Report-Ansicht, bei fremden Beiträgen bleibt Report wie gewohnt; beides live bestätigt.
- im Feed fehlt ein Indikator wenn in einem Post mehrere Bilder hinterlegt sind. das wischen funktioniert, aber nur wenn man weiß das dort mehrere bilder hinterlegt sind. unwissende würde nur ein bild sehen können. → Zähler-Badge oben rechts ("1 / 3") plus Punkte-Indikator unter dem Bild ergänzt (analog zur Web-Version), live bestätigt.
- Kommentar Fenster ist nicht im Darkmode
- Tier Anlegen ist nicht im Darkmode + Rassen werden als Dropdown angezeigt lassen sich aber nicht scrollen.
→ Darkmode: PetCreateForm.tsx + onboarding/pet.tsx nutzten noch die statischen (Hell-only)
colors/typographystattuseTheme()— umgestellt auf das gleiche Muster wie sign-up.tsx/OptionPicker.tsx. Scroll-Bug: onboarding/pet.tsx nutzte als äußeren Container ein normalesScrollView, darin verschachtelt der Rassen-Dropdown (OptionPicker) sein eigenesScrollView— zwei gleichachsige ScrollViews ineinander verschlucken auf Android die Scroll-Geste des inneren, auch mitnestedScrollEnabled. Fix: äußeren Container aufFlatList(leere Liste +ListHeaderComponent) umgestellt, exakt das bereits funktionierende Muster aus explore.tsx für dieselbe OptionPicker-Komponente. Typecheck + Lint sauber, i18n-Parität unverändert (243/243) — nicht live auf Android getestet (kein Emulator in dieser Session), nur strukturell gegen den bekannten Explore-Fix abgeglichen. - Nach dem Speichern des ersten Tiers, und dem Hochladen des Profilfotos, wird man wieder zurück geworfen auf Tier erstellen. erst ein neustart der app lässt einen auf den feed zugreifen. (BUG)
→ Ursache:
pets.create-Mutation in PetCreateForm.tsx hat dietrpc.pets.list-Query (ActivePetContext) nie invalidiert. Nach dem Avatar-Schritt landet man in(app), aberOnboardingRedirect(app/_layout.tsx) sah dort weiterhin den alten, leeren Pets-Stand aus dem Cache und schickte sofort wieder zurück nach/onboarding/pet— nur ein Neustart hat die Liste frisch geladen. Fix:queryClient.invalidateQueriesauftrpc.pets.listdirekt imonSuccessder Create-Mutation. Typecheck sauber.
TODO Neu3:
- Videos die angeklickt werden, laufen in Endlosschleife weiter obwohl man im Feed weiter scrollt.
→ expo-video hat kein eingebautes "bin ich noch sichtbar"-Bewusstsein; der Player lief nach dem Wegscrollen einfach weiter (mit
loop = true). Neuer HookuseVisiblePostIds(FlatList-viewabilityConfigCallbackPairs) trackt sichtbare Post-IDs,isVisible-Prop läuft durch PostCard → VideoPlayer, der beim Verschwindenplayer.pause()aufruft. In Feed UND Tierprofil-Postliste verdrahtet (beide nutzen VideoPlayer). Typecheck + Lint sauber, nicht live auf Android getestet (kein Emulator in dieser Session). - Profilbilder sind nicht vergrößerbar
→ Das Tierprofilbild (Tierprofil-Kopfbereich, size="lg") war reines, nicht antippbares
<Image>. Öffnet jetzt bei Vorhandensein eines Avatars den bereits vorhandenen/image-viewer(denselben Viewer inkl. Pinch-to-Zoom, den Beitragsbilder schon nutzen) mit dem vollen Avatar-Bild. Ohne hinterlegtes Foto (Initialen-Fallback) bleibt es unangetippt, da nichts zu vergrößern ist. Typecheck + Lint sauber. Nicht live getestet (kein Emulator in dieser Session). - Profilbearbeiten, lässt sich die Rasse nicht mehr ändern; wäre wichtig wenn man bei der Erstellung Fehler gemacht hat.
- Sowie kann man beim Tier kein Geburtstag hinterlegen oder Adoptionstag wie es im Web vorhanden ist.
→ Beides war nur eine Mobile-UI-Lücke, das Backend (
pets.update) unterstütztbreedId/birthday/adoptedAtbereits (Web nutzt es längst). Rassen-Dropdown aus PetCreateForm.tsx in eine geteilteBreedPicker-Komponente ausgelagert (DRY) und in edit.tsx eingebaut (Tierart selbst bleibt fix, nur die Rasse ist neu wählbar). Geburtstag/Adoptionstag als TT-MM-JJJJ-Textfelder ergänzt, gleiches Validierungs-/Konvertierungsmuster wie bei den Health-Formularen (date-format.ts). Da BreedPicker wie zuvor ein eigenes ScrollView verschachtelt, wurde edit.tsx aus demselben Grund wie onboarding/pet.tsx vonScrollViewaufFlatListumgestellt. Typecheck + Lint sauber (ein vorbestehender, nicht selbst verursachterset-state-in-effect-Lint-Hinweis in der bereits vorhandenen Effect besteht unverändert fort), i18n-Parität 247/247. Nicht live getestet (kein Emulator in dieser Session). - Meilensteine sind nicht im Darkmode verfügbar
→ milestones.tsx (Liste) und milestone-new.tsx (neuer Meilenstein) nutzten noch die statischen (Hell-only)
colors/typographystattuseTheme()— auf das etabliertemakeStyles(colors)-Muster umgestellt. Typecheck sauber, Lint zeigt nur vorbestehende, nicht selbst verursachte Warnungen (fehlendest-Dep, fehlendesalt). Nicht live getestet (kein Emulator in dieser Session).
TODO Neu4 (Tester-Feedback aus der Google Group, Closed Testing):
- Im Darkmode hatte ein User nach dem Login "alles in Schwarz" gesehen (kein Gerät bekannt).
→ Zwei konkrete Lücken gefunden und behoben: (1) Der
LoadingScreeninapp/_layout.tsx(Anzeige während Fonts/Clerk-Auth laden, u.a. direkt nach dem Login) war fest auf helle Farben verdrahtet, da er vor dem ThemeProvider rendert unduseTheme()dort nicht zur Verfügung steht — jetzt löstresolveInitialColors()(neu in ThemeContext.tsx) dieselbe Preference-oder-System-Vermutung auf, die der ThemeProvider selbst für seinen ersten Frame nutzt. (2) Der Splash-Screen (app.json) hatte nur eine hellebackgroundColor, keine Dark-Variante — im expo-splash-screen-Plugin per Source-Code verifiziert: ohnedark.backgroundColorbleibt der Splash in jedem System-Modus hell (kein automatisches Schwarz, aber eben auch kein Dark-Modus) — jetzt mitdark.backgroundColor: "#111111"(identisch zudarkColors.porchWhite) ergänzt, gleiches Icon (verifiziert: transparenter Alpha-Kanal, funktioniert auf beiden Hintergründen). Speculativ, da ohne Repro/Gerätedaten nicht 100% zu verifizieren — sollte den wahrscheinlichsten Kandidaten (helle Screens/Splash im dunklen Kontext) aber abdecken. Nicht live getestet (kein Emulator in dieser Session). - Tablet-Login-Screenshot: komplette App sieht "sehr großgezogen" aus.
→ Ursache (ohne den Screenshot selbst gesehen zu haben, aber strukturell bestätigt): sign-in.tsx/sign-up.tsx hatten keine Breitenbegrenzung — das äußere
Viewwarflex:1ohnemaxWidth, wodurch Inputs/Button per Default-alignItems:"stretch"auf die volle Bildschirmbreite gezogen wurden. Auf einem Phone (~350dp) unauffällig, auf einem Tablet (~800-1200dp) wirkt das wie randlos aufgeblasene Formularfelder. Fix: Inhalt in einen zentriertenform-Container mitmaxWidth: 420gepackt (beide Screens). Scope bewusst auf die gemeldeten Auth-Screens begrenzt — falls der Effekt laut nächstem Feedback app-weit auftritt, braucht es einen gemeinsamen Screen-Wrapper statt Einzelfixes. Nicht live auf einem Tablet getestet (kein Emulator/Tablet in dieser Session). - Ein User wünscht sich SSO mit Google.
→ Umgesetzt und live getestet (funktioniert).
expo-auth-session+expo-web-browserinstalliert (native Module, kein OTA-Update möglich), neuerGoogleSignInButton(eigenes SVG-„G"-Icon) auf sign-in.tsx + sign-up.tsx,useSSO()aus@clerk/expo(stable, nicht/experimental— konsistent mit dem Rest der App),WebBrowser.maybeCompleteAuthSession()im Root-Layout +useWarmUpBrowser-Hook. Zwei echte Blocker unterwegs gefunden und gelöst: (1) Google-Connection war im Clerk-Dashboard zunächst nicht aktiviert (per öffentlichem/v1/environment-Endpoint live verifiziert) — vom User nachgeholt. (2)pawfeed://sso-callbackwar nicht als erlaubte Redirect-URL in Clerk eingetragen → Clerk fiel auf einenhome_url+/sign-in-Fallback zurück, der auf dieser App nicht existiert (404, da die Web-Route/log-inheißt) — vom User im Dashboard eingetragen, jetzt behoben. Zusätzlich behandelt: Diese Clerk-Instanz verlangtlegal_consent_enabled: true, aber Clerks SSO-Transfer-Flow übergibt kein Consent-Feld — hätte sonst bei neuen Google-Konten im selbenmissing_requirements-Hänger geendet wie früher der Passwort-Flow. Fix: sign-up.tsx sperrt den Google-Button bis die bestehende Checkbox angehakt ist und ruft danachsignUp.update({ legalAccepted: true })nach; sign-in.tsx hat keine Consent-UI und zeigt bei einem noch nicht existierenden Account stattdessen eine Fehlermeldung, die auf „Registrieren" verweist, statt still ein Konto ohne Zustimmung anzulegen. Vorab noch ein reines EAS Update (Runtime 1.0.2, Branchproduction, Update-Gruppef1f3a381) mit nur den Dark-Mode-/Tablet-Fixes an die bestehenden Nutzer ausgeliefert, bevor der SSO-Code dazukam — die SSO-Verdrahtung ruftexpo-web-browserbeim App-Start auf (natives Modul, im installierten 1.0.2-Build nicht vorhanden); ein OTA-Update mit dieser Verdrahtung hätte den bestehenden Build zum Absturz gebracht. Dafür wurdeapp.json/app/_layout.tsx/sign-in.tsx/sign-up.tsx temporär auf den Stand ohne SSO zurückgesetzt, das Update veröffentlicht, danach alles 1:1 wiederhergestellt (Typecheck+Lint nach beiden Schritten sauber).
TODO Neu5 (weiteres Tester-Feedback):
- Erster Content-Load fühlt sich für Nutzer zu langsam an — Frage nach CDN-loser Beschleunigung.
→ Query-Cache (MMKV-persistiert, 24h gcTime), DB-Indizes und die Redis-gestützte Feed-Rangliste waren schon solide, das war nicht die Ursache. Eigentlicher Flaschenhals: Uploads liefen nur mit JPEG-Kompression (
quality: 0.8), aber ohne Auflösungs-Downscale — ein Kamerafoto blieb bei 3000-4000px, und jeder Feed-Betrachter lud dieses Vollbild nur um es auf Kartenbreite zu skalieren. User hat parallel geprüft: Supabase Image Transform API (serverseitiges Resize) ist nur im Pro-Plan verfügbar, den wir nicht haben — daher Fix am Schreib- statt Lesezeitpunkt. NeueresizeForUpload()-Utility (src/lib/resize-image.ts, nutztexpo-image-manipulators aktuelleImageManipulator.manipulate(uri).resize().renderAsync()-Chain-API, nicht das deprecatedmanipulateAsync) — skaliert auf max. 1600px lange Kante (nur falls nötig, kein Hochskalieren) und re-encoded immer auf JPEG @ 0.8 Kompression, unabhängig vom Ursprungsformat. In allen 4 Upload-Stellen eingebaut: AvatarStep.tsx, post-new.tsx, story-new.tsx, milestone-new.tsx.expo-image-manipulatorinstalliert (natives Modul, kein OTA-Update möglich — wird zusammen mit dem SSO-Build 1.0.3 ausgeliefert). Typecheck + Lint sauber (nur vorbestehendet-Dep-Warnungen). Nicht live getestet (kein Emulator in dieser Session) — insbesondereFile(uri).sizeist in den TS-Typdefs des installierten expo-file-system nicht direkt sichtbar, aber im nativen iOS-Quellcode (Property("size")) bestätigt. - Auf einem Android 17 - Pixel 10 Pro XL: App erneut gestartet (zweiter Tag) und dauerhafter Ladescreen. Frage nach Log-Einsicht.
→ Kein Log-Zugriff möglich: Mobile hatte bislang kein Crash-/Error-Tracking, kein Gerät/Emulator in dieser Session verfügbar. Verdacht:
app/_layout.tsxs allererster Ladescreen wartet auf ClerksisLoaded— beim Wiederöffnen nach längerer Pause ist der gespeicherte Session-Token abgelaufen, Clerk muss dann still über Netzwerk erneuern; hängt dieser Refresh (schlechte Verbindung o.ä.), bliebisLoadedfür immerfalseund der Spinner drehte endlos, ohne Fehleranzeige oder Retry-Möglichkeit. Zwei Fixes: (1) Sentry/GlitchTip für Mobile eingerichtet (@sentry/react-native, self-hosted GlitchTip-Instanz wie bei Web, eigener DSN überEXPO_PUBLIC_SENTRY_DSN).Sentry.init()nur wenn DSN gesetzt (No-op sonst),Sentry.wrap(RootLayout)als Error Boundary für sonst unbemerkte Render-Fehler. Bewusst ohne Sourcemap-Upload aus dem JS-Code heraus verdrahtet (schlägt bei dieser GlitchTip-Instanz für Web schon mit CSRF 403 fehl, siehe CLAUDE.md) — trotzdem versuchte der native Android-Build automatisch, Sourcemaps hochzuladen (Sentrys Expo-Config-Plugin hängt das standardmäßig als Gradle-Task ein), was den ersten 1.0.4-Build miterror: An organization ID or slug is required (provide with --org)hat scheitern lassen. Fix:SENTRY_DISABLE_AUTO_UPLOAD: "true"ineas.json(preview/productionenv) ergänzt — das ist die von@sentry/react-nativessentry.gradleselbst geprüfte Umgebungsvariable, um den Upload-Task zu überspringen. (2) 10-Sekunden-Timeout für die Auth-Ladephase: neueruseTimedOut()-Hook +AuthTimeoutScreen— zeigt nach 10s Wartezeit aufisLoadedstatt des endlosen Spinners einen Hinweistext + Retry-Button (Updates.reloadAsync(), nutzt das schon vorhandene EAS-Update-Runtime). Sentry-Warnmeldung wird sowohl beim Erscheinen des Timeout-Screens als auch beim Retry-Tap abgesetzt, damit ein künftiger Vorfall diesmal nachvollziehbar ist.expo-updates/expo-web-browserbereits vorhanden,@sentry/react-nativeist ein neues natives Modul (Config-Plugin automatisch in app.json ergänzt) — kein OTA-Update möglich, geht mit dem 1.0.4-Build raus. Typecheck + Lint sauber, i18n-Parität 252/252. Nicht live getestet (kein Emulator in dieser Session). - Bitte prüfen, ob das Rassen-Dropdown-Scroll-Problem bei "Tier erstellen" theoretisch erneut auftreten kann — ein Nutzer hat das bemängelt.
→ Struktur-Vergleich: onboarding/pet.tsx, edit.tsx und explore.tsx nutzen alle exakt dasselbe Muster (äußeres FlatList + OptionPickers inneres ScrollView mit
nestedScrollEnabled) — keine Inkonsistenz, kein Regressions-Bug zwischen den dreien. Das eigentliche Problem:nestedScrollEnabledist auf Android seit Jahren bekanntermaßen unzuverlässig bei verschachtelten gleichachsigen Scrollbereichen (FlatList/VirtualizedList + ScrollView) — kann je nach Android-Version/Listenlänge/Touch-Timing mal funktionieren, mal nicht (der Tester war auf Android 17, einer brandneuen Version). Strukturelle statt kosmetische Lösung:OptionPicker.tsxaufModalumgebaut — das Dropdown rendert jetzt in einer eigenen nativen Ebene komplett außerhalb der äußeren Scroll-Hierarchie, damit ist die ganze Klasse von Nested-Scroll-Bugs eliminiert statt nur umschifft. Button-Position wird permeasureInWindow()gemessen, damit das Dropdown weiterhin optisch wie ein Inline-Dropdown direkt unter dem Button erscheint (nicht als Vollbild-Sheet). Wirkt automatisch für alle 3 Verbraucher (Explore, Tier erstellen, Profil bearbeiten), keine Änderung an deren Props nötig. Aufräumen als Folge: explore.tsx'sheaderWrapper-zIndex-Hack (war nur für das alte absolut-positionierte Overlay nötig) entfernt; veraltete Kommentare in onboarding/pet.tsx + edit.tsx aktualisiert (FlatList bleibt dort bestehen, ist aber nicht mehr zwingend aus diesem Grund nötig). Kein neues natives Modul (Modal ist RN-Core) — theoretisch auch per OTA an 1.0.2-Nutzer ausliefbar, geht aber ohnehin mit dem 1.0.3-Build raus. Typecheck + Lint sauber. Nicht live getestet (kein Emulator/Gerät in dieser Session) — größtes Restrisiko: die Positions-Messung (measureInWindow) könnte auf einem echten Gerät leicht daneben liegen (z.B. bei Statusbar-Offsets), bitte beim nächsten Test gezielt prüfen.
TODO Neu6:
- Ein User hat ein Redirect-Problem beim Google-Login gemeldet, nicht reproduzierbar. Wann wurde die Redirect-Fix-Änderung gemacht?
→ Die
pawfeed://sso-callback-Whitelist wurde im Clerk-Dashboard noch vor dem 1.0.3-Build eingetragen (reine Backend-Config, kein App-Code) — wirkt serverseitig sofort für jede App-Version mit SSO-Code (1.0.3+), nicht an eine Build-Nummer gebunden. Ein Redirect-Problem danach wäre also entweder ein Tester auf einer Version vor 1.0.3 (kein SSO-Code, "Redirect-Problem" eher ein Missverständnis) oder ein bisher unbekannter Android-/Browser-Edge-Case. Echter Fund dabei:GoogleSignInButton.tsxs catch-Block meldete Fehler bisher nur lokal (Bildschirmtext), nicht an Sentry — nachgerüstet (Sentry.captureException(err)), damit ein künftiger Fall tatsächlich Logs hinterlässt. Per EAS Update (Runtime 1.0.4, Branchproduction, Update-Gruppeeea1524b) ausgeliefert — kein neuer Build nötig, reiner JS-Fix auf bereits vorhandenem@sentry/react-native. - Im Explorer gibt es beim Breed-Dropdown jetzt auch das Scroll-Problem.
→ War NICHT nur ein Altlast-Feedback von vor 1.0.4 — User hat nach dem ersten Fix-Versuch (EAS Update, s.o.) bestätigt: immer noch nicht scrollbar. Erster Fix-Versuch (Pressable→View bei der Dropdown-Karte) war unvollständig: die Karte steckte immer noch als Kind-Element im vollflächigen Backdrop-
Pressable, und einPressableIRGENDWO oberhalb einerScrollViewim Baum ist immer noch ein konkurrierender Responder-Kandidat für die Scroll-Geste. Nach erneuter Bestätigung durch den User ("neu gestartet, weiterhin defekt") das eigentliche Problem gefunden: Backdrop und Dropdown-Liste müssen Geschwister sein, nicht Eltern-Kind — jetzt so umgebaut, dass ein Tap innerhalb der Liste direkt in deren eigenen Teilbaum trifft und den Backdrop-Pressable darunter nie erreicht. Zweimal per EAS Update (Runtime 1.0.4) ausgeliefert, kein neuer Build nötig. Wichtige Erkenntnis für künftige Vorfälle: nach einem OTA-Update muss die App komplett geschlossen und neu geöffnet werden (nicht nur in den Hintergrund) —expo-updateswendet ein heruntergeladenes Update erst beim übernächsten Start an, der erste Start nach Download läuft noch mit dem alten Bundle. Typecheck + Lint sauber. Nicht live getestet (kein Emulator in dieser Session) — bitte diesmal wirklich explizit bestätigen lassen. - Kein Logout-Button in der App vorhanden — Neu-Anmeldung mit anderem Konto nicht möglich.
→ Echte Funktionslücke, nicht vorher gebaut. Neue
LogoutSectionunter „Meine Tiere" (ganz unten, nach den rechtlichen Links), mit Bestätigungsdialog (analog zum bestehenden "Tier löschen"-Muster in edit.tsx). Ruft ClerkssignOut()(useAuth()aus@clerk/expo) plusclearActivePetId()auf, damit nach einem Konto-Wechsel keine alte Pet-ID aus dem vorherigen Konto hängen bleibt — die Weiterleitung zum Login übernimmt automatischuseProtectedRoute()in app/_layout.tsx, sobaldisSignedInauffalsekippt. Kein neues natives Modul, per EAS Update (Runtime 1.0.4) ausgeliefert. Typecheck + Lint sauber, i18n-Parität 255/255. Nicht live getestet (kein Emulator in dieser Session).
Wichtiger Kontext im Nachhinein (User-Hinweis): Der 1.0.4-Build wurde zwar erstellt, aber nie in die Play Console hochgeladen — die Tester/Neuinstallationen liefen die ganze Zeit noch auf der vorherigen Version (vermutlich 1.0.2, ohne SSO-Code). Das erklärt rückwirkend, warum der Google-Redirect-Fehler und der Dropdown-Bug auch nach mehreren EAS Updates (die alle auf Runtime 1.0.4 zielten) und einer Neuinstallation weiter auftraten — die Updates konnten ein Gerät, das nie 1.0.4 installiert hatte, gar nicht erreichen. Die Analysen/Fixes selbst bleiben gültig, waren aber bis zum echten 1.0.4-Rollout nicht wirklich verifizierbar. User lädt 1.0.4 jetzt nach — sobald das live ist, sollten alle drei EAS-Update-Runden (Sentry-Fix, erster Dropdown-Versuch, finaler Sibling-Fix + Logout) automatisch beim übernächsten App-Start greifen, ohne weiteren Build.
Fortsetzung — echtes 1.0.4 live, EAS Updates kommen trotzdem nie an: Nachdem 1.0.4 wirklich in der Play Console lag, blieb der Logout-Button trotzdem unsichtbar. Diagnose-Werkzeug gebaut (UpdateDiagnosticsSection + FooterErrorBoundary in pets.tsx, temporär, per EAS Update ausgeliefert) — aber selbst DAS kam nie an, auch nach 20 Neustarts und 2 kompletten Neuinstallationen auf zwei komplett unterschiedlichen Geräten (Pixel 10 Pro XL extern, Xiaomi 13 Pro beim User selbst). Netzwerk-Check ausgeschlossen: https://u.expo.dev/<projectId> ist vom Testgerät aus erreichbar (erwartete Header-Fehlermeldung, kein Timeout/Blockade). Build-Metadaten (eas build:view) bestätigten Channel/Runtime-Version korrekt eingebettet — kein Konfigurationsfehler bei den Publishes selbst.
Ursache gefunden: app.jsons Plugin-Liste enthielt expo-dev-client — und zwar ungefiltert für alle Build-Profile inkl. production. Dieses Paket ist laut eigener Doku nur für Debug-/Development-Builds gedacht und bringt eine eigene Update-/Branch-Auswahl-Logik mit, die den normalen automatischen expo-updates-Check verdrängen kann — passt exakt zum Symptom (Updates kommen strukturell nie an, geräteunabhängig). Der lokale Dev-Client war laut früheren Sessions ohnehin schon kaputt, kein Verlust durchs Entfernen. Fix: expo-dev-client aus der gemeinsamen plugins-Liste entfernt (Paket bleibt in package.json, eas.jsons development-Profil hat sein eigenes developmentClient: true, unabhängig von dieser Liste). Braucht einen neuen nativen Build (Plugin-Änderungen sind nicht OTA-fähig) — 1.0.5/versionCode 6 angestoßen. Falls das die Ursache war, sollte ab diesem Build der normale automatische Update-Check endlich greifen.
Notiz für einen künftigen Build (nicht dringend, rein vorsorglich): Beim 1.0.5-Build meldete EAS Patch-Versions-Abweichungen bei etlichen Expo-Paketen (expo, expo-router, expo-updates, expo-image, u.v.m. — installiert liegt jeweils einen Patch hinter dem für SDK 57 aktuell erwarteten Stand). Nur Patch-Ebene, kein Build-Blocker, aber beim nächsten ohnehin anstehenden Build sollte vorher npx expo install --fix laufen, um das aufzuräumen.
Bestätigt und abgeschlossen: 1.0.5 freigegeben und getestet — Logout-Button UND das Diagnose-Tool waren sichtbar, expo-dev-client war also tatsächlich die Ursache. EAS Update funktioniert damit ab 1.0.5 wieder normal. Feedback: Logout-Button sah "wie ein Dev-Button" aus — lag am direkt danebenstehenden, absichtlich roh gehaltenen Diagnose-Block ("Update-Diagnose (temporär)" + Monospace-Dump + Debug-Button). Diagnose-Tool war ohnehin nur zur Fehlersuche gedacht und jetzt entfernt (UpdateDiagnosticsSection + zugehörige Styles raus aus pets.tsx); FooterErrorBoundary um LogoutSection bleibt als dauerhaftes, günstiges Sicherheitsnetz bestehen. Per EAS Update (Runtime 1.0.5, Gruppe 105f39e3) ausgeliefert, kein neuer Build nötig. Typecheck + Lint sauber.
Kleiner Nachklapp: Das Cleanup-Update (105f39e3) wurde von checkForUpdateAsync() als updatePreviouslyFailed gemeldet — vermutlich wurde der Download/Start bei einem der vielen Testneustarts unterbrochen, bevor die App sich als erfolgreich gestartet melden konnte; expo-updates blockiert eine einmal fehlgeschlagene Update-ID danach dauerhaft, unabhängig davon ob der Code eigentlich intakt ist (war er hier: Typecheck sauber, keine verwaisten Referenzen). Per eas update:republish mit frischer Update-ID (fdba4e10) umgangen — sollte jetzt ohne die Blockade ankommen.
Doch kein Interrupt-Artefakt — echter Absturz bestätigt: Der Republish (fdba4e10) zeigte im Expo-Dashboard ebenfalls "known launches: 1, known crashes: 1", identisch zum Original. Das widerlegt die Interrupt-Theorie (eine frische Update-ID hätte keinen alten Absturz "erben" können) — der Absturz ist real und reproduzierbar. GlitchTip zeigte dazu nichts (nur 2 alte, unabhängige Meldungen) — d.h. der Crash passiert unterhalb der JS-Ebene, bevor Sentry.wrap() überhaupt greifen kann, exakt beim Umschalten auf das heruntergeladene Bundle. Das eingebettete Bundle (im APK selbst) läuft dagegen einwandfrei — jedes bisher unter Runtime 1.0.5 versuchte OTA-Update war das allererste OTA-Update überhaupt auf diesem (Dev-Client-befreiten) nativen Build, und es scheiterte konsistent.
Verdacht: expo-updates lag laut expo install --check-Warnung beim 1.0.5-Build einen Patch hinter (57.0.21 statt erwartet ~57.0.22) — genau solche Patches beheben oft subtile Update-Apply-Bugs. Mit npx expo install --fix alle Expo-Pakete auf den erwarteten Stand gebracht (u.a. expo-updates → 57.0.22, expo → 57.0.22, expo-router → 57.0.21). Typecheck + volles Lint danach unverändert sauber (identische 39 vorbestehende Probleme wie zu Sessionbeginn, nichts Neues). Neuer nativer Build 1.0.6/versionCode 7 nötig (Dependency-Update wirkt nur bei einem frischen nativen Build) — bewusst neue Runtime-Version (nicht 1.0.5 wiederverwendet), um jede Verwechslung mit dem bekannt kaputten 1.0.5-Runtime-Zustand auszuschließen. Falls das die Ursache war, sollte OTA ab 1.0.6 tatsächlich funktionieren — sonst brauchen wir echte native Logs vom Gerät (Sentry kann diesen Absturz nicht sehen).
1.0.6 installiert, Diagnose-Text weg, Logout-Button da — aber "DEV-Optik". Nachfrage ergab: zu schlicht/unfertig (dünner Rahmen, kein Füllhintergrund), Farbe/Kontrast falsch, UND Icon/Schrift passen nicht — alle drei zugleich. Ursache: der Button war komplett handgestylt (kopiert von edit.tsx's Delete-Pet-Muster: roter Rahmen, LogOut-Icon, eigene Styles) statt die etablierte, überall sonst genutzte Button-Komponente zu verwenden. Logout ist zudem nicht destruktiv/irreversibel wie Löschen — die neutrale outline-Variante (heller Hintergrund, Ink-Text, Hairline-Rahmen) passt semantisch besser als Alarmrot. Fix: LogoutSection nutzt jetzt <Button variant="outline"> statt eigenem Pressable+Icon+Styles; LogOut-Icon-Import und radii-Import (nicht mehr gebraucht) entfernt. Per EAS Update (Runtime 1.0.6, Gruppe cb97d058) ausgeliefert.
DIESES Update hing wieder (60+ Sekunden Splash-Screen, kein ANR-Dialog) — expo-updates-Patch-Fix hat NICHT geholfen. Echte Gerätelogs geholt (adb logcat vom per USB verbundenen Xiaomi während eines Reproduktionsversuchs) und ausgewertet. Der dev.expo.updates-Log-Tag zeigte den kompletten Update-Lifecycle strukturiert als JSON — und darin die eigentliche Ursache:
ErrorRecovery: exception encountered: Error: Missing required env var EXPO_PUBLIC_API_URL — check mobile/.env
Echte Root Cause gefunden — expo-dev-client war eine Ablenkung, nicht die Ursache: eas update --environment production lädt Umgebungsvariablen aus einem serverseitigen EAS-Umgebungsspeicher (eas env:list/eas env:set), komplett getrennt von eas.jsons build.production.env-Block (der nur für eas build gilt). Dieser Server-Speicher war die ganze Session über leer ("No environment variables... found" stand in JEDEM Update-Log, wurde aber als harmlos übersehen) — jedes OTA-Bundle dieser Session lief also ohne EXPO_PUBLIC_*-Variablen, und env.tss requireEnv()-Check wirft dann sofort beim Laden (importiert von praktisch jeder Datei). Das erklärt auch rückwirkend, warum alle bisherigen "Updates kommen an"-Bestätigungen (Diagnose-Tool + Logout-Button sichtbar in 1.0.5) vermutlich nie ein echtes OTA-Update waren, sondern nur das beim eas build-Zeitpunkt eingebettete Bundle (das die Env-Vars korrekt aus eas.json bekommt) — expo-dev-client könnte für das ursprüngliche 1.0.4-Problem trotzdem ein Faktor gewesen sein, war aber nicht die vollständige Erklärung.
Fix: eas env:set production --name EXPO_PUBLIC_API_URL --value "..." --visibility plaintext (und ebenso für EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY, EXPO_PUBLIC_SUPABASE_URL, EXPO_PUBLIC_SENTRY_DSN) für die Environments production und preview gesetzt. Test-Update (Gruppe 6eb8b061) zeigte danach im Log "Environment variables... loaded from the 'production' environment on EAS: ..." statt "No environment variables... found" — Variablen kommen jetzt nachweislich mit.
✅ Am Gerät bestätigt: Logout-Button erscheint, funktioniert. Das OTA-Update kommt jetzt tatsächlich an und startet erfolgreich — die fehlenden EAS-Server-Env-Vars waren die echte, vollständige Ursache der gesamten Update-Odyssee dieser Session. expo-dev-client-Entfernung bleibt trotzdem sinnvoll (gehört nicht in Production), war aber nicht der entscheidende Fix. TODO Neu6 damit abgeschlossen.
Neu 7:
- Datums eingabe unüblich mit - getrennt statt mit . ansicht am Handy wird meist durch die tastatur überblendet. können wir für die mobile eine data wheel picker nutzen ?
→ Analyse erledigt, betrifft 4 Dateien (edit.tsx Geburtstag/Adoptionsdatum + 3 Health-Sektionen). Wheel-Picker braucht ein natives Modul (
@react-native-community/datetimepicker) → kein einzelner Build nur dafür, stattdessen aufPENDING-NATIVE-BUILD.mdverschoben und wird mit dem nächsten ohnehin anstehenden nativen Build mitgenommen. Der reine Trennzeichen-Fix ("-" → ".") ist davon unabhängig und OTA-fähig, sobald gewünscht. - Ein User bemängelt, das die Regisrierung gut ist, aber beim eingeben des Passworts diese hinter der "Bildschrimtastatur" verschwindet, ausserdem gibt es keine Möglichkeit für den User die korrekte eingabe nochmal zu prüfen.
→ Beides im Code bestätigt (
app/(auth)/sign-up.tsx, gleiches Muster auch insign-in.tsxgefunden und mitbehoben): (1) Der Screen nutzte nur ein einfachesViewals äußeren Container, keinScrollView/KeyboardAvoidingView— auf kleinen Screens schob sich die Tastatur einfach über die unteren Felder. Fix: äußerer Container jetztKeyboardAvoidingView(iOS:padding) +ScrollView(keyboardShouldPersistTaps="handled",contentContainerStylemitflexGrowstattflex, damit weiterhin zentriert bleibt aber scrollbar wird). (2) Neue geteiltePasswordField-Komponente (src/components/PasswordField.tsx) mit Auge/Auge-durchgestrichen-Toggle (lucideEye/EyeOff) ersetzt das nacktesecureTextEntry-TextFieldin beiden Screens. Kein natives Modul, OTA-fähig. Typecheck sauber (Mobile). - Ein anderer User, meinte wäre es nicht schöner und aufgeräumter wenn die App Settings nicht unter MyPets stehen würden sondern einen eigenen Tab erhalten.
→ Umgesetzt (Plan:
~/.claude/plans/mobile-settings-tab-extraction.md, Gitea-Issue #36, Branchfeature/settings-header-icon). Statt eines 5. Bottom-Tabs (ursprünglich vorgeschlagene Variante, siehe Mockupmobile-settings-tab-mockup.pngim Plan-Ordner) wurde ein Zahnrad-Icon im Header gewählt, neben der Mailbox — auf Wunsch des Users, da Settings realistisch nur 1–2× im Halbjahr geöffnet wird und ein permanenter 5. Tab dafür unnötig Platz kostet (Mockup:mobile-settings-header-icon-mockup.png). Neuer Screenapp/(app)/settings.tsx(Stack-Push wie "Suche", kein Modal, keine Tab-Bar sichtbar währenddessen), erreichbar überSettingsHeaderButtonin(tabs)/_layout.tsx— erscheint auf allen 4 Tabs nebenMessagesHeaderButton. Die 5 Footer-Sektionen auspets.tsx(Darstellung/Sprache/Tier-Vorschläge/Rechtliches/Logout) wurden nachsrc/components/settings/*.tsxextrahiert (DRY, je eigene Datei).pets.tsxist jetzt reine Tierliste. Nebenbei behoben:FooterErrorBoundaryschützt jetzt den gesamten Settings-Screen statt nur den Logout-Button (Lücke aus der Code-Review beim OTA-Indikator-Feature). Typecheck + Lint sauber, i18n-Parität geprüft. - Ein user hat die meldung erhalten, "you do not have premission to access this pet" - kannst du mir erklären woher das kommen könnte ?
→ Ursache im Code gefunden (Web-seitig, nicht Mobile): Der Fehlertext kommt exakt aus
assertPetOwnership(src/lib/assert-pet-ownership.ts:19), dem gemeinsamen Ownership-Check, den praktisch jede pet-bezogene Mutation zuerst durchläuft. Web speicherte die "aktive Tier"-ID inlocalStorage["active_pet_id"](src/context/ActivePetContext.tsx) — anders als Mobile (siehe TODO Neu6:LogoutSectionruft dort bewusstclearActivePetId()auf) wurde dieser Wert auf Web nirgends geleert, auch nicht beim Sign-out über Clerks<UserButton />.ActivePetInitializersetzt nur dann einen Wert, wennactivePetIdleer ist — er prüfte nie, ob die gespeicherte ID überhaupt noch zum aktuell eingeloggten Owner gehört. Konkretes Szenario: Nutzer A meldet sich ab, Nutzer B meldet sich im selben Browser an (gemeinsamer Rechner, oder A erstellt ein zweites Konto) — B's Session lud weiterhin A's alteactive_pet_id, und jede Aktion schickte diese fremde Pet-ID ans Backend, das sie zu Recht mit genau dieser Fehlermeldung ablehnte. Gleiche Bug-Klasse wie der bereits auf Mobile behobene Logout-Fund, hier aber nie für Web nachgezogen. Fix:clearActivePetId()insrc/context/ActivePetContext.tsxergänzt (Pendant zu Mobile). DaActivePetProviderim Root-Layout außerhalb vonClerkThemeProvidersitzt (Clerk-Hooks dort nicht verfügbar — bewusste Architektur laut(app)/layout.tsx-Kommentar, um den Clerk-Bundle aus öffentlichen Routen rauszuhalten), kein direkteruseAuth()-Zugriff im Context selbst möglich. Stattdessen neueSignOutCleanup-Komponente (src/components/layout/SignOutCleanup.tsx), mounted in(app)/layout.tsxnebenActivePetInitializer— beobachtetuseAuth().isSignedInund ruftclearActivePetId()beim Wechsel vontrueauffalse, statt einen Klick-Handler an Clerks<UserButton/>zu hängen (die bietet dafür keinen sauberen Hook-Punkt). Deckt damit jeden Sign-out ab, unabhängig vom Auslöser. Typecheck + voller Testlauf (209/210, 1 skip wie zuvor) sauber, keine neuen Lint-Fehler. - Nutzer Feedback - DarkMode - Neuer Beitrag - Text die getippt werden sind weiß auf weißem grund.
→ Ursache gefunden:
app/(app)/post-new.tsxgehörte noch zu den nicht auf Dark Mode migrierten Screens — es importierte die statischen, Light-onlycolors/typographyaustheme/tokens.tsdirekt, statt sie peruseTheme()zu beziehen (gleiche Bug-Klasse wie die bereits behobenen Fälle aus dem Darkmode-Audit, siehe README/CLAUDE.md-Kommentar intheme/tokens.ts: "dark-mode support has to be added file-by-file"). Der Container-Hintergrund blieb dadurch immer hell (colors.porchWhite), während die geteilteTextField-Komponente korrekt theme-abhängige Textfarbe zieht (colors.ink, im Dark Mode hell) — macht bei aktivem Dark Mode aus hellem Text auf hellem Hintergrund den gemeldeten "weiß auf weiß"-Effekt in der Caption-Eingabe. Fix:post-new.tsxaufuseTheme()+makeStyles(colors)-Muster umgestellt (identisch zumilestone-new.tsx). Gleiches Muster/gleicher Fund auch instory-new.tsxentdeckt (dort ohne Texteingabe, macht sich als falscher heller Hintergrund statt unlesbarem Text bemerkbar) — gleich mitbehoben. Typecheck + Lint sauber (0 neue Warnungen, 6 vorbestehendeexhaustive-deps-Warnungen unverändert). Nachtrag:app/(app)/search.tsx(gleicher Fehler im Suchfeld) auf Nachfrage ebenfalls behoben — dort bekommen die separaten Unterkomponenten (TabButton,PetRow,Loading,EmptyState)styles/colorsjetzt als Props von der Hauptkomponente durchgereicht, statt selbst die statischen Tokens zu importieren. Typecheck + Lint sauber (1 vorbestehende, unverändertealt-text-Warnung beiexpo-image).