Explore the 96Blocks-iOS project in ~/Apps/iOS/ if it exists:
1. Check for CLAUDE.md documentation
2. Look at the project structure
3. This app is described as "Time blocking, ROI tracking" - understand how it works
4. Check SwiftUI patterns and iOS version

This may have relevant patterns for a goal/plan tracking app.


I'll explore the 96Blocks-iOS project to understand its structure, documentation, and patterns for goal/plan tracking.


Bash

List iOS apps directory to verify 96Blocks-iOS exists


total 64
drwxr-xr-x@ 32 neog  staff   1024 Dec 23 01:17 .
drwxr-xr-x@  5 neog  staff    160 Dec 21 21:47 ..
-rw-r--r--@  1 neog  staff  30724 Dec 21 22:38 .DS_Store
drwxr-xr-x@ 18 neog  staff    576 Dec 22 05:21 AI-KeywordMonitor
drwxr-xr-x@ 15 neog  staff    480 Dec 21 23:57 BIZ-BusinessPlan
drwxr-xr-x@  4 neog  staff    128 Dec 10 16:01 BIZ-NEOGBusiness
drwxr-xr-x@  6 neog  staff    192 Dec 15 15:48 BIZ-RevenueFlow30D
drwxr-xr-x@ 18 neog  staff    576 Dec 22 05:21 EDU-17Licoes
drwxr-xr-x@  8 neog  staff    256 Dec 21 23:41 EDU-BibliaEstudo
drwxr-xr-x@ 10 neog  staff    320 Dec 22 05:21 EDU-DailyVirtue
drwxr-xr-x@ 14 neog  staff    448 Dec 21 20:52 EMAIL-EmailAnalyzer
drwxr-xr-x@ 24 neog  staff    768 Dec 21 22:56 EMAIL-GmailAI
drwxr-xr-x@ 19 neog  staff    608 Dec 21 23:27 FIN-MeusFIIs
drwxr-xr-x@  7 neog  staff    224 Dec 18 12:53 LOC-AroundMe
drwxr-xr-x@  6 neog  staff    192 Dec 20 23:10 LOC-CityExplorer
drwxr-xr-x@ 12 neog  staff    384 Dec 18 22:51 MEDIA-ObsidianViewer
drwxr-xr-x@ 12 neog  staff    384 Dec 21 16:06 MEDIA-YouTubeAudio
drwxr-xr-x@  6 neog  staff    192 Dec 13 12:56 MEDIA-YouTubeTranscripts
drwxr-xr-x@ 18 neog  staff    576 Dec 18 16:25 PROD-96Blocks


No files found


Good! The directory exists as `PROD-96Blocks`. Let me explore it further:


List contents of PROD-96Blocks directory


Error: EISDIR: illegal operation on a directory, read


total 120
drwxr-xr-x@ 18 neog  staff    576 Dec 18 16:25 .
drwxr-xr-x@ 32 neog  staff   1024 Dec 23 01:17 ..
-rw-r--r--@  1 neog  staff   6148 Dec 15 19:02 .DS_Store
-rw-r--r--   1 neog  staff    770 Dec  8 22:15 96Blocks-iOS.entitlements
drwxr-xr-x@  6 neog  staff    192 Dec 18 15:16 96Blocks-iOS.xcodeproj
drwxr-xr-x@  4 neog  staff    128 Dec 18 15:50 96BlocksWidget
drwxr-xr-x@  4 neog  staff    128 Dec 18 16:26 App
drwxr-xr-x@  5 neog  staff    160 Dec 18 15:14 Assets.xcassets
-rw-------@  1 neog  staff   4427 Dec  8 18:35 CLAUDE.md
drwxr-xr-x@  4 neog  staff    128 Dec  8 20:34 Components
drwxr-xr-x@  2 neog  staff     64 Dec  8 18:28 Extensions
-rw-r--r--@  1 neog  staff   1293 Dec 18 16:25 Info.plist
drwxr-xr-x@  8 neog  staff    256 Dec 10 14:56 Models
drwxr-xr-x@ 11 neog  staff    352 Dec 18 16:33 Services
drwxr-xr-x@  7 neog  staff    224 Dec  8 20:06 Views
-rw-r--r--@  1 neog  staff  15298 Dec  8 21:18 gen_project.rb
-rw-------@  1 neog  staff  13538 Dec  8 18:35 generate-xcode-project.rb
-rw-------@  1 neog  staff   1545 Dec  8 22:03 project.yml


Let me read the CLAUDE.md file and explore the structure:


find ~/Apps/iOS/PROD-96Blocks -name "*.swift" | head -20

Find Swift files to understand project structure


List Models directory


     1→# CLAUDE.md - 96Blocks-iOS
     2→
     3→## Project Overview
     4→
     5→**96Blocks-iOS** is a B2B SaaS iOS application for day planning with ROI tracking. The core concept divides each day into 96 blocks of 15 minutes each, allowing users to plan, track, and measure the monetary value of their time.
     6→
     7→### Key Features
     8→
     9→- **96-Block Timeline**: Vertical timeline showing all 96 blocks of the day
    10→- **ROI Tracking**: Estimated and actual monetary value (R$) per block
    11→- **Task Backlog**: Task management with priority, category, and time estimates
    12→- **Goals**: High-level objectives with progress tracking
    13→- **Templates**: Pre-defined day schedules (Workday, Weekend, Focus Day)
    14→- **Analytics**: Productivity score, ROI efficiency, category breakdown
    15→
    16→## Tech Stack
    17→
    18→- **UI**: SwiftUI (iOS 17+)
    19→- **Data**: SwiftData
    20→- **Architecture**: MVVM with @MainActor
    21→- **Platforms**: iPhone, iPad, Mac Catalyst
    22→- **Dependencies**: None (pure Apple frameworks)
    23→
    24→## Project Structure
    25→
    26→```
    27→96Blocks-iOS/
    28→├── App/
    29→│   ├── 96BlocksApp.swift         # @main entry point
    30→│   └── MainTabView.swift         # Adaptive navigation
    31→├── Models/
    32→│   ├── Block.swift               # 15-min block model
    33→│   ├── Task.swift                # Backlog task model
    34→│   ├── Goal.swift                # Goal tracking
    35→│   ├── Template.swift            # Day templates
    36→│   └── DailySummary.swift        # Analytics summary
    37→├── Services/
    38→│   └── DataManager.swift         # SwiftData manager
    39→├── Views/
    40→│   ├── DayPlanner/
    41→│   │   ├── DayPlannerView.swift  # Main timeline view
    42→│   │   └── BlockRow.swift        # Individual block cell
    43→│   ├── Backlog/
    44→│   │   └── BacklogView.swift     # Task management
    45→│   ├── Analytics/
    46→│   │   └── AnalyticsView.swift   # ROI dashboard
    47→│   └── Settings/
    48→│       └── SettingsView.swift    # Configuration
    49→├── Components/
    50→│   └── HapticManager.swift       # Haptic feedback
    51→└── Extensions/
    52→```
    53→
    54→## Data Models
    55→
    56→### Block (Core)
    57→- `blockIndex`: 0-95 (00:00 to 23:45)
    58→- `title`: Task description
    59→- `category`: work, personal, health, learning, meeting, focus, break, sleep
    60→- `estimatedValueBRL`: Expected ROI in R$
    61→- `actualValueBRL`: Real ROI achieved
    62→- `status`: planned, inProgress, completed, skipped
    63→
    64→### Task96 (Backlog)
    65→- `priority`: low, medium, high, urgent
    66→- `estimatedBlocks`: Number of 15-min blocks needed
    67→- `estimatedValueBRL`: Expected value
    68→- `goalId`: Link to parent goal
    69→
    70→### Goal
    71→- `targetValueBRL`: Target monetary value
    72→- `timeframe`: weekly, monthly, quarterly, yearly
    73→- `valueGeneratedBRL`: Progress tracking
    74→
    75→## Quick Commands
    76→
    77→```bash
    78→# Generate Xcode project
    79→cd ~/Apps/iOS/96Blocks-iOS
    80→ruby generate-xcode-project.rb
    81→
    82→# Open project
    83→open 96Blocks-iOS.xcodeproj
    84→
    85→# Build for simulator
    86→xcodebuild -project 96Blocks-iOS.xcodeproj \
    87→  -scheme 96Blocks-iOS \
    88→  -sdk iphonesimulator \
    89→  -destination 'platform=iOS Simulator,name=iPhone 17 Pro'
    90→```
    91→
    92→## Key Patterns
    93→
    94→### DataManager (Singleton)
    95→```swift
    96→@MainActor
    97→class DataManager: ObservableObject {
    98→    static let shared = DataManager()
    99→    let modelContainer: ModelContainer
   100→
   101→    func getBlocks(for date: Date) -> [Block]
   102→    func getTodayROI() -> (estimated: Decimal, actual: Decimal)
   103→    func getProductivityScore() -> Int
   104→}
   105→```
   106→
   107→### Block Time Calculation
   108→```swift
   109→// Block index to time
   110→let hour = blockIndex / 4        // 0-23
   111→let minute = (blockIndex % 4) * 15  // 0, 15, 30, 45
   112→
   113→// Current block
   114→let currentIndex = hour * 4 + minute / 15
   115→```
   116→
   117→### ROI Calculation
   118→```swift
   119→let efficiency = actual / estimated * 100  // Percentage
   120→let score = (completionRate * 0.4) + (roiEfficiency * 0.4) + (taskScore * 0.2)
   121→```
   122→
   123→## Future Enhancements (Phase 2+)
   124→
   125→1. **AI Integration**: Claude API for automatic day planning
   126→2. **Backend API**: FastAPI for multi-tenant B2B
   127→3. **Team Features**: Dashboard admin, team analytics
   128→4. **Web Dashboard**: Next.js for desktop access
   129→5. **Notifications**: Block reminders, daily summary
   130→6. **Widgets**: Home screen productivity widget
   131→
   132→## Related Documentation
   133→
   134→- **Business Plan**: `~/.claude/plans/buzzing-painting-hoare.md`
   135→- **ProductivityHub Reference**: `~/Apps/iOS/ProductivityHub-iOS/CLAUDE.md`
   136→- **MeusFIIs AI Pattern**: `~/Apps/iOS/MeusFIIs-iOS/Services/AI/`
   137→
   138→---
   139→
   140→**Created**: 2025-12-08
   141→**Version**: 1.0.0 (MVP)
   142→**Status**: Ready for Development
   143→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

# CLAUDE.md - 96Blocks-iOS

## Project Overview

**96Blocks-iOS** is a B2B SaaS iOS application for day planning with ROI tracking. The core concept divides each day into 96 blocks of 15 minutes each, allowing users to plan, track, and measure the monetary value of their time.

### Key Features

- **96-Block Timeline**: Vertical timeline showing all 96 blocks of the day
- **ROI Tracking**: Estimated and actual monetary value (R$) per block
- **Task Backlog**: Task management with priority, category, and time estimates
- **Goals**: High-level objectives with progress tracking
- **Templates**: Pre-defined day schedules (Workday, Weekend, Focus Day)
- **Analytics**: Productivity score, ROI efficiency, category breakdown

## Tech Stack

- **UI**: SwiftUI (iOS 17+)
- **Data**: SwiftData
- **Architecture**: MVVM with @MainActor
- **Platforms**: iPhone, iPad, Mac Catalyst
- **Dependencies**: None (pure Apple frameworks)

## Project Structure

