fix: connecting no longer purges demo data at all (iOS parity) - #52
Merged
Conversation
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
force-pushed
the
fix/demo-data-survives-reconnect
branch
from
August 22, 2026 16:19
bd20c99 to
a038b4e
Compare
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.
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.
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:
EventPersistenceSubscriberranclearDemo()across all four tables on CONNECTED, gated only onisConnectTransition— "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:
11bf6fainitialclearDemo()on measurements/activity +clear()on all sleep (real rows)aefa646clear()→clearDemo()c3b669ee11be6e7649513iOS — 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.mddocumentsConnectPurgeas 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 singleNOTHINGmember.The resulting single-branch
whens inEventPersistenceSubscriberandEventPersistenceIdentityTestread 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 andAGENTS.mdnow say explicitly not to simplify them away.AGENTS.mdis 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-whentripwire, now over the single-member enum.connecting never retires demo rows, first connect or reconnect— everyRingDeviceTypeplus 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.