UNDA — Data Flow
Status: Draft. Reflects the current prototype: local-first, sqflite on device, no network calls in production paths.
High-level flow
┌─────────────────────────────────────────────────────────┐
│ User's device │
│ │
user ──► │ UI (Flutter widgets) ──► Riverpod state ──► sqflite │
│ │ │ │
│ └── read ──► │ │
│ ▼ │
│ unda.db │
│ (unencrypted │
│ in v0.1 — B10) │
└─────────────────────────────────────────────────────────┘
(planned) Personalize → MockImporter → ProfileInferenceService → profileProvider
(planned) Settings → sync toggle ON → Supabase (EU region), row-level security
(planned) WorkoutDetail → .fit export → user shares file (never uploaded by UNDA)Actual current flows
F1. Onboarding → Profile persistence
- User picks cycle length + last period date.
OnboardingController.finish()→profileProvider.state = new Profile(...).PersistedProfileNotifierwrites toprofilestable withsync_enabled=false,onboarding_complete=true.- Nothing leaves the device.
F2. Symptom logging
- User taps a chip on Calendar.
SymptomLog.toggle(date, symptom)→ sqflitecycle_daysupsert.- Nothing leaves the device.
F3. Mark session completed
- User taps "Mark completed", picks felt-score 1–5.
SessionLog.logCompleted(...)→ sqfliteworkout_sessionsinsert.- Nothing leaves the device.
F4. Personalize demo import (MockImporter)
- User taps a demo scenario.
MockImporter.fetchRecent(...)returns fixture activities (no network).ProfileInferenceService.infer(...)produces anInferredProfile.- User confirms → profile updated in sqflite.
- Nothing leaves the device.
Planned flows (not yet wired)
FP1. Cloud sync (opt-in)
- Supabase EU region.
- Row-level security keyed on
auth.uid()— user only sees their own rows. - Sync direction: local ↔ remote, last-write-wins per row (
updated_at). - Transfer basis: within EU/EEA; no international transfer in the default configuration.
- Consent event required:
health_data_processingandcloud_sync(see consent-register.md).
FP2. Strava / Intervals.icu / Garmin import (opt-in per provider)
- OAuth in the user's browser.
- Tokens go to secure storage (iOS Keychain / Android EncryptedSharedPreferences) via
flutter_secure_storage. - Fetch: recent activities (default 90 days backfill, then incremental).
- Storage: only fields listed in
data-inventory.mdunderimported_activity. Raw JSON blobs are not persisted. - Per-provider consent event:
strava/intervals_icu/garmin. - Disconnect: stops future sync and deletes previously imported activity rows (documented per §8.4).
F5. Export (shipped)
- User taps "Export my data" in Privacy Center.
DataExporter.buildZip()writesUNDA-export-YYYYMMDD-HHmmss.ziptogetTemporaryDirectory()with:README.txt,profile.json,cycle.csv,sessions.csv,consents.csv,integrations.json.share_plusinvokes the OS share sheet. User chooses destination (Files, iCloud, AirDrop, mail, etc.).- Nothing leaves the device via UNDA-controlled channels — the user picks the recipient.
Data leaves the device only when...
- Cloud sync is explicitly ON.
- An integration OAuth is explicitly authorized.
- The user chooses to share/export.
Every one of these paths must be gated by a versioned consent record (§4).