```
96Blocks-iOS/
├── App/
│   ├── 96BlocksApp.swift         # @main entry point
│   └── MainTabView.swift         # Adaptive navigation
├── Models/
│   ├── Block.swift               # 15-min block model
│   ├── Task.swift                # Backlog task model
│   ├── Goal.swift                # Goal tracking
│   ├── Template.swift            # Day templates
│   └── DailySummary.swift        # Analytics summary
├── Services/
│   └── DataManager.swift         # SwiftData manager
├── Views/
│   ├── DayPlanner/
│   │   ├── DayPlannerView.swift  # Main timeline view
│   │   └── BlockRow.swift        # Individual block cell
│   ├── Backlog/
│   │   └── BacklogView.swift     # Task management
│   ├── Analytics/
│   │   └── AnalyticsView.swift   # ROI dashboard
│   └── Settings/
│       └── SettingsView.swift    # Configuration
├── Components/
│   └── HapticManager.swift       # Haptic feedback
└── Extensions/
```

## Data Models

### Block (Core)
- `blockIndex`: 0-95 (00:00 to 23:45)
- `title`: Task description
- `category`: work, personal, health, learning, meeting, focus, break, sleep
- `estimatedValueBRL`: Expected ROI in R$
- `actualValueBRL`: Real ROI achieved
- `status`: planned, inProgress, completed, skipped

### Task96 (Backlog)
- `priority`: low, medium, high, urgent
- `estimatedBlocks`: Number of 15-min blocks needed
- `estimatedValueBRL`: Expected value
- `goalId`: Link to parent goal

### Goal
- `targetValueBRL`: Target monetary value
- `timeframe`: weekly, monthly, quarterly, yearly
- `valueGeneratedBRL`: Progress tracking

## Quick Commands

```bash
# Generate Xcode project
cd ~/Apps/iOS/96Blocks-iOS
ruby generate-xcode-project.rb

# Open project
open 96Blocks-iOS.xcodeproj

# Build for simulator
xcodebuild -project 96Blocks-iOS.xcodeproj \
  -scheme 96Blocks-iOS \
  -sdk iphonesimulator \
  -destination 'platform=iOS Simulator,name=iPhone 17 Pro'
```

## Key Patterns

### DataManager (Singleton)
```swift
@MainActor
class DataManager: ObservableObject {
    static let shared = DataManager()
    let modelContainer: ModelContainer

    func getBlocks(for date: Date) -> [Block]
    func getTodayROI() -> (estimated: Decimal, actual: Decimal)
    func getProductivityScore() -> Int
}
```

### Block Time Calculation
```swift
// Block index to time
let hour = blockIndex / 4        // 0-23
let minute = (blockIndex % 4) * 15  // 0, 15, 30, 45

// Current block
let currentIndex = hour * 4 + minute / 15
```

### ROI Calculation
```swift
let efficiency = actual / estimated * 100  // Percentage
let score = (completionRate * 0.4) + (roiEfficiency * 0.4) + (taskScore * 0.2)
```

## Future Enhancements (Phase 2+)

1. **AI Integration**: Claude API for automatic day planning
2. **Backend API**: FastAPI for multi-tenant B2B
3. **Team Features**: Dashboard admin, team analytics
4. **Web Dashboard**: Next.js for desktop access
5. **Notifications**: Block reminders, daily summary
6. **Widgets**: Home screen productivity widget

## Related Documentation

- **Business Plan**: `~/.claude/plans/buzzing-painting-hoare.md`
- **ProductivityHub Reference**: `~/Apps/iOS/ProductivityHub-iOS/CLAUDE.md`
- **MeusFIIs AI Pattern**: `~/Apps/iOS/MeusFIIs-iOS/Services/AI/`

---

**Created**: 2025-12-08
**Version**: 1.0.0 (MVP)
**Status**: Ready for Development


total 112
drwxr-xr-x@  8 neog  staff    256 Dec 10 14:56 .
drwxr-xr-x@ 18 neog  staff    576 Dec 18 16:25 ..
-rw-------@  1 neog  staff   8406 Dec 10 14:56 Block.swift
-rw-------@  1 neog  staff   5840 Dec  8 21:22 BlockStep.swift
-rw-------@  1 neog  staff   4330 Dec  8 18:30 DailySummary.swift
-rw-------@  1 neog  staff   3762 Dec  8 22:10 Goal.swift
-rw-------@  1 neog  staff   5386 Dec  8 22:10 Task.swift
-rw-------@  1 neog  staff  13001 Dec  8 22:10 Template.swift


Now let me examine the key data models to understand the patterns:


     1→//
     2→//  Block.swift
     3→//  96Blocks-iOS
     4→//
     5→//  Core model representing a 15-minute block in the day
     6→//  96 blocks total (24 hours × 4 blocks per hour)
     7→//
     8→
     9→import Foundation
    10→import SwiftData
    11→import SwiftUI
    12→
    13→@Model
    14→final class Block {
    15→    var id: UUID = UUID()
    16→    var blockDate: Date = Date()
    17→    var blockIndex: Int = 0  // 0-95 (00:00 to 23:45)
    18→    var title: String = ""
    19→    var blockDescription: String = ""
    20→    var categoryRaw: String = BlockCategory.other.rawValue
    21→    var estimatedValueBRL: Decimal = 0
    22→    var actualValueBRL: Decimal? = nil
    23→    var statusRaw: String = BlockStatus.planned.rawValue
    24→    var isAISuggested: Bool = false
    25→    var startedAt: Date? = nil
    26→    var completedAt: Date? = nil
    27→    var createdAt: Date = Date()
    28→
    29→    // Relationship to Task (optional)
    30→    var taskId: UUID? = nil
    31→
    32→    // Flag indicating if block has steps/subtasks
    33→    var hasSteps: Bool = false
    34→
    35→    // Computed wrappers for enums
    36→    @Transient var category: BlockCategory {
    37→        get { BlockCategory(rawValue: categoryRaw) ?? .other }
    38→        set { categoryRaw = newValue.rawValue }
    39→    }
    40→
    41→    @Transient var status: BlockStatus {
    42→        get { BlockStatus(rawValue: statusRaw) ?? .planned }
    43→        set { statusRaw = newValue.rawValue }
    44→    }
    45→
    46→    /// Valid range for block index (0-95 representing 00:00 to 23:45)
    47→    static let validBlockIndexRange = 0...95
    48→
    49→    init(
    50→        id: UUID = UUID(),
    51→        blockDate: Date = Date(),
    52→        blockIndex: Int = 0,
    53→        title: String = "",
    54→        blockDescription: String = "",
    55→        category: BlockCategory = .other,
    56→        estimatedValueBRL: Decimal = 0,
    57→        actualValueBRL: Decimal? = nil,
    58→        status: BlockStatus = .planned,
    59→        isAISuggested: Bool = false,
    60→        startedAt: Date? = nil,
    61→        completedAt: Date? = nil,
    62→        taskId: UUID? = nil
    63→    ) {
    64→        self.id = id
    65→        self.blockDate = blockDate
    66→        // Validate blockIndex is in valid range (0-95)
    67→        self.blockIndex = Self.validBlockIndexRange.contains(blockIndex) ? blockIndex : max(0, min(95, blockIndex))
    68→        self.title = title.isEmpty ? "" : title.trimmingCharacters(in: .whitespacesAndNewlines)
    69→        self.blockDescription = blockDescription
    70→        self.categoryRaw = category.rawValue
    71→        self.estimatedValueBRL = estimatedValueBRL
    72→        self.actualValueBRL = actualValueBRL
    73→        self.statusRaw = status.rawValue
    74→        self.isAISuggested = isAISuggested
    75→        self.startedAt = startedAt
    76→        self.completedAt = completedAt
    77→        self.createdAt = Date()
    78→        self.taskId = taskId
    79→
    80→        #if DEBUG
    81→        if !Self.validBlockIndexRange.contains(blockIndex) {
    82→            print("⚠ Block: Invalid blockIndex \(blockIndex) clamped to valid range 0-95")
    83→        }
    84→        #endif
    85→    }
    86→
    87→    // MARK: - Computed Properties
    88→
    89→    /// Returns the time string for this block (e.g., "09:15")
    90→    var timeString: String {
    91→        let hour = blockIndex / 4
    92→        let minute = (blockIndex % 4) * 15
    93→        return String(format: "%02d:%02d", hour, minute)
    94→    }
    95→
    96→    /// Returns the end time string for this block (e.g., "09:30")
    97→    var endTimeString: String {
    98→        let nextIndex = blockIndex + 1
    99→        let hour = nextIndex / 4
   100→        let minute = (nextIndex % 4) * 15
   101→        return String(format: "%02d:%02d", hour, minute)
   102→    }
   103→
   104→    /// Returns the time range string (e.g., "09:15 - 09:30")
   105→    var timeRangeString: String {
   106→        "\(timeString) - \(endTimeString)"
   107→    }
   108→
   109→    /// Check if block is currently active (current time is within block)
   110→    var isCurrentBlock: Bool {
   111→        let calendar = Calendar.current
   112→        guard calendar.isDateInToday(blockDate) else { return false }
   113→
   114→        let now = Date()
   115→        let hour = calendar.component(.hour, from: now)
   116→        let minute = calendar.component(.minute, from: now)
   117→        let currentBlockIndex = hour * 4 + minute / 15
   118→
   119→        return currentBlockIndex == blockIndex
   120→    }
   121→
   122→    /// Check if block is in the past
   123→    var isPast: Bool {
   124→        let calendar = Calendar.current
   125→
   126→        if !calendar.isDateInToday(blockDate) {
   127→            return blockDate < Date()
   128→        }
   129→
   130→        let now = Date()
   131→        let hour = calendar.component(.hour, from: now)
   132→        let minute = calendar.component(.minute, from: now)
   133→        let currentBlockIndex = hour * 4 + minute / 15
   134→
   135→        return blockIndex < currentBlockIndex
   136→    }
   137→
   138→    /// Duration in seconds (always 15 minutes = 900 seconds)
   139→    var durationSeconds: TimeInterval {
   140→        return 15 * 60
   141→    }
   142→
   143→    /// Actual duration spent (if started)
   144→    var actualDurationSeconds: TimeInterval? {
   145→        guard let started = startedAt else { return nil }
   146→        let end = completedAt ?? Date()
   147→        return end.timeIntervalSince(started)
   148→    }
   149→
   150→    /// ROI efficiency (actual value / estimated value)
   151→    var roiEfficiency: Double? {
   152→        guard let actual = actualValueBRL, estimatedValueBRL > 0 else { return nil }
   153→        return Double(truncating: actual as NSNumber) / Double(truncating: estimatedValueBRL as NSNumber)
   154→    }
   155→}
   156→
   157→// MARK: - Block Category
   158→enum BlockCategory: String, Codable, CaseIterable {
   159→    case work = "Trabalho"
   160→    case personal = "Pessoal"
   161→    case health = "Saude"
   162→    case learning = "Aprendizado"
   163→    case meeting = "Reuniao"
   164→    case focus = "Foco Profundo"
   165→    case breakTime = "Intervalo"
   166→    case sleep = "Sono"
   167→    case other = "Outro"
   168→
   169→    var icon: String {
   170→        switch self {
   171→        case .work: return "briefcase.fill"
   172→        case .personal: return "person.fill"
   173→        case .health: return "heart.fill"
   174→        case .learning: return "book.fill"
   175→        case .meeting: return "person.2.fill"
   176→        case .focus: return "brain.head.profile"
   177→        case .breakTime: return "cup.and.saucer.fill"
   178→        case .sleep: return "moon.fill"
   179→        case .other: return "folder.fill"
   180→        }
   181→    }
   182→
   183→    // Light mode colors
   184→    var colorLight: String {
   185→        switch self {
   186→        case .work: return "#007AFF"       // Blue
   187→        case .personal: return "#AF52DE"   // Purple
   188→        case .health: return "#FF2D55"     // Pink
   189→        case .learning: return "#5AC8FA"   // Teal
   190→        case .meeting: return "#FF9500"    // Orange
   191→        case .focus: return "#34C759"      // Green
   192→        case .breakTime: return "#8E8E93"  // Gray
   193→        case .sleep: return "#5856D6"      // Indigo
   194→        case .other: return "#AEAEB2"      // Light Gray
   195→        }
   196→    }
   197→
   198→    // Dark mode colors (brighter for visibility)
   199→    var colorDark: String {
   200→        switch self {
   201→        case .work: return "#0A84FF"       // Brighter blue
   202→        case .personal: return "#BF5AF2"   // Brighter purple
   203→        case .health: return "#FF375F"     // Brighter pink
   204→        case .learning: return "#64D2FF"   // Brighter teal
   205→        case .meeting: return "#FF9F0A"    // Brighter orange
   206→        case .focus: return "#30D158"      // Brighter green
   207→        case .breakTime: return "#98989D"  // Lighter gray
   208→        case .sleep: return "#5E5CE6"      // Brighter indigo
   209→        case .other: return "#C7C7CC"      // Lighter gray
   210→        }
   211→    }
   212→
   213→    // Adaptive color based on color scheme
   214→    func adaptiveColor(for scheme: ColorScheme) -> Color {
   215→        let hex = scheme == .dark ? colorDark : colorLight
   216→        return Color(hex: hex) ?? .blue
   217→    }
   218→
   219→    // Legacy: Keep for compatibility (uses light mode)
   220→    var color: String { colorLight }
   221→}
   222→
   223→// MARK: - Block Status
   224→enum BlockStatus: String, Codable, CaseIterable {
   225→    case planned = "Planejado"
   226→    case inProgress = "Em Andamento"
   227→    case completed = "Concluido"
   228→    case skipped = "Pulado"
   229→
   230→    var icon: String {
   231→        switch self {
   232→        case .planned: return "circle"
   233→        case .inProgress: return "play.circle.fill"
   234→        case .completed: return "checkmark.circle.fill"
   235→        case .skipped: return "forward.fill"
   236→        }
   237→    }
   238→}
   239→
   240→// MARK: - Block Extensions
   241→extension Block {
   242→    /// Create 96 empty blocks for a given date
   243→    static func createDayBlocks(for date: Date) -> [Block] {
   244→        (0..<96).map { index in
   245→            Block(blockDate: date, blockIndex: index)
   246→        }
   247→    }
   248→
   249→    /// Get block index for current time
   250→    static func currentBlockIndex() -> Int {
   251→        let calendar = Calendar.current
   252→        let now = Date()
   253→        let hour = calendar.component(.hour, from: now)
   254→        let minute = calendar.component(.minute, from: now)
   255→        return hour * 4 + minute / 15
   256→    }
   257→}
   258→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

