Skip to content

fix: connecting no longer purges demo data at all (iOS parity) - #52

Merged
foureight84 merged 2 commits into
mainfrom
fix/demo-data-survives-reconnect
Aug 22, 2026
Merged

fix: connecting no longer purges demo data at all (iOS parity)#52
foureight84 merged 2 commits into
mainfrom
fix/demo-data-survives-reconnect

Conversation

@foureight84

@foureight84 foureight84 commented Aug 22, 2026

Copy link
Copy Markdown
Owner

The bug

Seeded demo data disappears within minutes of being created, with no user action, whenever a ring is paired.

Reported as "reseed demo data seems to reset when I navigate from Today to Sleep". Navigation was a red herring — four Today↔Sleep bounces with demo counts pinned at 772/84/36 and no device event firing at all.

The cause is the reconnect cadence. Captured live while sampling the DB every 5s with the phone untouched:

08:57:53  demo=772/84/36  conn=08:52:34
09:00:52  demo=772/84/36  conn=-             <- link drops
09:00:58  demo=0/0/0      conn=09:00:54      <- reconnect wipes it

EventPersistenceSubscriber ran clearDemo() across all four tables on CONNECTED, gated only on isConnectTransition — "this came from the BLE client's own connect, not a decoder re-asserting CONNECTED". That gate correctly excludes decoder status echoes, but not a first connect from the 200th, and a paired COLMI R10 re-establishes its link roughly every five minutes.

Why it's removed outright rather than narrowed

My first cut gated the purge to first-connect only. Digging into the history showed that was still too conservative: the purge was never a deliberate product decision. It's the last surviving fragment of the original "connect = clear everything and rebuild from the ring" design, which five commits have been steadily dismantling:

commit change
11bf6fa initial clearDemo() on measurements/activity + clear() on all sleep (real rows)
aefa646 sleep narrowed clear()clearDemo()
c3b669e gate the rebuild on a real transition, not any CONNECTED
e11be6e stop deleting stored sleep history on every connect
7649513 make "connect deletes nothing" structural

iOS — which this is a port of — has no connect-time demo purge anywhere. Its only deletion of seeded rows is user-initiated, inside SeedData. It handles the demo/real mix by detecting it (isDemo, source == "mock", DataFreshness.demo, calibrationState(isDemo:)) and adapting the UI. Demo data coexisting with a real ring is an expected state there, not something to clean up.

So this finishes the direction every prior commit was heading. Demo rows are now retired only from Settings → Privacy & Data → Clear Demo Data.

The tripwire is kept

android/AGENTS.md documents ConnectPurge as a deliberate compile-time guard against re-introducing connect-time deletion (issue #43). Deleting the type would have thrown that away, so it's kept and reduced to a single NOTHING member.

The resulting single-branch whens in EventPersistenceSubscriber and EventPersistenceIdentityTest read as dead code and are not — they're what makes re-adding a delete a compile error rather than a one-line .clear(). Both the code comment and AGENTS.md now say explicitly not to simplify them away.

AGENTS.md is updated: "a connect may retire demo rows and nothing else" → "a connect may delete nothing at all", plus a new paragraph recording the reconnect measurement and the iOS parity rationale.

Testing

Full suite: 1039 tests, 0 failures.

  • no connect event may purge anything, for any family — the exhaustive-when tripwire, now over the single-member enum.
  • connecting never retires demo rows, first connect or reconnect — every RingDeviceType plus the null (decoder-echo) origin.

Not yet hardware-verified. The honest test is: install, seed demo data, leave the phone alone ~10 minutes, confirm the counts survive a reconnect. Happy to run that before merge.

Seeded demo data vanished within minutes of being created whenever a ring was paired, with no user
action. Reported as "reseed demo data resets when I navigate Today -> Sleep"; navigation was a red
herring (four tab bounces with counts pinned at 772/84/36 and no device event firing). The cause was
the reconnect cadence. Captured live while sampling the DB with the phone untouched:

  08:57:53  demo=772/84/36  conn=08:52:34
  09:00:52  demo=772/84/36  conn=-           <- link drops
  09:00:58  demo=0/0/0      conn=09:00:54    <- reconnect wipes it

CONNECTED ran clearDemo() across measurements, activity_daily, sleep_sessions and
sleep_stage_blocks, gated only on "this is the BLE client's own connect". That gate correctly
excludes decoder Status echoes but not a first connect from the 200th, and a paired COLMI R10
re-establishes its link roughly every five minutes.

The purge is now removed outright rather than narrowed to first-connect, because it was never a
deliberate product decision. It is the last surviving fragment of the original "connect = clear
everything and rebuild from the ring" design, which five commits have been dismantling (aefa646
sleep clear() -> clearDemo(), c3b669e gate on a real transition, e11be6e stop deleting sleep
history, 7649513 make "connect deletes nothing" structural). iOS -- which this is a port of -- has
no connect-time demo purge anywhere: its only deletion of seeded rows is user-initiated inside
SeedData, and it handles the demo/real mix by DETECTING it (isDemo, source == "mock",
DataFreshness.demo) and adapting the UI. Demo rows are retired only from Settings -> Privacy & Data.

