Skip to content

Android: restore Today recording status - #2598

Open
kavemang wants to merge 2 commits into
ryanbr:mainfrom
kavemang:fix/restore-today-recording-status-2179
Open

kavemang wants to merge 2 commits into
ryanbr:mainfrom
kavemang:fix/restore-today-recording-status-2179

Conversation

@kavemang

@kavemang kavemang commented Sep 29, 2026 •

Copy link
Copy Markdown

Refs #2179.

Restores a live-recording status beneath Android Today's header while the existing sync chip owns last-sync age and experimental-history status. Both chips receive one clock snapshot from Today; neither child reads its own clock, and the recording row has no minute timer.

The recording row shows live recording, a connected strap without data, or no connection. Its historical last-sync fallback is omitted so it cannot duplicate the sync chip. Experimental history no longer overrides the live-recording question. The existing permission-aware Connect action remains available from Not recording and the header's disconnected sync affordance.

The row appears only on today's date and is hidden for an active Oura ring because the existing copy describes a strap. Existing strings and design tokens are retained. Shared analytics and stored data are unchanged; this restores the missing Android surface.

Validation: comment hygiene, diff-scoped i18n and whitespace passed locally. Guarded parity regeneration passed (289 known findings). A private Kotlin harness passed 256 cases using the actual production recording resolver and extracted presentation-selection body, including minute boundaries, stale/future sync stamps, streaming presence and experimental flags. This focused check does not compile Compose or Room. All four expected CI checks passed at c43a09e42da3c74385134c0aa1e5c6e29bc69f85: clean Android APK build and full unit suite, Tools Python, hygiene and i18n. The complete four-check roster and unchanged head were rechecked. No local full-app build is claimed.

@ryanbr

ryanbr commented Oct 4, 2026

Copy link
Copy Markdown
Owner

Thanks @kavemang. The gap is real: recordingStateFor and the whole RecordingState model sit in TodayScoring.kt fully tested and currently unused by any Android screen, so the logic survived while the surface did not. Restoring the surface is the right instinct, and CI is green.

Holding it on one thing, and it is the repo's own hard rule rather than a preference.

Two readouts of one fact, with two clocks

Android Today already renders a sync chip. Its resolver does:

lastSyncAtSec != null -> Synced(shortSyncAgo(lastSyncAtSec, nowSec))
historySyncExperimental -> ExperimentalLive

The new chip resolves from the same two inputs:

LastSynced(minutesAgo)         // from live.lastSyncAt
HistoryExperimental            // from live.historySyncExperimental

So a strap that last synced fourteen minutes ago and is not streaming puts two statements of lastSyncAt on one screen: the existing chip's "14m" and the new chip's "Last synced 14 minutes ago". Both also carry a state off the same historySyncExperimental flag, rendered with different words, "Live" against "Connected".

Worse than the duplication, they can disagree. The sync chip takes nowSec from the snapshot the screen already resolved; the new chip runs its own clock:

var nowSec by remember(live.lastSyncAt) { mutableStateOf(System.currentTimeMillis() / 1000L) }
LaunchedEffect(live.lastSyncAt) { while (true) { delay(60_000); nowSec = ... } }

Two clocks a minute apart, so the two chips can round to different minute values and sit there contradicting each other. AGENTS.md names that exact shape, from the Alarms work: "a countdown and its date stamp resolved from two clocks a tick apart", under the rule that two readouts of one fact must not be able to disagree. Its stated preference, in order, is not to show the fact twice, and failing that to resolve both from one funnel and one clock passed in rather than read separately.

What would make this mergeable

The distinction the comment draws is sound: history offload is not the same question as whether live data is being saved. So I would keep the feature and remove the overlap rather than drop it.

  • Have both chips take the same nowSec the screen already has, so no second clock exists. That alone removes the disagreement.
  • Then decide which chip owns "last synced". If the recording chip is to own it, the sync chip's Synced branch should stop restating it; if the sync chip keeps it, RecordingState.LastSynced should not be reachable on this screen and the recording chip can confine itself to the live question it is actually there to answer.
  • Same for the experimental state: one of ExperimentalLive and HistoryExperimental should render on Today, not both.

The Recording and NotRecording states are the genuinely new information here and do not collide with anything, so the useful half of the change survives all of that.

One smaller note while you are in it: the per-minute LaunchedEffect recomposes the row every sixty seconds for a label that only changes when the minute rolls over. Reusing the screen's existing clock removes that too.

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.

2 participants