From 32e0b63f3717f1d9ed280a6c4245add72fd2961d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 21 Mar 2026 04:00:31 +0000 Subject: [PATCH 1/2] Initial plan From 5c2e67fd14f284457dddb022832075b09d98eb4c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 21 Mar 2026 04:08:10 +0000 Subject: [PATCH 2/2] fix: persist sleep score to UserDefaults so it survives app restarts HomeSummary and its child structs are now Codable. HomeSummaryStore seeds summary from UserDefaults when the in-memory cache is cold (on app relaunch), and writes through to UserDefaults in cacheSummary() so every score source (month-cache, direct fetch, historical sync, polling) is persisted. The month-cache path in load() now calls cacheSummary() so those summaries are also durably stored." Co-authored-by: austinkimchi <40729751+austinkimchi@users.noreply.github.com> Agent-Logs-Url: https://github.com/SleepFocus/SleepFocus_Frontend/sessions/9c610f16-b098-4e0f-96c7-8f8dc2bf14d3 --- SleepFocus/Models/HomeSummary.swift | 10 +++++----- SleepFocus/Services/HomeSummaryStore.swift | 22 +++++++++++++++++++++- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/SleepFocus/Models/HomeSummary.swift b/SleepFocus/Models/HomeSummary.swift index 5c51285..0edbc5e 100644 --- a/SleepFocus/Models/HomeSummary.swift +++ b/SleepFocus/Models/HomeSummary.swift @@ -17,7 +17,7 @@ nonisolated struct HomeSummaryMonthPayload: Decodable { let updatedAt: String } -nonisolated struct HomeSummary: Decodable { +nonisolated struct HomeSummary: Codable { let userId: String let selectedDate: String? let sleepQuality: SleepQualitySummary @@ -29,25 +29,25 @@ nonisolated struct HomeSummary: Decodable { let processing: ProcessingSummary? } -nonisolated struct SleepQualitySummary: Decodable { +nonisolated struct SleepQualitySummary: Codable { let score: Double? let maxScore: Double? let status: String? let source: String? } -nonisolated struct FocusPredictionSummary: Decodable { +nonisolated struct FocusPredictionSummary: Codable { let score: Int? let label: String? let source: String? } -nonisolated struct CircadianRhythmSummary: Decodable { +nonisolated struct CircadianRhythmSummary: Codable { let averageWakeTimeMinutes: Int let confidence: Double } -nonisolated struct ProcessingSummary: Decodable { +nonisolated struct ProcessingSummary: Codable { let state: String let lastJobId: String? let updatedAt: String? diff --git a/SleepFocus/Services/HomeSummaryStore.swift b/SleepFocus/Services/HomeSummaryStore.swift index 51a41ee..33cdfa3 100644 --- a/SleepFocus/Services/HomeSummaryStore.swift +++ b/SleepFocus/Services/HomeSummaryStore.swift @@ -34,7 +34,7 @@ final class HomeSummaryStore: ObservableObject { isWaitingForModel = false isSyncing = false - summary = monthSummariesByDay[selectedMonthKey]?[selectedDayKey] + summary = monthSummariesByDay[selectedMonthKey]?[selectedDayKey] ?? loadPersistedSummary(for: selectedDayKey) sleepMetrics = nil sleepStageBreakdown = nil @@ -56,6 +56,7 @@ final class HomeSummaryStore: ObservableObject { var resolvedSummary = monthSummariesByDay[selectedMonthKey]?[selectedDayKey] if let resolvedSummary { summary = resolvedSummary + cacheSummary(resolvedSummary, fallbackDate: selectedDate) } if resolvedSummary == nil { @@ -195,6 +196,25 @@ final class HomeSummaryStore: ObservableObject { var monthMap = monthSummariesByDay[monthKey] ?? [:] monthMap[dayKey] = summary monthSummariesByDay[monthKey] = monthMap + + persistSummary(summary, for: dayKey) + } + + private func loadPersistedSummary(for dayKey: String) -> HomeSummary? { + guard let data = UserDefaults.standard.data(forKey: Self.persistenceKey(for: dayKey)), + let decoded = try? JSONDecoder().decode(HomeSummary.self, from: data) else { + return nil + } + return decoded + } + + private func persistSummary(_ summary: HomeSummary, for dayKey: String) { + guard let data = try? JSONEncoder().encode(summary) else { return } + UserDefaults.standard.set(data, forKey: Self.persistenceKey(for: dayKey)) + } + + private static func persistenceKey(for dayKey: String) -> String { + "homeSummary.\(dayKey)" } // Ramp up polling to improve fetching performance without spamming backend