From d80486b25e598dc881e266c8625c376bae8a7cbe Mon Sep 17 00:00:00 2001 From: Rijad Zuzo Date: Fri, 3 Jul 2026 12:44:15 +0200 Subject: [PATCH] Add maintainer onboarding documentation --- README.md | 6 ++ docs/AI_DIGEST.md | 132 ++++++++++++++++++++++++++++++++ docs/ARCHITECTURE.md | 123 +++++++++++++++++++++++++++++ docs/DATA_MODEL.md | 106 +++++++++++++++++++++++++ docs/FEATURE_MAP.md | 123 +++++++++++++++++++++++++++++ docs/IMPLEMENTATION_PLAYBOOK.md | 124 ++++++++++++++++++++++++++++++ docs/ONBOARDING.md | 92 ++++++++++++++++++++++ docs/README.md | 39 ++++++++++ docs/ROADMAP_AND_RISKS.md | 92 ++++++++++++++++++++++ docs/SETUP.md | 3 + docs/SHARE_INTAKE.md | 121 +++++++++++++++++++++++++++++ docs/STORAGE_AND_MIGRATIONS.md | 81 ++++++++++++++++++++ docs/SYNC_AND_BACKEND.md | 87 +++++++++++++++++++++ docs/TESTING_AND_DEBUGGING.md | 117 ++++++++++++++++++++++++++++ docs/open-tasks.md | 55 +++++++------ 15 files changed, 1273 insertions(+), 28 deletions(-) create mode 100644 docs/AI_DIGEST.md create mode 100644 docs/ARCHITECTURE.md create mode 100644 docs/DATA_MODEL.md create mode 100644 docs/FEATURE_MAP.md create mode 100644 docs/IMPLEMENTATION_PLAYBOOK.md create mode 100644 docs/ONBOARDING.md create mode 100644 docs/README.md create mode 100644 docs/ROADMAP_AND_RISKS.md create mode 100644 docs/SHARE_INTAKE.md create mode 100644 docs/STORAGE_AND_MIGRATIONS.md create mode 100644 docs/SYNC_AND_BACKEND.md create mode 100644 docs/TESTING_AND_DEBUGGING.md diff --git a/README.md b/README.md index 6124c2b..a908dde 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,12 @@ dart run build_runner build --delete-conflicting-outputs ## Docs +- [Documentation Index](docs/README.md) +- [Onboarding](docs/ONBOARDING.md) +- [Architecture](docs/ARCHITECTURE.md) +- [Data Model](docs/DATA_MODEL.md) +- [Feature Map](docs/FEATURE_MAP.md) +- [Implementation Playbook](docs/IMPLEMENTATION_PLAYBOOK.md) - [Setup](docs/SETUP.md) - [Progress Log](docs/progress.md) - [Open Tasks](docs/open-tasks.md) diff --git a/docs/AI_DIGEST.md b/docs/AI_DIGEST.md new file mode 100644 index 0000000..a0faa48 --- /dev/null +++ b/docs/AI_DIGEST.md @@ -0,0 +1,132 @@ +# AI Digest + +The AI digest is a private, review-first recommendation flow. It should help the +user maintain relationships without leaking identifying relationship data. + +## Main Files + +Core LLM: + +- `lib/core/llm/llm_config.dart` +- `lib/core/llm/llm_service.dart` +- `lib/core/llm/llm_diagnostics_log.dart` +- `lib/core/llm/ollama/` + +Digest: + +- `lib/features/ai_digest/application/anonymized_llm_context_builder.dart` +- `lib/features/ai_digest/application/llm_digest_orchestrator.dart` +- `lib/features/ai_digest/application/ai_digest_response_parser.dart` +- `lib/features/ai_digest/application/llm_digest_background_scheduler.dart` +- `lib/features/ai_digest/application/llm_digest_environment.dart` +- `lib/features/ai_digest/data/llm_digest_config.dart` +- `lib/features/ai_digest/data/llm_digest_run_store.dart` +- `lib/features/ai_digest/presentation/ai_digest_review_view.dart` +- `lib/app/data/relationship_repository_ai_digest.dart` + +## Provider Support + +`LlmService` supports: + +- OpenAI +- Anthropic +- Google AI +- Ollama +- OpenAI-compatible providers + +Settings owns provider setup, model listing, diagnostics, and manual test +actions. + +## Privacy Model + +Scheduled digest prompts use pseudonymous person tokens: + +```json +{ + "personToken": "person_001", + "relationshipCategory": "friend", + "interests": ["coffee"], + "capturedFacts": [...] +} +``` + +Do not include: + +- real names +- aliases +- sender names +- source user IDs +- source thread IDs +- raw shared message text +- sensitive facts +- unreviewed facts +- source URLs from private shares + +Allowed context: + +- safe category labels +- sanitized fact summaries +- preference signal labels +- relative windows such as "about 3 months ago" +- `shared_message_datetime` when it is already stored as timestamp evidence + +## Digest Lifecycle + +```text +manual or scheduled trigger + -> LlmDigestOrchestrator + -> load config and run policy + -> build anonymized context + -> call LlmService.completeText + -> parse JSON suggestions + -> save AiSuggestionDrafts + -> notify user + -> user reviews, accepts, or dismisses +``` + +Suggestions never directly mutate people, reminders, or ideas without review. + +## Temporal Context + +Shared message timestamp handling matters. If someone wrote "I am sick and +ginger tea helped" four months ago, the correct durable outcome is probably a +ginger tea preference, not a current health check-in. + +Relevant fields: + +- `SharedPayload.sharedMessageDateTime` +- `SharedMessageEntry.sharedMessageDateTime` +- digest `capturedFacts[].shared_message_datetime` +- digest `capturedFacts[].sourceAge` +- digest `preferenceSignals[].lastObserved` + +Prompts explicitly instruct models to avoid current check-ins from old +temporary health/status evidence. + +## Web Grounding + +Digest config can enable web grounding for current events. Grounded suggestions +must include source URLs and should prefer official event or venue pages. + +When web grounding is disabled, the model should avoid claims about current +availability, prices, or event schedules unless the local input already contains +concrete details. + +## Diagnostics + +Settings exposes LLM diagnostics. Diagnostics should record metadata and lengths, +not secrets. Do not log API keys or raw private relationship context beyond what +the existing diagnostics intentionally capture. + +## Tests + +Useful focused tests: + +```bash +flutter test test/core/llm +flutter test test/features/ai_digest +``` + +When changing prompt payloads, update +`test/features/ai_digest/anonymized_llm_context_builder_test.dart` so privacy +constraints are protected by tests. diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..e8c6aed --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,123 @@ +# Architecture + +## High-Level Shape + +The app is mid-migration to vertical slices. + +```text +lib/ + main.dart + app/ app bootstrap, shell, aggregate repository/state + core/ cross-cutting config, auth, networking, LLM, theme + features/ product slices + integrations/ backend gateway contracts and transport models +``` + +Canonical feature structure: + +```text +lib/features// + domain/ models and pure rules + application/ orchestration, providers, side effects + presentation/ screens, widgets, dialogs, UI state + data/ slice-owned persistence or adapters, when needed +``` + +Many files at `lib/features//.dart` are compatibility exports. +New implementation should go into the canonical subfolders. + +## Runtime Flow + +1. `lib/main.dart` initializes Flutter, Sentry/Bugsink, Workmanager, local + notifications, Hive, and Riverpod. +2. `RelationshipSaverApp` wires auth gates, share listeners, notification + listeners, and global app listeners. +3. `AppShell` owns responsive navigation between Dashboard, People, Moments, + Ideas, Reminders, Signals, Sync, and Settings. +4. `LocalRepository` exposes the aggregate `LocalDataState`. +5. Feature views read state through Riverpod providers and call repository + operations. +6. Repository writes persist state, reconcile reminder schedules, and enqueue + sync envelopes where applicable. + +## State And Persistence Boundary + +`LocalDataState` is the primary local aggregate. It contains: + +- people +- moments +- ideas +- reminders +- dashboard tasks +- source links +- shared messages +- share inbox +- person facts +- important dates +- preference signals +- AI suggestion drafts + +Most product writes should pass through `LocalRepository` because it handles: + +- local state update +- persistent storage write +- reminder schedule reconciliation +- sync queue enqueue +- derived side effects such as preference extraction + +The sync queue is intentionally separate from the product aggregate and lives in +`SyncQueueRepository`. + +## Important Providers + +| Provider | Purpose | +| --- | --- | +| `localRepositoryProvider` | primary local product state and write boundary | +| `syncQueueRepositoryProvider` | pending outbound sync mutations and sync status | +| `backendGatewayProvider` | fake vs REST backend transport selection | +| `sessionControllerProvider` | fake/REST auth session lifecycle | +| `llmConfigProvider` | LLM provider, endpoint, model, and API key state | +| `llmDigestConfigProvider` | digest cadence and grounding policy | +| `reminderSchedulerProvider` | local notification scheduler abstraction | + +## Design Decisions + +### Local-first aggregate first + +The app persists one aggregate snapshot for product state. This is simple, +offline-safe, and pragmatic for a prototype. It also means entity-level +migrations and partial writes need care. + +### Feature-owned models + +Even though storage is aggregate-based, models live with features. This keeps +the migration toward vertical slices open without forcing a storage rewrite. + +### Fake backend by default + +Fake backend mode must stay stable because the product is not yet backend +dependent. REST transport exists behind `BackendGatewayRest`. + +### Inbox-first share capture + +Share intake should prefer user review when identity evidence is weak. A wrong +automatic profile match is more damaging than an extra inbox step. + +### Pseudonymous AI digest + +Scheduled digest prompts use person tokens and safe summaries. Identifying +details stay local. Share timestamps can be included as temporal evidence, but +raw shared text and sender identity should not be sent. + +### Local notifications as best effort + +Reminder and digest notifications should not block local data writes. Scheduler +errors are intentionally swallowed at the repository boundary. + +## Cross-Cutting Rules + +- Keep `AppConfig` flags as build-time `--dart-define` friendly values. +- Add tests around parsing, local repository state changes, and prompt payload + shape when changing sensitive flows. +- Keep docs aligned with verified code, especially native share and background + behavior. diff --git a/docs/DATA_MODEL.md b/docs/DATA_MODEL.md new file mode 100644 index 0000000..cdb026f --- /dev/null +++ b/docs/DATA_MODEL.md @@ -0,0 +1,106 @@ +# Data Model + +The app stores most relationship data in `LocalDataState`, persisted by +`LocalRepository`. + +## Aggregate State + +Source: `lib/app/state/local_data_state.dart` + +| Field | Type | Meaning | +| --- | --- | --- | +| `people` | `List` | primary relationship profiles | +| `moments` | `List` | interaction and capture history | +| `ideas` | `List` | gift, activity, and follow-up ideas | +| `reminders` | `List` | local reminder rules | +| `tasks` | `List` | dashboard task surface | +| `dismissedSignalIds` | `List` | dismissed generated signal IDs | +| `sourceLinks` | `List` | external sender/thread to profile mapping | +| `sharedMessages` | `List` | imported shares attached to profiles | +| `sharedInbox` | `List` | unresolved or ambiguous shares | +| `personFacts` | `List` | structured relationship facts | +| `importantDates` | `List` | birthdays, anniversaries, events | +| `preferenceSignals` | `List` | inferred/confirmed likes/dislikes | +| `aiSuggestionDrafts` | `List` | pending AI review items | + +## Profile Model + +Source: `lib/features/people/domain/person_models.dart` + +`PersonProfile` is the stable profile root. Important fields: + +- `id`: internal local ID +- `name`, `aliases`: local identity only +- `relationship`: user-facing relationship category +- `affinityScore`: rough closeness score +- `nextMoment`: next planned contact or relationship moment +- `tags`, `notes`, `location`: user-authored context +- `lastUpdatedAt`, `lastInteractedAt`: operational recency fields + +`SourceProfileLink` maps an external source to a local profile. It is used by +share intake to avoid asking again after the user resolves a source identity. + +## Share Models + +Source: `lib/features/share_intake/domain/share_models.dart` + +`SharedPayload` is normalized input from share sheets, action extensions, or +simulation tools. + +Key timestamp fields: + +- `createdAt`: when the app created the normalized payload +- `receivedAt`: when the app received/imported the share +- `sharedMessageDateTime`: best-effort original message timestamp extracted + from shared text, or null + +Use `sharedMessageDateTime` as temporal evidence. Do not assume it exists. Do +not infer it from arbitrary content dates such as "birthday is May 20". + +`CapturedFactDraft` is the review/edit draft before a share becomes a durable +fact or important date. + +`SharedMessageEntry` is the durable record attached to a person. Its `sharedAt` +getter prefers `sharedMessageDateTime` when present. + +`SharedInboxEntry` is unresolved intake. Reasons include missing identity, +ambiguous profile match, near-profile conflict, and manual selection needs. + +## Facts, Dates, And Signals + +`PersonFact` is durable structured context: + +- `type`: note, like, dislike, importantDate, giftIdea, placeIdea, + activityIdea, misc +- `sourceKind`: manual, shareSheet, or shareInbox +- `sharedMessageId` or `inboxEntryId`: provenance +- `needsReview` and `isSensitive`: prompt and UI filtering controls + +`PersonImportantDate` is a dated profile-owned record. It can come from manual +input, share review, or AI-assisted extraction. + +`PersonPreferenceSignal` is inferred or confirmed preference evidence. It +tracks first/last seen timestamps, occurrence count, source apps, message IDs, +and evidence snippets. + +## AI Suggestion Drafts + +Source: `lib/features/ai_digest/domain/ai_digest_models.dart` + +AI suggestions are saved as drafts first. Accepted drafts can become local +ideas, reminders, or tasks. Dismissed drafts stay in history so future digest +prompts can avoid repeats. + +## Schema Versioning + +`LocalRepository` owns `_schemaVersion`. When adding persisted fields: + +1. Add nullable/defaulted fields where possible. +2. Update `toJson`, `fromJson`, and `copyWith`. +3. Increase `_schemaVersion` only when a migration has meaning beyond default + compatible parsing. +4. Add repository and serialization tests. +5. Verify legacy/missing field behavior. + +The current `fromJson` patterns are intentionally tolerant for many optional +lists and fields. Keep that property. diff --git a/docs/FEATURE_MAP.md b/docs/FEATURE_MAP.md new file mode 100644 index 0000000..a325895 --- /dev/null +++ b/docs/FEATURE_MAP.md @@ -0,0 +1,123 @@ +# Feature Map + +This file maps product areas to code ownership and maintenance notes. + +## App Shell + +Canonical files: + +- `lib/main.dart` +- `lib/app/relationship_saver_app.dart` +- `lib/app/presentation/app_shell.dart` +- `lib/app/navigation/app_destination.dart` + +Responsibilities: + +- app initialization +- auth gate +- global listeners for share intake, notifications, and digest behavior +- responsive navigation + +## People + +Canonical files: + +- `lib/features/people/domain/person_models.dart` +- `lib/features/people/presentation/people_view.dart` +- `lib/features/people/presentation/people_view_detail.dart` +- `lib/features/people/presentation/people_view_dialogs.dart` +- `lib/features/people/presentation/people_view_list.dart` +- `lib/app/data/relationship_repository_people.dart` + +Use this slice for profile CRUD, duplicate merge, facts/dates display, and +profile-centered history. + +## Share Intake + +Canonical files: + +- `lib/features/share_intake/domain/share_models.dart` +- `lib/features/share_intake/domain/share_payload_parser.dart` +- `lib/features/share_intake/domain/shared_message_datetime_extractor.dart` +- `lib/features/share_intake/presentation/incoming_share_listener.dart` +- `lib/features/share_intake/presentation/share_capture_review_sheet.dart` +- `lib/features/share_intake/presentation/share_inbox_view.dart` +- `lib/app/data/relationship_repository_share.dart` +- `ios/Share Extension/` +- `ios/Save Action/` + +Use this slice for incoming shared text, URLs, chat snippets, inbox triage, +source matching, and structured capture drafts. + +## AI Digest + +Canonical files: + +- `lib/core/llm/` +- `lib/features/ai_digest/application/` +- `lib/features/ai_digest/data/` +- `lib/features/ai_digest/domain/ai_digest_models.dart` +- `lib/features/ai_digest/presentation/ai_digest_review_view.dart` +- `lib/app/data/relationship_repository_ai_digest.dart` + +Use this slice for private weekly suggestions, LLM setup, prompt payload +construction, response parsing, diagnostics, and review workflows. + +## Dashboard And Signals + +Canonical files: + +- `lib/features/dashboard/domain/dashboard_models.dart` +- `lib/features/dashboard/presentation/dashboard_view.dart` +- `lib/features/signals/domain/signals_feed_synthesizer.dart` +- `lib/features/signals/presentation/signals_view.dart` + +Dashboard is a dense overview surface. Signals are derived local prompts and +should not become another persistence source unless there is a clear product +need. + +## Moments, Ideas, Reminders + +Canonical files: + +- `lib/features/moments/domain/moment_models.dart` +- `lib/features/ideas/domain/idea_models.dart` +- `lib/features/reminders/domain/reminder_models.dart` +- `lib/features/reminders/application/reminder_scheduler*.dart` +- `lib/app/data/relationship_repository_activity.dart` + +Reminders are reconciled after local state writes. Scheduler failures should not +block local data persistence. + +## Sync + +Canonical files: + +- `lib/features/sync/data/sync_queue_repository.dart` +- `lib/features/sync/application/sync_coordinator.dart` +- `lib/features/sync/application/sync_auto_trigger_controller.dart` +- `lib/features/sync/application/sync_background_runner.dart` +- `lib/features/sync/presentation/sync_view.dart` +- `lib/integrations/backend/` + +Sync is scaffolding, diagnostics, and repair flow. Do not assume every local +entity maps to the backend contract yet. + +## Settings + +Canonical files: + +- `lib/features/settings/presentation/settings_view.dart` + +Settings is the main debug and configuration surface. It includes: + +- gateway flags +- LLM configuration +- digest toggles +- manual digest run +- AI review entry +- notification permission action +- share simulation +- share diagnostics + +Keep debug tools practical and explicit. Avoid hidden state changes. diff --git a/docs/IMPLEMENTATION_PLAYBOOK.md b/docs/IMPLEMENTATION_PLAYBOOK.md new file mode 100644 index 0000000..35e7b85 --- /dev/null +++ b/docs/IMPLEMENTATION_PLAYBOOK.md @@ -0,0 +1,124 @@ +# Implementation Playbook + +This guide shows how to add features without breaking the current architecture. + +## General Workflow + +1. Read the slice README. +2. Identify whether the behavior is domain, application, presentation, or + repository state. +3. Add or change the feature-owned model. +4. Update `LocalDataState` only if a new persisted collection is needed. +5. Route all writes through `LocalRepository`. +6. Add focused tests. +7. Update docs when behavior changes across slices. + +## Example Feature: Add A New Captured Fact Type + +Suppose you want shares and manual capture to support a new fact type: +`communicationStyle`. + +### Files To Change + +Domain: + +- `lib/features/share_intake/domain/share_models.dart` + - add enum value to `CapturedFactType` +- `lib/features/people/domain/person_models.dart` + - ensure any switch over `CapturedFactType` handles the new value + +Extraction and suggestions: + +- `lib/features/share_intake/domain/share_capture_draft_suggester.dart` +- `lib/core/llm/llm_service.dart` +- `lib/core/llm/captured_fact_draft_parser.dart` + +Persistence: + +- `lib/app/data/relationship_repository_share.dart` + - ensure `_applyFactDraftToFacts` stores the new type correctly +- `lib/app/state/local_data_state.dart` + - usually no change needed because facts already store `CapturedFactType` + +UI: + +- `lib/features/share_intake/presentation/share_capture_review_sheet.dart` +- `lib/features/share_intake/presentation/share_inbox_view.dart` +- `lib/features/people/presentation/people_view_detail.dart` + +AI digest: + +- `lib/features/ai_digest/application/anonymized_llm_context_builder.dart` + - update `_factTypeLabel` + +Tests: + +- `test/features/share_intake/share_capture_draft_suggester_test.dart` +- `test/core/llm/captured_fact_draft_parser_test.dart` +- `test/features/local/local_repository_test.dart` +- `test/features/ai_digest/anonymized_llm_context_builder_test.dart` + +### Rules + +- Add enum values at the end unless there is a strong reason to reorder. +- Keep `fromJson` fallback behavior. +- Add tests for unknown or missing values when changing parsing behavior. +- Do not make LLM output the only way to create the new type. Provide a local + fallback or review UI path. + +## Example Feature: Add A New Share Source Parser + +Suppose you want better Telegram parsing. + +Files: + +- Add `lib/features/share_intake/domain/telegram_share_parser.dart`. +- Update `SharePayloadParser._normalizeSourceApp`. +- Update `SharePayloadParser._parseSourceMetadata`. +- Add tests in `test/features/share_intake/`. + +Parser expectations: + +- Return message text without source prefix noise. +- Extract display name only when the pattern is strong. +- Extract stable source IDs only when they are provided by the source. +- Leave unknown timestamps null. +- Never throw on malformed input. + +Repository expectations: + +- Exact identity match can auto-link only when unambiguous. +- Near matches should route to Share Inbox. +- Source profile links should be stable across follow-up shares. + +## Example Feature: Sync A New Entity + +Suppose AI suggestion drafts need REST sync. + +Files: + +- `docs/api/openapi.yaml` +- `lib/integrations/backend/models/backend_models.dart` +- generated backend model files +- `lib/integrations/backend/backend_gateway_rest.dart` +- `lib/integrations/backend/backend_gateway_fake.dart` +- `lib/integrations/backend/sync_envelope_validator.dart` +- `lib/app/data/relationship_repository_ai_digest.dart` +- tests under `test/integrations/backend` and `test/features/sync` + +Design choices to document: + +- entity type string in `ChangeEnvelope` +- create/update/delete semantics +- conflict behavior +- whether rejected changes are user-repairable +- privacy implications + +## Review Checklist Before Shipping + +- `flutter analyze` +- focused tests for touched slices +- full `flutter test` when practical +- manual Settings smoke test for config/debug actions +- manual share simulation test after share changes +- physical-device test after native share, notification, or background changes diff --git a/docs/ONBOARDING.md b/docs/ONBOARDING.md new file mode 100644 index 0000000..eede116 --- /dev/null +++ b/docs/ONBOARDING.md @@ -0,0 +1,92 @@ +# Onboarding + +This app is a local-first Flutter prototype for capturing relationship context: +people, moments, reminders, ideas, shared messages, preference signals, and +private AI-assisted suggestions. + +The most important product constraint is trust. The app should preserve +relationship details locally by default, avoid accidental profile matching, and +send only pseudonymous, reviewed context to LLM flows. + +## First Hour + +1. Read this file, then [Architecture](ARCHITECTURE.md), [Data Model](DATA_MODEL.md), + and [Feature Map](FEATURE_MAP.md). +2. Run `flutter pub get`. +3. Run `flutter analyze`. +4. Run focused tests before full tests: + - `flutter test test/features/share_intake` + - `flutter test test/features/ai_digest` + - `flutter test test/features/local` +5. Run the app with the fake backend: + +```bash +flutter run --dart-define=USE_FAKE_BACKEND=true +``` + +## Project Posture + +The app is not a fully server-backed production product yet. It is a +prototype-friendly, local-first app with REST/OpenAPI scaffolding for later +backend work. Fake backend mode is the default development path and should remain +safe offline. + +Current product strengths: + +- people profiles with aliases, notes, tags, facts, dates, and preference + signals +- manual capture for moments, ideas, reminders, and dashboard tasks +- share intake with inbox-first review for ambiguous content +- local notifications for reminders +- private weekly AI digest with pseudonymous prompt payloads +- fake backend auth/sync plus REST gateway boundary + +Current constraints: + +- data is mostly stored as one local aggregate snapshot +- many backend mappings are incomplete for newer local entities +- iOS share support has native targets, but real device behavior still needs + continued verification +- tests are good for parsers/repositories/widgets, but native share and + background scheduling need manual device testing + +## Daily Development Commands + +```bash +flutter pub get +flutter analyze +flutter test +dart run build_runner build --delete-conflicting-outputs +``` + +Useful run modes: + +```bash +flutter run --dart-define=USE_FAKE_BACKEND=true +flutter run --dart-define=USE_FAKE_BACKEND=false --dart-define=BACKEND_BASE_URL=https://your-api +flutter run --dart-define=ENABLE_BACKGROUND_SYNC=false +flutter run --dart-define=ENABLE_LOCAL_NOTIFICATIONS=true +``` + +## Where To Start For Common Work + +| Task | Start Here | +| --- | --- | +| Add or change persisted relationship data | `lib/app/state/local_data_state.dart`, model file in `lib/features/*/domain/`, repository part in `lib/app/data/` | +| Change profile UI | `lib/features/people/presentation/` | +| Change share capture | `lib/features/share_intake/`, `lib/app/data/relationship_repository_share.dart`, native files under `ios/` or Android manifest | +| Change AI suggestions | `lib/features/ai_digest/`, `lib/core/llm/` | +| Change reminder behavior | `lib/features/reminders/`, `lib/app/data/relationship_repository.dart` schedule reconciliation | +| Change backend sync | `lib/features/sync/`, `lib/integrations/backend/`, `docs/api/openapi.yaml` | +| Add settings/debug tools | `lib/features/settings/presentation/settings_view.dart` | + +## Non-Negotiable Maintainer Rules + +- Prefer canonical slice files under `presentation/`, `application/`, `domain/`, + and `data/`. +- Do not expand compatibility exports unless preserving old imports is the task. +- Keep local-first behavior working in fake backend mode. +- Treat ambiguous shared identities as inbox items, not auto-linked profiles. +- Never include names, aliases, sender names, source URLs, or raw shared text in + scheduled digest prompts. +- Update the nearest doc when changing architecture-level behavior. diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..c0d279a --- /dev/null +++ b/docs/README.md @@ -0,0 +1,39 @@ +# Documentation Index + +Start here when taking over the app: + +- [Onboarding](ONBOARDING.md): first-day setup, mental model, and where to look. +- [Architecture](ARCHITECTURE.md): module boundaries, state flow, and design + decisions. +- [Data Model](DATA_MODEL.md): persisted local state and important domain + models. +- [Feature Map](FEATURE_MAP.md): product slices, canonical files, and common + extension points. +- [Share Intake](SHARE_INTAKE.md): iOS/Android share entry, parsing, inbox, and + timestamp handling. +- [AI Digest](AI_DIGEST.md): LLM providers, privacy model, prompts, digest + lifecycle, and review workflow. +- [Storage And Migrations](STORAGE_AND_MIGRATIONS.md): Hive, legacy shared + preferences, schema versioning, and migration rules. +- [Sync And Backend](SYNC_AND_BACKEND.md): fake/REST gateway boundary, sync + queue, OpenAPI contract, and repair flow. +- [Implementation Playbook](IMPLEMENTATION_PLAYBOOK.md): example feature + implementation and files to update. +- [Testing And Debugging](TESTING_AND_DEBUGGING.md): commands, fixtures, + diagnostics, and known test risks. +- [Roadmap And Risks](ROADMAP_AND_RISKS.md): current gaps, priorities, and + traps for maintainers. + +Historical planning docs are still useful for context, but treat the files above +as the current takeover documentation. + +Existing historical docs: + +- [Progress Log](progress.md) +- [Open Tasks](open-tasks.md) +- [Client Audit](client-audit-relationship-app.md) +- [Client Implementation Plan](client-implementation-plan.md) +- [Client Iteration Summary](client-iteration-summary.md) +- [Backend REST/OpenAPI ADR](ADR/0002-backend-protocol-rest-openapi.md) +- [Backendless Local LLM ADR](ADR/0003-backendless-local-llm-pipeline.md) +- [OpenAPI Contract](api/openapi.yaml) diff --git a/docs/ROADMAP_AND_RISKS.md b/docs/ROADMAP_AND_RISKS.md new file mode 100644 index 0000000..b4069aa --- /dev/null +++ b/docs/ROADMAP_AND_RISKS.md @@ -0,0 +1,92 @@ +# Roadmap And Risks + +## Near-Term Priorities + +1. Keep share intake reliable and conservative. +2. Improve structured fact/date review UX. +3. Stabilize private AI digest behavior with strong privacy tests. +4. Verify iOS share/action extensions on physical devices. +5. Decide which new local entities must sync to a backend. +6. Reduce large UI files after behavior stabilizes. + +## Product Risks + +### Wrong profile matching + +Risk: a shared message gets attached to the wrong person. + +Mitigation: + +- prefer Share Inbox for ambiguous evidence +- keep source links stable after user confirmation +- test duplicate and near-match behavior + +### Over-eager AI check-ins + +Risk: old temporary status messages produce awkward current reminders. + +Mitigation: + +- preserve `sharedMessageDateTime` +- include source age in digest context +- instruct prompts to treat old health/status as historical evidence +- prefer durable preferences when evidence is old + +### Privacy leakage + +Risk: raw names, sender details, or private text reaches scheduled digest +prompts. + +Mitigation: + +- keep prompt builder tests strict +- sanitize identity terms +- exclude sensitive and unreviewed facts +- keep AI results in review drafts first + +### Local storage migration mistakes + +Risk: changing aggregate JSON breaks existing installs. + +Mitigation: + +- default nullable fields +- tolerant `fromJson` +- focused migration tests +- avoid destructive recovery paths + +### Native platform drift + +Risk: iOS/Android share, notifications, or background tasks compile but fail on +real devices. + +Mitigation: + +- physical device test for native features +- keep setup docs current +- verify Podfile and Xcode project after plugin upgrades + +## Technical Debt + +- Aggregate storage is simple but will eventually limit multi-device sync and + conflict handling. +- `PeopleView`, `DashboardView`, and Settings are large and should be split when + behavior changes require deeper edits. +- Compatibility exports should remain stable but should not receive new logic. +- Backend protocol does not yet cover every local-first entity. +- Some full-suite widget tests are layout-sensitive. + +## Decision Backlog + +- Which entities are first-class backend resources? +- Should AI suggestion drafts sync across devices or remain device-local? +- Should source-message timestamp extraction support locale settings explicitly? +- Should share inbox support batch triage? +- Should preference signals be promotable to profile tags? +- Should the graph explorer become its own feature slice? + +## Maintenance Rule + +When a roadmap item is completed or invalidated, update this file and the +nearest feature doc. The old progress log is useful history, but this file +should describe the current maintainer view. diff --git a/docs/SETUP.md b/docs/SETUP.md index 1fb2a90..c08110f 100644 --- a/docs/SETUP.md +++ b/docs/SETUP.md @@ -1,5 +1,8 @@ # Setup +For a full handoff path, start with [Onboarding](ONBOARDING.md), then use this +file for concrete runtime flags and platform setup notes. + ## Backend Base URL Build-time (recommended): diff --git a/docs/SHARE_INTAKE.md b/docs/SHARE_INTAKE.md new file mode 100644 index 0000000..798d4fe --- /dev/null +++ b/docs/SHARE_INTAKE.md @@ -0,0 +1,121 @@ +# Share Intake + +Share intake is the app's highest-friction capture path. It should be reliable, +reviewable, and conservative about identity matching. + +## Entry Points + +Flutter listener: + +- `lib/features/share_intake/presentation/incoming_share_listener.dart` + +Native iOS: + +- `ios/Share Extension/ShareViewController.swift` +- `ios/Save Action/SaveActionViewController.swift` +- `ios/Share Extension/Info.plist` +- `ios/Save Action/Info.plist` +- `ios/Runner.xcodeproj/project.pbxproj` + +Android: + +- `android/app/src/main/AndroidManifest.xml` +- `receive_sharing_intent` plugin integration + +Developer simulation: + +- Settings -> `Simulate Share Capture` + +## Pipeline + +```text +native share / simulation + -> IncomingShareListener + -> SharePayloadParser + -> startShareCaptureReview + -> capture to existing profile, create profile, or save to inbox + -> LocalRepositoryShareOperations + -> LocalDataState + sync queue + optional facts/dates/signals +``` + +## Payload Normalization + +`SharePayloadParser` normalizes: + +- source app labels +- text vs URL vs text-with-URL payloads +- WhatsApp sender prefixes +- Signal sender prefixes +- source-message timestamp evidence + +`SharedPayload` keeps both app-side timestamps and source-message timestamps: + +- `createdAt`: normalized payload creation time +- `receivedAt`: app receive/import time +- `sharedMessageDateTime`: best-effort original message time, nullable + +## Source Message Timestamp Extraction + +Source: `lib/features/share_intake/domain/shared_message_datetime_extractor.dart` + +The extractor tries multiple metadata-like patterns: + +- bracketed chat prefixes such as `[19/05/2026, 14:30] Sender: text` +- exported chat prefixes such as `5/19/26, 2:30 PM - Sender: text` +- labelled lines such as `Received at: 19 May 2026 at 14:30` +- ISO-like timestamps +- `today at HH:mm` and `yesterday at HH:mm` +- numeric and month-name date variants + +It returns `null` instead of throwing. It intentionally avoids arbitrary content +dates, for example `birthday is May 20`, because those are not message receive +times. + +## Identity Matching + +Matching order: + +1. Existing `SourceProfileLink` by source fingerprint/user/thread metadata. +2. Exact normalized display name when exactly one profile matches. +3. Near-name conflict detection. +4. Inbox when missing, ambiguous, or risky. + +Do not auto-link when evidence is weak. The inbox is the safety valve. + +## Inbox Resolution + +Share Inbox supports: + +- resolving to an existing person +- creating a person +- editing structured draft type/text/date +- reviewing conflict candidates +- saving provenance for facts and source links + +Relevant files: + +- `lib/features/share_intake/presentation/share_inbox_view.dart` +- `lib/features/share_intake/presentation/share_capture_review_sheet.dart` +- `lib/app/data/relationship_repository_share.dart` + +## Native iOS Notes + +The repository currently contains Share Extension and Save Action target entries +in the Xcode project. The iOS Podfile still only has the `Runner` target, so +verify extension build behavior after plugin updates or Pod changes. + +Physical iPhone testing is required for real chat apps. Simulators are useful +for Notes/Safari-style text and URL sharing, but not enough for WhatsApp, +iMessage, or app-specific metadata behavior. + +## Tests + +Useful focused tests: + +```bash +flutter test test/features/share_intake +flutter test test/features/local/local_repository_test.dart +``` + +Add parser tests for every new source format. Add repository tests when matching, +inbox, source links, facts, or signals change. diff --git a/docs/STORAGE_AND_MIGRATIONS.md b/docs/STORAGE_AND_MIGRATIONS.md new file mode 100644 index 0000000..3114928 --- /dev/null +++ b/docs/STORAGE_AND_MIGRATIONS.md @@ -0,0 +1,81 @@ +# Storage And Migrations + +## Local Product Store + +The product store is abstracted by `LocalDataStore`. + +Implementations: + +- `HiveLocalDataStore` +- `SharedPrefsLocalDataStore` +- `InMemoryLocalDataStore` + +Provider: + +- `lib/app/data/storage/local_data_store_provider.dart` + +Default: + +- `USE_HIVE_LOCAL_DB=true` + +## Sync Store + +The sync queue has a separate store abstraction: + +- `lib/features/sync/data/storage/sync_state_store.dart` +- `HiveSyncStateStore` +- `SharedPrefsSyncStateStore` +- `InMemorySyncStateStore` + +This separation is intentional. Product state and sync queue state have +different recovery and migration needs. + +## Migration Behavior + +On startup, `LocalRepository`: + +1. Reads the configured product store. +2. If Hive is enabled and empty, attempts to import legacy shared preferences. +3. Writes the migrated data into Hive. +4. Clears the legacy store. +5. Parses `LocalDataState`. +6. Reconciles reminder schedules. + +If stored JSON is invalid, the app falls back to empty seeded state. + +`SyncQueueRepository` follows the same Hive-first import pattern for sync state. + +## Adding Persisted Fields + +Checklist: + +1. Add the field to the feature-owned model. +2. Update constructor, field declaration, `copyWith`, `toJson`, and `fromJson`. +3. Use default values or nullable parsing for backward compatibility. +4. Add the field to `LocalDataState` if it is a new aggregate collection. +5. Update repository operations that should create, update, merge, or delete it. +6. Update merge behavior if the field is person-owned. +7. Update sync payloads if the backend should receive it. +8. Add tests for old JSON and new JSON. + +Increase `_schemaVersion` in `LocalRepository` only when the migration needs +explicit logic or when a stored shape changes in a way that cannot be handled by +default parsing. + +## Data Loss Traps + +- Do not remove unknown lists from `LocalDataState.fromJson` without a migration. +- Do not clear Hive/shared preferences to "fix" parse issues except in tests. +- When deleting a person, check all person-owned collections and source links. +- When merging people, move facts, dates, signals, messages, inbox candidates, + reminders, ideas, moments, and source links to the target profile. +- Reminder scheduling is a side effect of repository writes; failures are + swallowed by design. + +## Useful Tests + +```bash +flutter test test/features/local +flutter test test/features/local/storage +flutter test test/features/sync/storage +``` diff --git a/docs/SYNC_AND_BACKEND.md b/docs/SYNC_AND_BACKEND.md new file mode 100644 index 0000000..ce10c84 --- /dev/null +++ b/docs/SYNC_AND_BACKEND.md @@ -0,0 +1,87 @@ +# Sync And Backend + +The app has a backend boundary but remains local-first by default. + +## Runtime Modes + +Fake backend: + +```bash +flutter run --dart-define=USE_FAKE_BACKEND=true +``` + +REST backend: + +```bash +flutter run \ + --dart-define=USE_FAKE_BACKEND=false \ + --dart-define=BACKEND_BASE_URL=https://your-api +``` + +`BackendGatewayFake` is the normal development path. It should stay +offline-safe and must not seed relationship records into local state. + +## Backend Boundary + +Canonical files: + +- `lib/integrations/backend/backend_gateway.dart` +- `lib/integrations/backend/backend_gateway_fake.dart` +- `lib/integrations/backend/backend_gateway_rest.dart` +- `lib/integrations/backend/backend_gateway_provider.dart` +- `lib/integrations/backend/models/backend_models.dart` +- `docs/api/openapi.yaml` + +The OpenAPI contract is the source for REST protocol intent. Generated Freezed +files are committed and should be regenerated when backend models change. + +## Sync Queue + +Canonical files: + +- `lib/features/sync/data/sync_queue_repository.dart` +- `lib/features/sync/application/sync_coordinator.dart` +- `lib/features/sync/application/sync_auto_trigger_controller.dart` +- `lib/features/sync/application/sync_background_runner.dart` +- `lib/features/sync/presentation/sync_view.dart` + +Local writes enqueue `ChangeEnvelope` objects. Push accepts or rejects them. +Rejected changes can be inspected and repaired from Sync view. + +## Auto Sync + +Background sync is disabled by default in the current local-first posture: + +```text +ENABLE_BACKGROUND_SYNC=false +``` + +When enabled, triggers include startup, resume, periodic intervals, and +offline-to-online transitions. REST mode is reachability-gated. + +## Known Mapping Gap + +Not every local entity maps to the backend protocol yet. Newer local-first +entities such as facts, important dates, preference signals, shared inbox, and +AI drafts may need explicit backend protocol decisions before real multi-device +sync. + +Do not assume adding a local model automatically syncs it. + +## Backend Change Checklist + +1. Update `docs/api/openapi.yaml`. +2. Update backend DTOs in `lib/integrations/backend/models/`. +3. Regenerate generated models when needed. +4. Update REST gateway mapping. +5. Update fake gateway behavior. +6. Update sync envelope validation. +7. Update repository enqueue payloads. +8. Add serialization and sync coordinator tests. + +Useful tests: + +```bash +flutter test test/integrations/backend +flutter test test/features/sync +``` diff --git a/docs/TESTING_AND_DEBUGGING.md b/docs/TESTING_AND_DEBUGGING.md new file mode 100644 index 0000000..e164dab --- /dev/null +++ b/docs/TESTING_AND_DEBUGGING.md @@ -0,0 +1,117 @@ +# Testing And Debugging + +## Baseline Commands + +```bash +flutter analyze +flutter test +dart run build_runner build --delete-conflicting-outputs +``` + +Focused suites: + +```bash +flutter test test/features/share_intake +flutter test test/features/ai_digest +flutter test test/features/local +flutter test test/features/sync +flutter test test/core/llm +flutter test test/integrations/backend +``` + +## Manual Smoke Tests + +Fake backend: + +1. Launch with `USE_FAKE_BACKEND=true`. +2. Sign in with fake credentials. +3. Add a person. +4. Add a moment. +5. Add an idea. +6. Add a reminder. +7. Open Settings and verify flags. + +Share intake: + +1. Settings -> `Simulate Share Capture`. +2. Paste a sender-prefixed text share. +3. Confirm review sheet behavior. +4. Verify profile attachment or Share Inbox routing. +5. Open profile and check facts/messages/signals. + +AI digest: + +1. Configure provider in Settings. +2. Run LLM Debug if needed. +3. Enable weekly digest. +4. Run Private Digest Now. +5. Open AI Review. +6. Accept and dismiss drafts. + +Notifications: + +1. Request notification access from Settings. +2. Create or update reminders. +3. Verify scheduler tests still pass. +4. Test real delivery on device where possible. + +## Debug Surfaces + +Settings includes: + +- LLM Debug +- LLM diagnostics export +- Run Private Digest Now +- Open AI Review +- Request Notification Access +- Simulate Share Capture +- Open Share Inbox +- Share Diagnostics + +Sync view includes: + +- pending changes +- rejected changes +- repair actions +- retry/requeue tools + +## Common Failures + +### Share intent delivered nothing + +Check: + +- `ENABLE_WHATSAPP_SHARE_INTAKE` +- iOS extension target build +- App Group ID matches Runner and extension targets +- `ReceiveSharingIntent.instance.getInitialMedia()` +- Share Diagnostics in Settings + +### LLM digest creates bad suggestions + +Check: + +- `AnonymizedLlmContextBuilder` payload +- sensitive/unreviewed facts filtering +- prior suggestions included +- source age and `shared_message_datetime` +- parser response tests + +### Background digest does not run + +This can be normal. iOS background execution is best effort. Verify manual run +first, then check Workmanager setup, background modes, charging/network policy, +and run-state backoff. + +### Full widget tests fail due layout hit targets + +Some app-flow tests are sensitive to viewport/layout. Prefer focused tests for +feature work, then investigate full-suite failures separately with screenshots +or widget tree dumps. + +## Test Data Rules + +- Avoid real names, messages, phone numbers, or URLs in fixtures. +- Use synthetic names and pseudonymous tokens. +- Add regression tests for parser edge cases. +- Add privacy assertions when prompt payloads change. diff --git a/docs/open-tasks.md b/docs/open-tasks.md index 325a585..7072754 100644 --- a/docs/open-tasks.md +++ b/docs/open-tasks.md @@ -5,44 +5,43 @@ sessions can pick them up without losing context. ## Backlog -### iOS Share Sheet Integration (Deferred) +### iOS Share Sheet Integration Verification -Status: `deferred` +Status: `partially implemented, needs device verification` Priority: `high` (for real cross-app sharing UX on iPhone/iPad) Problem -- The app does not appear in the iOS share sheet yet. -- `receive_sharing_intent` is added in Flutter (`pubspec.yaml`), but the iOS - Share Extension target is not configured in the Xcode project. +- The repository now contains native iOS Share Extension and Save Action target + files, and the Xcode project references those targets. +- The remaining work is to verify the end-to-end behavior on real devices and + keep CocoaPods/Xcode target wiring healthy as dependencies change. -What is missing (current repo state) -- No `ios/Share Extension/` target files in the project -- No Share Extension target configured in `ios/Runner.xcodeproj/project.pbxproj` -- No extension target block in `ios/Podfile` -- No shared App Group capability setup for `Runner` + extension +Current implementation touchpoints +- `ios/Share Extension/` +- `ios/Save Action/` +- `ios/Runner.xcodeproj/project.pbxproj` +- `ios/Runner/Info.plist` +- `lib/features/share_intake/presentation/incoming_share_listener.dart` -Implementation plan (later) -1. Create iOS `Share Extension` target in Xcode. -2. Add/port extension files based on `receive_sharing_intent` example: - - `Share Extension/Info.plist` - - `ShareViewController.swift` (inherits `RSIShareViewController`) - - extension entitlements file -3. Update `ios/Runner/Info.plist` with plugin-required app group / URL scheme - keys. -4. Add extension target to `ios/Podfile` (`target 'Share Extension' do ...`). -5. Enable App Groups in both targets (`Runner` and `Share Extension`) with the - same group id. -6. Verify build phase ordering (`Embed Foundation Extension` before `Thin Binary`) - if needed. -7. Test text sharing from iOS Simulator apps (Notes/Safari). -8. Test real WhatsApp/iMessage sharing on a physical iPhone (simulator is not a - reliable substitute for chat metadata behavior). +Verification plan +1. Run `flutter pub get` and `pod install` from `ios/` when native dependency + versions change. +2. Open the workspace in Xcode and verify Runner, Share Extension, and Save + Action signing/App Group capabilities. +3. Confirm `APP_GROUP_ID` / `CUSTOM_GROUP_ID` resolves consistently for all + targets. +4. Verify build phase ordering (`Embed Foundation Extension` before `Thin Binary`) + if Xcode reports extension embed issues. +5. Test text sharing from iOS Simulator apps such as Notes and Safari. +6. Test real WhatsApp/iMessage sharing on a physical iPhone. Simulator behavior + is not a reliable substitute for chat metadata behavior. +7. Confirm imported shares reach either the review sheet or Share Inbox. Notes - The app already has an in-app fallback path for testing ingest logic: `Settings` -> `Simulate WhatsApp Share`. -- Flutter-side ingest/parsing/inbox routing is implemented; this backlog item is - specifically native iOS share-extension wiring. +- Flutter-side ingest/parsing/inbox routing is implemented. See + `docs/SHARE_INTAKE.md` for the current pipeline. ## Future Ideas (placeholder)