Skip to content

Schedule loading, clear, and Health screen overhaul#16

Merged
ng merged 6 commits intomainfrom
fix/schedule-load
Mar 16, 2026
Merged

Schedule loading, clear, and Health screen overhaul#16
ng merged 6 commits intomainfrom
fix/schedule-load

Conversation

@ng
Copy link
Copy Markdown
Contributor

@ng ng commented Mar 16, 2026

Summary

  • Schedule write fix: updateSchedules now only deletes/recreates entries for days with data instead of wiping all days. Skips cross-midnight power schedules (core#205 workaround)
  • Schedule loading: Smart curve loads existing schedule data on day selection
  • Clear schedule: Button with confirmation alert to clear selected days
  • Sleep stages hypnogram: Replaces presence-only timeline with real Wake/REM/Light/Deep chart from SleepAnalyzer, with tap interaction showing HR/HRV/BR
  • Unified sleep analysis card: Merged score ring + distribution bar + hypnogram into one card ("Sleepypod Intelligence")
  • Side toggle pill: Compact pill in Health header showing side name + L/R badge, tap to switch
  • Calibration warning: Now checks only selected side, non-blocking (scrollable)
  • Link button: Repositioned below side selector on Temp screen to avoid overlap

Test plan

  • Apply smart curve to Monday, verify other days untouched
  • Clear schedule for selected days
  • Verify sleep stages render with vitals data
  • Tap hypnogram to see epoch details
  • Toggle side pill on Health screen, verify data refreshes
  • Check link button position on Temp screen

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Added ability to clear selected schedules with confirmation dialog.
    • Enhanced sleep stages visualization with quality scoring and detailed hypnogram display.
    • Inline side toggle in Health screen header for quicker navigation.
  • Style

    • Redesigned Health screen layout for improved organization.
    • Adjusted spacing on temperature and selector views.

- On appear + day change: loads bedtime from power schedule, wake from
  alarm, min/max from temperature set points
- Tapping a different day updates the curve to reflect saved data
- If no schedule exists, defaults remain (22:00-07:00, 68-86°F)
- Apply to Schedule now works end-to-end (core#199 was fixed)
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 16, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

Adds a schedule-clear UI and backend flow to ScheduleScreen; syncs SmartCurveView state from schedules on appear/selection changes; reworks updateSchedules to handle per-side, per-day delete/create logic; significantly expands SleepStagesTimelineView and adjusts HealthScreen, SideSelector/TempScreen layout tweaks, and Xcode project signing info.

Changes

Cohort / File(s) Summary
Schedule UI & SmartCurve
Sleepypod/Views/Schedule/ScheduleScreen.swift, Sleepypod/Views/Schedule/SmartCurveView.swift
Adds "Clear Schedule" confirmation and clearSchedule() that clears selected days and calls API; adds loadFromSchedule() in SmartCurveView and hooks it to onAppear and onChange to populate bedtime, wakeTime, min/max temps.
Networking: per-day schedule handling
Sleepypod/Networking/SleepypodCoreClient.swift
Reworks updateSchedules to delete/create schedules per-side per-day, only recreate when per-day hasData (temperatures, power, or alarm), adds minute conversion helper and guard for power schedules spanning midnight.
Sleep stages & Health UI
Sleepypod/Views/Data/SleepStagesTimelineView.swift, Sleepypod/Views/Data/HealthScreen.swift
Large feature expansion: SleepStagesTimelineView gains staged hypnogram model, merged epoch blocks, chart rendering, and optional qualityScore input; HealthScreen reworks side toggle, layout, removes Sleep Analysis card, and adds debug logging.
Temp UI adjustments
Sleepypod/Views/Temp/SideSelectorView.swift, Sleepypod/Views/Temp/TempScreen.swift
Layout tweaks: button overlay alignment and increased bottom padding for SideSelectorView/TempScreen.
Project config
Sleepypod.xcodeproj/project.pbxproj
Project object/version and signing config updated (DEVELOPMENT_TEAM added, explicitFileType set for app file reference).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ScheduleScreen as "ScheduleScreen (UI)"
    participant Client as "SleepypodCoreClient"
    participant Server as "API/Server"
    participant Manager as "scheduleManager"

    User->>ScheduleScreen: Tap "Clear Schedule"
    ScheduleScreen->>User: Present confirmation alert
    User->>ScheduleScreen: Confirm clear
    ScheduleScreen->>Manager: update local selection -> prepare cleared DailySchedule
    ScheduleScreen->>Client: clearSchedule() -> updateSchedules(per-side, per-day)
    Client->>Server: DELETE / POST per-side per-day changes
    Server-->>Client: 200 OK
    Client-->>Manager: refresh schedules / update state
    Manager-->>ScheduleScreen: selectedDay/state updated
    ScheduleScreen->>User: Haptic + UI reflects cleared days
Loading
sequenceDiagram
    participant View as "SmartCurveView"
    participant Manager as "scheduleManager"
    participant UIState as "Local view state (bedtime,wake,temps)"

    View->>Manager: onAppear / onChange(selectedDay or schedules)
    Manager-->>View: currentDailySchedule
    View->>UIState: loadFromSchedule() -> set bedtime, wakeTime, minTemp, maxTemp
    UIState-->>View: UI updates controls/curve
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I nudged the schedule, soft and light,
Cleared the days to let mornings bright.
Curves wake up and temps align,
Hops of code make nesting fine.
A tidy burrow, all in sight!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.32% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Schedule loading, clear, and Health screen overhaul' accurately summarizes the main changes: schedule loading/clearing logic and a significant Health screen redesign.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/schedule-load
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

- 'Clear Schedule' button appears when phases exist for selected days
- Confirmation alert: 'Clear Selected Days' / Cancel
- Clears temperature, power, and alarm for all selected days + side
- Deletes via updateSchedules (delete-all-then-recreate)
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

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 the current code and only fix it if needed.

Inline comments:
In `@Sleepypod/Views/Schedule/ScheduleScreen.swift`:
- Around line 113-120: The code optimistically sets scheduleManager.schedules
before persisting with APIBackend.current.createClient() and
api.updateSchedules(schedules); if that call fails you must revert
scheduleManager.schedules to the previous value and surface an error to the user
instead of silently logging it. Capture the previous schedules (e.g., let
previous = scheduleManager.schedules) before mutating, await
api.updateSchedules, and on catch restore scheduleManager.schedules = previous,
avoid triggering Haptics.heavy() on failure, and present a user-visible error
(alert/toast) in addition to Log.general.error("Failed to clear schedule:
\(error)").

In `@Sleepypod/Views/Schedule/SmartCurveView.swift`:
- Around line 521-523: The loadFromSchedule() function currently early-returns
when scheduleManager.currentDailySchedule is nil and leaves previous state in
place; change it to apply default values when no schedule exists by removing the
early return and using the app's default min/max/temperatures (or explicit
defaults) whenever currentDailySchedule is nil. Also, when daily.temperatures is
empty (the block around the temperatures check at lines ~546-550), ensure you
reset minTemp and maxTemp to those same defaults instead of leaving stale
values; update the logic in loadFromSchedule() to compute min/max only when
temperatures.nonEmpty and otherwise assign defaults, referencing
scheduleManager.currentDailySchedule, daily.temperatures, and the
minTemp/maxTemp properties in that method.
- Around line 173-174: The view only calls loadFromSchedule() on appear and when
scheduleManager.selectedDay changes, so it misses updates when schedules finish
loading; update SmartCurveView to also trigger loadFromSchedule() when the
schedule data changes (e.g., add an .onChange(of: scheduleManager.schedules) or
.onChange(of: scheduleManager.isLoaded / isLoading) modifier) so that when the
ScheduleManager finishes loading its schedules it will call loadFromSchedule()
and populate the view.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8bb60b42-14b3-4003-ac8a-b992a39a19e0

📥 Commits

Reviewing files that changed from the base of the PR and between 6871200 and 7ea2f13.

📒 Files selected for processing (2)
  • Sleepypod/Views/Schedule/ScheduleScreen.swift
  • Sleepypod/Views/Schedule/SmartCurveView.swift

Comment on lines +113 to +120
scheduleManager.schedules = schedules
do {
let api = APIBackend.current.createClient()
scheduleManager.schedules = try await api.updateSchedules(schedules)
Haptics.heavy()
} catch {
Log.general.error("Failed to clear schedule: \(error)")
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Failed clear leaves local state out of sync with backend.

At Line 113, state is mutated optimistically, but on failure (Line 118-120) there is no rollback or user-visible error. Users can see a cleared schedule that was never persisted.

Proposed fix
-        scheduleManager.schedules = schedules
+        let previousSchedules = scheduleManager.schedules
+        scheduleManager.schedules = schedules
         do {
             let api = APIBackend.current.createClient()
             scheduleManager.schedules = try await api.updateSchedules(schedules)
             Haptics.heavy()
         } catch {
+            scheduleManager.schedules = previousSchedules
             Log.general.error("Failed to clear schedule: \(error)")
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
scheduleManager.schedules = schedules
do {
let api = APIBackend.current.createClient()
scheduleManager.schedules = try await api.updateSchedules(schedules)
Haptics.heavy()
} catch {
Log.general.error("Failed to clear schedule: \(error)")
}
let previousSchedules = scheduleManager.schedules
scheduleManager.schedules = schedules
do {
let api = APIBackend.current.createClient()
scheduleManager.schedules = try await api.updateSchedules(schedules)
Haptics.heavy()
} catch {
scheduleManager.schedules = previousSchedules
Log.general.error("Failed to clear schedule: \(error)")
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sleepypod/Views/Schedule/ScheduleScreen.swift` around lines 113 - 120, The
code optimistically sets scheduleManager.schedules before persisting with
APIBackend.current.createClient() and api.updateSchedules(schedules); if that
call fails you must revert scheduleManager.schedules to the previous value and
surface an error to the user instead of silently logging it. Capture the
previous schedules (e.g., let previous = scheduleManager.schedules) before
mutating, await api.updateSchedules, and on catch restore
scheduleManager.schedules = previous, avoid triggering Haptics.heavy() on
failure, and present a user-visible error (alert/toast) in addition to
Log.general.error("Failed to clear schedule: \(error)").

Comment thread Sleepypod/Views/Schedule/SmartCurveView.swift
Comment thread Sleepypod/Views/Schedule/SmartCurveView.swift
… power

Previously updateSchedules deleted ALL schedule entries then recreated
everything, causing edits to one day to wipe other days. Now only deletes
and recreates entries for the specific day being updated. Also skips
power schedules that cross midnight (core#205 workaround).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Sleepypod/Networking/SleepypodCoreClient.swift`:
- Around line 181-183: The current check converts only the hour prefix of
daily.power.on/off into Ints (onHour/offHour) and defaults parse failures to 0,
which breaks same-hour ranges like "22:15->22:45" and misclassifies malformed
times; instead parse both hour and minute components from daily.power.on and
daily.power.off into total minutes (e.g., onMinutes = hour*60 + minute) using
safe optional parsing (do not default to 0), then compare onMinutes and
offMinutes to determine the window (handle equal-hours by comparing minutes),
and treat parse failures as invalid by returning false or skipping the range;
update the condition around where onHour/offHour are used to use these
total-minute comparisons and handle overnight ranges (offMinutes <= onMinutes
means spans midnight) accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3d3f75d3-173c-4b2a-a766-7de5e3526024

📥 Commits

Reviewing files that changed from the base of the PR and between 7ea2f13 and c664522.

📒 Files selected for processing (1)
  • Sleepypod/Networking/SleepypodCoreClient.swift

Comment thread Sleepypod/Networking/SleepypodCoreClient.swift Outdated
ng and others added 2 commits March 16, 2026 14:21
Rewrites SleepStagesTimelineView as a real hypnogram showing
Wake/REM/Light/Deep stages from SleepAnalyzer epochs. Falls back
to bed presence chart when no vitals data. Adds tap interaction
for HR/HRV/BR tooltip and stage duration legend.

Also adds DEVELOPMENT_TEAM for device signing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…sitioned

- Merge sleep analysis + hypnogram into single card with score ring,
  distribution bar, and interactive chart
- Add compact side toggle pill (shows name + L/R badge) in Health header
- Calibration warning checks only selected side, moves into scroll
- Reposition link button below side selector on Temp screen
- Remove debug logging from fetchVitals

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ng ng changed the title feat: smart curve loads from existing schedule Schedule loading, clear, and Health screen overhaul Mar 16, 2026
…arsing

- clearSchedule: rollback optimistic update on API failure
- loadFromSchedule: reset to defaults before loading, handles nil schedule
- SmartCurveView: reload when schedules finish loading (onChange)
- Power schedule: parse full HH:mm to minutes for cross-midnight check

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

🎉 This PR is included in version 1.0.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant