Architecture and ADRs
3 min read
Architecture and ADRs
Architecture Decision Records (ADRs) are short documents that capture what you decided and why. They prevent Claude Code from second-guessing itself mid-build — and stop you from making inconsistent choices across sessions. This doc also covers design system generation: Claude Code derives your brand autonomously from the PRD so you don't have to specify every hex code by hand.
The iOS default stack
For most new iOS apps, these choices are correct. Only deviate if your PRD gives you a clear reason.
- UI framework: SwiftUI (not UIKit — unless your PRD targets iOS 15 or earlier)
- Architecture: MVVM with @Observable — never ObservableObject for new code
- All view models: @MainActor — UI state must be on the main thread
- Persistence: SwiftData for iOS 17+; Core Data as explicit fallback (document in ADR)
- Networking: URLSession with async/await — no Combine, no Alamofire unless PRD justifies it
- Dependency management: Swift Package Manager — no CocoaPods unless a needed package isn't on SPM
- Navigation: NavigationStack — never NavigationView (deprecated in iOS 16)
- State: @State for owned state, @Binding for passed-down state — never @StateObject or @ObservedObject in new code
- Concurrency: Swift 6 strict concurrency, all async work via async/await
Step 1: Generate ADRs
In your Claude Code session, paste this prompt. It generates ADRs for all key decisions in your specific project — not generic decisions, but ones driven by your PRD.
Read docs/prd/prd.md, docs/idea/idea-pack.md, and CLAUDE.md.
Generate Architecture Decision Records for this iOS project.Save each ADR to docs/adr/ using the template at docs/adr/template.md.Number sequentially: ADR-001, ADR-002, etc.
Generate ADRs for ALL of the following decisions.For each one, read the PRD first and make a recommendation based on what the app actually needs.Do not default to the most complex option — prefer the simplest option that meets the PRD requirements.
--- REQUIRED ADRs ---
ADR-001: UI FrameworkDecision: SwiftUI vs UIKitContext: Consider minimum iOS target from PRD, team familiarity, custom UI requirements.Recommendation basis: Choose SwiftUI unless PRD requires iOS < 16 or deeply custom UIKit components.
ADR-002: Architecture PatternDecision: MVVM vs MVC vs TCAContext: App complexity, state management needs from PRD, team structure.Recommendation basis: MVVM with @Observable for most apps. TCA only if PRD requires complex state machines.
ADR-003: State ManagementDecision: @Observable (iOS 17+) vs ObservableObject (iOS 16-)Context: Minimum iOS version from PRD.Recommendation basis: @Observable if targeting iOS 17+. Document the tradeoff explicitly.
ADR-004: Data PersistenceDecision: SwiftData vs Core Data vs UserDefaults vs no local storageContext: Data volume from PRD, offline requirements, sync requirements, iOS version target.Recommendation basis: - SwiftData: iOS 17+, moderate data needs, no legacy migration required - Core Data: iOS 16-, complex queries, existing data migration required - UserDefaults: Simple settings and preferences only (not user-generated data) - Network-only: App has no offline requirement and data lives entirely on backend
ADR-005: NetworkingDecision: URLSession (vanilla) vs Alamofire vs no networkingContext: Does the PRD require a backend? How complex is the API surface?Recommendation basis: - No networking: If PRD data model is 100% local - URLSession: Standard choice — async/await makes it clean without a library - Alamofire: Only if PRD requires complex retry logic, interceptors, or multipart uploads at scale
ADR-006: Navigation StructureDecision: TabView vs single NavigationStack vs NavigationSplitView (iPad)Context: Number of major sections from PRD, iPhone-only vs Universal.Recommendation basis: - TabView + NavigationStack per tab: 3-5 distinct app sections with independent navigation - Single NavigationStack: Linear flows, task-completion apps, wizard patterns - NavigationSplitView: iPad-required apps with sidebar navigation
ADR-007: Authentication (skip if PRD has no auth requirement)Decision: Sign in with Apple vs email/magic link vs anonymous vs noneContext: PRD auth requirements, user privacy expectations, App Store requirements.Recommendation basis: - Sign in with Apple: REQUIRED if app offers any other social login (App Store rule 4.8) - Anonymous: Valid for apps where users shouldn't need an account to get value - No auth: Valid for single-user local apps (notes, trackers, utilities)
ADR-008: Data Sync (skip if app is local-only)Decision: iCloud/CloudKit vs custom backend vs no syncContext: PRD multi-device requirements.Recommendation basis: - CloudKit: Best for Apple-ecosystem apps with no custom backend — handles auth, storage, sync - Custom backend: Required if PRD has cross-platform requirements or complex business logic - No sync: Explicitly acceptable for many utility apps — document the decision
ADR-009: AnalyticsDecision: App Store Connect analytics vs Firebase vs Mixpanel vs noneContext: Metrics defined in PRD Success Metrics section.Recommendation basis: - App Store Connect (free, built-in): Sufficient for most v1 apps — installs, retention, crashes - Firebase: If you need custom events and funnels - None for v1: Acceptable — add after launch when you know what to measure
ADR-010: Monetisation (skip if PRD specifies free with no IAP)Decision: Free vs one-time purchase vs subscription vs freemium via StoreKit 2Context: PRD business model.Recommendation basis: - StoreKit 2 (iOS 15+): Required for all App Store purchases — no third-party payment for digital goods - Subscription: Best for ongoing value (content, cloud sync, features) - One-time purchase: Best for utility apps with clear permanent value
--- OPTIONAL ADRs (include only if PRD requires) ---
If PRD requires push notifications:ADR-011: Push Notifications — APNs direct vs Firebase Cloud Messaging
If PRD requires background processing:ADR-012: Background Mode — Background fetch, background processing, or silent push
If PRD requires location:ADR-013: Location Access — Always vs When In Use vs none
After creating all ADRs, generate a summary at docs/adr/README.md that lists all ADRs with their status and one-line decision summary.Step 2: Generate your design system autonomously
Do not manually specify colours or fonts. Claude Code extracts your design system from your PRD and Idea Pack — the app's category, target users, tone, and competitive context. Run this prompt immediately after the ADRs are approved.
Read docs/prd/prd.md, docs/idea/idea-pack.md, and docs/adr/.
Generate a complete iOS design system for this app and implement it.Do not ask questions. Make design decisions based on the PRD and Idea Pack content.Follow Apple's Human Interface Guidelines throughout.
--- DESIGN SYSTEM GENERATION RULES ---
1. DERIVE brand tone from: - App category (fitness, finance, social, productivity, entertainment, etc.) - Target user persona from Idea Pack (age, context, tech comfort) - Competitive apps mentioned in Idea Pack (differentiate, don't copy) - Emotional job-to-be-done from Problem Statement
2. GENERATE a colour palette: - Primary (1 colour): The dominant brand colour. Must be memorable and work on both light and dark backgrounds. - Secondary/Accent (1 colour): Used sparingly for CTAs and highlights. High contrast with primary. - Background (light + dark variants): Off-white for light, near-black for dark — not pure #FFFFFF or #000000. - Surface (light + dark): Slightly elevated from background — for cards, sheets, modals. - On-Primary text: White or dark depending on primary contrast ratio (must meet WCAG AA: ≥ 4.5:1). - Semantic colours: Success (green-ish), Error (red-ish), Warning (amber-ish), Info (blue-ish). - All colours must meet WCAG AA contrast ratio for text on their intended backgrounds. - Provide the rationale for primary colour choice (1-2 sentences tied to brand tone).
3. GENERATE typography: - Use SF Pro (system font) by default — only use a custom font if the Idea Pack explicitly names one. - Define a type scale using Dynamic Type styles: largeTitle, title, title2, title3, headline, body, callout, subheadline, footnote, caption, caption2. - Specify which Dynamic Type style maps to each role (page title, section header, body copy, caption, label).
4. GENERATE spacing tokens (4pt base grid): - xs: 4pt, sm: 8pt, md: 16pt, lg: 24pt, xl: 32pt, xxl: 48pt, xxxl: 64pt
5. GENERATE shape tokens: - Corner radius: xs (4pt), sm (8pt), md (12pt), lg (16pt), xl (24pt), full (9999pt) - Shadows: define none/small/medium/large as shadow radius + offset + opacity values
6. GENERATE animation tokens: - snappy: duration 0.15s, spring damping 0.9 - standard: duration 0.25s, spring damping 0.8 - expressive: duration 0.4s, spring damping 0.7
--- OUTPUT FILES ---
After generating the design system, create these files:
A) docs/design-system/DESIGN-SYSTEM.md Full design system documentation. Include: - Brand rationale (1 paragraph — why these choices fit this app) - Colour palette table (name, hex light, hex dark, usage, WCAG ratio) - Typography mapping table (role → Dynamic Type style) - All tokens (spacing, radius, shadow, animation) as named tables - Liquid Glass guidance: which surfaces get glass treatment, iOS 26+ gated
B) [AppName]/Resources/DesignSystem.swift Swift constants file. Structure: ```swift import SwiftUI
enum DS { enum Color { static let primary = SwiftUI.Color("DSPrimary") static let secondary = SwiftUI.Color("DSSecondary") static let background = SwiftUI.Color("DSBackground") static let surface = SwiftUI.Color("DSSurface") static let onPrimary = SwiftUI.Color("DSOnPrimary") static let success = SwiftUI.Color("DSSuccess") static let error = SwiftUI.Color("DSError") static let warning = SwiftUI.Color("DSWarning") } enum Spacing { static let xs: CGFloat = 4 static let sm: CGFloat = 8 static let md: CGFloat = 16 static let lg: CGFloat = 24 static let xl: CGFloat = 32 static let xxl: CGFloat = 48 static let xxxl: CGFloat = 64 } enum Radius { static let xs: CGFloat = 4 static let sm: CGFloat = 8 static let md: CGFloat = 12 static let lg: CGFloat = 16 static let xl: CGFloat = 24 static let full: CGFloat = 9999 } enum Animation { static let snappy: SwiftUI.Animation = .spring(duration: 0.15, bounce: 0.1) static let standard: SwiftUI.Animation = .spring(duration: 0.25, bounce: 0.2) static let expressive: SwiftUI.Animation = .spring(duration: 0.4, bounce: 0.3) } } ```
C) Instructions for Assets.xcassets (Claude cannot write xcassets directly — provide step-by-step): Generate a numbered list of exactly what to add to Assets.xcassets/Colors/: For each colour: name (e.g. "DSPrimary"), light mode hex, dark mode hex. Tell the user to open Xcode → Assets.xcassets → + → Color Set, then enter both values. This is the only manual step in the design system setup.
After creating all files, print a one-paragraph summary of the design decisions and why they fit this app.Human review: ADRs
- Every ADR has a status of Accepted (not Proposed)
- docs/adr/README.md exists with a summary list
- No ADR defers a decision that will block Phase 1 of the build
- Authentication ADR is present if PRD requires accounts
- Data persistence ADR explicitly addresses offline behaviour
Human review: Design system
- docs/design-system/DESIGN-SYSTEM.md exists
- [AppName]/Resources/DesignSystem.swift exists
- WCAG AA contrast ratios are listed and all pass
- You've added the colours to Assets.xcassets (the one manual step)
- The brand rationale feels right for the app — if it doesn't, tell Claude why and ask it to revise
# Commit ADRs and design system togethergit add docs/adr/ docs/design-system/ [AppName]/Resources/DesignSystem.swiftgit commit -m "docs: architecture decisions and design system approved"With ADRs approved and the design system in place, you're ready to write the full CLAUDE.md. Go to Your Project's CLAUDE.md.
Sign up to read the full guide
Free access to all 12 workflow guides. No password needed.