swift-ui-architect
SwiftUI Modular MVVM-C Architecture
Opinionated architecture enforcement for SwiftUI clinic-style apps. This skill aligns to the iOS 26 / Swift 6.2 clinic architecture: modular MVVM-C in local SPM packages, concrete coordinators and route shells in the App target, pure Domain protocols, and Data as the only I/O layer.
Mandated Architecture Stack
┌───────────────────────────────────────────────────────────────┐
│ App target: DependencyContainer, Coordinators, Route Shells │
├───────────────┬───────────────┬───────────────┬──────────────┤
│ Feature* SPM │ Feature* SPM │ Feature* SPM │ Feature* SPM │
│ View + VM │ View + VM │ View + VM │ View + VM │
├───────────────────────────────────────────────────────────────┤
│ Data SPM: repository impls, remote/local, retry, sync queue │
├───────────────────────────────────────────────────────────────┤
│ Domain SPM: models, repository protocols, coordinator protocols│
│ and ErrorRouting/AppError │
├───────────────────────────────────────────────────────────────┤
│ Shared SPMs: DesignSystem, SharedKit │
└───────────────────────────────────────────────────────────────┘
Dependency Rule: Feature modules import Domain + DesignSystem only. Features never import Data or other features. App target is the only convergence point.
Clinic Architecture Contract (iOS 26 / Swift 6.2)
All guidance in this skill assumes the clinic modular MVVM-C architecture:
- Feature modules import
Domain+DesignSystemonly (neverData, never sibling features) - App target is the convergence point and owns
DependencyContainer, concrete coordinators, and Route Shell wiring Domainstays pure Swift and defines models plus repository,*Coordinating,ErrorRouting, andAppErrorcontractsDataowns SwiftData/network/sync/retry/background I/O and implements Domain protocols- Read/write flow defaults to stale-while-revalidate reads and optimistic queued writes
- ViewModels call repository protocols directly (no default use-case/interactor layer)
When to Apply
Reference these guidelines when:
- Building or refactoring feature modules under local SPM packages
- Wiring coordinators, route shells, and dependency container factories
- Defining Domain protocols for repositories, coordinators, and error routing
- Enforcing Data-only ownership of networking, persistence, and sync
- Reviewing stale-while-revalidate reads and optimistic queued writes
Non-Negotiable Constraints (iOS 26 / Swift 6.2)
@Observablefor ViewModels/coordinators,ObservableObject/@Publishednever- No dedicated use-case/interactor layer: ViewModels call Domain repository protocols directly
- Coordinator protocols live in Domain; concrete coordinators own
NavigationPathin App target - Route shells live in App target and own
.navigationDestinationmapping AppError+ErrorRoutingdrive presentation policy; ViewModels do not hardcode global error UI- SwiftData / URLSession / retry / sync queue logic stays in Data package only
Rule Categories by Priority
| Priority | Category | Impact | Prefix | Rules |
|---|---|---|---|---|
| 1 | View Identity & Diffing | CRITICAL | diff- |
6 |
| 2 | State Architecture | CRITICAL | state- |
7 |
| 3 | View Composition | HIGH | view- |
6 |
| 4 | Navigation & Coordination | HIGH | nav- |
5 |
| 5 | Layer Architecture | HIGH | layer- |
6 |
| 6 | Dependency Injection | MEDIUM-HIGH | di- |
4 |
| 7 | List & Collection Performance | MEDIUM | list- |
4 |
| 8 | Async & Data Flow | MEDIUM | data- |
5 |
Quick Reference
1. View Identity & Diffing (CRITICAL)
diff-equatable-views- Apply @Equatable macro to every SwiftUI viewdiff-closure-skip- Use @SkipEquatable for closure/handler propertiesdiff-reference-types- Never store reference types without Equatable conformancediff-identity-stability- Use stable O(1) identifiers in ForEachdiff-avoid-anyview- Never use AnyView — use @ViewBuilder or genericsdiff-printchanges-debug- Use _printChanges() to diagnose unnecessary re-renders
2. State Architecture (CRITICAL)
state-observable-class- Use @Observable classes for all ViewModelsstate-ownership- @State for owned data, plain property for injected datastate-single-source- One source of truth per piece of statestate-scoped-observation- Leverage @Observable property-level trackingstate-binding-minimal- Pass @Binding only for two-way data flowstate-environment-global- Use @Environment for app-wide shared dependenciesstate-no-published- Never use @Published or ObservableObject
3. View Composition (HIGH)
view-body-complexity- Maximum 10 nodes in view bodyview-extract-subviews- Extract computed properties/helpers into separate View structsview-no-logic-in-body- Zero business logic in bodyview-minimal-dependencies- Pass only needed properties, not entire modelsview-viewbuilder-composition- Use @ViewBuilder for conditional compositionview-no-init-sideeffects- Never perform work in View init
4. Navigation & Coordination (HIGH)
nav-coordinator-pattern- Every feature has a coordinator owning NavigationStacknav-routes-enum- Define all routes as a Hashable enumnav-deeplink-support- Coordinators must support URL-based deep linkingnav-modal-sheets- Present modals via coordinator, not inlinenav-no-navigationlink- Never use NavigationLink(destination:) — use navigationDestination(for:)
5. Layer Architecture (HIGH)
layer-dependency-rule- Domain layer has zero framework importslayer-usecase-protocol- Do not add a use-case layer; keep orchestration in ViewModel + repository protocolslayer-repository-protocol- Repository protocols in Domain, implementations in Datalayer-model-value-types- Domain models are structs, never classeslayer-no-view-repository- Views never access repositories directly; ViewModel calls repository protocolslayer-viewmodel-boundary- ViewModels expose display-ready state only
6. Dependency Injection (MEDIUM-HIGH)
di-environment-injection- Inject container-managed protocol dependencies via @Environmentdi-protocol-abstraction- All injected dependencies are protocol typesdi-container-composition- ComposeDependencyContainerin App target and expose VM factoriesdi-mock-testing- Every protocol dependency has a mock for testing
7. List & Collection Performance (MEDIUM)
list-constant-viewcount- ForEach must produce constant view count per elementlist-filter-in-model- Filter/sort in ViewModel, never inside ForEachlist-lazy-stacks- Use LazyVStack/LazyHStack for unbounded contentlist-id-keypath- Provide explicit id keyPath — never rely on implicit identity
8. Async & Data Flow (MEDIUM)
data-task-modifier- Use.task(id:)as the primary feature data-loading triggerdata-async-init- Never perform async work in initdata-error-loadable- Model loading states as enum, not booleansdata-combine-avoid- Prefer async/await over Combine for new codedata-cancellation- Use .task automatic cancellation — never manage Tasks manually
How to Use
Read individual reference files for detailed explanations and code examples:
- Section definitions - Category structure and impact levels
- Rule template - Template for adding new rules
Reference Files
| File | Description |
|---|---|
| references/_sections.md | Category definitions and ordering |
| assets/templates/_template.md | Template for new rules |
| metadata.json | Version and reference information |
More from pproenca/dot-skills
zod
Zod schema validation best practices for type safety, parsing, and error handling. This skill should be used when defining z.object schemas, using z.string validations, safeParse, or z.infer. This skill does NOT cover React Hook Form integration patterns (use react-hook-form skill) or OpenAPI client generation (use orval skill).
2.0Kclean-architecture
Clean Architecture principles and best practices from Robert C. Martin's book. This skill should be used when designing software systems, reviewing code structure, or refactoring applications to achieve better separation of concerns. Triggers on tasks involving layers, boundaries, dependency direction, entities, use cases, or system architecture.
1.4Kemilkowal-animations
Emil Kowalski's animation best practices for web interfaces. Use when writing, reviewing, or implementing animations in React, CSS, or Framer Motion. Triggers on tasks involving transitions, easing, gestures, toasts, drawers, or motion.
918vitest
Vitest testing framework patterns for test setup, async testing, mocking with vi.*, snapshots, and test performance (formerly test-vitest). This skill should be used when writing or debugging Vitest tests. This skill does NOT cover TDD methodology (use test-tdd skill), API mocking with MSW (use test-msw skill), or Jest-specific APIs.
907typescript
This skill should be used when the user asks to "optimize TypeScript performance", "speed up tsc compilation", "configure tsconfig.json", "fix type errors", "improve async patterns", or encounters TS errors (TS2322, TS2339, "is not assignable to"). Also triggers on .ts, .tsx, .d.ts file work involving type definitions, module organization, or memory management. Does NOT cover TypeScript basics, framework-specific patterns, or testing.
821nuqs
nuqs (type-safe URL query state) best practices for Next.js applications. This skill should be used when writing, reviewing, or refactoring code that uses nuqs for URL state management. Triggers on tasks involving useQueryState, useQueryStates, search params, URL state, query parameters, nuqs parsers, or Next.js routing with state.
735