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

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