- Status: Accepted
- Date: 2026-03-04
Cookle aims to stay close to Apple's preferred app architecture. The codebase is
built around SwiftUI, SwiftData, Environment, and AppIntents rather
than a custom layered framework.
At the same time, Cookle already has multiple targets and will likely grow more over time. The iOS app, widgets, and future targets need to reuse the same domain behavior without copying business logic into each target.
Before this decision, some user-facing flows had started to drift:
- views directly mutated SwiftData models
- App Intents sometimes owned their own command logic
- app-only side effects were mixed into UI components
- search logic risked diverging between UI and intents
That drift made the design harder to explain and weakened the shared-library boundary.
We will separate the system into shared services and target adapters.
ADR 0007 refines the public shared-library entry point: delivery surfaces now
call public *Operations facades, while lower-level services remain internal
collaborators behind that boundary.
CookleLibrary is the source of truth for shared business logic.
It owns:
- SwiftData models
- predicates and query helpers
- validation and mutation services
- migrations
- route helpers
The Cookle target owns adapter code for the main app.
It owns:
- SwiftUI views
- App Intents
- app-owned root assembly, runtime bootstrap, and environment wiring
- runtime lifecycle planning
- workflow services that orchestrate app-only follow-up
- notifications, widget reloads, review prompts, and app-only routing
App Intents are treated as system-facing adapters, not domain services.
They should:
- resolve parameters
- call a shared Operations API or workflow service
- return dialogs, values, snippets, or route actions
They should not:
- become the only implementation of a business action
- duplicate validation or mutation logic
User-facing command flows in the app should go through workflow services such as
RecipeActionService, DiaryActionService, PhotoActionService,
TagActionService, and SettingsActionService.
These services wrap shared mutations and then run app-only side effects. Review prompts should be attached to successful user-facing workflows rather than generic app foreground transitions.
RecipeOperations.search is the canonical recipe search implementation used by
views, intents, and widgets.
- shared domain behavior is reusable across targets
- App Intents expose existing workflows instead of creating a parallel system
- app-only side effects have a single home
- startup and foreground refresh logic can be explained through shared runtime lifecycle plans instead of scattered handlers
- the architecture is easier to explain and maintain
- future targets can add their own adapters without changing the shared core
- the app target contains more thin orchestration types
- some flows require an extra hop through a workflow service
- engineers need to follow placement rules consistently to keep the boundary clean
Rejected because it does not scale across App Intents, widgets, or future targets.
Rejected because App Intents are a system-facing interface, not a good internal API for normal app code.
Rejected because it adds abstraction without matching Cookle's current needs and would move the code away from Apple's standard patterns.