2.0: fix crash de arranque por IDs duplicados (CloudKit)
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) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A3HaWmmtTQ1vdTERtSYU6p
This commit is contained in:
@@ -24,8 +24,8 @@ struct AutocompleteEngine {
|
|||||||
recentPlans: [WeekPlan] = [],
|
recentPlans: [WeekPlan] = [],
|
||||||
rejectionCounts: [UUID: Int] = [:]
|
rejectionCounts: [UUID: Int] = [:]
|
||||||
) -> AutocompleteResult {
|
) -> AutocompleteResult {
|
||||||
let tagMap = Dictionary(uniqueKeysWithValues: allTags.map { ($0.id, $0) })
|
let tagMap = Dictionary(allTags.map { ($0.id, $0) }, uniquingKeysWith: { first, _ in first })
|
||||||
let dishMap = Dictionary(uniqueKeysWithValues: allDishes.map { ($0.id, $0) })
|
let dishMap = Dictionary(allDishes.map { ($0.id, $0) }, uniquingKeysWith: { first, _ in first })
|
||||||
let historyScores = periodScores(dishes: allDishes, recentPlans: recentPlans, currentWeekStart: currentPlan.weekStartDate)
|
let historyScores = periodScores(dishes: allDishes, recentPlans: recentPlans, currentWeekStart: currentPlan.weekStartDate)
|
||||||
let ratingScores = Self.ratingScores(dishes: allDishes, recentPlans: recentPlans)
|
let ratingScores = Self.ratingScores(dishes: allDishes, recentPlans: recentPlans)
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ struct AutocompleteEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static func findViolations(plan: WeekPlan, allDishes: [Dish], allTags: [Tag]) -> [RuleViolation] {
|
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
|
return plan.slotList.compactMap { slot in
|
||||||
guard let dishId = slot.dishId,
|
guard let dishId = slot.dishId,
|
||||||
let dish = dishMap[dishId],
|
let dish = dishMap[dishId],
|
||||||
@@ -100,8 +100,8 @@ struct AutocompleteEngine {
|
|||||||
allTags: [Tag],
|
allTags: [Tag],
|
||||||
allDishes: [Dish]
|
allDishes: [Dish]
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
let tagMap = Dictionary(uniqueKeysWithValues: allTags.map { ($0.id, $0) })
|
let tagMap = Dictionary(allTags.map { ($0.id, $0) }, uniquingKeysWith: { first, _ in first })
|
||||||
let dishMap = Dictionary(uniqueKeysWithValues: allDishes.map { ($0.id, $0) })
|
let dishMap = Dictionary(allDishes.map { ($0.id, $0) }, uniquingKeysWith: { first, _ in first })
|
||||||
return !violatesRules(dish: dish, slot: slot, plan: plan, tagMap: tagMap, dishMap: dishMap)
|
return !violatesRules(dish: dish, slot: slot, plan: plan, tagMap: tagMap, dishMap: dishMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ struct FeedbackStore {
|
|||||||
guard let data = UserDefaults.standard.data(forKey: rejectionKey),
|
guard let data = UserDefaults.standard.data(forKey: rejectionKey),
|
||||||
let raw = try? JSONDecoder().decode([String: Int].self, from: data)
|
let raw = try? JSONDecoder().decode([String: Int].self, from: data)
|
||||||
else { return [:] }
|
else { return [:] }
|
||||||
return Dictionary(uniqueKeysWithValues: raw.compactMap { k, v in
|
return Dictionary(raw.compactMap { k, v in
|
||||||
UUID(uuidString: k).map { ($0, v) }
|
UUID(uuidString: k).map { ($0, v) }
|
||||||
})
|
}, uniquingKeysWith: { first, _ in first })
|
||||||
}
|
}
|
||||||
|
|
||||||
static func recordRejection(for dishId: UUID) {
|
static func recordRejection(for dishId: UUID) {
|
||||||
|
|||||||
@@ -352,9 +352,9 @@ final class HomeViewModel: ObservableObject {
|
|||||||
private func captureUndoSnapshot(plan: WeekPlan) {
|
private func captureUndoSnapshot(plan: WeekPlan) {
|
||||||
guard !isApplyingUndo else { return }
|
guard !isApplyingUndo else { return }
|
||||||
lastWeekStartSnapshot = plan.weekStartDate
|
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))
|
(slot.id, SlotSnapshot(dishId: slot.dishId, isRuleOverridden: slot.isRuleOverridden, isEatingOut: slot.isEatingOut))
|
||||||
})
|
}, uniquingKeysWith: { first, _ in first })
|
||||||
hasUndoSnapshot = true
|
hasUndoSnapshot = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -410,7 +410,7 @@ final class HomeViewModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func syncAllAssignedSlotsToCalendar(plan: WeekPlan, dishes: [Dish], settings: AppSettings) {
|
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 {
|
for slot in plan.slotList {
|
||||||
guard let dishId = slot.dishId, let dish = dishById[dishId] else {
|
guard let dishId = slot.dishId, let dish = dishById[dishId] else {
|
||||||
if let eventId = slot.calendarEventId {
|
if let eventId = slot.calendarEventId {
|
||||||
|
|||||||
@@ -344,7 +344,7 @@ struct WeekCalendarView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func refreshDishSnapshots() {
|
private func refreshDishSnapshots() {
|
||||||
let liveSnapshots = Dictionary(uniqueKeysWithValues: dishes.map { dish in
|
let liveSnapshots = Dictionary(dishes.map { dish in
|
||||||
(
|
(
|
||||||
dish.id,
|
dish.id,
|
||||||
DishSnapshot(
|
DishSnapshot(
|
||||||
@@ -354,7 +354,7 @@ struct WeekCalendarView: View {
|
|||||||
photoData: dish.photoData
|
photoData: dish.photoData
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
})
|
}, uniquingKeysWith: { first, _ in first })
|
||||||
|
|
||||||
dishSnapshotsById = dishSnapshotsById.merging(liveSnapshots) { _, new in new }
|
dishSnapshotsById = dishSnapshotsById.merging(liveSnapshots) { _, new in new }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ struct StatsView: View {
|
|||||||
|
|
||||||
private var topDishes: [DishEntry] {
|
private var topDishes: [DishEntry] {
|
||||||
let usage = dishUsage
|
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
|
return usage
|
||||||
.compactMap { id, count in dishMap[id].map { DishEntry(dish: $0, count: count) } }
|
.compactMap { id, count in dishMap[id].map { DishEntry(dish: $0, count: count) } }
|
||||||
.sorted { $0.count > $1.count }
|
.sorted { $0.count > $1.count }
|
||||||
@@ -192,8 +192,8 @@ struct StatsView: View {
|
|||||||
private struct TagEntry { let tag: Tag; let count: Int }
|
private struct TagEntry { let tag: Tag; let count: Int }
|
||||||
|
|
||||||
private var tagUsage: [TagEntry] {
|
private var tagUsage: [TagEntry] {
|
||||||
let dishMap = Dictionary(uniqueKeysWithValues: allDishes.map { ($0.id, $0) })
|
let dishMap = Dictionary(allDishes.map { ($0.id, $0) }, uniquingKeysWith: { first, _ in first })
|
||||||
let tagMap = Dictionary(uniqueKeysWithValues: allTags.map { ($0.id, $0) })
|
let tagMap = Dictionary(allTags.map { ($0.id, $0) }, uniquingKeysWith: { first, _ in first })
|
||||||
var counts: [UUID: Int] = [:]
|
var counts: [UUID: Int] = [:]
|
||||||
for plan in weekPlans {
|
for plan in weekPlans {
|
||||||
for slot in plan.slotList {
|
for slot in plan.slotList {
|
||||||
|
|||||||
Reference in New Issue
Block a user