This document explains where shared logic belongs in Cookle when the same operation must work across the iOS app, widgets, and App Intents.
CookleLibraryis the source of truth for shared business logic.- Public cross-surface business use cases enter through explicit
*Operationsfacades. Lower-level services, calculators, builders, planners, loaders, parsers, and codecs stay as narrower collaborators unless they are durable contracts in their own right. - Target-local adapters own Apple-framework integrations and presentation orchestration.
- App Intents are adapters, not a second domain layer.
- Views own presentation state and screen-scoped models, but not canonical business rules.
CookleLibraryremains a single module unless there is a stronger reason than code organization alone.
| Concern | Lives in | Examples |
|---|---|---|
| Shared domain logic | CookleLibrary |
Recipe, Diary, Tag, predicates, RecipeOperations, RecipeFormOperations, DiaryOperations, TagOperations, DataMaintenanceOperations |
| Apple framework adapters | Cookle, Widgets, Watch |
NotificationService, App Intent types, widget timeline/provider types, WatchCookingSessionStore |
| App-side platform support | Cookle/Sources/Platform |
CookleAppAssemblyFactory, MHAppRuntimeBootstrap assembly, MHAppRoutePipeline<CookleRoute> assembly |
| Presentation orchestration | Cookle, Widgets, Watch |
SwiftUI views, widget view composition, MainNavigationRouter, RecipeFormModel, RecipeFormSaveCoordinator, DiaryFormModel, DiaryFormSaveCoordinator, SettingsScreenModel, WatchActiveCookingView |
Cookle/Sources/Appis for app entry points, app-level App Intents, app-wide support, and generic workflow adapters.Cookle/Sources/Featuresis for feature-owned SwiftUI, App Intents, presentation models, and target-local action services.Cookle/Sources/Platformis for app-side Apple framework and package glue that is reused by multiple app entry points.Cookle/Sources/SharedUIis for reusable app-target UI components, modifiers, styles, navigation environment helpers, and tips.CookleLibrary/Sources/<Capability>is for shared capability groups rather than a broadCommonfolder.Widgets/Sources/AppandWatch/Sources/Appcontain target entry wiring; theirFeaturesfolders contain visible surface behavior.CookleLibrary/Tests/Default/<Capability>mirrors the shared-library source capabilities.
Cookle follows the Incomes June boundary direction without copying
finance-domain behavior. Public *Operations facades are the shared-library
application layer for delivery surfaces.
Use this rule for new work:
- Add or extend
*Operationswhen a delivery surface needs a public business use case. - Keep services, calculators, builders, planners, loaders, parsers, and codecs as narrower collaborators when they are implementation details.
- Keep existing services internal when they only support Operations or tested library behavior.
Delivery surfaces should not call calculators, builders, planners, loaders, or parser helpers for business behavior when an Operations boundary can own that use case.
Cookleis the intentionalMHPlatformumbrella adopter.CookleLibraryadoptsMHPlatformCoreand must not depend on the fullMHPlatformumbrella.WidgetsandWatchcallCookleLibraryfirst and stay off direct app-runtime umbrella adoption.- This repository intentionally uses the MHPlatform 1.x semver range
1.13.0..<2.0.0to preserve verified subscription entitlement handling. Cookleadopts full MHUI on2.3.0..<3.0.0for its root theme and selected presentation primitives, using the MHDesign re-export for metrics. ADR 0009 records the accepted main-app presentation boundary; the rollout record lists the screen treatments and verification limits. App Intent implementations remain free of MHUI/MHDesign imports.CookleLibrarystays presentation-free and must not depend on MHUI or MHDesign.Widgetsretains native WidgetKit composition. Watch retains its existing presentation because the MHUI 1.18 standard surface failed the watchOS readability comparison. These dependency decisions follow ADR 0010; companion targets are evaluated individually rather than excluded from future adoption. Their adapters continue to call shared Operations first.- Cookle does not keep a generic utility package dependency. Generic utilities should not be treated as an MHUI migration target unless a utility becomes a stable platform-foundation contract.
The following types are the current shared entry points for business use cases and supporting contracts:
RecipeBrowseCriteriaRecipeBrowseSortModeRecipeFormDraftMutationOutcomeMutationEffectRecipePhotoRemovalBehaviorRecipeOperationsRecipeFormOperationsRecipeInferenceOperationsDiaryOperationsTagOperationsPhotoOperationsDataMaintenanceOperations
Delivery-surface call sites should consume Operations APIs and keep
platform-only side effects in the app target. Existing *Service types remain
as implementation collaborators rather than public delivery-surface APIs.
- If an operation is reusable across more than one surface, add or extend a
library
*Operationsfacade first. - If an operation depends on Apple-only frameworks, keep it in
Cookleand make it call library Operations or supporting contracts. - If a view or App Intent starts recreating mutation rules, treat that as a missing Operations boundary.
- Keep platform-specific types out of
CookleLibrary. Convert them at the boundary into library models or value types. - If glue code is app-only but reused by multiple app entry points, factor it
into
Cookle/Sources/Platformor a dedicated app-side service.
- Keep repository-owned unit tests in
CookleLibrary/Tests/Default. - Do not add a separate unit test target for
Cookle,Widgets, orWatch. - If an app-side adapter or screen model starts needing durable coverage, first
extract the reusable rule into
CookleLibraryand test it there.
MainNavigationRouterstays inCooklebecause navigation meaning and compact settings presentation are app-only concerns.RecipeFormSaveCoordinatorandDiaryFormSaveCoordinatorstay inCooklebecause they convert screen state into canonical library drafts and inputs.RecipeOperations,RecipeFormOperations,DiaryOperations,TagOperations,PhotoOperations, andDataMaintenanceOperationsstay inCookleLibrarybecause their use cases must remain stable across the app, widgets, watch surface, and App Intents.- Service, builder, and planner types remain in
CookleLibraryas internal implementation collaborators behind the Operations boundary. NotificationServicestays inCooklebecause scheduling, authorization, and route delivery depend on Apple frameworks.SettingsActionServicestays inCooklebecause destructive reset follow-up is platform orchestration, while the actual reset use case is exposed throughDataMaintenanceOperations.
When a business rule is duplicated, the default fix is to move the rule into
CookleLibrary behind an Operations facade rather than duplicating it in a
view, App Intent, or widget.
When the duplicated code is still Apple-framework glue, the default fix is to
extract it into an app-side adapter in Cookle/Sources/Platform or a
feature-local app service.