leafittome/docs/handoff.md
cschlaefke 81d990dd7e V3: Umtopf-Erinnerungen (opt-in, Intervall in Monaten)
Dritter Aufgabentyp repotting im Intervall-Modell: optionales
repottingIntervalMonths + lastRepotted/By am Pflanzenprofil, Formular
mit Intervall-Feld und Datums-Auswahl, Umtopf-Zeile im Detail,
Aufgaben in Checkliste und Sammel-Push (reminders.ts gespiegelt),
Sitter dürfen bestätigen (Rules-Allowlist). Monats-Arithmetik kürzt
den 31. auf den Monatsletzten. Ende-zu-Ende-Widget-Test.

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

14 KiB
Raw Blame History

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), diagnosePlant (V3: Krankheits-Diagnose per Foto, Claude Vision), 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) + Anmelden mit Google (beide Plattformen Web-Flow, eingebauter GoogleAuthProvider) — Code in lib/features/auth/data/auth_repository.dart, Login-UI in .../presentation/login_screen.dart. Haushalts-Bootstrap (Profil + eigener Haushalt beim ersten Login) läuft reaktiv über householdBootstrapProvider + BootstrapGate (Maßstab: householdId im users-Doc).

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

🟡 V3 Baustein 1 — Krankheits-Diagnose per Foto: implementiert, wartet auf Deploy + Gerätetest

2026-07-23 implementiert:

  • Function diagnosePlant (functions/src/index.ts): Foto (+ optionale Art als speciesHint) → Claude beurteilt Gesundheitszustand → JSON {healthy, summary, details, treatment, prevention} auf Deutsch. Nutzt den bestehenden askClaude-Helper (Modell claude-sonnet-5); dessen max_tokens von 1024 auf 2048 erhöht (adaptives Denken zählt mit ins Limit).
  • App: PlantDiagnosisService (lib/features/plants/data/plant_diagnosis_service.dart, lazy Functions-Getter wie beim Household-Repo) + „Pflanze untersuchen"-Sektion im Pflanzen-Detail (_DiagnosisSection): Kamera/Galerie-Sheet → Spinner im Button → Ergebnis als scrollbares Bottom-Sheet (Befund mit Ampel-Icon, „Was du tun kannst", „So beugst du vor", KI-Disclaimer). Auch Sitter dürfen den Check nutzen (rein lesend). Fehler der Function (z. B. „keine Pflanze erkannt") werden als Snackbar durchgereicht.
  • Analyzer + alle 10 Tests grün, tsc kompiliert.

Stand nach erstem Gerätetest (2026-07-23): Deploy erfolgreich, Grundfunktion vom Nutzer bestätigt. Drei Nachbesserungen aus dem Test umgesetzt (Commit b665bae): (1) speciesHint ist jetzt eine unbestätigte Angabe — Claude prüft selbst, welche Pflanze zu sehen ist, beurteilt bei Abweichung die Pflanze im Bild und meldet matchesSpecies=false, worauf die App einen Warnhinweis im Ergebnis-Sheet zeigt; (2) Prompt verbietet HTML/Markdown (Nutzer hatte ein </br> im Text); (3) durchgängiges Duzen vorgeschrieben (war gemischt).

