Files
Nextcloud-Cookbook-iOS/CLAUDE.md
Hendrik Hogertz 8b23652f10 Add meal plan feature with cross-device sync and automatic stale data cleanup
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>
2026-02-15 05:23:29 +01:00

118 lines
5.6 KiB
Markdown

# 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](https://github.com/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.
```bash
# 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](https://github.com/scinfu/SwiftSoup.git) (2.6.1) | HTML parsing for client-side recipe scraping |
| [TPPDF](https://github.com/techprimate/TPPDF.git) (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 `FetchMode` enum (`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
- `CookbookApi` protocol defines all endpoints; `CookbookApiV1` is the concrete implementation with all `static` methods.
- The global `cookbookApi` constant (`CookbookApi.swift:14`) resolves the API version at launch.
- `ApiRequest` is a generic HTTP request builder using `URLSession.shared.data(for:)` with HTTP Basic Auth.
- `NextcloudApi` handles Nextcloud-specific auth (Login Flow v2 and token-based login).
### Persistence
- **`DataStore`**: File-based persistence using `JSONEncoder`/`JSONDecoder` writing to the app's Documents directory. No Core Data or SQLite.
- **`UserSettings`**: Singleton wrapping `UserDefaults` for all user preferences and credentials.
- **Image caching**: Two-tier — in-memory dictionary in `AppState` + on-disk base64-encoded PNG files via `DataStore`.
### 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 `cookbookApi` global is resolved once at launch based on `UserSettings.shared.cookbookApiVersion` and uses static protocol methods, which makes dependency injection and unit testing difficult.
- Server credentials (username, token, authString) are stored in `UserDefaults` via `UserSettings`, not in Keychain.
- No `.gitignore` file exists in the repository.
- No CI/CD, no linting tools, and no meaningful test coverage.
## Workflow
- Do not run `xcodebuild` directly. Ask the user to build manually in Xcode and report the results back.