ConnectPurge is kept, reduced to a single NOTHING member. The single-branch `when`s that switch on
it read as dead code and are not: they are what makes re-introducing a connect-time delete a
compile error rather than a one-line .clear(), which is the tripwire the old per-family
preservesSleepOnConnect boolean never had. AGENTS.md updated to match, including a note not to
"simplify" those whens away.

Full suite: 1039 tests, 0 failures.
@foureight84
foureight84 force-pushed the fix/demo-data-survives-reconnect branch from bd20c99 to a038b4e Compare August 22, 2026 16:19
@foureight84 foureight84 changed the title fix: demo data no longer vanishes on every ring reconnect fix: connecting no longer purges demo data at all (iOS parity) Aug 22, 2026
Removing the connect-time demo purge also removed the thing that had been
keeping seeded rows out of every unfiltered query. Demo and ring rows now
coexist indefinitely, and Reseed Demo Data ships in Settings, so the mixed
state is reachable by any user who seeds and then pairs.

DemoDataPolicy states the rule the readers follow: real wins. A reader
surfaces demo rows only while the corresponding real series is empty, and
switches to the *Real DAO queries the moment the ring has synced anything.
Values that are persisted or exported read *Real unconditionally, since a
demo-derived number outlives the demo data behind it.

- SleepInsights.collapseByDay: a ring night outranks a demo night on the
  same date. Summing them reported ~13h asleep and inflated every average,
  stage average and score built on the collapse. The seeder's guard only
  covers seed-after-sync, not sync-after-seed.
- ViewModels: the sleep aggregate and its anchor take inRangeReal/recentReal
  once any night has synced; the Day-view comment claiming a day is
  all-ring-or-all-demo is corrected.
- ViewModels.buildState: the demo-mode "chart full history" switch keys off
  hasReal(kind), not hasDemo(kind). One reseed used to pin every vitals
  chart to full history over an interleaved series, permanently.
- RestingHRBaselineService, DailyCalorieEstimator: rangeReal. The seeder
  plants ~30d of HR inside the baseline window, and the calorie estimate is
  written back onto a real activity_daily row that is export-eligible.
- MetricsService.isDemo compared against "mock" while the seeder writes
  "demo", so it never fired; DemoDataPolicy.isDemo accepts both.
- CoachContextBuilder: isDemo was hardcoded false, so the demo-data warning
  could never be emitted while stepsWeek/hrResting fed the LLM a blend.
- ConnectPurge/AGENTS.md: the tripwire only catches an author who routes a
  new deletion through the enum — a bare clearDemo() in the CONNECTED arm
  still compiles. Say so, and note isConnectTransition has no production
  caller left.

1044 tests, 0 failures.
@foureight84
foureight84 merged commit ac6df2c into main Aug 22, 2026
1 check passed
@foureight84
foureight84 deleted the fix/demo-data-survives-reconnect branch August 22, 2026 16:41
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