1.0.4: Smarter auto-assign, quick dish entry, calendar fix, frictionless onboarding

- AutocompleteEngine: no-repeat rule now applies to all dishes (not just tagless ones); fallback still allows repeats when no alternative exists
- CalendarService: update existing events instead of delete+create to prevent duplicates; fix weekStartDate using plan date instead of currentWeekStart
- DishFormView: form stays open after saving in create mode for quick multi-dish entry; Done button to close; saved feedback banner
- OnboardingViewModel/FirstDishesStepView: dishes are now optional during onboarding; Finish button always enabled
- AnalyticsService: new events — onboarding_step_viewed(step) and auto_assign_used(filled, unfilled)
- Localization: dish_done and first_dishes_optional_hint added in 6 languages; release notes updated in all 6 languages
- Version bump: 1.0.3 → 1.0.4 (build 19)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
alexandrev-tibco
2026-04-19 11:35:50 +02:00
parent c66132f494
commit 6c7e12b41f
25 changed files with 659 additions and 49 deletions
+4 -21
View File
@@ -95,14 +95,10 @@ struct AutocompleteEngine {
plan: WeekPlan,
tagMap: [UUID: Tag]
) -> Bool {
// Default behavior: if a dish has no explicit rules, avoid repeating it in the same week.
if !dishHasExplicitRules(dish: dish, tagMap: tagMap) {
let alreadyAssignedThisWeek = plan.slots.contains {
$0.id != slot.id && $0.dishId == dish.id
}
if alreadyAssignedThisWeek { return true }
}
return false
// Default behavior: avoid assigning the same dish twice in the same week,
// regardless of whether it has explicit tag rules. The fallback path in
// autocomplete() still allows repeating when no alternative exists.
return plan.slots.contains { $0.id != slot.id && $0.dishId == dish.id }
}
private static func violatesExplicitRules(
@@ -149,19 +145,6 @@ struct AutocompleteEngine {
return false
}
private static func dishHasExplicitRules(dish: Dish, tagMap: [UUID: Tag]) -> Bool {
for tagId in dish.tagIds {
guard let tag = tagMap[tagId] else { continue }
if tag.maxPerWeek != nil ||
tag.noConsecutive ||
tag.noDuplicateInDay ||
tag.mealTypeRestriction != nil {
return true
}
}
return false
}
private static func adjacentSlots(of slot: MealSlot, in plan: WeekPlan) -> [MealSlot] {
var result: [MealSlot] = []
if slot.dayOfWeek > 0,