Add maintainer onboarding documentation
Flutter CI / analyze_and_test (push) Has been cancelled

This commit is contained in:
Rijad Zuzo
2026-07-03 12:44:15 +02:00
parent 5d80af375e
commit d80486b25e
15 changed files with 1273 additions and 28 deletions
+6
View File
@@ -113,6 +113,12 @@ dart run build_runner build --delete-conflicting-outputs
## Docs ## 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) - [Setup](docs/SETUP.md)
- [Progress Log](docs/progress.md) - [Progress Log](docs/progress.md)
- [Open Tasks](docs/open-tasks.md) - [Open Tasks](docs/open-tasks.md)
+132
View File
@@ -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.
+123
View File
@@ -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/<slice>/
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/<slice>/<name>.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.
+106
View File
@@ -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<PersonProfile>` | primary relationship profiles |
| `moments` | `List<RelationshipMoment>` | interaction and capture history |
| `ideas` | `List<RelationshipIdea>` | gift, activity, and follow-up ideas |
| `reminders` | `List<ReminderRule>` | local reminder rules |
| `tasks` | `List<DashboardTask>` | dashboard task surface |
| `dismissedSignalIds` | `List<String>` | dismissed generated signal IDs |
| `sourceLinks` | `List<SourceProfileLink>` | external sender/thread to profile mapping |
| `sharedMessages` | `List<SharedMessageEntry>` | imported shares attached to profiles |
| `sharedInbox` | `List<SharedInboxEntry>` | unresolved or ambiguous shares |
| `personFacts` | `List<PersonFact>` | structured relationship facts |
| `importantDates` | `List<PersonImportantDate>` | birthdays, anniversaries, events |
| `preferenceSignals` | `List<PersonPreferenceSignal>` | inferred/confirmed likes/dislikes |
| `aiSuggestionDrafts` | `List<AiSuggestionDraft>` | 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.
+123
View File
@@ -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.
+124
View File
@@ -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
+92
View File
@@ -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.
+39
View File
@@ -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)
+92
View File
@@ -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.
+3
View File
@@ -1,5 +1,8 @@
# Setup # 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 ## Backend Base URL
Build-time (recommended): Build-time (recommended):
+121
View File
@@ -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.
+81
View File
@@ -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
```
+87
View File
@@ -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
```
+117
View File
@@ -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.
+27 -28
View File
@@ -5,44 +5,43 @@ sessions can pick them up without losing context.
## Backlog ## 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) Priority: `high` (for real cross-app sharing UX on iPhone/iPad)
Problem Problem
- The app does not appear in the iOS share sheet yet. - The repository now contains native iOS Share Extension and Save Action target
- `receive_sharing_intent` is added in Flutter (`pubspec.yaml`), but the iOS files, and the Xcode project references those targets.
Share Extension target is not configured in the Xcode project. - 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) Current implementation touchpoints
- No `ios/Share Extension/` target files in the project - `ios/Share Extension/`
- No Share Extension target configured in `ios/Runner.xcodeproj/project.pbxproj` - `ios/Save Action/`
- No extension target block in `ios/Podfile` - `ios/Runner.xcodeproj/project.pbxproj`
- No shared App Group capability setup for `Runner` + extension - `ios/Runner/Info.plist`
- `lib/features/share_intake/presentation/incoming_share_listener.dart`
Implementation plan (later) Verification plan
1. Create iOS `Share Extension` target in Xcode. 1. Run `flutter pub get` and `pod install` from `ios/` when native dependency
2. Add/port extension files based on `receive_sharing_intent` example: versions change.
- `Share Extension/Info.plist` 2. Open the workspace in Xcode and verify Runner, Share Extension, and Save
- `ShareViewController.swift` (inherits `RSIShareViewController`) Action signing/App Group capabilities.
- extension entitlements file 3. Confirm `APP_GROUP_ID` / `CUSTOM_GROUP_ID` resolves consistently for all
3. Update `ios/Runner/Info.plist` with plugin-required app group / URL scheme targets.
keys. 4. Verify build phase ordering (`Embed Foundation Extension` before `Thin Binary`)
4. Add extension target to `ios/Podfile` (`target 'Share Extension' do ...`). if Xcode reports extension embed issues.
5. Enable App Groups in both targets (`Runner` and `Share Extension`) with the 5. Test text sharing from iOS Simulator apps such as Notes and Safari.
same group id. 6. Test real WhatsApp/iMessage sharing on a physical iPhone. Simulator behavior
6. Verify build phase ordering (`Embed Foundation Extension` before `Thin Binary`) is not a reliable substitute for chat metadata behavior.
if needed. 7. Confirm imported shares reach either the review sheet or Share Inbox.
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).
Notes Notes
- The app already has an in-app fallback path for testing ingest logic: - The app already has an in-app fallback path for testing ingest logic:
`Settings` -> `Simulate WhatsApp Share`. `Settings` -> `Simulate WhatsApp Share`.
- Flutter-side ingest/parsing/inbox routing is implemented; this backlog item is - Flutter-side ingest/parsing/inbox routing is implemented. See
specifically native iOS share-extension wiring. `docs/SHARE_INTAKE.md` for the current pipeline.
## Future Ideas (placeholder) ## Future Ideas (placeholder)