//
//  Block.swift
//  96Blocks-iOS
//
//  Core model representing a 15-minute block in the day
//  96 blocks total (24 hours × 4 blocks per hour)
//

import Foundation
import SwiftData
import SwiftUI

@Model
final class Block {
    var id: UUID = UUID()
    var blockDate: Date = Date()
    var blockIndex: Int = 0  // 0-95 (00:00 to 23:45)
    var title: String = ""
    var blockDescription: String = ""
    var categoryRaw: String = BlockCategory.other.rawValue
    var estimatedValueBRL: Decimal = 0
    var actualValueBRL: Decimal? = nil
    var statusRaw: String = BlockStatus.planned.rawValue
    var isAISuggested: Bool = false
    var startedAt: Date? = nil
    var completedAt: Date? = nil
    var createdAt: Date = Date()

    // Relationship to Task (optional)
    var taskId: UUID? = nil

    // Flag indicating if block has steps/subtasks
    var hasSteps: Bool = false

    // Computed wrappers for enums
    @Transient var category: BlockCategory {
        get { BlockCategory(rawValue: categoryRaw) ?? .other }
        set { categoryRaw = newValue.rawValue }
    }

    @Transient var status: BlockStatus {
        get { BlockStatus(rawValue: statusRaw) ?? .planned }
        set { statusRaw = newValue.rawValue }
    }

    /// Valid range for block index (0-95 representing 00:00 to 23:45)
    static let validBlockIndexRange = 0...95

    init(
        id: UUID = UUID(),
        blockDate: Date = Date(),
        blockIndex: Int = 0,
        title: String = "",
        blockDescription: String = "",
        category: BlockCategory = .other,
        estimatedValueBRL: Decimal = 0,
        actualValueBRL: Decimal? = nil,
        status: BlockStatus = .planned,
        isAISuggested: Bool = false,
        startedAt: Date? = nil,
        completedAt: Date? = nil,
        taskId: UUID? = nil
    ) {
        self.id = id
        self.blockDate = blockDate
        // Validate blockIndex is in valid range (0-95)
        self.blockIndex = Self.validBlockIndexRange.contains(blockIndex) ? blockIndex : max(0, min(95, blockIndex))
        self.title = title.isEmpty ? "" : title.trimmingCharacters(in: .whitespacesAndNewlines)
        self.blockDescription = blockDescription
        self.categoryRaw = category.rawValue
        self.estimatedValueBRL = estimatedValueBRL
        self.actualValueBRL = actualValueBRL
        self.statusRaw = status.rawValue
        self.isAISuggested = isAISuggested
        self.startedAt = startedAt
        self.completedAt = completedAt
        self.createdAt = Date()
        self.taskId = taskId

        #if DEBUG
        if !Self.validBlockIndexRange.contains(blockIndex) {
            print("⚠ Block: Invalid blockIndex \(blockIndex) clamped to valid range 0-95")
        }
        #endif
    }

    // MARK: - Computed Properties

    /// Returns the time string for this block (e.g., "09:15")
    var timeString: String {
        let hour = blockIndex / 4
        let minute = (blockIndex % 4) * 15
        return String(format: "%02d:%02d", hour, minute)
    }

    /// Returns the end time string for this block (e.g., "09:30")
    var endTimeString: String {
        let nextIndex = blockIndex + 1
        let hour = nextIndex / 4
        let minute = (nextIndex % 4) * 15
        return String(format: "%02d:%02d", hour, minute)
    }

    /// Returns the time range string (e.g., "09:15 - 09:30")
    var timeRangeString: String {
        "\(timeString) - \(endTimeString)"
    }

    /// Check if block is currently active (current time is within block)
    var isCurrentBlock: Bool {
        let calendar = Calendar.current
        guard calendar.isDateInToday(blockDate) else { return false }

        let now = Date()
        let hour = calendar.component(.hour, from: now)
        let minute = calendar.component(.minute, from: now)
        let currentBlockIndex = hour * 4 + minute / 15

        return currentBlockIndex == blockIndex
    }

    /// Check if block is in the past
    var isPast: Bool {
        let calendar = Calendar.current

        if !calendar.isDateInToday(blockDate) {
            return blockDate < Date()
        }

        let now = Date()
        let hour = calendar.component(.hour, from: now)
        let minute = calendar.component(.minute, from: now)
        let currentBlockIndex = hour * 4 + minute / 15

        return blockIndex < currentBlockIndex
    }

    /// Duration in seconds (always 15 minutes = 900 seconds)
    var durationSeconds: TimeInterval {
        return 15 * 60
    }

    /// Actual duration spent (if started)
    var actualDurationSeconds: TimeInterval? {
        guard let started = startedAt else { return nil }
        let end = completedAt ?? Date()
        return end.timeIntervalSince(started)
    }

    /// ROI efficiency (actual value / estimated value)
    var roiEfficiency: Double? {
        guard let actual = actualValueBRL, estimatedValueBRL > 0 else { return nil }
        return Double(truncating: actual as NSNumber) / Double(truncating: estimatedValueBRL as NSNumber)
    }
}

// MARK: - Block Category
enum BlockCategory: String, Codable, CaseIterable {
    case work = "Trabalho"
    case personal = "Pessoal"
    case health = "Saude"
    case learning = "Aprendizado"
    case meeting = "Reuniao"
    case focus = "Foco Profundo"
    case breakTime = "Intervalo"
    case sleep = "Sono"
    case other = "Outro"

    var icon: String {
        switch self {
        case .work: return "briefcase.fill"
        case .personal: return "person.fill"
        case .health: return "heart.fill"
        case .learning: return "book.fill"
        case .meeting: return "person.2.fill"
        case .focus: return "brain.head.profile"
        case .breakTime: return "cup.and.saucer.fill"
        case .sleep: return "moon.fill"
        case .other: return "folder.fill"
        }
    }

    // Light mode colors
    var colorLight: String {
        switch self {
        case .work: return "#007AFF"       // Blue
        case .personal: return "#AF52DE"   // Purple
        case .health: return "#FF2D55"     // Pink
        case .learning: return "#5AC8FA"   // Teal
        case .meeting: return "#FF9500"    // Orange
        case .focus: return "#34C759"      // Green
        case .breakTime: return "#8E8E93"  // Gray
        case .sleep: return "#5856D6"      // Indigo
        case .other: return "#AEAEB2"      // Light Gray
        }
    }

    // Dark mode colors (brighter for visibility)
    var colorDark: String {
        switch self {
        case .work: return "#0A84FF"       // Brighter blue
        case .personal: return "#BF5AF2"   // Brighter purple
        case .health: return "#FF375F"     // Brighter pink
        case .learning: return "#64D2FF"   // Brighter teal
        case .meeting: return "#FF9F0A"    // Brighter orange
        case .focus: return "#30D158"      // Brighter green
        case .breakTime: return "#98989D"  // Lighter gray
        case .sleep: return "#5E5CE6"      // Brighter indigo
        case .other: return "#C7C7CC"      // Lighter gray
        }
    }

    // Adaptive color based on color scheme
    func adaptiveColor(for scheme: ColorScheme) -> Color {
        let hex = scheme == .dark ? colorDark : colorLight
        return Color(hex: hex) ?? .blue
    }

    // Legacy: Keep for compatibility (uses light mode)
    var color: String { colorLight }
}

// MARK: - Block Status
enum BlockStatus: String, Codable, CaseIterable {
    case planned = "Planejado"
    case inProgress = "Em Andamento"
    case completed = "Concluido"
    case skipped = "Pulado"

    var icon: String {
        switch self {
        case .planned: return "circle"
        case .inProgress: return "play.circle.fill"
        case .completed: return "checkmark.circle.fill"
        case .skipped: return "forward.fill"
        }
    }
}

// MARK: - Block Extensions
extension Block {
    /// Create 96 empty blocks for a given date
    static func createDayBlocks(for date: Date) -> [Block] {
        (0..<96).map { index in
            Block(blockDate: date, blockIndex: index)
        }
    }

