Add bedtime Live Activity - #37
Draft
gsbernstein wants to merge 77 commits into
Draft
Conversation
Co-authored-by: Greg <gsbernstein@users.noreply.github.com>
Co-authored-by: Greg <gsbernstein@users.noreply.github.com>
Co-authored-by: Greg <gsbernstein@users.noreply.github.com>
Co-authored-by: Greg <gsbernstein@users.noreply.github.com>
Co-authored-by: Greg <gsbernstein@users.noreply.github.com>
Co-authored-by: Greg <gsbernstein@users.noreply.github.com>
…-activity-43cf # Conflicts: # Bedtime/Bedtime/ContentView.swift # Bedtime/Bedtime/Views/BedtimeRecommendationCard.swift # README.md Co-authored-by: Greg <gsbernstein@users.noreply.github.com>
Co-authored-by: Greg <gsbernstein@users.noreply.github.com>
Co-authored-by: Greg <gsbernstein@users.noreply.github.com>
Co-authored-by: Greg <gsbernstein@users.noreply.github.com>
Co-authored-by: Greg <gsbernstein@users.noreply.github.com>
Co-authored-by: Greg <gsbernstein@users.noreply.github.com>
Co-authored-by: Greg <gsbernstein@users.noreply.github.com>
Co-authored-by: Greg <gsbernstein@users.noreply.github.com>
Co-authored-by: Greg <gsbernstein@users.noreply.github.com>
Co-authored-by: Greg <gsbernstein@users.noreply.github.com>
Co-authored-by: Greg <gsbernstein@users.noreply.github.com>
Co-authored-by: Greg <gsbernstein@users.noreply.github.com>
Co-authored-by: Greg <gsbernstein@users.noreply.github.com>
Co-authored-by: Greg <gsbernstein@users.noreply.github.com>
Co-authored-by: Greg <gsbernstein@users.noreply.github.com>
Co-authored-by: Greg <gsbernstein@users.noreply.github.com>
Co-authored-by: Greg <gsbernstein@users.noreply.github.com>
Co-authored-by: Greg <gsbernstein@users.noreply.github.com>
Co-authored-by: Greg <gsbernstein@users.noreply.github.com>
# Conflicts: # Bedtime/Bedtime.xcodeproj/xcshareddata/xcschemes/Bedtime.xcscheme
gsbernstein
force-pushed
the
cursor/live-bedtime-activity-43cf
branch
from
August 14, 2026 15:04
6195876 to
389b9fb
Compare
Co-authored-by: Greg <gsbernstein@users.noreply.github.com>
Co-authored-by: Greg <gsbernstein@users.noreply.github.com>
HealthKit background delivery and a BGProcessingTask backup call LiveActivityManager.markAwakeIfNeeded(), which flips a new isAwake flag and clears staleDate on the existing active/stale activity without recomputing the recommendation. The widget adds a third phase for this, showing a link to the Oura app (or a generic open-app prompt) instead of a countdown once the person wakes up. staleDate is a one-shot latch already spent on the wind-down->sleeping transition, so a second transition needs the app to actually run and push a fresh update rather than a second local staleness flip. Co-authored-by: Greg <gsbernstein@users.noreply.github.com>
Co-authored-by: Greg <gsbernstein@users.noreply.github.com>
Co-authored-by: Greg <gsbernstein@users.noreply.github.com>
Removes SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor from the project's Debug/Release configs (shared by both targets) and the nonisolated markers that only existed to counteract it (Constants, SleepWindow, the TimeInterval extension, BedtimeActivityAttributes/ BedtimeSourceAppLink). Adds the two things that were actually relying on the implicit default rather than an explicit annotation: @mainactor on SourcePreferences (an ObservableObject, like HealthKitManager and LiveActivityManager), and nonisolated on LiveActivityManager.registerBackgroundTask() so it can still be called synchronously from BedtimeApp.init(), which is no longer MainActor-isolated by default. Co-authored-by: Greg <gsbernstein@users.noreply.github.com>
Co-authored-by: Greg <gsbernstein@users.noreply.github.com>
refreshBedtimePlan() only ran on initial launch and returning from background, so adjusting the sleep bank range, sleep goal, or wake time while the app stayed foregrounded updated the on-screen recommendation but never told the Live Activity about it. Makes BedtimeRecommendation Equatable and adds .onChange(of:) so any foreground change to the computed recommendation re-syncs it. Co-authored-by: Greg <gsbernstein@users.noreply.github.com>
activeActivityID was cached from Activity.activities.first, which isn't guaranteed to be the on-screen activity once the multi-night queue can add several .pending future nights to that array. If it latched onto a pending night instead of tonight's active/stale one, startOrUpdate would look it up, find it .pending, end it (destroying a correctly queued future night), and create a brand-new separate activity — leaving the one actually on screen untouched. That's why neither the auto-sync nor the manual Update button appeared to do anything: they were updating something you couldn't see. Adds currentlyVisibleActivity(), which finds whatever's .active or .stale (on screen) directly rather than trusting the cached id, and uses it in init(), startOrUpdate(), and markAwakeIfNeeded(). Each of those also now writes activeActivityID back afterward, so the cache self-heals instead of staying wrong once it's drifted. Co-authored-by: Greg <gsbernstein@users.noreply.github.com>
startOrUpdate only ended whichever single activity 'existingActivity'
happened to resolve to before falling through to Activity.request().
Once the multi-night queue can leave several .pending nights
scheduled at once, that left the others still counting against the
5-concurrent-activity budget, so request() failed with
targetMaximumExceeded ('Maximum number of activities for target
already exists') even though nothing was visibly on screen.
Now ends every .pending activity before requesting a new one:
starting/updating right now always takes priority over a background
pre-scheduled queue, which syncWithSchedule rebuilds fresh anyway.
Co-authored-by: Greg <gsbernstein@users.noreply.github.com>
Co-authored-by: Greg <gsbernstein@users.noreply.github.com>
- registerBackgroundTask's launch handler captured the non-Sendable
BGTask into a nested Task { @mainactor in }; nonisolated(unsafe)
matches the existing HealthKit completionHandler workaround.
- startOrUpdate's shared 'existingActivity' let was read at two
separate points (computing startTime, then the if-let/await), which
taints it for sending under strict concurrency. Replaced with a
local function that re-derives it fresh at each call site, same
fix as the earlier scheduleUpcomingNights issue.
Co-authored-by: Greg <gsbernstein@users.noreply.github.com>
The previous fix used a local nested function, but local functions referencing self still inherit the enclosing method's MainActor isolation at their call site, which taints the returned Activity for the region just like a stored let did. Added existingActivity(cachedID:) as a real nonisolated static function (taking the cached ID as a parameter instead of reading self), matching the existing activity(withID:)/currentlyVisibleActivity() pattern, and call it fresh at each use site in startOrUpdate. Co-authored-by: Greg <gsbernstein@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a Live Activity for tonight's bedtime countdown, shown on the Lock Screen and in the Dynamic Island (compact, expanded, and minimal presentations), plus in-app controls to start/update/end it.
Highlights:
LiveActivityManagerstarts, updates, schedules, and ends the activity, and auto-syncs with the current recommendation on foreground.BedtimePlanStorepersists the latest recommendation so a background App Intent (StartBedtimeCountdownIntent/EndBedtimeCountdownIntent, exposed viaAppShortcutsProvider) can start/stop the countdown from a Shortcuts automation without launching the app.BedtimeLiveActivityWidgetKit extension target wired into the Xcode project, deploying to iOS 18 (gated behindLiveActivityManager.isSupportedin the app UI, which stays on iOS 17.6).Latest changes: extracted the progress-bar layout-direction handling into a shared
PhaseProgressBarview, and added a thin linear progress bar under the compact trailing countdown text so it mirrors the wind-down/sleep progress shown elsewhere.