- Create DesignSystem with ColorTokens, AppearanceManager, spacing, and typography - Add appearance picker in Settings (System/Light/Dark modes) - Replace hardcoded colors with design tokens in MainTabView and Enums+UI - Inject AppearanceManager via environment in PlantGuideApp - Add FEATURE_ROADMAP.md documenting 8 planned features Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
546 lines
21 KiB
Markdown
546 lines
21 KiB
Markdown
# PlantGuide Feature Roadmap
|
|
|
|
Implementation plan for 8 new features: Dark Mode, CloudKit Sync, Plant Rooms, Animations, Flexible Snoozing, Today View, Batch Actions, and Progress Photos.
|
|
|
|
---
|
|
|
|
## Requirements Summary
|
|
|
|
| Feature | Specification |
|
|
|---------|---------------|
|
|
| **Dark Mode** | System-following + manual toggle, new color token system |
|
|
| **CloudKit** | `NSPersistentCloudKitContainer`, fresh installs only |
|
|
| **Rooms** | User-creatable `Room` entity, defaults: Kitchen, Living Room, Bedroom, Bathroom, Office, Patio/Balcony, Other |
|
|
| **Snoozing** | 3 days, 1 week, 2 weeks, 1 month options |
|
|
| **Today View** | Replaces Care tab, shows overdue + today's tasks |
|
|
| **Batch Actions** | "Water all" for room or selection, logs per-plant |
|
|
| **Progress Photos** | Scheduled reminders, time-lapse with user-controllable speed slider |
|
|
| **Animations** | Subtle, defining—specified per context below |
|
|
|
|
**Tab structure after implementation:**
|
|
Camera | Browse | Collection | Today | Settings
|
|
|
|
---
|
|
|
|
## Implementation Order
|
|
|
|
| Phase | Feature | Effort | Notes |
|
|
|-------|---------|--------|-------|
|
|
| **1** | Dark Mode + Color Tokens | S | Foundation for all UI |
|
|
| **2** | CloudKit Sync | M | Must happen before new entities to avoid migration pain |
|
|
| **3** | Plant Rooms/Zones | M | New `Room` entity (synced), user-creatable with defaults |
|
|
| **4** | Subtle Animations | M | Design system completion |
|
|
| **5** | Flexible Snoozing | S | Extend existing CareTask system |
|
|
| **6** | Today View | M | Replace Care tab |
|
|
| **7** | Batch Actions | S | Builds on rooms |
|
|
| **8** | Progress Photos | L | New entity, reminders, time-lapse playback |
|
|
|
|
---
|
|
|
|
## Dependency Graph
|
|
|
|
```
|
|
Dark Mode (color tokens) ─────────────────┐
|
|
├──► All UI features depend on this
|
|
Subtle Animations ────────────────────────┘
|
|
|
|
Plant Rooms/Zones ──► Batch Actions ("Water all in Kitchen")
|
|
──► Today View (group by room)
|
|
|
|
Progress Photos ──► Independent (but benefits from animations)
|
|
|
|
Flexible Snoozing ──► Independent (extends existing system)
|
|
|
|
Today View ──► Requires rooms to be meaningful
|
|
──► Benefits from animations
|
|
```
|
|
|
|
---
|
|
|
|
## Phase 1: Dark Mode + Color Tokens
|
|
|
|
### Technical Approach
|
|
Create a centralized design system with semantic color tokens that respond to color scheme.
|
|
|
|
### Files to Create
|
|
| File | Purpose |
|
|
|------|---------|
|
|
| `Core/DesignSystem/ColorTokens.swift` | Semantic colors (background, surface, textPrimary, textSecondary, accent, destructive, etc.) |
|
|
| `Core/DesignSystem/AppearanceManager.swift` | `@Observable` class managing appearance preference (system/light/dark) |
|
|
| `Core/DesignSystem/DesignSystem.swift` | Namespace for tokens + spacing + typography |
|
|
|
|
### Files to Modify
|
|
| File | Change |
|
|
|------|--------|
|
|
| `PlantGuideApp.swift` | Inject `AppearanceManager`, apply `.preferredColorScheme()` |
|
|
| `MainTabView.swift` | Replace `.tint(.green)` with `DesignSystem.Colors.accent` |
|
|
| `SettingsView.swift` | Add appearance picker (System/Light/Dark) |
|
|
| All Views | Replace hardcoded colors with semantic tokens |
|
|
|
|
### Color Token Structure
|
|
```swift
|
|
enum Colors {
|
|
static let background = Color("Background") // Primary background
|
|
static let surface = Color("Surface") // Cards, sheets
|
|
static let textPrimary = Color("TextPrimary")
|
|
static let textSecondary = Color("TextSecondary")
|
|
static let accent = Color("Accent") // Green
|
|
static let destructive = Color("Destructive")
|
|
static let taskWatering = Color("TaskWatering")
|
|
static let taskFertilizing = Color("TaskFertilizing")
|
|
// ... etc
|
|
}
|
|
```
|
|
|
|
### Risks & Mitigations
|
|
| Risk | Mitigation |
|
|
|------|------------|
|
|
| Inconsistent adoption | Grep for hardcoded `Color(` and `.foregroundColor` after implementation |
|
|
| Asset catalog bloat | Use programmatic colors with `UIColor { traitCollection in }` |
|
|
|
|
### Tests
|
|
- Unit: `AppearanceManager` persists preference correctly
|
|
- UI: Screenshot tests for light/dark variants of key screens
|
|
|
|
---
|
|
|
|
## Phase 2: CloudKit Sync
|
|
|
|
### Technical Approach
|
|
Replace `NSPersistentContainer` with `NSPersistentCloudKitContainer`. Enable automatic sync.
|
|
|
|
### Files to Modify
|
|
| File | Change |
|
|
|------|--------|
|
|
| `CoreDataStack.swift` | Switch to `NSPersistentCloudKitContainer`, configure CloudKit container |
|
|
| `PlantGuideModel.xcdatamodeld` | Enable "Used with CloudKit" on configuration |
|
|
| `PlantGuide.entitlements` | Add CloudKit entitlement + iCloud container |
|
|
| `Info.plist` | Add `NSUbiquitousContainerIdentifier` if needed |
|
|
|
|
### Xcode Configuration
|
|
1. Enable CloudKit capability in Signing & Capabilities
|
|
2. Create CloudKit container: `iCloud.com.yourteam.PlantGuide`
|
|
3. Enable "CloudKit" checkbox on Core Data model configuration
|
|
|
|
### Schema Considerations
|
|
- All entities must have no unique constraints (CloudKit limitation)
|
|
- All relationships must have inverses
|
|
- No `Undefined` attribute types
|
|
- Optional attributes for any field that might be nil during sync
|
|
|
|
### Risks & Mitigations
|
|
| Risk | Mitigation |
|
|
|------|------------|
|
|
| Sync conflicts | CloudKit uses last-writer-wins; acceptable for this app |
|
|
| Large photos slow sync | Store photos in `CKAsset` (handled automatically by Core Data) |
|
|
| Offline-first gaps | `NSPersistentCloudKitContainer` handles this automatically |
|
|
| Rate limiting | Monitor CloudKit dashboard; unlikely for personal use |
|
|
|
|
### Tests
|
|
- Integration: Create plant on device A, verify appears on device B
|
|
- Unit: Mock `NSPersistentCloudKitContainer` for offline behavior
|
|
|
|
---
|
|
|
|
## Phase 3: Plant Rooms/Zones
|
|
|
|
### Technical Approach
|
|
New `Room` entity with one-to-many relationship to `Plant`. Replace `PlantLocation` enum usage.
|
|
|
|
### Files to Create
|
|
| File | Purpose |
|
|
|------|---------|
|
|
| `Domain/Entities/Room.swift` | `Room` struct (id, name, icon, sortOrder, isDefault) |
|
|
| `Domain/UseCases/Room/CreateDefaultRoomsUseCase.swift` | Creates 7 default rooms on first launch |
|
|
| `Domain/UseCases/Room/ManageRoomsUseCase.swift` | CRUD for rooms |
|
|
| `Domain/RepositoryInterfaces/RoomRepositoryProtocol.swift` | Repository protocol |
|
|
| `Data/Repositories/CoreDataRoomRepository.swift` | Core Data implementation |
|
|
| `Presentation/Scenes/Rooms/RoomsListView.swift` | Settings screen to manage rooms |
|
|
| `Presentation/Scenes/Rooms/RoomEditorView.swift` | Create/edit room |
|
|
| `Presentation/Scenes/Rooms/RoomsViewModel.swift` | ViewModel |
|
|
|
|
### Files to Modify
|
|
| File | Change |
|
|
|------|--------|
|
|
| `PlantGuideModel.xcdatamodeld` | Add `RoomMO` entity, relationship to `PlantMO` |
|
|
| `Plant.swift` | Change `location: PlantLocation?` to `roomID: UUID?` |
|
|
| `PlantMO` | Add `room` relationship |
|
|
| `CollectionView.swift` | Add room filter/grouping option |
|
|
| `PlantDetailView.swift` | Room picker instead of location enum |
|
|
| `SettingsView.swift` | Add "Manage Rooms" row |
|
|
| `DIContainer.swift` | Register room repository and use cases |
|
|
|
|
### Default Rooms
|
|
```swift
|
|
let defaults = [
|
|
Room(name: "Kitchen", icon: "refrigerator", sortOrder: 0, isDefault: true),
|
|
Room(name: "Living Room", icon: "sofa", sortOrder: 1, isDefault: true),
|
|
Room(name: "Bedroom", icon: "bed.double", sortOrder: 2, isDefault: true),
|
|
Room(name: "Bathroom", icon: "shower", sortOrder: 3, isDefault: true),
|
|
Room(name: "Office", icon: "desktopcomputer", sortOrder: 4, isDefault: true),
|
|
Room(name: "Patio/Balcony", icon: "sun.max", sortOrder: 5, isDefault: true),
|
|
Room(name: "Other", icon: "square.grid.2x2", sortOrder: 6, isDefault: true),
|
|
]
|
|
```
|
|
|
|
### Risks & Mitigations
|
|
| Risk | Mitigation |
|
|
|------|------------|
|
|
| Orphaned plants when room deleted | Cascade to "Other" room or prompt user |
|
|
| Migration from `PlantLocation` enum | Map existing enum values to default rooms in migration |
|
|
|
|
### Tests
|
|
- Unit: `CreateDefaultRoomsUseCase` creates exactly 7 rooms
|
|
- Unit: Room deletion cascades plants correctly
|
|
- UI: Room picker appears in plant detail
|
|
|
|
---
|
|
|
|
## Phase 4: Subtle Animations
|
|
|
|
### Technical Approach
|
|
Create reusable animation modifiers and view extensions. Apply consistently.
|
|
|
|
### Files to Create
|
|
| File | Purpose |
|
|
|------|---------|
|
|
| `Core/DesignSystem/Animations.swift` | Standard timing curves, durations |
|
|
| `Presentation/Common/Modifiers/FadeInModifier.swift` | Staggered fade-in for lists |
|
|
| `Presentation/Common/Modifiers/TaskCompleteModifier.swift` | Checkmark + confetti burst |
|
|
| `Presentation/Common/Modifiers/WateringFeedbackModifier.swift` | Water droplet animation |
|
|
| `Presentation/Common/Modifiers/PlantGrowthModifier.swift` | Subtle scale pulse |
|
|
| `Presentation/Common/Components/SuccessCheckmark.swift` | Animated checkmark component |
|
|
|
|
### Animation Specifications
|
|
|
|
| Context | Animation |
|
|
|---------|-----------|
|
|
| **Task complete** | Checkmark draws in (0.3s), row scales down + fades (0.2s), optional confetti particles |
|
|
| **Watering feedback** | 3 water droplets fall and fade (0.5s staggered) |
|
|
| **List appearance** | Staggered fade-in, 0.05s delay per item, max 10 items animated |
|
|
| **Tab switch** | Cross-fade (0.2s) |
|
|
| **Card press** | Scale to 0.97 on press, spring back |
|
|
| **Pull to refresh** | Plant icon rotates while loading |
|
|
| **Empty state** | Gentle float animation on illustration |
|
|
|
|
### Timing Standards
|
|
```swift
|
|
enum Animations {
|
|
static let quick = Animation.easeOut(duration: 0.15)
|
|
static let standard = Animation.easeInOut(duration: 0.25)
|
|
static let emphasis = Animation.spring(response: 0.4, dampingFraction: 0.7)
|
|
static let stagger = 0.05 // seconds between items
|
|
}
|
|
```
|
|
|
|
### Files to Modify
|
|
| File | Change |
|
|
|------|--------|
|
|
| `CareTaskRow.swift` | Add completion animation |
|
|
| `CollectionView.swift` | Staggered list animation |
|
|
| `MainTabView.swift` | Tab transition |
|
|
| All cards/buttons | Press feedback |
|
|
|
|
### Risks & Mitigations
|
|
| Risk | Mitigation |
|
|
|------|------------|
|
|
| Animation jank on older devices | Use `Animation.interactiveSpring` which degrades gracefully |
|
|
| Overdone animations | Keep durations under 0.5s, use subtle scales (0.95-1.05 range) |
|
|
| Accessibility | Respect `UIAccessibility.isReduceMotionEnabled` |
|
|
|
|
### Tests
|
|
- Unit: Animations respect reduce motion setting
|
|
- Manual: Test on oldest supported device (iPhone XR or similar)
|
|
|
|
---
|
|
|
|
## Phase 5: Flexible Snoozing
|
|
|
|
### Technical Approach
|
|
Extend existing snooze system with new intervals. Add snooze picker UI.
|
|
|
|
### Files to Create
|
|
| File | Purpose |
|
|
|------|---------|
|
|
| `Presentation/Common/Components/SnoozePickerView.swift` | Bottom sheet with snooze options |
|
|
|
|
### Files to Modify
|
|
| File | Change |
|
|
|------|--------|
|
|
| `CareTask.swift` | Add `snoozedUntil: Date?` property, `snoozeCount: Int` |
|
|
| `CareTaskRow.swift` | Replace swipe actions with snooze picker trigger |
|
|
| `CareScheduleViewModel.swift` | Update `snoozeTask()` to accept `SnoozeInterval` enum |
|
|
| `NotificationService.swift` | Reschedule notification on snooze |
|
|
| `CareTaskMO` | Add `snoozedUntil` and `snoozeCount` attributes |
|
|
|
|
### Snooze Options
|
|
```swift
|
|
enum SnoozeInterval: CaseIterable {
|
|
case threeDays
|
|
case oneWeek
|
|
case twoWeeks
|
|
case oneMonth
|
|
|
|
var days: Int {
|
|
switch self {
|
|
case .threeDays: return 3
|
|
case .oneWeek: return 7
|
|
case .twoWeeks: return 14
|
|
case .oneMonth: return 30
|
|
}
|
|
}
|
|
|
|
var label: String {
|
|
switch self {
|
|
case .threeDays: return "3 days"
|
|
case .oneWeek: return "1 week"
|
|
case .twoWeeks: return "2 weeks"
|
|
case .oneMonth: return "1 month"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### UI Behavior
|
|
- Swipe left on task → shows snooze picker (bottom sheet)
|
|
- Picker shows 4 options + "Custom date" option
|
|
- Snoozed tasks show "Snoozed until [date]" badge
|
|
- Snoozed tasks appear in "Snoozed" filter section
|
|
|
|
### Risks & Mitigations
|
|
| Risk | Mitigation |
|
|
|------|------------|
|
|
| Users abuse snooze | Track `snoozeCount`, optionally show "frequently snoozed" indicator |
|
|
| Notification sync | Cancel old notification, schedule new one in same transaction |
|
|
|
|
### Tests
|
|
- Unit: `snoozeTask(interval:)` calculates correct future date
|
|
- Unit: Snooze count increments
|
|
- UI: Snoozed task appears in correct section
|
|
|
|
---
|
|
|
|
## Phase 6: Today View
|
|
|
|
### Technical Approach
|
|
New view replacing Care tab. Dashboard with overdue + today sections, grouped by room.
|
|
|
|
### Files to Create
|
|
| File | Purpose |
|
|
|------|---------|
|
|
| `Presentation/Scenes/TodayView/TodayView.swift` | Main dashboard |
|
|
| `Presentation/Scenes/TodayView/TodayViewModel.swift` | ViewModel |
|
|
| `Presentation/Scenes/TodayView/Components/TaskSection.swift` | Overdue/Today section |
|
|
| `Presentation/Scenes/TodayView/Components/RoomTaskGroup.swift` | Tasks grouped by room |
|
|
| `Presentation/Scenes/TodayView/Components/QuickStatsBar.swift` | Today's progress (3/7 tasks done) |
|
|
|
|
### Files to Modify
|
|
| File | Change |
|
|
|------|--------|
|
|
| `MainTabView.swift` | Replace Care tab with Today tab |
|
|
| `DIContainer.swift` | Add `makeTodayViewModel()` |
|
|
|
|
### Today View Layout
|
|
```
|
|
┌─────────────────────────────────┐
|
|
│ Good morning! │
|
|
│ 3 of 7 tasks completed │
|
|
├─────────────────────────────────┤
|
|
│ OVERDUE (2) │
|
|
│ ┌─────────────────────────────┐ │
|
|
│ │ Monstera - Water (2d ago) │ │
|
|
│ │ Fern - Fertilize (1d ago) │ │
|
|
│ └─────────────────────────────┘ │
|
|
├─────────────────────────────────┤
|
|
│ TODAY (5) │
|
|
│ │
|
|
│ Kitchen (2) │
|
|
│ ┌─────────────────────────────┐ │
|
|
│ │ Pothos - Water │ │
|
|
│ │ Basil - Water │ │
|
|
│ └─────────────────────────────┘ │
|
|
│ │
|
|
│ Living Room (3) │
|
|
│ ┌─────────────────────────────┐ │
|
|
│ │ Snake Plant - Water │ │
|
|
│ │ Spider Plant - Fertilize │ │
|
|
│ │ Cactus - Water │ │
|
|
│ └─────────────────────────────┘ │
|
|
└─────────────────────────────────┘
|
|
```
|
|
|
|
### Risks & Mitigations
|
|
| Risk | Mitigation |
|
|
|------|------------|
|
|
| Empty state confusion | Show encouraging empty state: "All caught up!" |
|
|
| Performance with many tasks | Limit visible tasks, add "Show all" expansion |
|
|
|
|
### Tests
|
|
- Unit: Tasks correctly grouped by overdue/today/room
|
|
- UI: Empty state displays correctly
|
|
- UI: Task completion updates stats bar
|
|
|
|
---
|
|
|
|
## Phase 7: Batch Actions
|
|
|
|
### Technical Approach
|
|
Multi-select mode + batch action bar. Actions apply to selection or entire room.
|
|
|
|
### Files to Create
|
|
| File | Purpose |
|
|
|------|---------|
|
|
| `Presentation/Common/Components/BatchActionBar.swift` | Floating action bar |
|
|
| `Domain/UseCases/PlantCare/BatchCompleteTasksUseCase.swift` | Complete multiple tasks |
|
|
|
|
### Files to Modify
|
|
| File | Change |
|
|
|------|--------|
|
|
| `TodayView.swift` | Add edit mode, selection state |
|
|
| `TodayViewModel.swift` | `selectedTaskIDs: Set<UUID>`, batch action methods |
|
|
| `RoomTaskGroup.swift` | "Water all in [Room]" button |
|
|
| `CareScheduleRepositoryProtocol.swift` | Add `batchUpdate(taskIDs:completion:)` |
|
|
|
|
### UI Behavior
|
|
1. **Room-level batch**: Each room group has "Water all" button (only for watering tasks)
|
|
2. **Selection mode**: Long-press or Edit button enables multi-select
|
|
3. **Batch bar**: Appears at bottom when items selected: "Complete (5)" button
|
|
4. **Logging**: Each plant gets individual `CareTask` completion entry
|
|
|
|
### Batch Action Bar
|
|
```
|
|
┌─────────────────────────────────┐
|
|
│ 5 selected [Complete] [Cancel]│
|
|
└─────────────────────────────────┘
|
|
```
|
|
|
|
### Risks & Mitigations
|
|
| Risk | Mitigation |
|
|
|------|------------|
|
|
| Accidental batch complete | Require confirmation for >3 tasks |
|
|
| Mixed task types in selection | Only show "Complete" (generic), not task-specific action |
|
|
|
|
### Tests
|
|
- Unit: `BatchCompleteTasksUseCase` creates individual completion records
|
|
- UI: Selection count updates correctly
|
|
- UI: Confirmation appears for large batches
|
|
|
|
---
|
|
|
|
## Phase 8: Progress Photos
|
|
|
|
### Technical Approach
|
|
New `ProgressPhoto` entity linked to `Plant`. Scheduled reminders for capture. Time-lapse viewer with speed control.
|
|
|
|
### Files to Create
|
|
| File | Purpose |
|
|
|------|---------|
|
|
| `Domain/Entities/ProgressPhoto.swift` | Photo entity (id, plantID, imageData, dateTaken, notes) |
|
|
| `Domain/RepositoryInterfaces/ProgressPhotoRepositoryProtocol.swift` | Repository protocol |
|
|
| `Data/Repositories/CoreDataProgressPhotoRepository.swift` | Core Data implementation |
|
|
| `Domain/UseCases/Photos/CaptureProgressPhotoUseCase.swift` | Save photo with metadata |
|
|
| `Domain/UseCases/Photos/SchedulePhotoReminderUseCase.swift` | Weekly/monthly reminder |
|
|
| `Presentation/Scenes/ProgressPhotos/ProgressPhotoGalleryView.swift` | Grid of photos for plant |
|
|
| `Presentation/Scenes/ProgressPhotos/ProgressPhotoCaptureView.swift` | Camera capture screen |
|
|
| `Presentation/Scenes/ProgressPhotos/TimeLapsePlayerView.swift` | Time-lapse viewer |
|
|
| `Presentation/Scenes/ProgressPhotos/ProgressPhotosViewModel.swift` | ViewModel |
|
|
|
|
### Files to Modify
|
|
| File | Change |
|
|
|------|--------|
|
|
| `PlantGuideModel.xcdatamodeld` | Add `ProgressPhotoMO` entity |
|
|
| `PlantDetailView.swift` | Add "Progress Photos" section |
|
|
| `NotificationService.swift` | Add photo reminder notification type |
|
|
| `DIContainer.swift` | Register photo repositories and use cases |
|
|
|
|
### Core Data Entity
|
|
```
|
|
ProgressPhotoMO
|
|
├── id: UUID
|
|
├── plantID: UUID
|
|
├── imageData: Binary Data (External Storage)
|
|
├── thumbnailData: Binary Data
|
|
├── dateTaken: Date
|
|
├── notes: String?
|
|
└── plant: PlantMO (relationship)
|
|
```
|
|
|
|
### Time-Lapse Player
|
|
```
|
|
┌─────────────────────────────────┐
|
|
│ [Photo Display] │
|
|
│ │
|
|
│ ◀ ▶ Jan 15, 2026 │
|
|
│ │
|
|
│ Speed: [━━━━●━━━━━] 0.3s/frame │
|
|
│ │
|
|
│ [ Play ] │
|
|
└─────────────────────────────────┘
|
|
```
|
|
|
|
### Photo Reminder Options
|
|
- Weekly (same day each week)
|
|
- Bi-weekly
|
|
- Monthly (same date each month)
|
|
- Off
|
|
|
|
### Storage Strategy
|
|
- Full resolution stored as `Binary Data` with "Allows External Storage" enabled
|
|
- Thumbnail (200x200) stored inline for fast gallery loading
|
|
- CloudKit syncs via `CKAsset` automatically
|
|
|
|
### Risks & Mitigations
|
|
| Risk | Mitigation |
|
|
|------|------------|
|
|
| Storage bloat | Compress to HEIC, limit resolution to 2048px max dimension |
|
|
| CloudKit photo sync slow | Show sync indicator, allow offline viewing of local photos |
|
|
| Gallery performance | Load thumbnails only, full image on tap |
|
|
|
|
### Tests
|
|
- Unit: Photo saved with correct metadata
|
|
- Unit: Thumbnail generated at correct size
|
|
- UI: Time-lapse plays at correct speed
|
|
- Integration: Photo syncs to CloudKit
|
|
|
|
---
|
|
|
|
## Red Team Check
|
|
|
|
### Potential Issues Across All Phases
|
|
|
|
| Issue | Phase | Severity | Mitigation |
|
|
|-------|-------|----------|------------|
|
|
| CloudKit container misconfigured | 2 | Critical | Test on real device early, not just simulator |
|
|
| Color tokens not adopted everywhere | 1 | Medium | Add SwiftLint rule or grep check |
|
|
| Room deletion orphans data | 3 | High | Cascade to "Other" room, never delete "Other" |
|
|
| Animation performance on old devices | 4 | Medium | Test on iPhone XR/XS, use `interactiveSpring` |
|
|
| Snooze notifications don't update | 5 | High | Wrap in transaction: cancel + reschedule |
|
|
| Today view empty state confusing | 6 | Low | Design clear empty states early |
|
|
| Batch action logging wrong | 7 | Medium | Unit test verifies individual records |
|
|
| Photo storage exceeds iCloud quota | 8 | Medium | Warn user when approaching limit |
|
|
|
|
### Rollback Strategy
|
|
|
|
Each phase is independently deployable. If issues arise:
|
|
|
|
1. **Feature flags**: Wrap new features in `@AppStorage("feature_X_enabled")` flags
|
|
2. **Database migrations**: All Core Data changes are additive (new entities/attributes), never destructive
|
|
3. **CloudKit schema**: Once deployed to production, schema can only be extended, not modified
|
|
|
|
---
|
|
|
|
## Totals
|
|
|
|
| Phase | Feature | New Files | Modified Files | Effort |
|
|
|-------|---------|-----------|----------------|--------|
|
|
| 1 | Dark Mode | 3 | ~15 | S |
|
|
| 2 | CloudKit | 0 | 4 | M |
|
|
| 3 | Rooms | 8 | 7 | M |
|
|
| 4 | Animations | 6 | 5 | M |
|
|
| 5 | Snoozing | 1 | 5 | S |
|
|
| 6 | Today View | 5 | 2 | M |
|
|
| 7 | Batch Actions | 2 | 4 | S |
|
|
| 8 | Progress Photos | 9 | 4 | L |
|
|
|
|
**Total: ~34 new files, ~30 modified files**
|