    /// Get block index for current time
    static func currentBlockIndex() -> Int {
        let calendar = Calendar.current
        let now = Date()
        let hour = calendar.component(.hour, from: now)
        let minute = calendar.component(.minute, from: now)
        return hour * 4 + minute / 15
    }
}


     1→//
     2→//  Goal.swift
     3→//  96Blocks-iOS
     4→//
     5→//  Goal model for tracking high-level objectives
     6→//
     7→
     8→import Foundation
     9→import SwiftData
    10→
    11→@Model
    12→final class Goal {
    13→    var id: UUID = UUID()
    14→    var title: String = ""
    15→    var goalDescription: String = ""
    16→    var targetValueBRL: Decimal = 0
    17→    var timeframeRaw: String = GoalTimeframe.monthly.rawValue
    18→    var blocksAllocated: Int = 0
    19→    var blocksCompleted: Int = 0
    20→    var valueGeneratedBRL: Decimal = 0
    21→    var statusRaw: String = GoalStatus.active.rawValue
    22→    var dueDate: Date? = nil
    23→    var createdAt: Date = Date()
    24→
    25→    // Computed wrappers for enums
    26→    @Transient var timeframe: GoalTimeframe {
    27→        get { GoalTimeframe(rawValue: timeframeRaw) ?? .monthly }
    28→        set { timeframeRaw = newValue.rawValue }
    29→    }
    30→
    31→    @Transient var status: GoalStatus {
    32→        get { GoalStatus(rawValue: statusRaw) ?? .active }
    33→        set { statusRaw = newValue.rawValue }
    34→    }
    35→
    36→    init(
    37→        id: UUID = UUID(),
    38→        title: String,
    39→        goalDescription: String = "",
    40→        targetValueBRL: Decimal = 0,
    41→        timeframe: GoalTimeframe = .monthly,
    42→        dueDate: Date? = nil
    43→    ) {
    44→        self.id = id
    45→        self.title = title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
    46→            ? "Nova Meta"
    47→            : title.trimmingCharacters(in: .whitespacesAndNewlines)
    48→        self.goalDescription = goalDescription
    49→        self.targetValueBRL = targetValueBRL
    50→        self.timeframeRaw = timeframe.rawValue
    51→        self.dueDate = dueDate
    52→        self.createdAt = Date()
    53→    }
    54→
    55→    // MARK: - Computed Properties
    56→
    57→    /// Progress percentage (0-100)
    58→    var progressPercentage: Double {
    59→        guard targetValueBRL > 0 else { return 0 }
    60→        return min(100, Double(truncating: valueGeneratedBRL as NSNumber) / Double(truncating: targetValueBRL as NSNumber) * 100)
    61→    }
    62→
    63→    /// Blocks completion rate
    64→    var blocksCompletionRate: Double {
    65→        guard blocksAllocated > 0 else { return 0 }
    66→        return Double(blocksCompleted) / Double(blocksAllocated) * 100
    67→    }
    68→
    69→    /// Remaining value to reach target
    70→    var remainingValueBRL: Decimal {
    71→        max(0, targetValueBRL - valueGeneratedBRL)
    72→    }
    73→
    74→    /// Days until due date
    75→    var daysRemaining: Int? {
    76→        guard let due = dueDate else { return nil }
    77→        let calendar = Calendar.current
    78→        let components = calendar.dateComponents([.day], from: Date(), to: due)
    79→        return components.day
    80→    }
    81→
    82→    /// Check if goal is overdue
    83→    var isOverdue: Bool {
    84→        guard let due = dueDate, status == .active else { return false }
    85→        return due < Date()
    86→    }
    87→}
    88→
    89→// MARK: - Goal Timeframe
    90→enum GoalTimeframe: String, Codable, CaseIterable {
    91→    case weekly = "Semanal"
    92→    case monthly = "Mensal"
    93→    case quarterly = "Trimestral"
    94→    case yearly = "Anual"
    95→
    96→    var icon: String {
    97→        switch self {
    98→        case .weekly: return "calendar.badge.clock"
    99→        case .monthly: return "calendar"
   100→        case .quarterly: return "calendar.badge.exclamationmark"
   101→        case .yearly: return "calendar.badge.plus"
   102→        }
   103→    }
   104→
   105→    /// Default number of days for the timeframe
   106→    var defaultDays: Int {
   107→        switch self {
   108→        case .weekly: return 7
   109→        case .monthly: return 30
   110→        case .quarterly: return 90
   111→        case .yearly: return 365
   112→        }
   113→    }
   114→}
   115→
   116→// MARK: - Goal Status
   117→enum GoalStatus: String, Codable, CaseIterable {
   118→    case active = "Ativa"
   119→    case completed = "Concluida"
   120→    case paused = "Pausada"
   121→    case archived = "Arquivada"
   122→
   123→    var icon: String {
   124→        switch self {
   125→        case .active: return "target"
   126→        case .completed: return "checkmark.seal.fill"
   127→        case .paused: return "pause.circle"
   128→        case .archived: return "archivebox"
   129→        }
   130→    }
   131→}
   132→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

//
//  Goal.swift
//  96Blocks-iOS
//
//  Goal model for tracking high-level objectives
//

import Foundation
import SwiftData

@Model
final class Goal {
    var id: UUID = UUID()
    var title: String = ""
    var goalDescription: String = ""
    var targetValueBRL: Decimal = 0
    var timeframeRaw: String = GoalTimeframe.monthly.rawValue
    var blocksAllocated: Int = 0
    var blocksCompleted: Int = 0
    var valueGeneratedBRL: Decimal = 0
    var statusRaw: String = GoalStatus.active.rawValue
    var dueDate: Date? = nil
    var createdAt: Date = Date()

    // Computed wrappers for enums
    @Transient var timeframe: GoalTimeframe {
        get { GoalTimeframe(rawValue: timeframeRaw) ?? .monthly }
        set { timeframeRaw = newValue.rawValue }
    }

    @Transient var status: GoalStatus {
        get { GoalStatus(rawValue: statusRaw) ?? .active }
        set { statusRaw = newValue.rawValue }
    }

    init(
        id: UUID = UUID(),
        title: String,
        goalDescription: String = "",
        targetValueBRL: Decimal = 0,
        timeframe: GoalTimeframe = .monthly,
        dueDate: Date? = nil
    ) {
        self.id = id
        self.title = title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
            ? "Nova Meta"
            : title.trimmingCharacters(in: .whitespacesAndNewlines)
        self.goalDescription = goalDescription
        self.targetValueBRL = targetValueBRL
        self.timeframeRaw = timeframe.rawValue
        self.dueDate = dueDate
        self.createdAt = Date()
    }

    // MARK: - Computed Properties

    /// Progress percentage (0-100)
    var progressPercentage: Double {
        guard targetValueBRL > 0 else { return 0 }
        return min(100, Double(truncating: valueGeneratedBRL as NSNumber) / Double(truncating: targetValueBRL as NSNumber) * 100)
    }

    /// Blocks completion rate
    var blocksCompletionRate: Double {
        guard blocksAllocated > 0 else { return 0 }
        return Double(blocksCompleted) / Double(blocksAllocated) * 100
    }

    /// Remaining value to reach target
    var remainingValueBRL: Decimal {
        max(0, targetValueBRL - valueGeneratedBRL)
    }

    /// Days until due date
    var daysRemaining: Int? {
        guard let due = dueDate else { return nil }
        let calendar = Calendar.current
        let components = calendar.dateComponents([.day], from: Date(), to: due)
        return components.day
    }

    /// Check if goal is overdue
    var isOverdue: Bool {
        guard let due = dueDate, status == .active else { return false }
        return due < Date()
    }
}

// MARK: - Goal Timeframe
enum GoalTimeframe: String, Codable, CaseIterable {
    case weekly = "Semanal"
    case monthly = "Mensal"
    case quarterly = "Trimestral"
    case yearly = "Anual"

    var icon: String {
        switch self {
        case .weekly: return "calendar.badge.clock"
        case .monthly: return "calendar"
        case .quarterly: return "calendar.badge.exclamationmark"
        case .yearly: return "calendar.badge.plus"
        }
    }

    /// Default number of days for the timeframe
    var defaultDays: Int {
        switch self {
        case .weekly: return 7
        case .monthly: return 30
        case .quarterly: return 90
        case .yearly: return 365
        }
    }
}

// MARK: - Goal Status
enum GoalStatus: String, Codable, CaseIterable {
    case active = "Ativa"
    case completed = "Concluida"
    case paused = "Pausada"
    case archived = "Arquivada"

    var icon: String {
        switch self {
        case .active: return "target"
        case .completed: return "checkmark.seal.fill"
        case .paused: return "pause.circle"
        case .archived: return "archivebox"
        }
    }
}


     1→//
     2→//  Task.swift
     3→//  96Blocks-iOS
     4→//
     5→//  Task model for the backlog - tasks that can be scheduled to blocks
     6→//
     7→
     8→import Foundation
     9→import SwiftData
    10→
    11→@Model
    12→final class Task96 {
    13→    var id: UUID = UUID()
    14→    var title: String = ""
    15→    var taskDescription: String = ""
    16→    var priorityRaw: String = TaskPriority.medium.rawValue
    17→    var categoryRaw: String = BlockCategory.work.rawValue
    18→    var estimatedBlocks: Int = 2  // Number of 15-min blocks (default 30 min)
    19→    var estimatedValueBRL: Decimal = 0
    20→    var actualValueBRL: Decimal? = nil
    21→    var statusRaw: String = TaskStatus.backlog.rawValue
    22→    var dueDate: Date? = nil
    23→    var completedAt: Date? = nil
    24→    var createdAt: Date = Date()
    25→    var goalId: UUID? = nil
    26→    // Store tags as JSON string for secure CloudKit compatibility
    27→    var tagsJSON: String = "[]"
    28→
    29→    // Computed property to access tags as array
    30→    @Transient var tags: [String] {
    31→        get {
    32→            guard let data = tagsJSON.data(using: .utf8),
    33→                  let decoded = try? JSONDecoder().decode([String].self, from: data) else {
    34→                return []
    35→            }
    36→            return decoded
    37→        }
    38→        set {
    39→            if let encoded = try? JSONEncoder().encode(newValue),
    40→               let json = String(data: encoded, encoding: .utf8) {
    41→                tagsJSON = json
    42→            }
    43→        }
    44→    }
    45→
    46→    // Computed wrappers for enums
    47→    @Transient var priority: TaskPriority {
    48→        get { TaskPriority(rawValue: priorityRaw) ?? .medium }
    49→        set { priorityRaw = newValue.rawValue }
    50→    }
    51→
    52→    @Transient var category: BlockCategory {
    53→        get { BlockCategory(rawValue: categoryRaw) ?? .work }
    54→        set { categoryRaw = newValue.rawValue }
    55→    }
    56→
    57→    @Transient var status: TaskStatus {
    58→        get { TaskStatus(rawValue: statusRaw) ?? .backlog }
    59→        set { statusRaw = newValue.rawValue }
    60→    }
    61→
    62→    init(
    63→        id: UUID = UUID(),
    64→        title: String,
    65→        taskDescription: String = "",
    66→        priority: TaskPriority = .medium,
    67→        category: BlockCategory = .work,
    68→        estimatedBlocks: Int = 2,
    69→        estimatedValueBRL: Decimal = 0,
    70→        actualValueBRL: Decimal? = nil,
    71→        status: TaskStatus = .backlog,
    72→        dueDate: Date? = nil,
    73→        goalId: UUID? = nil,
    74→        tags: [String] = []
    75→    ) {
    76→        self.id = id
    77→        self.title = title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
    78→            ? "Nova Tarefa"
    79→            : title.trimmingCharacters(in: .whitespacesAndNewlines)
    80→        self.taskDescription = taskDescription
    81→        self.priorityRaw = priority.rawValue
    82→        self.categoryRaw = category.rawValue
    83→        self.estimatedBlocks = max(1, estimatedBlocks)
    84→        self.estimatedValueBRL = estimatedValueBRL
    85→        self.actualValueBRL = actualValueBRL
    86→        self.statusRaw = status.rawValue
    87→        self.dueDate = dueDate
    88→        self.createdAt = Date()
    89→        self.goalId = goalId
    90→        // Encode tags as JSON
    91→        if let encoded = try? JSONEncoder().encode(tags),
    92→           let json = String(data: encoded, encoding: .utf8) {
    93→            self.tagsJSON = json
    94→        }
    95→    }
    96→
    97→    // MARK: - Computed Properties
    98→
    99→    /// Estimated duration in minutes
   100→    var estimatedDurationMinutes: Int {
   101→        estimatedBlocks * 15
   102→    }
   103→
   104→    /// Estimated duration formatted string
   105→    var estimatedDurationString: String {
   106→        let hours = estimatedDurationMinutes / 60
   107→        let minutes = estimatedDurationMinutes % 60
   108→
   109→        if hours > 0 && minutes > 0 {
   110→            return "\(hours)h \(minutes)min"
   111→        } else if hours > 0 {
   112→            return "\(hours)h"
   113→        } else {
   114→            return "\(minutes)min"
   115→        }
   116→    }
   117→
   118→    /// Check if task is overdue
   119→    var isOverdue: Bool {
   120→        guard let due = dueDate, status != .completed else { return false }
   121→        return due < Date()
   122→    }
   123→
   124→    /// ROI per block (estimated)
   125→    var roiPerBlock: Decimal {
   126→        guard estimatedBlocks > 0 else { return 0 }
   127→        return estimatedValueBRL / Decimal(estimatedBlocks)
   128→    }
   129→}
   130→
   131→// MARK: - Task Priority
   132→enum TaskPriority: String, Codable, CaseIterable {
   133→    case low = "Baixa"
   134→    case medium = "Media"
   135→    case high = "Alta"
   136→    case urgent = "Urgente"
   137→
   138→    var sortOrder: Int {
   139→        switch self {
   140→        case .urgent: return 0
   141→        case .high: return 1
   142→        case .medium: return 2
   143→        case .low: return 3
   144→        }
   145→    }
   146→
   147→    var icon: String {
   148→        switch self {
   149→        case .low: return "arrow.down"
   150→        case .medium: return "minus"
   151→        case .high: return "arrow.up"
   152→        case .urgent: return "exclamationmark.2"
   153→        }
   154→    }
   155→
   156→    var color: String {
   157→        switch self {
   158→        case .low: return "#8E8E93"      // Gray
   159→        case .medium: return "#007AFF"   // Blue
   160→        case .high: return "#FF9500"     // Orange
   161→        case .urgent: return "#FF3B30"   // Red
   162→        }
   163→    }
   164→}
   165→
   166→// MARK: - Task Status
   167→enum TaskStatus: String, Codable, CaseIterable {
   168→    case backlog = "Backlog"
   169→    case scheduled = "Agendada"
   170→    case inProgress = "Em Andamento"
   171→    case completed = "Concluida"
   172→    case archived = "Arquivada"
   173→
   174→    var icon: String {
   175→        switch self {
   176→        case .backlog: return "tray"
   177→        case .scheduled: return "calendar"
   178→        case .inProgress: return "play.fill"
   179→        case .completed: return "checkmark"
   180→        case .archived: return "archivebox"
   181→        }
   182→    }
   183→}
   184→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

