feat: integrate product feature train and engineering baseline - #15
Merged
Conversation
Add GitHub Actions for the openhealth/ Flutter app: - ci.yml: PR/main quality gate — pub get, dart format check, flutter analyze, flutter test --coverage, plus Android debug and iOS no-codesign build sanity checks (Flutter/Gradle cached). - release.yml: on v* tags / manual dispatch — signed Android APK+AAB attached to the GitHub Release, and signed iOS IPA uploaded to TestFlight via App Store Connect API key. Each job skips gracefully with a warning when its secrets are absent. - docs/RELEASING.md: required secrets, how to generate them (keystore, App Store Connect API key), and how to cut a release. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…nt, contributing Set up production-grade local dev tooling for the Flutter app: - Lints: extend very_good_analysis (replaces bare flutter_lints) with a few pre-existing-violation rules baselined as documented follow-ups. analyze is green. - Formatting: standardize on dart format (pre-commit enforced). - Git hooks via Lefthook (lefthook.yml): pre-commit format+analyze on staged Dart, commit-msg commitlint (Conventional Commits), pre-push flutter test. - commitlint.config.mjs with app/package-area scopes. - scripts/install-hooks.sh + Makefile (make setup) to install hooks without npm. - CONTRIBUTING.md documenting setup, commands, lint/format, hooks, commits. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Stand up a Backlog.md project board for the OpenGlucose (formerly OpenHealth) Flutter app: 39 tasks across epics (integrations, health-data, ai, ux, onboarding, core, docs, dev-ex) and phases (1-foundation -> 2-build -> 3-polish), authored from the founder's inspiration roadmap and the other-apps reverse-engineering notes. Each task carries acceptance criteria, epic+phase labels, priority, and a "Fleet" note on parallelizability and the files/dirs it touches so a build fleet knows what to serialize. Also update the READMEs: - Root README: Vision (local-first Whoop/Oura-style platform, glucose-first today), sensor integrations roadmap (Aidex X supported; Dexcom/Libre/etc. wanted), and a roadmap/TODO summarizing the backlog epics + pending openhealth->openglucose rename. - openhealth/README: replace the default Flutter template with a real app README pointing at the workspace architecture and the rename. Honesty: wellness / self-experimentation framing throughout; AI gives patterns/observations, not medical advice; no overstating what is built. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ator testing Add a compile-time OG_DEMO build flag (--dart-define=OG_DEMO=true) so native/simulator builds run the in-memory DemoCgmDriver instead of the real BLE Aidex driver, and auto-scan + auto-connect on launch so the app lands directly on the populated dashboard. The iOS simulator has no Bluetooth, so this unblocks simulator-based feature verification. When the flag is false (the default), behavior is unchanged: production builds keep using the real AidexSensorDriver and the manual scan flow. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add four founder-driven sensor tasks and expand the README sensor roadmap to a multi-sensor / multi-analyte (glucose + ketone) table. - TASK-040 Sensor warmup flow fix (BLOCKED — awaiting founder details) - TASK-041 Cross-sensor reading-history persistence (+ per-sensor archive) - TASK-042 Staged mock sensor harness (WIP on feat/mock-sensor-scenarios) - TASK-043 Fix sensor-life label 14 -> 15 days (xref TASK-008) - README: Sensor | Maker | Analyte | Status table; FreeStyle Libre family incl. Lingo, Dexcom Stelo/ONE, SiBionics GS1 + CKM ketone monitor; note the pluggable multi-analyte cgm_* abstraction. Refs: TASK-008 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the pure-Dart foundation for journaling, health-data import, and AI correlation features to packages/cgm_core (no Flutter dependency): - HealthEvent: id, timestamp, typed payloads (meal macros, exercise duration/intensity, free-text note, insulin/medication dose) plus tags; fully serializable. Backs the future journal. - ActivitySample / SleepSample / HeartRateSample: source-agnostic imported health samples with a DataSource enum (appleHealth, healthConnect, manual); serializable, for HealthKit / Health Connect import. - TimelineEntry: a minimal unifying interface so events, samples, and CgmReading sit on one chronological timeline, with sortedByTime / inWindow helpers and mergeTimelines for correlation features. All models are serializable with defensive fromJson (never throws on bad input). Comprehensive unit tests cover construction, JSON round-trip, edge cases, and timeline ordering/merge. Refs: TASK-002 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add pure-Dart glucose analytics in cgm_core (glucose_analytics.dart): time-in-range / below / above, average, SD, CV, estimated GMI-style indicator, and spike count over 24h/7d/14d windows with configurable range bounds (default 70-180 mg/dL). Fully unit-tested with synthetic readings. Render these in a new explainable, wellness-framed "Patterns" section on the dashboard (metrics_section.dart) with a timeframe selector, a one-line plain-language explanation per metric, and unit-pref-aware formatting. Edits to main.dart are minimal and clearly delimited. Wellness/self-experimentation framing only: observations and patterns, not medical metrics or diagnosis. Refs: TASK-012 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Introduce a pure-Dart HealthRepository abstraction in cgm_core covering HealthEvent, Activity/Sleep/HeartRate samples, and a new AiInsight model, with add/update/delete, query-by-window (half-open) + type filtering, and bulk insert for imports. Ships an in-memory implementation plus a reusable contract test suite. The Flutter app provides a concrete SqfliteHealthRepository (sqflite chosen over hive/isar: relational + query-friendly so window/type filters are index-backed, scales to many imported rows, and gives explicit schema migration hooks). Stays fully local; CGM reading history keeps its existing shared_preferences persistence untouched. - cgm_core: AiInsight model, HealthRepository + TimeWindow, InMemory impl - app: SqfliteHealthRepository (versioned schema + onCreate/onUpgrade), openHealthRepository() opener, FFI-backed in-process tests (no device) - adds sqflite/path/path_provider (+ sqflite_common_ffi dev dep) Unblocks journaling, on-device AI insights, and HealthKit import. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extend the OG_DEMO demo driver into a scenario-driven mock so the whole sensor lifecycle and alert surface can be exercised in the iOS simulator without real BLE. - Add MockScenarioCatalog (mock_scenarios.dart): pure, clock-injectable mapping from MockScenario -> CgmSessionSnapshot for warmup, activeNormal, activeHigh, activeLow, rapidRise, rapidFall, expiringSoon, expired, signalLoss, disconnected, multiSensorHistory, and error. - Make DemoCgmDriver/DemoCgmSession scenario-driven with a live applyScenario() switch; default stays activeNormal so existing demo behavior is unchanged. - Select the initial scenario via --dart-define=OG_SCENARIO=<name> and switch at runtime from a new "Mock scenario" picker in the Developer settings tab. Both paths gated behind OG_DEMO; production untouched. - Tests: scenario->snapshot mapping, live scenario switching, and a widget test driving the Developer-tab picker end to end. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Introduce a light, skippable first-run onboarding for OpenGlucose: welcome (open-source, local-first, wellness-not-medical), how it works (apply Aidex X, ~1h warm-up, reading every minute), a unit-aware target range picker (defaults 70-180 mg/dL), and a connect-your-sensor handoff. Onboarding state and the chosen target range persist in shared_preferences via OnboardingStore; the flow shows only on first launch and hands off to the existing scan/connect flow on completion or skip. The launch gate in main.dart is a small, clearly delimited block. Adds widget/unit tests for the gate, completion persistence, the full screen flow, and skip. Refs: TASK-007 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add an opt-in Apple Health integration that writes the app's CgmReadings
to HealthKit as HKQuantityTypeIdentifierBloodGlucose samples (mg/dL).
- HealthKitExportService wraps the `health` package: configures, requests
authorization, and writes blood-glucose samples. iOS-only (Platform.isIOS);
short-circuits everywhere else so Android is unaffected.
- HealthExportController owns the opt-in + last-synced state (persisted via
SharedPreferences) and runs incremental syncs using a recorded-time
watermark so repeated taps don't duplicate samples.
- New "Integrations" tab in the settings sheet with an "Export to Apple
Health" toggle, "Sync now", reading count, and last-synced state. Writes
are gated behind explicit user opt-in. Wellness framing, no medical claims.
- iOS: add HealthKit entitlement (Runner.entitlements + CODE_SIGN_ENTITLEMENTS
on all Runner configs) and NSHealth{Share,Update}UsageDescription strings.
Bump iOS deployment target 13 -> 14 (health plugin minimum).
- Android: bump minSdk to 26 (Health Connect minimum pulled in by the plugin).
Tests: unit coverage for opt-in/auth-decline/incremental-sync/unsupported via
an injected fake exporter. flutter analyze clean; flutter test green; iOS
simulator + Android debug builds pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…vice Add a transport-agnostic, pure-Dart AI foundation to cgm_core: - AiProvider interface + AiRequest/AiMessage/AiProviderConfig, with an injected AiTransport so cgm_core stays dependency-free and fully unit-testable with a fake transport. - NullAiProvider stub (disabled => provably no network I/O). - HttpChatAiProvider: BYO-key, OpenAI/Anthropic-compatible chat client (configurable base URL + model + auth scheme); request/response parsing is static + shared so the host supplies only the bytes-on-wire. - InsightService: reduces a window of local readings + journal events to a privacy-conscious GlucoseSummary (aggregates, not raw dumps), builds a guard-railed prompt, calls the provider, and persists an AiInsight via HealthRepository.upsertInsight. Every insight carries the wellness disclaimer + provenance (model id). - AiDisclaimer: single source of truth for the wellness/self-experimentation framing baked into the system prompt and stored on each insight. Wellness framing only: AI output = patterns/observations, never medical advice, diagnosis, or dosing. Tests cover provider enable/disable, transport failure -> AiGenerationException (no crash), prompt construction, persistence round-trip, and the disabled path. Refs: TASK-020 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wire the cgm_core AI foundation into the app, privacy-first and opt-in: - AiSettings (non-secret config: enable toggle, base URL, model, auth scheme) persisted in SharedPreferences; the API key is NEVER stored here. - AiSettingsStore: keeps the BYO API key only in the platform secure store (iOS Keychain / Android Keystore) via flutter_secure_storage; UI exposes only whether a key is set, never the key. - HttpAiTransport: dart:io HttpClient-based AiTransport (no new HTTP dep) — the single place the opted-in outbound request is made; all failures map to AiGenerationException so nothing crashes. - AiController: builds a NullAiProvider whenever AI is off or no key is set (so the disabled path provably does no I/O), else an HttpChatAiProvider + InsightService bound to the local repository. - Minimal, clearly-delimited "AI insights (optional)" settings tab: enable toggle, secure key entry, provider/model fields, the wellness disclaimer, and a small "Generate insights now" dev action to exercise the foundation. AI is off by default; enabling it (with the user's own key) is the only time a privacy-conscious summary of on-device data leaves the device — stated plainly in the UI. Output is wellness/self-experimentation only, never medical advice. Tests use mocked secure storage + in-memory repo. Unblocks the AI chat UI and dashboard insight cards (later tasks). Refs: TASK-020 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add WeeklyRecapAnalytics in cgm_core computing a 7-day recap: per-day time-in-range/average/variability buckets, this-week-vs-last-week deltas (average, TIR, CV), best/worst day by time-in-range, top upward spikes, and day-of-week average patterns. Wellness framing — observations for self-experimentation, not medical metrics. Unit-agnostic (values kept in mg/dL; UI converts). Includes thorough unit tests for bucketing, deltas, best/worst, spikes, day-of-week, and empty/partial weeks. Refs: TASK-028 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a self-contained Weekly recap screen summarising the last 7 days as plain-language cards: this-week overview (TIR, average, variability, spikes), versus-last-week deltas with direction colouring, steadiest and bumpiest day, top spikes, and a day-of-week pattern bar chart. Unit-aware (mg/dL <-> mmol/L), wellness-framed with the standard disclaimer, and graceful empty/partial-week states. Reached via a single "Weekly recap" button on the dashboard. Includes widget tests for data, empty, and mmol/L rendering. Refs: TASK-028 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a context/time-triggered, dismissible in-app message subsystem that
TIPS (TASK-005) and temporary INFO BOXES (TASK-006) both build on.
- Model (app_message.dart): AppMessage { id, kind (tip|info|alert),
title/body, optional trigger predicate over MessageContext, dismissible,
priority, persistence (recurring | showOnce | showUntilDismissed) }.
MessageContext is an immutable app-state snapshot so selection is pure.
- Controller (message_controller.dart): a ChangeNotifier that selects the
eligible messages for the current context (trigger + dismissal +
priority/kind ordering), surfaces one at a time, and persists dismissals
via shared_preferences. Notifies only when the visible set changes.
- Host (message_host.dart): self-contained dismissible banner card with a
size+fade AnimatedSwitcher; renders zero-height when nothing is eligible.
- Wiring (main.dart): build a MessageController in bootstrap, push a fresh
MessageContext on each controller change, and insert one MessageHost
sliver above the hero card. The param is optional so other branches and
existing tests are unaffected.
- Demo content (message_catalog.dart): a warmup info box and a chart tip,
honest/wellness-framed. TASK-005/006 plug in as more AppMessages.
Tests: controller selection/ordering/dismiss/persistence + a host widget
test. flutter analyze clean.
Refs: TASK-004
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a self-contained "sensor lifecycle center" card to the dashboard covering the sensor's full 15-day life cycle, derived purely from the session timing (sessionStart / warmupMinutes) plus expiry/stopped health flags. No new wiring beyond a single, clearly-delimited insert in the dashboard so concurrent branches merge cleanly. The card renders, by lifecycle phase: - active: sensor age, % of 15-day life used (progress ring), time remaining, total life, last-sync time - warmup: the ~1h warmup countdown + a "warming up" heads-up banner - expiringSoon: an amber "expiring soon" heads-up to have a replacement ready (threshold 12h) - expired: a full offboarding state — clear "sensor expired" messaging, the last-known readings kept (frozen, not blanked), a "history is preserved" note, a 3-step remove/replace flow, and a "Replace sensor" action 14 -> 15 day correctness (TASK-043): the sensor-life duration is now a single source of truth (kSensorLifeDuration = 15 days in session_presentation.dart) consumed by the lifecycle math, the dashboard header, and the mock harness (mock_scenarios.dart no longer defines its own constant). sensorLifeText also rounds the days up so a freshly started 15-day sensor reads "15 days left" instead of "14". Lifecycle math (computeSensorLifecycle), the 15-day constant, the compact-duration / last-sync formatters, and the expired/warmup/ expiringSoon card rendering are covered by new unit + widget tests. Verified in the iOS simulator across the warmup, activeNormal, expiringSoon, and expired mock scenarios. Refs: TASK-008, TASK-043 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
# Conflicts: # packages/cgm_core/lib/cgm_core.dart
# Conflicts: # openhealth/pubspec.lock # openhealth/pubspec.yaml
…tion # Conflicts: # openhealth/lib/main.dart
# Conflicts: # openhealth/lib/main.dart
# Conflicts: # openhealth/lib/main.dart # openhealth/pubspec.lock # openhealth/pubspec.yaml # openhealth/test/widget_test.dart
# Conflicts: # openhealth/ios/Podfile.lock # openhealth/lib/main.dart # openhealth/pubspec.lock # openhealth/pubspec.yaml
# Conflicts: # packages/cgm_core/lib/cgm_core.dart
# Conflicts: # openhealth/lib/main.dart
# Conflicts: # openhealth/lib/main.dart
…tion # Conflicts: # .github/workflows/ci.yml # CONTRIBUTING.md # Makefile # README.md # lefthook.yml # openhealth/README.md # openhealth/ios/Runner.xcodeproj/project.pbxproj # openhealth/ios/Runner/AppDelegate.swift # openhealth/lib/main.dart # openhealth/lib/src/app_controller.dart # openhealth/pubspec.lock # packages/cgm_ble_flutter/lib/src/flutter_blue_plus_transport.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Risk
R3: this stack touches health data, BLE behavior, local persistence, AI transmission, HealthKit export, signing, and release automation.
Validation
make checkpassed on commit817a411.Follow-up evidence