- Implement camera capture and plant identification workflow - Add Core Data persistence for plants, care schedules, and cached API data - Create collection view with grid/list layouts and filtering - Build plant detail views with care information display - Integrate Trefle botanical API for plant care data - Add local image storage for captured plant photos - Implement dependency injection container for testability - Include accessibility support throughout the app Bug fixes in this commit: - Fix Trefle API decoding by removing duplicate CodingKeys - Fix LocalCachedImage to load from correct PlantImages directory - Set dateAdded when saving plants for proper collection sorting Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
8.8 KiB
Architecture Remediation Plan
Project: PlantGuide iOS App Date: 2026-01-23 Priority: Pre-Production Cleanup Estimated Scope: Medium (1-2 sprint cycles)
Overview
This plan addresses architectural concerns identified during code review. Items are prioritized by risk and impact to production stability.
Phase 1: Critical Fixes (Do First)
These items could cause runtime crashes or data inconsistency if not addressed.
Task 1.1: Fix Repository Protocol Conformance
File: PlantGuide/Data/Repositories/InMemoryPlantRepository.swift
Problem: InMemoryPlantRepository is returned as PlantRepositoryProtocol in DIContainer but doesn't actually conform to that protocol.
Action Items:
- Add
PlantRepositoryProtocolconformance toInMemoryPlantRepository - Implement any missing required methods from the protocol
- Verify compilation succeeds
- Add unit test to verify conformance
Acceptance Criteria:
- Code compiles without warnings
DIContainer.plantRepositoryreturns a validPlantRepositoryProtocolinstance
Task 1.2: Fix Core Data Stack Thread Safety
File: PlantGuide/Data/DataSources/Local/CoreData/CoreDataStack.swift
Problem: lazy var persistentContainer initialization isn't thread-safe despite class being marked @unchecked Sendable.
Action Items:
- Replace
lazy varwith a thread-safe initialization pattern - Option A: Use
DispatchQueue.syncfor synchronized access - Option B: Use an actor for the Core Data stack
- Option C: Initialize in
init()instead of lazily - Add concurrency tests to verify thread safety
Acceptance Criteria:
- No race conditions when accessing
persistentContainerfrom multiple threads - Existing Core Data tests still pass
Task 1.3: Replace Unowned with Weak References in DIContainer
File: PlantGuide/Core/DI/DIContainer.swift
Problem: [unowned self] in lazy closures could crash if accessed after deallocation.
Action Items:
- Find all
[unowned self]captures in DIContainer (lines ~121, 150, 196, 204, 213) - Replace with
[weak self]and add guard statements - Example pattern:
LazyService { [weak self] in guard let self else { fatalError("DIContainer deallocated unexpectedly") } return PlantNetAPIService.configured(...) } - Consider if fatalError is appropriate or if optional return is better
Acceptance Criteria:
- No
unownedreferences in DIContainer - App behaves correctly during normal lifecycle
Phase 2: Architectural Consistency (High Priority)
These items address design inconsistencies that affect maintainability.
Task 2.1: Resolve SwiftData vs Core Data Conflict
Files:
PlantGuide/PlantGuideApp.swiftPlantGuide/Item.swift
Problem: App initializes SwiftData ModelContainer but uses Core Data for persistence. Item.swift appears to be unused template code.
Action Items:
- Decision Required: Confirm with team - Are we using SwiftData or Core Data?
- If Core Data (current approach):
- Remove
sharedModelContainerfromPlantGuideApp.swift - Remove
.modelContainer(sharedModelContainer)modifier - Delete
Item.swift - Remove
import SwiftDataif no longer needed
- Remove
- If migrating to SwiftData (future):
- Document migration plan
- Keep current Core Data as transitional
Acceptance Criteria:
- Only one persistence technology in active use
- No dead code related to unused persistence layer
Task 2.2: Unify Repository Implementation
Files:
PlantGuide/Core/DI/DIContainer.swiftPlantGuide/Data/Repositories/InMemoryPlantRepository.swift
Problem: plantRepository returns in-memory storage while plantCollectionRepository returns Core Data. This creates potential data sync issues.
Action Items:
- Decision Required: Should all plant data use Core Data in production?
- If yes (recommended):
- Update
DIContainer.plantRepositoryto return_coreDataPlantStorage.value - Ensure
CoreDataPlantStorageconforms toPlantRepositoryProtocol - Keep
InMemoryPlantRepositoryfor testing only - Add
#if DEBUGaround sample data inInMemoryPlantRepository
- Update
- Update any code that assumes in-memory behavior
- Run full test suite to verify no regressions
Acceptance Criteria:
- Single source of truth for plant data in production
- Tests can still use in-memory repository via DI
Task 2.3: Guard Sample Data with DEBUG Flag
File: PlantGuide/Data/Repositories/InMemoryPlantRepository.swift
Problem: seedWithSampleData() runs in production builds.
Action Items:
- Wrap
seedWithSampleData()call ininit()with#if DEBUG:private init() { #if DEBUG seedWithSampleData() #endif } - Consider making sample data opt-in via a parameter
- Verify production builds don't include sample plants
Acceptance Criteria:
- Release builds start with empty repository
- Debug builds can optionally include sample data
Phase 3: Clean Architecture Compliance (Medium Priority)
These items improve separation of concerns and testability.
Task 3.1: Extract UI Extensions from Domain Enums
Files:
PlantGuide/Domain/Entities/Enums.swift(source)- Create:
PlantGuide/Presentation/Extensions/CareTaskType+UI.swift - Create:
PlantGuide/Presentation/Extensions/Enums+UI.swift
Problem: Domain enums import SwiftUI and contain UI-specific code (colors, icons), violating Clean Architecture.
Action Items:
- Create new file
Presentation/Extensions/Enums+UI.swift - Move these extensions from
Enums.swift:CareTaskType.iconNameCareTaskType.iconColorCareTaskType.descriptionLightRequirement.descriptionWateringFrequency.descriptionFertilizerFrequency.descriptionHumidityLevel.description
- Remove
import SwiftUIfromEnums.swift - Verify all views still compile and display correctly
Acceptance Criteria:
Domain/Entities/Enums.swifthas no SwiftUI import- All UI functionality preserved in Presentation layer
- Domain layer has zero UI framework dependencies
Task 3.2: Reduce Singleton Usage (Optional Refactor)
Files: Multiple
Problem: Heavy singleton usage reduces testability and flexibility.
Action Items:
- Low Priority - Document current singletons:
DIContainer.sharedCoreDataStack.sharedInMemoryPlantRepository.sharedFilterPreferencesStorage.shared
- For new code, prefer dependency injection over
.sharedaccess - Consider refactoring
FilterPreferencesStorageto be injected - Keep
DIContainer.sharedas acceptable app-level singleton
Acceptance Criteria:
- New code uses DI patterns
- Existing singletons documented
- No new singletons added without justification
Phase 4: Code Cleanup (Low Priority)
Minor improvements for code hygiene.
Task 4.1: Remove Unused Template Files
File: PlantGuide/Item.swift
Action Items:
- Verify
Itemis not referenced anywhere (search project) - Delete
Item.swift - Remove from Xcode project if needed
Acceptance Criteria:
- No unused files in project
Task 4.2: Review @unchecked Sendable Usage
Files:
PlantGuide/Data/DataSources/Remote/NetworkService/NetworkService.swiftPlantGuide/Data/DataSources/Local/CoreData/CoreDataStack.swift
Action Items:
- Audit each
@unchecked Sendableusage - Document why each is safe OR fix the underlying issue
- Add code comments explaining thread-safety guarantees
Acceptance Criteria:
- Each
@unchecked Sendablehas documented justification - No hidden thread-safety bugs
Task Checklist Summary
Must Complete Before Release
- 1.1 - Fix Repository Protocol Conformance
- 1.2 - Fix Core Data Stack Thread Safety
- 1.3 - Replace Unowned References
- 2.1 - Resolve SwiftData vs Core Data
- 2.2 - Unify Repository Implementation
- 2.3 - Guard Sample Data
Should Complete
- 3.1 - Extract UI Extensions from Domain
- 4.1 - Remove Unused Template Files
Nice to Have
- 3.2 - Reduce Singleton Usage
- 4.2 - Review @unchecked Sendable
Decision Log
| Decision | Options | Chosen | Date | Decided By |
|---|---|---|---|---|
| Persistence technology | SwiftData / Core Data | TBD | ||
| Production repository | Core Data / In-Memory | TBD |
Notes for Developer
- Start with Phase 1 - These are potential crash sources
- Run tests after each task - Ensure no regressions
- Create separate commits - One commit per task for easy review/revert
- Update this doc - Check off items as completed
- Ask questions - Flag any blockers in standup
Document created by: Project Manager Last updated: 2026-01-23