leafittome/docs/handoff.md
cschlaefke 9aa49c641d iOS/TestFlight: App-Check-Release-Provider, App-Icon, Datenschutz-Hosting
- main.dart: App-Check-Provider per kReleaseMode umgeschaltet (Release =
  PlayIntegrity/AppAttest, Debug = Debug-Provider). Nötig, da die Functions
  App Check erzwingen — ein Release-Build mit Debug-Provider würde auf
  fremden Geräten bei jedem Function-Aufruf scheitern.
- App-Icon aus assets/icon/icon.png via flutter_launcher_icons generiert
  (iOS-Set + Android-Mipmaps).
- docs/legal/datenschutz.html: Platzhalter ausgefüllt, interner Hinweis raus.
- firebase.json: hosting-Block (public: docs/legal, Redirect / -> /datenschutz).
  URL: https://leaf-it-to-me-app.web.app/datenschutz
- Doku: firebase-einrichtung.md Schritt 9/10 + Hosting fortgeschrieben,
  handoff.md aktualisiert (iOS/TestFlight abgeschlossen, nur noch Android offen).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CMRhL16hB14xcfef1qez7N
2026-09-08 20:37:49 +02:00

99 lines
17 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-09-08 (Session-Ende). 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ünge-/Umtopfplan mit Tages-Checkliste, tägliche Sammel-Push, Krankheits-Diagnose per Foto mit Historie, 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` (inkl. Kernentscheidungen: berechnete Aufgaben, reaktiver Haushalts-Bootstrap, Umtopfen als Opt-in-Monatsintervall), `docs/firebase-einrichtung.md` (Schritt-für-Schritt-Historie aller Backend-Einrichtungen, zuletzt Schritt 8 Google-Login), `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), `diagnosePlant` (V3: Krankheits-Diagnose per Foto, Claude Vision, Modell `claude-sonnet-5`), `analyzeLocation` (V3: Standort-Analyse per Foto, Claude Vision, Lichtverhältnisse), `assessPlantFit` (V3: Eignungs-Bewertung einer Pflanze für ihren Stellplatz, rein textbasiert ohne Foto), `sendDailyReminders` (alle 15 Min, sammelt Gießen/Düngen/Umtopfen über alle Haushalte eines Nutzers), `joinHousehold`, `leaveHousehold`, `removeMember`
- **Auth:** E-Mail/Passwort + **Apple** (iOS nativ, Android Web-Flow) + **Google** (beide Plattformen Web-Flow, eingebauter `GoogleAuthProvider`) — Code in `lib/features/auth/`. Haushalts-Bootstrap läuft **reaktiv** über `householdBootstrapProvider` + `BootstrapGate` (Maßstab: `householdId` im users-Doc, NICHT Doc-Existenz — die Push-Registrierung legt das users-Doc nebenbei an!). Details der Entscheidung in `docs/architektur.md`.
## Verifizierter Stand
- **V1 komplett** (Foto → Erkennung → Profil; Checkliste; Sammel-Push), auf Android + iOS Ende-zu-Ende getestet. iOS-Push seit 2026-07-20 eingerichtet und laufend (`firebase-einrichtung.md` Schritt 6).
- **V2/V2.1/V2.2 komplett getestet und bestätigt:** Einladungscodes member/sitter, Multi-Haushalt (`memberUids` = Quelle der Wahrheit, `users.householdId` = aktiver Zeiger), Besitzer-Konzept, Umbenennen, Haushalts-Wechsler, `leaveHousehold`/`removeMember`.
- **Apple-Login: implementiert, Bug behoben, vom Nutzer bestätigt** (2026-07-23). Der Bug (neuer Apple-Account konnte nichts speichern) hatte zwei Schichten: Bootstrap hing am Login-Aufruf und wurde vom Router-Redirect überholt; UND die Push-Registrierung hatte das users-Doc ohne `householdId` vorab angelegt, weshalb ein Existenz-Check nicht reichte. Fix: reaktiver Bootstrap + `householdId`-Maßstab + merge-Writes; selbstheilend für kaputte Accounts. Zusätzlich beseitigt: Riverpod-3-Warnung „markNeedsBuild during build" (BootstrapGate als Stack-Overlay statt Baum-Austausch + `checklistWarmupProvider` in `app.dart` gegen das Pausieren unbeobachteter Provider).
- **Google-Login: implementiert, deployt und auf Gerät getestet** (2026-07-23 implementiert, Gerätetest 2026-07-24 bestätigt; `firebase-einrichtung.md` Schritt 8; Debug-SHA-1/SHA-256 per `firebase apps:android:sha:create` hinterlegt — der SHA-Konsolen-Hinweis betrifft nur den nativen Flow und ist für unseren Browser-Flow irrelevant).
- **V3 Baustein 1 — Krankheits-Diagnose per Foto: komplett, deployt, vom Nutzer bestätigt** („sieht richtig gut aus"). Function `diagnosePlant` + „Pflanze untersuchen" im Pflanzen-Detail (Kamera/Galerie → Ergebnis-Sheet mit Ampel-Icon, Behandlung, Vorbeugung, KI-Disclaimer; `diagnosis_sheet.dart` gemeinsam genutzt). Nachbesserungen aus Live-Tests, alle bestätigt: Art-Angabe ist unbestätigter Hinweis (`matchesSpecies=false` → Warnkasten „Foto zeigt andere Pflanze"), kein HTML/Markdown im Text, konsequentes Duzen. **Untersuchungshistorie** unter `households/{id}/plants/{id}/diagnoses` (Rules: read/create alle im Haushalt, delete nur member, update nie), auf eigener Seite `/plants/:id/diagnoses`; Button „Frühere Untersuchungen" im Detail ist bei leerer Historie deaktiviert. **Historie-Seite auf Gerät getestet und bestätigt** (2026-07-24).
- **V3 Baustein 2 — Umtopf-Erinnerungen: implementiert und deployt** (2026-07-23, Commit `81d990d`). Dritter Aufgabentyp `repotting`, als Einziger opt-in (`repottingIntervalMonths` optional) und in Kalender-Monaten (31. → Monatsletzter; Logik gespiegelt in `due_tasks_provider` und `reminders.ts`). Formular mit Intervall-Feld + „Zuletzt umgetopft"-Datum, Checkliste + Push („Umtopfen: …"), Sitter dürfen bestätigen. Rules + `sendDailyReminders` sind deployt. **Gerätetest komplett bestätigt** (2026-08-19, inkl. Nachbesserung „Nächstes Fälligkeitsdatum"). Pflanzen-Detailseite zeigt bei Gießen/Düngen/Umtopfen zusätzlich zum letzten Datum das **nächste Fälligkeitsdatum** an (bzw. „heute fällig"). Berechnung dafür aus `due_tasks_provider.dart` public gemacht (`nextWateringDue`/`nextFertilizingDue`/`nextRepottingDue`) statt dupliziert. Neue l10n-Keys `nextWatering`/`nextFertilizing`/`nextRepotting`/`dueToday`. **V3 Baustein 2 damit vollständig abgeschlossen.**
- **V3 Baustein 3 — Stellplatz-Bewertung: implementiert, deployt und vom Nutzer bestätigt** („sieht gut aus", 2026-08-19). Functions `analyzeLocation`/`assessPlantFit` + Firestore-Rules live. Konzept mit dem Nutzer abgestimmt: Standort-Daten nur per Foto (keine Zusatzfragen zu Himmelsrichtung), Eignungs-Sterne ausschließlich auf Abruf (Kostenkontrolle, eigener Anthropic-Key). Zwei Functions `analyzeLocation` (Foto → Lichtkategorie + Freitext, gespeichert auf `PlantLocation`) und `assessPlantFit` (textbasiert, Pflegeprofil + Standort-Analyse → 1-5 Sterne + Begründung, gespeichert auf `Plant`). Neue Seite `LocationDetailScreen` (`/locations/:id`, erreichbar per Tap in der Stellplätze-Liste): Foto/Analyse-Bereich (nur Mitglieder) + Liste zugeordneter Pflanzen mit Sternen oder „Eignung prüfen"-Button (auch für Sitter, informativ). Pflanzen-Detail zeigt den Stellplatz jetzt kompakt mit Sternen und verlinkt dorthin. Veraltete Bewertungen (Pflanze verschoben oder Stellplatz neu analysiert) werden erkannt und als solche markiert statt falscher Sterne. Firestore-Rules erweitert (Sitter dürfen die vier Fit-Felder schreiben). Details/Begründung der Entscheidungen in `docs/architektur.md`. **V3 Baustein 3 damit vollständig abgeschlossen.**
- **Damit ist die komplette V3-Roadmap (Diagnose, Umtopf-Erinnerungen, Stellplatz-Bewertung) implementiert, deployt und gerätegetestet.**
- Testabdeckung: 18 Widget-Tests grün (u. a. Bootstrap-Heilung, Untersuchungshistorie bis ins Sheet, Umtopf-Flow Ende-zu-Ende inkl. Detailanzeige, Stellplatz-Bewertung inkl. Veraltet-Erkennung), Analyzer sauber, `tsc` kompiliert, Android-Debug-Build ok.
## Offen / Nächstes
### 1. App-Store-Vorbereitung (iOS/TestFlight abgeschlossen 2026-09-08; Android offen)
Ziel fürs Erste: **nur TestFlight + Play-Internal-Testing**, kein öffentlicher Store-Eintrag (mit Chris abgestimmt — spart Screenshots/Store-Texte/Altersfreigabe für später). Bundle-ID bleibt bewusst `dev.leafittome.app` (eigene Domain steht noch aus, ist aber kein Blocker für TestFlight/Internal-Testing).
**iOS / TestFlight: fertig (2026-09-08).** Erster Build `1.0.0 (1)` hochgeladen, in TestFlight „Bereit zum Testen", auf dem iPhone installiert und nutzbar. Dabei erledigt:
- `lib/main.dart`: App-Check-Provider per `kReleaseMode` umgeschaltet — Release nutzt `AndroidPlayIntegrityProvider` / `AppleAppAttestProvider`, Debug weiter Debug-Provider. Nötig, weil die Functions App Check erzwingen.
- App-Icon: `assets/icon/icon.png` (1024×1024, ohne Alpha) + `dart run flutter_launcher_icons` → iOS-/Android-Icon-Sätze generiert. (Tool-Macke v0.14.4: verhunzt `ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS` in `project.pbxproj` von `YES` auf `AppIcon` — danach zurücksetzen.)
- Datenschutzerklärung: Platzhalter in `docs/legal/datenschutz.html` ausgefüllt (Christopher Schläfke / cschlaefke@live.com / 8. September 2026), interner Hinweiskasten raus. Gehostet via **Firebase Hosting** (`hosting`-Block in `firebase.json`, `public: "docs/legal"`), URL **`https://leaf-it-to-me-app.web.app/datenschutz`** — in App Store Connect eingetragen. Deploy: `firebase deploy --only hosting`.
- App Store Connect: App-Eintrag + App-Datenschutz-Fragebogen ausgefüllt (E-Mail, Fotos, Nutzerinhalte, Nutzer-ID — alle verknüpft, kein Tracking).
- **Stolperfalle (teuer erkauft):** `flutter build ipa` über die CLI signiert headless nur mit „Apple Development" → App-Store-Upload lehnt mit „Invalid Signature / Code 90035" jede Framework-Datei ab. Fix: in Xcode → Settings → Accounts → Manage Certificates → „+" → **Apple Distribution** anlegen, dann **aus Xcode archivieren** (Product → Archive) statt CLI. Details in `firebase-einrichtung.md` Schritt 10.
- Die „Upload Symbols Failed / dSYM"-Meldungen für Firebase/gRPC/absl/Recaptcha beim Upload sind **harmlos** (vorkompilierte Frameworks ohne Debug-Symbole, nur Crash-Symbolik).
**Offene Kleinigkeit iOS:** Launch-Screen ist noch der weiße Flutter-Platzhalter (Warnung beim Build, kein Blocker) → Backlog.
**App Check: vollständig abgeschlossen** (2026-08-20). Play Integrity (Android) + App Attest (iOS) in der Firebase-Console registriert, Debug-Token hinterlegt, „Verified requests" für Cloud Functions auf beiden Plattformen bestätigt, `enforceAppCheck: true` auf allen 7 `onCall`-Functions deployt (Commit `49b7f44`) und per Gerätetest bestätigt (Pflanze anlegen/Diagnose funktionieren normal). **Hinweis für später:** Unverified Requests bei Firestore/Storage in der Console sind erwartet/harmlos — dafür wurde nie Enforcement eingerichtet, nur für die Functions. Ebenso die einmalige iOS-Kaltstart-Meldung „App not registered … exchangeDeviceCheckToken" — bekannte, folgenlose Plugin-Race, siehe `firebase-einrichtung.md` Schritt 9.
**Weiteres Code-seitig erledigt:**
- iOS: `ITSAppUsesNonExemptEncryption = false` in `Info.plist` (vermeidet wiederkehrende Export-Compliance-Nachfrage bei jedem TestFlight-Upload).
- Android: Release-Signing vorbereitet (`android/app/build.gradle.kts` liest `key.properties`, Vorlage `android/key.properties.example`; Datei + `.jks` sind in `.gitignore`). **Der Upload-Key selbst existiert noch nicht** — Chris muss ihn per `keytool` erzeugen (Befehl in `firebase-einrichtung.md` Schritt 11) und sofort sichern.
- `flutter_launcher_icons` vorbereitet (`pubspec.yaml`), zeigt auf `assets/icon/icon.png` — Datei existiert noch nicht.
- Entwurf Datenschutzerklärung: `docs/legal/datenschutz.html` (Platzhalter `[Name]`/`[E-Mail-Adresse]`/`[Datum]` müssen noch von Chris ausgefüllt werden). Hosting-Anleitung in `firebase-einrichtung.md` jetzt konkret auf Chris' Setup zugeschnitten: eigene Domain + Cloudflare (Subdomain per A-Record auf den Hetzner-VPS, SSL-Modus „Full (strict)" mit Cloudflare-Origin-Certificate, nginx-Server-Block-Vorlage).
- Nebenbei behoben: SPM-Versionskonflikt durch `firebase_app_check`-Einführung (inkonsistente Firebase-Paket-Minor-Versionen verhinderten den iOS-Build) — Commit `248f3be`.
- Neue Doku-Schritte in `firebase-einrichtung.md`: **Schritt 9** (App Check, jetzt komplett abgeschlossen), **Schritt 10** (TestFlight einrichten), **Schritt 11** (Play Internal Testing einrichten) — inkl. Textvorlagen für Apples App-Privacy-Fragebogen und Googles Data-Safety-Formular.
**Noch offen — nur noch Android (Play Internal Testing), dokumentiert in `firebase-einrichtung.md` Schritt 11:**
- Android-Upload-Key erzeugen (`keytool`, Schritt 11) und sofort sichern (Passwort-Manager + Backup außerhalb des Repos).
- `android/key.properties` lokal anlegen (Vorlage `android/key.properties.example`).
- Release-Fingerabdruck (SHA-1/SHA-256 des Upload-Keys) in Firebase hinterlegen (`firebase apps:android:sha:create`) — für App Check / Play Integrity im Release.
- `flutter build appbundle --release` → Play Console: App anlegen, Datenschutz-URL (`https://leaf-it-to-me-app.web.app/datenschutz`) + Data-Safety-Formular + Content-Rating, internen Test-Track + Tester.
- App-Icon ist bereits generiert (auch Android-Mipmaps), Datenschutzerklärung ist bereits gehostet — beides gilt für Android mit.
### 2. Backlog
Button „KI-Zweitmeinung" bei unbefriedigendem PlantNet-Ergebnis; Push-Anzeige auch bei App im Vordergrund; Englisch (`app_en.arb`); `firebase-functions`-Paket-Upgrade (Deploy-Warnung, mit Breaking Changes — bei Gelegenheit).
## Stolperfallen (teuer erkauft, nicht erneut zahlen)
- **Deploys und Produktions-Datenzugriffe blockiert der Auto-Mode-Berechtigungsfilter der Session.** Nicht dagegen anarbeiten: Kommando fertig vorbereiten und den Nutzer bitten, es per `! <kommando>` in der Eingabezeile auszuführen — macht er zuverlässig und prompt.
- Frisch **angelegte** Gen-2-Functions können beim ersten Aufruf HTTP 401 liefern (fehlende Cloud-Run-Invoker-Berechtigung) → dieselbe Function einfach erneut deployen. Updates bestehender Functions sind nicht betroffen.
- `firebase functions:log` hinkt **1520 Minuten** hinterher — „keine Läufe" heißt meist nur Log-Verzögerung. Scheduler-Status in der 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).
- Die **Push-Registrierung legt `users/{uid}` per merge selbst an** (fcmTokens/timezone). Jede Logik, die „Profil existiert" prüfen will, muss auf das Feld `householdId` schauen, nie auf die Dokument-Existenz.
- Riverpod 3 **pausiert Provider ohne aktive Zuhörer**; beim Einhängen von Screens mitten im Provider-Update gibt das „setState() or markNeedsBuild() called during build". Gegenmittel im Repo: Overlay statt Baum-Austausch (BootstrapGate) und dauerhaftes Warmhalten kritischer Ketten (`checklistWarmupProvider`).
- Storage-Rules mit `firestore.get()` brauchen die IAM-Rolle „Firebase Rules Firestore Service Agent" fürs Storage-Dienstkonto (wurde manuell erteilt).
- 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": nach `flutter clean` oder beim Bauen direkt aus Xcode einmal `flutter build ios --config-only` ausführen (SwiftPM-Paket wird bei `pub get` mit iOS 13 erzeugt).
- In `.arb`-Dateien UND in deutschen Texten innerhalb von TS-Strings (Function-Prompts!) **typografische Anführungszeichen „…“** verwenden — gerade `"` brechen das JSON bzw. den TS-String.
- Die Bash-Session wechselt das Arbeitsverzeichnis **nicht zurück**: nach `cd functions && npm run build` vor Flutter-Kommandos wieder explizit ins Projekt-Root.
- **iOS-App-Store-Upload braucht ein „Apple Distribution"-Zertifikat.** `flutter build ipa` über die CLI zieht es headless nicht und signiert nur mit „Apple Development" → Upload scheitert mit „Invalid Signature / Code 90035". Zuerst in Xcode → Settings → Accounts → Manage Certificates → „+" → Apple Distribution anlegen (`security find-identity -v -p codesigning` muss die Zeile zeigen), dann **aus Xcode archivieren** (Product → Archive), nicht per CLI.
- `flutter_launcher_icons` v0.14.4 schreibt beim Generieren `ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon` (statt `YES`) in `ios/Runner.xcodeproj/project.pbxproj` — danach `git checkout` auf die Datei, die generierten Icons bleiben.
- Nutzer wünscht zu jedem Backend-Baustein **begleitende deutsche Doku** in `docs/` (keine Firebase-Vorerfahrung).
## Arbeitsabläufe
- Prüfen vor jedem Commit: `flutter analyze && flutter test` (Tests mocken Firebase via `firebase_auth_mocks`/`fake_cloud_firestore`); bei TS-Änderungen zusätzlich `npm run build` in `functions/`; Android-Verifikation: `flutter build apk --debug`
- Texte: nur in `lib/l10n/app_de.arb`, danach `flutter gen-l10n`
- Deploy (führt der Nutzer aus, s. Stolperfallen): `firebase deploy --only functions|firestore|storage --project leaf-it-to-me-app`
- Nach jedem Arbeitsstand: committen + `git push` + dieses Handoff aktualisieren (deutsche Commit-Messages, Muster siehe Git-Log)
- Der Nutzer testet auf echten Geräten gegen das Produktions-Firebase und meldet präzise Beobachtungen; gezielte Diagnosefragen (AskUserQuestion) haben sich bewährt
## 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
- `claude-api` — vor Änderungen an den Claude-Aufrufen in `functions/src/` laden (Modell-IDs, Vision, Prompt-Konventionen)