Fix meal plan removal ignored on first attempt after app launch

Guard reconcileFromServer() with a syncStartTime so that entries
modified locally during an active performSync() cycle are never
overwritten by stale server data. This prevents the race condition
where a user removes a meal plan entry while Phase 2 of sync is
still iterating server recipes.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-15 11:54:13 +01:00
parent 1f7f19c74b
commit 285e91a429
2 changed files with 11 additions and 0 deletions

View File

@@ -14,6 +14,7 @@ class MealPlanManager: ObservableObject {
private var recipeNames: [String: String] = [:] private var recipeNames: [String: String] = [:]
private let dataStore = DataStore() private let dataStore = DataStore()
var syncManager: MealPlanSyncManager? var syncManager: MealPlanSyncManager?
var syncStartTime: String?
private static let persistencePath = "meal_plan.data" private static let persistencePath = "meal_plan.data"
@@ -128,6 +129,13 @@ class MealPlanManager: ObservableObject {
for (dayStr, serverEntry) in serverAssignment.dates { for (dayStr, serverEntry) in serverAssignment.dates {
if let localEntry = local.dates[dayStr] { if let localEntry = local.dates[dayStr] {
// Skip entries modified locally during this sync cycle
if let syncStart = syncStartTime,
let syncStartDate = MealPlanDate.date(from: syncStart),
let localModDate = MealPlanDate.date(from: localEntry.modifiedAt),
localModDate >= syncStartDate {
continue
}
let localDate = MealPlanDate.date(from: localEntry.modifiedAt) ?? .distantPast let localDate = MealPlanDate.date(from: localEntry.modifiedAt) ?? .distantPast
let serverDate = MealPlanDate.date(from: serverEntry.modifiedAt) ?? .distantPast let serverDate = MealPlanDate.date(from: serverEntry.modifiedAt) ?? .distantPast
if serverDate > localDate { if serverDate > localDate {

View File

@@ -65,6 +65,9 @@ class MealPlanSyncManager {
func performSync() async { func performSync() async {
guard let appState, let mealPlanManager else { return } guard let appState, let mealPlanManager else { return }
mealPlanManager.syncStartTime = MealPlanDate.now()
defer { mealPlanManager.syncStartTime = nil }
// Phase 1: Push locally-known meal plan state // Phase 1: Push locally-known meal plan state
let localRecipeIds = Array(Set( let localRecipeIds = Array(Set(
mealPlanManager.entriesByDate.values.flatMap { $0 }.map(\.recipeId) mealPlanManager.entriesByDate.values.flatMap { $0 }.map(\.recipeId)