# Architecture Remediation - Executable Task Plan **Project:** PlantGuide iOS App **Created:** 2026-01-23 **Owner:** Senior Developer **Source:** ARCHITECTURE_REMEDIATION_PLAN.md --- ## How to Use This Plan Each task has: - **Validation Command**: Run this to verify completion - **Done When**: Specific observable criteria - **Commit Message**: Use this exact message format Work through tasks in order. Do not skip ahead. --- ## PHASE 1: Critical Fixes (Crash Prevention) ### Task 1.1.1: Add Protocol Conformance Declaration **File:** `PlantGuide/Data/Repositories/InMemoryPlantRepository.swift` **Do:** ```swift // Find this line: final class InMemoryPlantRepository { ... } // Change to: final class InMemoryPlantRepository: PlantRepositoryProtocol { ... } ``` **Validation:** ```bash # Build the project - should compile without errors xcodebuild -scheme PlantGuide -destination 'platform=iOS Simulator,name=iPhone 16' build 2>&1 | grep -E "(error:|BUILD SUCCEEDED)" ``` **Done When:** - [ ] Build succeeds with no errors - [ ] `InMemoryPlantRepository` explicitly declares `PlantRepositoryProtocol` conformance **Commit:** `fix(data): add PlantRepositoryProtocol conformance to InMemoryPlantRepository` --- ### Task 1.1.2: Implement Missing Protocol Methods (if any) **File:** `PlantGuide/Data/Repositories/InMemoryPlantRepository.swift` **Do:** 1. Check what methods `PlantRepositoryProtocol` requires 2. Compare against what `InMemoryPlantRepository` implements 3. Add any missing methods **Validation:** ```bash # Grep for protocol definition grep -A 50 "protocol PlantRepositoryProtocol" PlantGuide/Domain/Interfaces/Repositories/*.swift # Compare with implementation grep -E "func (save|delete|fetch|get)" PlantGuide/Data/Repositories/InMemoryPlantRepository.swift ``` **Done When:** - [ ] All protocol-required methods exist in `InMemoryPlantRepository` - [ ] Build succeeds **Commit:** `fix(data): implement missing PlantRepositoryProtocol methods in InMemoryPlantRepository` --- ### Task 1.1.3: Add Protocol Conformance Unit Test **File:** Create `PlantGuideTests/Data/Repositories/InMemoryPlantRepositoryTests.swift` **Do:** ```swift import XCTest @testable import PlantGuide final class InMemoryPlantRepositoryTests: XCTestCase { func testConformsToPlantRepositoryProtocol() { let repo: PlantRepositoryProtocol = InMemoryPlantRepository.shared XCTAssertNotNil(repo) } } ``` **Validation:** ```bash xcodebuild test -scheme PlantGuide -destination 'platform=iOS Simulator,name=iPhone 16' -only-testing:PlantGuideTests/InMemoryPlantRepositoryTests 2>&1 | grep -E "(Test Suite|passed|failed)" ``` **Done When:** - [ ] Test file exists - [ ] Test passes - [ ] Test explicitly checks protocol conformance at compile time **Commit:** `test(data): add protocol conformance test for InMemoryPlantRepository` --- ### Task 1.2.1: Identify Current Thread Safety Issue **File:** `PlantGuide/Data/DataSources/Local/CoreData/CoreDataStack.swift` **Do:** 1. Read the file 2. Find `lazy var persistentContainer` 3. Document the race condition scenario **Validation:** ```bash # Check current implementation grep -A 10 "lazy var persistentContainer" PlantGuide/Data/DataSources/Local/CoreData/CoreDataStack.swift ``` **Done When:** - [ ] Understand where race condition occurs - [ ] Decision made: Actor / DispatchQueue / Eager init **Commit:** N/A (research task) --- ### Task 1.2.2: Fix Thread Safety with Eager Initialization **File:** `PlantGuide/Data/DataSources/Local/CoreData/CoreDataStack.swift` **Do:** ```swift // Replace lazy var with let + init // BEFORE: lazy var persistentContainer: NSPersistentContainer = { ... }() // AFTER: let persistentContainer: NSPersistentContainer init() { persistentContainer = NSPersistentContainer(name: "PlantGuide") persistentContainer.loadPersistentStores { ... } } ``` **Validation:** ```bash # Verify no lazy var for persistentContainer grep "lazy var persistentContainer" PlantGuide/Data/DataSources/Local/CoreData/CoreDataStack.swift # Should return nothing # Build and run tests xcodebuild test -scheme PlantGuide -destination 'platform=iOS Simulator,name=iPhone 16' 2>&1 | grep -E "(BUILD|Test Suite)" ``` **Done When:** - [ ] No `lazy var` for `persistentContainer` - [ ] Initialization happens in `init()` - [ ] All existing Core Data tests pass **Commit:** `fix(coredata): make persistentContainer thread-safe with eager initialization` --- ### Task 1.3.1: Find All Unowned References in DIContainer **File:** `PlantGuide/Core/DI/DIContainer.swift` **Do:** ```bash grep -n "unowned self" PlantGuide/Core/DI/DIContainer.swift ``` **Validation:** ```bash # List all line numbers with unowned self grep -n "unowned self" PlantGuide/Core/DI/DIContainer.swift | wc -l # Document the count ``` **Done When:** - [ ] All `unowned self` locations documented - [ ] Count recorded: _____ occurrences **Commit:** N/A (audit task) --- ### Task 1.3.2: Replace Unowned with Weak + Guard **File:** `PlantGuide/Core/DI/DIContainer.swift` **Do:** For each occurrence found in 1.3.1: ```swift // BEFORE: LazyService { [unowned self] in return SomeService(dependency: self.otherService) } // AFTER: LazyService { [weak self] in guard let self else { fatalError("DIContainer deallocated unexpectedly") } return SomeService(dependency: self.otherService) } ``` **Validation:** ```bash # Zero unowned self references should remain grep "unowned self" PlantGuide/Core/DI/DIContainer.swift # Should return nothing # Verify weak self exists grep "weak self" PlantGuide/Core/DI/DIContainer.swift | wc -l # Should match the count from 1.3.1 ``` **Done When:** - [ ] Zero `unowned self` in DIContainer - [ ] All replaced with `weak self` + guard - [ ] App launches successfully - [ ] Build succeeds **Commit:** `fix(di): replace unowned with weak references in DIContainer` --- ## PHASE 2: Architectural Consistency ### Task 2.1.1: Audit SwiftData Usage **Files:** - `PlantGuide/PlantGuideApp.swift` - `PlantGuide/Item.swift` **Do:** ```bash # Check for SwiftData imports grep -r "import SwiftData" PlantGuide/ # Check for @Model usage grep -r "@Model" PlantGuide/ # Check for modelContainer usage grep -r "modelContainer" PlantGuide/ ``` **Validation:** Record findings: - SwiftData imports: _____ files - @Model usages: _____ types - ModelContainer usage: _____ locations **Done When:** - [ ] Complete inventory of SwiftData usage - [ ] Decision documented: Keep SwiftData or Remove **Commit:** N/A (audit task) --- ### Task 2.1.2: Remove Unused SwiftData (if decision is Core Data only) **Files:** - `PlantGuide/PlantGuideApp.swift` - `PlantGuide/Item.swift` **Do:** 1. Delete `Item.swift` 2. In `PlantGuideApp.swift`: - Remove `sharedModelContainer` property - Remove `.modelContainer(sharedModelContainer)` modifier - Remove `import SwiftData` **Validation:** ```bash # Item.swift should not exist ls PlantGuide/Item.swift 2>&1 # Should return "No such file" # No SwiftData in PlantGuideApp grep -E "(SwiftData|modelContainer|sharedModelContainer)" PlantGuide/PlantGuideApp.swift # Should return nothing # Build succeeds xcodebuild -scheme PlantGuide -destination 'platform=iOS Simulator,name=iPhone 16' build 2>&1 | grep "BUILD SUCCEEDED" ``` **Done When:** - [ ] `Item.swift` deleted - [ ] No SwiftData references in `PlantGuideApp.swift` - [ ] Build succeeds - [ ] App launches correctly **Commit:** `refactor(app): remove unused SwiftData setup, keeping Core Data` --- ### Task 2.2.1: Verify CoreDataPlantStorage Conforms to PlantRepositoryProtocol **Do:** ```bash grep -A 5 "class CoreDataPlantStorage" PlantGuide/Data/DataSources/Local/CoreData/ grep "PlantRepositoryProtocol" PlantGuide/Data/DataSources/Local/CoreData/CoreDataPlantStorage.swift ``` **Validation:** - [ ] `CoreDataPlantStorage` explicitly conforms to `PlantRepositoryProtocol` If not conforming, add conformance first. **Done When:** - [ ] Conformance verified or added **Commit:** `fix(coredata): ensure CoreDataPlantStorage conforms to PlantRepositoryProtocol` --- ### Task 2.2.2: Update DIContainer to Use Core Data Repository **File:** `PlantGuide/Core/DI/DIContainer.swift` **Do:** ```swift // Find plantRepository property // BEFORE: var plantRepository: PlantRepositoryProtocol { return InMemoryPlantRepository.shared } // AFTER: var plantRepository: PlantRepositoryProtocol { return _coreDataPlantStorage.value } ``` **Validation:** ```bash # Check plantRepository returns Core Data grep -A 3 "var plantRepository:" PlantGuide/Core/DI/DIContainer.swift # Should reference coreData, not InMemory # Run tests xcodebuild test -scheme PlantGuide -destination 'platform=iOS Simulator,name=iPhone 16' 2>&1 | grep -E "(Test Suite|passed|failed)" ``` **Done When:** - [ ] `plantRepository` returns Core Data storage - [ ] All tests pass - [ ] App displays persisted data correctly **Commit:** `refactor(di): switch plantRepository to Core Data storage` --- ### Task 2.3.1: Guard Sample Data Seeding **File:** `PlantGuide/Data/Repositories/InMemoryPlantRepository.swift` **Do:** ```swift // Find seedWithSampleData() call in init // Wrap with DEBUG flag: private init() { #if DEBUG seedWithSampleData() #endif } ``` **Validation:** ```bash # Check DEBUG guard exists grep -A 5 "private init()" PlantGuide/Data/Repositories/InMemoryPlantRepository.swift # Should show #if DEBUG around seedWithSampleData # Build release config xcodebuild -scheme PlantGuide -configuration Release -destination 'platform=iOS Simulator,name=iPhone 16' build 2>&1 | grep "BUILD SUCCEEDED" ``` **Done When:** - [ ] `seedWithSampleData()` wrapped in `#if DEBUG` - [ ] Release build succeeds **Commit:** `fix(data): guard sample data seeding with DEBUG flag` --- ## PHASE 3: Clean Architecture Compliance ### Task 3.1.1: Create UI Extensions File **Do:** ```bash mkdir -p PlantGuide/Presentation/Extensions touch PlantGuide/Presentation/Extensions/Enums+UI.swift ``` **Validation:** ```bash ls PlantGuide/Presentation/Extensions/Enums+UI.swift # Should exist ``` **Done When:** - [ ] File created at correct path **Commit:** N/A (file creation only) --- ### Task 3.1.2: Move UI Extensions from Domain **Files:** - Source: `PlantGuide/Domain/Entities/Enums.swift` - Destination: `PlantGuide/Presentation/Extensions/Enums+UI.swift` **Do:** 1. Copy these extensions to new file: - `CareTaskType.iconName` - `CareTaskType.iconColor` - `CareTaskType.description` - `LightRequirement.description` - `WateringFrequency.description` - `FertilizerFrequency.description` - `HumidityLevel.description` 2. Remove from original file 3. Add `import SwiftUI` to new file only **Validation:** ```bash # No SwiftUI in Domain Enums grep "import SwiftUI" PlantGuide/Domain/Entities/Enums.swift # Should return nothing # SwiftUI in Presentation extension grep "import SwiftUI" PlantGuide/Presentation/Extensions/Enums+UI.swift # Should return the import # Build succeeds xcodebuild -scheme PlantGuide -destination 'platform=iOS Simulator,name=iPhone 16' build 2>&1 | grep "BUILD SUCCEEDED" ``` **Done When:** - [ ] `Domain/Entities/Enums.swift` has no SwiftUI import - [ ] All UI extensions live in `Presentation/Extensions/Enums+UI.swift` - [ ] Build succeeds - [ ] UI displays correctly (icons, colors visible) **Commit:** `refactor(architecture): extract UI extensions from domain enums to presentation layer` --- ### Task 3.2.1: Document Existing Singletons **Do:** Create list of all `.shared` singletons: ```bash grep -r "\.shared" PlantGuide/ --include="*.swift" | grep -v "Tests" | grep -v ".build" ``` **Validation:** Document findings in this task: - [ ] `DIContainer.shared` - Location: _____ - [ ] `CoreDataStack.shared` - Location: _____ - [ ] `InMemoryPlantRepository.shared` - Location: _____ - [ ] `FilterPreferencesStorage.shared` - Location: _____ - [ ] Other: _____ **Done When:** - [ ] All singletons documented **Commit:** N/A (documentation task) --- ## PHASE 4: Code Cleanup ### Task 4.1.1: Verify Item.swift is Unused **Do:** ```bash # Search for any reference to Item type grep -r "Item" PlantGuide/ --include="*.swift" | grep -v "Item.swift" | grep -v "MenuItem" | grep -v "ListItem" | grep -v "// Item" ``` **Validation:** - [ ] No meaningful references to `Item` type found **Done When:** - [ ] Confirmed `Item.swift` is dead code **Commit:** N/A (verification only) --- ### Task 4.1.2: Delete Item.swift **Do:** ```bash rm PlantGuide/Item.swift ``` **Validation:** ```bash # File should not exist ls PlantGuide/Item.swift 2>&1 | grep "No such file" # Build succeeds xcodebuild -scheme PlantGuide -destination 'platform=iOS Simulator,name=iPhone 16' build 2>&1 | grep "BUILD SUCCEEDED" ``` **Done When:** - [ ] File deleted - [ ] Build succeeds **Commit:** `chore: remove unused Item.swift template file` --- ### Task 4.2.1: Document @unchecked Sendable Usage **Do:** ```bash grep -rn "@unchecked Sendable" PlantGuide/ --include="*.swift" ``` For each occurrence, add a comment explaining WHY it's safe: ```swift // MARK: - Thread Safety // This type is @unchecked Sendable because: // - All mutable state is protected by [mechanism] // - Public interface is thread-safe because [reason] @unchecked Sendable ``` **Validation:** ```bash # Each @unchecked Sendable should have a comment within 5 lines above it grep -B 5 "@unchecked Sendable" PlantGuide/Data/DataSources/Local/CoreData/CoreDataStack.swift | grep -E "(Thread Safety|thread-safe|Sendable because)" ``` **Done When:** - [ ] Every `@unchecked Sendable` has justification comment - [ ] No hidden thread-safety bugs identified **Commit:** `docs(concurrency): document thread-safety justification for @unchecked Sendable types` --- ## Completion Checklist ### Phase 1 (Critical - Do First) - [ ] 1.1.1 - Protocol conformance declaration - [ ] 1.1.2 - Missing protocol methods - [ ] 1.1.3 - Protocol conformance test - [ ] 1.2.1 - Identify thread safety issue - [ ] 1.2.2 - Fix thread safety - [ ] 1.3.1 - Find unowned references - [ ] 1.3.2 - Replace unowned with weak ### Phase 2 (High Priority) - [ ] 2.1.1 - Audit SwiftData - [ ] 2.1.2 - Remove unused SwiftData - [ ] 2.2.1 - Verify CoreDataPlantStorage conformance - [ ] 2.2.2 - Update DIContainer - [ ] 2.3.1 - Guard sample data ### Phase 3 (Medium Priority) - [ ] 3.1.1 - Create UI extensions file - [ ] 3.1.2 - Move UI extensions - [ ] 3.2.1 - Document singletons ### Phase 4 (Low Priority) - [ ] 4.1.1 - Verify Item.swift unused - [ ] 4.1.2 - Delete Item.swift - [ ] 4.2.1 - Document @unchecked Sendable --- ## Final Validation After all tasks complete: ```bash # Full build xcodebuild -scheme PlantGuide -destination 'platform=iOS Simulator,name=iPhone 16' clean build 2>&1 | tail -5 # Full test suite xcodebuild test -scheme PlantGuide -destination 'platform=iOS Simulator,name=iPhone 16' 2>&1 | grep -E "Test Suite.*passed" # No unowned self grep -r "unowned self" PlantGuide/ --include="*.swift" | wc -l # Should be 0 # No SwiftUI in Domain grep -r "import SwiftUI" PlantGuide/Domain/ --include="*.swift" | wc -l # Should be 0 ``` **All Done When:** - [ ] Clean build succeeds - [ ] All tests pass - [ ] Zero `unowned self` in codebase - [ ] Zero SwiftUI imports in Domain layer - [ ] App runs correctly on simulator --- *Plan created: 2026-01-23*