Files
PlantGuide/Docs/ARCHITECTURE_REMEDIATION_PLAN_Shit.md
Trey t 136dfbae33 Add PlantGuide iOS app with plant identification and care management
- 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>
2026-01-23 12:18:01 -06:00

617 lines
15 KiB
Markdown

# 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*