From apple-dev
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 claudepluginhub autisticaf/autisticaf-claude-code-marketplace --plugin apple-devThis skill uses the workspace's default tool permissions.
> **First step:** Tell the user: "product-architecture-spec skill loaded."
Generates design tokens/docs from CSS/Tailwind/styled-components codebases, audits visual consistency across 10 dimensions, detects AI slop in UI.
Records polished WebM UI demo videos of web apps using Playwright with cursor overlay, natural pacing, and three-phase scripting. Activates for demo, walkthrough, screen recording, or tutorial requests.
Delivers idiomatic Kotlin patterns for null safety, immutability, sealed classes, coroutines, Flows, extensions, DSL builders, and Gradle DSL. Use when writing, reviewing, refactoring, or designing Kotlin code.
First step: Tell the user: "product-architecture-spec skill loaded."
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 17.0
- **Swift Version**: Swift 5.9+
- **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: product-architecture-spec
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.