//
//  Task.swift
//  96Blocks-iOS
//
//  Task model for the backlog - tasks that can be scheduled to blocks
//

import Foundation
import SwiftData

@Model
final class Task96 {
    var id: UUID = UUID()
    var title: String = ""
    var taskDescription: String = ""
    var priorityRaw: String = TaskPriority.medium.rawValue
    var categoryRaw: String = BlockCategory.work.rawValue
    var estimatedBlocks: Int = 2  // Number of 15-min blocks (default 30 min)
    var estimatedValueBRL: Decimal = 0
    var actualValueBRL: Decimal? = nil
    var statusRaw: String = TaskStatus.backlog.rawValue
    var dueDate: Date? = nil
    var completedAt: Date? = nil
    var createdAt: Date = Date()
    var goalId: UUID? = nil
    // Store tags as JSON string for secure CloudKit compatibility
    var tagsJSON: String = "[]"

    // Computed property to access tags as array
    @Transient var tags: [String] {
        get {
            guard let data = tagsJSON.data(using: .utf8),
                  let decoded = try? JSONDecoder().decode([String].self, from: data) else {
                return []
            }
            return decoded
        }
        set {
            if let encoded = try? JSONEncoder().encode(newValue),
               let json = String(data: encoded, encoding: .utf8) {
                tagsJSON = json
            }
        }
    }

    // Computed wrappers for enums
    @Transient var priority: TaskPriority {
        get { TaskPriority(rawValue: priorityRaw) ?? .medium }
        set { priorityRaw = newValue.rawValue }
    }

    @Transient var category: BlockCategory {
        get { BlockCategory(rawValue: categoryRaw) ?? .work }
        set { categoryRaw = newValue.rawValue }
    }

    @Transient var status: TaskStatus {
        get { TaskStatus(rawValue: statusRaw) ?? .backlog }
        set { statusRaw = newValue.rawValue }
    }

    init(
        id: UUID = UUID(),
        title: String,
        taskDescription: String = "",
        priority: TaskPriority = .medium,
        category: BlockCategory = .work,
        estimatedBlocks: Int = 2,
        estimatedValueBRL: Decimal = 0,
        actualValueBRL: Decimal? = nil,
        status: TaskStatus = .backlog,
        dueDate: Date? = nil,
        goalId: UUID? = nil,
        tags: [String] = []
    ) {
        self.id = id
        self.title = title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
            ? "Nova Tarefa"
            : title.trimmingCharacters(in: .whitespacesAndNewlines)
        self.taskDescription = taskDescription
        self.priorityRaw = priority.rawValue
        self.categoryRaw = category.rawValue
        self.estimatedBlocks = max(1, estimatedBlocks)
        self.estimatedValueBRL = estimatedValueBRL
        self.actualValueBRL = actualValueBRL
        self.statusRaw = status.rawValue
        self.dueDate = dueDate
        self.createdAt = Date()
        self.goalId = goalId
        // Encode tags as JSON
        if let encoded = try? JSONEncoder().encode(tags),
           let json = String(data: encoded, encoding: .utf8) {
            self.tagsJSON = json
        }
    }

    // MARK: - Computed Properties

    /// Estimated duration in minutes
    var estimatedDurationMinutes: Int {
        estimatedBlocks * 15
    }

    /// Estimated duration formatted string
    var estimatedDurationString: String {
        let hours = estimatedDurationMinutes / 60
        let minutes = estimatedDurationMinutes % 60

        if hours > 0 && minutes > 0 {
            return "\(hours)h \(minutes)min"
        } else if hours > 0 {
            return "\(hours)h"
        } else {
            return "\(minutes)min"
        }
    }

    /// Check if task is overdue
    var isOverdue: Bool {
        guard let due = dueDate, status != .completed else { return false }
        return due < Date()
    }

    /// ROI per block (estimated)
    var roiPerBlock: Decimal {
        guard estimatedBlocks > 0 else { return 0 }
        return estimatedValueBRL / Decimal(estimatedBlocks)
    }
}

// MARK: - Task Priority
enum TaskPriority: String, Codable, CaseIterable {
    case low = "Baixa"
    case medium = "Media"
    case high = "Alta"
    case urgent = "Urgente"

    var sortOrder: Int {
        switch self {
        case .urgent: return 0
        case .high: return 1
        case .medium: return 2
        case .low: return 3
        }
    }

    var icon: String {
        switch self {
        case .low: return "arrow.down"
        case .medium: return "minus"
        case .high: return "arrow.up"
        case .urgent: return "exclamationmark.2"
        }
    }

    var color: String {
        switch self {
        case .low: return "#8E8E93"      // Gray
        case .medium: return "#007AFF"   // Blue
        case .high: return "#FF9500"     // Orange
        case .urgent: return "#FF3B30"   // Red
        }
    }
}

// MARK: - Task Status
enum TaskStatus: String, Codable, CaseIterable {
    case backlog = "Backlog"
    case scheduled = "Agendada"
    case inProgress = "Em Andamento"
    case completed = "Concluida"
    case archived = "Arquivada"

    var icon: String {
        switch self {
        case .backlog: return "tray"
        case .scheduled: return "calendar"
        case .inProgress: return "play.fill"
        case .completed: return "checkmark"
        case .archived: return "archivebox"
        }
    }
}


Now let me check the Services and Views to understand the architecture patterns better:


List Services directory


find ~/Apps/iOS/PROD-96Blocks/Services -name "*.swift" | sort

Find all service files


total 272
drwxr-xr-x@ 11 neog  staff    352 Dec 18 16:33 .
drwxr-xr-x@ 18 neog  staff    576 Dec 18 16:25 ..
-rw-------@  1 neog  staff  22711 Dec 18 16:33 AIService.swift
-rw-------@  1 neog  staff   8102 Dec  8 21:19 AuthManager.swift
-rw-------@  1 neog  staff  29769 Dec 18 16:26 DataManager.swift
-rw-------@  1 neog  staff  19468 Dec  8 21:47 IntegrityService.swift
-rw-------@  1 neog  staff   3842 Dec 10 14:44 KeychainService.swift
-rw-------@  1 neog  staff  11269 Dec 10 14:47 LoggingService.swift
-rw-------@  1 neog  staff  13424 Dec 10 14:46 NotificationService.swift
-rw-------@  1 neog  staff  16746 Dec 10 14:55 TimerService.swift
drwxr-xr-x@  6 neog  staff    192 Dec  8 21:52 Validation


