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>
173 lines
7.1 KiB
Markdown
173 lines
7.1 KiB
Markdown
# CLAUDE.md
|
|
|
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
|
|
## Build & Test Commands
|
|
|
|
```bash
|
|
# 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 via `LazyService<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 @Observable` classes, created via `DIContainer.makeXxxViewModel()`
|
|
- **Core Data + CloudKit**: `NSPersistentCloudKitContainer` with async store loading and `waitForStoreLoaded()` pattern
|
|
|
|
### Data Flow Example
|
|
|
|
```
|
|
CameraView → IdentificationViewModel → HybridIdentificationUseCase
|
|
→ PlantClassificationService (on-device ML)
|
|
→ PlantNetAPIService (online fallback)
|
|
→ IdentificationView (results)
|
|
```
|
|
|
|
### Directory Structure
|
|
|
|
- `App/` - Entry point, configuration, API keys
|
|
- `Core/DI/` - DIContainer and LazyService
|
|
- `Core/DesignSystem/` - Color tokens, animations, appearance management
|
|
- `Core/Services/` - NotificationService
|
|
- `Domain/` - Business logic: Entities, UseCases, RepositoryInterfaces
|
|
- `Data/` - Persistence: CoreData, Repositories, API services, Mappers
|
|
- `ML/` - Core ML inference service and image preprocessing
|
|
- `Presentation/` - SwiftUI views, ViewModels, navigation
|
|
- `Scenes/` - 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 data
|
|
- `RoomMO` - User-created rooms/zones
|
|
- `CareScheduleMO` - Care schedule configuration per plant
|
|
- `CareTaskMO` - Individual care tasks (watering, fertilizing, etc.)
|
|
- `ProgressPhotoMO` - Progress photos with thumbnails
|
|
- `IdentificationMO` - Plant identification history
|
|
- `PlantCareInfoMO` - Cached care information from APIs
|
|
|
|
## Testing
|
|
|
|
- Unit tests in `PlantGuideTests/` with mock implementations in `Mocks/`
|
|
- 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](Docs/XCUITest-Authoring.md) for the full guide.
|
|
|
|
**Key patterns:**
|
|
- Inherit `BaseUITestCase`, not `XCTestCase`
|
|
- Launch with `launchClean()`, `launchWithMockData()`, or `launchOffline()`
|
|
- Locate elements via `UITestID.*` identifiers (mirrors `AccessibilityIdentifiers` in app)
|
|
- Navigate with screen objects: `TabBarScreen`, `CameraScreen`, `CollectionScreen`, `TodayScreen`, `SettingsScreen`, `PlantDetailScreen`
|
|
- Wait with `waitForExistence(timeout:)`, `waitUntilHittable()`, `waitUntilGone()` -- never `sleep()`
|
|
- One assertion focus per test method
|
|
|
|
## Claude GitHub App
|
|
|
|
### Installation
|
|
1. Go to [github.com/apps/claude](https://github.com/apps/claude)
|
|
2. Click "Install" and select this repository
|
|
3. Grant the requested permissions (read/write for code, issues, and pull requests)
|
|
4. Authenticate with your Anthropic account when prompted
|
|
|
|
### How It Works
|
|
- The app reads this `CLAUDE.md` file for project context and contribution rules
|
|
- Claude responds to `@claude` mentions in issues and PRs
|
|
- No API key configuration needed - authentication is handled through the GitHub App integration
|
|
|
|
### Triggering Claude
|
|
- **Issues**: Mention `@claude` in an issue to request implementation help
|
|
- **PRs**: Mention `@claude` to 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
|