Skip to content

Refactor Today pure logic helpers - #686

Merged
ryanbr merged 2 commits into
ryanbr:mainfrom
tigercraft4:refactor/today-logic-extraction
Jul 22, 2026
Merged

ryanbr merged 2 commits into
ryanbr:mainfrom
tigercraft4:refactor/today-logic-extraction

Conversation

@tigercraft4

@tigercraft4 tigercraft4 commented Jul 20, 2026 •

Copy link
Copy Markdown

Summary

Follow-up to #665. This starts the low-risk Phase 1 split the maintainer outlined for TodayScreen.kt: move pure/package-level Today helpers into sibling files in the same package, without changing call sites or behavior.

Extracted files:

  • TodayDayNav.kt — launch/day navigation helpers.
  • TodayScoring.kt — recovery/scoring/recording state helpers.
  • TodayProvenance.kt — source/provenance/readiness/footer summary helpers.
  • TodayMetricsLogic.kt — HR window, footer cache shape, workout feed, weight/steps tile helpers.

TodayScreen.kt drops 688 lines and remains the UI assembler. No iOS twin is included because this is Android/Kotlin file organization only.

Verification

  • JAVA_HOME=/opt/homebrew/opt/openjdk@21/libexec/openjdk.jdk/Contents/Home ANDROID_HOME=$HOME/Library/Android/sdk ./gradlew compileFullDebugKotlin testFullDebugUnitTest --tests com.noop.ui.DayNavTest --tests com.noop.ui.TodayLaunchLandTest --tests com.noop.ui.LandTargetDayStartTest --tests com.noop.ui.RecoveryCalibrationTest --tests com.noop.ui.TodayExplainabilityTest --tests com.noop.ui.TodayChargeTapCollapseTest --tests com.noop.ui.TodayResolverEffortScaleTest --tests com.noop.ui.HrWindowTest --tests com.noop.ui.LastWorkoutsFeedTest --tests com.noop.ui.TodayMetricTilesTest --tests com.noop.ui.StepsReadingsMergeTest
  • python3 Tools/i18n_audit.py --ci origin/main

@ryanbr ryanbr left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good direction — this is the #665 Phase-1 split the maintainer outlined, and the logic move is faithful. Verified three ways:

  • 66 symbols removed = 66 added, identical signatures (only change is com.noop.analytics.FusionSource → imported FusionSource). Nothing dropped or duplicated.
  • Real code lines move ~1:1 (≈332 → ≈326 after excluding comments/blanks/imports).
  • Compiles + the moved-helper tests pass (DayNav, RecoveryCalibration, HrWindow, LastWorkoutsFeed, TodayMetricTiles, StepsReadingsMerge, TodayExplainability).

One thing to fix before merge: ~223 lines of comments/KDoc got dropped (298 comment lines removed vs 75 added — that's the whole 688-vs-479 gap), and they're the valuable kind:

  • 26 issue-provenance refs gone (30 #NNN comment lines removed, 4 kept): the "why it exists / which bug motivated it" trail — #547 future-dated-row guard, #543/#779 carry-over + freshness cap, #860 "as ONE pure decision so the rule can't drift", #393/#85 baseline calibration.
  • The cross-platform parity anchors are stripped — "Mirror EXACTLY in Swift", "Mirrors the iOS lastScoredCharge tuple", "Mirrors iOS lastChargeDateFmt". Those guard the repo's #1 rule (Swift↔Kotlin parity); a future edit to the now-bare extracted helper won't see them and could silently diverge from the Swift twin.

Could you carry the KDoc/rationale comments over with the functions? A pure move should move the comments too — especially the "Mirror EXACTLY in Swift" / "Mirrors iOS…" parity notes and the #NNN provenance. The logic is sound; this is purely about not losing the context that keeps these helpers maintainable and parity-safe. With the comments preserved, happy to merge.

@tigercraft4

Copy link
Copy Markdown
Author

Pushed follow-up commit a2f1ffb to address this review. The extraction now carries the Today helper KDoc/rationale comments, issue provenance refs, and Swift/iOS parity notes over into the new helper files without changing the helper logic.\n\nVerified locally with:\n- JAVA_HOME=/opt/homebrew/opt/openjdk@21/libexec/openjdk.jdk/Contents/Home ANDROID_HOME=/Users/francisco/Library/Android/sdk ./gradlew :app:testFullDebugUnitTest --tests 'com.noop.ui.HrWindowTest' --tests 'com.noop.ui.TodayExplainabilityTest' --tests 'com.noop.ui.RecoveryCalibrationTest' --tests 'com.noop.ui.TodayMetricTilesTest' --tests 'com.noop.ui.LastWorkoutsFeedTest' --tests 'com.noop.ui.DayNavTest' --tests 'com.noop.ui.StepsReadingsMergeTest'\n- same command against :app:testDemoDebugUnitTest

@ryanbr
ryanbr merged commit cc7249a into ryanbr:main Jul 22, 2026
1 check passed
DX23876 pushed a commit to DX23876/noop that referenced this pull request Jul 22, 2026
…xtraction

Refactor Today pure logic helpers
simoncad7 pushed a commit to simoncad7/noop that referenced this pull request Aug 17, 2026
…xtraction

Refactor Today pure logic helpers
ryanbr pushed a commit that referenced this pull request Sep 6, 2026
…ple counts

RecoveryScorer.restingHR and RecoveryScorer.recoveryIndexSlope document and
prefilter a closed [start, end] window but bin it as [t, t + 300) with a loop
that stops at `end`. A sample sitting exactly on an aligned `end` passes the
prefilter and then belongs to no bin: admitted as data, silently ignored. For
the Recovery Index that can leave the six-bin gate one bin short and turn a
complete 30-minute window into nil/null.

The final bin now closes on `end` on both platforms ([t, t + 300) for every bin
except the last, which is [t, end]); a zero-length window collapses to that one
final bin. Bin midpoints, the #686 artifact gate and recoveryIndexMinBins are
unchanged, so nothing moves except a sample on an aligned endpoint. Both
functions already guard on an empty prefiltered segment, so the loop conversion
cannot manufacture a bin. Doc comments and docs/ANALYTICS.md state the rule.

Verified surgical rather than taken on trust: sweeping both the old and new
binning over 55,100 random window/sample configurations produced zero
differences for any span not aligned to 300s, and zero differences that did not
involve a sample sitting exactly on `end`.

The tests earn their place. Reverting only the production Kotlin file turns
three of the ten new cases red (endpointSampleLandsInFinalBinOnly,
recoveryIndexSlopeAlignedEndpointCompletesTheBinGate,
restingHrEndpointSweepMatchesSwiftOracle) and they pass again with it; the other
seven pin behaviour that must not move.

Scope, stated plainly: both helpers are library API with no in-app caller today,
so this moves no shipped number. The resting HR the apps display comes from
SleepStager.sessionRestingHR, whose window loop has the same shape and the same
defect; that is deliberately not part of this change.

Reported and implemented by @bhelm. The reporting fork's issue number was
dropped from the comments before merge: read in this repo it lands on an
unrelated report, and the same number is already used in HealthLiveHrLogic.kt
and AppViewModel.kt for live-HR median parity.
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