From 9e84650a8ef83b0993583f9f5af189a10045749e Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 4 Jun 2026 14:25:25 +0000 Subject: [PATCH 1/8] Show all days in recent sleep list, including days without data Fill the Recent Sleep list with every day in the 30-day history window instead of only days that have HealthKit sessions. Empty days display 'No sleep data', 0:00 duration, and the full goal deficit. Co-authored-by: Greg --- Bedtime/Bedtime/Constants.swift | 1 + Bedtime/Bedtime/Models/HealthKitManager.swift | 2 +- .../Views/Components/SleepDayGroup.swift | 31 +++++++++++++------ .../Views/RecentSleepSessionsCard.swift | 11 +++++-- 4 files changed, 33 insertions(+), 12 deletions(-) diff --git a/Bedtime/Bedtime/Constants.swift b/Bedtime/Bedtime/Constants.swift index ba9082d..945e4e9 100644 --- a/Bedtime/Bedtime/Constants.swift +++ b/Bedtime/Bedtime/Constants.swift @@ -9,4 +9,5 @@ import Foundation class Constants { static let iconWidth: CGFloat = 30 + static let sleepHistoryDays = 30 } diff --git a/Bedtime/Bedtime/Models/HealthKitManager.swift b/Bedtime/Bedtime/Models/HealthKitManager.swift index ebad451..d10d491 100644 --- a/Bedtime/Bedtime/Models/HealthKitManager.swift +++ b/Bedtime/Bedtime/Models/HealthKitManager.swift @@ -83,7 +83,7 @@ class HealthKitManager: ObservableObject { private func fetchSleepDataForDisplay() async throws { let calendar = Calendar.current let endDate = Date() - guard let startDate = calendar.date(byAdding: .day, value: -30, to: endDate) else { + guard let startDate = calendar.date(byAdding: .day, value: -Constants.sleepHistoryDays, to: endDate) else { throw NSError(domain: "HealthKitManager", code: 1, userInfo: [NSLocalizedDescriptionKey: "Failed to calculate start date"]) } diff --git a/Bedtime/Bedtime/Views/Components/SleepDayGroup.swift b/Bedtime/Bedtime/Views/Components/SleepDayGroup.swift index ce2a9cf..b91e141 100644 --- a/Bedtime/Bedtime/Views/Components/SleepDayGroup.swift +++ b/Bedtime/Bedtime/Views/Components/SleepDayGroup.swift @@ -33,19 +33,29 @@ struct SleepDayGroup: View { return (abs(difference), difference >= 0, color) } + private var hasSessions: Bool { + !sessions.isEmpty + } + var body: some View { VStack(spacing: 0) { // Day header (always visible) - Button(action: onToggle) { + Button(action: hasSessions ? onToggle : {}) { HStack { VStack(alignment: .leading, spacing: 2) { Text(dateFormatter.string(from: date)) .font(.subheadline) .fontWeight(.medium) - Text("\(timeFormatter.string(from: sessions.last?.startDate ?? Date())) - \(timeFormatter.string(from: sessions.first?.endDate ?? Date()))") - .font(.caption) - .foregroundColor(.secondary) + if hasSessions { + Text("\(timeFormatter.string(from: sessions.last?.startDate ?? Date())) - \(timeFormatter.string(from: sessions.first?.endDate ?? Date()))") + .font(.caption) + .foregroundColor(.secondary) + } else { + Text("No sleep data") + .font(.caption) + .foregroundColor(.secondary) + } } Spacer() @@ -54,7 +64,7 @@ struct SleepDayGroup: View { Text(TimeFormatter.formatDuration(sessions.reduce(0) { $0 + $1.duration })) .font(.subheadline) .fontWeight(.semibold) - .foregroundColor(.primary) + .foregroundColor(hasSessions ? .primary : .secondary) HStack(spacing: 2) { Text(balanceImpact.isPositive ? "+" : "-") @@ -67,17 +77,20 @@ struct SleepDayGroup: View { } } - Image(systemName: isExpanded ? "chevron.up" : "chevron.down") - .font(.caption) - .foregroundColor(.secondary) + if hasSessions { + Image(systemName: isExpanded ? "chevron.up" : "chevron.down") + .font(.caption) + .foregroundColor(.secondary) + } } .padding(.vertical, 8) .contentShape(Rectangle()) } .buttonStyle(PlainButtonStyle()) + .disabled(!hasSessions) // Session details (expandable) - if isExpanded { + if isExpanded && hasSessions { VStack(spacing: 4) { ForEach(Array(sessions.enumerated()), id: \.offset) { index, session in SleepSessionRow(session: session) diff --git a/Bedtime/Bedtime/Views/RecentSleepSessionsCard.swift b/Bedtime/Bedtime/Views/RecentSleepSessionsCard.swift index e061641..aad860f 100644 --- a/Bedtime/Bedtime/Views/RecentSleepSessionsCard.swift +++ b/Bedtime/Bedtime/Views/RecentSleepSessionsCard.swift @@ -9,10 +9,17 @@ import SwiftUI struct RecentSleepSessionsCard: View { - init(sessions: [Date: [SleepSession]], sleepGoal: Double) { + init(sessions: [Date: [SleepSession]], sleepGoal: Double, dayCount: Int = Constants.sleepHistoryDays) { self.sessions = sessions - self.sortedSessions = sessions.sorted { $0.key > $1.key } self.sleepGoal = sleepGoal + + let calendar = Calendar.current + let today = calendar.startOfDay(for: Date()) + self.sortedSessions = (0.. (Date, [SleepSession])? in + guard let day = calendar.date(byAdding: .day, value: -offset, to: today) else { return nil } + let dayStart = calendar.startOfDay(for: day) + return (dayStart, sessions[dayStart] ?? []) + } } let sessions: [Date: [SleepSession]] From e1e1c7802f6491c03869c1a4a468c14dc6b0e6f4 Mon Sep 17 00:00:00 2001 From: Greg Date: Sat, 6 Jun 2026 08:42:34 -0700 Subject: [PATCH 2/8] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- Bedtime/Bedtime/Views/Components/SleepDayGroup.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Bedtime/Bedtime/Views/Components/SleepDayGroup.swift b/Bedtime/Bedtime/Views/Components/SleepDayGroup.swift index b91e141..34c12ba 100644 --- a/Bedtime/Bedtime/Views/Components/SleepDayGroup.swift +++ b/Bedtime/Bedtime/Views/Components/SleepDayGroup.swift @@ -40,7 +40,7 @@ struct SleepDayGroup: View { var body: some View { VStack(spacing: 0) { // Day header (always visible) - Button(action: hasSessions ? onToggle : {}) { + Button(action: onToggle) { HStack { VStack(alignment: .leading, spacing: 2) { Text(dateFormatter.string(from: date)) From 42df836da7d2bfa2522c807d42a5a771c92746a1 Mon Sep 17 00:00:00 2001 From: Greg Date: Sat, 6 Jun 2026 09:35:06 -0700 Subject: [PATCH 3/8] should be safe --- Bedtime/Bedtime/Views/Components/SleepDayGroup.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Bedtime/Bedtime/Views/Components/SleepDayGroup.swift b/Bedtime/Bedtime/Views/Components/SleepDayGroup.swift index 34c12ba..cc13972 100644 --- a/Bedtime/Bedtime/Views/Components/SleepDayGroup.swift +++ b/Bedtime/Bedtime/Views/Components/SleepDayGroup.swift @@ -48,7 +48,7 @@ struct SleepDayGroup: View { .fontWeight(.medium) if hasSessions { - Text("\(timeFormatter.string(from: sessions.last?.startDate ?? Date())) - \(timeFormatter.string(from: sessions.first?.endDate ?? Date()))") + Text("\(timeFormatter.string(from: sessions.last!.startDate)) - \(timeFormatter.string(from: sessions.first!.endDate))") .font(.caption) .foregroundColor(.secondary) } else { From 83e4745debf9391a2463cb179e8444292735e158 Mon Sep 17 00:00:00 2001 From: Greg Date: Sat, 6 Jun 2026 09:39:43 -0700 Subject: [PATCH 4/8] don't fetch extra day --- Bedtime/Bedtime/Models/HealthKitManager.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Bedtime/Bedtime/Models/HealthKitManager.swift b/Bedtime/Bedtime/Models/HealthKitManager.swift index d10d491..683d974 100644 --- a/Bedtime/Bedtime/Models/HealthKitManager.swift +++ b/Bedtime/Bedtime/Models/HealthKitManager.swift @@ -83,7 +83,8 @@ class HealthKitManager: ObservableObject { private func fetchSleepDataForDisplay() async throws { let calendar = Calendar.current let endDate = Date() - guard let startDate = calendar.date(byAdding: .day, value: -Constants.sleepHistoryDays, to: endDate) else { + let today = calendar.startOfDay(for: endDate) + guard let startDate = calendar.date(byAdding: .day, value: -(Constants.sleepHistoryDays - 1), to: today) else { throw NSError(domain: "HealthKitManager", code: 1, userInfo: [NSLocalizedDescriptionKey: "Failed to calculate start date"]) } From 675016801e5166c13970b13ef18748de50eeef09 Mon Sep 17 00:00:00 2001 From: Greg Date: Sat, 6 Jun 2026 09:43:37 -0700 Subject: [PATCH 5/8] don't show debt for empty days --- .../Views/Components/SleepDayGroup.swift | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/Bedtime/Bedtime/Views/Components/SleepDayGroup.swift b/Bedtime/Bedtime/Views/Components/SleepDayGroup.swift index cc13972..d306609 100644 --- a/Bedtime/Bedtime/Views/Components/SleepDayGroup.swift +++ b/Bedtime/Bedtime/Views/Components/SleepDayGroup.swift @@ -61,19 +61,21 @@ struct SleepDayGroup: View { Spacer() VStack(alignment: .trailing, spacing: 2) { - Text(TimeFormatter.formatDuration(sessions.reduce(0) { $0 + $1.duration })) - .font(.subheadline) - .fontWeight(.semibold) - .foregroundColor(hasSessions ? .primary : .secondary) - - HStack(spacing: 2) { - Text(balanceImpact.isPositive ? "+" : "-") - .font(.caption) - .foregroundColor(balanceImpact.color) + if hasSessions { + Text(TimeFormatter.formatDuration(sessions.reduce(0) { $0 + $1.duration })) + .font(.subheadline) + .fontWeight(.semibold) + .foregroundColor(.primary) - Text(String(format: "%.1fh", balanceImpact.value)) - .font(.caption) - .foregroundColor(balanceImpact.color) + HStack(spacing: 2) { + Text(balanceImpact.isPositive ? "+" : "-") + .font(.caption) + .foregroundColor(balanceImpact.color) + + Text(String(format: "%.1fh", balanceImpact.value)) + .font(.caption) + .foregroundColor(balanceImpact.color) + } } } From bff06dba8885b7bdd3a498036f9d4f62810d683d Mon Sep 17 00:00:00 2001 From: Greg Date: Sat, 6 Jun 2026 09:43:47 -0700 Subject: [PATCH 6/8] minor --- .vscode/settings.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 3503ee7..7808894 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,5 +6,6 @@ }, "sweetpad.xcodebuildserver.serverEnv": { "DEVELOPER_DIR": "/Applications/Xcode.app/Contents/Developer" - } + }, + "diffEditor.ignoreTrimWhitespace": true } From d0b9a850735e31f36547d3b42a5464482ff2afbe Mon Sep 17 00:00:00 2001 From: Greg Date: Sat, 6 Jun 2026 09:45:56 -0700 Subject: [PATCH 7/8] revert --- Bedtime/Bedtime/Models/HealthKitManager.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Bedtime/Bedtime/Models/HealthKitManager.swift b/Bedtime/Bedtime/Models/HealthKitManager.swift index 683d974..b32190c 100644 --- a/Bedtime/Bedtime/Models/HealthKitManager.swift +++ b/Bedtime/Bedtime/Models/HealthKitManager.swift @@ -84,7 +84,8 @@ class HealthKitManager: ObservableObject { let calendar = Calendar.current let endDate = Date() let today = calendar.startOfDay(for: endDate) - guard let startDate = calendar.date(byAdding: .day, value: -(Constants.sleepHistoryDays - 1), to: today) else { + // Fetch one "extra" day (intentional off-by-one) to include overnight sleep on the first day. + guard let startDate = calendar.date(byAdding: .day, value: -Constants.sleepHistoryDays, to: today) else { throw NSError(domain: "HealthKitManager", code: 1, userInfo: [NSLocalizedDescriptionKey: "Failed to calculate start date"]) } From 288f30185f10866c422d0be641a62bae363b92bd Mon Sep 17 00:00:00 2001 From: Greg Date: Sat, 6 Jun 2026 09:56:16 -0700 Subject: [PATCH 8/8] clarify comment --- Bedtime/Bedtime/Models/HealthKitManager.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Bedtime/Bedtime/Models/HealthKitManager.swift b/Bedtime/Bedtime/Models/HealthKitManager.swift index b32190c..9889942 100644 --- a/Bedtime/Bedtime/Models/HealthKitManager.swift +++ b/Bedtime/Bedtime/Models/HealthKitManager.swift @@ -84,7 +84,9 @@ class HealthKitManager: ObservableObject { let calendar = Calendar.current let endDate = Date() let today = calendar.startOfDay(for: endDate) - // Fetch one "extra" day (intentional off-by-one) to include overnight sleep on the first day. + // Fetch one extra day before the UI range: grouping (midpoint + 6h) can assign + // sessions that start the previous evening to the oldest displayed day, including + // short blocks (e.g. 9–11pm) as well as overnight sleep. guard let startDate = calendar.date(byAdding: .day, value: -Constants.sleepHistoryDays, to: today) else { throw NSError(domain: "HealthKitManager", code: 1, userInfo: [NSLocalizedDescriptionKey: "Failed to calculate start date"]) }