Replace brittle localized-string selectors and broken wait helpers with a robust, identifier-first UI test infrastructure. All 41 UI tests pass on iOS 26.2 simulator (iPhone 17). Foundation: - BaseUITestCase with deterministic launch helpers (launchClean, launchOffline) - WaitHelpers (waitUntilHittable, waitUntilGone, tapWhenReady) replacing sleep() - UITestID enum mirroring AccessibilityIdentifiers from the app target - Screen objects: TabBarScreen, CameraScreen, CollectionScreen, TodayScreen, SettingsScreen, PlantDetailScreen Key fixes: - Tab navigation uses waitForExistence+tap instead of isHittable (unreliable in iOS 26 simulator) - Tests handle real app state (empty collection, no camera permission) - Increased timeouts for parallel clone execution - Added NetworkMonitorProtocol and protocol-typed DI for testability - Fixed actor-isolation issues in unit test mocks Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
7.1 KiB
7.1 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Build & Test Commands
# Build
xcodebuild -project PlantGuide.xcodeproj -scheme PlantGuide -configuration Debug
# Run all tests
xcodebuild test -project PlantGuide.xcodeproj -scheme PlantGuide -destination 'platform=iOS Simulator,name=iPhone 17'
# Run a single test class
xcodebuild test -project PlantGuide.xcodeproj -scheme PlantGuide -destination 'platform=iOS Simulator,name=iPhone 17' -only-testing:PlantGuideTests/HybridIdentificationUseCaseTests
# Run a single test method
xcodebuild test -project PlantGuide.xcodeproj -scheme PlantGuide -destination 'platform=iOS Simulator,name=iPhone 17' -only-testing:PlantGuideTests/SavePlantUseCaseTests/testSavePlant_Success
# Build UI tests only (compile check, no simulator required)
xcodebuild build-for-testing -project PlantGuide.xcodeproj -scheme PlantGuide -destination 'platform=iOS Simulator,name=iPhone 17' -quiet
# Run all UI tests
xcodebuild test -project PlantGuide.xcodeproj -scheme PlantGuide -destination 'platform=iOS Simulator,name=iPhone 17' -only-testing:PlantGuideUITests
App Structure
Tab Navigation: Camera | Collection | Today | Settings
Implemented Features
- Plant Identification - Camera-based plant ID using on-device ML + PlantNet API fallback
- Plant Collection - Save identified plants with care schedules
- Plant Rooms/Zones - Organize plants by room (Kitchen, Living Room, Bedroom, etc.)
- Today View - Dashboard showing overdue + today's care tasks grouped by room
- Progress Photos - Capture growth photos with time-lapse playback and photo reminders
- Care Scheduling - Watering, fertilizing, repotting, pruning, pest control tasks
- Notifications - Local notifications for care reminders and photo reminders
- CloudKit Sync - iCloud sync via NSPersistentCloudKitContainer
- Dark Mode - System-following color scheme with semantic color tokens
Architecture
Clean Architecture + MVVM with three main layers:
Presentation (SwiftUI Views + ViewModels)
↓
Domain (Use Cases + Entities + Repository Protocols)
↓
Data (Repository Implementations + Data Sources)
Key Patterns
- Dependency Injection:
DIContainer(singleton) provides all dependencies viaLazyService<T>wrappers - Use Cases: Each user action is a separate use case class (e.g.,
SavePlantUseCase,HybridIdentificationUseCase) - Repository Pattern: Protocols in Domain layer, implementations in Data layer
- Actor-based Concurrency:
PlantClassificationService,NotificationService, and Core Data background tasks use actors - ViewModels:
@MainActor @Observableclasses, created viaDIContainer.makeXxxViewModel() - Core Data + CloudKit:
NSPersistentCloudKitContainerwith async store loading andwaitForStoreLoaded()pattern
Data Flow Example
CameraView → IdentificationViewModel → HybridIdentificationUseCase
→ PlantClassificationService (on-device ML)
→ PlantNetAPIService (online fallback)
→ IdentificationView (results)
Directory Structure
App/- Entry point, configuration, API keysCore/DI/- DIContainer and LazyServiceCore/DesignSystem/- Color tokens, animations, appearance managementCore/Services/- NotificationServiceDomain/- Business logic: Entities, UseCases, RepositoryInterfacesData/- Persistence: CoreData, Repositories, API services, MappersML/- Core ML inference service and image preprocessingPresentation/- SwiftUI views, ViewModels, navigationScenes/- Feature-specific views (Camera, Collection, Today, Settings, ProgressPhotos, Rooms)Common/- Shared components and modifiers
External Services
- PlantNet API (
my.plantnet.org) - Plant identification via image upload (500 free/day) - Trefle API (
trefle.io) - Botanical care data (400K+ species) - CloudKit - iCloud sync for plants, rooms, care tasks, and progress photos
API keys configured in App/Configuration/APIKeys.swift via environment variables.
ML Model
PlantNet-300K ResNet50 for on-device plant classification:
- Input: 224x224 RGB image with ImageNet normalization
- Output: 1,081 plant species probabilities
- Conversion scripts in
scripts/directory
Core Data Model
Entities (all CloudKit-compatible with optional attributes):
PlantMO- Plant records with identification dataRoomMO- User-created rooms/zonesCareScheduleMO- Care schedule configuration per plantCareTaskMO- Individual care tasks (watering, fertilizing, etc.)ProgressPhotoMO- Progress photos with thumbnailsIdentificationMO- Plant identification historyPlantCareInfoMO- Cached care information from APIs
Testing
- Unit tests in
PlantGuideTests/with mock implementations inMocks/ - Test fixtures available for
Plant,CareTask,PlantCareSchedule - Mock services:
MockPlantCollectionRepository,MockNetworkService, etc. - In-memory Core Data stack for test isolation:
CoreDataStack(inMemory: true)
UI Testing
UI tests live in PlantGuideUITests/ using a page-object foundation. See Docs/XCUITest-Authoring.md for the full guide.
Key patterns:
- Inherit
BaseUITestCase, notXCTestCase - Launch with
launchClean(),launchWithMockData(), orlaunchOffline() - Locate elements via
UITestID.*identifiers (mirrorsAccessibilityIdentifiersin app) - Navigate with screen objects:
TabBarScreen,CameraScreen,CollectionScreen,TodayScreen,SettingsScreen,PlantDetailScreen - Wait with
waitForExistence(timeout:),waitUntilHittable(),waitUntilGone()-- neversleep() - One assertion focus per test method
Claude GitHub App
Installation
- Go to github.com/apps/claude
- Click "Install" and select this repository
- Grant the requested permissions (read/write for code, issues, and pull requests)
- Authenticate with your Anthropic account when prompted
How It Works
- The app reads this
CLAUDE.mdfile for project context and contribution rules - Claude responds to
@claudementions in issues and PRs - No API key configuration needed - authentication is handled through the GitHub App integration
Triggering Claude
- Issues: Mention
@claudein an issue to request implementation help - PRs: Mention
@claudeto request code review or changes
Claude Contribution Rules
Scope
- Work ONLY on the issue explicitly assigned to you.
- One issue = one pull request.
- Do not refactor unrelated code.
- Do not change public APIs unless the issue explicitly says so.
Safety Rules
- Never auto-merge.
- Never force-push to main.
- Never delete code unless instructed.
- Preserve existing behavior unless tests say otherwise.
iOS Rules
- Respect Swift concurrency (MainActor, async/await).
- Do not introduce Combine unless already used.
- Prefer pure functions for new logic.
- No new dependencies without approval.
Output Expectations
Each PR must include:
- Clear summary of changes
- Files touched (with rationale)
- Risks and how to test