Files
PlantGuide/Docs/FEATURE_ROADMAP.md
Trey t be0d298d9f Add dark mode support and design system foundation
- 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>
2026-01-23 14:17:56 -06:00

21 KiB

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

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

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

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

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