Let me examine the DataManager and some key services:


     1→//
     2→//  DataManager.swift
     3→//  96Blocks-iOS
     4→//
     5→//  Unified data manager using SwiftData + CloudKit for persistence
     6→//  Manages Blocks, Tasks, Goals, Templates, and DailySummaries
     7→//  Syncs automatically to iCloud when user is signed in
     8→//
     9→
    10→import Foundation
    11→import SwiftData
    12→import SwiftUI
    13→import CloudKit
    14→import Combine
    15→import WidgetKit
    16→
    17→@MainActor
    18→class DataManager: ObservableObject {
    19→    static let shared = DataManager()
    20→
    21→    // MARK: - CloudKit Configuration
    22→    private static let cloudKitContainerIdentifier = "iCloud.com.neog.96Blocks-iOS"
    23→
    24→    // MARK: - Model Container
    25→    let modelContainer: ModelContainer
    26→
    27→    // MARK: - Published Properties for UI Binding
    28→    @Published var blocks: [Block] = []
    29→    @Published var tasks: [Task96] = []
    30→    @Published var goals: [Goal] = []
    31→    @Published var templates: [Template] = []
    32→    @Published var dailySummaries: [DailySummary] = []
    33→    @Published var blockSteps: [BlockStep] = []
    34→
    35→    // MARK: - Selected Date
    36→    @Published var selectedDate: Date = Date()
    37→
    38→    // MARK: - Loading State
    39→    @Published var isLoading: Bool = false
    40→
    41→    // MARK: - Sync State
    42→    @Published var isSyncing: Bool = false
    43→    @Published var lastSyncDate: Date?
    44→    @Published var syncError: String?
    45→
    46→    // MARK: - Statistics Cache (Performance Optimization)
    47→    private var cachedTodayBlocks: [Block]?
    48→    private var cachedTodayBlocksDate: Date?
    49→    private var cachedTodayROI: (estimated: Decimal, actual: Decimal)?
    50→    private var cachedProductivityScore: Int?
    51→    private var lastStatsCacheTime: Date?
    52→    private let statsCacheValiditySeconds: TimeInterval = 5.0  // Cache valid for 5 seconds
    53→
    54→    // Combine-based debounce for CloudKit sync (more robust than manual date tracking)
    55→    private let remoteChangeSubject = PassthroughSubject<Void, Never>()
    56→    private var isHandlingRemoteChange = false
    57→    private var lastRemoteChangeHandled: Date?
    58→    private var cancellables = Set<AnyCancellable>()
    59→    private let remoteChangeDebounceInterval: TimeInterval = 2.0  // Increased for stability
    60→    private let minimumRemoteChangeInterval: TimeInterval = 3.0  // Minimum time between handling remote changes
    61→
    62→    // MARK: - Initialization
    63→    private init() {
    64→        // Ensure Application Support directory exists BEFORE creating ModelContainer
    65→        Self.ensureApplicationSupportDirectoryExists()
    66→
    67→        do {
    68→            let schema = Schema([
    69→                Block.self,
    70→                Task96.self,
    71→                Goal.self,
    72→                Template.self,
    73→                DailySummary.self,
    74→                BlockStep.self
    75→            ])
    76→
    77→            // Configure CloudKit sync
    78→            let modelConfiguration = ModelConfiguration(
    79→                schema: schema,
    80→                isStoredInMemoryOnly: false,
    81→                allowsSave: true,
    82→                groupContainer: .none,
    83→                cloudKitDatabase: .private(Self.cloudKitContainerIdentifier)
    84→            )
    85→
    86→            modelContainer = try ModelContainer(
    87→                for: schema,
    88→                configurations: [modelConfiguration]
    89→            )
    90→
    91→            // Load initial data (sync for init)
    92→            loadAllDataSync()
    93→
    94→            // Setup sync monitoring
    95→            setupCloudKitSyncMonitoring()
    96→
    97→            #if DEBUG
    98→            print("DataManager: Initialized with CloudKit sync enabled")
    99→            print("DataManager: Container = \(Self.cloudKitContainerIdentifier)")
   100→            #endif

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

//
//  DataManager.swift
//  96Blocks-iOS
//
//  Unified data manager using SwiftData + CloudKit for persistence
//  Manages Blocks, Tasks, Goals, Templates, and DailySummaries
//  Syncs automatically to iCloud when user is signed in
//

import Foundation
import SwiftData
import SwiftUI
import CloudKit
import Combine
import WidgetKit

@MainActor
class DataManager: ObservableObject {
    static let shared = DataManager()

    // MARK: - CloudKit Configuration
    private static let cloudKitContainerIdentifier = "iCloud.com.neog.96Blocks-iOS"

    // MARK: - Model Container
    let modelContainer: ModelContainer

    // MARK: - Published Properties for UI Binding
    @Published var blocks: [Block] = []
    @Published var tasks: [Task96] = []
    @Published var goals: [Goal] = []
    @Published var templates: [Template] = []
    @Published var dailySummaries: [DailySummary] = []
    @Published var blockSteps: [BlockStep] = []

    // MARK: - Selected Date
    @Published var selectedDate: Date = Date()

    // MARK: - Loading State
    @Published var isLoading: Bool = false

    // MARK: - Sync State
    @Published var isSyncing: Bool = false
    @Published var lastSyncDate: Date?
    @Published var syncError: String?

    // MARK: - Statistics Cache (Performance Optimization)
    private var cachedTodayBlocks: [Block]?
    private var cachedTodayBlocksDate: Date?
    private var cachedTodayROI: (estimated: Decimal, actual: Decimal)?
    private var cachedProductivityScore: Int?
    private var lastStatsCacheTime: Date?
    private let statsCacheValiditySeconds: TimeInterval = 5.0  // Cache valid for 5 seconds

    // Combine-based debounce for CloudKit sync (more robust than manual date tracking)
    private let remoteChangeSubject = PassthroughSubject<Void, Never>()
    private var isHandlingRemoteChange = false
    private var lastRemoteChangeHandled: Date?
    private var cancellables = Set<AnyCancellable>()
    private let remoteChangeDebounceInterval: TimeInterval = 2.0  // Increased for stability
    private let minimumRemoteChangeInterval: TimeInterval = 3.0  // Minimum time between handling remote changes

    // MARK: - Initialization
    private init() {
        // Ensure Application Support directory exists BEFORE creating ModelContainer
        Self.ensureApplicationSupportDirectoryExists()

        do {
            let schema = Schema([
                Block.self,
                Task96.self,
                Goal.self,
                Template.self,
                DailySummary.self,
                BlockStep.self
            ])

            // Configure CloudKit sync
            let modelConfiguration = ModelConfiguration(
                schema: schema,
                isStoredInMemoryOnly: false,
                allowsSave: true,
                groupContainer: .none,
                cloudKitDatabase: .private(Self.cloudKitContainerIdentifier)
            )

            modelContainer = try ModelContainer(
                for: schema,
                configurations: [modelConfiguration]
            )

            // Load initial data (sync for init)
            loadAllDataSync()

            // Setup sync monitoring
            setupCloudKitSyncMonitoring()

            #if DEBUG
            print("DataManager: Initialized with CloudKit sync enabled")
            print("DataManager: Container = \(Self.cloudKitContainerIdentifier)")
            #endif


     1→//
     2→//  AIService.swift
     3→//  96Blocks-iOS
     4→//
     5→//  AI Service for generating day plans using multiple AI providers
     6→//  Supports: Claude (Anthropic), OpenAI (GPT-4), Grok (xAI), LM Studio (Local)
     7→//
     8→//  API keys are embedded for TestFlight users - no configuration needed!
     9→//
    10→
    11→import Foundation
    12→
    13→// MARK: - Secure API Key Storage
    14→// API keys are stored securely in Keychain. Users configure keys in Settings.
    15→// For TestFlight: Keys should be set via Settings > AI Configuration
    16→
    17→private struct SecureKeyStore {
    18→    // Default LM Studio URL (can be changed in Settings)
    19→    static let defaultLMStudioURL = "http://192.168.1.96:1234"
    20→
    21→    /// Get API key from Keychain
    22→    static func key(for provider: AIProvider) -> String? {
    23→        switch provider {
    24→        case .grok4, .grok41:
    25→            return KeychainService.get(.grokAPIKey)
    26→        case .openai:
    27→            return KeychainService.get(.openaiAPIKey)
    28→        case .claude:
    29→            return KeychainService.get(.claudeAPIKey)
    30→        case .lmstudio:
    31→            return "local"  // LM Studio doesn't need API key
    32→        }
    33→    }
    34→
    35→    /// Save API key to Keychain
    36→    static func saveKey(_ key: String, for provider: AIProvider) {
    37→        switch provider {
    38→        case .grok4, .grok41:
    39→            KeychainService.save(key, for: .grokAPIKey)
    40→        case .openai:
    41→            KeychainService.save(key, for: .openaiAPIKey)
    42→        case .claude:
    43→            KeychainService.save(key, for: .claudeAPIKey)
    44→        case .lmstudio:
    45→            KeychainService.save(key, for: .lmstudioURL)
    46→        }
    47→    }
    48→
    49→    /// Delete API key from Keychain
    50→    static func deleteKey(for provider: AIProvider) {
    51→        switch provider {
    52→        case .grok4, .grok41:
    53→            KeychainService.delete(.grokAPIKey)
    54→        case .openai:
    55→            KeychainService.delete(.openaiAPIKey)
    56→        case .claude:
    57→            KeychainService.delete(.claudeAPIKey)
    58→        case .lmstudio:
    59→            KeychainService.delete(.lmstudioURL)
    60→        }
    61→    }
    62→
    63→    /// Get LM Studio URL from Keychain or return default
    64→    static var lmStudioURL: String {
    65→        KeychainService.get(.lmstudioURL) ?? defaultLMStudioURL
    66→    }
    67→}
    68→
    69→// MARK: - AI Provider Enum
    70→
    71→enum AIProvider: String, CaseIterable, Codable {
    72→    case grok4 = "Grok 4"
    73→    case grok41 = "Grok 4.1"
    74→    case claude = "Claude"
    75→    case openai = "OpenAI"
    76→    case lmstudio = "LM Studio"
    77→
    78→    var displayName: String {
    79→        switch self {
    80→        case .grok4: return "Grok 4 (xAI)"

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

//
//  AIService.swift
//  96Blocks-iOS
//
//  AI Service for generating day plans using multiple AI providers
//  Supports: Claude (Anthropic), OpenAI (GPT-4), Grok (xAI), LM Studio (Local)
//
//  API keys are embedded for TestFlight users - no configuration needed!
//

import Foundation

// MARK: - Secure API Key Storage
// API keys are stored securely in Keychain. Users configure keys in Settings.
// For TestFlight: Keys should be set via Settings > AI Configuration

private struct SecureKeyStore {
    // Default LM Studio URL (can be changed in Settings)
    static let defaultLMStudioURL = "http://192.168.1.96:1234"

    /// Get API key from Keychain
    static func key(for provider: AIProvider) -> String? {
        switch provider {
        case .grok4, .grok41:
            return KeychainService.get(.grokAPIKey)
        case .openai:
            return KeychainService.get(.openaiAPIKey)
        case .claude:
            return KeychainService.get(.claudeAPIKey)
        case .lmstudio:
            return "local"  // LM Studio doesn't need API key
        }
    }

    /// Save API key to Keychain
    static func saveKey(_ key: String, for provider: AIProvider) {
        switch provider {
        case .grok4, .grok41:
            KeychainService.save(key, for: .grokAPIKey)
        case .openai:
            KeychainService.save(key, for: .openaiAPIKey)
        case .claude:
            KeychainService.save(key, for: .claudeAPIKey)
        case .lmstudio:
            KeychainService.save(key, for: .lmstudioURL)
        }
    }

    /// Delete API key from Keychain
    static func deleteKey(for provider: AIProvider) {
        switch provider {
        case .grok4, .grok41:
            KeychainService.delete(.grokAPIKey)
        case .openai:
            KeychainService.delete(.openaiAPIKey)
        case .claude:
            KeychainService.delete(.claudeAPIKey)
        case .lmstudio:
            KeychainService.delete(.lmstudioURL)
        }
    }

    /// Get LM Studio URL from Keychain or return default
    static var lmStudioURL: String {
        KeychainService.get(.lmstudioURL) ?? defaultLMStudioURL
    }
}

// MARK: - AI Provider Enum

enum AIProvider: String, CaseIterable, Codable {
    case grok4 = "Grok 4"
    case grok41 = "Grok 4.1"
    case claude = "Claude"
    case openai = "OpenAI"
    case lmstudio = "LM Studio"

    var displayName: String {
        switch self {
        case .grok4: return "Grok 4 (xAI)"


     1→//
     2→//  96BlocksApp.swift
     3→//  96Blocks-iOS
     4→//
     5→//  Main entry point for 96 Blocks - Day Planning with ROI Tracking
     6→//
     7→
     8→import SwiftUI
     9→import SwiftData
    10→import UserNotifications
    11→import BackgroundTasks
    12→
    13→// MARK: - App Notifications for Menu Commands
    14→extension Notification.Name {
    15→    static let showNewTask = Notification.Name("96Blocks.showNewTask")
    16→    static let showGeneratePlan = Notification.Name("96Blocks.showGeneratePlan")
    17→}
    18→
    19→// MARK: - Background Task Identifiers
    20→private enum BackgroundTaskIdentifier {
    21→    static let timerRefresh = "com.neog.96Blocks-iOS.timer-refresh"
    22→    static let dataSync = "com.neog.96Blocks-iOS.data-sync"
    23→}
    24→
    25→@main
    26→struct _96BlocksApp: App {
    27→    // PERFORMANCE: Use singletons directly instead of @StateObject wrapper
    28→    // @StateObject creates unnecessary overhead for objects that manage their own lifecycle
    29→    private let dataManager = DataManager.shared
    30→    private let timerService = TimerService.shared
    31→    private let notificationService = NotificationService.shared
    32→
    33→    @AppStorage("hasCompletedOnboarding") private var hasCompletedOnboarding = false
    34→    @Environment(\.scenePhase) private var scenePhase
    35→
    36→    init() {
    37→        // IMPORTANT: Setup notification delegate BEFORE any notifications can arrive
    38→        // This fixes cold start notification handling
    39→        UNUserNotificationCenter.current().delegate = NotificationDelegate.shared
    40→        NotificationService.shared.setupNotificationCategories()
    41→
    42→        // Prepare haptic generators on launch
    43→        HapticManager.prepare()
    44→
    45→        // Register background tasks for timer and data sync
    46→        registerBackgroundTasks()
    47→    }
    48→
    49→    // MARK: - Background Task Registration
    50→
    51→    private func registerBackgroundTasks() {
    52→        // Register timer refresh task
    53→        BGTaskScheduler.shared.register(
    54→            forTaskWithIdentifier: BackgroundTaskIdentifier.timerRefresh,
    55→            using: nil
    56→        ) { task in
    57→            self.handleTimerRefreshTask(task as! BGAppRefreshTask)
    58→        }
    59→
    60→        // Register data sync task
    61→        BGTaskScheduler.shared.register(
    62→            forTaskWithIdentifier: BackgroundTaskIdentifier.dataSync,
    63→            using: nil
    64→        ) { task in
    65→            self.handleDataSyncTask(task as! BGProcessingTask)
    66→        }
    67→    }
    68→
    69→    private func handleTimerRefreshTask(_ task: BGAppRefreshTask) {
    70→        // Schedule next refresh
    71→        scheduleTimerRefreshTask()
    72→
    73→        // Check if timer is running and update if needed
    74→        Task { @MainActor in
    75→            if timerService.isRunning {
    76→                timerService.handleAppDidBecomeActive()
    77→            }
    78→            task.setTaskCompleted(success: true)
    79→        }
    80→
    81→        task.expirationHandler = {
    82→            task.setTaskCompleted(success: false)
    83→        }
    84→    }
    85→
    86→    private func handleDataSyncTask(_ task: BGProcessingTask) {
    87→        Task { @MainActor in
    88→            dataManager.triggerSync()
    89→            task.setTaskCompleted(success: true)
    90→        }
    91→
    92→        task.expirationHandler = {
    93→            task.setTaskCompleted(success: false)
    94→        }
    95→    }
    96→
    97→    private func scheduleTimerRefreshTask() {
    98→        let request = BGAppRefreshTaskRequest(identifier: BackgroundTaskIdentifier.timerRefresh)
    99→        request.earliestBeginDate = Date(timeIntervalSinceNow: 60) // 1 minute
   100→
   101→        do {
   102→            try BGTaskScheduler.shared.submit(request)
   103→        } catch {
   104→            #if DEBUG
   105→            print("Failed to schedule timer refresh task: \(error)")
   106→            #endif
   107→        }
   108→    }
   109→
   110→    private func scheduleDataSyncTask() {
   111→        let request = BGProcessingTaskRequest(identifier: BackgroundTaskIdentifier.dataSync)
   112→        request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60) // 15 minutes
   113→        request.requiresNetworkConnectivity = true
   114→
   115→        do {
   116→            try BGTaskScheduler.shared.submit(request)
   117→        } catch {
   118→            #if DEBUG
   119→            print("Failed to schedule data sync task: \(error)")
   120→            #endif
   121→        }
   122→    }
   123→
   124→    var body: some Scene {
   125→        WindowGroup {
   126→            if hasCompletedOnboarding {
   127→                MainTabView()
   128→                    .environmentObject(dataManager)
   129→                    .environmentObject(timerService)
   130→                    .modelContainer(dataManager.modelContainer)
   131→                    .onAppear {
   132→                        setupNotifications()
   133→                    }
   134→            } else {
   135→                OnboardingView()
   136→                    .environmentObject(dataManager)
   137→                    .modelContainer(dataManager.modelContainer)
   138→            }
   139→        }
   140→        .onChange(of: scenePhase) { oldPhase, newPhase in
   141→            handleScenePhaseChange(from: oldPhase, to: newPhase)
   142→        }
   143→        .commands {
   144→            #if targetEnvironment(macCatalyst)
   145→            // Mac menu bar commands
   146→
   147→            // File menu - New items
   148→            CommandGroup(replacing: .newItem) {
   149→                Button("Nova Tarefa") {
   150→                    NotificationCenter.default.post(name: .showNewTask, object: nil)
   151→                }
   152→                .keyboardShortcut("n", modifiers: .command)
   153→
   154→                Button("Gerar Plano do Dia") {
   155→                    NotificationCenter.default.post(name: .showGeneratePlan, object: nil)
   156→                }
   157→                .keyboardShortcut("g", modifiers: [.command, .shift])
   158→            }
   159→
   160→            // View menu - Navigation
   161→            CommandGroup(after: .sidebar) {
   162→                Button("Ir para Hoje") {
   163→                    dataManager.selectedDate = Date()
   164→                }
   165→                .keyboardShortcut("t", modifiers: .command)
   166→            }
   167→
   168→            // Remove unused Edit menu items that cause "Internal inconsistency" errors
   169→            // These are default text editing menus that iOS Catalyst adds but aren't properly configured
   170→            CommandGroup(replacing: .textEditing) { }
   171→            CommandGroup(replacing: .textFormatting) { }
   172→            CommandGroup(replacing: .undoRedo) { }
   173→            CommandGroup(replacing: .pasteboard) { }
   174→            #endif
   175→        }
   176→    }
   177→
   178→    // MARK: - Scene Phase Handling
   179→    private func handleScenePhaseChange(from oldPhase: ScenePhase, to newPhase: ScenePhase) {
   180→        switch newPhase {
   181→        case .active:
   182→            // App became active - restore timer state
   183→            timerService.handleAppDidBecomeActive()
   184→        case .inactive:
   185→            // App will become inactive - save timer state
   186→            timerService.handleAppWillResignActive()
   187→        case .background:
   188→            // App entered background - schedule background tasks
   189→            if timerService.isRunning {
   190→                scheduleTimerRefreshTask()
   191→            }
   192→            scheduleDataSyncTask()
   193→        @unknown default:
   194→            break
   195→        }
   196→    }
   197→
   198→    // MARK: - Setup
   199→
   200→    private func setupNotifications() {
   201→        // Request notification authorization and schedule reminders
   202→        Task {
   203→            let authorized = await notificationService.requestAuthorization()
   204→
   205→            guard authorized else {
   206→                #if DEBUG
   207→                print("⚠ Notification authorization denied - reminders disabled")
   208→                #endif
   209→                return
   210→            }
   211→
   212→            // Schedule morning reminder (7:00 AM)
   213→            notificationService.scheduleMorningReminder(at: 7, minute: 0)
   214→
   215→            // Schedule daily summary (8:00 PM)
   216→            notificationService.scheduleDailySummary(at: 20, minute: 0)
   217→        }
   218→    }
   219→}
   220→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

//
//  96BlocksApp.swift
//  96Blocks-iOS
//
//  Main entry point for 96 Blocks - Day Planning with ROI Tracking
//

import SwiftUI
import SwiftData
import UserNotifications
import BackgroundTasks

// MARK: - App Notifications for Menu Commands
extension Notification.Name {
    static let showNewTask = Notification.Name("96Blocks.showNewTask")
    static let showGeneratePlan = Notification.Name("96Blocks.showGeneratePlan")
}

// MARK: - Background Task Identifiers
private enum BackgroundTaskIdentifier {
    static let timerRefresh = "com.neog.96Blocks-iOS.timer-refresh"
    static let dataSync = "com.neog.96Blocks-iOS.data-sync"
}

@main
struct _96BlocksApp: App {
    // PERFORMANCE: Use singletons directly instead of @StateObject wrapper
    // @StateObject creates unnecessary overhead for objects that manage their own lifecycle
    private let dataManager = DataManager.shared
    private let timerService = TimerService.shared
    private let notificationService = NotificationService.shared

    @AppStorage("hasCompletedOnboarding") private var hasCompletedOnboarding = false
    @Environment(\.scenePhase) private var scenePhase

    init() {
        // IMPORTANT: Setup notification delegate BEFORE any notifications can arrive
        // This fixes cold start notification handling
        UNUserNotificationCenter.current().delegate = NotificationDelegate.shared
        NotificationService.shared.setupNotificationCategories()

        // Prepare haptic generators on launch
        HapticManager.prepare()

        // Register background tasks for timer and data sync
        registerBackgroundTasks()
    }

    // MARK: - Background Task Registration

    private func registerBackgroundTasks() {
        // Register timer refresh task
        BGTaskScheduler.shared.register(
            forTaskWithIdentifier: BackgroundTaskIdentifier.timerRefresh,
            using: nil
        ) { task in
            self.handleTimerRefreshTask(task as! BGAppRefreshTask)
        }

        // Register data sync task
        BGTaskScheduler.shared.register(
            forTaskWithIdentifier: BackgroundTaskIdentifier.dataSync,
            using: nil
        ) { task in
            self.handleDataSyncTask(task as! BGProcessingTask)
        }
    }

    private func handleTimerRefreshTask(_ task: BGAppRefreshTask) {
        // Schedule next refresh
        scheduleTimerRefreshTask()

        // Check if timer is running and update if needed
        Task { @MainActor in
            if timerService.isRunning {
                timerService.handleAppDidBecomeActive()
            }
            task.setTaskCompleted(success: true)
        }

        task.expirationHandler = {
            task.setTaskCompleted(success: false)
        }
    }

    private func handleDataSyncTask(_ task: BGProcessingTask) {
        Task { @MainActor in
            dataManager.triggerSync()
            task.setTaskCompleted(success: true)
        }

        task.expirationHandler = {
            task.setTaskCompleted(success: false)
        }
    }

    private func scheduleTimerRefreshTask() {
        let request = BGAppRefreshTaskRequest(identifier: BackgroundTaskIdentifier.timerRefresh)
        request.earliestBeginDate = Date(timeIntervalSinceNow: 60) // 1 minute

        do {
            try BGTaskScheduler.shared.submit(request)
        } catch {
            #if DEBUG
            print("Failed to schedule timer refresh task: \(error)")
            #endif
        }
    }

    private func scheduleDataSyncTask() {
        let request = BGProcessingTaskRequest(identifier: BackgroundTaskIdentifier.dataSync)
        request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60) // 15 minutes
        request.requiresNetworkConnectivity = true

        do {
            try BGTaskScheduler.shared.submit(request)
        } catch {
            #if DEBUG
            print("Failed to schedule data sync task: \(error)")
            #endif
        }
    }

    var body: some Scene {
        WindowGroup {
            if hasCompletedOnboarding {
                MainTabView()
                    .environmentObject(dataManager)
                    .environmentObject(timerService)
                    .modelContainer(dataManager.modelContainer)
                    .onAppear {
                        setupNotifications()
                    }
            } else {
                OnboardingView()
                    .environmentObject(dataManager)
                    .modelContainer(dataManager.modelContainer)
            }
        }
        .onChange(of: scenePhase) { oldPhase, newPhase in
            handleScenePhaseChange(from: oldPhase, to: newPhase)
        }
        .commands {
            #if targetEnvironment(macCatalyst)
            // Mac menu bar commands

            // File menu - New items
            CommandGroup(replacing: .newItem) {
                Button("Nova Tarefa") {
                    NotificationCenter.default.post(name: .showNewTask, object: nil)
                }
                .keyboardShortcut("n", modifiers: .command)

                Button("Gerar Plano do Dia") {
                    NotificationCenter.default.post(name: .showGeneratePlan, object: nil)
                }
                .keyboardShortcut("g", modifiers: [.command, .shift])
            }

            // View menu - Navigation
            CommandGroup(after: .sidebar) {
                Button("Ir para Hoje") {
                    dataManager.selectedDate = Date()
                }
                .keyboardShortcut("t", modifiers: .command)
            }

            // Remove unused Edit menu items that cause "Internal inconsistency" errors
            // These are default text editing menus that iOS Catalyst adds but aren't properly configured
            CommandGroup(replacing: .textEditing) { }
            CommandGroup(replacing: .textFormatting) { }
            CommandGroup(replacing: .undoRedo) { }
            CommandGroup(replacing: .pasteboard) { }
            #endif
        }
    }

    // MARK: - Scene Phase Handling
    private func handleScenePhaseChange(from oldPhase: ScenePhase, to newPhase: ScenePhase) {
        switch newPhase {
        case .active:
            // App became active - restore timer state
            timerService.handleAppDidBecomeActive()
        case .inactive:
            // App will become inactive - save timer state
            timerService.handleAppWillResignActive()
        case .background:
            // App entered background - schedule background tasks
            if timerService.isRunning {
                scheduleTimerRefreshTask()
            }
            scheduleDataSyncTask()
        @unknown default:
            break
        }
    }

    // MARK: - Setup

    private func setupNotifications() {
        // Request notification authorization and schedule reminders
        Task {
            let authorized = await notificationService.requestAuthorization()

            guard authorized else {
                #if DEBUG
                print("⚠ Notification authorization denied - reminders disabled")
                #endif
                return
            }

            // Schedule morning reminder (7:00 AM)
            notificationService.scheduleMorningReminder(at: 7, minute: 0)

            // Schedule daily summary (8:00 PM)
            notificationService.scheduleDailySummary(at: 20, minute: 0)
        }
    }
}


Let me check one of the Views to understand the UI patterns and structure:


     1→//
     2→//  DayPlannerView.swift
     3→//  96Blocks-iOS
     4→//
     5→//  Main view showing the 96-block timeline for the selected day
     6→//
     7→
     8→import SwiftUI
     9→
    10→struct DayPlannerView: View {
    11→    @EnvironmentObject var dataManager: DataManager
    12→    @StateObject private var timerService = TimerService.shared
    13→
    14→    @State private var showDatePicker = false
    15→    @State private var showTemplateSheet = false
    16→    @State private var showAIGenerateSheet = false
    17→    @State private var scrollToCurrentBlock = true
    18→
    19→    var body: some View {
    20→        NavigationStack {
    21→            ZStack(alignment: .bottom) {
    22→                VStack(spacing: 0) {
    23→                    // Header with date navigation and ROI summary
    24→                    headerView
    25→
    26→                    // Timeline
    27→                    timelineView
    28→                }
    29→
    30→                // Floating Timer (when active)
    31→                if timerService.isRunning {
    32→                    TimerView(timerService: timerService)
    33→                        .padding()
    34→                        .transition(.move(edge: .bottom).combined(with: .opacity))
    35→                }
    36→            }
    37→            .navigationTitle("96 Blocks")
    38→            .navigationBarTitleDisplayMode(.inline)
    39→            .toolbar {
    40→                ToolbarItem(placement: .topBarLeading) {
    41→                    Menu {
    42→                        Button {
    43→                            showTemplateSheet = true
    44→                        } label: {
    45→                            Label("Aplicar Template", systemImage: "doc.on.doc")
    46→                        }
    47→
    48→                        Button {
    49→                            showAIGenerateSheet = true
    50→                        } label: {
    51→                            Label("Gerar com AI", systemImage: "sparkles")
    52→                        }
    53→                    } label: {
    54→                        Image(systemName: "wand.and.stars")
    55→                    }
    56→                }
    57→
    58→                ToolbarItem(placement: .topBarTrailing) {
    59→                    HStack(spacing: 12) {
    60→                        // Mini timer indicator
    61→                        if timerService.isRunning {
    62→                            MiniTimerView(timerService: timerService)
    63→                        }
    64→
    65→                        Button {
    66→                            goToToday()
    67→                        } label: {
    68→                            Image(systemName: "calendar.badge.clock")
    69→                        }
    70→                    }
    71→                }
    72→            }
    73→            .sheet(isPresented: $showDatePicker) {
    74→                DatePickerSheet(selectedDate: $dataManager.selectedDate)
    75→            }
    76→            .sheet(isPresented: $showTemplateSheet) {
    77→                TemplateSelectionSheet()
    78→            }
    79→            .sheet(isPresented: $showAIGenerateSheet) {
    80→                GeneratePlanView()
    81→            }
    82→        }
    83→    }
    84→
    85→    // MARK: - Header View
    86→    private var headerView: some View {
    87→        VStack(spacing: 12) {
    88→            // Date Navigation
    89→            HStack {
    90→                Button {
    91→                    previousDay()
    92→                } label: {
    93→                    Image(systemName: "chevron.left")
    94→                        .font(.title3)
    95→                }
    96→
    97→                Spacer()
    98→
    99→                Button {
   100→                    showDatePicker = true
   101→                } label: {
   102→                    VStack(spacing: 2) {
   103→                        Text(dateString)
   104→                            .font(.headline)
   105→                        Text(dayOfWeekString)
   106→                            .font(.caption)
   107→                            .foregroundStyle(.secondary)
   108→                    }
   109→                }
   110→
   111→                Spacer()
   112→
   113→                Button {
   114→                    nextDay()
   115→                } label: {
   116→                    Image(systemName: "chevron.right")
   117→                        .font(.title3)
   118→                }
   119→            }
   120→            .padding(.horizontal)
   121→
   122→            // Day Progress Bar (only for today)
   123→            if Calendar.current.isDateInToday(dataManager.selectedDate) {
   124→                dayProgressBar
   125→            }
   126→
   127→            // ROI Summary
   128→            roiSummaryView
   129→        }
   130→        .padding(.vertical, 8)
   131→        .background(Color(.systemGroupedBackground))
   132→    }
   133→
   134→    // MARK: - Day Progress Bar
   135→    private var dayProgressBar: some View {
   136→        let blocks = dataManager.getBlocks(for: dataManager.selectedDate)
   137→        let currentBlockIndex = Block.currentBlockIndex()
   138→        let completed = blocks.filter { $0.status == .completed && $0.blockIndex < currentBlockIndex }.count
   139→        let skipped = blocks.filter { $0.status == .skipped && $0.blockIndex < currentBlockIndex }.count
   140→        let lostEmpty = currentBlockIndex - completed - skipped
   141→
   142→        return VStack(spacing: 4) {
   143→            GeometryReader { geo in
   144→                HStack(spacing: 1) {
   145→                    // Concluídos (verde)
   146→                    if completed > 0 {
   147→                        Rectangle()
   148→                            .fill(Color.green)
   149→                            .frame(width: geo.size.width * CGFloat(completed) / 96)
   150→                    }

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

//
//  DayPlannerView.swift
//  96Blocks-iOS
//
//  Main view showing the 96-block timeline for the selected day
//

import SwiftUI

struct DayPlannerView: View {
    @EnvironmentObject var dataManager: DataManager
    @StateObject private var timerService = TimerService.shared

    @State private var showDatePicker = false
    @State private var showTemplateSheet = false
    @State private var showAIGenerateSheet = false
    @State private var scrollToCurrentBlock = true

    var body: some View {
        NavigationStack {
            ZStack(alignment: .bottom) {
                VStack(spacing: 0) {
                    // Header with date navigation and ROI summary
                    headerView

                    // Timeline
                    timelineView
                }

                // Floating Timer (when active)
                if timerService.isRunning {
                    TimerView(timerService: timerService)
                        .padding()
                        .transition(.move(edge: .bottom).combined(with: .opacity))
                }
            }
            .navigationTitle("96 Blocks")
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .topBarLeading) {
                    Menu {
                        Button {
                            showTemplateSheet = true
                        } label: {
                            Label("Aplicar Template", systemImage: "doc.on.doc")
                        }

                        Button {
                            showAIGenerateSheet = true
                        } label: {
                            Label("Gerar com AI", systemImage: "sparkles")
                        }
                    } label: {
                        Image(systemName: "wand.and.stars")
                    }
                }

                ToolbarItem(placement: .topBarTrailing) {
                    HStack(spacing: 12) {
                        // Mini timer indicator
                        if timerService.isRunning {
                            MiniTimerView(timerService: timerService)
                        }

                        Button {
                            goToToday()
                        } label: {
                            Image(systemName: "calendar.badge.clock")
                        }
                    }
                }
            }
            .sheet(isPresented: $showDatePicker) {
                DatePickerSheet(selectedDate: $dataManager.selectedDate)
            }
            .sheet(isPresented: $showTemplateSheet) {
                TemplateSelectionSheet()
            }
            .sheet(isPresented: $showAIGenerateSheet) {
                GeneratePlanView()
            }
        }
    }

    // MARK: - Header View
    private var headerView: some View {
        VStack(spacing: 12) {
            // Date Navigation
            HStack {
                Button {
                    previousDay()
                } label: {
                    Image(systemName: "chevron.left")
                        .font(.title3)
                }

                Spacer()

                Button {
                    showDatePicker = true
                } label: {
                    VStack(spacing: 2) {
                        Text(dateString)
                            .font(.headline)
                        Text(dayOfWeekString)
                            .font(.caption)
                            .foregroundStyle(.secondary)
                    }
                }

                Spacer()

                Button {
                    nextDay()
                } label: {
                    Image(systemName: "chevron.right")
                        .font(.title3)
                }
            }
            .padding(.horizontal)

            // Day Progress Bar (only for today)
            if Calendar.current.isDateInToday(dataManager.selectedDate) {
                dayProgressBar
            }

            // ROI Summary
            roiSummaryView
        }
        .padding(.vertical, 8)
        .background(Color(.systemGroupedBackground))
    }

    // MARK: - Day Progress Bar
    private var dayProgressBar: some View {
        let blocks = dataManager.getBlocks(for: dataManager.selectedDate)
        let currentBlockIndex = Block.currentBlockIndex()
        let completed = blocks.filter { $0.status == .completed && $0.blockIndex < currentBlockIndex }.count
        let skipped = blocks.filter { $0.status == .skipped && $0.blockIndex < currentBlockIndex }.count
        let lostEmpty = currentBlockIndex - completed - skipped

        return VStack(spacing: 4) {
            GeometryReader { geo in
                HStack(spacing: 1) {
                    // Concluídos (verde)
                    if completed > 0 {
                        Rectangle()
                            .fill(Color.green)
                            .frame(width: geo.size.width * CGFloat(completed) / 96)
                    }