Fix boundary bugs, route side effects through MoodLogger, add data listeners

- Fix ExtensionDataProvider <= boundary to < in date queries (prevented cross-day leaks)
- Replace force-unwraps with guards and add error logging in DataControllerGET and ExtensionDataProvider
- Route DayViewViewModel update/delete through MoodLogger.shared (was duplicating side effects)
- Add data listeners to InsightsViewModel and YearViewModel for cross-tab refresh
- Add HealthKitManager.deleteMood(for:) for single-date cleanup
- Add SharedModelContainer.isUsingInMemoryFallback flag with critical logging
- Add analytics events: entryDeleted, allDataCleared, duplicatesRemoved, storageFallbackActivated

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-02-14 23:34:09 -06:00
parent 0e8738794b
commit 7c142568be
8 changed files with 157 additions and 39 deletions

View File

@@ -106,17 +106,25 @@ final class ExtensionDataProvider {
/// Get a single entry for a specific date
func getEntry(byDate date: Date) -> MoodEntryModel? {
let startDate = Calendar.current.startOfDay(for: date)
let endDate = Calendar.current.date(byAdding: .day, value: 1, to: startDate)!
guard let endDate = Calendar.current.date(byAdding: .day, value: 1, to: startDate) else {
Self.logger.error("Failed to calculate end date for getEntry")
return nil
}
var descriptor = FetchDescriptor<MoodEntryModel>(
predicate: #Predicate { entry in
entry.forDate >= startDate && entry.forDate <= endDate
entry.forDate >= startDate && entry.forDate < endDate
},
sortBy: [SortDescriptor(\.forDate, order: .forward)]
)
descriptor.fetchLimit = 1
return try? modelContext.fetch(descriptor).first
do {
return try modelContext.fetch(descriptor).first
} catch {
Self.logger.error("Failed to fetch entry: \(error.localizedDescription)")
return nil
}
}
/// Get today's mood entry
@@ -141,7 +149,12 @@ final class ExtensionDataProvider {
sortBy: [SortDescriptor(\.forDate, order: .forward)]
)
return (try? modelContext.fetch(descriptor)) ?? []
do {
return try modelContext.fetch(descriptor)
} catch {
Self.logger.error("Failed to fetch entries: \(error.localizedDescription)")
return []
}
}
/// Get the earliest entry in the database
@@ -150,7 +163,12 @@ final class ExtensionDataProvider {
sortBy: [SortDescriptor(\.forDate, order: .forward)]
)
descriptor.fetchLimit = 1
return try? modelContext.fetch(descriptor).first
do {
return try modelContext.fetch(descriptor).first
} catch {
Self.logger.error("Failed to fetch earliest entry: \(error.localizedDescription)")
return nil
}
}
/// Get the latest entry in the database
@@ -159,14 +177,24 @@ final class ExtensionDataProvider {
sortBy: [SortDescriptor(\.forDate, order: .reverse)]
)
descriptor.fetchLimit = 1
return try? modelContext.fetch(descriptor).first
do {
return try modelContext.fetch(descriptor).first
} catch {
Self.logger.error("Failed to fetch latest entry: \(error.localizedDescription)")
return nil
}
}
/// Get the current streak count
/// - Parameter includedDays: Weekdays to include in streak calculation (empty = all days)
func getCurrentStreak(includedDays: [Int] = []) -> Int {
guard let yearAgo = Calendar.current.date(byAdding: .day, value: -365, to: Date()) else {
Self.logger.error("Failed to calculate year-ago date for streak")
return 0
}
let entries = getData(
startDate: Calendar.current.date(byAdding: .day, value: -365, to: Date())!,
startDate: yearAgo,
endDate: Date(),
includedDays: includedDays
).sorted { $0.forDate > $1.forDate }
@@ -179,7 +207,11 @@ final class ExtensionDataProvider {
if entryDate == currentDate && entry.mood != .missing && entry.mood != .placeholder {
streak += 1
currentDate = Calendar.current.date(byAdding: .day, value: -1, to: currentDate)!
guard let previousDate = Calendar.current.date(byAdding: .day, value: -1, to: currentDate) else {
Self.logger.error("Failed to calculate previous date in streak")
break
}
currentDate = previousDate
} else if entryDate < currentDate {
break
}
@@ -202,7 +234,11 @@ final class ExtensionDataProvider {
modelContext.delete(entry)
}
if !existing.isEmpty {
try? modelContext.save()
do {
try modelContext.save()
} catch {
Self.logger.error("Failed to save after deleting existing entries: \(error.localizedDescription)")
}
}
let entry = MoodEntryModel(
@@ -227,16 +263,24 @@ final class ExtensionDataProvider {
/// Get ALL entries for a specific date (not just the first one)
func getAllEntries(byDate date: Date) -> [MoodEntryModel] {
let startDate = Calendar.current.startOfDay(for: date)
let endDate = Calendar.current.date(byAdding: .day, value: 1, to: startDate)!
guard let endDate = Calendar.current.date(byAdding: .day, value: 1, to: startDate) else {
Self.logger.error("Failed to calculate end date for getAllEntries")
return []
}
let descriptor = FetchDescriptor<MoodEntryModel>(
predicate: #Predicate { entry in
entry.forDate >= startDate && entry.forDate <= endDate
entry.forDate >= startDate && entry.forDate < endDate
},
sortBy: [SortDescriptor(\.forDate, order: .forward)]
)
return (try? modelContext.fetch(descriptor)) ?? []
do {
return try modelContext.fetch(descriptor)
} catch {
Self.logger.error("Failed to fetch all entries: \(error.localizedDescription)")
return []
}
}
/// Invalidate cached container (call when data might have changed)