Migrate journal entries to CoreData for iCloud sync (build 33)

- Add JournalEntry CoreData entity (syncable=YES): id, monthKey, note, moodRaw, rating, completionTime, createdAt
- Rewrite MonthlyCheckInStore: CoreData as primary storage, same public API
- One-time migration from UserDefaults triggered after store loads
- MonthlyCheckInCard: @FetchRequest on JournalEntry — reactive to iCloud sync
- MonthlyCheckInView: onReceive NSManagedObjectContextObjectsDidChange to refresh @State vars on sync
- Stats cards: observe NSManagedObjectContextObjectsDidChange instead of UserDefaults
- ChartsViewModel.completedMonthKeys: remove MonthlyCheckInStore dependency (data-driven)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
alexandrev-tibco
2026-05-26 15:05:53 +02:00
parent 0b01d6f563
commit 270edeb536
8 changed files with 236 additions and 276 deletions
@@ -709,8 +709,7 @@ class ChartsViewModel: ObservableObject {
private func completedMonthKeys(
sources: [InvestmentSource],
snapshots: [Snapshot],
after cutoff: Date
snapshots: [Snapshot]
) -> Set<DateComponents> {
let sourceIds = Set(sources.compactMap { $0.id })
guard !sourceIds.isEmpty else { return [] }
@@ -723,9 +722,8 @@ class ChartsViewModel: ObservableObject {
completed.reserveCapacity(groupedByMonth.count)
for (key, monthSnapshots) in groupedByMonth {
guard let monthDate = Calendar.current.date(from: key) else { continue }
guard monthDate > cutoff else { continue }
guard MonthlyCheckInStore.completionDate(for: monthDate) != nil else { continue }
// A month is complete when all active sources have snapshot data for it.
// MonthlyCheckInStore (UserDefaults) is NOT synced via iCloud don't use it as a filter.
let monthSourceIds = Set(monthSnapshots.compactMap { $0.source?.id })
if sourceIds.isSubset(of: monthSourceIds) {
completed.insert(key)
@@ -739,24 +737,8 @@ class ChartsViewModel: ObservableObject {
sources: [InvestmentSource],
snapshots: [Snapshot]
) -> [Snapshot] {
guard let lastCompleted = MonthlyCheckInStore.latestCompletionDate()?.startOfMonth else {
return snapshots
}
let completedMonthsAfter = completedMonthKeys(
sources: sources,
snapshots: snapshots,
after: lastCompleted
)
return snapshots.filter { snapshot in
let monthDate = chartMonthStart(for: snapshot.date)
if monthDate <= lastCompleted {
return true
}
let key = chartMonth(for: snapshot.date)
return completedMonthsAfter.contains(key)
}
let completedKeys = completedMonthKeys(sources: sources, snapshots: snapshots)
return snapshots.filter { completedKeys.contains(chartMonth(for: $0.date)) }
}
private func groupSnapshotsBySource(_ snapshots: [Snapshot]) -> [UUID: [Snapshot]] {