Repository navigation
Agents
This file is the implementation guide for future agents working in this repository.
Read this before adding a new feature or extending an existing metric screen.
The app is moving toward a consistent, period-based detail architecture for health metrics.
The goal is:
- dashboard-first navigation
- feature-first code organization
- clear separation between data access, feature state, and UI
- reusable screen scaffolding without forcing all metrics into one generic chart system
Use these docs together:
- docs/README.md: doc index
- docs/engineering/architecture.md: target architecture, package map, and the app-wide cross-cutting rules
- docs/engineering/feature-playbook.md: step-by-step guide for adding a feature, a settings section, or a Room entity
- docs/engineering/development.md: build and verification tasks, including the known translation-gate failure
- docs/engineering/test-parity/README.md: Flutter ↔ Kotlin test parity matrix and outstanding gaps
- docs/engineering/analysis/README.md: code analysis (MVVM, Clean Architecture, Compose performance, refactor backlog)
If code and docs disagree, prefer the docs for new work and refactor toward them incrementally.
The codebase already has aligned period-based detail screens for:
- steps/activity
- sleep
- heart
- activities
- body
The global Browse feature has been removed. Entries and sessions should be browsable from the relevant dashboard widget/detail screen instead of through a standalone app destination.
These features already show the intended direction.
Beyond the metric screens, the app now carries a few subsystems that are not metric features and do not follow the period-detail pattern:
-
features/devicesync/— phone-to-phone Health Connect sync over Bluetooth Classic RFCOMM. -
domain/— the:domainGradle module: the app-local pure code (domain/model,domain/insights,domain/preferences,domain/cycle,domain/dashboard,domain/report,domain/medical, andcore/period,core/fit,core/geo,core/stats,core/export,core/privacy), with the same package names as the app. It exists so its lint and tests run beside the app's. Nothing in it may importdata/,healthconnect/,features/orui/;domain/queryanddomain/usecasestay in:appbecause they do. -
data/migration/— a one-time Flutter-to-Kotlin data importer that runs in two phases fromOpenVitalsApp.onCreate(). Its ordering aroundsuper.onCreate()is load-bearing; read the architecture doc before touching startup.
The following areas are still transitional and should not be copied as the default pattern:
- duplicated period selection logic in multiple ViewModels
- broad shared component files that still mix several concerns
- oversized screen files that should keep being split into route, section, card, and helper files
When adding a new detail feature, follow this shape:
-
Define the feature contract.
- screen state
- user actions
- any derived display fields
-
Make the feature period-driven.
- support
Day / Week / Month / Year - use a selected anchor date
- support previous/next navigation
- cap navigation at the current period
- support
-
Keep the frame reusable, keep the charts specific.
- reuse the shared period scaffolding
- keep metric-specific cards and charts inside the feature package
-
Keep repository APIs query-oriented.
- prefer
DatePeriodor feature query objects over adding more ad hoc overloads - keep Health Connect specifics below the feature layer
- prefer
-
Register the feature from the dashboard.
- dashboard card
- route
- top bar title
-
Update docs if the pattern evolves.
Do not break these without an explicit decision. They are app-wide, and each has a section in docs/engineering/architecture.md.
-
No
INTERNETpermission. The manifest removesINTERNET,ACCESS_NETWORK_STATE, andACCESS_WIFI_STATE. Phone-to-phone sync is Bluetooth Classic specifically so this stays true. Never add a dependency that needs a socket. - One foreground service at a time. Activity recording, the Apple Health import, and phone sync contend for the single foreground slot and refuse rather than queue. No service starts without a tap.
-
A missing permission is
ScreenError.PermissionDenied. UseisPermissionFailure()/toScreenError(); never pattern-match exception messages. The screens render this as a grant affordance. -
One Health Connect backoff, one pacer, one tally. A refusal is process-wide state in
HealthConnectRateLimitBackoff, read under the reader semaphore, climbing 1, 2, 5, 15 minutes;HealthConnectCallPacerspaces call starts;HealthConnectCallCounterscounts every call by operation name for the report email. A screen read that is skipped or refused marksHealthConnectReadOutcome, and the dashboard reloads those tiles alone when the deadline passes. Never add a second backoff, a retry loop around a reader, or a toast for a rate limit. -
Health Connect reads and record mapping live behind
healthconnect/*HealthReader. Writes go throughAppleHealthImportRepository.insertImportedRecordswith a deterministicclientRecordId. Medical records (FHIR) have noclientRecordId: they go throughhealthconnect/MedicalRecordsWriter, where a record's data source, type and id make a rewrite an update. -
Text that leaves the app is structure, not data. Reports, the crash draft and the diagnostics log hold counts, types, reasons and masked error text, never a value, a record's time, a name or a file name. Exception text goes through
core/privacy/PrivacyRedactor; a copy goes throughsensitiveTextClip; a log line logs a count or passes the throwable, never a value. SeedPrivacySentinelsin a test of any new report. -
Nothing waits on the main thread. No
runBlockinginapp/src/main(NoRunBlockingRatchetTestholds the allow-list). A receiver never holds a broadcast for a Health Connect read. Composablesrememberany pass over samples. -
Every locale ships complete. A new string goes to
values/strings.xmland to everyvalues-*/strings.xml, with the plural shape each locale needs;python3 scripts/verify-translations.pychecks them. See translations.md and the translation-gate note in development.md. -
Room is at version 18. A new entity means a
MIGRATION_18_19and a bump, notfallbackToDestructiveMigration. -
The docs are checked against the code.
ArchitectureDocTestfails when a Room table, the Room version or a package is missing from the docs.HealthConnectLayeringTestholds the Health Connect layering rule.FileSizeRatchetTeststops a file passing 800 lines, andFunctionLengthRatchetTeststops a new function passing 150. When one fails, fix the code or the doc in the same commit.
Prefer adding code under features/<metric>/....
A feature should own:
- screen composables
- screen state
- screen ViewModel
- feature-specific chart and row components
- feature-specific formatting only when it is truly metric-specific
Shared code belongs in:
-
ui/componentsfor reusable shell components -
core/periodfor app-local period math and formatting (in the:domainmodule) -
domain/model,domain/insights, anddomain/preferencesfor app-local pure models, calculations, and preference enums (in:domain; a member the app uses ispublic, sinceinternalstops at the module) -
core/presentationfor shared formatters,ScreenError, and UI models that remain repository-free -
core/fitfor FIT container decoding; interpretation stays with the consumer
Do not put feature-specific business logic into ui/components.
Prefer one ViewModel per screen.
ViewModels should:
- own loading state
- own selected range/date state
- call repositories or query services
- prepare UI-ready state
ViewModels should not:
- contain large formatting blocks
- duplicate generic period math forever
- directly mirror raw Health Connect response structures if a cleaner UI model is needed
HealthRepository is intentionally narrow and should stay that way.
When adding new capability:
- prefer a feature-oriented API
- prefer query objects or
DatePeriod - avoid adding both
loadX(range)andloadX(start, end)unless it is temporary during migration
New detail screens should follow this mental model:
- scaffold: refresh + range selector + period navigator + error + date picker
- content:
-
Daymode -
Week / Month / Yearmode - optional list/breakdown
-
Health Connect-backed destinations should use the shared shell instead of wiring access gates, sync banners, or permission callouts ad hoc:
-
WithHealthConnectFeatureScreen/HealthConnectScreenShellinui/components -
HealthConnectFeature+HealthConnectScreenUxCoordinatorinhealthconnect -
rememberHealthConnectPermissionLauncherfor permission requests
Do not add per-screen PermissionCallout, inline HealthConnectSyncStatusBanner, or duplicate HealthConnectAccessGate wiring.
A grant missing for one part of a card (a write a sync skipped, a background read an option needs) uses ui/components/InlineGrantPrompt: the card's own message and a button, no hand-built text-plus-button.
- local coroutine loading directly in screens for new feature work
- brand new navigator implementations per feature
- new screen-specific period helper types if a shared one can be used
- giant abstract base ViewModels
- a universal chart abstraction that hides metric semantics
- ad-hoc Health Connect permission UI outside the shared shell
- a second FIT decoder
- a second Health Connect rate-limit backoff, pacer or call tally; a permission check or feature status cached after it failed
- editing
values-*/strings.xmlby hand to makeverifyTranslationspass
Read docs/engineering/feature-playbook.md and follow the checklist there.
If the feature would require copying code from ActivityScreen, SleepScreen, or HeartScreen, stop and ask:
"Should this be a shared scaffold/component first?"
In most cases, the answer should be yes for the shell, and no for the actual chart body.
Generated from AGENTS.md. Edit it there; changes made here are overwritten.
This wiki mirrors the Markdown in Hale-MTU/OpenVitals on every push to main.
Features
- Features
- Achievements
- Activity And Training Plans
- Activity Metrics
- Apple Health Import
- Beverage Logging And Caffeine
- Bluetooth LE Sensors
- Body Energy
- Body Metrics
- CoMaps Navigation Context
- CSV Import
- Cycle Tracking
- Daily Readiness
- Elevation Correction
- Feature Map
- FIT Files Import
- Food Logging
- GPX/KML/KMZ/TCX Route Import
- Health Connect Metrics Dashboard
- Health Report Export
- Heart And Vitals
- Heart Rate Recovery
- Home Screen Widgets
- Hydration
- Manual Entry Of Metrics
- Medical Records
- Metric Detail Customization
- Mindfulness
- Non Health Connect Metrics Dashboard
- Nutrition
- Offline Maps Support
- Onboarding And Permissions
- Preloaded Beverage Nutrition Reference
- Privacy, Support, And Diagnostics
- Recording Of Activity
- Reminders
- Settings And Preferences
- Sleep Score And Recovery
- Sleep Tracking
- Statistics
- Sync With Another Phone
How-to
App
- App Guide
- FAQ
- Getting Started
- Health Connect
- Install
- Local And Connected Editions
- Permissions
- Privacy
- Screenshots
- Support
Reference
- Design Reference
- Reference
- Design Guidelines for Offline & Sync
- OpenVitals UI Notes
- Reference: Data Capture
- Reference: Data Display
- Reference: Health Connect Matchmaking
- Reference: Health Connect Onboarding
- Reference: Health Connect Permissions
- Reference: Health Connect UX
- Reference: Promoting Health Connect
Engineering
- Code Analysis
- Engineering
- Flutter ↔ Kotlin test-parity matrix
- apple-health-import
- Architecture
- body-cycle-recovery
- Design system conformance
- Development
- Feature Playbook
- Flutter -> Kotlin test parity: domain/data/core/contract/state/navigation/di/bootstrap/l10n/widget_test
- Flutter → Kotlin test-parity matrix: features section
- Flutter → Kotlin test-parity matrix: ui / goldens / integration / fixtures
- Flutter-to-Kotlin test-parity matrix — dashboard + devicesync
- manual-entry
- Translations
Proposals
- Proposals
- Activity Start Flow Analysis
- Activity Start Flow Simplification Proposals
- Caffeine Detail And Future Sleep Insights
- Cycle Tracking: Design and Status
- Medical Records Through Health Connect
- Medical Records: What Is Left
Releases
- OpenVitals 2.16.0
- OpenVitals 2.15.2
- OpenVitals 2.15.1
- OpenVitals 2.15.0
- OpenVitals 2.14.5
- OpenVitals 2.14.4
- OpenVitals 2.14.3
- OpenVitals 2.14.2
- OpenVitals 2.14.1
- OpenVitals 2.14.0
- OpenVitals 2.13.0
- OpenVitals 2.12.0
- OpenVitals 2.11.2
- OpenVitals 2.11.1
- OpenVitals 2.11.0
- OpenVitals 2.10.1
- OpenVitals 2.10.0
- OpenVitals 2.9.1
- OpenVitals 2.9.0
- OpenVitals 2.8.0
- OpenVitals 2.7.3
- OpenVitals 2.7.2
- OpenVitals 2.7.1
- OpenVitals 2.7.0
- OpenVitals 2.6.6
- OpenVitals 2.6.5
- OpenVitals 2.6.4
- OpenVitals 2.6.3
- OpenVitals 2.6.2
- OpenVitals 2.6.1
- OpenVitals 2.6.0
- OpenVitals 2.5.0
- OpenVitals 2.4.1
- OpenVitals 2.4.0
- OpenVitals 2.3.2
- OpenVitals 2.3.1
- OpenVitals 2.3.0
- OpenVitals 2.2.8
- OpenVitals 2.2.7
- OpenVitals 2.2.6
- OpenVitals 2.2.5
- OpenVitals 2.2.4
- OpenVitals 2.2.3
- OpenVitals 2.2.2
- OpenVitals 2.2.1
- OpenVitals 2.2.0
- OpenVitals 2.1.0
- OpenVitals 2.0.4
- OpenVitals 2.0.3
- OpenVitals 2.0.2
- OpenVitals 2.0.1
- OpenVitals 1.9.0
- OpenVitals 1.8.0
- OpenVitals 1.7.7
- OpenVitals 1.7.6
- OpenVitals 1.7.5
- OpenVitals 1.7.4
- OpenVitals 1.7.3
- OpenVitals 1.7.2
- OpenVitals 1.7.1
- OpenVitals 1.7.0
- OpenVitals 1.6.3
- OpenVitals 1.6.2
- OpenVitals 1.6.1
- OpenVitals 1.6.0
- OpenVitals 1.5.1
- OpenVitals 1.5.0
Project