Skip to content

feat: integrate product feature train and engineering baseline - #15

Merged
shroominic merged 38 commits into
mainfrom
chore/pr-stack-integration
Aug 12, 2026
Merged

feat: integrate product feature train and engineering baseline#15
shroominic merged 38 commits into
mainfrom
chore/pr-stack-integration

Conversation

@shroominic

Copy link
Copy Markdown
Owner

Summary

Risk

R3: this stack touches health data, BLE behavior, local persistence, AI transmission, HealthKit export, signing, and release automation.

Validation

  • make check passed on commit 817a411.
  • 275 Dart/Flutter tests passed across the workspace, plus the demo integration test and five native iOS tests.
  • Android debug, web release, and unsigned iOS builds passed.
  • Android release signing guard failed closed as designed.
  • All 14 original pull-request heads are ancestors of the aggregate commit.

Follow-up evidence

  • Physical-device BLE/HealthKit validation will be recorded after installation on the connected iPhone.
  • HealthKit export is intentionally at-least-once; an interrupted retry can duplicate a sample and this behavior is disclosed in-product.

shroominic and others added 30 commits June 23, 2026 19:19
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
#	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
…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
@shroominic
shroominic merged commit 9b55020 into main Aug 12, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant