4.0 KiB
Architecture
High-Level Shape
The app is mid-migration to vertical slices.
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:
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
lib/main.dartinitializes Flutter, Sentry/Bugsink, Workmanager, local notifications, Hive, and Riverpod.RelationshipSaverAppwires auth gates, share listeners, notification listeners, and global app listeners.AppShellowns responsive navigation between Dashboard, People, Moments, Ideas, Reminders, Signals, Sync, and Settings.LocalRepositoryexposes the aggregateLocalDataState.- Feature views read state through Riverpod providers and call repository operations.
- 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
AppConfigflags as build-time--dart-definefriendly 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.