leafittome/docs/handoff.md
cschlaefke 8d2b10b9ee Doku: App Check abgeschlossen, Hosting-Anleitung auf Cloudflare/Hetzner zugeschnitten
App Check ist jetzt vollständig live und gerätegetestet (Deploy 49b7f44,
Bestätigung durch Chris). Die Datenschutz-Hosting-Anleitung ersetzt den
vagen Platzhalter-Vorschlag durch Chris' konkretes Setup: eigene Domain
über Cloudflare vor dem Hetzner-VPS, inkl. Origin-Certificate und
nginx-Vorlage.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-20 19:27:19 +02:00

14 KiB
Raw Blame History

Handoff: LeafItToMe (Pflanzenpflege-App)

Stand: 2026-08-20 (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 (läuft, 2026-08-20 begonnen)

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).

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 (Chris' nächste Schritte, alle in firebase-einrichtung.md Schritt 1011 dokumentiert):

  • App-Icon: 1024×1024-Design liefern → unter assets/icon/icon.png ablegen → dart run flutter_launcher_icons.
  • Datenschutzerklärung: Platzhalter ausfüllen + über Cloudflare/Hetzner hosten (Anleitung s. o.).
  • Android-Upload-Key erzeugen (keytool, Schritt 11) und sichern.
  • App Store Connect: App-Eintrag anlegen, Privacy-URL + App-Privacy-Fragebogen ausfüllen, Build hochladen, interne TestFlight-Gruppe + Tester.
  • Play Console: App anlegen, Datenschutz-URL + Data-Safety-Formular + Content-Rating ausfüllen, internen Test-Track + Tester.

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.
  • 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)