Introduces weekly meal planning with a calendar-based tab view, per-recipe date assignments synced via Nextcloud Cookbook custom metadata, and 30-day automatic pruning of old entries on load, save, and sync merge. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
5.6 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
Nextcloud Cookbook iOS Client — a native iOS/iPadOS/macOS (Mac Catalyst) client for the Nextcloud Cookbook server application. Built entirely in Swift and SwiftUI. Licensed under GPLv3.
This is not a standalone app. It requires a Nextcloud server with the Cookbook app installed.
Build & Run
This is an Xcode project (no workspace, no CocoaPods). Open Nextcloud Cookbook iOS Client.xcodeproj in Xcode.
# Build from command line
xcodebuild -project "Nextcloud Cookbook iOS Client.xcodeproj" \
-scheme "Nextcloud Cookbook iOS Client" \
-destination 'platform=iOS Simulator,name=iPhone 16' \
build
# Run tests (note: tests are currently boilerplate stubs with no real coverage)
xcodebuild -project "Nextcloud Cookbook iOS Client.xcodeproj" \
-scheme "Nextcloud Cookbook iOS Client" \
-destination 'platform=iOS Simulator,name=iPhone 16' \
test
- Deployment target: iOS 18
- Swift version: 5.0
- Targets: iPhone and iPad, Mac Catalyst enabled
Dependencies (SPM)
Only two third-party packages, managed via Swift Package Manager integrated in the Xcode project:
| Package | Purpose |
|---|---|
| SwiftSoup (2.6.1) | HTML parsing for client-side recipe scraping |
| TPPDF (2.4.1) | PDF generation for recipe export |
Architecture
Central State Pattern
The app uses a centralized AppState ObservableObject (AppState.swift) as the primary ViewModel, injected via .environmentObject() into the SwiftUI view hierarchy. AppState owns:
- All data (categories, recipes, recipe details, images, timers)
- All CRUD operations against the Cookbook API
- A
FetchModeenum (preferLocal,preferServer,onlyLocal,onlyServer) that governs the data-fetching strategy (server-first vs local-first) - Local persistence via
DataStore
Additional ViewModels exist as nested classes within their views (RecipeTabView.ViewModel, SearchTabView.ViewModel) or as standalone classes (RecipeEditViewModel, GroceryList).
Data Flow
SwiftUI Views
├── @EnvironmentObject appState: AppState
├── @EnvironmentObject groceryList: GroceryListManager
├── @EnvironmentObject mealPlan: MealPlanManager
└── Per-view @StateObject ViewModels
│
▼
AppState
├── cookbookApi (CookbookApiV1 — static methods) → ApiRequest → URLSession
├── DataStore (file-based JSON persistence in Documents directory)
└── UserSettings.shared (UserDefaults singleton)
Both GroceryListManager and MealPlanManager use custom metadata fields (_groceryState, _mealPlanAssignment) embedded in recipe JSON on the Nextcloud Cookbook API for cross-device sync. Each has a dedicated sync manager (GroceryStateSyncManager, MealPlanSyncManager) that handles debounced push, pull reconciliation, and per-item/per-date last-writer-wins merge.
Network Layer
CookbookApiprotocol defines all endpoints;CookbookApiV1is the concrete implementation with allstaticmethods.- The global
cookbookApiconstant (CookbookApi.swift:14) resolves the API version at launch. ApiRequestis a generic HTTP request builder usingURLSession.shared.data(for:)with HTTP Basic Auth.NextcloudApihandles Nextcloud-specific auth (Login Flow v2 and token-based login).
Persistence
DataStore: File-based persistence usingJSONEncoder/JSONDecoderwriting to the app's Documents directory. No Core Data or SQLite.UserSettings: Singleton wrappingUserDefaultsfor all user preferences and credentials.- Image caching: Two-tier — in-memory dictionary in
AppState+ on-disk base64-encoded PNG files viaDataStore.
Key Source Directories
Nextcloud Cookbook iOS Client/
├── Data/ # Models (Category, Recipe, RecipeDetail, Nutrition) + DataStore + UserSettings + MealPlan + GroceryList
├── Models/ # RecipeEditViewModel
├── Network/ # ApiRequest, NetworkError, CookbookApi protocol + V1, NextcloudApi
├── Views/
│ ├── Tabs/ # Main tab views (RecipeTab, SearchTab, MealPlanTab, GroceryListTab)
│ ├── Recipes/ # Recipe detail, list, card, share, timer views
│ ├── RecipeViewSections/ # Decomposed recipe detail sections (ingredients, instructions, etc.)
│ ├── Onboarding/ # Login flows (V2LoginView, TokenLoginView)
│ └── ReusableViews/
├── Extensions/ # Color, Date, JSONCoder, Logger extensions
├── Util/ # Alerts, DurationComponents (ISO 8601 PT parser), JsonAny, NumberFormatter
├── RecipeExport/ # PDF, text, JSON export via RecipeExporter
└── RecipeImport/ # HTML scraping via SwiftSoup (schema.org ld+json Recipe data)
Localization
Four languages supported via Localizable.xcstrings: English, German, Spanish, French. Spanish and French are mostly machine-translated.
Notable Design Decisions
- The
cookbookApiglobal is resolved once at launch based onUserSettings.shared.cookbookApiVersionand uses static protocol methods, which makes dependency injection and unit testing difficult. - Server credentials (username, token, authString) are stored in
UserDefaultsviaUserSettings, not in Keychain. - No
.gitignorefile exists in the repository. - No CI/CD, no linting tools, and no meaningful test coverage.
Workflow
- Do not run
xcodebuilddirectly. Ask the user to build manually in Xcode and report the results back.