feat(lookback): browse past days on the Your day screen#142
Conversation
The lookback / "Your day" screen only ever showed today. Add day navigation so past days' naps, HR/HRV/resp/skin-temp and movement can be browsed one day at a time, mirroring the Sleep section. - JourneyScreen now holds a mutable current-date (initialised from the entry date); prev/next chevrons and a tappable date jump across days, each re-running the existing per-date load. Next is disabled at today (never into the future), prev at the earliest recorded day; empty gaps between recorded days are skipped. All existing behaviour is preserved: retention gating and the partial-today fallback still key off the viewed date, so older days show derived summaries rather than minute curves, and truly-empty days show the empty state. - Add LocalRepository.availableDays() (LocalDb.availableDayIds: the UNION of day_result rows and decoded_onehz day labels, newest first) to bound navigation and the date picker. - Extract the stepping/bounds maths into a pure DayNav helper. Tests: DayNav bounds/stepping (pure) and availableDays over the real sqflite_ffi LocalDb. Closes OpenStrap#112
|
Warning Review limit reached
Next review available in: 47 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (6)
📝 WalkthroughWalkthroughJourney history now exposes locally recorded days, computes bounded day navigation, and integrates previous/next controls and a date picker into ChangesJourney history navigation
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant JourneyScreen
participant LocalRepositoryImpl
participant LocalDb
participant DayNav
participant JourneyContent
JourneyScreen->>LocalRepositoryImpl: availableDays()
LocalRepositoryImpl->>LocalDb: availableDayIds()
LocalDb-->>LocalRepositoryImpl: ordered day labels
LocalRepositoryImpl-->>JourneyScreen: available days
JourneyScreen->>DayNav: compute navigable days
JourneyScreen->>DayNav: select next or previous day
JourneyScreen->>JourneyContent: reload selected timeline
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
lib/ui/journey/journey_screen.dart (1)
54-81: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winIgnore stale timeline responses.
Rapid navigation can overlap
_load()calls; an older request can finish last and overwrite the data/error state for the newly selected day.Proposed fix
Future<void> _load() async { + final requestedDate = _currentDate; final api = context.read<AppState>().repo; ... try { - final res = await api.getDayTimeline(_currentDate); - if (!mounted) return; + final res = await api.getDayTimeline(requestedDate); + if (!mounted || requestedDate != _currentDate) return; setState(() { _data = res; ... }); } catch (e) { - if (!mounted) return; + if (!mounted || requestedDate != _currentDate) return; setState(() { ... }); } }🤖 Prompt for 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. In `@lib/ui/journey/journey_screen.dart` around lines 54 - 81, Update _load so overlapping requests cannot apply stale results: capture the selected day or a request-generation token before awaiting getDayTimeline, then verify it still matches the current selection before applying success or error state. Preserve the existing mounted checks and ensure only the latest request updates _data, _phase, and _error.
🤖 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/data/db.dart`:
- Around line 2371-2375: Update the available-days SQL query to exclude
derivation skip markers by filtering day_result rows to skipped = 0, while
retaining partial rows and the existing decoded_onehz union and ordering.
In `@lib/data/local_repository_impl.dart`:
- Around line 513-514: Update availableDays() so it exposes only days that
getDayTimeline() can render from an existing derived bundle, or ensure raw-only
days are derived before returning them. Preserve the existing day ordering and
return behavior for timeline-renderable days.
In `@lib/ui/journey/journey_screen.dart`:
- Around line 130-148: The _openPicker method currently permits unrecorded
dates; add a selectableDayPredicate to showDatePicker backed by _navigable so
only recorded days are selectable, while preserving the existing earliest/today
bounds and initial-date handling.
---
Outside diff comments:
In `@lib/ui/journey/journey_screen.dart`:
- Around line 54-81: Update _load so overlapping requests cannot apply stale
results: capture the selected day or a request-generation token before awaiting
getDayTimeline, then verify it still matches the current selection before
applying success or error state. Preserve the existing mounted checks and ensure
only the latest request updates _data, _phase, and _error.
🪄 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: aa0c991e-1d7f-4d84-9238-f2654830d575
📒 Files selected for processing (7)
lib/data/db.dartlib/data/local_repository.dartlib/data/local_repository_impl.dartlib/ui/journey/day_nav.dartlib/ui/journey/journey_screen.darttest/day_nav_test.darttest/journey_available_days_test.dart
CodeRabbit flagged that day navigation could land on a blank screen:
availableDays() unioned raw-only decoded_onehz days and included
derivation skip-markers, but getDayTimeline only renders a day with a
genuine derived bundle (returning {} otherwise). Nav chevrons and the
date picker could therefore reach a day that renders empty.
- LocalDb.availableDayIds() now returns exactly the days _bundleForDate
would render: the latest-algo_version day_result per day with
skipped = 0. Dropped the raw-only decoded_onehz union and excluded
skip-markers (the skipped column is set by _markDaySkipped alongside
the {skipped:true} payload). A day with pruned minute-detail still
qualifies — its curves persist in the bundle payload.
- The date picker now passes selectableDayPredicate so only recorded
days are choosable; empty gaps are greyed out instead of selectable.
The prev/next chevrons already step over gaps via the same set.
Tests: availableDays now asserts raw-only and skip-marker days are
excluded and that the latest algo_version decides skip-vs-render; new
DayNav.isSelectable tests cover the picker predicate.
What
The lookback / Your day screen only ever rendered today. This adds day navigation so users can browse past days' lookback — nap times, HR / HRV / respiration / skin-temp and movement trends — one day at a time, the way the Sleep section already lets you.
Almost all the plumbing already existed:
JourneyScreenalready rendered any date passed to it, already handled the retention boundary (minute detail only for recent days; older days show derived summaries) and the partial-"today" fallback, and every day-detail repo method is date-parameterised. So this is essentially a day-navigation UI addition on top of behaviour the screen already supports.Navigation UX
The screen header gains a day-nav bar (in the scaffold's
headerslot, below the title):showDatePicker, bounded to[earliestAvailable, today], to jump straight to a day.RoundIconButton,Pressable,OsAppIcon,Tag) rather than inventing a new style.JourneyScreennow holds a mutable current-date (initialised from the entry date, which stays the entry point). Every navigation re-runs the existing_load()for the new date, so refresh, empty-state and error handling are unchanged.Retention boundary respected
No data or retention behaviour changed — navigation only moves across dates the screen already handles. Retention gating and the partial-today fallback still key off the viewed date, so older days show derived summaries, not minute curves, and truly-empty days show the empty state.
Data
Adds
LocalRepository.availableDays()→LocalDb.availableDayIds(): theUNIONofday_resultday_ids anddecoded_onehzday labels (bucketed to the local calendar day), newest first. It bounds the prev/earliest control and the date picker. A day with aday_resultbut pruned minute-detail still appears (its summary shows); only a day with neither derived row nor raw substrate is absent.Files
lib/ui/journey/journey_screen.dart— mutable current-date state, day-nav header, date picker,_go/_loadDays.lib/ui/journey/day_nav.dart— new pureDayNavhelper (navigable-set / next / prev / earliest maths).lib/data/local_repository.dart—availableDays()on the contract.lib/data/local_repository_impl.dart— implements it.lib/data/db.dart—availableDayIds()query.test/day_nav_test.dart— pure stepping/bounds tests (never past today, stop at earliest, skip gaps).test/journey_available_days_test.dart—availableDays()over the realsqflite_ffiLocalDb(union, ordering, de-dup, empty).Testing
Unit tests cover the new repo method and the pure stepping/bounds logic. The widget wiring (header nav bar, picker, per-date reload) is inspection-verified only — full widget tests would need repo + plugin mocking and are brittle for this screen.
Note
Runtime is unverified — no Flutter/Dart SDK or device was available in this environment, so this was verified by inspection and unit tests, not run on a device.
Closes #112
Summary by CodeRabbit
New Features
Bug Fixes