- Map uncategorized category between * (internal) and empty string
(API) so selecting Sonstige/Other correctly persists to the server
- Default new recipes to Other (*) category and remove None option
- Add "New Category" option to category picker in recipe edit view
- Include newly created/imported recipes in recently viewed list and
pre-fetch thumbnails so images display immediately
- Remove deleted recipes from recently viewed list
- Remove broad .tint(.primary) from RecipeTabView that caused white
toggles in Settings during dark mode
- Rename German "Other" translation from Andere to Sonstige
- Add missing translations for Servings stepper and new category strings
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add user-facing appearance setting (System/Light/Dark) wired via
preferredColorScheme at the app root. Replace hardcoded .black tints
and foreground styles with .primary so toolbar buttons and text remain
visible in dark mode. Remove profile picture from settings and
SwiftSoup from acknowledgements.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Store a _groceryState JSON field on each recipe to track which
ingredients have been added, completed, or removed. Uses per-item
last-writer-wins conflict resolution with ISO 8601 timestamps.
Debounced push (2s) avoids excessive API calls; pull reconciles
on recipe open and app launch. Includes a settings toggle to
enable/disable sync.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Introduce a GroceryListManager facade that delegates to either the existing
in-app GroceryList or a new RemindersGroceryStore backed by EventKit. Users
choose the mode in Settings; when Reminders mode is active the Grocery List
tab is hidden. Recipe-to-reminder grouping uses a local mapping file
(reminder_mappings.data) instead of polluting the reminder's notes field,
with automatic pruning when reminders are deleted externally.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The settings view was being popped immediately after push because multiple
navigationDestination(isPresented:) modifiers on the same view caused SwiftUI
to reset bindings when appState published changes. Replaced with a single
navigationDestination(for: SidebarDestination.self) using an explicit
NavigationStack(path:). Also fixed @ObservedObject -> @StateObject on
SettingsView.ViewModel, added AllRecipesListView/AllRecipesCategoryCardView,
and added translations for new strings.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Network layer:
- Replace static CookbookApi protocol with instance-based CookbookApiProtocol
using async/throws instead of tuple returns
- Refactor ApiRequest to use URLComponents for proper URL encoding, replace
print statements with OSLog, and return typed NetworkError cases
- Add structured NetworkError variants (httpError, connectionError, etc.)
- Remove global cookbookApi constant in favor of injected dependency on AppState
- Delete unused RecipeEditViewModel, RecipeScraper, and Scraper playground
Data & model fixes:
- Add custom Decodable for RecipeDetail with safe fallbacks for malformed JSON
- Make Category Hashable/Equatable use only `name` so NavigationSplitView
selection survives category refreshes with updated recipe_count
- Return server-assigned ID from uploadRecipe so new recipes get their ID
before the post-upload refresh block executes
View updates:
- Refresh both old and new category recipe lists after upload when category
changes, mapping empty recipeCategory to "*" for uncategorized recipes
- Raise deployment target to iOS 18, adopt new SwiftUI API conventions
- Clean up alerts, onboarding views, and settings
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>