skills/product/architecture-spec/SKILL.md
Generates technical architecture specification from PRD. Covers architecture pattern, tech stack, data models, and app structure. Use when creating ARCHITECTURE.md or designing system architecture.
npx skillsauth add rshankras/claude-code-apple-skills architecture-specInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Generate technical architecture specification for iOS/macOS app.
This skill activates when the user says:
You are an iOS/macOS Architect AI agent specializing in Apple platform app architecture. Your job is to design a comprehensive technical architecture based on the Product Requirements Document (PRD) and make opinionated technology stack decisions following Apple best practices.
Before activating this skill, ensure:
docs/PRD.mdRead and extract information from:
docs/PRD.md
Product development plan (if available)
User preferences (ask if needed):
Generate docs/ARCHITECTURE.md with the following structure:
# Technical Architecture: [App Name]
**Version**: 1.0.0
**Last Updated**: [Date]
**Status**: Draft / In Review / Approved
**Owner**: Technical Architect
**Platform**: iOS [version]+ / macOS [version]+
---
## 1. Architecture Overview
### 1.1 Architecture Pattern
**Selected Pattern**: MVVM (Model-View-ViewModel) with SwiftUI
*or* Clean Architecture *or* TCA (The Composable Architecture)
**Reasoning**:
[Explain why this pattern was chosen based on app complexity]
**Characteristics**:
- **Layers**: [Describe the architectural layers]
- **Data Flow**: [Unidirectional / Bidirectional]
- **State Management**: [@Observable, Combine, TCA Store, etc.]
- **Testability**: [How architecture supports testing]
### 1.2 High-Level Component Diagram
┌─────────────────────────────────────────────────┐ │ Presentation Layer │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ Views │ │ViewModels│ │ Models │ │ │ │ (SwiftUI)│←→│(@Observ.)│←→│ (Data) │ │ │ └──────────┘ └──────────┘ └──────────┘ │ └────────────────────┬────────────────────────────┘ │ ┌────────────────────┴────────────────────────────┐ │ Business Logic Layer │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ Services │ │ Use │ │Repository│ │ │ │ │ │ Cases │ │ Pattern │ │ │ └──────────┘ └──────────┘ └──────────┘ │ └────────────────────┬────────────────────────────┘ │ ┌────────────────────┴────────────────────────────┐ │ Data Layer │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │SwiftData │ │ Network │ │ Keychain │ │ │ │ / Core │ │ Client │ │ Storage │ │ │ │ Data │ │ (URLSess)│ │ │ │ │ └──────────┘ └──────────┘ └──────────┘ │ └─────────────────────────────────────────────────┘
### 1.3 Key Architectural Decisions
| Decision | Choice | Alternative Considered | Rationale |
|----------|--------|----------------------|-----------|
| UI Framework | SwiftUI | UIKit | Modern, declarative, iOS 17+ target allows it |
| Data Persistence | SwiftData | Core Data | Simpler API, better SwiftUI integration |
| Architecture Pattern | MVVM | VIPER, TCA | Balanced complexity vs maintainability |
| Networking | URLSession | Alamofire | No third-party dependency needed |
| State Management | @Observable | Combine, TCA | iOS 17+ Observation framework |
| Navigation | NavigationStack | Coordinator | SwiftUI native, simpler for MVP |
---
## 2. Technology Stack
### 2.1 Apple Frameworks
**UI & Presentation**:
- **SwiftUI** (primary) - Declarative UI framework
- Minimum iOS 17.0 for @Observable, ContentUnavailableView, etc.
- Navigation: NavigationStack, NavigationPath
- Data binding: @State, @Binding, @Environment
**Data Persistence**:
- **SwiftData** (iOS 17+) - Data modeling and persistence
- @Model macro for model classes
- ModelContainer for database configuration
- ModelContext for CRUD operations
- @Query property wrapper for automatic observation
**Networking & Concurrency**:
- **URLSession** - HTTP networking
- **async/await** - Concurrency
- **Actors** - Thread-safe state management
- **Codable** - JSON serialization/deserialization
**Security**:
- **Keychain Services** - Secure credential storage
- **CryptoKit** - Encryption (if needed)
- **LocalAuthentication** - Biometric authentication (if needed)
**Other**:
- [List any other frameworks based on features]
- MapKit (if maps needed)
- Vision (if image recognition)
- CoreML (if ML features)
- StoreKit (if IAP)
- CloudKit (if iCloud sync)
### 2.2 Third-Party Dependencies
**Via Swift Package Manager**:
1. **[Package Name]** (if needed)
- **Repository**: https://github.com/[org]/[repo]
- **Version**: ~> X.X.X
- **Purpose**: [Why this is needed]
- **Alternative Considered**: [Why not chosen]
- **License**: [MIT, Apache, etc.]
*Note*: Keep dependencies minimal. Only add if:
- Provides significant value not available in Apple frameworks
- Well-maintained and trusted
- No suitable alternative
**Decision**: Start with zero third-party dependencies for MVP. Add only if needed.
### 2.3 Development Tools
- **Xcode**: [Latest stable version]
- **iOS Deployment Target**: iOS 26.0 (adjust lower for broader reach — iOS 17+ retains `@Observable` and SwiftData support)
- **Swift Version**: Swift 6+
- **Package Manager**: Swift Package Manager (SPM)
- **CI/CD**: Xcode Cloud / GitHub Actions (to be determined)
---
## 3. App Structure
### 3.1 Module Breakdown
[AppName]/ ├── App/ │ ├── [AppName]App.swift # App entry point (@main) │ ├── ContentView.swift # Root view │ └── AppState.swift # Global app state (if needed) │ ├── Features/ # Feature-based modules │ ├── Home/ │ │ ├── Views/ │ │ │ ├── HomeView.swift │ │ │ ├── HomeCardView.swift │ │ │ └── HomeEmptyStateView.swift │ │ ├── ViewModels/ │ │ │ └── HomeViewModel.swift │ │ └── Models/ │ │ └── HomeItem.swift (if feature-specific) │ │ │ ├── [Feature2]/ │ │ ├── Views/ │ │ ├── ViewModels/ │ │ └── Models/ │ │ │ └── [Feature3]/ │ └── ... │ ├── Core/ # Shared core functionality │ ├── Networking/ │ │ ├── APIClient.swift # HTTP client │ │ ├── APIEndpoint.swift # Endpoint definitions │ │ ├── APIError.swift # Error types │ │ └── RequestModels/ # API request DTOs │ │ └── ... │ │ │ ├── Storage/ │ │ ├── DataManager.swift # SwiftData container wrapper │ │ └── KeychainManager.swift # Keychain operations │ │ │ ├── Extensions/ │ │ ├── View+Extensions.swift # SwiftUI View extensions │ │ ├── Color+Extensions.swift # Color palette │ │ ├── Font+Extensions.swift # Typography │ │ └── Date+Extensions.swift # Date utilities │ │ │ └── Utilities/ │ ├── Logger.swift # Logging utility │ ├── Validator.swift # Input validation │ └── Constants.swift # App constants │ ├── Models/ # Domain models (shared) │ ├── User.swift # @Model classes │ ├── [Entity2].swift │ └── ResponseModels/ # API response DTOs │ └── ... │ ├── Services/ # Business logic services │ ├── AuthenticationService.swift │ ├── [Feature]Service.swift │ └── SyncService.swift (if background sync) │ ├── Resources/ │ ├── Assets.xcassets # Images, colors │ ├── Localizable.xcstrings # Translations │ └── PrivacyInfo.xcprivacy # Privacy manifest │ └── Tests/ ├── UnitTests/ │ ├── ViewModelTests/ │ ├── ServiceTests/ │ └── ModelTests/ └── UITests/ └── ...
**Organizational Principles**:
- **Feature-based organization**: Each major feature in its own folder
- **Vertical slicing**: Feature folder contains Views, ViewModels, and feature-specific Models
- **Core for shared**: Reusable components go in Core/
- **Models for domain**: Shared domain models (SwiftData @Model classes)
- **Services for business logic**: Business logic that spans features
### 3.2 Data Models
Based on PRD requirements, core entities are:
#### [Entity 1]: User
```swift
import Foundation
import SwiftData
@Model
final class User {
// Identity
@Attribute(.unique) var id: UUID
var email: String
var name: String
var createdAt: Date
var updatedAt: Date
// Relationships
@Relationship(deleteRule: .cascade)
var [relatedEntities]: [RelatedEntity]
// Computed Properties
var displayName: String {
name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
? email
: name
}
// Validation
var isValid: Bool {
!email.isEmpty && email.contains("@") && !name.isEmpty
}
init(email: String, name: String) {
self.id = UUID()
self.email = email
self.name = name
self.createdAt = Date()
self.updatedAt = Date()
}
}
@Model
final class [Entity2] {
@Attribute(.unique) var id: UUID
var [property1]: String
var [property2]: Date
// Relationships
@Relationship(inverse: \User.[relatedEntities])
var owner: User?
init(...) {
// Initialization
}
}
Entity Relationships:
SwiftData Considerations:
Pattern: NavigationStack (SwiftUI native)
Primary Navigation:
Navigation State:
// In each feature's root view
@State private var navigationPath = NavigationPath()
NavigationStack(path: $navigationPath) {
ListView()
.navigationDestination(for: Item.self) { item in
DetailView(item: item)
}
.navigationDestination(for: EditMode.self) { _ in
EditView()
}
}
Deep Linking:
[appname]://[route]/[id].onOpenURL modifier at app rootModal Presentation:
.sheet for full-screen modal forms.alert for simple confirmations.confirmationDialog for action sheetsPattern: @Observable (iOS 17+ Observation framework)
State Layers:
View State (@State)
ViewModel State (@Observable)
Persistent State (SwiftData @Query)
Example ViewModel:
import Foundation
import Observation
@Observable
final class HomeViewModel {
// Published state
var items: [Item] = []
var isLoading = false
var errorMessage: String?
var showError = false
// Dependencies (injected)
private let apiClient: APIClient
private let dataManager: DataManager
init(apiClient: APIClient = .shared,
dataManager: DataManager = .shared) {
self.apiClient = apiClient
self.dataManager = dataManager
}
// Actions
@MainActor
func loadItems() async {
isLoading = true
defer { isLoading = false }
do {
let fetchedItems = try await apiClient.fetchItems()
items = fetchedItems
// Persist to SwiftData
try dataManager.saveItems(fetchedItems)
} catch {
errorMessage = error.localizedDescription
showError = true
}
}
}
Data Flow Diagram:
User Action (Tap Button)
↓
View calls ViewModel method
↓
ViewModel calls Service/APIClient
↓
Service makes API call
↓
Response updates ViewModel @Observable properties
↓
SwiftUI automatically updates View
↓
(Optional) Persist to SwiftData
Strategy: Local-first with optional sync
Local Storage:
SwiftData Setup:
// In App struct
@main
struct [AppName]App: App {
let container: ModelContainer
init() {
do {
let schema = Schema([User.self, Item.self, ...])
let config = ModelConfiguration(
schema: schema,
isStoredInMemoryOnly: false
)
container = try ModelContainer(
for: schema,
configurations: config
)
} catch {
fatalError("Failed to create ModelContainer: \(error)")
}
}
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(container)
}
}
Data Migration:
Backup & Sync (if needed):
Architecture: Protocol-oriented with async/await
APIClient Design:
actor APIClient {
static let shared = APIClient()
private let baseURL = URL(string: "https://api.example.com/v1")!
private let session: URLSession
private var authToken: String?
init() {
let config = URLSessionConfiguration.default
config.timeoutIntervalForRequest = 30
config.waitsForConnectivity = true
self.session = URLSession(configuration: config)
}
// Generic request method
func request<T: Decodable>(
_ endpoint: APIEndpoint,
responseType: T.Type
) async throws -> T {
var request = URLRequest(url: baseURL.appendingPathComponent(endpoint.path))
request.httpMethod = endpoint.method.rawValue
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
// Add auth token if available
if let token = authToken {
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
}
// Add body for POST/PUT
if let body = endpoint.body {
request.httpBody = try JSONEncoder().encode(body)
}
// Perform request
let (data, response) = try await session.data(for: request)
// Validate response
guard let httpResponse = response as? HTTPURLResponse else {
throw APIError.invalidResponse
}
guard (200...299).contains(httpResponse.statusCode) else {
throw APIError.httpError(statusCode: httpResponse.statusCode, data: data)
}
// Decode
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .iso8601
return try decoder.decode(T.self, from: data)
}
}
// Endpoint definition
struct APIEndpoint {
let path: String
let method: HTTPMethod
let body: (any Encodable)?
enum HTTPMethod: String {
case get = "GET"
case post = "POST"
case put = "PUT"
case delete = "DELETE"
case patch = "PATCH"
}
}
// Error handling
enum APIError: LocalizedError {
case invalidResponse
case httpError(statusCode: Int, data: Data?)
case decodingError(Error)
case networkError(Error)
var errorDescription: String? {
switch self {
case .invalidResponse:
return "Invalid response from server"
case .httpError(let code, _):
return "Server error: \(code)"
case .decodingError:
return "Failed to parse response"
case .networkError:
return "Network connection failed"
}
}
}
Request/Response Models:
Core/Networking/RequestModels/ and ResponseModels/Error Handling Strategy:
Caching:
Sensitive Data Storage:
// KeychainManager for secure storage
final class KeychainManager {
static let shared = KeychainManager()
func save(key: String, data: Data) throws {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key,
kSecValueData as String: data
]
let status = SecItemAdd(query as CFDictionary, nil)
guard status == errSecSuccess else {
throw KeychainError.saveFailed(status)
}
}
func retrieve(key: String) throws -> Data {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key,
kSecReturnData as String: true
]
var result: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &result)
guard status == errSecSuccess, let data = result as? Data else {
throw KeychainError.retrieveFailed(status)
}
return data
}
}
Encryption:
Communication Security:
Privacy Manifest (PrivacyInfo.xcprivacy):
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyCollectedDataTypes</key>
<array>
<dict>
<key>NSPrivacyCollectedDataType</key>
<string>NSPrivacyCollectedDataTypeEmailAddress</string>
<key>NSPrivacyCollectedDataTypeLinked</key>
<true/>
<key>NSPrivacyCollectedDataTypeTracking</key>
<false/>
<key>NSPrivacyCollectedDataTypePurposes</key>
<array>
<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
</array>
</dict>
</array>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<!-- List any required reason APIs used -->
</array>
</dict>
</plist>
Data Collection Policy:
App Tracking Transparency:
Cold Launch (< 1.5s target):
Warm Launch (< 0.5s target):
Best Practices:
[weak self] in closures@MainActor for UI updatesImage Handling:
Background Refresh:
// If needed for data sync
func scheduleBackgroundRefresh() {
BGTaskScheduler.shared.register(
forTaskWithIdentifier: "com.app.refresh",
using: nil
) { task in
self.handleBackgroundRefresh(task: task as! BGAppRefreshTask)
}
}
Background URLSession:
Strategies:
Coverage Target: 70%+ for business logic
What to Test:
Testing Framework: XCTest
Example:
import XCTest
@testable import [AppName]
final class HomeViewModelTests: XCTestCase {
var sut: HomeViewModel!
var mockAPIClient: MockAPIClient!
var mockDataManager: MockDataManager!
override func setUp() {
super.setUp()
mockAPIClient = MockAPIClient()
mockDataManager = MockDataManager()
sut = HomeViewModel(
apiClient: mockAPIClient,
dataManager: mockDataManager
)
}
override func tearDown() {
sut = nil
mockAPIClient = nil
mockDataManager = nil
super.tearDown()
}
func testLoadItems_Success() async throws {
// Given
let expectedItems = [Item(id: UUID(), name: "Test")]
mockAPIClient.itemsToReturn = expectedItems
// When
await sut.loadItems()
// Then
XCTAssertEqual(sut.items, expectedItems)
XCTAssertFalse(sut.isLoading)
XCTAssertFalse(sut.showError)
}
func testLoadItems_Failure() async throws {
// Given
mockAPIClient.shouldThrowError = true
// When
await sut.loadItems()
// Then
XCTAssertTrue(sut.showError)
XCTAssertNotNil(sut.errorMessage)
XCTAssertTrue(sut.items.isEmpty)
}
}
Coverage Target: Critical user journeys only (~10% of tests)
What to Test:
Framework: XCTest with XCUITest
Best Practices:
What to Test:
Mock Types:
Dependency Injection:
Debug:
Release:
Configuration:
enum Environment {
case development
case staging
case production
static var current: Environment {
#if DEBUG
return .development
#else
return .production
#endif
}
var apiBaseURL: URL {
switch self {
case .development:
return URL(string: "https://dev.api.example.com")!
case .staging:
return URL(string: "https://staging.api.example.com")!
case .production:
return URL(string: "https://api.example.com")!
}
}
}
Recommended: Xcode Cloud or GitHub Actions
Pipeline Stages:
On Pull Request:
On Merge to Main:
On Tag (e.g., v1.0.0):
Example GitHub Actions (placeholder):
name: CI
on: [pull_request, push]
jobs:
test:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Build and Test
run: |
xcodebuild clean build test \
-scheme [AppName] \
-destination 'platform=iOS Simulator,name=iPhone 15'
Implementation: (If needed for gradual rollouts)
Risk: SwiftData is relatively new, may have bugs or limitations Impact: Data loss, migration issues, performance problems Probability: Medium Mitigation:
Risk: Limits addressable market (older iOS versions excluded) Impact: Reduced potential user base Probability: Certain Mitigation:
Risk: App requires network for most features Impact: Poor user experience in offline scenarios Probability: High Mitigation:
Risk: Backend API downtime or rate limiting Impact: App functionality degraded Probability: Low-Medium Mitigation:
After MVP Launch:
Monitor:
Code Documentation:
Architecture Decision Records (ADRs):
Onboarding:
Technical KPIs:
Monitoring:
Document History: | Version | Date | Author | Changes | |---------|------|--------|---------| | 1.0.0 | [Date] | [Name] | Initial architecture design |
## Execution Instructions
When activated, follow these steps:
1. **Read PRD**
Read docs/PRD.md Extract:
2. **Assess Complexity**
- Simple (1-3 core features, basic CRUD): MVVM with SwiftUI
- Medium (4-8 features, some complexity): MVVM or Clean Architecture
- Complex (9+ features, high complexity): Clean Architecture or TCA
3. **Make Technology Decisions**
Based on PRD requirements and iOS version target:
- iOS 17+ → SwiftUI + SwiftData + @Observable (recommended)
- iOS 16+ → SwiftUI + Core Data + Combine
- UIKit → Only if strong reason (legacy, specific UI needs)
4. **Ask User Preferences** (if needed)
Quick questions about the architecture:
Do you have a preference for UI framework?
Do you have a backend API already?
Any required third-party libraries?
5. **Create Output Directory**
```bash
mkdir -p docs
Generate ARCHITECTURE.md
Write to File
Write to: docs/ARCHITECTURE.md
Present Summary
✅ Technical Architecture generated!
🏗️ **Architecture Summary**:
- Document: docs/ARCHITECTURE.md
- Pattern: [MVVM / Clean / TCA]
- UI Framework: [SwiftUI / UIKit]
- Data Persistence: [SwiftData / Core Data]
- Minimum iOS: [17.0 / 16.0 / 15.0]
- Third-party deps: [X] (or "None - Apple frameworks only")
- Data models: [X] entities defined
**Key Decisions**:
1. [Decision 1]: [Choice] - [Reason]
2. [Decision 2]: [Choice] - [Reason]
3. [Decision 3]: [Choice] - [Reason]
**Next Steps**:
1. Review the architecture in docs/ARCHITECTURE.md
2. Confirm technology stack choices
3. Once approved, we can proceed to UX spec
Any questions or changes to the architecture?
Iterate if Needed
Be Opinionated: Make clear technology choices
Explain Reasoning: Every major decision should have rationale
Be Specific: Provide actual code examples
Consider Trade-offs: Document risks and mitigations
Stay Current: Use modern Swift and iOS features
Follow Apple HIG: Architecture should enable HIG compliance
This skill is typically:
The architecture document guides all downstream technical decisions.
development
US web checkout via the StoreKit External Purchase Link entitlement — currently 0% Apple commission (litigation ongoing), how to ship it safely, and how to architect for a commission flip so a future ruling is a config change, not a rewrite. Use when adding external purchase links, weighing web checkout vs IAP, or planning US-storefront pricing strategy.
tools
Revenue beyond the single-app price tag — own-app bundles, Family Sharing as a conversion lever, cross-developer bundles & suites, and institutional licensing via Group Purchases / Apple School & Business Manager. Use when a developer has multiple apps, a subscription worth sharing, complementary indie partners, or school/clinic/business buyers.
testing
Run a structured accessibility audit on an iOS/macOS app — automated XCUITest audits, Accessibility Inspector, manual VoiceOver/Dynamic Type passes, and App Store Accessibility Nutrition Label evaluation. Use before release, when preparing Nutrition Label declarations, or for EU Accessibility Act compliance.
tools
Stage-by-stage audit of an app's App Store growth machinery against a 54-item P0–P9 playbook — every item scored from an App Store Connect MCP call, a codebase check, or an explicit question to the user, then routed to the skill or command that fixes it. Read-only on App Store Connect. Use for a growth audit or scorecard, a pre-launch growth plan, a quarterly re-audit, or "which growth levers am I missing."