Skill: Add Feature
Purpose
Add a new feature to the iOS/Swift project strictly following the MVVM + SPM modular architecture layer execution order. Each step must be completed before moving to the next.
Execution Flow — 8 Strict Steps
Step 1: API Models
Create in Sources/DatosFeature/ModelosApi/:
- Request models — Codable structs for API requests
- Response models — Codable structs for API responses
- Domain models — Mapped models for the presentation layer
- Use
CodingKeyswhen JSON keys differ from Swift naming - Never expose raw API models to the UI layer
Step 2: ApiImplementacion
Create in Sources/DatosFeature/:
- Protocol —
FeatureApiProtocolwith Combine publisher return typesprotocol FeatureApiProtocol { func obtenerDatos() -> AnyPublisher<FeatureModel, Error> } - Implementation —
FeatureApiImplementacionconforming to the protocol- Use Alamofire for network requests
- Return
AnyPublisher<T, Error>for all methods - Map API response models to domain models
- Handle errors with typed error mapping
Step 3: EnrutadorApi
Create in Sources/DatosFeature/:
- EnrutadorApiFeature — Route definitions for the feature's endpoints
- Base URL from configuration
- HTTP method, path, parameters, headers
- Follow existing
EnrutadorApipattern in the project
Step 4: ViewModel (@MainActor, @Published)
Create in Sources/PresentacionFeature/ViewModels/:
- Annotate class with
@MainActor - Conform to
ObservableObject - Declare
@Publishedproperties for all view state:isLoading: Boolresponse: FeatureModel?errorMessage: String?
- Inject dependencies via
initparameter (protocol-typed) - Use
@Injected(\.dependency)for Coordinator access - Store Combine subscriptions in
cancellables: Set<AnyCancellable> - Use
[weak self]in all closures - Receive values on
DispatchQueue.main
Step 5: SwiftUI View
Create in Sources/PresentacionFeature/Vistas/:
- Use
@StateObjectfor the owning ViewModel (created here) - Use
@ObservedObjectif ViewModel is passed from parent - Keep Views stateless — all logic in ViewModel
- Use CoreUI components (Atomos, Moleculas, Organismos) where available
- Handle loading, error, and empty states
- Follow Atomic Design principles for new UI components
Step 6: Coordinator Connection
Create or update in Sources/PresentacionFeature/Coordinadores/:
- Create
FeatureCoordinadorconforming to navigation protocol - Register navigation routes
- Handle deep linking if applicable
- Connect to parent Coordinator flow
- Never navigate directly from Views — always through Coordinator
Step 7: Factory DI Registration (Proveedor)
Create or update Sources/PresentacionFeature/ProveedorFeature.swift:
- Register API implementation:
Container.shared.featureApi.register { FeatureApiImplementacion() } - Register ViewModel factory if needed
- Register Coordinator
- Follow existing
Proveedorpattern in the project - Never use singletons outside Factory container
Step 8: Tests
Create in Tests/FeatureTests/:
- ViewModelTests — Full coverage of ViewModel methods
- MockApi — Mock with
Result<T, Error>return control - MockCoordinador — Spy for navigation verification
- Factory setUp/tearDown — Register mocks, reset container
- Use XCTestExpectation for Combine publisher assertions
- Follow AAA pattern with comments
- Coverage targets: 80% ViewModel, 70% Api
Auto-Shielding
- ABORT if the feature introduces a dependency not in the Golden Path — propose ADR first
- ABORT if the ViewModel does not use @MainActor — mandatory per ADR
- ABORT if navigation is done directly from View without Coordinator
- WARN if a step is attempted out of order
- WARN if more than 8 @Published properties in a single ViewModel — suggest decomposition
Rules
- Strictly follow step order: Models → Api → Router → ViewModel → View → Coordinator → DI → Tests
- Each step must compile before moving to the next
- Never skip the test step
- All API calls return
AnyPublisher<T, Error> - All ViewModels use
@MainActorandObservableObject - All dependencies injected via protocols (never concrete types)
- All navigation through Coordinator pattern
- DTOs never reach the UI — map to domain models in the data layer
- Use
[weak self]in all Combine sink closures - Run SwiftLint after all changes
Source: juankmvanegas/factoria-powers — distributed by TomeVault.