This commit is contained in:
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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)
|
||||
@@ -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.
|
||||
@@ -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):
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
```
|
||||
@@ -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
|
||||
```
|
||||
@@ -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
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user