[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6E63S0VBJ7YZRYAXPB4RRDA":3,"skill-same:01M6E63S0VBJ7YZRYAXPB4RRDA":75},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":39,"linkOnly":40,"markdownContent":41,"markdownBody":42,"frontMatter":43,"refName":29,"commitSha":47,"skillDirectory":48,"forksCount":49,"repositoryHtmlUrl":50,"repositoryHomepageUrl":51,"ownerHtmlUrl":52,"ownerBlogUrl":6,"ownerDescription":53,"updatedAt":54,"install":55,"resources":68},"01M6E63S0VBJ7YZRYAXPB4RRDA","axiom-app-composition",null,"Use when structuring app entry points, managing authentication flows, switching root views, handling scene lifecycle, or asking 'how do I structure my @main', 'where does auth state live', 'how do I prevent screen flicker on launch', 'when should I modularize' - app-level composition patterns for iOS 26+","ComeOnOliver\u002Fskillshub","ComeOnOliver","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F43215937?v=4","01M6E5JNFECJAAVFE0W7SMWWD2","Haoran Shu","01M6E5JNNDQS7AJZDYFQ8TPZCK",66,"MIT","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002FCharlesWiltgen\u002FAxiom\u002Faxiom-app-composition\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38],"axiom app composition","app","Composition","do","screen","live","switching","xcode","structure","patterns","main","macos","use","how","lifecycle","ios","state","authentication","auth","launch","2026-11-10 10:12:30",false,"---\nname: axiom-app-composition\ndescription: Use when structuring app entry points, managing authentication flows, switching root views, handling scene lifecycle, or asking 'how do I structure my @main', 'where does auth state live', 'how do I prevent screen flicker on launch', 'when should I modularize' - app-level composition patterns for iOS 26+\nlicense: MIT\ncompatibility: iOS 26+, iPadOS 26+, macOS Tahoe+, watchOS 26+, axiom-visionOS 26+. Xcode 26+\nmetadata:\n  version: \"1.0\"\n---\n\n# App Composition\n\n## When to Use This Skill\n\nUse this skill when:\n- Structuring your @main entry point and root view\n- Managing authentication state (login → onboarding → main)\n- Switching between app-level states without flicker\n- Handling scene lifecycle events (scenePhase)\n- Restoring app state after termination\n- Deciding when to split into feature modules\n- Coordinating between multiple windows (iPad, axiom-visionOS)\n\n## Example Prompts\n\n| What You Might Ask | Why This Skill Helps |\n|--------------------|----------------------|\n| \"How do I switch between login and main screens?\" | AppStateController pattern with validated transitions |\n| \"My app flickers when switching from splash to main\" | Flicker prevention with animation coordination |\n| \"Where should auth state live?\" | App-level state machine, not scattered booleans |\n| \"How do I handle app going to background?\" | scenePhase lifecycle patterns |\n| \"When should I split my app into modules?\" | Decision tree based on codebase size and team |\n| \"How do I restore state after app is killed?\" | SceneStorage and state validation patterns |\n\n## Quick Decision Tree\n\n```\nWhat app-level architecture question are you solving?\n│\n├─ How do I manage app states (loading, auth, main)?\n│  └─ Part 1: App-Level State Machines\n│     - Enum-based state with validated transitions\n│     - AppStateController pattern\n│     - Prevents \"boolean soup\" anti-pattern\n│\n├─ How do I structure @main and root view switching?\n│  └─ Part 2: Root View Switching Patterns\n│     - Delegate to AppStateController (no logic in @main)\n│     - Flicker prevention with animation\n│     - Coordinator integration\n│\n├─ How do I handle scene lifecycle?\n│  └─ Part 3: Scene Lifecycle Integration\n│     - scenePhase for session validation\n│     - SceneStorage for restoration\n│     - Multi-window coordination\n│\n├─ When should I modularize?\n│  └─ Part 4: Feature Module Basics\n│     - Decision tree by size\u002Fteam\n│     - Module boundaries and DI\n│     - Navigation coordination\n│\n└─ What mistakes should I avoid?\n   └─ Part 5: Anti-Patterns + Part 6: Pressure Scenarios\n      - Boolean-based state\n      - Logic in @main\n      - Missing restoration validation\n```\n\n---\n\n# Part 1: App-Level State Machines\n\n## Core Principle\n\n> \"Apps have discrete states. Model them explicitly with enums, not scattered booleans.\"\n\nEvery non-trivial app has distinct states: loading, unauthenticated, onboarding, authenticated, error recovery. These states should be:\n1. **Explicit** — An enum, not multiple booleans\n2. **Validated** — Transitions are checked and logged\n3. **Centralized** — One source of truth\n4. **Observable** — Views react to state changes\n\n## The Boolean Soup Problem\n\n```swift\n\u002F\u002F ❌ Boolean soup — impossible to validate, prone to invalid states\nclass AppState {\n    var isLoading = true\n    var isLoggedIn = false\n    var hasCompletedOnboarding = false\n    var hasError = false\n    var user: User?\n\n    \u002F\u002F What if isLoading && isLoggedIn && hasError are all true?\n    \u002F\u002F Invalid state, but nothing prevents it\n}\n```\n\n**Problems**\n- No compile-time guarantee of valid states\n- Easy to forget to update one boolean\n- Testing requires checking all combinations\n- Race conditions create impossible states\n\n## The AppStateController Pattern\n\n### Step 1: Define Explicit States\n\n```swift\nenum AppState: Equatable {\n    case loading\n    case unauthenticated\n    case onboarding(OnboardingStep)\n    case authenticated(User)\n    case error(AppError)\n}\n\nenum OnboardingStep: Equatable {\n    case welcome\n    case permissions\n    case profileSetup\n    case complete\n}\n\nenum AppError: Equatable {\n    case networkUnavailable\n    case sessionExpired\n    case maintenanceMode\n}\n```\n\n### Step 2: Create the Controller\n\n```swift\n@Observable\n@MainActor\nclass AppStateController {\n    private(set) var state: AppState = .loading\n\n    \u002F\u002F MARK: - State Transitions\n\n    func transition(to newState: AppState) {\n        guard isValidTransition(from: state, to: newState) else {\n            assertionFailure(\"Invalid transition: \\(state) → \\(newState)\")\n            logInvalidTransition(from: state, to: newState)\n            return\n        }\n\n        let oldState = state\n        state = newState\n        logTransition(from: oldState, to: newState)\n    }\n\n    \u002F\u002F MARK: - Validation\n\n    private func isValidTransition(from: AppState, to: AppState) -> Bool {\n        switch (from, to) {\n        \u002F\u002F From loading\n        case (.loading, .unauthenticated): return true\n        case (.loading, .authenticated): return true\n        case (.loading, .error): return true\n\n        \u002F\u002F From unauthenticated\n        case (.unauthenticated, .onboarding): return true\n        case (.unauthenticated, .authenticated): return true\n        case (.unauthenticated, .error): return true\n\n        \u002F\u002F From onboarding\n        case (.onboarding, .onboarding): return true  \u002F\u002F Step changes\n        case (.onboarding, .authenticated): return true\n        case (.onboarding, .unauthenticated): return true  \u002F\u002F Cancelled\n\n        \u002F\u002F From authenticated\n        case (.authenticated, .unauthenticated): return true  \u002F\u002F Logout\n        case (.authenticated, .error): return true\n\n        \u002F\u002F From error\n        case (.error, .loading): return true  \u002F\u002F Retry\n        case (.error, .unauthenticated): return true\n\n        default: return false\n        }\n    }\n\n    \u002F\u002F MARK: - Logging\n\n    private func logTransition(from: AppState, to: AppState) {\n        #if DEBUG\n        print(\"AppState: \\(from) → \\(to)\")\n        #endif\n    }\n\n    private func logInvalidTransition(from: AppState, to: AppState) {\n        \u002F\u002F Log to analytics for debugging\n        Analytics.log(\"InvalidStateTransition\", properties: [\n            \"from\": String(describing: from),\n            \"to\": String(describing: to)\n        ])\n    }\n}\n```\n\n### Step 3: Initialize from Storage\n\n```swift\nextension AppStateController {\n    func initialize() async {\n        \u002F\u002F Check for stored session\n        if let session = await SessionStorage.loadSession() {\n            \u002F\u002F Validate session is still valid\n            do {\n                let user = try await AuthService.validateSession(session)\n                transition(to: .authenticated(user))\n            } catch {\n                \u002F\u002F Session expired or invalid\n                await SessionStorage.clearSession()\n                transition(to: .unauthenticated)\n            }\n        } else {\n            transition(to: .unauthenticated)\n        }\n    }\n}\n```\n\n## State Machine Diagram\n\n```\n┌─────────────────────────────────────────────────────────────┐\n│                        .loading                              │\n└────────────┬───────────────┬────────────────┬───────────────┘\n             │               │                │\n             ▼               ▼                ▼\n    .unauthenticated   .authenticated     .error\n             │               │                │\n             ▼               │                │\n       .onboarding ─────────►│◄───────────────┘\n             │               │\n             └───────────────┘\n```\n\n## Testing State Machines\n\n```swift\n@Test func testValidTransitions() async {\n    let controller = AppStateController()\n\n    \u002F\u002F Loading → Unauthenticated (valid)\n    controller.transition(to: .unauthenticated)\n    #expect(controller.state == .unauthenticated)\n\n    \u002F\u002F Unauthenticated → Authenticated (valid)\n    let user = User(id: \"1\", name: \"Test\")\n    controller.transition(to: .authenticated(user))\n    #expect(controller.state == .authenticated(user))\n}\n\n@Test func testInvalidTransitionRejected() async {\n    let controller = AppStateController()\n\n    \u002F\u002F Loading → Onboarding (invalid — must go through unauthenticated)\n    controller.transition(to: .onboarding(.welcome))\n    #expect(controller.state == .loading)  \u002F\u002F Unchanged\n}\n\n@Test func testSessionExpiredTransition() async {\n    let controller = AppStateController()\n    let user = User(id: \"1\", name: \"Test\")\n    controller.transition(to: .authenticated(user))\n\n    \u002F\u002F Authenticated → Error (session expired)\n    controller.transition(to: .error(.sessionExpired))\n    #expect(controller.state == .error(.sessionExpired))\n\n    \u002F\u002F Error → Unauthenticated (force re-login)\n    controller.transition(to: .unauthenticated)\n    #expect(controller.state == .unauthenticated)\n}\n```\n\n## The State-as-Bridge Pattern (WWDC 2025\u002F266)\n\nFrom WWDC 2025's \"Explore concurrency in SwiftUI\":\n\n> \"Find the boundaries between UI code that requires time-sensitive changes, and long-running async logic.\"\n\nThe key insight: **synchronous state changes drive UI** (for animations), **async code lives in the model** (testable without SwiftUI), and **state bridges the two**.\n\n```swift\n\u002F\u002F ✅ State-as-Bridge: UI triggers state, model does async work\nstruct ColorExtractorView: View {\n    @State private var model = ColorExtractor()\n\n    var body: some View {\n        Button(\"Extract Colors\") {\n            \u002F\u002F ✅ Synchronous state change triggers animation\n            withAnimation { model.isExtracting = true }\n\n            \u002F\u002F Async work happens in Task\n            Task {\n                await model.extractColors()\n\n                \u002F\u002F ✅ Synchronous state change ends animation\n                withAnimation { model.isExtracting = false }\n            }\n        }\n        .scaleEffect(model.isExtracting ? 1.5 : 1.0)\n    }\n}\n\n@Observable\nclass ColorExtractor {\n    var isExtracting = false\n    var colors: [Color] = []\n\n    func extractColors() async {\n        \u002F\u002F Heavy computation happens here, testable without SwiftUI\n        let extracted = await heavyComputation()\n        colors = extracted\n    }\n}\n```\n\n**Why this matters for app composition**\n- App-level state changes (loading → authenticated) should be **synchronous**\n- Heavy work (session validation, data loading) should be **async in the model**\n- This separation makes state machines testable without SwiftUI imports\n\n---\n\n# Part 2: Root View Switching Patterns\n\n## Core Principle\n\n> \"The @main entry point should be a thin shell. All logic belongs in AppStateController.\"\n\n## The Clean @main Pattern\n\n```swift\n@main\nstruct MyApp: App {\n    @State private var appState = AppStateController()\n\n    var body: some Scene {\n        WindowGroup {\n            RootView()\n                .environment(appState)\n                .task {\n                    await appState.initialize()\n                }\n        }\n    }\n}\n```\n\n**What @main does**\n- Creates AppStateController\n- Injects it via environment\n- Triggers initialization\n\n**What @main does NOT do**\n- Business logic\n- Auth checks\n- Conditional rendering\n- Navigation decisions\n\n## RootView: The State Switch\n\n```swift\nstruct RootView: View {\n    @Environment(AppStateController.self) private var appState\n\n    var body: some View {\n        Group {\n            switch appState.state {\n            case .loading:\n                LaunchView()\n            case .unauthenticated:\n                AuthenticationFlow()\n            case .onboarding(let step):\n                OnboardingFlow(step: step)\n            case .authenticated(let user):\n                MainTabView(user: user)\n            case .error(let error):\n                ErrorRecoveryView(error: error)\n            }\n        }\n    }\n}\n```\n\n## Testability Benefit\n\nThe thin-shell pattern enables up to 60x faster tests. When app logic lives in a Swift Package instead of the app target, tests run with `swift test` (~0.4s) vs `xcodebuild test` (~25s) — no simulator, no app launch.\n\n| Component | Location | Tested With |\n|-----------|----------|-------------|\n| Business logic, models, services | Swift Package (`MyAppCore`) | `swift test` (0.4s) |\n| Root view composition | App target (thin shell) | `xcodebuild test` (25s) |\n\nSee `axiom-swift-testing` Strategy 1 for the complete package extraction walkthrough.\n\n## Preventing Flicker During Transitions\n\n### Problem: Flash of Wrong Content\n\nWhen app state changes, you might see a flash of the old screen before the new one appears. This happens when:\n- State changes before view is ready\n- No transition animation\n- Loading state too short to perceive\n\n### Solution: Animated Transitions\n\n```swift\nstruct RootView: View {\n    @Environment(AppStateController.self) private var appState\n\n    var body: some View {\n        ZStack {\n            switch appState.state {\n            case .loading:\n                LaunchView()\n                    .transition(.opacity)\n            case .unauthenticated:\n                AuthenticationFlow()\n                    .transition(.opacity)\n            case .onboarding(let step):\n                OnboardingFlow(step: step)\n                    .transition(.opacity)\n            case .authenticated(let user):\n                MainTabView(user: user)\n                    .transition(.opacity)\n            case .error(let error):\n                ErrorRecoveryView(error: error)\n                    .transition(.opacity)\n            }\n        }\n        .animation(.easeInOut(duration: 0.3), value: appState.state)\n    }\n}\n```\n\n### Minimum Loading Duration\n\nFor a polished experience, ensure the loading screen is visible long enough:\n\n```swift\nextension AppStateController {\n    func initialize() async {\n        let startTime = Date()\n\n        \u002F\u002F Do actual initialization\n        await performInitialization()\n\n        \u002F\u002F Ensure minimum display time for loading screen\n        let elapsed = Date().timeIntervalSince(startTime)\n        let minimumDuration: TimeInterval = 0.5\n        if elapsed \u003C minimumDuration {\n            try? await Task.sleep(for: .seconds(minimumDuration - elapsed))\n        }\n    }\n}\n```\n\n## Coordinator Integration\n\nIf using coordinators, integrate them at the root level:\n\n```swift\nstruct RootView: View {\n    @Environment(AppStateController.self) private var appState\n    @State private var authCoordinator = AuthCoordinator()\n    @State private var mainCoordinator = MainCoordinator()\n\n    var body: some View {\n        Group {\n            switch appState.state {\n            case .loading:\n                LaunchView()\n            case .unauthenticated, .onboarding:\n                AuthenticationFlow()\n                    .environment(authCoordinator)\n            case .authenticated(let user):\n                MainTabView(user: user)\n                    .environment(mainCoordinator)\n            case .error(let error):\n                ErrorRecoveryView(error: error)\n            }\n        }\n        .animation(.easeInOut(duration: 0.3), value: appState.state)\n    }\n}\n```\n\n---\n\n# Part 3: Scene Lifecycle Integration\n\n## Core Principle\n\n> \"Scene lifecycle events are app-wide concerns handled centrally, not scattered across features.\"\n\n## Understanding ScenePhase (Apple Documentation)\n\nScenePhase indicates a scene's operational state. How you interpret the value depends on **where it's read**.\n\n**Read from a View** → Returns the phase of the enclosing scene\n**Read from App** → Returns an **aggregate** value reflecting all scenes\n\n| Phase | Description |\n|-------|-------------|\n| `.active` | Scene is in the foreground and interactive |\n| `.inactive` | Scene is in the foreground but should pause work |\n| `.background` | Scene isn't visible; app may terminate soon |\n\n**Critical insight from Apple docs** When reading at the App level, `.active` means *any* scene is active, and `.background` means *all* scenes are in background.\n\n## scenePhase Handling\n\n```swift\n@main\nstruct MyApp: App {\n    @State private var appState = AppStateController()\n    @Environment(\\.scenePhase) private var scenePhase\n\n    var body: some Scene {\n        WindowGroup {\n            RootView()\n                .environment(appState)\n                .task {\n                    await appState.initialize()\n                }\n        }\n        .onChange(of: scenePhase) { oldPhase, newPhase in\n            handleScenePhaseChange(from: oldPhase, to: newPhase)\n        }\n    }\n\n    private func handleScenePhaseChange(from: ScenePhase, to: ScenePhase) {\n        switch to {\n        case .active:\n            \u002F\u002F App became active — validate session, refresh data\n            Task {\n                await appState.validateSession()\n                await appState.refreshIfNeeded()\n            }\n\n        case .inactive:\n            \u002F\u002F App about to go inactive — save state\n            appState.prepareForBackground()\n\n        case .background:\n            \u002F\u002F App in background — release resources\n            appState.releaseResources()\n\n        @unknown default:\n            break\n        }\n    }\n}\n```\n\n## Session Validation on Active\n\n```swift\nextension AppStateController {\n    func validateSession() async {\n        guard case .authenticated(let user) = state else { return }\n\n        do {\n            \u002F\u002F Check if token is still valid\n            let isValid = try await AuthService.validateToken(user.token)\n            if !isValid {\n                transition(to: .error(.sessionExpired))\n            }\n        } catch {\n            \u002F\u002F Network error — keep authenticated but show warning\n            \u002F\u002F Don't immediately log out on transient network issues\n        }\n    }\n\n    func prepareForBackground() {\n        \u002F\u002F Save any pending data\n        \u002F\u002F Cancel non-essential network requests\n        \u002F\u002F Prepare for potential termination\n    }\n\n    func releaseResources() {\n        \u002F\u002F Release cached images\n        \u002F\u002F Stop location updates if not essential\n        \u002F\u002F Reduce memory footprint\n    }\n}\n```\n\n## SceneStorage for State Restoration\n\nFrom Apple documentation: SceneStorage provides automatic state restoration. The system manages saving and restoring on your behalf.\n\n**Key constraints**\n- Keep data lightweight (not full models)\n- Each Scene has its own storage (not shared)\n- Data destroyed when scene is explicitly destroyed\n\n```swift\nstruct MainTabView: View {\n    @SceneStorage(\"selectedTab\") private var selectedTab = 0\n    @SceneStorage(\"lastViewedItemID\") private var lastViewedItemID: String?\n\n    var body: some View {\n        TabView(selection: $selectedTab) {\n            HomeTab()\n                .tag(0)\n            SearchTab()\n                .tag(1)\n            ProfileTab()\n                .tag(2)\n        }\n        .onAppear {\n            if let itemID = lastViewedItemID {\n                \u002F\u002F Restore to last viewed item\n                navigateToItem(itemID)\n            }\n        }\n    }\n}\n```\n\n### Navigation State Restoration (WWDC 2022\u002F10054)\n\nFor complex navigation, use a Codable NavigationModel:\n\n```swift\n\u002F\u002F Encapsulate navigation state with Codable conformance\nclass NavigationModel: ObservableObject, Codable {\n    @Published var selectedCategory: Category?\n    @Published var recipePath: [Recipe] = []\n\n    enum CodingKeys: String, CodingKey {\n        case selectedCategory\n        case recipePathIds\n    }\n\n    func encode(to encoder: Encoder) throws {\n        var container = encoder.container(keyedBy: CodingKeys.self)\n        try container.encodeIfPresent(selectedCategory, forKey: .selectedCategory)\n        \u002F\u002F Store only IDs, not full models\n        try container.encode(recipePath.map(\\.id), forKey: .recipePathIds)\n    }\n\n    required init(from decoder: Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        self.selectedCategory = try container.decodeIfPresent(\n            Category.self, forKey: .selectedCategory)\n\n        let recipePathIds = try container.decode([Recipe.ID].self, forKey: .recipePathIds)\n        \u002F\u002F compactMap discards deleted items gracefully\n        self.recipePath = recipePathIds.compactMap { DataModel.shared[$0] }\n    }\n\n    var jsonData: Data? {\n        get { try? JSONEncoder().encode(self) }\n        set {\n            guard let data = newValue,\n                  let model = try? JSONDecoder().decode(NavigationModel.self, from: data)\n            else { return }\n            self.selectedCategory = model.selectedCategory\n            self.recipePath = model.recipePath\n        }\n    }\n}\n\n\u002F\u002F Use with SceneStorage\nstruct ContentView: View {\n    @StateObject private var navModel = NavigationModel()\n    @SceneStorage(\"navigation\") private var data: Data?\n\n    var body: some View {\n        NavigationSplitView { \u002F* ... *\u002F }\n        .task {\n            if let data = data {\n                navModel.jsonData = data\n            }\n            for await _ in navModel.objectWillChangeSequence {\n                data = navModel.jsonData\n            }\n        }\n    }\n}\n```\n\n**Key patterns from WWDC**\n- Store **IDs only**, not full model objects\n- Use `compactMap` to handle deleted items gracefully\n- Save on every `objectWillChange` for real-time persistence\n\n### Validating Restored State\n\nNever trust restored state blindly:\n\n```swift\nstruct DetailView: View {\n    @SceneStorage(\"detailItemID\") private var restoredItemID: String?\n    @State private var item: Item?\n\n    var body: some View {\n        Group {\n            if let item {\n                ItemContent(item: item)\n            } else {\n                ProgressView()\n            }\n        }\n        .task {\n            if let itemID = restoredItemID {\n                \u002F\u002F Validate item still exists\n                item = await ItemService.fetch(itemID)\n                if item == nil {\n                    \u002F\u002F Item was deleted — clear restoration\n                    restoredItemID = nil\n                }\n            }\n        }\n    }\n}\n```\n\n## Multi-Window Coordination (iPad, axiom-visionOS)\n\nFrom Apple documentation: Every window in a WindowGroup maintains **independent state**. The system allocates new storage for @State and @StateObject for each window.\n\n```swift\n@main\nstruct MyApp: App {\n    @State private var appState = AppStateController()\n\n    var body: some Scene {\n        \u002F\u002F Primary window\n        WindowGroup {\n            MainView()\n                .environment(appState)\n        }\n\n        \u002F\u002F Data-presenting window (iPad)\n        \u002F\u002F Prefer lightweight data (IDs, not full models)\n        WindowGroup(\"Detail\", id: \"detail\", for: Item.ID.self) { $itemID in\n            if let itemID {\n                DetailView(itemID: itemID)\n                    .environment(appState)\n            }\n        }\n\n        #if os(visionOS)\n        \u002F\u002F Immersive space\n        ImmersiveSpace(id: \"immersive\") {\n            ImmersiveView()\n                .environment(appState)\n        }\n        #endif\n    }\n}\n```\n\n**Key behaviors from Apple docs**\n- If a window with the same value already exists, the system **brings it to front** instead of opening a new one\n- SwiftUI persists the binding value for state restoration\n- Use unique identifier strings for each window group\n\n### Opening Additional Windows\n\n```swift\nstruct ItemRow: View {\n    let item: Item\n    @Environment(\\.openWindow) private var openWindow\n\n    var body: some View {\n        Button(item.title) {\n            \u002F\u002F Open in new window on iPad\n            \u002F\u002F Use ID to match window group, value to pass data\n            openWindow(id: \"detail\", value: item.id)\n        }\n    }\n}\n```\n\n### Dismissing Windows Programmatically\n\n```swift\nstruct DetailView: View {\n    var itemID: Item.ID?\n    @Environment(\\.dismiss) private var dismiss\n\n    var body: some View {\n        VStack {\n            \u002F\u002F ...\n            Button(\"Done\") {\n                dismiss()  \u002F\u002F Closes this window\n            }\n        }\n    }\n}\n```\n\n---\n\n# Part 4: Feature Module Basics\n\n## Core Principle\n\n> \"Split into modules when features have clear boundaries. Not before.\"\n\nPremature modularization creates overhead. Late modularization creates pain. Use this decision tree.\n\n## When to Modularize Decision Tree\n\n```\nShould I extract this feature into a module?\n│\n├─ Is the codebase under 5,000 lines with 1-2 developers?\n│  └─ NO modularization needed yet\n│     Single target is fine, revisit at 10,000 lines\n│\n├─ Is the codebase 5,000-20,000 lines with 3+ developers?\n│  └─ CONSIDER modularization\n│     Look for natural boundaries\n│\n├─ Is the codebase over 20,000 lines?\n│  └─ MODULARIZE for build times\n│     Parallel compilation essential\n│\n├─ Could this feature be used in multiple apps?\n│  └─ EXTRACT to reusable module\n│     Shared authentication, analytics, axiom-networking\n│\n├─ Do multiple developers work on this feature daily?\n│  └─ EXTRACT for merge conflict reduction\n│     Isolated codebases = parallel work\n│\n└─ Does the feature have clear input\u002Foutput boundaries?\n   ├─ YES → Good candidate for module\n   └─ NO → Refactor boundaries first, then extract\n```\n\n## Module Boundary Pattern\n\n### Define a Public API\n\n```swift\n\u002F\u002F FeatureModule\u002FSources\u002FFeatureModule\u002FFeatureAPI.swift\n\n\u002F\u002F\u002F Public interface for the feature module\npublic protocol FeatureAPI {\n    \u002F\u002F\u002F Show the feature's main view\n    @MainActor\n    func makeMainView() -> AnyView\n\n    \u002F\u002F\u002F Handle deep link into feature\n    @MainActor\n    func handleDeepLink(_ url: URL) -> Bool\n}\n\n\u002F\u002F\u002F Factory to create feature with dependencies\npublic struct FeatureFactory {\n    public static func create(\n        analytics: AnalyticsProtocol,\n        networking: NetworkingProtocol\n    ) -> FeatureAPI {\n        FeatureImplementation(\n            analytics: analytics,\n            networking: axiom-networking\n        )\n    }\n}\n```\n\n### Internal Implementation\n\n```swift\n\u002F\u002F FeatureModule\u002FSources\u002FFeatureModule\u002FInternal\u002FFeatureImplementation.swift\n\ninternal class FeatureImplementation: FeatureAPI {\n    private let analytics: AnalyticsProtocol\n    private let networking: NetworkingProtocol\n\n    internal init(\n        analytics: AnalyticsProtocol,\n        networking: NetworkingProtocol\n    ) {\n        self.analytics = analytics\n        self.networking = networking\n    }\n\n    @MainActor\n    public func makeMainView() -> AnyView {\n        AnyView(FeatureMainView(viewModel: makeViewModel()))\n    }\n\n    public func handleDeepLink(_ url: URL) -> Bool {\n        \u002F\u002F Handle feature-specific deep links\n        return false\n    }\n\n    private func makeViewModel() -> FeatureViewModel {\n        FeatureViewModel(analytics: analytics, axiom-networking: axiom-networking)\n    }\n}\n```\n\n### Use in Main App\n\n```swift\n\u002F\u002F MainApp\u002FSources\u002FApp\u002FAppDependencies.swift\n\n@Observable\nclass AppDependencies {\n    let analytics: AnalyticsProtocol\n    let networking: NetworkingProtocol\n\n    \u002F\u002F Lazy-created feature modules\n    lazy var profileFeature: FeatureAPI = {\n        ProfileFeatureFactory.create(\n            analytics: analytics,\n            networking: axiom-networking\n        )\n    }()\n\n    lazy var settingsFeature: FeatureAPI = {\n        SettingsFeatureFactory.create(\n            analytics: analytics,\n            networking: axiom-networking\n        )\n    }()\n}\n\n\u002F\u002F MainApp\u002FSources\u002FApp\u002FMainTabView.swift\n\nstruct MainTabView: View {\n    @Environment(AppDependencies.self) private var dependencies\n\n    var body: some View {\n        TabView {\n            dependencies.profileFeature.makeMainView()\n                .tabItem { Label(\"Profile\", systemImage: \"person\") }\n\n            dependencies.settingsFeature.makeMainView()\n                .tabItem { Label(\"Settings\", systemImage: \"gear\") }\n        }\n    }\n}\n```\n\n## Navigation Coordination Between Modules\n\nFeatures should not know about each other directly:\n\n```swift\n\u002F\u002F ❌ Feature knows about other features\nstruct ProfileView: View {\n    func showSettings() {\n        \u002F\u002F ProfileView imports SettingsFeature — circular dependency risk\n        NavigationLink(value: SettingsDestination())\n    }\n}\n\n\u002F\u002F ✅ Feature delegates navigation to coordinator\nstruct ProfileView: View {\n    let onShowSettings: () -> Void\n\n    func showSettings() {\n        onShowSettings()  \u002F\u002F ProfileView doesn't know what happens\n    }\n}\n\n\u002F\u002F Coordinator wires features together\nclass MainCoordinator {\n    func showSettings(from profile: ProfileFeatureAPI) {\n        \u002F\u002F Coordinator knows about both features\n        navigationPath.append(SettingsRoute())\n    }\n}\n```\n\n## Module Folder Structure\n\n```\nMyApp\u002F\n├── App\u002F                          # Main app target\n│   ├── MyApp.swift               # @main entry point\n│   ├── AppDependencies.swift     # Dependency container\n│   ├── AppStateController.swift  # App state machine\n│   └── Coordinators\u002F             # Navigation coordinators\n│\n├── Packages\u002F\n│   ├── Core\u002F                     # Shared utilities\n│   │   ├── Networking\u002F\n│   │   ├── Analytics\u002F\n│   │   └── Design\u002F               # Design system\n│   │\n│   ├── Features\u002F                 # Feature modules\n│   │   ├── Profile\u002F\n│   │   ├── Settings\u002F\n│   │   └── Onboarding\u002F\n│   │\n│   └── Domain\u002F                   # Business logic\n│       ├── Models\u002F\n│       └── Services\u002F\n```\n\n---\n\n# Part 5: Anti-Patterns\n\n## Anti-Pattern 1: Boolean-Based State\n\n```swift\n\u002F\u002F ❌ Boolean soup — impossible to validate\nclass AppState {\n    var isLoading = true\n    var isLoggedIn = false\n    var hasCompletedOnboarding = false\n    var hasError = false\n}\n\n\u002F\u002F What if isLoading && isLoggedIn && hasError are all true?\n```\n\n**Fix** Use enum-based state (Part 1)\n\n```swift\n\u002F\u002F ✅ Explicit states — compiler prevents invalid combinations\nenum AppState {\n    case loading\n    case unauthenticated\n    case onboarding(OnboardingStep)\n    case authenticated(User)\n    case error(AppError)\n}\n```\n\n## Anti-Pattern 2: Logic in @main\n\n```swift\n\u002F\u002F ❌ Business logic in App entry point\n@main\nstruct MyApp: App {\n    @State private var user: User?\n    @State private var isLoading = true\n\n    var body: some Scene {\n        WindowGroup {\n            if isLoading {\n                LoadingView()\n            } else if let user {\n                MainView(user: user)\n            } else {\n                LoginView(onLogin: { self.user = $0 })\n            }\n        }\n        .task {\n            user = await AuthService.getCurrentUser()\n            isLoading = false\n        }\n    }\n}\n```\n\n**Problems**\n- @main becomes bloated with logic\n- Hard to test without launching app\n- State scattered across multiple @State\n\n**Fix** Delegate to AppStateController (Part 2)\n\n```swift\n\u002F\u002F ✅ @main is a thin shell\n@main\nstruct MyApp: App {\n    @State private var appState = AppStateController()\n\n    var body: some Scene {\n        WindowGroup {\n            RootView()\n                .environment(appState)\n                .task { await appState.initialize() }\n        }\n    }\n}\n```\n\n## Anti-Pattern 3: Missing State Validation on Restore\n\n```swift\n\u002F\u002F ❌ Trusts restored state blindly\n.onAppear {\n    if let savedState = SceneStorage.appState {\n        appState.state = savedState  \u002F\u002F Token might be expired!\n    }\n}\n```\n\n**Problems**\n- Session could have expired\n- User could have been logged out on another device\n- Data could have been deleted\n\n**Fix** Validate before applying (Part 3)\n\n```swift\n\u002F\u002F ✅ Validates restored state\n.task {\n    if let savedSession = await SessionStorage.loadSession() {\n        do {\n            let user = try await AuthService.validateSession(savedSession)\n            appState.transition(to: .authenticated(user))\n        } catch {\n            \u002F\u002F Session invalid — force re-login\n            await SessionStorage.clearSession()\n            appState.transition(to: .unauthenticated)\n        }\n    }\n}\n```\n\n## Anti-Pattern 4: Navigation Logic Scattered Across Features\n\n```swift\n\u002F\u002F ❌ Every feature knows about every other feature\nstruct ProfileView: View {\n    @Environment(\\.navigationPath) private var path\n\n    func showSettings() {\n        path.append(SettingsDestination())  \u002F\u002F ProfileView imports Settings\n    }\n\n    func showOrderHistory() {\n        path.append(OrderHistoryDestination())  \u002F\u002F ProfileView imports Orders\n    }\n}\n```\n\n**Problems**\n- Circular dependencies\n- Hard to test navigation\n- Changes ripple across modules\n\n**Fix** Delegate to coordinator (Part 4)\n\n```swift\n\u002F\u002F ✅ Feature delegates navigation decisions\nstruct ProfileView: View {\n    let onShowSettings: () -> Void\n    let onShowOrderHistory: () -> Void\n\n    \u002F\u002F ProfileView doesn't know what these do\n}\n```\n\n## Anti-Pattern 5: God Coordinator\n\n```swift\n\u002F\u002F ❌ Single coordinator knows all features\nclass AppCoordinator {\n    func showProfile() { }\n    func showSettings() { }\n    func showOnboarding() { }\n    func showPayment() { }\n    func showChat() { }\n    func showOrderHistory() { }\n    func showNotifications() { }\n    \u002F\u002F ... 50 more methods\n}\n```\n\n**Problems**\n- Massive file that everyone touches\n- Merge conflicts\n- Single point of failure\n\n**Fix** Scoped coordinators\n\n```swift\n\u002F\u002F ✅ Scoped coordinators for each domain\nclass AuthCoordinator { }      \u002F\u002F Login, signup, forgot password\nclass MainCoordinator { }      \u002F\u002F Tab navigation, main flows\nclass SettingsCoordinator { }  \u002F\u002F Settings navigation tree\nclass OrderCoordinator { }     \u002F\u002F Order flow, history, details\n```\n\n---\n\n# Part 5b: UIKit Integration (Incremental Adoption)\n\n> For comprehensive bridging patterns (UIViewRepresentable, UIViewControllerRepresentable, UIHostingConfiguration, coordinators, lifecycle, gotchas), see `\u002Fskill axiom-uikit-bridging`. This section covers app-level integration strategy only.\n\n## When This Applies\n\nMost production iOS apps have existing UIKit code. Rewriting everything in SwiftUI is rarely practical. Use these patterns for incremental adoption.\n\n## UIHostingController — SwiftUI Inside UIKit\n\nEmbed SwiftUI views in an existing UIKit navigation hierarchy:\n\n```swift\n\u002F\u002F Present a SwiftUI view from a UIKit view controller\nlet settingsView = SettingsView(store: store)\nlet hostingController = UIHostingController(rootView: settingsView)\nnavigationController?.pushViewController(hostingController, animated: true)\n```\n\n**Key rules**:\n- `UIHostingController` owns the SwiftUI view's lifecycle — don't store the root view separately\n- Use `sizingOptions: .intrinsicContentSize` when embedding as a child for correct Auto Layout sizing\n- For sheets: `hostingController.modalPresentationStyle = .pageSheet` works naturally\n- SwiftUI environment doesn't bridge automatically — inject dependencies through the root view's initializer\n\n## UIViewControllerRepresentable — UIKit Inside SwiftUI\n\nWrap existing UIKit view controllers for use in SwiftUI:\n\n```swift\nstruct DocumentPickerView: UIViewControllerRepresentable {\n    @Binding var selectedURL: URL?\n\n    func makeUIViewController(context: Context) -> UIDocumentPickerViewController {\n        let picker = UIDocumentPickerViewController(forOpeningContentTypes: [.pdf])\n        picker.delegate = context.coordinator\n        return picker\n    }\n\n    func updateUIViewController(_ uiViewController: UIDocumentPickerViewController, context: Context) { }\n\n    func makeCoordinator() -> Coordinator { Coordinator(self) }\n\n    class Coordinator: NSObject, UIDocumentPickerDelegate {\n        let parent: DocumentPickerView\n        init(_ parent: DocumentPickerView) { self.parent = parent }\n\n        func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {\n            parent.selectedURL = urls.first\n        }\n    }\n}\n```\n\n**When to use**: Camera UI, document pickers, mail compose, any UIKit controller without a SwiftUI equivalent.\n\n## AppDelegate + SwiftUI @main\n\nBridge `UIApplicationDelegate` callbacks into a SwiftUI app:\n\n```swift\n@main\nstruct MyApp: App {\n    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate\n\n    var body: some Scene {\n        WindowGroup {\n            RootView()\n        }\n    }\n}\n\nclass AppDelegate: NSObject, UIApplicationDelegate {\n    func application(_ application: UIApplication,\n                     didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {\n        \u002F\u002F Push notification registration, third-party SDK init, etc.\n        return true\n    }\n\n    func application(_ application: UIApplication,\n                     didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {\n        \u002F\u002F Forward to push notification service\n    }\n}\n```\n\n**When to use**: Push notifications, third-party SDKs requiring AppDelegate, background URL sessions, Handoff.\n\n## Migration Priority\n\nWhen incrementally adopting SwiftUI in a UIKit app:\n\n1. **Leaf screens first** — Settings, About, detail views (no navigation complexity)\n2. **New features in SwiftUI** — Don't rewrite, but build new screens in SwiftUI\n3. **Shared components** — Build reusable SwiftUI components, wrap in `UIHostingController`\n4. **Navigation last** — Don't mix `UINavigationController` with `NavigationStack` in the same flow; migrate entire navigation subtrees\n\n**Don't**: Replace `UINavigationController` with `NavigationStack` for half the app. Either a flow is fully SwiftUI navigation or fully UIKit navigation.\n\n---\n\n# Part 6: Pressure Scenarios\n\n## Scenario 1: \"Just hardcode the root for now\"\n\n### The Pressure\n\n> \"We only have one flow right now. Just show MainView directly, we'll add auth later.\"\n\n### Red Flags\n- \"We'll add X later\" → Tech debt that compounds\n- \"It's just one flow\" → Flows multiply\n- \"Keep it simple\" → Simplicity now, complexity later\n\n### Time Cost Comparison\n\n| Option | Initial | When Adding Auth | Total |\n|--------|---------|------------------|-------|\n| Hardcode MainView | 0 min | 2-4 hours refactor | 2-4 hours |\n| AppStateController | 30 min | 30 min add state | 1 hour |\n\n### Push-Back Script\n\n> \"The AppStateController pattern takes 30 minutes now. When we add auth later — and we will — it'll take another 30 minutes to add the state. Hardcoding now saves 0 minutes because we'll spend 2-4 hours refactoring when we need auth. Let's invest 30 minutes now.\"\n\n### What to Do\n\n1. Create minimal AppStateController with two states:\n   ```swift\n   enum AppState {\n       case loading\n       case ready\n   }\n   ```\n\n2. When auth is needed, add states:\n   ```swift\n   enum AppState {\n       case loading\n       case unauthenticated  \u002F\u002F Added\n       case authenticated(User)  \u002F\u002F Added\n   }\n   ```\n\n3. Total effort: 1 hour instead of 4 hours\n\n---\n\n## Scenario 2: \"We don't need modules yet\"\n\n### The Pressure\n\n> \"Let's keep everything in one target. Modules are over-engineering.\"\n\n### Decision Framework\n\n| Codebase | Team | Recommendation |\n|----------|------|----------------|\n| \u003C 5,000 lines | 1-2 devs | Single target is fine |\n| 5,000-20,000 lines | 3+ devs | Consider modules |\n| > 20,000 lines | Any | Modules essential |\n\n### Push-Back Script\n\n> \"I agree modules add overhead. Let's use this decision tree: We have [X] lines and [Y] developers. Based on that, we [should\u002Fshouldn't] modularize yet. If we hit [threshold], we'll revisit. Sound good?\"\n\n### What to Do\n\n1. Check codebase size: `find . -name \"*.swift\" | xargs wc -l`\n2. If under threshold, document decision and threshold for revisit\n3. If over threshold, identify natural boundaries first\n\n---\n\n## Scenario 3: \"Navigation is too complex to test\"\n\n### The Pressure\n\n> \"Testing navigation state is too hard. Let's just do manual QA.\"\n\n### Why This Fails\n\n- Navigation bugs are #1 \"works on my machine\" cause\n- Deep linking requires automated verification\n- State restoration needs regression testing\n- Manual QA misses edge cases\n\n### Solution: Test the State Machine\n\n```swift\n\u002F\u002F ✅ Test navigation state without UI\n@Test func testLoginCompletesOnboarding() async {\n    let controller = AppStateController()\n    controller.transition(to: .unauthenticated)\n\n    \u002F\u002F Simulate login\n    await controller.handleLogin(user: mockUser)\n\n    \u002F\u002F First-time user goes to onboarding\n    #expect(controller.state == .onboarding(.welcome))\n}\n\n@Test func testDeepLinkWhileUnauthenticated() async {\n    let controller = AppStateController()\n    controller.transition(to: .unauthenticated)\n\n    \u002F\u002F Deep link to order\n    let handled = controller.handleDeepLink(URL(string: \"app:\u002F\u002Forder\u002F123\")!)\n\n    \u002F\u002F Should not navigate — requires auth\n    #expect(handled == false)\n    #expect(controller.state == .unauthenticated)\n}\n```\n\n### Push-Back Script\n\n> \"Navigation is complex, which is exactly why we need automated tests. The AppStateController pattern lets us test state transitions without launching the UI. We can verify deep linking, auth flows, and restoration in seconds. Manual QA can't catch all the combinations.\"\n\n---\n\n# Part 7: Code Review Checklist\n\n## App State\n\n- [ ] App state is an enum, not booleans\n- [ ] All valid states are explicitly defined\n- [ ] State transitions are validated in `isValidTransition`\n- [ ] Invalid transitions are caught (assertion in debug, logged in prod)\n- [ ] State changes are logged for debugging\n\n## Root View\n\n- [ ] @main delegates to AppStateController\n- [ ] No business logic in @main\n- [ ] RootView switches on single source of truth\n- [ ] Transitions are animated (no flicker)\n- [ ] Loading state has minimum display duration\n\n## Scene Lifecycle\n\n- [ ] scenePhase changes handled in one place\n- [ ] Session validated on .active (not blindly trusted)\n- [ ] Resources released on .background\n- [ ] SceneStorage used for tab selection \u002F navigation state\n- [ ] Restored state validated before applying\n\n## Module Boundaries\n\n- [ ] Features have public API protocols\n- [ ] No circular dependencies between modules\n- [ ] Navigation delegates to coordinators\n- [ ] Dependencies injected, not singletons\n- [ ] Module decision documented (why split \u002F not split)\n\n## Testing\n\n- [ ] State transitions tested without UI\n- [ ] Invalid transitions tested\n- [ ] Deep link handling tested\n- [ ] Restoration validation tested\n\n---\n\n## Resources\n\n**WWDC**: 2025-266, 2024-10150, 2023-10149, 2025-256, 2022-10054\n\n**Docs**: \u002Fswiftui\u002Fscenephase, \u002Fswiftui\u002Fscene, \u002Fswiftui\u002Fscenestorage, \u002Fswiftui\u002Fwindowgroup, \u002Fobservation\u002Fobservable()\n\n**Skills**: axiom-swiftui-architecture, axiom-swiftui-nav, axiom-swift-concurrency\n\n","# App Composition\n\n## When to Use This Skill\n\nUse this skill when:\n- Structuring your @main entry point and root view\n- Managing authentication state (login → onboarding → main)\n- Switching between app-level states without flicker\n- Handling scene lifecycle events (scenePhase)\n- Restoring app state after termination\n- Deciding when to split into feature modules\n- Coordinating between multiple windows (iPad, axiom-visionOS)\n\n## Example Prompts\n\n| What You Might Ask | Why This Skill Helps |\n|--------------------|----------------------|\n| \"How do I switch between login and main screens?\" | AppStateController pattern with validated transitions |\n| \"My app flickers when switching from splash to main\" | Flicker prevention with animation coordination |\n| \"Where should auth state live?\" | App-level state machine, not scattered booleans |\n| \"How do I handle app going to background?\" | scenePhase lifecycle patterns |\n| \"When should I split my app into modules?\" | Decision tree based on codebase size and team |\n| \"How do I restore state after app is killed?\" | SceneStorage and state validation patterns |\n\n## Quick Decision Tree\n\n```\nWhat app-level architecture question are you solving?\n│\n├─ How do I manage app states (loading, auth, main)?\n│  └─ Part 1: App-Level State Machines\n│     - Enum-based state with validated transitions\n│     - AppStateController pattern\n│     - Prevents \"boolean soup\" anti-pattern\n│\n├─ How do I structure @main and root view switching?\n│  └─ Part 2: Root View Switching Patterns\n│     - Delegate to AppStateController (no logic in @main)\n│     - Flicker prevention with animation\n│     - Coordinator integration\n│\n├─ How do I handle scene lifecycle?\n│  └─ Part 3: Scene Lifecycle Integration\n│     - scenePhase for session validation\n│     - SceneStorage for restoration\n│     - Multi-window coordination\n│\n├─ When should I modularize?\n│  └─ Part 4: Feature Module Basics\n│     - Decision tree by size\u002Fteam\n│     - Module boundaries and DI\n│     - Navigation coordination\n│\n└─ What mistakes should I avoid?\n   └─ Part 5: Anti-Patterns + Part 6: Pressure Scenarios\n      - Boolean-based state\n      - Logic in @main\n      - Missing restoration validation\n```\n\n---\n\n# Part 1: App-Level State Machines\n\n## Core Principle\n\n> \"Apps have discrete states. Model them explicitly with enums, not scattered booleans.\"\n\nEvery non-trivial app has distinct states: loading, unauthenticated, onboarding, authenticated, error recovery. These states should be:\n1. **Explicit** — An enum, not multiple booleans\n2. **Validated** — Transitions are checked and logged\n3. **Centralized** — One source of truth\n4. **Observable** — Views react to state changes\n\n## The Boolean Soup Problem\n\n```swift\n\u002F\u002F ❌ Boolean soup — impossible to validate, prone to invalid states\nclass AppState {\n    var isLoading = true\n    var isLoggedIn = false\n    var hasCompletedOnboarding = false\n    var hasError = false\n    var user: User?\n\n    \u002F\u002F What if isLoading && isLoggedIn && hasError are all true?\n    \u002F\u002F Invalid state, but nothing prevents it\n}\n```\n\n**Problems**\n- No compile-time guarantee of valid states\n- Easy to forget to update one boolean\n- Testing requires checking all combinations\n- Race conditions create impossible states\n\n## The AppStateController Pattern\n\n### Step 1: Define Explicit States\n\n```swift\nenum AppState: Equatable {\n    case loading\n    case unauthenticated\n    case onboarding(OnboardingStep)\n    case authenticated(User)\n    case error(AppError)\n}\n\nenum OnboardingStep: Equatable {\n    case welcome\n    case permissions\n    case profileSetup\n    case complete\n}\n\nenum AppError: Equatable {\n    case networkUnavailable\n    case sessionExpired\n    case maintenanceMode\n}\n```\n\n### Step 2: Create the Controller\n\n```swift\n@Observable\n@MainActor\nclass AppStateController {\n    private(set) var state: AppState = .loading\n\n    \u002F\u002F MARK: - State Transitions\n\n    func transition(to newState: AppState) {\n        guard isValidTransition(from: state, to: newState) else {\n            assertionFailure(\"Invalid transition: \\(state) → \\(newState)\")\n            logInvalidTransition(from: state, to: newState)\n            return\n        }\n\n        let oldState = state\n        state = newState\n        logTransition(from: oldState, to: newState)\n    }\n\n    \u002F\u002F MARK: - Validation\n\n    private func isValidTransition(from: AppState, to: AppState) -> Bool {\n        switch (from, to) {\n        \u002F\u002F From loading\n        case (.loading, .unauthenticated): return true\n        case (.loading, .authenticated): return true\n        case (.loading, .error): return true\n\n        \u002F\u002F From unauthenticated\n        case (.unauthenticated, .onboarding): return true\n        case (.unauthenticated, .authenticated): return true\n        case (.unauthenticated, .error): return true\n\n        \u002F\u002F From onboarding\n        case (.onboarding, .onboarding): return true  \u002F\u002F Step changes\n        case (.onboarding, .authenticated): return true\n        case (.onboarding, .unauthenticated): return true  \u002F\u002F Cancelled\n\n        \u002F\u002F From authenticated\n        case (.authenticated, .unauthenticated): return true  \u002F\u002F Logout\n        case (.authenticated, .error): return true\n\n        \u002F\u002F From error\n        case (.error, .loading): return true  \u002F\u002F Retry\n        case (.error, .unauthenticated): return true\n\n        default: return false\n        }\n    }\n\n    \u002F\u002F MARK: - Logging\n\n    private func logTransition(from: AppState, to: AppState) {\n        #if DEBUG\n        print(\"AppState: \\(from) → \\(to)\")\n        #endif\n    }\n\n    private func logInvalidTransition(from: AppState, to: AppState) {\n        \u002F\u002F Log to analytics for debugging\n        Analytics.log(\"InvalidStateTransition\", properties: [\n            \"from\": String(describing: from),\n            \"to\": String(describing: to)\n        ])\n    }\n}\n```\n\n### Step 3: Initialize from Storage\n\n```swift\nextension AppStateController {\n    func initialize() async {\n        \u002F\u002F Check for stored session\n        if let session = await SessionStorage.loadSession() {\n            \u002F\u002F Validate session is still valid\n            do {\n                let user = try await AuthService.validateSession(session)\n                transition(to: .authenticated(user))\n            } catch {\n                \u002F\u002F Session expired or invalid\n                await SessionStorage.clearSession()\n                transition(to: .unauthenticated)\n            }\n        } else {\n            transition(to: .unauthenticated)\n        }\n    }\n}\n```\n\n## State Machine Diagram\n\n```\n┌─────────────────────────────────────────────────────────────┐\n│                        .loading                              │\n└────────────┬───────────────┬────────────────┬───────────────┘\n             │               │                │\n             ▼               ▼                ▼\n    .unauthenticated   .authenticated     .error\n             │               │                │\n             ▼               │                │\n       .onboarding ─────────►│◄───────────────┘\n             │               │\n             └───────────────┘\n```\n\n## Testing State Machines\n\n```swift\n@Test func testValidTransitions() async {\n    let controller = AppStateController()\n\n    \u002F\u002F Loading → Unauthenticated (valid)\n    controller.transition(to: .unauthenticated)\n    #expect(controller.state == .unauthenticated)\n\n    \u002F\u002F Unauthenticated → Authenticated (valid)\n    let user = User(id: \"1\", name: \"Test\")\n    controller.transition(to: .authenticated(user))\n    #expect(controller.state == .authenticated(user))\n}\n\n@Test func testInvalidTransitionRejected() async {\n    let controller = AppStateController()\n\n    \u002F\u002F Loading → Onboarding (invalid — must go through unauthenticated)\n    controller.transition(to: .onboarding(.welcome))\n    #expect(controller.state == .loading)  \u002F\u002F Unchanged\n}\n\n@Test func testSessionExpiredTransition() async {\n    let controller = AppStateController()\n    let user = User(id: \"1\", name: \"Test\")\n    controller.transition(to: .authenticated(user))\n\n    \u002F\u002F Authenticated → Error (session expired)\n    controller.transition(to: .error(.sessionExpired))\n    #expect(controller.state == .error(.sessionExpired))\n\n    \u002F\u002F Error → Unauthenticated (force re-login)\n    controller.transition(to: .unauthenticated)\n    #expect(controller.state == .unauthenticated)\n}\n```\n\n## The State-as-Bridge Pattern (WWDC 2025\u002F266)\n\nFrom WWDC 2025's \"Explore concurrency in SwiftUI\":\n\n> \"Find the boundaries between UI code that requires time-sensitive changes, and long-running async logic.\"\n\nThe key insight: **synchronous state changes drive UI** (for animations), **async code lives in the model** (testable without SwiftUI), and **state bridges the two**.\n\n```swift\n\u002F\u002F ✅ State-as-Bridge: UI triggers state, model does async work\nstruct ColorExtractorView: View {\n    @State private var model = ColorExtractor()\n\n    var body: some View {\n        Button(\"Extract Colors\") {\n            \u002F\u002F ✅ Synchronous state change triggers animation\n            withAnimation { model.isExtracting = true }\n\n            \u002F\u002F Async work happens in Task\n            Task {\n                await model.extractColors()\n\n                \u002F\u002F ✅ Synchronous state change ends animation\n                withAnimation { model.isExtracting = false }\n            }\n        }\n        .scaleEffect(model.isExtracting ? 1.5 : 1.0)\n    }\n}\n\n@Observable\nclass ColorExtractor {\n    var isExtracting = false\n    var colors: [Color] = []\n\n    func extractColors() async {\n        \u002F\u002F Heavy computation happens here, testable without SwiftUI\n        let extracted = await heavyComputation()\n        colors = extracted\n    }\n}\n```\n\n**Why this matters for app composition**\n- App-level state changes (loading → authenticated) should be **synchronous**\n- Heavy work (session validation, data loading) should be **async in the model**\n- This separation makes state machines testable without SwiftUI imports\n\n---\n\n# Part 2: Root View Switching Patterns\n\n## Core Principle\n\n> \"The @main entry point should be a thin shell. All logic belongs in AppStateController.\"\n\n## The Clean @main Pattern\n\n```swift\n@main\nstruct MyApp: App {\n    @State private var appState = AppStateController()\n\n    var body: some Scene {\n        WindowGroup {\n            RootView()\n                .environment(appState)\n                .task {\n                    await appState.initialize()\n                }\n        }\n    }\n}\n```\n\n**What @main does**\n- Creates AppStateController\n- Injects it via environment\n- Triggers initialization\n\n**What @main does NOT do**\n- Business logic\n- Auth checks\n- Conditional rendering\n- Navigation decisions\n\n## RootView: The State Switch\n\n```swift\nstruct RootView: View {\n    @Environment(AppStateController.self) private var appState\n\n    var body: some View {\n        Group {\n            switch appState.state {\n            case .loading:\n                LaunchView()\n            case .unauthenticated:\n                AuthenticationFlow()\n            case .onboarding(let step):\n                OnboardingFlow(step: step)\n            case .authenticated(let user):\n                MainTabView(user: user)\n            case .error(let error):\n                ErrorRecoveryView(error: error)\n            }\n        }\n    }\n}\n```\n\n## Testability Benefit\n\nThe thin-shell pattern enables up to 60x faster tests. When app logic lives in a Swift Package instead of the app target, tests run with `swift test` (~0.4s) vs `xcodebuild test` (~25s) — no simulator, no app launch.\n\n| Component | Location | Tested With |\n|-----------|----------|-------------|\n| Business logic, models, services | Swift Package (`MyAppCore`) | `swift test` (0.4s) |\n| Root view composition | App target (thin shell) | `xcodebuild test` (25s) |\n\nSee `axiom-swift-testing` Strategy 1 for the complete package extraction walkthrough.\n\n## Preventing Flicker During Transitions\n\n### Problem: Flash of Wrong Content\n\nWhen app state changes, you might see a flash of the old screen before the new one appears. This happens when:\n- State changes before view is ready\n- No transition animation\n- Loading state too short to perceive\n\n### Solution: Animated Transitions\n\n```swift\nstruct RootView: View {\n    @Environment(AppStateController.self) private var appState\n\n    var body: some View {\n        ZStack {\n            switch appState.state {\n            case .loading:\n                LaunchView()\n                    .transition(.opacity)\n            case .unauthenticated:\n                AuthenticationFlow()\n                    .transition(.opacity)\n            case .onboarding(let step):\n                OnboardingFlow(step: step)\n                    .transition(.opacity)\n            case .authenticated(let user):\n                MainTabView(user: user)\n                    .transition(.opacity)\n            case .error(let error):\n                ErrorRecoveryView(error: error)\n                    .transition(.opacity)\n            }\n        }\n        .animation(.easeInOut(duration: 0.3), value: appState.state)\n    }\n}\n```\n\n### Minimum Loading Duration\n\nFor a polished experience, ensure the loading screen is visible long enough:\n\n```swift\nextension AppStateController {\n    func initialize() async {\n        let startTime = Date()\n\n        \u002F\u002F Do actual initialization\n        await performInitialization()\n\n        \u002F\u002F Ensure minimum display time for loading screen\n        let elapsed = Date().timeIntervalSince(startTime)\n        let minimumDuration: TimeInterval = 0.5\n        if elapsed \u003C minimumDuration {\n            try? await Task.sleep(for: .seconds(minimumDuration - elapsed))\n        }\n    }\n}\n```\n\n## Coordinator Integration\n\nIf using coordinators, integrate them at the root level:\n\n```swift\nstruct RootView: View {\n    @Environment(AppStateController.self) private var appState\n    @State private var authCoordinator = AuthCoordinator()\n    @State private var mainCoordinator = MainCoordinator()\n\n    var body: some View {\n        Group {\n            switch appState.state {\n            case .loading:\n                LaunchView()\n            case .unauthenticated, .onboarding:\n                AuthenticationFlow()\n                    .environment(authCoordinator)\n            case .authenticated(let user):\n                MainTabView(user: user)\n                    .environment(mainCoordinator)\n            case .error(let error):\n                ErrorRecoveryView(error: error)\n            }\n        }\n        .animation(.easeInOut(duration: 0.3), value: appState.state)\n    }\n}\n```\n\n---\n\n# Part 3: Scene Lifecycle Integration\n\n## Core Principle\n\n> \"Scene lifecycle events are app-wide concerns handled centrally, not scattered across features.\"\n\n## Understanding ScenePhase (Apple Documentation)\n\nScenePhase indicates a scene's operational state. How you interpret the value depends on **where it's read**.\n\n**Read from a View** → Returns the phase of the enclosing scene\n**Read from App** → Returns an **aggregate** value reflecting all scenes\n\n| Phase | Description |\n|-------|-------------|\n| `.active` | Scene is in the foreground and interactive |\n| `.inactive` | Scene is in the foreground but should pause work |\n| `.background` | Scene isn't visible; app may terminate soon |\n\n**Critical insight from Apple docs** When reading at the App level, `.active` means *any* scene is active, and `.background` means *all* scenes are in background.\n\n## scenePhase Handling\n\n```swift\n@main\nstruct MyApp: App {\n    @State private var appState = AppStateController()\n    @Environment(\\.scenePhase) private var scenePhase\n\n    var body: some Scene {\n        WindowGroup {\n            RootView()\n                .environment(appState)\n                .task {\n                    await appState.initialize()\n                }\n        }\n        .onChange(of: scenePhase) { oldPhase, newPhase in\n            handleScenePhaseChange(from: oldPhase, to: newPhase)\n        }\n    }\n\n    private func handleScenePhaseChange(from: ScenePhase, to: ScenePhase) {\n        switch to {\n        case .active:\n            \u002F\u002F App became active — validate session, refresh data\n            Task {\n                await appState.validateSession()\n                await appState.refreshIfNeeded()\n            }\n\n        case .inactive:\n            \u002F\u002F App about to go inactive — save state\n            appState.prepareForBackground()\n\n        case .background:\n            \u002F\u002F App in background — release resources\n            appState.releaseResources()\n\n        @unknown default:\n            break\n        }\n    }\n}\n```\n\n## Session Validation on Active\n\n```swift\nextension AppStateController {\n    func validateSession() async {\n        guard case .authenticated(let user) = state else { return }\n\n        do {\n            \u002F\u002F Check if token is still valid\n            let isValid = try await AuthService.validateToken(user.token)\n            if !isValid {\n                transition(to: .error(.sessionExpired))\n            }\n        } catch {\n            \u002F\u002F Network error — keep authenticated but show warning\n            \u002F\u002F Don't immediately log out on transient network issues\n        }\n    }\n\n    func prepareForBackground() {\n        \u002F\u002F Save any pending data\n        \u002F\u002F Cancel non-essential network requests\n        \u002F\u002F Prepare for potential termination\n    }\n\n    func releaseResources() {\n        \u002F\u002F Release cached images\n        \u002F\u002F Stop location updates if not essential\n        \u002F\u002F Reduce memory footprint\n    }\n}\n```\n\n## SceneStorage for State Restoration\n\nFrom Apple documentation: SceneStorage provides automatic state restoration. The system manages saving and restoring on your behalf.\n\n**Key constraints**\n- Keep data lightweight (not full models)\n- Each Scene has its own storage (not shared)\n- Data destroyed when scene is explicitly destroyed\n\n```swift\nstruct MainTabView: View {\n    @SceneStorage(\"selectedTab\") private var selectedTab = 0\n    @SceneStorage(\"lastViewedItemID\") private var lastViewedItemID: String?\n\n    var body: some View {\n        TabView(selection: $selectedTab) {\n            HomeTab()\n                .tag(0)\n            SearchTab()\n                .tag(1)\n            ProfileTab()\n                .tag(2)\n        }\n        .onAppear {\n            if let itemID = lastViewedItemID {\n                \u002F\u002F Restore to last viewed item\n                navigateToItem(itemID)\n            }\n        }\n    }\n}\n```\n\n### Navigation State Restoration (WWDC 2022\u002F10054)\n\nFor complex navigation, use a Codable NavigationModel:\n\n```swift\n\u002F\u002F Encapsulate navigation state with Codable conformance\nclass NavigationModel: ObservableObject, Codable {\n    @Published var selectedCategory: Category?\n    @Published var recipePath: [Recipe] = []\n\n    enum CodingKeys: String, CodingKey {\n        case selectedCategory\n        case recipePathIds\n    }\n\n    func encode(to encoder: Encoder) throws {\n        var container = encoder.container(keyedBy: CodingKeys.self)\n        try container.encodeIfPresent(selectedCategory, forKey: .selectedCategory)\n        \u002F\u002F Store only IDs, not full models\n        try container.encode(recipePath.map(\\.id), forKey: .recipePathIds)\n    }\n\n    required init(from decoder: Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        self.selectedCategory = try container.decodeIfPresent(\n            Category.self, forKey: .selectedCategory)\n\n        let recipePathIds = try container.decode([Recipe.ID].self, forKey: .recipePathIds)\n        \u002F\u002F compactMap discards deleted items gracefully\n        self.recipePath = recipePathIds.compactMap { DataModel.shared[$0] }\n    }\n\n    var jsonData: Data? {\n        get { try? JSONEncoder().encode(self) }\n        set {\n            guard let data = newValue,\n                  let model = try? JSONDecoder().decode(NavigationModel.self, from: data)\n            else { return }\n            self.selectedCategory = model.selectedCategory\n            self.recipePath = model.recipePath\n        }\n    }\n}\n\n\u002F\u002F Use with SceneStorage\nstruct ContentView: View {\n    @StateObject private var navModel = NavigationModel()\n    @SceneStorage(\"navigation\") private var data: Data?\n\n    var body: some View {\n        NavigationSplitView { \u002F* ... *\u002F }\n        .task {\n            if let data = data {\n                navModel.jsonData = data\n            }\n            for await _ in navModel.objectWillChangeSequence {\n                data = navModel.jsonData\n            }\n        }\n    }\n}\n```\n\n**Key patterns from WWDC**\n- Store **IDs only**, not full model objects\n- Use `compactMap` to handle deleted items gracefully\n- Save on every `objectWillChange` for real-time persistence\n\n### Validating Restored State\n\nNever trust restored state blindly:\n\n```swift\nstruct DetailView: View {\n    @SceneStorage(\"detailItemID\") private var restoredItemID: String?\n    @State private var item: Item?\n\n    var body: some View {\n        Group {\n            if let item {\n                ItemContent(item: item)\n            } else {\n                ProgressView()\n            }\n        }\n        .task {\n            if let itemID = restoredItemID {\n                \u002F\u002F Validate item still exists\n                item = await ItemService.fetch(itemID)\n                if item == nil {\n                    \u002F\u002F Item was deleted — clear restoration\n                    restoredItemID = nil\n                }\n            }\n        }\n    }\n}\n```\n\n## Multi-Window Coordination (iPad, axiom-visionOS)\n\nFrom Apple documentation: Every window in a WindowGroup maintains **independent state**. The system allocates new storage for @State and @StateObject for each window.\n\n```swift\n@main\nstruct MyApp: App {\n    @State private var appState = AppStateController()\n\n    var body: some Scene {\n        \u002F\u002F Primary window\n        WindowGroup {\n            MainView()\n                .environment(appState)\n        }\n\n        \u002F\u002F Data-presenting window (iPad)\n        \u002F\u002F Prefer lightweight data (IDs, not full models)\n        WindowGroup(\"Detail\", id: \"detail\", for: Item.ID.self) { $itemID in\n            if let itemID {\n                DetailView(itemID: itemID)\n                    .environment(appState)\n            }\n        }\n\n        #if os(visionOS)\n        \u002F\u002F Immersive space\n        ImmersiveSpace(id: \"immersive\") {\n            ImmersiveView()\n                .environment(appState)\n        }\n        #endif\n    }\n}\n```\n\n**Key behaviors from Apple docs**\n- If a window with the same value already exists, the system **brings it to front** instead of opening a new one\n- SwiftUI persists the binding value for state restoration\n- Use unique identifier strings for each window group\n\n### Opening Additional Windows\n\n```swift\nstruct ItemRow: View {\n    let item: Item\n    @Environment(\\.openWindow) private var openWindow\n\n    var body: some View {\n        Button(item.title) {\n            \u002F\u002F Open in new window on iPad\n            \u002F\u002F Use ID to match window group, value to pass data\n            openWindow(id: \"detail\", value: item.id)\n        }\n    }\n}\n```\n\n### Dismissing Windows Programmatically\n\n```swift\nstruct DetailView: View {\n    var itemID: Item.ID?\n    @Environment(\\.dismiss) private var dismiss\n\n    var body: some View {\n        VStack {\n            \u002F\u002F ...\n            Button(\"Done\") {\n                dismiss()  \u002F\u002F Closes this window\n            }\n        }\n    }\n}\n```\n\n---\n\n# Part 4: Feature Module Basics\n\n## Core Principle\n\n> \"Split into modules when features have clear boundaries. Not before.\"\n\nPremature modularization creates overhead. Late modularization creates pain. Use this decision tree.\n\n## When to Modularize Decision Tree\n\n```\nShould I extract this feature into a module?\n│\n├─ Is the codebase under 5,000 lines with 1-2 developers?\n│  └─ NO modularization needed yet\n│     Single target is fine, revisit at 10,000 lines\n│\n├─ Is the codebase 5,000-20,000 lines with 3+ developers?\n│  └─ CONSIDER modularization\n│     Look for natural boundaries\n│\n├─ Is the codebase over 20,000 lines?\n│  └─ MODULARIZE for build times\n│     Parallel compilation essential\n│\n├─ Could this feature be used in multiple apps?\n│  └─ EXTRACT to reusable module\n│     Shared authentication, analytics, axiom-networking\n│\n├─ Do multiple developers work on this feature daily?\n│  └─ EXTRACT for merge conflict reduction\n│     Isolated codebases = parallel work\n│\n└─ Does the feature have clear input\u002Foutput boundaries?\n   ├─ YES → Good candidate for module\n   └─ NO → Refactor boundaries first, then extract\n```\n\n## Module Boundary Pattern\n\n### Define a Public API\n\n```swift\n\u002F\u002F FeatureModule\u002FSources\u002FFeatureModule\u002FFeatureAPI.swift\n\n\u002F\u002F\u002F Public interface for the feature module\npublic protocol FeatureAPI {\n    \u002F\u002F\u002F Show the feature's main view\n    @MainActor\n    func makeMainView() -> AnyView\n\n    \u002F\u002F\u002F Handle deep link into feature\n    @MainActor\n    func handleDeepLink(_ url: URL) -> Bool\n}\n\n\u002F\u002F\u002F Factory to create feature with dependencies\npublic struct FeatureFactory {\n    public static func create(\n        analytics: AnalyticsProtocol,\n        networking: NetworkingProtocol\n    ) -> FeatureAPI {\n        FeatureImplementation(\n            analytics: analytics,\n            networking: axiom-networking\n        )\n    }\n}\n```\n\n### Internal Implementation\n\n```swift\n\u002F\u002F FeatureModule\u002FSources\u002FFeatureModule\u002FInternal\u002FFeatureImplementation.swift\n\ninternal class FeatureImplementation: FeatureAPI {\n    private let analytics: AnalyticsProtocol\n    private let networking: NetworkingProtocol\n\n    internal init(\n        analytics: AnalyticsProtocol,\n        networking: NetworkingProtocol\n    ) {\n        self.analytics = analytics\n        self.networking = networking\n    }\n\n    @MainActor\n    public func makeMainView() -> AnyView {\n        AnyView(FeatureMainView(viewModel: makeViewModel()))\n    }\n\n    public func handleDeepLink(_ url: URL) -> Bool {\n        \u002F\u002F Handle feature-specific deep links\n        return false\n    }\n\n    private func makeViewModel() -> FeatureViewModel {\n        FeatureViewModel(analytics: analytics, axiom-networking: axiom-networking)\n    }\n}\n```\n\n### Use in Main App\n\n```swift\n\u002F\u002F MainApp\u002FSources\u002FApp\u002FAppDependencies.swift\n\n@Observable\nclass AppDependencies {\n    let analytics: AnalyticsProtocol\n    let networking: NetworkingProtocol\n\n    \u002F\u002F Lazy-created feature modules\n    lazy var profileFeature: FeatureAPI = {\n        ProfileFeatureFactory.create(\n            analytics: analytics,\n            networking: axiom-networking\n        )\n    }()\n\n    lazy var settingsFeature: FeatureAPI = {\n        SettingsFeatureFactory.create(\n            analytics: analytics,\n            networking: axiom-networking\n        )\n    }()\n}\n\n\u002F\u002F MainApp\u002FSources\u002FApp\u002FMainTabView.swift\n\nstruct MainTabView: View {\n    @Environment(AppDependencies.self) private var dependencies\n\n    var body: some View {\n        TabView {\n            dependencies.profileFeature.makeMainView()\n                .tabItem { Label(\"Profile\", systemImage: \"person\") }\n\n            dependencies.settingsFeature.makeMainView()\n                .tabItem { Label(\"Settings\", systemImage: \"gear\") }\n        }\n    }\n}\n```\n\n## Navigation Coordination Between Modules\n\nFeatures should not know about each other directly:\n\n```swift\n\u002F\u002F ❌ Feature knows about other features\nstruct ProfileView: View {\n    func showSettings() {\n        \u002F\u002F ProfileView imports SettingsFeature — circular dependency risk\n        NavigationLink(value: SettingsDestination())\n    }\n}\n\n\u002F\u002F ✅ Feature delegates navigation to coordinator\nstruct ProfileView: View {\n    let onShowSettings: () -> Void\n\n    func showSettings() {\n        onShowSettings()  \u002F\u002F ProfileView doesn't know what happens\n    }\n}\n\n\u002F\u002F Coordinator wires features together\nclass MainCoordinator {\n    func showSettings(from profile: ProfileFeatureAPI) {\n        \u002F\u002F Coordinator knows about both features\n        navigationPath.append(SettingsRoute())\n    }\n}\n```\n\n## Module Folder Structure\n\n```\nMyApp\u002F\n├── App\u002F                          # Main app target\n│   ├── MyApp.swift               # @main entry point\n│   ├── AppDependencies.swift     # Dependency container\n│   ├── AppStateController.swift  # App state machine\n│   └── Coordinators\u002F             # Navigation coordinators\n│\n├── Packages\u002F\n│   ├── Core\u002F                     # Shared utilities\n│   │   ├── Networking\u002F\n│   │   ├── Analytics\u002F\n│   │   └── Design\u002F               # Design system\n│   │\n│   ├── Features\u002F                 # Feature modules\n│   │   ├── Profile\u002F\n│   │   ├── Settings\u002F\n│   │   └── Onboarding\u002F\n│   │\n│   └── Domain\u002F                   # Business logic\n│       ├── Models\u002F\n│       └── Services\u002F\n```\n\n---\n\n# Part 5: Anti-Patterns\n\n## Anti-Pattern 1: Boolean-Based State\n\n```swift\n\u002F\u002F ❌ Boolean soup — impossible to validate\nclass AppState {\n    var isLoading = true\n    var isLoggedIn = false\n    var hasCompletedOnboarding = false\n    var hasError = false\n}\n\n\u002F\u002F What if isLoading && isLoggedIn && hasError are all true?\n```\n\n**Fix** Use enum-based state (Part 1)\n\n```swift\n\u002F\u002F ✅ Explicit states — compiler prevents invalid combinations\nenum AppState {\n    case loading\n    case unauthenticated\n    case onboarding(OnboardingStep)\n    case authenticated(User)\n    case error(AppError)\n}\n```\n\n## Anti-Pattern 2: Logic in @main\n\n```swift\n\u002F\u002F ❌ Business logic in App entry point\n@main\nstruct MyApp: App {\n    @State private var user: User?\n    @State private var isLoading = true\n\n    var body: some Scene {\n        WindowGroup {\n            if isLoading {\n                LoadingView()\n            } else if let user {\n                MainView(user: user)\n            } else {\n                LoginView(onLogin: { self.user = $0 })\n            }\n        }\n        .task {\n            user = await AuthService.getCurrentUser()\n            isLoading = false\n        }\n    }\n}\n```\n\n**Problems**\n- @main becomes bloated with logic\n- Hard to test without launching app\n- State scattered across multiple @State\n\n**Fix** Delegate to AppStateController (Part 2)\n\n```swift\n\u002F\u002F ✅ @main is a thin shell\n@main\nstruct MyApp: App {\n    @State private var appState = AppStateController()\n\n    var body: some Scene {\n        WindowGroup {\n            RootView()\n                .environment(appState)\n                .task { await appState.initialize() }\n        }\n    }\n}\n```\n\n## Anti-Pattern 3: Missing State Validation on Restore\n\n```swift\n\u002F\u002F ❌ Trusts restored state blindly\n.onAppear {\n    if let savedState = SceneStorage.appState {\n        appState.state = savedState  \u002F\u002F Token might be expired!\n    }\n}\n```\n\n**Problems**\n- Session could have expired\n- User could have been logged out on another device\n- Data could have been deleted\n\n**Fix** Validate before applying (Part 3)\n\n```swift\n\u002F\u002F ✅ Validates restored state\n.task {\n    if let savedSession = await SessionStorage.loadSession() {\n        do {\n            let user = try await AuthService.validateSession(savedSession)\n            appState.transition(to: .authenticated(user))\n        } catch {\n            \u002F\u002F Session invalid — force re-login\n            await SessionStorage.clearSession()\n            appState.transition(to: .unauthenticated)\n        }\n    }\n}\n```\n\n## Anti-Pattern 4: Navigation Logic Scattered Across Features\n\n```swift\n\u002F\u002F ❌ Every feature knows about every other feature\nstruct ProfileView: View {\n    @Environment(\\.navigationPath) private var path\n\n    func showSettings() {\n        path.append(SettingsDestination())  \u002F\u002F ProfileView imports Settings\n    }\n\n    func showOrderHistory() {\n        path.append(OrderHistoryDestination())  \u002F\u002F ProfileView imports Orders\n    }\n}\n```\n\n**Problems**\n- Circular dependencies\n- Hard to test navigation\n- Changes ripple across modules\n\n**Fix** Delegate to coordinator (Part 4)\n\n```swift\n\u002F\u002F ✅ Feature delegates navigation decisions\nstruct ProfileView: View {\n    let onShowSettings: () -> Void\n    let onShowOrderHistory: () -> Void\n\n    \u002F\u002F ProfileView doesn't know what these do\n}\n```\n\n## Anti-Pattern 5: God Coordinator\n\n```swift\n\u002F\u002F ❌ Single coordinator knows all features\nclass AppCoordinator {\n    func showProfile() { }\n    func showSettings() { }\n    func showOnboarding() { }\n    func showPayment() { }\n    func showChat() { }\n    func showOrderHistory() { }\n    func showNotifications() { }\n    \u002F\u002F ... 50 more methods\n}\n```\n\n**Problems**\n- Massive file that everyone touches\n- Merge conflicts\n- Single point of failure\n\n**Fix** Scoped coordinators\n\n```swift\n\u002F\u002F ✅ Scoped coordinators for each domain\nclass AuthCoordinator { }      \u002F\u002F Login, signup, forgot password\nclass MainCoordinator { }      \u002F\u002F Tab navigation, main flows\nclass SettingsCoordinator { }  \u002F\u002F Settings navigation tree\nclass OrderCoordinator { }     \u002F\u002F Order flow, history, details\n```\n\n---\n\n# Part 5b: UIKit Integration (Incremental Adoption)\n\n> For comprehensive bridging patterns (UIViewRepresentable, UIViewControllerRepresentable, UIHostingConfiguration, coordinators, lifecycle, gotchas), see `\u002Fskill axiom-uikit-bridging`. This section covers app-level integration strategy only.\n\n## When This Applies\n\nMost production iOS apps have existing UIKit code. Rewriting everything in SwiftUI is rarely practical. Use these patterns for incremental adoption.\n\n## UIHostingController — SwiftUI Inside UIKit\n\nEmbed SwiftUI views in an existing UIKit navigation hierarchy:\n\n```swift\n\u002F\u002F Present a SwiftUI view from a UIKit view controller\nlet settingsView = SettingsView(store: store)\nlet hostingController = UIHostingController(rootView: settingsView)\nnavigationController?.pushViewController(hostingController, animated: true)\n```\n\n**Key rules**:\n- `UIHostingController` owns the SwiftUI view's lifecycle — don't store the root view separately\n- Use `sizingOptions: .intrinsicContentSize` when embedding as a child for correct Auto Layout sizing\n- For sheets: `hostingController.modalPresentationStyle = .pageSheet` works naturally\n- SwiftUI environment doesn't bridge automatically — inject dependencies through the root view's initializer\n\n## UIViewControllerRepresentable — UIKit Inside SwiftUI\n\nWrap existing UIKit view controllers for use in SwiftUI:\n\n```swift\nstruct DocumentPickerView: UIViewControllerRepresentable {\n    @Binding var selectedURL: URL?\n\n    func makeUIViewController(context: Context) -> UIDocumentPickerViewController {\n        let picker = UIDocumentPickerViewController(forOpeningContentTypes: [.pdf])\n        picker.delegate = context.coordinator\n        return picker\n    }\n\n    func updateUIViewController(_ uiViewController: UIDocumentPickerViewController, context: Context) { }\n\n    func makeCoordinator() -> Coordinator { Coordinator(self) }\n\n    class Coordinator: NSObject, UIDocumentPickerDelegate {\n        let parent: DocumentPickerView\n        init(_ parent: DocumentPickerView) { self.parent = parent }\n\n        func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {\n            parent.selectedURL = urls.first\n        }\n    }\n}\n```\n\n**When to use**: Camera UI, document pickers, mail compose, any UIKit controller without a SwiftUI equivalent.\n\n## AppDelegate + SwiftUI @main\n\nBridge `UIApplicationDelegate` callbacks into a SwiftUI app:\n\n```swift\n@main\nstruct MyApp: App {\n    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate\n\n    var body: some Scene {\n        WindowGroup {\n            RootView()\n        }\n    }\n}\n\nclass AppDelegate: NSObject, UIApplicationDelegate {\n    func application(_ application: UIApplication,\n                     didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {\n        \u002F\u002F Push notification registration, third-party SDK init, etc.\n        return true\n    }\n\n    func application(_ application: UIApplication,\n                     didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {\n        \u002F\u002F Forward to push notification service\n    }\n}\n```\n\n**When to use**: Push notifications, third-party SDKs requiring AppDelegate, background URL sessions, Handoff.\n\n## Migration Priority\n\nWhen incrementally adopting SwiftUI in a UIKit app:\n\n1. **Leaf screens first** — Settings, About, detail views (no navigation complexity)\n2. **New features in SwiftUI** — Don't rewrite, but build new screens in SwiftUI\n3. **Shared components** — Build reusable SwiftUI components, wrap in `UIHostingController`\n4. **Navigation last** — Don't mix `UINavigationController` with `NavigationStack` in the same flow; migrate entire navigation subtrees\n\n**Don't**: Replace `UINavigationController` with `NavigationStack` for half the app. Either a flow is fully SwiftUI navigation or fully UIKit navigation.\n\n---\n\n# Part 6: Pressure Scenarios\n\n## Scenario 1: \"Just hardcode the root for now\"\n\n### The Pressure\n\n> \"We only have one flow right now. Just show MainView directly, we'll add auth later.\"\n\n### Red Flags\n- \"We'll add X later\" → Tech debt that compounds\n- \"It's just one flow\" → Flows multiply\n- \"Keep it simple\" → Simplicity now, complexity later\n\n### Time Cost Comparison\n\n| Option | Initial | When Adding Auth | Total |\n|--------|---------|------------------|-------|\n| Hardcode MainView | 0 min | 2-4 hours refactor | 2-4 hours |\n| AppStateController | 30 min | 30 min add state | 1 hour |\n\n### Push-Back Script\n\n> \"The AppStateController pattern takes 30 minutes now. When we add auth later — and we will — it'll take another 30 minutes to add the state. Hardcoding now saves 0 minutes because we'll spend 2-4 hours refactoring when we need auth. Let's invest 30 minutes now.\"\n\n### What to Do\n\n1. Create minimal AppStateController with two states:\n   ```swift\n   enum AppState {\n       case loading\n       case ready\n   }\n   ```\n\n2. When auth is needed, add states:\n   ```swift\n   enum AppState {\n       case loading\n       case unauthenticated  \u002F\u002F Added\n       case authenticated(User)  \u002F\u002F Added\n   }\n   ```\n\n3. Total effort: 1 hour instead of 4 hours\n\n---\n\n## Scenario 2: \"We don't need modules yet\"\n\n### The Pressure\n\n> \"Let's keep everything in one target. Modules are over-engineering.\"\n\n### Decision Framework\n\n| Codebase | Team | Recommendation |\n|----------|------|----------------|\n| \u003C 5,000 lines | 1-2 devs | Single target is fine |\n| 5,000-20,000 lines | 3+ devs | Consider modules |\n| > 20,000 lines | Any | Modules essential |\n\n### Push-Back Script\n\n> \"I agree modules add overhead. Let's use this decision tree: We have [X] lines and [Y] developers. Based on that, we [should\u002Fshouldn't] modularize yet. If we hit [threshold], we'll revisit. Sound good?\"\n\n### What to Do\n\n1. Check codebase size: `find . -name \"*.swift\" | xargs wc -l`\n2. If under threshold, document decision and threshold for revisit\n3. If over threshold, identify natural boundaries first\n\n---\n\n## Scenario 3: \"Navigation is too complex to test\"\n\n### The Pressure\n\n> \"Testing navigation state is too hard. Let's just do manual QA.\"\n\n### Why This Fails\n\n- Navigation bugs are #1 \"works on my machine\" cause\n- Deep linking requires automated verification\n- State restoration needs regression testing\n- Manual QA misses edge cases\n\n### Solution: Test the State Machine\n\n```swift\n\u002F\u002F ✅ Test navigation state without UI\n@Test func testLoginCompletesOnboarding() async {\n    let controller = AppStateController()\n    controller.transition(to: .unauthenticated)\n\n    \u002F\u002F Simulate login\n    await controller.handleLogin(user: mockUser)\n\n    \u002F\u002F First-time user goes to onboarding\n    #expect(controller.state == .onboarding(.welcome))\n}\n\n@Test func testDeepLinkWhileUnauthenticated() async {\n    let controller = AppStateController()\n    controller.transition(to: .unauthenticated)\n\n    \u002F\u002F Deep link to order\n    let handled = controller.handleDeepLink(URL(string: \"app:\u002F\u002Forder\u002F123\")!)\n\n    \u002F\u002F Should not navigate — requires auth\n    #expect(handled == false)\n    #expect(controller.state == .unauthenticated)\n}\n```\n\n### Push-Back Script\n\n> \"Navigation is complex, which is exactly why we need automated tests. The AppStateController pattern lets us test state transitions without launching the UI. We can verify deep linking, auth flows, and restoration in seconds. Manual QA can't catch all the combinations.\"\n\n---\n\n# Part 7: Code Review Checklist\n\n## App State\n\n- [ ] App state is an enum, not booleans\n- [ ] All valid states are explicitly defined\n- [ ] State transitions are validated in `isValidTransition`\n- [ ] Invalid transitions are caught (assertion in debug, logged in prod)\n- [ ] State changes are logged for debugging\n\n## Root View\n\n- [ ] @main delegates to AppStateController\n- [ ] No business logic in @main\n- [ ] RootView switches on single source of truth\n- [ ] Transitions are animated (no flicker)\n- [ ] Loading state has minimum display duration\n\n## Scene Lifecycle\n\n- [ ] scenePhase changes handled in one place\n- [ ] Session validated on .active (not blindly trusted)\n- [ ] Resources released on .background\n- [ ] SceneStorage used for tab selection \u002F navigation state\n- [ ] Restored state validated before applying\n\n## Module Boundaries\n\n- [ ] Features have public API protocols\n- [ ] No circular dependencies between modules\n- [ ] Navigation delegates to coordinators\n- [ ] Dependencies injected, not singletons\n- [ ] Module decision documented (why split \u002F not split)\n\n## Testing\n\n- [ ] State transitions tested without UI\n- [ ] Invalid transitions tested\n- [ ] Deep link handling tested\n- [ ] Restoration validation tested\n\n---\n\n## Resources\n\n**WWDC**: 2025-266, 2024-10150, 2023-10149, 2025-256, 2022-10054\n\n**Docs**: \u002Fswiftui\u002Fscenephase, \u002Fswiftui\u002Fscene, \u002Fswiftui\u002Fscenestorage, \u002Fswiftui\u002Fwindowgroup, \u002Fobservation\u002Fobservable()\n\n**Skills**: axiom-swiftui-architecture, axiom-swiftui-nav, axiom-swift-concurrency\n\n",{"name":5,"license":15,"metadata":44,"description":7,"compatibility":46},{"version":45},"1.0","iOS 26+, iPadOS 26+, macOS Tahoe+, watchOS 26+, axiom-visionOS 26+. Xcode 26+","def8531e65114c0fca8fb8551c1871ee0eed705c","skills\u002FCharlesWiltgen\u002FAxiom\u002Faxiom-app-composition",24,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub","https:\u002F\u002Fskillshub.wtf","https:\u002F\u002Fgithub.com\u002FComeOnOliver","AI & Blockchain Engineer\r\n","2026-06-24 22:07:19",{"source":8,"skillName":5,"commands":56,"prompt":66,"zipAvailable":67},[57,60,63],{"tool":58,"command":59},"npx","npx skills add ComeOnOliver\u002Fskillshub --skill axiom-app-composition",{"tool":61,"command":62},"bunx","bunx skills add ComeOnOliver\u002Fskillshub --skill axiom-app-composition",{"tool":64,"command":65},"pnpm","pnpm dlx skills add ComeOnOliver\u002Fskillshub --skill axiom-app-composition","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002FCharlesWiltgen\u002FAxiom\u002Faxiom-app-composition\u002FSKILL.md",true,[69],{"path":70,"resourceType":71,"resourceTypeDesc":72,"sizeBytes":73,"sourceHtmlUrl":74},"SKILL.md.tmpl","OTHER","其他",5255,"https:\u002F\u002Fgithub.com\u002Fmarshadgani\u002FArshad.AI\u002Fblob\u002Fclaude\u002Fai-personal-assistant-main\u002F.claude\u002Fskills\u002Fgstack\u002Fcontext-restore\u002FSKILL.md.tmpl",[76,90,100,109,115,122],{"skillNo":77,"name":78,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":79,"categories":80,"keywords":86,"publishedAt":39},"01M6FKJN13GMG4KY57J3QD8GY5","model-hierarchy-skill","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzscole\u002Fmodel-hierarchy-skill\u002Fmodel-hierarchy-skill\u002FSKILL.md",[81],{"categoryNo":82,"categoryName":83,"taxonomyType":84,"taxonomyTypeDesc":85},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[87,88,89],"model","model hierarchy","model hierarchy skill",{"skillNo":91,"name":92,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":93,"categories":94,"keywords":96,"publishedAt":39},"01M6FKJJJ6GCAMR4PPPRD12M5X","zoom-mcp","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-mcp\u002FSKILL.md",[95],{"categoryNo":82,"categoryName":83,"taxonomyType":84,"taxonomyTypeDesc":85},[97,98,99],"MCP","zoom","zoom mcp",{"skillNo":101,"name":102,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":103,"categories":104,"keywords":105,"publishedAt":39},"01M6FKJE3KYT3KPEZMEEHYKA6K","zoom-apps-sdk","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-apps-sdk\u002FSKILL.md",[],[106,107,108,98],"sdk","zoom apps sdk","apps",{"skillNo":110,"name":111,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":112,"categories":113,"keywords":114,"publishedAt":39},"01M6FKJ9EDH9Q5VWDF8HAJRSP0","windows","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwindows\u002FSKILL.md",[],[111],{"skillNo":116,"name":117,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":118,"categories":119,"keywords":121,"publishedAt":39},"01M6FKJ666BEEFC3AE9GJ72Q93","whiteboard","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwhiteboard\u002FSKILL.md",[120],{"categoryNo":82,"categoryName":83,"taxonomyType":84,"taxonomyTypeDesc":85},[117],{"skillNo":123,"name":124,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":125,"categories":126,"keywords":127,"publishedAt":39},"01M6FKJ21WFCDGTJQ2Q91T9DVW","websockets","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwebsockets\u002FSKILL.md",[],[124]]