8000 feat(app): sensor lifecycle center with expiry/offboarding by shroominic · Pull Request #14 · shroominic/OpenGlucose · GitHub
[go: up one dir, main page]

Skip to content

feat(app): sensor lifecycle center with expiry/offboarding - #14

Merged
shroominic merged 3 commits into
mainfrom
feat/sensor-lifecycle
Aug 12, 2026
Merged

shroominic merged 3 commits into
mainfrom
feat/sensor-lifecycle

Conversation

@shroominic
Copy link
Copy Markdown
Owner

Sensor lifecycle center (TASK-008, TASK-043 + founder-flagged expiry/offboarding)

Adds a self-contained sensor lifecycle center card to the dashboard, derived purely from the session timing (sessionStart / warmupMinutes) plus the expiry/stopped health flags. Wellness framing throughout (sensor logistics, not medical).

Stacked PR. Chain: #4 → #8 (feat/mock-sensor-scenarios) → this. Built on #8's OG_DEMO + OG_SCENARIO mock harness so the expiring/expired states are testable. Rebase to main after the chain merges. Do not merge yet.

1. Lifecycle card (SensorLifecycleCard)

New, fully self-contained widget (openhealth/lib/src/sensor_lifecycle_card.dart) inserted into the dashboard via a single, clearly-delimited block (// --- Sensor lifecycle center ---) in main.dart so other branches touching the dashboard merge cleanly. By phase:

  • active — sensor age, % of the 15-day life used (progress ring), time remaining, total life, last-sync time
  • warmup — the ~1h warmup countdown + a "warming up" heads-up
  • expiringSoon — amber "expiring soon" heads-up to have a replacement ready (12h threshold)
  • expired — full offboarding state

The card is stateless and recomputes on each parent rebuild (the dashboard already rebuilds on controller notifications + the hero card's 1s ticker), so it owns no perpetual timer.

2. 14 → 15 day fix (TASK-043)

The whole app was searched (grep -rn across .dart/.swift/.kt/.md). There was no remaining hardcoded "14-day" sensor-life constant — earlier commits had already moved the life math to 15 days. The remaining drift risk was a duplicated 15-day literal (in session_presentation.dart and mock_scenarios.dart). This PR makes it a single source of truth:

  • kSensorLifeDuration = Duration(days: 15) in session_presentation.dart, consumed by the lifecycle math, the dashboard header, and the mock harness (mock_scenarios.dart now references it instead of its own _sensorLife).
  • sensorLifeText now rounds days up so a freshly-started sensor reads "15 days left" instead of "14" (a partial first day still counts). This fixed a real header artifact seen in the warmup screenshot.

3. Expiry / offboarding UI (founder-flagged)

When the sensor is expired (mock expired / session stopped / health.expired):

  • clear "Sensor expired" messaging + "Expired" badge
  • the last-known readings are kept and shown frozen (the dashboard is not blanked) with a "history is preserved" note
  • a 3-step remove/replace flow + a "Replace sensor" action (wired to disconnect/rescan)
  • expiringSoon gets a proactive heads-up banner

Verification (iOS simulator — iPhone 15, mock scenarios)

Scenario Screenshot
warmup /tmp/og-life-warmup.png — 40 min countdown, age 20m, 15-day total
activeNormal /tmp/og-life-active.png — 4% used, 14d 7h left, header "15 days left"
expiringSoon /tmp/og-life-expiring.png — 99% used, 2h 59m left, amber heads-up
expired /tmp/og-life-expired.png — offboarding flow, last reading 112 mg/dL preserved

Tests

New + existing unit/widget tests cover the life-% math, the 15-day constant, the compact-duration/last-sync formatters, and the warmup/expiringSoon/expired card rendering (incl. "expired preserves data, does not blank"). flutter analyze clean. 48/48 tests pass across all suites except the pre-existing widget_test.dart, which hangs in pumpAndSettle due to the dashboard hero card's existing 1-second Timer.periodic (reproduced on the unmodified base branch — independent of this PR; left untouched to avoid scope creep on the stacked chain).

🤖 Generated with Claude Code

shroominic and others added 3 commits June 23, 2026 19:24
…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>
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>
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>
@shroominic
shroominic changed the base branch from feat/mock-sensor-scenarios to main August 12, 2026 17:22
shroominic added a commit that referenced this pull request Aug 12, 2026
Reviewed aggregate of pull requests #1 through #14 with the approved engineering baseline. All eight protected GitHub checks passed on the exact head commit.
@shroominic
shroominic merged commit 3539db5 into main Aug 12, 2026
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

0