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

15 KiB

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:

// Find this line:
final class InMemoryPlantRepository { ... }

// Change to:
final class InMemoryPlantRepository: PlantRepositoryProtocol { ... }

Validation:

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

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

import XCTest
@testable import PlantGuide

final class InMemoryPlantRepositoryTests: XCTestCase {
    func testConformsToPlantRepositoryProtocol() {
        let repo: PlantRepositoryProtocol = InMemoryPlantRepository.shared
        XCTAssertNotNil(repo)
    }
}

Validation:

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:

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

// Replace lazy var with let + init
// BEFORE:
lazy var persistentContainer: NSPersistentContainer = { ... }()

// AFTER:
let persistentContainer: NSPersistentContainer

init() {
    persistentContainer = NSPersistentContainer(name: "PlantGuide")
    persistentContainer.loadPersistentStores { ... }
}

Validation:

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

grep -n "unowned self" PlantGuide/Core/DI/DIContainer.swift

Validation:

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

// 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:

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

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

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

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:

// Find plantRepository property
// BEFORE:
var plantRepository: PlantRepositoryProtocol {
    return InMemoryPlantRepository.shared
}

// AFTER:
var plantRepository: PlantRepositoryProtocol {
    return _coreDataPlantStorage.value
}

Validation:

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

// Find seedWithSampleData() call in init
// Wrap with DEBUG flag:
private init() {
    #if DEBUG
    seedWithSampleData()
    #endif
}

Validation:

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

mkdir -p PlantGuide/Presentation/Extensions
touch PlantGuide/Presentation/Extensions/Enums+UI.swift

Validation:

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:

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

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:

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

rm PlantGuide/Item.swift

Validation:

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

grep -rn "@unchecked Sendable" PlantGuide/ --include="*.swift"

For each occurrence, add a comment explaining WHY it's safe:

// 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:

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

# 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