Skip to content

fix a pile of stuff from the issue backlog (#129, #130, #123, #147, #80)#150

Merged
abdulsaheel merged 2 commits into
mainfrom
fix/issues
Jul 25, 2026
Merged

fix a pile of stuff from the issue backlog (#129, #130, #123, #147, #80)#150
abdulsaheel merged 2 commits into
mainfrom
fix/issues

Conversation

@abdulsaheel

@abdulsaheel abdulsaheel commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator

went digging through the open issues and fixed what i could get through. rundown below, commit message has the same thing if you want it inline with the diff.

fixes

#129 — coach mis-dates workouts
v_sessions only had raw epoch start_ts/end_ts, so the coach had to guess how to turn "today" (a local date) into a utc epoch range itself — that's exactly how it got today's workout wrong. added a real local date column to the view (same strftime(...,'localtime') trick dataHistoryDays() already uses) and told the coach to filter on that column instead of doing the conversion itself.

#130 — apple health never gets today's workout, even after forcing a sync
export only ever ran as a side effect of the day_result derive pass, which needs a BLE connection + a scheduled derive to actually happen. finish a workout with the band disconnected (or just before the next derive tick) and it silently never exports. added HealthExporter.exportWorkout() — writes just that one workout immediately when it finishes, independent of the derive pipeline.

#123 — "time to move" nudge only fires when you open the app
foreground-only, immediate-emit, no wall-clock timer behind it at all — so it could only ever "fire" the instant you happened to open the app. implemented the issue's own suggested fix: schedule a real one-shot OS notification for now + 2h whenever movement is detected, cancel + reschedule on the next movement. now it actually fires while the app is closed.

#147 (new, fixed same pass) — stuck workout, delete is the only option
if the app got killed mid-workout, activeWorkout (100% in-memory) never got restored on relaunch, so the only reachable action on it was delete. now on boot the app checks for a stranded status='live' session row: resumes it if recent, or quietly finalizes it if it's been hours (don't want a 3-day-old workout showing as "still live").

#80 — ios sideload fails with "invalid value 'openstrap_edge' for appIdName"
CFBundleName had an underscore in it, which some free-signing tools choke on when registering an App ID with apple's API. renamed to "OpenStrap Edge" — only place that literal string was used in the project.

design-system bug (found in passing)
SectionHeader's trailing label got the tappable accent-color + chevron treatment whether or not it actually had an onTap — so a plain status label ("Select days") looked exactly as clickable as a real button ("Select all") sitting right next to it. now the tappable styling only shows up when there's really something to tap.

3 pre-existing test failures, unrelated to any of the above
found these by stashing my changes and running against clean main first — not something i introduced. root cause: the merged timeline chart's crosshair readout is deliberately always mounted (Visibility(maintainState: true), just hidden via opacity not Offstage) so its layout slot doesn't jump around when you start/stop scrubbing — correct behavior, but it means there are genuinely two "Heart rate" text widgets in the tree at once now (the chip + the readout row), so the old find.text('Heart rate') asserts were ambiguous. keyed the chip row and scoped the test finders to it instead of touching the (correct) UI.

answered, no code needed

#82 — confirmed rrIntervalsMs is beat-to-beat RR in ms, pointed at the existing irregular-rhythm screen (poincare sd1/sd2 + pnn70). doesn't specifically flag single dropped beats the way mobitz detection would need, called that out.

filed along the way

closed

#139, #26 — re-verified the actual code matches what the old maintainer comments claimed before closing, didn't just take their word for it.

testing

611/611 tests green, flutter analyze clean (same 8 pre-existing baseline issues as before this branch, nothing new introduced).

Summary by CodeRabbit

  • New Features
    • Auto-sync workouts to Health/Health Connect immediately after a workout ends (and for auto-detected workout suggestions when enabled).
    • “Time to move” now uses a one-time, OS-scheduled reminder that only fires if motion stops long enough.
    • Workouts can now be resumed after interruptions, while stale live sessions are automatically finalized.
  • Improvements
    • Sedentary/inactivity reminders were refined for daytime timing and better rate limiting.
    • Coach session guidance and workout-date handling were clarified for more consistent “today” filtering.
    • UI trailing section text can now be informational-only or clearly actionable.
  • Platform Updates
    • iOS app display name is now OpenStrap Edge.

went through the open issues and fixed what i could, here's the rundown:

- coach kept mixing up "today's workout" (#129) because v_sessions only had
  raw epoch start_ts/end_ts and the model had to guess how to turn a local
  date back into a utc range itself. just gave the view a real local `date`
  column (same strftime(...,'localtime') trick dataHistoryDays() already
  uses) and told the coach to filter on that instead of doing the math.

- apple health was never getting today's workout (#130) even after forcing
  a sync, because export only ever ran as a side effect of the day_result
  derive pass, which needs a ble connection + a scheduled derive to happen.
  if you finish a workout with the band disconnected, nothing runs. added
  a proper exportWorkout() that writes just that one workout immediately
  when it finishes, no waiting around for the derive pipeline.

- the "time to move" nudge only ever fired the instant you opened the app
  (#123) because it was foreground-only, immediate-emit, no wall clock timer
  behind it at all. per the issue's own suggested fix: now it schedules a
  real one-shot OS notification for now+2h whenever movement is seen, and
  reschedules on every new movement. so it actually fires while the app is
  closed like it's supposed to.

- if you got killed mid-workout the only thing left to do was delete it,
  no way to actually finish it (filed as #147, fixed same breath). turns out
  activeWorkout was 100% in-memory and nothing ever restored it from the db
  on restart. now on boot it checks for a stranded 'live' session row and
  either resumes it (if recent) or quietly finalizes it (if it's been
  hours, don't want to show a "still live" workout from 3 days ago).

- ios sideload was failing for someone with "invalid value 'openstrap_edge'
  for appIdName" (#80) - turned out CFBundleName had an underscore in it
  and that's apparently a thing some of these free-signing tools choke on
  when registering an app id with apple. renamed it to "OpenStrap Edge",
  only place that string was used anywhere in the project so should be safe.

- answered #82 (rrIntervalsMs is beat-to-beat RR in ms, and yeah there's
  already an irregular rhythm screen built on poincare sd1/sd2 + pnn70, just
  doesn't specifically flag single dropped beats the way mobitz would need)

- small design system bug: SectionHeader showed a trailing label with the
  same tappable-looking accent color + chevron whether or not it actually
  had an onTap handler, so "select days" (just a status text) looked exactly
  as clickable as "select all" (an actual button) right next to it. now it
  only gets the coral/chevron treatment when there's really something to tap.

- also found 3 pre-existing test failures on main that had nothing to do
  with any of this (checked by stashing my changes and running against
  clean main first) - the crosshair readout on the merged timeline chart
  is deliberately always mounted now (just hidden via opacity, not
  offstage) so its layout slot doesn't jump around on scrub start/stop.
  that's correct behavior, but it means there are now genuinely two
  "Heart rate" text widgets in the tree at once (the chip + the readout
  row), which made the old find.text('Heart rate') asserts ambiguous.
  keyed the chip row and scoped the finders to it instead of touching
  the (correct) ui behavior.

filed 3 new issues along the way that weren't tracked yet: #147 (workout
stop bug above), #148 (stress score shows a confident number on ~20min of
data while stress index correctly shows "-" — same family as the readiness
honesty fixes, just a different scalar that didn't get the memo), #149
(workout auto-detect is a little trigger-happy and there's no way to turn
it off). also closed #139 and #26 after actually re-checking the code
matched what the old maintainer comments claimed, not just taking their
word for it.

611/611 tests green, flutter analyze clean (same 8 pre-existing baseline
issues as before, nothing new).
@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0bd220e4-4db6-4d17-aff3-77c45446bcb8

📥 Commits

Reviewing files that changed from the base of the PR and between 23a9710 and 1118876.

📒 Files selected for processing (2)
  • lib/health/health_export.dart
  • lib/state/app_state.dart
🚧 Files skipped from review as they are similar to previous changes (2)
  • lib/health/health_export.dart
  • lib/state/app_state.dart

📝 Walkthrough

Walkthrough

The change adds local-day session filtering guidance, centralized immediate health export, startup recovery for live workouts, scheduled stillness reminders, posture-focused inactivity detection, clearer trailing UI affordances, and scoped timeline vital-chip tests.

Changes

Workout lifecycle and health export

Layer / File(s) Summary
Centralized workout export and session triggers
lib/health/health_export.dart, lib/state/app_state.dart, lib/ui/workouts/workouts_screen.dart
Workout writes use shared validation and export logic, with immediate exports for completed and auto-detected sessions.
Orphaned live-session recovery
lib/data/db.dart, lib/state/app_state.dart
Startup queries live sessions, resumes recent valid rows, and marks stale or malformed rows as done.

Local session date querying

Layer / File(s) Summary
Local session date contract
lib/data/db.dart, lib/coach/coach_prompt.dart, lib/coach/coach_engine.dart
v_sessions.date is derived from local time, and coach SQL guidance uses that date for filtering and joins.

Notification scheduling and inactivity detection

Layer / File(s) Summary
Scheduled stillness reminders
lib/notify/notification_service.dart, lib/state/app_state.dart
Movement now cancels and reschedules a daytime one-shot “Time to move” notification.
Foreground posture inactivity check
lib/state/app_state.dart
Inactivity checks use recent pronation and walk-idle state for posture notifications.

Presentation and test targeting

Layer / File(s) Summary
Display name and trailing affordances
ios/Runner/Info.plist, lib/ui/kit/kit.dart
The iOS display name and conditional section-header trailing presentation were updated.
Scoped vital-chip targeting
lib/ui/timeline/timeline_screen.dart, test/history_screens_redesign_test.dart
The vital-chip row has a stable key, and tests scope vital-label lookups and taps to that row.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

Workout export flow

sequenceDiagram
  participant WorkoutSuggestionScreen
  participant AppState
  participant HealthExporter
  participant HealthConnect
  WorkoutSuggestionScreen->>AppState: exportWorkoutToHealth(session)
  AppState->>HealthExporter: exportWorkout(session)
  HealthExporter->>HealthConnect: delete and write workout samples
Loading

Stillness reminder flow

sequenceDiagram
  participant AppState
  participant NotificationService
  participant NotificationPlugin
  AppState->>NotificationService: cancel and schedule stillness reminder
  NotificationService->>NotificationPlugin: zonedSchedule absolute notification
  AppState->>NotificationService: reschedule after movement
Loading

Possibly related PRs

  • OpenStrap/edge#83: Overlaps with the workout export and _exportDay behavior in lib/health/health_export.dart.
  • OpenStrap/edge#114: Overlaps with the detected-workout suggestion flow in lib/ui/workouts/workouts_screen.dart.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title is vague and generic; it doesn’t describe the main change in the pull request. Use a concise title that names the primary change, such as app startup live-workout recovery and health/notification export updates.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issues

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/health/health_export.dart`:
- Around line 554-559: Update _writeOneWorkout and the _exportDay export loops
so a live or malformed workout is represented as a skipped/no-op result distinct
from an attempted write failure. Make _exportDay set success to false only for
genuine write failures or thrown errors, while preserving skipped rows without
advancing retry or give-up state; apply the same distinction to the related
export paths noted in the diff.

In `@lib/state/app_state.dart`:
- Line 1004: Update the today string construction near the date-handling logic
to zero-pad month and day to two digits, preserving the YYYY-MM-DD format used
by _markMomentFromGesture and todayLabel().
- Around line 3104-3160: Initialize _workoutRawBase when rehydrating the session
in _reconcileOrphanedLiveWorkout, using the same reset/start pedometer setup
established by startWorkout() or _resetLivePedometer(), so workoutSteps and
final stopWorkout() persistence count steps from zero onward. Also guard the
reconciliation path with an early return when activeWorkout is already set,
preventing it from replacing a concurrently started workout and leaking its
timer.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 20881b3b-5a75-4e69-815c-fd23927da141

📥 Commits

Reviewing files that changed from the base of the PR and between a4f36f0 and 23a9710.

📒 Files selected for processing (11)
  • ios/Runner/Info.plist
  • lib/coach/coach_engine.dart
  • lib/coach/coach_prompt.dart
  • lib/data/db.dart
  • lib/health/health_export.dart
  • lib/notify/notification_service.dart
  • lib/state/app_state.dart
  • lib/ui/kit/kit.dart
  • lib/ui/timeline/timeline_screen.dart
  • lib/ui/workouts/workouts_screen.dart
  • test/history_screens_redesign_test.dart

Comment thread lib/health/health_export.dart Outdated
Comment thread lib/state/app_state.dart Outdated
Comment thread lib/state/app_state.dart
went through all 3 things it flagged, all were real, fixed all of them:

- health_export.dart: _writeOneWorkout was returning false for both "this
  row is still live/malformed, skip it" AND "we actually tried to write
  and it failed" - same value, two totally different meanings. that
  matters because _exportDay feeds a false into the attempts/backoff/
  give-up retry machinery, which is explicitly reserved for genuine
  thrown errors per the comment above _kRetryCursor. so an in-progress
  workout could trip that on every single drain/derive pass and
  eventually get the WHOLE DAY given up on - not just the workout, real
  stuff like RHR/HRV/steps/sleep export for that day too. switched it to
  bool? (null = skip, not a failure) and fixed both call sites.

- app_state.dart: the posture-check notification's dedupeKey date wasn't
  zero-padded ('2026-7-5' instead of '2026-07-05'), unlike literally
  every other date string in this file. just swapped it for the
  todayLabel() helper that's already used elsewhere here.

- app_state.dart: the orphaned-workout resume path never set
  _workoutRawBase, so a resumed workout's step count stays stuck at 0
  for its whole remaining duration, even once real pedometer data starts
  flowing again after the band reconnects, and stopWorkout() ends up
  persisting 0 steps at the end too. added the same snapshot
  startWorkout() already does. also added a guard at the top so this
  can't clobber a workout that got started for real while the reconcile
  was still awaiting its db query (unawaited from _init(), so technically
  possible even if unlikely).

611/611 still green, analyze still clean.
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