Repository navigation
Feature Playbook
Follow this checklist when adding or extending a metric screen.
For the recipes that are not "a metric screen" — a settings section, a Room table — see Other Recipes at the end.
- Keep the screen state in the feature package.
- Put expensive derived display values in the ViewModel state, not in composable getters.
- Keep metric-specific formatting local unless more than one feature needs it.
- Use
TimeRange,DatePeriod,PeriodLoadQuery, andPeriodSelectionDriverfromcore/period. - Support
Day / Week / Month / Year. - Clamp future navigation to the current period.
- Add a
PeriodRangePreferenceKeywhen the screen needs a remembered range.
- Prefer bundled period APIs that return current, previous, and baseline data from one public call.
- Keep Health Connect permissions and record types below the repository layer.
- Keep granular APIs only for real entry-list/detail reads.
- When a period load becomes expensive, narrow the read first: an aggregate, a chunk, a budget. A cache needs a fingerprint and a reason, and never holds raw Health Connect records.
- Annotate screen ViewModels with
@HiltViewModel. - Use constructor injection for repositories and services.
- Use
SavedStateHandlefor route arguments. - Keep direct constructors usable in unit tests when a screen needs custom initial state.
- Use
MetricDetailScaffoldfor the shell. - Keep charts, rows, and cards inside the feature package.
- Split route/container/content files when a screen becomes hard to scan.
- Convert throwables with
toScreenError()/onScreenError()fromcore/presentation, never by readingthrowable.messageyourself. - Do not special-case
SecurityException.isPermissionFailure()already maps it toScreenError.PermissionDenied, which the scaffold renders as a grant affordance instead of red text. - Pass
screenErrorto the scaffold rather than rendering an error block in the feature.
- Add or update ViewModel tests for period navigation and stale-load behavior.
- Add repository tests when introducing a new bundled query.
- Update architecture docs if the feature changes a shared pattern.
- If the work closes a gap listed in test-parity, update that row.
Use the sleep feature as the template for splitting a period-based detail screen:
| File | Responsibility |
|---|---|
SleepScreen.kt |
Route only: collect state, Health Connect shell, MetricDetailScaffold, delegate to content extensions |
SleepDayContent.kt |
LazyListScope extensions for Day mode (timeline, sessions, education) |
SleepPeriodContent.kt |
LazyListScope extensions for Week / Month / Year (bar chart, drill-down list, sessions) |
SleepCharts.kt |
Overview top cards, metric cards, sparklines, and sleepOverview list section |
SleepSharedSections.kt |
Reusable insight sections (confidence, goal, statistics, target context, HRV, caffeine) |
SleepDisplayState.kt |
UI-ready display models produced by the ViewModel |
SleepPresentationMapper.kt |
Maps repository/domain data, including optional cross-metric signals, into SleepDisplayState
|
SleepViewModel.kt |
Period selection, loading, and display state |
SleepCards.kt |
Session timeline and list row composables |
Keep screenError on the scaffold and read presentation values from state.display, not raw repository models in composables.
A settings section is six edits, and missing any one of them fails the build, a test, or leaves a dead card:
- Add an entry to the
SettingsSectionenum infeatures/settings/SettingsSection.kt, with atitleResand asummaryRes. - Add the two strings to
values/strings.xmland, translated, to everyvalues-*/strings.xml, then runpython3 scripts/verify-translations.py. - Add an icon branch to
SettingsSection.iconinfeatures/settings/SettingsCards.kt. It is an exhaustivewhen, so this is compulsory. - Add a content branch to the
wheninfeatures/settings/SettingsScreenContent.kt. Sections that are a bespoke screen rather than a card list map toUnitthere (seeDEVICE_SYNC). - Add a
Screenentry innavigation/Screen.kt, acomposableinnavigation/AppNavigationSettingsRoutes.kt, and the mapping insettingsSectionRoute. - List the new
ScreeninScreen.all.ScreenTitleTestfails on one that is missing.
Sections hidden outside diagnostics builds are filtered in SettingsScreenContent against BuildConfig.OPENVITALS_DIAGNOSTICS; follow DEBUG_DIAGNOSTICS if the new section is developer-only.
The database is at version 18. A new entity means:
- Add the
@Entityand its DAO underdata/local/<area>/. - Add the entity to the
entitiesarray inOpenVitalsDatabase, add the abstract DAO accessor, and bumpVERSIONto 19. - Add a
MIGRATION_18_19in the companion object that creates the table, and add it toALL_MIGRATIONS. Provide the DAO indi/AppModule.kt. Build once and commit the schema file Room writes toapp/schemas. - Give the migration a KDoc saying whether it copies data or only creates the table, and why. Every existing migration does.
- Prefer a natural composite primary key that makes a re-import idempotent, as
food_nutrientsdoes with(food_id, nutrient).
Only add a table for data Health Connect cannot hold, or for a derived summary cache. Do not mirror Health Connect records.
When a repository is a seam that another subsystem writes through, split it:
- Declare the interface in
data/repository/contract/. - Put the implementation in
data/repository/as<Name>Impl. -
@Bindsit indi/RepositoryModule.kt.
Keep the contract free of windowing, aggregation, and interpretation, as CycleJournalRepository is.
Generated from docs/engineering/feature-playbook.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