Feinschliff deployt und bestätigt („sieht sehr gut aus", 2026-07-23).

Erweiterung Untersuchungshistorie (2026-07-23): Jede Diagnose wird automatisch unter households/{id}/plants/{id}/diagnoses gespeichert (healthy, matchesSpecies, Texte, createdAt Server-Timestamp). Im Pflanzen-Detail unter dem Untersuchen-Button: Liste „Untersuchungen" (neueste zuerst, Ampel-Icon + Kurzbefund + Datum), Tipp öffnet dasselbe Ergebnis-Sheet inkl. „Untersucht am …". Volle Mitglieder können Einträge löschen (Bestätigungsdialog); Sitter dürfen speichern/lesen, Einträge sind nachträglich unveränderbar (Rules). Neuer Widget-Test navigiert bis ins Sheet — alle 11 Tests grün.

Rules deployt (2026-07-23). Auf Nutzerwunsch ausgelagert: Die Historie liegt jetzt auf einer eigenen Seite (/plants/:id/diagnoses, PlantDiagnosesScreen) statt inline im Detail; im Detail führt der Button „Frühere Untersuchungen" dorthin (deaktiviert/ausgegraut, solange keine Untersuchungen existieren). Das Ergebnis-Sheet ist in diagnosis_sheet.dart ausgelagert und wird von Detail (frische Untersuchung) und Historie-Seite gemeinsam genutzt. 12 Tests grün (neu: Button-deaktiviert-Test).

Noch zu tun (Nutzer): App neu bauen und die ausgelagerte Historie-Seite einmal durchklicken (Button → Liste → Eintrag → Sheet; Löschen).

🟡 V3 Baustein 2 — Umtopf-Erinnerungen: implementiert, wartet auf Deploys + Gerätetest

2026-07-23 implementiert: Dritter Aufgabentyp repotting im Intervall-Modell — als Einziger opt-in (repottingIntervalMonths optional am Pflanzenprofil, leer = keine Erinnerung) und in Kalender-Monaten. Formular: „Umtopf-Intervall (Monate)" + Datums-Auswahl „Zuletzt umgetopft" (erscheint nur bei gesetztem Intervall; Intervall leeren verwirft auch das Datum). Detail zeigt die Umtopf-Zeile nur bei gesetztem Intervall. Checkliste/Sammel-Push enthalten Umtopf-Aufgaben (reminders.ts gespiegelt, „Umtopfen: …" im Push-Text); Bestätigen schreibt lastRepotted/lastRepottedBy; Sitter dürfen bestätigen (Rules-Allowlist erweitert). Monats-Arithmetik kürzt den 31. auf den Monatsletzten. Analyzer, 13 Tests (neu: Ende-zu-Ende-Test Checkliste→Bestätigen), tsc, Debug-APK grün.

Noch zu tun (Nutzer, Deploys sind in der Session blockiert):

  1. firebase deploy --only firestore,functions:sendDailyReminders --project leaf-it-to-me-app
  2. Gerätetest: Pflanze bearbeiten → Intervall z. B. 12 Monate + „Zuletzt umgetopft" vor >12 Monaten setzen → Aufgabe „… umtopfen" in der Checkliste; Erledigt-Bestätigung; Sammel-Push müsste „Umtopfen: …" enthalten (nächster regulärer Push-Slot).

Danach V3 weiter: Stellplatz-Bewertung (Zwei-Ebenen-Konzept — Konzept vor Implementierung mit Nutzer abstimmen).

🟡 Google-Login: implementiert, wartet auf Konsolen-Schalter + Gerätetest

2026-07-23 implementiert (AuthRepository.signInWithGoogle via eingebautem GoogleAuthProvider, Google-Button im Login mit gemeinsamem Provider-Handler _signInWithProvider, l10n-Strings, iOS-URL-Scheme/Encoded App ID in Info.plist, Doku firebase-einrichtung.md Schritt 8). Analyzer + alle 10 Tests grün. Nutzer muss noch: In der Firebase-Konsole Authentication → Sign-in method → Google aktivieren (Support-E-Mail wählen — der einzige Konfigurationsschritt), dann auf iPhone und Android testen; der erste Google-Login muss kurz den Einrichtungs-Bildschirm zeigen (reaktiver Bootstrap deckt Google automatisch ab).

Apple-Login-Bug behoben und vom Nutzer bestätigt (2026-07-23)

Der Bug in Kurzform: Mit einem neuen Apple-Account schlug jedes Speichern fehl. Zwei Ursachen-Schichten:

  1. Der Haushalts-Bootstrap hing an signInWithApple — der Router leitet aber schon bei „angemeldet" weiter; scheiterte der Bootstrap danach, blieb der Account dauerhaft ohne Haushalt (Rules lehnen dann korrekt jeden Write ab).
  2. Die eigentliche Falle: Die Push-Registrierung (_saveToken, set(..., merge: true)) hatte das users-Dokument beim ersten Apple-Login längst angelegt (fcmTokens/timezone, aber ohne householdId). Eine Reparatur, die nur auf „users-Doc existiert" prüft, hielt den Account deshalb fälschlich für fertig.

Der Fix (alles committet und gepusht):

  • householdBootstrapProvider (lib/features/auth/data/household_bootstrap_provider.dart): reaktiver Bootstrap — legt Profil + Haushalt an, sobald „angemeldet, aber kein householdId im users-Doc". Maßstab ist überall householdId, nie die Dokument-Existenz. Schreibt per merge (Push-Felder bleiben erhalten). Deckt alle Login-Wege ab (auch künftig Google).
  • BootstrapGate (lib/features/auth/presentation/bootstrap_gate.dart, in app.dart eingehängt): legt bis dahin einen deckenden Warte-Bildschirm über die App (Stack, kein Austausch des App-Baums — ein Remount mitten im Provider-Update löste „markNeedsBuild during build" aus); Fehler werden mit „Erneut versuchen" sichtbar.
  • AuthRepository: ensureHouseholdBootstrap öffentlich, idempotent, In-Flight-Schutz; signInWithApple ruft ihn nicht mehr selbst auf. Selbstheilend für bereits kaputte Accounts.
  • Speicher-Fehler in plant_form_screen zeigen jetzt den Firebase-Fehlercode (plugin/code) statt einer Generik.
  • Vom Nutzer auf dem iPhone bestätigt: Anmeldung + Pflanze anlegen funktionieren mit dem Apple-Account.

Zuletzt erledigt

  • 2026-07-23 (2): Apple-Login-Bug diagnostiziert und in zwei Schritten behoben (reaktiver Bootstrap + householdId-Maßstab, siehe oben); Riverpod-„markNeedsBuild during build"-Warnung per Overlay-Gate beseitigt; Architektur-Doku um die Bootstrap-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: Button „KI-Zweitmeinung" bei unbefriedigendem PlantNet-Ergebnis; Push-Anzeige auch bei App im Vordergrund; App Check vor App-Store-Release (firebase-einrichtung.md Schritt 9); 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