Skip to content

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

Open
shroominic wants to merge 1 commit into
feat/mock-sensor-scenariosfrom
feat/sensor-lifecycle
Open

feat(app): sensor lifecycle center with expiry/offboarding#14
shroominic wants to merge 1 commit into
feat/mock-sensor-scenariosfrom
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

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>
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