From 5272f6533ffb709d7de28b626ee50958254030de Mon Sep 17 00:00:00 2001 From: alexandrev-tibco Date: Wed, 22 Jul 2026 18:23:50 +0200 Subject: [PATCH] 2.0: fix crash de arranque por IDs duplicados (CloudKit) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fatal error "Duplicate values for key" al construir diccionarios con Dictionary(uniqueKeysWithValues:) sobre dishes/tags/slots cuyos UUID se duplican por la sincronización CloudKit de 2.0. Se sustituyen todas las ocurrencias por Dictionary(_, uniquingKeysWith:) para tolerar duplicados (se conserva el primero) en lugar de abortar. La app ya no crashea al abrir con datos duplicados; la deduplicación real queda pendiente aparte. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01A3HaWmmtTQ1vdTERtSYU6p --- MealMood/Services/AutocompleteEngine.swift | 10 +++++----- MealMood/Services/FeedbackStore.swift | 4 ++-- MealMood/ViewModels/HomeViewModel.swift | 6 +++--- MealMood/Views/Home/WeekCalendarView.swift | 4 ++-- MealMood/Views/Stats/StatsView.swift | 6 +++--- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/MealMood/Services/AutocompleteEngine.swift b/MealMood/Services/AutocompleteEngine.swift index f1fb077..3c37817 100644 --- a/MealMood/Services/AutocompleteEngine.swift +++ b/MealMood/Services/AutocompleteEngine.swift @@ -24,8 +24,8 @@ struct AutocompleteEngine { recentPlans: [WeekPlan] = [], rejectionCounts: [UUID: Int] = [:] ) -> AutocompleteResult { - let tagMap = Dictionary(uniqueKeysWithValues: allTags.map { ($0.id, $0) }) - let dishMap = Dictionary(uniqueKeysWithValues: allDishes.map { ($0.id, $0) }) + let tagMap = Dictionary(allTags.map { ($0.id, $0) }, uniquingKeysWith: { first, _ in first }) + let dishMap = Dictionary(allDishes.map { ($0.id, $0) }, uniquingKeysWith: { first, _ in first }) let historyScores = periodScores(dishes: allDishes, recentPlans: recentPlans, currentWeekStart: currentPlan.weekStartDate) let ratingScores = Self.ratingScores(dishes: allDishes, recentPlans: recentPlans) @@ -77,7 +77,7 @@ struct AutocompleteEngine { } static func findViolations(plan: WeekPlan, allDishes: [Dish], allTags: [Tag]) -> [RuleViolation] { - let dishMap = Dictionary(uniqueKeysWithValues: allDishes.map { ($0.id, $0) }) + let dishMap = Dictionary(allDishes.map { ($0.id, $0) }, uniquingKeysWith: { first, _ in first }) return plan.slotList.compactMap { slot in guard let dishId = slot.dishId, let dish = dishMap[dishId], @@ -100,8 +100,8 @@ struct AutocompleteEngine { allTags: [Tag], allDishes: [Dish] ) -> Bool { - let tagMap = Dictionary(uniqueKeysWithValues: allTags.map { ($0.id, $0) }) - let dishMap = Dictionary(uniqueKeysWithValues: allDishes.map { ($0.id, $0) }) + let tagMap = Dictionary(allTags.map { ($0.id, $0) }, uniquingKeysWith: { first, _ in first }) + let dishMap = Dictionary(allDishes.map { ($0.id, $0) }, uniquingKeysWith: { first, _ in first }) return !violatesRules(dish: dish, slot: slot, plan: plan, tagMap: tagMap, dishMap: dishMap) } diff --git a/MealMood/Services/FeedbackStore.swift b/MealMood/Services/FeedbackStore.swift index fb84599..847fd7f 100644 --- a/MealMood/Services/FeedbackStore.swift +++ b/MealMood/Services/FeedbackStore.swift @@ -7,9 +7,9 @@ struct FeedbackStore { guard let data = UserDefaults.standard.data(forKey: rejectionKey), let raw = try? JSONDecoder().decode([String: Int].self, from: data) else { return [:] } - return Dictionary(uniqueKeysWithValues: raw.compactMap { k, v in + return Dictionary(raw.compactMap { k, v in UUID(uuidString: k).map { ($0, v) } - }) + }, uniquingKeysWith: { first, _ in first }) } static func recordRejection(for dishId: UUID) { diff --git a/MealMood/ViewModels/HomeViewModel.swift b/MealMood/ViewModels/HomeViewModel.swift index fb8be0b..42aed4f 100644 --- a/MealMood/ViewModels/HomeViewModel.swift +++ b/MealMood/ViewModels/HomeViewModel.swift @@ -352,9 +352,9 @@ final class HomeViewModel: ObservableObject { private func captureUndoSnapshot(plan: WeekPlan) { guard !isApplyingUndo else { return } lastWeekStartSnapshot = plan.weekStartDate - lastSlotsSnapshot = Dictionary(uniqueKeysWithValues: plan.slotList.map { slot in + lastSlotsSnapshot = Dictionary(plan.slotList.map { slot in (slot.id, SlotSnapshot(dishId: slot.dishId, isRuleOverridden: slot.isRuleOverridden, isEatingOut: slot.isEatingOut)) - }) + }, uniquingKeysWith: { first, _ in first }) hasUndoSnapshot = true } @@ -410,7 +410,7 @@ final class HomeViewModel: ObservableObject { } private func syncAllAssignedSlotsToCalendar(plan: WeekPlan, dishes: [Dish], settings: AppSettings) { - let dishById = Dictionary(uniqueKeysWithValues: dishes.map { ($0.id, $0) }) + let dishById = Dictionary(dishes.map { ($0.id, $0) }, uniquingKeysWith: { first, _ in first }) for slot in plan.slotList { guard let dishId = slot.dishId, let dish = dishById[dishId] else { if let eventId = slot.calendarEventId { diff --git a/MealMood/Views/Home/WeekCalendarView.swift b/MealMood/Views/Home/WeekCalendarView.swift index e9bbc18..90b017d 100644 --- a/MealMood/Views/Home/WeekCalendarView.swift +++ b/MealMood/Views/Home/WeekCalendarView.swift @@ -344,7 +344,7 @@ struct WeekCalendarView: View { } private func refreshDishSnapshots() { - let liveSnapshots = Dictionary(uniqueKeysWithValues: dishes.map { dish in + let liveSnapshots = Dictionary(dishes.map { dish in ( dish.id, DishSnapshot( @@ -354,7 +354,7 @@ struct WeekCalendarView: View { photoData: dish.photoData ) ) - }) + }, uniquingKeysWith: { first, _ in first }) dishSnapshotsById = dishSnapshotsById.merging(liveSnapshots) { _, new in new } } diff --git a/MealMood/Views/Stats/StatsView.swift b/MealMood/Views/Stats/StatsView.swift index 4b61314..208d504 100644 --- a/MealMood/Views/Stats/StatsView.swift +++ b/MealMood/Views/Stats/StatsView.swift @@ -181,7 +181,7 @@ struct StatsView: View { private var topDishes: [DishEntry] { let usage = dishUsage - let dishMap = Dictionary(uniqueKeysWithValues: allDishes.map { ($0.id, $0) }) + let dishMap = Dictionary(allDishes.map { ($0.id, $0) }, uniquingKeysWith: { first, _ in first }) return usage .compactMap { id, count in dishMap[id].map { DishEntry(dish: $0, count: count) } } .sorted { $0.count > $1.count } @@ -192,8 +192,8 @@ struct StatsView: View { private struct TagEntry { let tag: Tag; let count: Int } private var tagUsage: [TagEntry] { - let dishMap = Dictionary(uniqueKeysWithValues: allDishes.map { ($0.id, $0) }) - let tagMap = Dictionary(uniqueKeysWithValues: allTags.map { ($0.id, $0) }) + let dishMap = Dictionary(allDishes.map { ($0.id, $0) }, uniquingKeysWith: { first, _ in first }) + let tagMap = Dictionary(allTags.map { ($0.id, $0) }, uniquingKeysWith: { first, _ in first }) var counts: [UUID: Int] = [:] for plan in weekPlans { for slot in plan.slotList {