leafittome/docs/handoff.md
cschlaefke 0cc39f69b1 Fix: Bootstrap-Maßstab ist householdId, nicht users-Doc-Existenz
Die Push-Registrierung legt users/{uid} per merge selbst an (fcmTokens,
timezone) — beim kaputten Apple-Account existierte das Dokument daher
ohne householdId, und Gate + Bootstrap hielten den Account für fertig.
Jetzt prüfen beide auf householdId; der Bootstrap schreibt das
users-Dokument mit merge, damit die Push-Felder erhalten bleiben.
Neuer Test bildet exakt diesen Zustand ab.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 21:43:23 +02:00

80 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Handoff: LeafItToMe (Pflanzenpflege-App)
Stand: 2026-07-23. Sprache mit dem Nutzer (Chris): **Deutsch**.
## Projekt in einem Satz
Private Flutter-App (iOS+Android) zur Pflanzenpflege für Haushalt + Pflanzen-Sitter: Foto-Erkennung (PlantNet + Claude), Gieß-/Düngeplan mit Tages-Checkliste, tägliche Sammel-Push, Haushalt-Teilen per Einladungscode mit Rollen.
## Wo alles liegt
- **Code:** `~/Documents/Projekte/Planty` (Ordner heißt noch Planty, Projekt heißt seit Umbenennung `leafittome`, App-Name „LeafItToMe", Bundle-ID-Platzhalter `dev.leafittome.app`)
- **Git-Remote:** selbst gehostetes Forgejo `https://git.cshomelabs.work/cschlaefke/leafittome.git` (HTTPS + Token im macOS-Schlüsselbund, `git push` läuft ohne Abfrage)
- **Doku (im Repo, deutsch, aktuell halten!):** `README.md`, `docs/architektur.md`, `docs/firebase-einrichtung.md` (Schritt-für-Schritt-Historie aller Backend-Einrichtungen), `docs/git-forgejo.md`
- **Claude-Memory:** Projektplan, Nutzerprofil und Backlog liegen im persistenten Memory-Verzeichnis dieser Claude-Code-Installation (werden automatisch geladen)
- **Firebase-Projekt:** `leaf-it-to-me-app` (Blaze aktiv, Budget-Alarm eingerichtet; Firebase CLI ist eingeloggt)
- **Functions:** `functions/` (TypeScript, Node 22, Region europe-west3): `identifyPlant` (PlantNet + Claude, Secrets `PLANTNET_API_KEY`/`ANTHROPIC_API_KEY` im Secret Manager), `sendDailyReminders` (alle 15 Min, sammelt über alle Haushalte eines Nutzers), `joinHousehold` (Einladungs-Einlösung), `leaveHousehold` (Haushalt verlassen), `removeMember` (Mitglied entfernen, nur Besitzer)
- **Auth:** E-Mail/Passwort + **Anmelden mit Apple** (iOS nativ, Android Web-Flow über Firebase-Handler) — Code in `lib/features/auth/data/auth_repository.dart`, Login-UI in `.../presentation/login_screen.dart`. Haushalts-Bootstrap (Profil + eigener Haushalt beim ersten Login) im gemeinsamen Helper `_bootstrapHouseholdIfNeeded`.
## Verifizierter Stand
- **V1 komplett** und auf Android + iOS Ende-zu-Ende getestet: Foto → Erkennung → vorbefülltes Profil; Tages-Checkliste; Sammel-Push kam im regulären Scheduler-Lauf auf dem Sperrbildschirm an. **iOS-Push seit 2026-07-20 eingerichtet und laufend** (Apple Developer Account freigeschaltet, APNs-Schlüssel in Firebase, Push-/Background-Capability in Xcode — Details + Stolperfalle „Personal Team" in `docs/firebase-einrichtung.md` Schritt 6).
- **V2 implementiert, deployt und vom Nutzer erfolgreich getestet** (Einladungscodes member/sitter, `joinHousehold`-Function, Rules-Enforcement für Sitter, „bestätigt von"-Anzeige).
- **V2.1 Multi-Haushalt implementiert und getestet**: Nutzer bleiben Mitglied aller Haushalte (`memberUids` ist Quelle der Wahrheit, `users.householdId` nur noch aktiver Zeiger), `sendDailyReminders` sammelt Aufgaben aus allen Haushalten.
- **V2.2 implementiert, deployt und vom Nutzer vollständig getestet und bestätigt**: Besitzer-Konzept (`ownerUid`, Alt-Haushalte: memberUids[0]), Besitzer-Anzeige, Haushalt umbenennen (Rules: Clients dürfen nur noch `name` ändern), Haushalts-Wechsler im Drawer, Tages-Checkliste gruppiert Aufgaben aus allen Haushalten, Functions `leaveHousehold`/`removeMember` (biegen den aktiven Zeiger des Betroffenen um, legen notfalls frischen Haushalt an). Zwei Nachbesserungen aus dem Live-Test, beide behoben und bestätigt:
- `leaveHousehold`/`removeMember` warfen zunächst HTTP 401 — fehlende Cloud-Run-Invoker-Berechtigung bei frisch angelegten Gen-2-Functions (kein Auth-Bug im Code); Fix war ein gezielter Neu-Deploy dieser beiden Functions.
- Besitzer-Anzeige blieb bei Alt-Haushalten aus der V1-Zeit leer (kein `members`-Eintrag für den Ersteller, die Map kam erst mit V2). Fix: `memberEntrySyncProvider` trägt beim App-Start die eigene E-Mail in allen eigenen Haushalten nach, plus eine eng gefasste Rules-Ausnahme (jeder darf nur den eigenen `members`-Eintrag pflegen, Rollenwechsel bleibt gesperrt).
## Offen / Nächstes
### 🟡 Apple-Login-Bug: Fix implementiert, wartet auf Gerätetest durch den Nutzer
**Der Bug:** Mit einem neuen Apple-Account schlug das Anlegen einer Pflanze fehl („Speichern fehlgeschlagen"). Ursache (per Code-Analyse bestätigt, Firestore-Sichtung stand aus): Der Haushalts-Bootstrap hing an `signInWithApple` **nach** `signInWithProvider` — der Router leitet aber schon bei „angemeldet" weiter (redirect prüft nur `currentUser != null`). Wirft der Bootstrap oder wird die App vorher beendet, bleibt der Nutzer dauerhaft ohne `users`-Doc + Haushalt, und die (korrekten) Rules lehnen jeden Schreibzugriff ab.
**Der Fix (2026-07-23 implementiert, Option „reaktiver Bootstrap"):**
- `householdBootstrapProvider` (`lib/features/auth/data/household_bootstrap_provider.dart`): beobachtet das `users/{uid}`-Dokument; bei „angemeldet, aber kein Profil" legt er Profil + Haushalt an. Deckt **alle** Login-Wege ab (auch künftig Google).
- `BootstrapGate` (`lib/features/auth/presentation/bootstrap_gate.dart`, eingehängt in `app.dart` builder): hält die App zurück, bis das Profil existiert; zeigt Warte-Bildschirm bzw. Fehler mit „Erneut versuchen".
- `AuthRepository`: Bootstrap jetzt öffentlich (`ensureHouseholdBootstrap`, idempotent + In-Flight-Schutz); `signInWithApple` ruft ihn nicht mehr selbst auf.
- **Selbstheilend:** Der bereits kaputte Apple-Account wird beim nächsten App-Start automatisch repariert (Gate sieht „kein Profil" → legt Haushalt an).
- Analyzer grün, alle 9 Tests grün (neuer Test: „Erster Login ohne Profil → Haushalt wird reaktiv angelegt").
**Nachbesserung (gleicher Tag, nach erstem Fehlversuch des Nutzers):** Mit dem Gate-Build schlug Speichern **weiter** fehl, und zwar ohne dass der Einrichtungs-Bildschirm je erschien (mit und ohne Foto). Befund: Die **Push-Registrierung** (`_saveToken`, `set(..., merge: true)`) hatte das `users`-Dokument beim ersten Apple-Login längst angelegt (fcmTokens/timezone, aber ohne `householdId`) — Gate und Bootstrap prüften nur die **Dokument-Existenz** und hielten den Account deshalb für fertig. Fix: Überall ist jetzt **`householdId` vorhanden** der Maßstab (Gate, Bootstrap-Provider, `_bootstrapHouseholdIfNeeded`), und der Bootstrap schreibt das users-Dokument mit **merge**, damit Push-Felder erhalten bleiben. Außerdem zeigt die Speichern-Fehlermeldung jetzt den konkreten Firebase-Fehlercode an (plugin/code statt Generik). Neuer Test stellt exakt den kaputten Zustand nach (users-Doc nur mit fcmTokens) — alle 10 Tests grün.
**Was der Nutzer jetzt testen sollte:**
1. App auf dem iPhone neu bauen/starten, mit dem **bestehenden Apple-Account** anmelden → jetzt sollte kurz „Dein Haushalt wird eingerichtet …" erscheinen (diesmal wirklich, da das Gate jetzt auf `householdId` prüft), danach Pflanze-Anlegen mit und ohne Foto.
2. Optional Härtetest „echter Erst-Login": unter <https://appleid.apple.com> die Freigabe für „LeafItToMe" entfernen → nächster Login ist wieder „erster Login" → Ende-zu-Ende inkl. Pflanze mit Foto.
3. Falls es **weiter** fehlschlägt: Die Fehlermeldung nennt jetzt den Code — `firebase_storage/…` heißt Foto-Upload/Storage-Rules/IAM, `cloud_firestore/…` heißt Plant-Write/Firestore-Rules. Den Code melden, dann gezielt weiter.
### Zuletzt erledigt
- **2026-07-23 (2):** Reaktiver Haushalts-Bootstrap + BootstrapGate implementiert (Fix für den Apple-Login-Bug, siehe oben); Architektur-Doku um die Entscheidung ergänzt.
- **2026-07-23 (1):** Apple-Login **implementiert und committet** (`signInWithApple` via eingebautem `AppleAuthProvider`, „oder"-Trenner + Button im Login, iOS-Entitlement `com.apple.developer.applesignin`, l10n-Strings, Doku `firebase-einrichtung.md` Schritt 7). Konfiguration vom Nutzer erledigt, Login funktioniert.
- **2026-07-20:** iOS-Push eingerichtet (Apple Developer Account freigeschaltet, APNs/Firebase/Xcode). V1 damit auch auf iOS vollständig.
### Weitere Kandidaten (nach dem Bug)
1. **V3:** Krankheits-Diagnose per Foto, Stellplatz-Bewertung (Standort-Analyse pro Stellplatz + Eignungs-Sterne pro Pflanze — Zwei-Ebenen-Konzept siehe Memory), Umtopf-Erinnerungen.
2. **Backlog:** Google-Login (nach Apple, `firebase-einrichtung.md` Schritt 8); Button „KI-Zweitmeinung" bei unbefriedigendem PlantNet-Ergebnis; Push-Anzeige auch bei App im Vordergrund; App Check vor App-Store-Release; Englisch (`app_en.arb`).
## Stolperfallen (teuer erkauft, nicht erneut zahlen)
- `firebase functions:log` hinkt **1520 Minuten** hinterher — „keine Läufe" heißt meist nur Log-Verzögerung. Scheduler-Status im Cloud-Scheduler-Console prüfen lassen.
- Android zeigt FCM-Pushes **nur bei App im Hintergrund**.
- `flutter_timezone` ≥5 liefert `TimezoneInfo`-Objekt → `.identifier` verwenden (Objekt crasht Firestore-Writes still).
- Storage-Rules mit `firestore.get()` brauchen die IAM-Rolle „Firebase Rules Firestore Service Agent" fürs Storage-Dienstkonto (wurde manuell erteilt).
- Erster Functions-Deploy in frischem Projekt kann mit 403 `iam.serviceaccounts.actAs` scheitern → wenige Minuten warten, erneut deployen.
- Analyzer: `build/` und `ios/build/` sind in `analysis_options.yaml` ausgeschlossen (SwiftPM legt dort Fremdcode ab).
- iOS-Fehler „requires minimum platform version 15.0 … but this target supports 13.0": Das von Flutter generierte SwiftPM-Paket (`ios/Flutter/ephemeral/…/Package.swift`) wird bei `pub get` mit iOS 13 erzeugt; erst `flutter build ios`/`flutter run` hebt es auf das Projekt-Target (15.0) an. Nach `flutter clean` oder beim Bauen direkt aus Xcode daher einmal `flutter build ios --config-only` ausführen.
- Nutzer wünscht zu jedem Backend-Baustein **begleitende deutsche Doku** in `docs/` (keine Firebase-Vorerfahrung).
## Arbeitsabläufe
- Prüfen: `flutter analyze && flutter test` (Tests mocken Firebase via `firebase_auth_mocks`/`fake_cloud_firestore`); Android-Verifikation: `flutter build apk --debug`
- Texte: nur in `lib/l10n/app_de.arb` (typografische Anführungszeichen „…“ verwenden, gerade `"` brechen das JSON), dann `flutter gen-l10n`
- Deploy: `firebase deploy --only functions|firestore|storage --project leaf-it-to-me-app`; nach jedem Arbeitsstand committen + `git push`
## Suggested skills
- `/verify` — nach nichttrivialen Änderungen den echten Flow treiben (Gerät/Simulator), nicht nur Tests
- `/code-review` — vor größeren Merges über den Diff laufen lassen
- `/run` — App starten/screenshotten, wenn Änderungen sichtbar geprüft werden sollen