Files
honeyDueKMP/CLAUDE.md
Trey t 74a474007b Add project documentation and test setup guides
Added comprehensive documentation for the KMM project structure, build
commands, and UI testing setup/troubleshooting.

Documentation added:
- CLAUDE.md: Complete KMM project guide for Claude Code with architecture,
  build commands, common tasks, and development patterns
- iosApp/UI_TESTS_*.md: UI testing strategy, implementation guides, summaries
- iosApp/XCUITEST_*.md: XCUITest implementation and debugging guides
- iosApp/TEST_FAILURES_ANALYSIS.md: Analysis of common test failures
- iosApp/ACCESSIBILITY_IDENTIFIERS_FIX.md: Guide for fixing accessibility issues
- iosApp/FIX_TEST_TARGET*.md: Guides for fixing test target configuration
- iosApp/fix_test_target.sh: Script to automate test target setup

The CLAUDE.md serves as the primary documentation for working with this
repository, providing quick access to build commands, architecture overview,
and common development tasks for both iOS and Android platforms.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-20 23:07:14 -06:00

305 lines
10 KiB
Markdown

# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
**⚠️ Important:** This is the KMM mobile client repository. For full-stack documentation covering both the mobile app and backend API, see the root CLAUDE.md at `../CLAUDE.md`.
## Important Guidelines
**⚠️ DO NOT auto-commit code changes.** Always ask the user before committing. Only create commits when the user explicitly requests it with commands like "commit this work" or "create a commit".
## Project Overview
MyCrib is a Kotlin Multiplatform Mobile (KMM) property management application with shared business logic and platform-specific UI implementations. The backend is a Django REST Framework API (located in the sibling `myCribAPI` directory).
**Tech Stack:**
- **Shared (Kotlin)**: Compose Multiplatform for Android, networking layer, ViewModels, models
- **iOS**: SwiftUI with Kotlin shared layer integration via SKIE
- **Backend**: Django REST Framework (separate repository at `../myCribAPI`)
## Build Commands
### Android
```bash
# Build debug APK
./gradlew :composeApp:assembleDebug
# Build release APK
./gradlew :composeApp:assembleRelease
# Run on connected device/emulator
./gradlew :composeApp:installDebug
```
### iOS
```bash
# Build from command line (use Xcode for best experience)
xcodebuild -project iosApp/iosApp.xcodeproj -scheme iosApp -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 17' build
# Or open in Xcode
open iosApp/iosApp.xcodeproj
```
### Desktop (JVM)
```bash
./gradlew :composeApp:run
```
### Web
```bash
# Wasm target (modern browsers)
./gradlew :composeApp:wasmJsBrowserDevelopmentRun
# JS target (older browser support)
./gradlew :composeApp:jsBrowserDevelopmentRun
```
## Architecture
### Shared Kotlin Layer (`composeApp/src/commonMain/kotlin/com/example/mycrib/`)
**Core Components:**
1. **APILayer** (`network/APILayer.kt`)
- **Single entry point for all API calls**
- Manages caching via DataCache
- Handles automatic cache updates on mutations (create/update/delete)
- Pattern: Cache-first reads with optional `forceRefresh` parameter
- Returns `ApiResult<T>` (Success/Error/Loading states)
2. **DataCache** (`cache/DataCache.kt`)
- In-memory cache for lookup data (residence types, task categories, priorities, statuses, etc.)
- Must be initialized via `APILayer.initializeLookups()` after login
- Stores `MutableState` objects that UI can observe directly
- Cleared on logout
3. **TokenStorage** (`storage/TokenStorage.kt`)
- Platform-specific secure token storage
- Android: EncryptedSharedPreferences
- iOS: Keychain
- All API calls automatically include token from TokenStorage
4. **ViewModels** (`viewmodel/`)
- Shared ViewModels expose StateFlow for UI observation
- Pattern: ViewModel calls APILayer → APILayer manages cache + network → ViewModel emits ApiResult states
- ViewModels: `ResidenceViewModel`, `TaskViewModel`, `AuthViewModel`, `ContractorViewModel`, etc.
5. **Navigation** (`navigation/`)
- Type-safe navigation using kotlinx.serialization
- Routes defined as `@Serializable` data classes
- Shared between Android Compose Navigation
**Data Flow:**
```
UI → ViewModel → APILayer → (Cache Check) → Network API → Update Cache → Return to ViewModel → UI observes StateFlow
```
### iOS Layer (`iosApp/iosApp/`)
**Integration Pattern:**
- SwiftUI views wrap Kotlin ViewModels via `@StateObject`
- iOS-specific ViewModels (Swift) wrap shared Kotlin ViewModels
- Pattern: `@Published var data` in Swift observes Kotlin `StateFlow` via async iteration
- Navigation uses SwiftUI `NavigationStack` with sheets for modals
**Key iOS Files:**
- `MainTabView.swift`: Tab-based navigation
- `*ViewModel.swift` (Swift): Wraps shared Kotlin ViewModels, exposes `@Published` properties
- `*View.swift`: SwiftUI screens
- Directory structure mirrors feature organization (Residence/, Task/, Contractor/, etc.)
**iOS ↔ Kotlin Bridge:**
```swift
// Swift ViewModel wraps Kotlin ViewModel
@StateObject private var viewModel = ResidenceViewModel() // Swift wrapper
// Inside: let sharedViewModel: ComposeApp.ResidenceViewModel // Kotlin
// Observe Kotlin StateFlow
Task {
for await state in sharedViewModel.residencesState {
await MainActor.run {
self.residences = (state as? ApiResultSuccess)?.data
}
}
}
```
### Android Layer
Android uses Compose UI directly from `composeApp` with shared ViewModels. Navigation via Jetpack Compose Navigation in `App.kt`.
## Environment Configuration
**API Environment Toggle** (`composeApp/src/commonMain/kotlin/com/example/mycrib/network/ApiConfig.kt`):
```kotlin
val CURRENT_ENV = Environment.DEV // or Environment.LOCAL
```
- `Environment.LOCAL`: Points to `http://10.0.2.2:8000/api` (Android emulator) or `http://127.0.0.1:8000/api` (iOS simulator)
- `Environment.DEV`: Points to `https://mycrib.treytartt.com/api`
**Change this to switch between local Django backend and production server.**
## Common Development Patterns
### Adding a New API Endpoint
1. Add API call to appropriate `*Api.kt` class in `network/` (e.g., `TaskApi.kt`)
2. Add method to `APILayer.kt` that manages caching (if applicable)
3. Add method to relevant ViewModel that calls APILayer
4. Update UI to observe the new StateFlow
### Handling Platform-Specific Code
Use `expect/actual` pattern:
```kotlin
// commonMain
expect fun platformSpecificFunction(): String
// androidMain
actual fun platformSpecificFunction(): String = "Android"
// iosMain
actual fun platformSpecificFunction(): String = "iOS"
```
### Type Conversions for iOS
Kotlin types bridge to Swift with special wrappers:
- `Double``KotlinDouble` (use `KotlinDouble(double:)` constructor)
- `Int``KotlinInt` (use `KotlinInt(int:)` constructor)
- `String` stays `String`
- Optional types: Kotlin nullable (`Type?`) becomes Swift optional (`Type?`)
**Example iOS form submission:**
```swift
// TextField uses String binding
@State private var estimatedCost: String = ""
// Convert to KotlinDouble for API
estimatedCost: estimatedCost.isEmpty ? nil : KotlinDouble(double: Double(estimatedCost) ?? 0.0)
```
### Refreshing Lists After Mutations
**iOS Pattern:**
```swift
.sheet(isPresented: $showingAddForm) {
AddFormView(
isPresented: $showingAddForm,
onSuccess: {
viewModel.loadData(forceRefresh: true)
}
)
}
```
**Android Pattern:**
```kotlin
// Use savedStateHandle to pass refresh flag between screens
navController.previousBackStackEntry?.savedStateHandle?.set("refresh", true)
navController.popBackStack()
// In destination composable
val shouldRefresh = backStackEntry.savedStateHandle.get<Boolean>("refresh") ?: false
LaunchedEffect(shouldRefresh) {
if (shouldRefresh) viewModel.loadData(forceRefresh = true)
}
```
## Testing
Currently tests are minimal. When adding tests:
- Android: Place in `composeApp/src/androidUnitTest/` or `composeApp/src/commonTest/`
- iOS: Use XCTest framework in Xcode project
## Key Dependencies
- Kotlin Multiplatform: 2.1.0
- Compose Multiplatform: 1.7.1
- Ktor Client: Network requests
- kotlinx.serialization: JSON serialization
- kotlinx.coroutines: Async operations
- SKIE: Kotlin ↔ Swift interop improvements
## Important Notes
### Committing Changes
When committing changes that span both iOS and Android, commit them together in the KMM repository. If backend changes are needed, commit separately in the `myCribAPI` repository.
### Data Cache Initialization
**Critical**: After user login, call `APILayer.initializeLookups()` to populate DataCache with reference data. Without this, dropdowns and pickers will be empty.
```kotlin
// After successful login
val initResult = APILayer.initializeLookups()
if (initResult is ApiResult.Success) {
// Navigate to main screen
}
```
### iOS Build Issues
If iOS build fails with type mismatch errors:
1. Check that cost fields (estimatedCost, actualCost, purchasePrice) use `KotlinDouble`, not `String`
2. Verify preview/mock data matches current model signatures
3. Clean build folder in Xcode (Cmd+Shift+K) and rebuild
### Force Refresh Pattern
Always use `forceRefresh: true` when data should be fresh:
- After creating/updating/deleting items
- On pull-to-refresh gestures
- When explicitly requested by user
Without `forceRefresh`, APILayer returns cached data.
## Project Structure Summary
```
MyCribKMM/
├── composeApp/
│ └── src/
│ ├── commonMain/kotlin/com/example/mycrib/
│ │ ├── cache/ # DataCache
│ │ ├── models/ # Shared data models
│ │ ├── network/ # APILayer, API clients
│ │ ├── repository/ # Additional data repositories
│ │ ├── storage/ # TokenStorage
│ │ ├── ui/ # Compose UI (Android)
│ │ │ ├── components/ # Reusable components
│ │ │ ├── screens/ # Screen composables
│ │ │ └── theme/ # Material theme
│ │ ├── viewmodel/ # Shared ViewModels
│ │ └── App.kt # Android navigation
│ ├── androidMain/ # Android-specific code
│ ├── iosMain/ # iOS-specific Kotlin code
│ └── commonTest/ # Shared tests
├── iosApp/iosApp/
│ ├── *ViewModel.swift # Swift wrappers for Kotlin VMs
│ ├── *View.swift # SwiftUI screens
│ ├── Components/ # Reusable SwiftUI components
│ ├── Design/ # Design system (spacing, colors)
│ ├── Extensions/ # Swift extensions
│ ├── Helpers/ # Utility helpers
│ ├── PushNotifications/ # APNs integration
│ └── [Feature]/ # Feature-grouped files
│ ├── Task/
│ ├── Residence/
│ ├── Contractor/
│ └── Documents/
└── gradle/ # Gradle wrapper and configs
```
## Related Repositories
- **Backend API**: `../myCribAPI` - Django REST Framework backend
- **Load Testing**: `../myCribAPI/locust` - Locust load testing scripts
- **Documentation**: `../myCribAPI/docs` - Server configuration guides