- 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>
- Create unified ExtensionDataProvider for Widget and Watch targets
- Remove duplicate WatchDataProvider and WatchConnectivityManager from Watch App
- Add side effects catch-up mechanism in MoodLogger for widget votes
- Process pending side effects on app launch and midnight background task
- Reduce ~450 lines of duplicated code across targets
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>