Seed UserPreferences at launch instead of during view rendering - #41
Open
gsbernstein wants to merge 1 commit into
Open
Seed UserPreferences at launch instead of during view rendering#41gsbernstein wants to merge 1 commit into
gsbernstein wants to merge 1 commit into
Conversation
ContentView created and inserted the default UserPreferences record from a computed property read while SwiftUI evaluated body. SwiftUI reads view properties an unspecified number of times per update, so that could insert several default records and mutated persistent state mid-render. Seed the record in BedtimeApp.init instead, before the view tree is built, and have ContentView read preferences without writing. The dashboard is gated on the fetched record; the placeholder branch only appears if that seed failed or in previews starting from an empty in-memory container, and seeds from .task rather than from body. Co-authored-by: Greg <gsbernstein@users.noreply.github.com>
gsbernstein
marked this pull request as ready for review
August 7, 2026 22:33
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.
Why the insert exists at all
SwiftData persists the schema, but it never creates model instances. On a fresh install — or on the launch right after the store-reset recovery in
BedtimeApp— the store is empty, so@Query private var preferences: [UserPreferences]comes back with nothing and there is no settings record to read a sleep goal or wake time from. Something has to insert the firstUserPreferences()so its defaults become a real, editable, persisted row.The insert is necessary. Doing it while rendering is not.
Problem
ContentViewcreated and inserted that record from a computed property:userPreferenceswas read frombodyand from six other computed properties feeding it (sleepBank,fullWindowSleepBank,bedtimeRecommendation,sleepBankInsight, and two bindings). SwiftUI reads a view's properties an unspecified number of times per update, and@Querydoes not reflect the insert until the context change propagates, so on an empty store a single update could:This is a latent correctness issue rather than the cause of the recent TestFlight crash — that one was the
ModelContainerfatalError, whichmasteralready recovers from.Fix
Seed once in
BedtimeApp.init(), before the view tree exists:It fetches with
fetchLimit = 1, inserts only when the store is genuinely empty, and saves. A failed fetch deliberately does not insert.ContentViewthen only ever reads. The dashboard is gated on the fetched record, and the six preference-dependent computed properties became functions taking the resolvedUserPreferences, so the values are computed once per update and shared by every card:The placeholder branch is only reachable if the launch seed failed, or in previews and tests that start from an empty in-memory container. It seeds from
.task— an explicit side-effect context — rather than frombody.Notes for review
.task { fetchSleepData() }stays attached to the dashboard'sNavigationStack, matching its current placement, so the HealthKit fetch is unaffected.insertcalls are now outside body evaluation — one at app init, one inside.task.Verification
Xcode Cloud is green on this branch:
That covers compilation and archiving only. Runtime behavior can't be exercised here — the Cloud Agent environment is Linux, and Xcode plus the Apple frameworks are macOS-only — so this is worth a manual pass on a simulator: delete the app first, launch, confirm the dashboard appears without a flash of the placeholder, change a setting, and relaunch to confirm it persisted against a single settings record.