# 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.