Fix task stats consistency and improve residence card UI
- Compute task stats locally from kanban data for both summary card and residence cards - Filter out completed_tasks and cancelled_tasks columns from counts - Use startOfDay for accurate date comparisons (overdue, due this week, next 30 days) - Add parseDate helper to DateUtils - Make address tappable to open in Apple Maps - Remove navigation title from residences list - Update CLAUDE.md with Go backend references and DataManager architecture 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
129
CLAUDE.md
129
CLAUDE.md
@@ -10,12 +10,12 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
||||
|
||||
## 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).
|
||||
MyCrib (Casera) is a Kotlin Multiplatform Mobile (KMM) property management application with shared business logic and platform-specific UI implementations. The backend is a Go REST API with PostgreSQL (located in the sibling `myCribAPI-go` 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`)
|
||||
- **Backend**: Go REST API with PostgreSQL (separate directory at `../myCribAPI-go`)
|
||||
|
||||
## Build Commands
|
||||
|
||||
@@ -56,42 +56,64 @@ open iosApp/iosApp.xcodeproj
|
||||
|
||||
## Architecture
|
||||
|
||||
### Shared Kotlin Layer (`composeApp/src/commonMain/kotlin/com/example/mycrib/`)
|
||||
### Shared Kotlin Layer (`composeApp/src/commonMain/kotlin/com/example/casera/`)
|
||||
|
||||
**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)
|
||||
1. **DataManager** (`data/DataManager.kt`) - **Single Source of Truth**
|
||||
- Unified cache for ALL app data (auth, residences, tasks, lookups, etc.)
|
||||
- All data is exposed via `StateFlow` for reactive UI updates
|
||||
- Automatic cache timeout validation (1 hour default)
|
||||
- Persists data to disk for offline access
|
||||
- Platform-specific initialization (TokenManager, ThemeStorage, PersistenceManager)
|
||||
- O(1) lookup helpers: `getTaskPriority(id)`, `getTaskCategory(id)`, etc.
|
||||
|
||||
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
|
||||
2. **APILayer** (`network/APILayer.kt`) - **Single Entry Point for Network Calls**
|
||||
- Every API response immediately updates DataManager
|
||||
- All screens observe DataManager StateFlows directly
|
||||
- Handles cache-first reads with `forceRefresh` parameter
|
||||
- ETag-based conditional fetching for lookups (304 Not Modified support)
|
||||
- Guards against concurrent initialization/prefetch calls
|
||||
- Returns `ApiResult<T>` (Success/Error/Loading/Idle states)
|
||||
|
||||
3. **TokenStorage** (`storage/TokenStorage.kt`)
|
||||
- Platform-specific secure token storage
|
||||
- Android: EncryptedSharedPreferences
|
||||
- iOS: Keychain
|
||||
- All API calls automatically include token from TokenStorage
|
||||
3. **API Clients** (`network/*Api.kt`)
|
||||
- Domain-specific API clients: `ResidenceApi`, `TaskApi`, `ContractorApi`, etc.
|
||||
- Low-level HTTP calls using Ktor
|
||||
- Error parsing and response handling
|
||||
|
||||
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.
|
||||
4. **PersistenceManager** (`data/PersistenceManager.kt`)
|
||||
- Platform-specific disk persistence (expect/actual pattern)
|
||||
- Stores serialized JSON for offline access
|
||||
- Loads cached data on app startup
|
||||
|
||||
5. **Navigation** (`navigation/`)
|
||||
- Type-safe navigation using kotlinx.serialization
|
||||
- Routes defined as `@Serializable` data classes
|
||||
- Shared between Android Compose Navigation
|
||||
5. **ViewModels** (`viewmodel/`)
|
||||
- Thin wrappers that call APILayer methods
|
||||
- Expose loading/error states for UI feedback
|
||||
- ViewModels: `ResidenceViewModel`, `TaskViewModel`, `AuthViewModel`, etc.
|
||||
|
||||
**Data Flow:**
|
||||
```
|
||||
UI → ViewModel → APILayer → (Cache Check) → Network API → Update Cache → Return to ViewModel → UI observes StateFlow
|
||||
User Action → ViewModel → APILayer → API Client → Server Response
|
||||
↓
|
||||
DataManager Updated (cache + disk)
|
||||
↓
|
||||
All Screens React (StateFlow observers)
|
||||
```
|
||||
|
||||
**Cache Architecture:**
|
||||
```kotlin
|
||||
// DataManager exposes StateFlows that UI observes directly
|
||||
DataManager.residences: StateFlow<List<Residence>>
|
||||
DataManager.myResidences: StateFlow<MyResidencesResponse?>
|
||||
DataManager.allTasks: StateFlow<TaskColumnsResponse?>
|
||||
DataManager.taskCategories: StateFlow<List<TaskCategory>>
|
||||
|
||||
// Cache validation (1 hour timeout)
|
||||
DataManager.isCacheValid(DataManager.residencesCacheTime)
|
||||
|
||||
// O(1) lookups for IDs
|
||||
DataManager.getTaskPriority(task.priorityId) // Returns TaskPriority?
|
||||
DataManager.getTaskCategory(task.categoryId) // Returns TaskCategory?
|
||||
```
|
||||
|
||||
### iOS Layer (`iosApp/iosApp/`)
|
||||
@@ -855,7 +877,7 @@ fun ThemePickerDialog(
|
||||
|
||||
## Environment Configuration
|
||||
|
||||
**API Environment Toggle** (`composeApp/src/commonMain/kotlin/com/example/mycrib/network/ApiConfig.kt`):
|
||||
**API Environment Toggle** (`composeApp/src/commonMain/kotlin/com/example/casera/network/ApiConfig.kt`):
|
||||
|
||||
```kotlin
|
||||
val CURRENT_ENV = Environment.DEV // or Environment.LOCAL
|
||||
@@ -864,7 +886,7 @@ 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.**
|
||||
**Change this to switch between local Go backend and production server.**
|
||||
|
||||
## Common Development Patterns
|
||||
|
||||
@@ -953,20 +975,34 @@ Currently tests are minimal. When adding tests:
|
||||
|
||||
### 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.
|
||||
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-go` repository.
|
||||
|
||||
### Data Cache Initialization
|
||||
### DataManager Initialization
|
||||
|
||||
**Critical**: After user login, call `APILayer.initializeLookups()` to populate DataCache with reference data. Without this, dropdowns and pickers will be empty.
|
||||
**Critical**: DataManager must be initialized at app startup with platform-specific managers:
|
||||
|
||||
```kotlin
|
||||
// In Application.onCreate() or equivalent
|
||||
DataManager.initialize(
|
||||
tokenMgr = TokenManager(context),
|
||||
themeMgr = ThemeStorageManager(context),
|
||||
persistenceMgr = PersistenceManager(context)
|
||||
)
|
||||
```
|
||||
|
||||
After user login, call `APILayer.initializeLookups()` to populate DataManager with reference data. This uses ETag-based caching - if data hasn't changed, server returns 304 Not Modified.
|
||||
|
||||
```kotlin
|
||||
// After successful login
|
||||
val initResult = APILayer.initializeLookups()
|
||||
if (initResult is ApiResult.Success) {
|
||||
// Navigate to main screen
|
||||
// Now safe to navigate to main screen
|
||||
// Lookups are cached in DataManager and persisted to disk
|
||||
}
|
||||
```
|
||||
|
||||
Without this, dropdowns and pickers will be empty.
|
||||
|
||||
### iOS Build Issues
|
||||
|
||||
If iOS build fails with type mismatch errors:
|
||||
@@ -989,19 +1025,19 @@ Without `forceRefresh`, APILayer returns cached data.
|
||||
MyCribKMM/
|
||||
├── composeApp/
|
||||
│ └── src/
|
||||
│ ├── commonMain/kotlin/com/example/mycrib/
|
||||
│ │ ├── cache/ # DataCache
|
||||
│ │ ├── models/ # Shared data models
|
||||
│ │ ├── network/ # APILayer, API clients
|
||||
│ │ ├── repository/ # Additional data repositories
|
||||
│ │ ├── storage/ # TokenStorage
|
||||
│ ├── commonMain/kotlin/com/example/casera/
|
||||
│ │ ├── data/ # DataManager, PersistenceManager
|
||||
│ │ ├── models/ # Shared data models (kotlinx.serialization)
|
||||
│ │ ├── network/ # APILayer, *Api clients, ApiConfig
|
||||
│ │ ├── storage/ # TokenManager, ThemeStorageManager
|
||||
│ │ ├── util/ # DateUtils, helpers
|
||||
│ │ ├── ui/ # Compose UI (Android)
|
||||
│ │ │ ├── components/ # Reusable components
|
||||
│ │ │ ├── screens/ # Screen composables
|
||||
│ │ │ └── theme/ # Material theme
|
||||
│ │ │ └── theme/ # Material theme, ThemeManager
|
||||
│ │ ├── viewmodel/ # Shared ViewModels
|
||||
│ │ └── App.kt # Android navigation
|
||||
│ ├── androidMain/ # Android-specific code
|
||||
│ ├── androidMain/ # Android-specific (TokenManager, etc.)
|
||||
│ ├── iosMain/ # iOS-specific Kotlin code
|
||||
│ └── commonTest/ # Shared tests
|
||||
│
|
||||
@@ -1009,9 +1045,9 @@ MyCribKMM/
|
||||
│ ├── *ViewModel.swift # Swift wrappers for Kotlin VMs
|
||||
│ ├── *View.swift # SwiftUI screens
|
||||
│ ├── Components/ # Reusable SwiftUI components
|
||||
│ ├── Design/ # Design system (spacing, colors)
|
||||
│ ├── Design/ # Design system (DesignSystem.swift, OrganicDesign.swift)
|
||||
│ ├── Extensions/ # Swift extensions
|
||||
│ ├── Helpers/ # Utility helpers
|
||||
│ ├── Helpers/ # Utility helpers (DateUtils, etc.)
|
||||
│ ├── PushNotifications/ # APNs integration
|
||||
│ └── [Feature]/ # Feature-grouped files
|
||||
│ ├── Task/
|
||||
@@ -1024,6 +1060,5 @@ MyCribKMM/
|
||||
|
||||
## Related Repositories
|
||||
|
||||
- **Backend API**: `../myCribAPI` - Django REST Framework backend
|
||||
- **Load Testing**: `../myCribAPI/locust` - Locust load testing scripts
|
||||
- **Documentation**: `../myCribAPI/docs` - Server configuration guides
|
||||
- **Backend API**: `../myCribAPI-go` - Go REST API with PostgreSQL
|
||||
- **Documentation**: `../myCribAPI-go/docs` - Server configuration and API docs
|
||||
|
||||
Reference in New Issue
Block a user