Fix 7 data mutation layer risks identified in audit

- save()/saveAndRunDataListeners() now return @discardableResult Bool;
  listeners only fire on successful save
- MoodLogger.updateMood() now recalculates streak, updates Live Activity,
  and notifies watch (was missing these side effects)
- CSV import uses new addBatch()/importMoods() for O(1) side effects
  instead of O(n) per-row widget reloads and streak calcs
- Foreground task ordering: fillInMissingDates() now runs before
  removeDuplicates() so backfill-created duplicates are caught same cycle
- WidgetMoodSaver deletes ALL entries for date (was fetchLimit=1, leaving
  CloudKit sync duplicates behind)
- cleanupPhotoIfNeeded logs warning on failed photo deletion instead of
  silently orphaning files

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-02-14 23:09:11 -06:00
parent 3023475f66
commit f1cd81c395
8 changed files with 306 additions and 55 deletions

View File

@@ -52,19 +52,32 @@ final class DataController: ObservableObject {
editedDataClosure.append(closure)
}
func saveAndRunDataListeners() {
save()
for closure in editedDataClosure {
closure()
@discardableResult
func saveAndRunDataListeners() -> Bool {
let success = save()
if success {
for closure in editedDataClosure {
closure()
}
}
return success
}
func save() {
guard modelContext.hasChanges else { return }
@discardableResult
func save() -> Bool {
guard modelContext.hasChanges else { return true }
do {
try modelContext.save()
return true
} catch {
Self.logger.error("Failed to save context: \(error.localizedDescription)")
Self.logger.error("Failed to save context, retrying: \(error.localizedDescription)")
do {
try modelContext.save()
return true
} catch {
Self.logger.critical("Failed to save context after retry: \(error.localizedDescription)")
return false
}
}
}