diff --git a/MealMood/Models/Dish.swift b/MealMood/Models/Dish.swift index 7a4e800..75808df 100644 --- a/MealMood/Models/Dish.swift +++ b/MealMood/Models/Dish.swift @@ -23,3 +23,14 @@ final class Dish { self.createdAt = createdAt } } + +extension Dish { + static func stableSortedForDisplay(_ dishes: [Dish]) -> [Dish] { + dishes.sorted { lhs, rhs in + if lhs.createdAt != rhs.createdAt { + return lhs.createdAt > rhs.createdAt + } + return lhs.id.uuidString < rhs.id.uuidString + } + } +} diff --git a/MealMood/Views/Dishes/DishListView.swift b/MealMood/Views/Dishes/DishListView.swift index 59dfc35..3bd65f5 100644 --- a/MealMood/Views/Dishes/DishListView.swift +++ b/MealMood/Views/Dishes/DishListView.swift @@ -32,7 +32,7 @@ struct DishListView: View { } } else { List { - ForEach(dishes) { dish in + ForEach(dishes, id: \.id) { dish in let dishTags = tags.filter { dish.tagIds.contains($0.id) } Button { editingDish = dish diff --git a/MealMood/Views/Home/DishDrawerView.swift b/MealMood/Views/Home/DishDrawerView.swift index 574588f..3e797f6 100644 --- a/MealMood/Views/Home/DishDrawerView.swift +++ b/MealMood/Views/Home/DishDrawerView.swift @@ -15,7 +15,7 @@ struct DishDrawerView: View { private var filteredDishes: [Dish] { let term = searchText.trimmingCharacters(in: .whitespacesAndNewlines) - return dishes.filter { dish in + return Dish.stableSortedForDisplay(dishes).filter { dish in let matchesSearch = term.isEmpty || dish.name.localizedCaseInsensitiveContains(term) let matchesUsedFilter = !hideUsedThisWeek || !usedDishIds.contains(dish.id) return matchesSearch && matchesUsedFilter @@ -95,7 +95,7 @@ struct DishDrawerView: View { .buttonStyle(.plain) LazyVStack(spacing: 8) { - ForEach(filteredDishes) { dish in + ForEach(filteredDishes, id: \.id) { dish in DishCardView( dish: dish, tags: tags, diff --git a/MealMood/Views/Home/HomeView.swift b/MealMood/Views/Home/HomeView.swift index ca816cf..e88cbaf 100644 --- a/MealMood/Views/Home/HomeView.swift +++ b/MealMood/Views/Home/HomeView.swift @@ -5,7 +5,7 @@ struct HomeView: View { @Environment(\.horizontalSizeClass) private var horizontalSizeClass @Environment(\.modelContext) private var context @Environment(\.openURL) private var openURL - @Query private var dishes: [Dish] + @Query(sort: \Dish.createdAt, order: .reverse) private var dishes: [Dish] @Query private var tags: [Tag] @Query(sort: \WeekPlan.weekStartDate, order: .forward) private var weekPlans: [WeekPlan] @Query private var allSettings: [AppSettings] @@ -452,7 +452,7 @@ struct HomeView: View { .padding(24) } else { List { - ForEach(filteredDishes) { dish in + ForEach(filteredDishes, id: \.id) { dish in Button { onPickDish(dish) dismiss() diff --git a/MealMoodTests/AutocompleteEngineTests.swift b/MealMoodTests/AutocompleteEngineTests.swift index 11de064..1cb7cb6 100644 --- a/MealMoodTests/AutocompleteEngineTests.swift +++ b/MealMoodTests/AutocompleteEngineTests.swift @@ -59,5 +59,17 @@ final class AutocompleteEngineTests: XCTestCase { XCTAssertEqual(result.unfilledCount, 1) XCTAssertNil(tuesdayDinner.dishId) } -} + func testDishStableSortRemainsConsistentAfterDeletion() { + let base = Date() + let oldest = Dish(id: UUID(uuidString: "00000000-0000-0000-0000-000000000001")!, name: "Old", createdAt: base.addingTimeInterval(-60)) + let newest = Dish(id: UUID(uuidString: "00000000-0000-0000-0000-000000000002")!, name: "New", createdAt: base) + let middle = Dish(id: UUID(uuidString: "00000000-0000-0000-0000-000000000003")!, name: "Middle", createdAt: base.addingTimeInterval(-30)) + + let initial = Dish.stableSortedForDisplay([oldest, newest, middle]).map(\.id) + XCTAssertEqual(initial, [newest.id, middle.id, oldest.id]) + + let afterDeletion = Dish.stableSortedForDisplay([newest, oldest]).map(\.id) + XCTAssertEqual(afterDeletion, [newest.id, oldest.id]) + } +}