     1→//
     2→//  Task.swift
     3→//  ProductivityHub-iOS
     4→//
     5→//  Unified task model merging ToDoMax + FocusWriter features
     6→//
     7→
     8→import Foundation
     9→import SwiftUI
    10→
    11→enum TaskPriority: String, Codable, CaseIterable {
    12→    case low = "Low"
    13→    case medium = "Medium"
    14→    case high = "High"
    15→    case urgent = "Urgent"
    16→
    17→    var color: String {
    18→        switch self {
    19→        case .low: return "gray"
    20→        case .medium: return "blue"
    21→        case .high: return "orange"
    22→        case .urgent: return "red"
    23→        }
    24→    }
    25→
    26→    var swiftUIColor: Color {
    27→        switch self {
    28→        case .low: return .gray
    29→        case .medium: return .blue
    30→        case .high: return .orange
    31→        case .urgent: return .red
    32→        }
    33→    }
    34→}
    35→
    36→enum TaskCategory: String, Codable, CaseIterable {
    37→    case work = "Work"
    38→    case personal = "Personal"
    39→    case health = "Health"
    40→    case learning = "Learning"
    41→    case creative = "Creative"
    42→    case finance = "Finance"
    43→    case social = "Social"
    44→    case other = "Other"
    45→
    46→    var icon: String {
    47→        switch self {
    48→        case .work: return "briefcase.fill"
    49→        case .personal: return "person.fill"
    50→        case .health: return "heart.fill"
    51→        case .learning: return "book.fill"
    52→        case .creative: return "paintbrush.fill"
    53→        case .finance: return "dollarsign.circle.fill"
    54→        case .social: return "person.2.fill"
    55→        case .other: return "folder.fill"
    56→        }
    57→    }
    58→}
    59→
    60→struct Subtask: Identifiable, Codable, Hashable {
    61→    var id: UUID = UUID()
    62→    var title: String
    63→    var isCompleted: Bool = false
    64→    var createdAt: Date = Date()
    65→
    66→    /// Validated initializer that ensures title is not empty
    67→    init(id: UUID = UUID(), title: String, isCompleted: Bool = false, createdAt: Date = Date()) {
    68→        self.id = id
    69→        self.title = title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
    70→            ? "Untitled Subtask"
    71→            : title.trimmingCharacters(in: .whitespacesAndNewlines)
    72→        self.isCompleted = isCompleted
    73→        self.createdAt = createdAt
    74→    }
    75→}
    76→
    77→struct Task: Identifiable, Codable, Hashable {
    78→    var id: UUID = UUID()
    79→    var title: String
    80→    var notes: String = ""
    81→    var isCompleted: Bool = false
    82→    var isImportant: Bool = false
    83→    var priority: TaskPriority = .medium
    84→    var category: TaskCategory = .personal
    85→    var subtasks: [Subtask] = []
    86→    var linkedSessionIds: [UUID] = [] // Link to WorkSessions
    87→    var tags: [UUID] = [] // Link to Tags
    88→    var estimatedDuration: TimeInterval? = nil
    89→    var dueDate: Date? = nil
    90→    var createdAt: Date = Date()
    91→    var completedAt: Date? = nil
    92→
    93→    /// Validated initializer that ensures title is not empty
    94→    init(
    95→        id: UUID = UUID(),
    96→        title: String,
    97→        notes: String = "",
    98→        isCompleted: Bool = false,
    99→        isImportant: Bool = false,
   100→        priority: TaskPriority = .medium,
   101→        category: TaskCategory = .personal,
   102→        subtasks: [Subtask] = [],
   103→        linkedSessionIds: [UUID] = [],
   104→        tags: [UUID] = [],
   105→        estimatedDuration: TimeInterval? = nil,
   106→        dueDate: Date? = nil,
   107→        createdAt: Date = Date(),
   108→        completedAt: Date? = nil
   109→    ) {
   110→        self.id = id
   111→        self.title = title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
   112→            ? "Untitled Task"
   113→            : title.trimmingCharacters(in: .whitespacesAndNewlines)
   114→        self.notes = notes.trimmingCharacters(in: .whitespacesAndNewlines)
   115→        self.isCompleted = isCompleted
   116→        self.isImportant = isImportant
   117→        self.priority = priority
   118→        self.category = category
   119→        self.subtasks = subtasks
   120→        self.linkedSessionIds = linkedSessionIds
   121→        self.tags = tags
   122→        self.estimatedDuration = estimatedDuration
   123→        self.dueDate = dueDate
   124→        self.createdAt = createdAt
   125→        self.completedAt = completedAt
   126→    }
   127→
   128→    // MARK: - Computed properties
   129→    var progress: Double {
   130→        guard !subtasks.isEmpty else { return isCompleted ? 1.0 : 0.0 }
   131→        let completed = subtasks.filter { $0.isCompleted }.count
   132→        return Double(completed) / Double(subtasks.count)
   133→    }
   134→
   135→    var totalSubtasks: Int {
   136→        subtasks.count
   137→    }
   138→
   139→    var completedSubtasks: Int {
   140→        subtasks.filter { $0.isCompleted }.count
   141→    }
   142→
   143→    var isOverdue: Bool {
   144→        guard let dueDate = dueDate, !isCompleted else { return false }
   145→        return dueDate < Date()
   146→    }
   147→}
   148→

<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
//  ProductivityHub-iOS
//
//  Unified task model merging ToDoMax + FocusWriter features
//

import Foundation
import SwiftUI

enum TaskPriority: String, Codable, CaseIterable {
    case low = "Low"
    case medium = "Medium"
    case high = "High"
    case urgent = "Urgent"

    var color: String {
        switch self {
        case .low: return "gray"
        case .medium: return "blue"
        case .high: return "orange"
        case .urgent: return "red"
        }
    }

    var swiftUIColor: Color {
        switch self {
        case .low: return .gray
        case .medium: return .blue
        case .high: return .orange
        case .urgent: return .red
        }
    }
}

enum TaskCategory: String, Codable, CaseIterable {
    case work = "Work"
    case personal = "Personal"
    case health = "Health"
    case learning = "Learning"
    case creative = "Creative"
    case finance = "Finance"
    case social = "Social"
    case other = "Other"

    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 .creative: return "paintbrush.fill"
        case .finance: return "dollarsign.circle.fill"
        case .social: return "person.2.fill"
        case .other: return "folder.fill"
        }
    }
}

struct Subtask: Identifiable, Codable, Hashable {
    var id: UUID = UUID()
    var title: String
    var isCompleted: Bool = false
    var createdAt: Date = Date()

    /// Validated initializer that ensures title is not empty
    init(id: UUID = UUID(), title: String, isCompleted: Bool = false, createdAt: Date = Date()) {
        self.id = id
        self.title = title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
            ? "Untitled Subtask"
            : title.trimmingCharacters(in: .whitespacesAndNewlines)
        self.isCompleted = isCompleted
        self.createdAt = createdAt
    }
}

struct Task: Identifiable, Codable, Hashable {
    var id: UUID = UUID()
    var title: String
    var notes: String = ""
    var isCompleted: Bool = false
    var isImportant: Bool = false
    var priority: TaskPriority = .medium
    var category: TaskCategory = .personal
    var subtasks: [Subtask] = []
    var linkedSessionIds: [UUID] = [] // Link to WorkSessions
    var tags: [UUID] = [] // Link to Tags
    var estimatedDuration: TimeInterval? = nil
    var dueDate: Date? = nil
    var createdAt: Date = Date()
    var completedAt: Date? = nil

    /// Validated initializer that ensures title is not empty
    init(
        id: UUID = UUID(),
        title: String,
        notes: String = "",
        isCompleted: Bool = false,
        isImportant: Bool = false,
        priority: TaskPriority = .medium,
        category: TaskCategory = .personal,
        subtasks: [Subtask] = [],
        linkedSessionIds: [UUID] = [],
        tags: [UUID] = [],
        estimatedDuration: TimeInterval? = nil,
        dueDate: Date? = nil,
        createdAt: Date = Date(),
        completedAt: Date? = nil
    ) {
        self.id = id
        self.title = title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
            ? "Untitled Task"
            : title.trimmingCharacters(in: .whitespacesAndNewlines)
        self.notes = notes.trimmingCharacters(in: .whitespacesAndNewlines)
        self.isCompleted = isCompleted
        self.isImportant = isImportant
        self.priority = priority
        self.category = category
        self.subtasks = subtasks
        self.linkedSessionIds = linkedSessionIds
        self.tags = tags
        self.estimatedDuration = estimatedDuration
        self.dueDate = dueDate
        self.createdAt = createdAt
        self.completedAt = completedAt
    }

    // MARK: - Computed properties
    var progress: Double {
        guard !subtasks.isEmpty else { return isCompleted ? 1.0 : 0.0 }
        let completed = subtasks.filter { $0.isCompleted }.count
        return Double(completed) / Double(subtasks.count)
    }

    var totalSubtasks: Int {
        subtasks.count
    }

    var completedSubtasks: Int {
        subtasks.filter { $0.isCompleted }.count
    }

    var isOverdue: Bool {
        guard let dueDate = dueDate, !isCompleted else { return false }
        return dueDate < Date()
    }
}


     1→//
     2→//  DataManager.swift
     3→//  ProductivityHub-iOS
     4→//
     5→//  Unified data manager with 3-layer backup system (from ToDoMax)
     6→//  TODO: Consider migrating to SwiftData for better performance and automatic persistence
     7→//  See: https://developer.apple.com/documentation/swiftdata
     8→//
     9→
    10→import Foundation
    11→import Combine
    12→
    13→@MainActor
    14→class DataManager: ObservableObject {
    15→    static let shared = DataManager()
    16→
    17→    // Logger instance
    18→    private let logger = Logger.dataManager
    19→
    20→    // Track if data has been loaded to prevent duplicate loads
    21→    private var isDataLoaded = false
    22→
    23→    // MARK: - Debounce System
    24→    // Debounce saves to avoid excessive disk writes during rapid operations
    25→    private var saveDebounceTask: _Concurrency.Task<Void, Never>?
    26→    private let saveDebounceDelay: UInt64 = 500_000_000 // 500ms in nanoseconds
    27→    private var pendingSaves: Set<String> = [] // Track which data types need saving
    28→
    29→    // MARK: - Published Properties
    30→    @Published var tasks: [Task] = []
    31→    @Published var workSessions: [WorkSession] = []
    32→    @Published var journalEntries: [JournalEntry] = []
    33→    @Published var tags: [Tag] = []
    34→    @Published var templates: [Template] = []
    35→
    36→    // MARK: - UserDefaults Keys
    37→    private let tasksKey = "ProductivityHub_Tasks"
    38→    private let tasksBackup1Key = "ProductivityHub_Tasks_backup1"
    39→    private let tasksBackup2Key = "ProductivityHub_Tasks_backup2"
    40→
    41→    private let sessionsKey = "ProductivityHub_Sessions"
    42→    private let sessionsBackup1Key = "ProductivityHub_Sessions_backup1"
    43→    private let sessionsBackup2Key = "ProductivityHub_Sessions_backup2"
    44→
    45→    private let journalKey = "ProductivityHub_Journal"
    46→    private let journalBackup1Key = "ProductivityHub_Journal_backup1"
    47→    private let journalBackup2Key = "ProductivityHub_Journal_backup2"
    48→
    49→    private let tagsKey = "ProductivityHub_Tags"
    50→    private let tagsBackup1Key = "ProductivityHub_Tags_backup1"
    51→    private let tagsBackup2Key = "ProductivityHub_Tags_backup2"
    52→
    53→    private let templatesKey = "ProductivityHub_Templates"
    54→    private let templatesBackup1Key = "ProductivityHub_Templates_backup1"
    55→    private let templatesBackup2Key = "ProductivityHub_Templates_backup2"
    56→
    57→    // MARK: - Initialization
    58→    private init() {
    59→        loadAllData()
    60→    }
    61→
    62→    // MARK: - Load All Data
    63→    func loadAllData() {
    64→        // Prevent duplicate loading
    65→        guard !isDataLoaded else {
    66→            logger.debug("Data already loaded, skipping duplicate load", category: .load)
    67→            return
    68→        }
    69→
    70→        tasks = loadWithBackup(key: tasksKey, backup1: tasksBackup1Key, backup2: tasksBackup2Key) ?? []
    71→        workSessions = loadWithBackup(key: sessionsKey, backup1: sessionsBackup1Key, backup2: sessionsBackup2Key) ?? []
    72→        journalEntries = loadWithBackup(key: journalKey, backup1: journalBackup1Key, backup2: journalBackup2Key) ?? []
    73→        tags = loadWithBackup(key: tagsKey, backup1: tagsBackup1Key, backup2: tagsBackup2Key) ?? Tag.systemTags
    74→        templates = loadWithBackup(key: templatesKey, backup1: templatesBackup1Key, backup2: templatesBackup2Key) ?? Template.systemTemplates
    75→
    76→        // Populate sample data on first launch
    77→        if tasks.isEmpty && workSessions.isEmpty && journalEntries.isEmpty {
    78→            logger.info("First launch detected - populating sample data", category: .lifecycle)
    79→            populateSampleData()
    80→        }
    81→
    82→        isDataLoaded = true
    83→
    84→        logger.info("DataManager loaded successfully", category: .load, context: [
    85→            "tasks": tasks.count,
    86→            "workSessions": workSessions.count,
    87→            "journalEntries": journalEntries.count,
    88→            "tags": tags.count,
    89→            "templates": templates.count
    90→        ])
    91→    }
    92→
    93→    // MARK: - Save All Data
    94→    func saveAllData() {
    95→        saveWithBackup(data: tasks, key: tasksKey, backup1: tasksBackup1Key, backup2: tasksBackup2Key)
    96→        saveWithBackup(data: workSessions, key: sessionsKey, backup1: sessionsBackup1Key, backup2: sessionsBackup2Key)
    97→        saveWithBackup(data: journalEntries, key: journalKey, backup1: journalBackup1Key, backup2: journalBackup2Key)
    98→        saveWithBackup(data: tags, key: tagsKey, backup1: tagsBackup1Key, backup2: tagsBackup2Key)
    99→        saveWithBackup(data: templates, key: templatesKey, backup1: templatesBackup1Key, backup2: templatesBackup2Key)
   100→
   101→        logger.info("All data saved successfully", category: .save)
   102→
   103→        // Trigger CloudKit sync if enabled
   104→        triggerCloudSync()
   105→    }
   106→
   107→    // MARK: - CloudKit Sync Integration
   108→    private func triggerCloudSync() {
   109→        let cloudKitService = CloudKitSyncService.shared
   110→        guard cloudKitService.isSyncEnabled else { return }
   111→
   112→        // Debounced sync - CloudKitSyncService handles the debouncing
   113→        _Concurrency.Task {
   114→            await cloudKitService.syncAllData(dataManager: self)
   115→        }
   116→    }
   117→
   118→    // MARK: - 3-Layer Backup System (from ToDoMax)
   119→    private func loadWithBackup<T: Decodable>(key: String, backup1: String, backup2: String) -> T? {
   120→        // Try primary key
   121→        if let data = UserDefaults.standard.data(forKey: key),
   122→           let decoded = try? JSONDecoder().decode(T.self, from: data) {
   123→            logger.debug("Loaded from primary key", category: .load, context: ["key": key])
   124→            return decoded
   125→        }
   126→
   127→        // Try backup 1
   128→        if let data = UserDefaults.standard.data(forKey: backup1),
   129→           let decoded = try? JSONDecoder().decode(T.self, from: data) {
   130→            logger.warning("Primary key corrupted, loaded from backup1", category: .backup, context: ["key": backup1])
   131→            // Restore to primary
   132→            UserDefaults.standard.set(data, forKey: key)
   133→            return decoded
   134→        }
   135→
   136→        // Try backup 2
   137→        if let data = UserDefaults.standard.data(forKey: backup2),
   138→           let decoded = try? JSONDecoder().decode(T.self, from: data) {
   139→            logger.warning("Primary and backup1 corrupted, loaded from backup2", category: .backup, context: ["key": backup2])
   140→            // Restore to primary and backup1
   141→            UserDefaults.standard.set(data, forKey: key)
   142→            UserDefaults.standard.set(data, forKey: backup1)
   143→            return decoded
   144→        }
   145→
   146→        logger.info("No existing data found for key (first launch)", category: .load, context: ["key": key])
   147→        return nil
   148→    }
   149→
   150→    // MARK: - Debounced Save Methods

<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
//  ProductivityHub-iOS
//
//  Unified data manager with 3-layer backup system (from ToDoMax)
//  TODO: Consider migrating to SwiftData for better performance and automatic persistence
//  See: https://developer.apple.com/documentation/swiftdata
//

import Foundation
import Combine

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

    // Logger instance
    private let logger = Logger.dataManager

    // Track if data has been loaded to prevent duplicate loads
    private var isDataLoaded = false

    // MARK: - Debounce System
    // Debounce saves to avoid excessive disk writes during rapid operations
    private var saveDebounceTask: _Concurrency.Task<Void, Never>?
    private let saveDebounceDelay: UInt64 = 500_000_000 // 500ms in nanoseconds
    private var pendingSaves: Set<String> = [] // Track which data types need saving

    // MARK: - Published Properties
    @Published var tasks: [Task] = []
    @Published var workSessions: [WorkSession] = []
    @Published var journalEntries: [JournalEntry] = []
    @Published var tags: [Tag] = []
    @Published var templates: [Template] = []

    // MARK: - UserDefaults Keys
    private let tasksKey = "ProductivityHub_Tasks"
    private let tasksBackup1Key = "ProductivityHub_Tasks_backup1"
    private let tasksBackup2Key = "ProductivityHub_Tasks_backup2"

    private let sessionsKey = "ProductivityHub_Sessions"
    private let sessionsBackup1Key = "ProductivityHub_Sessions_backup1"
    private let sessionsBackup2Key = "ProductivityHub_Sessions_backup2"

    private let journalKey = "ProductivityHub_Journal"
    private let journalBackup1Key = "ProductivityHub_Journal_backup1"
    private let journalBackup2Key = "ProductivityHub_Journal_backup2"

    private let tagsKey = "ProductivityHub_Tags"
    private let tagsBackup1Key = "ProductivityHub_Tags_backup1"
    private let tagsBackup2Key = "ProductivityHub_Tags_backup2"

    private let templatesKey = "ProductivityHub_Templates"
    private let templatesBackup1Key = "ProductivityHub_Templates_backup1"
    private let templatesBackup2Key = "ProductivityHub_Templates_backup2"

    // MARK: - Initialization
    private init() {
        loadAllData()
    }

    // MARK: - Load All Data
    func loadAllData() {
        // Prevent duplicate loading
        guard !isDataLoaded else {
            logger.debug("Data already loaded, skipping duplicate load", category: .load)
            return
        }

        tasks = loadWithBackup(key: tasksKey, backup1: tasksBackup1Key, backup2: tasksBackup2Key) ?? []
        workSessions = loadWithBackup(key: sessionsKey, backup1: sessionsBackup1Key, backup2: sessionsBackup2Key) ?? []
        journalEntries = loadWithBackup(key: journalKey, backup1: journalBackup1Key, backup2: journalBackup2Key) ?? []
        tags = loadWithBackup(key: tagsKey, backup1: tagsBackup1Key, backup2: tagsBackup2Key) ?? Tag.systemTags
        templates = loadWithBackup(key: templatesKey, backup1: templatesBackup1Key, backup2: templatesBackup2Key) ?? Template.systemTemplates

        // Populate sample data on first launch
        if tasks.isEmpty && workSessions.isEmpty && journalEntries.isEmpty {
            logger.info("First launch detected - populating sample data", category: .lifecycle)
            populateSampleData()
        }

        isDataLoaded = true

        logger.info("DataManager loaded successfully", category: .load, context: [
            "tasks": tasks.count,
            "workSessions": workSessions.count,
            "journalEntries": journalEntries.count,
            "tags": tags.count,
            "templates": templates.count
        ])
    }

    // MARK: - Save All Data
    func saveAllData() {
        saveWithBackup(data: tasks, key: tasksKey, backup1: tasksBackup1Key, backup2: tasksBackup2Key)
        saveWithBackup(data: workSessions, key: sessionsKey, backup1: sessionsBackup1Key, backup2: sessionsBackup2Key)
        saveWithBackup(data: journalEntries, key: journalKey, backup1: journalBackup1Key, backup2: journalBackup2Key)
        saveWithBackup(data: tags, key: tagsKey, backup1: tagsBackup1Key, backup2: tagsBackup2Key)
        saveWithBackup(data: templates, key: templatesKey, backup1: templatesBackup1Key, backup2: templatesBackup2Key)

        logger.info("All data saved successfully", category: .save)

        // Trigger CloudKit sync if enabled
        triggerCloudSync()
    }

    // MARK: - CloudKit Sync Integration
    private func triggerCloudSync() {
        let cloudKitService = CloudKitSyncService.shared
        guard cloudKitService.isSyncEnabled else { return }

        // Debounced sync - CloudKitSyncService handles the debouncing
        _Concurrency.Task {
            await cloudKitService.syncAllData(dataManager: self)
        }
    }

    // MARK: - 3-Layer Backup System (from ToDoMax)
    private func loadWithBackup<T: Decodable>(key: String, backup1: String, backup2: String) -> T? {
        // Try primary key
        if let data = UserDefaults.standard.data(forKey: key),
           let decoded = try? JSONDecoder().decode(T.self, from: data) {
            logger.debug("Loaded from primary key", category: .load, context: ["key": key])
            return decoded
        }

        // Try backup 1
        if let data = UserDefaults.standard.data(forKey: backup1),
           let decoded = try? JSONDecoder().decode(T.self, from: data) {
            logger.warning("Primary key corrupted, loaded from backup1", category: .backup, context: ["key": backup1])
            // Restore to primary
            UserDefaults.standard.set(data, forKey: key)
            return decoded
        }

        // Try backup 2
        if let data = UserDefaults.standard.data(forKey: backup2),
           let decoded = try? JSONDecoder().decode(T.self, from: data) {
            logger.warning("Primary and backup1 corrupted, loaded from backup2", category: .backup, context: ["key": backup2])
            // Restore to primary and backup1
            UserDefaults.standard.set(data, forKey: key)
            UserDefaults.standard.set(data, forKey: backup1)
            return decoded
        }

        logger.info("No existing data found for key (first launch)", category: .load, context: ["key": key])
        return nil
    }

    // MARK: - Debounced Save Methods


     1→//
     2→//  NotificationService.swift
     3→//  ProductivityHub-iOS
     4→//
     5→//  Service for managing local notifications
     6→//
     7→
     8→import Foundation
     9→@preconcurrency import UserNotifications
    10→
    11→@MainActor
    12→class NotificationService: NSObject, ObservableObject {
    13→    static let shared = NotificationService()
    14→
    15→    private let logger = Logger.notification
    16→
    17→    @Published var isAuthorized = false
    18→
    19→    private override init() {
    20→        super.init()
    21→        checkAuthorizationStatus()
    22→    }
    23→
    24→    // MARK: - Authorization
    25→
    26→    func requestAuthorization() async -> Bool {
    27→        do {
    28→            let granted = try await UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge])
    29→            isAuthorized = granted
    30→            logger.info("Notification authorization \(granted ? "granted" : "denied")", category: .general)
    31→            return granted
    32→        } catch {
    33→            logger.error("Failed to request notification authorization", category: .error, error: error)
    34→            isAuthorized = false
    35→            return false
    36→        }
    37→    }
    38→
    39→    func checkAuthorizationStatus() {
    40→        UNUserNotificationCenter.current().getNotificationSettings { [weak self] settings in
    41→            DispatchQueue.main.async {
    42→                self?.isAuthorized = settings.authorizationStatus == .authorized
    43→            }
    44→        }
    45→    }
    46→
    47→    // MARK: - Category Registration
    48→
    49→    func registerNotificationCategories() {
    50→        let timerCategory = UNNotificationCategory(
    51→            identifier: "TIMER_COMPLETE",
    52→            actions: [],
    53→            intentIdentifiers: []
    54→        )
    55→
    56→        let taskDueCategory = UNNotificationCategory(
    57→            identifier: "TASK_DUE_SOON",
    58→            actions: [],
    59→            intentIdentifiers: []
    60→        )
    61→
    62→        let overdueCategory = UNNotificationCategory(
    63→            identifier: "OVERDUE_TASK",
    64→            actions: [],
    65→            intentIdentifiers: []
    66→        )
    67→
    68→        let journalCategory = UNNotificationCategory(
    69→            identifier: "JOURNAL_REMINDER",
    70→            actions: [],
    71→            intentIdentifiers: []
    72→        )
    73→
    74→        let dailySummaryCategory = UNNotificationCategory(
    75→            identifier: "DAILY_SUMMARY",
    76→            actions: [],
    77→            intentIdentifiers: []
    78→        )
    79→
    80→        let weeklyCategory = UNNotificationCategory(
    81→            identifier: "WEEKLY_PROGRESS",
    82→            actions: [],
    83→            intentIdentifiers: []
    84→        )
    85→
    86→        UNUserNotificationCenter.current().setNotificationCategories([
    87→            timerCategory, taskDueCategory, overdueCategory,
    88→            journalCategory, dailySummaryCategory, weeklyCategory
    89→        ])
    90→
    91→        logger.info("Notification categories registered", category: .general)
    92→    }
    93→
    94→    // MARK: - Schedule Notification
    95→
    96→    func scheduleTimerCompletion(sessionType: String, duration: TimeInterval) {
    97→        guard isAuthorized else {
    98→            logger.warning("Cannot schedule notification - not authorized", category: .general)
    99→            return
   100→        }
   101→
   102→        // Create notification content
   103→        let content = UNMutableNotificationContent()
   104→        content.title = "Focus Session Complete!"
   105→        content.body = "\(sessionType) session finished. Great work!"
   106→        content.sound = .default
   107→        content.badge = 1
   108→        content.categoryIdentifier = "TIMER_COMPLETE"
   109→
   110→        // Create trigger (fire after duration)
   111→        let trigger = UNTimeIntervalNotificationTrigger(timeInterval: duration, repeats: false)
   112→
   113→        // Create request
   114→        let request = UNNotificationRequest(
   115→            identifier: "timer-completion",
   116→            content: content,
   117→            trigger: trigger
   118→        )
   119→
   120→        // Schedule notification

<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>

//
//  NotificationService.swift
//  ProductivityHub-iOS
//
//  Service for managing local notifications
//

import Foundation
@preconcurrency import UserNotifications

@MainActor
class NotificationService: NSObject, ObservableObject {
    static let shared = NotificationService()

    private let logger = Logger.notification

    @Published var isAuthorized = false

    private override init() {
        super.init()
        checkAuthorizationStatus()
    }

    // MARK: - Authorization

    func requestAuthorization() async -> Bool {
        do {
            let granted = try await UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge])
            isAuthorized = granted
            logger.info("Notification authorization \(granted ? "granted" : "denied")", category: .general)
            return granted
        } catch {
            logger.error("Failed to request notification authorization", category: .error, error: error)
            isAuthorized = false
            return false
        }
    }

    func checkAuthorizationStatus() {
        UNUserNotificationCenter.current().getNotificationSettings { [weak self] settings in
            DispatchQueue.main.async {
                self?.isAuthorized = settings.authorizationStatus == .authorized
            }
        }
    }

    // MARK: - Category Registration

    func registerNotificationCategories() {
        let timerCategory = UNNotificationCategory(
            identifier: "TIMER_COMPLETE",
            actions: [],
            intentIdentifiers: []
        )

        let taskDueCategory = UNNotificationCategory(
            identifier: "TASK_DUE_SOON",
            actions: [],
            intentIdentifiers: []
        )

        let overdueCategory = UNNotificationCategory(
            identifier: "OVERDUE_TASK",
            actions: [],
            intentIdentifiers: []
        )

        let journalCategory = UNNotificationCategory(
            identifier: "JOURNAL_REMINDER",
            actions: [],
            intentIdentifiers: []
        )

        let dailySummaryCategory = UNNotificationCategory(
            identifier: "DAILY_SUMMARY",
            actions: [],
            intentIdentifiers: []
        )

        let weeklyCategory = UNNotificationCategory(
            identifier: "WEEKLY_PROGRESS",
            actions: [],
            intentIdentifiers: []
        )

        UNUserNotificationCenter.current().setNotificationCategories([
            timerCategory, taskDueCategory, overdueCategory,
            journalCategory, dailySummaryCategory, weeklyCategory
        ])

        logger.info("Notification categories registered", category: .general)
    }

    // MARK: - Schedule Notification

    func scheduleTimerCompletion(sessionType: String, duration: TimeInterval) {
        guard isAuthorized else {
            logger.warning("Cannot schedule notification - not authorized", category: .general)
            return
        }

        // Create notification content
        let content = UNMutableNotificationContent()
        content.title = "Focus Session Complete!"
        content.body = "\(sessionType) session finished. Great work!"
        content.sound = .default
        content.badge = 1
        content.categoryIdentifier = "TIMER_COMPLETE"

        // Create trigger (fire after duration)
        let trigger = UNTimeIntervalNotificationTrigger(timeInterval: duration, repeats: false)

        // Create request
        let request = UNNotificationRequest(
            identifier: "timer-completion",
            content: content,
            trigger: trigger
        )

        // Schedule notification


Let me continue exploring key components and patterns:


     1→//
     2→//  FocusView.swift
     3→//  ProductivityHub-iOS
     4→//
     5→//  Tab 1: Focus Timer (Deep Work + Pomodoro)
     6→//
     7→
     8→import SwiftUI
     9→
    10→struct FocusView: View {
    11→    @EnvironmentObject var dataManager: DataManager
    12→    @EnvironmentObject var toastManager: ToastManager
    13→    @EnvironmentObject var timerService: TimerService  // NEW: Use shared instance from App for background support
    14→    @StateObject private var notificationService = NotificationService.shared
    15→    @StateObject private var onboardingManager = OnboardingManager.shared
    16→
    17→    @State private var selectedSessionType: SessionType = .deepWork
    18→    @State private var completionScale: CGFloat = 1.0  // For completion animation
    19→    @State private var minusButtonScale: CGFloat = 1.0  // For bounce effect
    20→    @State private var plusButtonScale: CGFloat = 1.0   // For bounce effect
    21→    @State private var showTaskPicker = false
    22→    @State private var selectedTasks: [UUID] = []
    23→    @State private var showSessionHistory = false
    24→    @State private var sessionNotes = ""
    25→    @State private var notesIsFocused = false
    26→    @State private var showCustomDuration = false
    27→    @State private var customDurationMinutes: Int = 30
    28→    @State private var useCustomDuration = false
    29→    @State private var showNotificationAlert = false
    30→    @State private var showStopConfirmation = false
    31→
    32→    @Environment(\.horizontalSizeClass) private var horizontalSizeClass
    33→
    34→    // Responsive padding for Mac/iPad
    35→    private var horizontalPadding: CGFloat {
    36→        horizontalSizeClass == .regular ? 40 : 16
    37→    }
    38→
    39→    var body: some View {
    40→        NavigationView {
    41→            ZStack {
    42→                // Background gradient (adaptive for dark mode)
    43→                LinearGradient(
    44→                    colors: [
    45→                        Color(.systemBackground),
    46→                        sessionColor.opacity(0.05)
    47→                    ],
    48→                    startPoint: .topLeading,
    49→                    endPoint: .bottomTrailing
    50→                )
    51→                .ignoresSafeArea()
    52→
    53→                ScrollView {
    54→                    VStack(spacing: 30) {
    55→                        // Session Type Picker
    56→                        if !timerService.isRunning {
    57→                            sessionTypePicker
    58→                        }
    59→
    60→                        // Timer Display
    61→                        timerDisplay
    62→
    63→                        // Session Notes (when timer is active)
    64→                        if timerService.currentSession != nil {
    65→                            sessionNotesSection
    66→                        }
    67→
    68→                        // Controls
    69→                        timerControls
    70→
    71→                        // Onboarding Tip for linking tasks
    72→                        if !onboardingManager.hasShownFocusLinkTip && selectedTasks.isEmpty && !dataManager.tasks.isEmpty {
    73→                            OnboardingTip(
    74→                                icon: "link.circle.fill",
    75→                                title: "Link Tasks to Your Session",
    76→                                message: "Connect specific tasks to your focus session to track what you're working on. Tap the + button below!",
    77→                                accentColor: .purple,
    78→                                onDismiss: {
    79→                                    onboardingManager.dismissFocusLinkTip()
    80→                                }
    81→                            )
    82→                            .transition(.move(edge: .top).combined(with: .opacity))
    83→                        }
    84→
    85→                        // Linked Tasks (always show to display empty state)
    86→                        linkedTasksSection
    87→
    88→                        // Session Stats
    89→                        sessionStats
    90→
    91→                        Spacer(minLength: 50)
    92→                    }
    93→                    .padding(.horizontal, horizontalPadding)
    94→                    .padding(.vertical)
    95→                    .frame(maxWidth: horizontalSizeClass == .regular ? 700 : .infinity)
    96→                    .frame(maxWidth: .infinity)
    97→                }
    98→            }
    99→            .navigationTitle("Focus")
   100→            .toolbar {
   101→                ToolbarItem(placement: .topBarTrailing) {
   102→                    Button(action: { showSessionHistory = true }) {
   103→                        Label("History", systemImage: "clock.arrow.circlepath")
   104→                            .labelStyle(.iconOnly)
   105→                    }
   106→                }
   107→            }
   108→            .sheet(isPresented: $showTaskPicker) {
   109→                TaskPickerSheet(selectedTasks: $selectedTasks, dataManager: dataManager)
   110→            }
   111→            .sheet(isPresented: $showSessionHistory) {
   112→                SessionHistoryView(sessions: dataManager.workSessions) { session in
   113→                    dataManager.deleteWorkSession(session)
   114→                    HapticManager.notification(.success)
   115→                }
   116→            }
   117→            .alert("Notifications Disabled", isPresented: $showNotificationAlert) {
   118→                Button("Settings", action: {
   119→                    if let url = URL(string: UIApplication.openSettingsURLString) {
   120→                        UIApplication.shared.open(url)
   121→                    }
   122→                })
   123→                Button("OK", role: .cancel) { }
   124→            } message: {
   125→                Text("Enable notifications in Settings to get alerts when your focus session completes.")
   126→            }
   127→            .alert("Stop Session?", isPresented: $showStopConfirmation) {
   128→                Button("Continue Focusing", role: .cancel) { }
   129→                Button("Stop", role: .destructive) {
   130→                    performStopTimer()
   131→                }
   132→            } message: {
   133→                let elapsedMinutes = timerService.elapsedTime / 60
   134→                Text("You've been focused for \(Int(elapsedMinutes)) minutes. Are you sure you want to stop?")
   135→            }
   136→            .onAppear {
   137→                // Clear badge when view appears
   138→                notificationService.clearBadge()
   139→            }
   140→        }
   141→    }
   142→
   143→    // MARK: - Session Type Picker
   144→    private var sessionTypePicker: some View {
   145→        VStack(alignment: .leading, spacing: 12) {
   146→            Text("Choose Your Session")
   147→                .font(.headline)
   148→                .foregroundColor(.primary)
   149→
   150→            // Primary Session Types (Deep Work + Pomodoro)

<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>

//
//  FocusView.swift
//  ProductivityHub-iOS
//
//  Tab 1: Focus Timer (Deep Work + Pomodoro)
//

import SwiftUI

struct FocusView: View {
    @EnvironmentObject var dataManager: DataManager
    @EnvironmentObject var toastManager: ToastManager
    @EnvironmentObject var timerService: TimerService  // NEW: Use shared instance from App for background support
    @StateObject private var notificationService = NotificationService.shared
    @StateObject private var onboardingManager = OnboardingManager.shared

    @State private var selectedSessionType: SessionType = .deepWork
    @State private var completionScale: CGFloat = 1.0  // For completion animation
    @State private var minusButtonScale: CGFloat = 1.0  // For bounce effect
    @State private var plusButtonScale: CGFloat = 1.0   // For bounce effect
    @State private var showTaskPicker = false
    @State private var selectedTasks: [UUID] = []
    @State private var showSessionHistory = false
    @State private var sessionNotes = ""
    @State private var notesIsFocused = false
    @State private var showCustomDuration = false
    @State private var customDurationMinutes: Int = 30
    @State private var useCustomDuration = false
    @State private var showNotificationAlert = false
    @State private var showStopConfirmation = false

    @Environment(\.horizontalSizeClass) private var horizontalSizeClass

    // Responsive padding for Mac/iPad
    private var horizontalPadding: CGFloat {
        horizontalSizeClass == .regular ? 40 : 16
    }

    var body: some View {
        NavigationView {
            ZStack {
                // Background gradient (adaptive for dark mode)
                LinearGradient(
                    colors: [
                        Color(.systemBackground),
                        sessionColor.opacity(0.05)
                    ],
                    startPoint: .topLeading,
                    endPoint: .bottomTrailing
                )
                .ignoresSafeArea()

                ScrollView {
                    VStack(spacing: 30) {
                        // Session Type Picker
                        if !timerService.isRunning {
                            sessionTypePicker
                        }

                        // Timer Display
                        timerDisplay

                        // Session Notes (when timer is active)
                        if timerService.currentSession != nil {
                            sessionNotesSection
                        }

                        // Controls
                        timerControls

                        // Onboarding Tip for linking tasks
                        if !onboardingManager.hasShownFocusLinkTip && selectedTasks.isEmpty && !dataManager.tasks.isEmpty {
                            OnboardingTip(
                                icon: "link.circle.fill",
                                title: "Link Tasks to Your Session",
                                message: "Connect specific tasks to your focus session to track what you're working on. Tap the + button below!",
                                accentColor: .purple,
                                onDismiss: {
                                    onboardingManager.dismissFocusLinkTip()
                                }
                            )
                            .transition(.move(edge: .top).combined(with: .opacity))
                        }

                        // Linked Tasks (always show to display empty state)
                        linkedTasksSection

                        // Session Stats
                        sessionStats

                        Spacer(minLength: 50)
                    }
                    .padding(.horizontal, horizontalPadding)
                    .padding(.vertical)
                    .frame(maxWidth: horizontalSizeClass == .regular ? 700 : .infinity)
                    .frame(maxWidth: .infinity)
                }
            }
            .navigationTitle("Focus")
            .toolbar {
                ToolbarItem(placement: .topBarTrailing) {
                    Button(action: { showSessionHistory = true }) {
                        Label("History", systemImage: "clock.arrow.circlepath")
                            .labelStyle(.iconOnly)
                    }
                }
            }
            .sheet(isPresented: $showTaskPicker) {
                TaskPickerSheet(selectedTasks: $selectedTasks, dataManager: dataManager)
            }
            .sheet(isPresented: $showSessionHistory) {
                SessionHistoryView(sessions: dataManager.workSessions) { session in
                    dataManager.deleteWorkSession(session)
                    HapticManager.notification(.success)
                }
            }
            .alert("Notifications Disabled", isPresented: $showNotificationAlert) {
                Button("Settings", action: {
                    if let url = URL(string: UIApplication.openSettingsURLString) {
                        UIApplication.shared.open(url)
                    }
                })
                Button("OK", role: .cancel) { }
            } message: {
                Text("Enable notifications in Settings to get alerts when your focus session completes.")
            }
            .alert("Stop Session?", isPresented: $showStopConfirmation) {
                Button("Continue Focusing", role: .cancel) { }
                Button("Stop", role: .destructive) {
                    performStopTimer()
                }
            } message: {
                let elapsedMinutes = timerService.elapsedTime / 60
                Text("You've been focused for \(Int(elapsedMinutes)) minutes. Are you sure you want to stop?")
            }
            .onAppear {
                // Clear badge when view appears
                notificationService.clearBadge()
            }
        }
    }

    // MARK: - Session Type Picker
    private var sessionTypePicker: some View {
        VStack(alignment: .leading, spacing: 12) {
            Text("Choose Your Session")
                .font(.headline)
                .foregroundColor(.primary)

            // Primary Session Types (Deep Work + Pomodoro)


     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)

<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)


     1→//
     2→//  ProductivityHubApp.swift
     3→//  ProductivityHub-iOS
     4→//
     5→//  Created by Claude Code
     6→//  Main app entry point with CloudKit real-time sync
     7→//
     8→
     9→import SwiftUI
    10→import CoreSpotlight
    11→import CloudKit
    12→import UserNotifications
    13→
    14→// MARK: - Quick Action Types
    15→enum QuickActionType: String {
    16→    case newTask = "NewTaskAction"
    17→    case startFocus = "StartFocusAction"
    18→    case newJournal = "NewJournalAction"
    19→    case viewInsights = "ViewInsightsAction"
    20→}
    21→
    22→// MARK: - Quick Action Manager
    23→class QuickActionManager: ObservableObject {
    24→    static let shared = QuickActionManager()
    25→
    26→    @Published var selectedTab: Int = 0
    27→    @Published var shouldShowNewTask: Bool = false
    28→    @Published var shouldShowNewJournal: Bool = false
    29→    @Published var shouldStartFocus: Bool = false
    30→
    31→    func handleQuickAction(_ shortcutItem: UIApplicationShortcutItem) {
    32→        guard let actionType = QuickActionType(rawValue: shortcutItem.type) else { return }
    33→
    34→        DispatchQueue.main.async {
    35→            switch actionType {
    36→            case .newTask:
    37→                self.selectedTab = 1  // Tasks tab
    38→                self.shouldShowNewTask = true
    39→            case .startFocus:
    40→                self.selectedTab = 0  // Focus tab
    41→                self.shouldStartFocus = true
    42→            case .newJournal:
    43→                self.selectedTab = 2  // Journal tab
    44→                self.shouldShowNewJournal = true
    45→            case .viewInsights:
    46→                self.selectedTab = 3  // Insights tab
    47→            }
    48→            HapticManager.impact(.light)
    49→        }
    50→    }
    51→
    52→    func setupQuickActions() {
    53→        UIApplication.shared.shortcutItems = [
    54→            UIApplicationShortcutItem(
    55→                type: QuickActionType.newTask.rawValue,
    56→                localizedTitle: "New Task",
    57→                localizedSubtitle: "Create a new task",
    58→                icon: UIApplicationShortcutIcon(systemImageName: "plus.circle"),
    59→                userInfo: nil
    60→            ),
    61→            UIApplicationShortcutItem(
    62→                type: QuickActionType.startFocus.rawValue,
    63→                localizedTitle: "Start Focus",
    64→                localizedSubtitle: "Begin a focus session",
    65→                icon: UIApplicationShortcutIcon(systemImageName: "timer"),
    66→                userInfo: nil
    67→            ),
    68→            UIApplicationShortcutItem(
    69→                type: QuickActionType.newJournal.rawValue,
    70→                localizedTitle: "New Entry",
    71→                localizedSubtitle: "Write a journal entry",
    72→                icon: UIApplicationShortcutIcon(systemImageName: "book"),
    73→                userInfo: nil
    74→            ),
    75→            UIApplicationShortcutItem(
    76→                type: QuickActionType.viewInsights.rawValue,
    77→                localizedTitle: "View Insights",
    78→                localizedSubtitle: "See your statistics",
    79→                icon: UIApplicationShortcutIcon(systemImageName: "chart.bar"),
    80→                userInfo: nil
    81→            )
    82→        ]
    83→    }
    84→}
    85→
    86→// MARK: - App Delegate
    87→class AppDelegate: NSObject, UIApplicationDelegate {
    88→
    89→    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    90→        // 1. Set notification delegate FIRST (required for foreground display & tap handling)
    91→        UNUserNotificationCenter.current().delegate = self
    92→
    93→        // 2. Register notification categories
    94→        _Concurrency.Task { @MainActor in
    95→            NotificationService.shared.registerNotificationCategories()
    96→        }
    97→
    98→        // 3. Request authorization for remote notifications (CloudKit sync)
    99→        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in
   100→            if granted {
   101→                DispatchQueue.main.async {
   102→                    application.registerForRemoteNotifications()
   103→                }
   104→            }
   105→            if let error = error {
   106→                print("❌ Notification authorization error: \(error)")
   107→            }
   108→        }
   109→        return true
   110→    }
   111→
   112→    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
   113→        // Handle shortcut item if launched from quick action
   114→        if let shortcutItem = options.shortcutItem {
   115→            QuickActionManager.shared.handleQuickAction(shortcutItem)
   116→        }
   117→
   118→        let configuration = UISceneConfiguration(name: nil, sessionRole: connectingSceneSession.role)
   119→        configuration.delegateClass = SceneDelegate.self
   120→        return configuration

<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>

//
//  ProductivityHubApp.swift
//  ProductivityHub-iOS
//
//  Created by Claude Code
//  Main app entry point with CloudKit real-time sync
//

import SwiftUI
import CoreSpotlight
import CloudKit
import UserNotifications

// MARK: - Quick Action Types
enum QuickActionType: String {
    case newTask = "NewTaskAction"
    case startFocus = "StartFocusAction"
    case newJournal = "NewJournalAction"
    case viewInsights = "ViewInsightsAction"
}

// MARK: - Quick Action Manager
class QuickActionManager: ObservableObject {
    static let shared = QuickActionManager()

    @Published var selectedTab: Int = 0
    @Published var shouldShowNewTask: Bool = false
    @Published var shouldShowNewJournal: Bool = false
    @Published var shouldStartFocus: Bool = false

    func handleQuickAction(_ shortcutItem: UIApplicationShortcutItem) {
        guard let actionType = QuickActionType(rawValue: shortcutItem.type) else { return }

        DispatchQueue.main.async {
            switch actionType {
            case .newTask:
                self.selectedTab = 1  // Tasks tab
                self.shouldShowNewTask = true
            case .startFocus:
                self.selectedTab = 0  // Focus tab
                self.shouldStartFocus = true
            case .newJournal:
                self.selectedTab = 2  // Journal tab
                self.shouldShowNewJournal = true
            case .viewInsights:
                self.selectedTab = 3  // Insights tab
            }
            HapticManager.impact(.light)
        }
    }

    func setupQuickActions() {
        UIApplication.shared.shortcutItems = [
            UIApplicationShortcutItem(
                type: QuickActionType.newTask.rawValue,
                localizedTitle: "New Task",
                localizedSubtitle: "Create a new task",
                icon: UIApplicationShortcutIcon(systemImageName: "plus.circle"),
                userInfo: nil
            ),
            UIApplicationShortcutItem(
                type: QuickActionType.startFocus.rawValue,
                localizedTitle: "Start Focus",
                localizedSubtitle: "Begin a focus session",
                icon: UIApplicationShortcutIcon(systemImageName: "timer"),
                userInfo: nil
            ),
            UIApplicationShortcutItem(
                type: QuickActionType.newJournal.rawValue,
                localizedTitle: "New Entry",
                localizedSubtitle: "Write a journal entry",
                icon: UIApplicationShortcutIcon(systemImageName: "book"),
                userInfo: nil
            ),
            UIApplicationShortcutItem(
                type: QuickActionType.viewInsights.rawValue,
                localizedTitle: "View Insights",
                localizedSubtitle: "See your statistics",
                icon: UIApplicationShortcutIcon(systemImageName: "chart.bar"),
                userInfo: nil
            )
        ]
    }
}

// MARK: - App Delegate
class AppDelegate: NSObject, UIApplicationDelegate {

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // 1. Set notification delegate FIRST (required for foreground display & tap handling)
        UNUserNotificationCenter.current().delegate = self

        // 2. Register notification categories
        _Concurrency.Task { @MainActor in
            NotificationService.shared.registerNotificationCategories()
        }

        // 3. Request authorization for remote notifications (CloudKit sync)
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in
            if granted {
                DispatchQueue.main.async {
                    application.registerForRemoteNotifications()
                }
            }
            if let error = error {
                print("❌ Notification authorization error: \(error)")
            }
        }
        return true
    }

    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        // Handle shortcut item if launched from quick action
        if let shortcutItem = options.shortcutItem {
            QuickActionManager.shared.handleQuickAction(shortcutItem)
        }

        let configuration = UISceneConfiguration(name: nil, sessionRole: connectingSceneSession.role)
        configuration.delegateClass = SceneDelegate.self
        return configuration


Let me explore a few more key patterns:


Bash

find /Users/neog/Apps/iOS -maxdepth 2 -name "CLAUDE.md" -type f | wc -l

Count CLAUDE.md files across iOS apps


     1→//
     2→//  TimerService.swift
     3→//  ProductivityHub-iOS
     4→//
     5→//  Timer service for Deep Work and Pomodoro sessions (from FocusWriter)
     6→//  Uses modern Swift Concurrency for better performance
     7→//
     8→
     9→import Foundation
    10→import Combine
    11→import UIKit
    12→
    13→@MainActor
    14→class TimerService: ObservableObject {
    15→    @Published var currentSession: WorkSession?
    16→    @Published var timeRemaining: TimeInterval = 0
    17→    @Published var isRunning: Bool = false
    18→    @Published var sessionType: SessionType = .deepWork
    19→    @Published var timerDidComplete: Bool = false  // Triggers completion animation
    20→
    21→    /// Computed elapsed time since session started
    22→    var elapsedTime: TimeInterval {
    23→        guard let session = currentSession else { return 0 }
    24→        return session.targetDuration - timeRemaining
    25→    }
    26→
    27→    private var timer: Timer?
    28→    private var startDate: Date?
    29→    private var targetEndDate: Date?  // NEW: Timestamp-based tracking for background support
    30→    private let logger = Logger.timerService
    31→
    32→    // NEW: UserDefaults persistence for background recovery
    33→    private let defaults = UserDefaults.standard
    34→    private let endDateKey = "TimerService_TargetEndDate"
    35→    private let wasRunningKey = "TimerService_WasRunning"
    36→    private let sessionDataKey = "TimerService_SessionData"
    37→
    38→    // MARK: - Start Timer
    39→    func startTimer(type: SessionType) {
    40→        sessionType = type
    41→        timeRemaining = type.defaultDuration
    42→        startDate = Date()
    43→
    44→        // NEW: Calculate target end date for background support
    45→        targetEndDate = Date().addingTimeInterval(type.defaultDuration)
    46→
    47→        let session = WorkSession(
    48→            sessionType: type,
    49→            duration: 0,
    50→            targetDuration: type.defaultDuration,
    51→            startTime: Date()
    52→        )
    53→
    54→        currentSession = session
    55→        isRunning = true
    56→
    57→        // NEW: Persist timer state for background recovery
    58→        saveTimerState()
    59→
    60→        logger.info("Timer started", category: .timerStart, context: [
    61→            "sessionType": type.rawValue,
    62→            "duration": "\(Int(type.defaultDuration / 60))min",
    63→            "sessionId": session.id.uuidString,
    64→            "targetEndDate": targetEndDate?.description ?? "nil"
    65→        ])
    66→
    67→        HapticManager.timerStarted()
    68→
    69→        // VoiceOver announcement
    70→        let minutes = Int(type.defaultDuration / 60)
    71→        AccessibilityHelpers.announce("\(type.rawValue) session started. \(minutes) minutes remaining.")
    72→
    73→        startUITimer()
    74→    }
    75→
    76→    // MARK: - Start Timer with Custom Duration
    77→    func startTimerWithCustomDuration(type: SessionType, customMinutes: Int) {
    78→        sessionType = type
    79→        let customDuration = TimeInterval(customMinutes * 60)
    80→        timeRemaining = customDuration
    81→        startDate = Date()
    82→
    83→        // NEW: Calculate target end date for background support
    84→        targetEndDate = Date().addingTimeInterval(customDuration)
    85→
    86→        let session = WorkSession(
    87→            sessionType: type,
    88→            duration: 0,
    89→            targetDuration: customDuration,
    90→            startTime: Date()
    91→        )
    92→
    93→        currentSession = session
    94→        isRunning = true
    95→
    96→        // NEW: Persist timer state for background recovery
    97→        saveTimerState()
    98→
    99→        logger.info("Timer started with custom duration", category: .timerStart, context: [
   100→            "sessionType": type.rawValue,
   101→            "customDuration": "\(customMinutes)min",
   102→            "sessionId": session.id.uuidString,
   103→            "targetEndDate": targetEndDate?.description ?? "nil"
   104→        ])
   105→
   106→        HapticManager.timerStarted()
   107→        startUITimer()
   108→    }
   109→
   110→    // MARK: - Pause Timer
   111→    func pauseTimer() {
   112→        isRunning = false
   113→        timer?.invalidate()
   114→        timer = nil
   115→
   116→        // NEW: Update persisted state to reflect pause
   117→        defaults.set(false, forKey: wasRunningKey)
   118→
   119→        HapticManager.impact(.medium)
   120→

<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>

//
//  TimerService.swift
//  ProductivityHub-iOS
//
//  Timer service for Deep Work and Pomodoro sessions (from FocusWriter)
//  Uses modern Swift Concurrency for better performance
//

import Foundation
import Combine
import UIKit

@MainActor
class TimerService: ObservableObject {
    @Published var currentSession: WorkSession?
    @Published var timeRemaining: TimeInterval = 0
    @Published var isRunning: Bool = false
    @Published var sessionType: SessionType = .deepWork
    @Published var timerDidComplete: Bool = false  // Triggers completion animation

    /// Computed elapsed time since session started
    var elapsedTime: TimeInterval {
        guard let session = currentSession else { return 0 }
        return session.targetDuration - timeRemaining
    }

    private var timer: Timer?
    private var startDate: Date?
    private var targetEndDate: Date?  // NEW: Timestamp-based tracking for background support
    private let logger = Logger.timerService

    // NEW: UserDefaults persistence for background recovery
    private let defaults = UserDefaults.standard
    private let endDateKey = "TimerService_TargetEndDate"
    private let wasRunningKey = "TimerService_WasRunning"
    private let sessionDataKey = "TimerService_SessionData"

    // MARK: - Start Timer
    func startTimer(type: SessionType) {
        sessionType = type
        timeRemaining = type.defaultDuration
        startDate = Date()

        // NEW: Calculate target end date for background support
        targetEndDate = Date().addingTimeInterval(type.defaultDuration)

        let session = WorkSession(
            sessionType: type,
            duration: 0,
            targetDuration: type.defaultDuration,
            startTime: Date()
        )

        currentSession = session
        isRunning = true

        // NEW: Persist timer state for background recovery
        saveTimerState()

        logger.info("Timer started", category: .timerStart, context: [
            "sessionType": type.rawValue,
            "duration": "\(Int(type.defaultDuration / 60))min",
            "sessionId": session.id.uuidString,
            "targetEndDate": targetEndDate?.description ?? "nil"
        ])

        HapticManager.timerStarted()

        // VoiceOver announcement
        let minutes = Int(type.defaultDuration / 60)
        AccessibilityHelpers.announce("\(type.rawValue) session started. \(minutes) minutes remaining.")

        startUITimer()
    }

    // MARK: - Start Timer with Custom Duration
    func startTimerWithCustomDuration(type: SessionType, customMinutes: Int) {
        sessionType = type
        let customDuration = TimeInterval(customMinutes * 60)
        timeRemaining = customDuration
        startDate = Date()

        // NEW: Calculate target end date for background support
        targetEndDate = Date().addingTimeInterval(customDuration)

        let session = WorkSession(
            sessionType: type,
            duration: 0,
            targetDuration: customDuration,
            startTime: Date()
        )

        currentSession = session
        isRunning = true

        // NEW: Persist timer state for background recovery
        saveTimerState()

        logger.info("Timer started with custom duration", category: .timerStart, context: [
            "sessionType": type.rawValue,
            "customDuration": "\(customMinutes)min",
            "sessionId": session.id.uuidString,
            "targetEndDate": targetEndDate?.description ?? "nil"
        ])

        HapticManager.timerStarted()
        startUITimer()
    }

    // MARK: - Pause Timer
    func pauseTimer() {
        isRunning = false
        timer?.invalidate()
        timer = nil

        // NEW: Update persisted state to reflect pause
        defaults.set(false, forKey: wasRunningKey)

        HapticManager.impact(.medium)


     1→//
     2→//  HapticManager.swift
     3→//  ProductivityHub-iOS
     4→//
     5→//  Centralized haptic feedback manager for better UX
     6→//  Optimized with prepared generators for minimal latency
     7→//  Compatible with iPhone, iPad, and Mac Catalyst
     8→//
     9→
    10→import UIKit
    11→
    12→enum HapticManager {
    13→    // MARK: - User Preference
    14→    private static let hapticsEnabledKey = "ProductivityHub_HapticsEnabled"
    15→
    16→    /// Check if haptics are enabled (defaults to true)
    17→    static var isEnabled: Bool {
    18→        get {
    19→            // Default to true if never set
    20→            if UserDefaults.standard.object(forKey: hapticsEnabledKey) == nil {
    21→                return true
    22→            }
    23→            return UserDefaults.standard.bool(forKey: hapticsEnabledKey)
    24→        }
    25→        set {
    26→            UserDefaults.standard.set(newValue, forKey: hapticsEnabledKey)
    27→        }
    28→    }
    29→
    30→    /// Check if device supports haptic feedback
    31→    private static var supportsHaptics: Bool {
    32→        #if targetEnvironment(macCatalyst)
    33→        return false // Mac doesn't support haptics
    34→        #else
    35→        // Only iPhones with Taptic Engine support haptics (iPhone 7+)
    36→        return UIDevice.current.userInterfaceIdiom == .phone
    37→        #endif
    38→    }
    39→
    40→    // MARK: - Prepared Generators (for better performance)
    41→    #if !targetEnvironment(macCatalyst)
    42→    private static let lightImpact = UIImpactFeedbackGenerator(style: .light)
    43→    private static let mediumImpact = UIImpactFeedbackGenerator(style: .medium)
    44→    private static let heavyImpact = UIImpactFeedbackGenerator(style: .heavy)
    45→    private static let softImpact = UIImpactFeedbackGenerator(style: .soft)
    46→    private static let rigidImpact = UIImpactFeedbackGenerator(style: .rigid)
    47→    private static let notificationGenerator = UINotificationFeedbackGenerator()
    48→    private static let selectionGenerator = UISelectionFeedbackGenerator()
    49→    #endif
    50→
    51→    // MARK: - Preparation (call on app launch or before expected use)
    52→    static func prepare() {
    53→        #if !targetEnvironment(macCatalyst)
    54→        guard supportsHaptics else { return }
    55→        lightImpact.prepare()
    56→        mediumImpact.prepare()
    57→        heavyImpact.prepare()
    58→        softImpact.prepare()
    59→        rigidImpact.prepare()
    60→        notificationGenerator.prepare()
    61→        selectionGenerator.prepare()
    62→        #endif
    63→    }
    64→
    65→    // MARK: - Impact Feedback
    66→    static func impact(_ style: UIImpactFeedbackGenerator.FeedbackStyle = .medium) {
    67→        #if !targetEnvironment(macCatalyst)
    68→        // Check if haptics are enabled by user preference and device support
    69→        guard isEnabled, supportsHaptics else { return }
    70→
    71→        switch style {
    72→        case .light:
    73→            lightImpact.impactOccurred()
    74→            lightImpact.prepare() // Prepare for next use
    75→        case .medium:
    76→            mediumImpact.impactOccurred()
    77→            mediumImpact.prepare()
    78→        case .heavy:
    79→            heavyImpact.impactOccurred()
    80→            heavyImpact.prepare()
    81→        case .soft:
    82→            softImpact.impactOccurred()
    83→            softImpact.prepare()
    84→        case .rigid:
    85→            rigidImpact.impactOccurred()
    86→            rigidImpact.prepare()
    87→        @unknown default:
    88→            mediumImpact.impactOccurred()
    89→            mediumImpact.prepare()
    90→        }
    91→        #endif
    92→    }
    93→
    94→    // MARK: - Notification Feedback
    95→    static func notification(_ type: UINotificationFeedbackGenerator.FeedbackType) {
    96→        #if !targetEnvironment(macCatalyst)
    97→        guard isEnabled, supportsHaptics else { return }
    98→        notificationGenerator.notificationOccurred(type)
    99→        notificationGenerator.prepare()
   100→        #endif
   101→    }
   102→
   103→    // MARK: - Selection Feedback
   104→    static func selection() {
   105→        #if !targetEnvironment(macCatalyst)
   106→        guard isEnabled, supportsHaptics else { return }
   107→        selectionGenerator.selectionChanged()
   108→        selectionGenerator.prepare()
   109→        #endif
   110→    }
   111→
   112→    // MARK: - Convenience Methods
   113→    static func taskCompleted() {
   114→        notification(.success)
   115→    }
   116→
   117→    static func taskDeleted() {
   118→        notification(.warning)
   119→    }
   120→
   121→    static func timerStarted() {
   122→        impact(.light)
   123→    }
   124→
   125→    static func timerFinished() {
   126→        notification(.success)
   127→    }
   128→
   129→    static func entryAdded() {
   130→        impact(.medium)
   131→    }
   132→}
   133→

<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>

//
//  HapticManager.swift
//  ProductivityHub-iOS
//
//  Centralized haptic feedback manager for better UX
//  Optimized with prepared generators for minimal latency
//  Compatible with iPhone, iPad, and Mac Catalyst
//

import UIKit

enum HapticManager {
    // MARK: - User Preference
    private static let hapticsEnabledKey = "ProductivityHub_HapticsEnabled"

    /// Check if haptics are enabled (defaults to true)
    static var isEnabled: Bool {
        get {
            // Default to true if never set
            if UserDefaults.standard.object(forKey: hapticsEnabledKey) == nil {
                return true
            }
            return UserDefaults.standard.bool(forKey: hapticsEnabledKey)
        }
        set {
            UserDefaults.standard.set(newValue, forKey: hapticsEnabledKey)
        }
    }

    /// Check if device supports haptic feedback
    private static var supportsHaptics: Bool {
        #if targetEnvironment(macCatalyst)
        return false // Mac doesn't support haptics
        #else
        // Only iPhones with Taptic Engine support haptics (iPhone 7+)
        return UIDevice.current.userInterfaceIdiom == .phone
        #endif
    }

    // MARK: - Prepared Generators (for better performance)
    #if !targetEnvironment(macCatalyst)
    private static let lightImpact = UIImpactFeedbackGenerator(style: .light)
    private static let mediumImpact = UIImpactFeedbackGenerator(style: .medium)
    private static let heavyImpact = UIImpactFeedbackGenerator(style: .heavy)
    private static let softImpact = UIImpactFeedbackGenerator(style: .soft)
    private static let rigidImpact = UIImpactFeedbackGenerator(style: .rigid)
    private static let notificationGenerator = UINotificationFeedbackGenerator()
    private static let selectionGenerator = UISelectionFeedbackGenerator()
    #endif

    // MARK: - Preparation (call on app launch or before expected use)
    static func prepare() {
        #if !targetEnvironment(macCatalyst)
        guard supportsHaptics else { return }
        lightImpact.prepare()
        mediumImpact.prepare()
        heavyImpact.prepare()
        softImpact.prepare()
        rigidImpact.prepare()
        notificationGenerator.prepare()
        selectionGenerator.prepare()
        #endif
    }

    // MARK: - Impact Feedback
    static func impact(_ style: UIImpactFeedbackGenerator.FeedbackStyle = .medium) {
        #if !targetEnvironment(macCatalyst)
        // Check if haptics are enabled by user preference and device support
        guard isEnabled, supportsHaptics else { return }

        switch style {
        case .light:
            lightImpact.impactOccurred()
            lightImpact.prepare() // Prepare for next use
        case .medium:
            mediumImpact.impactOccurred()
            mediumImpact.prepare()
        case .heavy:
            heavyImpact.impactOccurred()
            heavyImpact.prepare()
        case .soft:
            softImpact.impactOccurred()
            softImpact.prepare()
        case .rigid:
            rigidImpact.impactOccurred()
            rigidImpact.prepare()
        @unknown default:
            mediumImpact.impactOccurred()
            mediumImpact.prepare()
        }
        #endif
    }

    // MARK: - Notification Feedback
    static func notification(_ type: UINotificationFeedbackGenerator.FeedbackType) {
        #if !targetEnvironment(macCatalyst)
        guard isEnabled, supportsHaptics else { return }
        notificationGenerator.notificationOccurred(type)
        notificationGenerator.prepare()
        #endif
    }

    // MARK: - Selection Feedback
    static func selection() {
        #if !targetEnvironment(macCatalyst)
        guard isEnabled, supportsHaptics else { return }
        selectionGenerator.selectionChanged()
        selectionGenerator.prepare()
        #endif
    }

    // MARK: - Convenience Methods
    static func taskCompleted() {
        notification(.success)
    }

    static func taskDeleted() {
        notification(.warning)
    }

    static func timerStarted() {
        impact(.light)
    }

    static func timerFinished() {
        notification(.success)
    }

    static func entryAdded() {
        impact(.medium)
    }
}


Let me check for fitness apps and examine one more key pattern:


find /Users/neog -maxdepth 3 -iname "*200*flexoes*" -o -iname "*fitness*" -o -iname "*pushups*" 2>/dev/null | head -10

Search for fitness-related apps


     1→//
     2→//  WorkSession.swift
     3→//  ProductivityHub-iOS
     4→//
     5→//  Work/focus session tracking (from FocusWriter)
     6→//
     7→
     8→import Foundation
     9→import SwiftUI
    10→
    11→enum SessionType: String, Codable, CaseIterable {
    12→    case deepWork = "Deep Work"
    13→    case pomodoro = "Pomodoro"
    14→    case break5 = "Short Break"
    15→    case break15 = "Long Break"
    16→
    17→    var defaultDuration: TimeInterval {
    18→        switch self {
    19→        case .deepWork: return 2 * 60 * 60 // 2 hours
    20→        case .pomodoro: return 25 * 60 // 25 minutes
    21→        case .break5: return 5 * 60 // 5 minutes
    22→        case .break15: return 15 * 60 // 15 minutes
    23→        }
    24→    }
    25→
    26→    // Legacy color property for backwards compatibility
    27→    var color: String {
    28→        switch self {
    29→        case .deepWork: return "blue"
    30→        case .pomodoro: return "green"
    31→        case .break5, .break15: return "orange"
    32→        }
    33→    }
    34→
    35→    // Adaptive color that works well in both light and dark mode
    36→    var adaptiveColor: Color {
    37→        switch self {
    38→        case .deepWork:
    39→            return Color.blue
    40→        case .pomodoro:
    41→            return Color.green
    42→        case .break5, .break15:
    43→            return Color.orange
    44→        }
    45→    }
    46→
    47→    // Alias for consistency with other models
    48→    var swiftUIColor: Color { adaptiveColor }
    49→}
    50→
    51→struct WorkSession: Identifiable, Codable, Hashable {
    52→    var id: UUID = UUID()
    53→    var sessionType: SessionType
    54→    var duration: TimeInterval // Actual duration in seconds
    55→    var targetDuration: TimeInterval // Planned duration
    56→    var linkedTaskIds: [UUID] = [] // Link to Tasks
    57→    var notes: String = ""
    58→    var startTime: Date
    59→    var endTime: Date?
    60→    var wasCompleted: Bool = false // Did they finish the full session?
    61→
    62→    // Computed properties
    63→    var isActive: Bool {
    64→        endTime == nil
    65→    }
    66→
    67→    var completionPercentage: Double {
    68→        guard targetDuration > 0 else { return 0 }
    69→        return min(duration / targetDuration, 1.0) * 100
    70→    }
    71→
    72→    var formattedDuration: String {
    73→        let hours = Int(duration) / 3600
    74→        let minutes = Int(duration) / 60 % 60
    75→        let seconds = Int(duration) % 60
    76→
    77→        if hours > 0 {
    78→            return String(format: "%dh %02dm %02ds", hours, minutes, seconds)
    79→        } else if minutes > 0 {
    80→            return String(format: "%dm %02ds", minutes, seconds)
    81→        } else {
    82→            return String(format: "%ds", seconds)
    83→        }
    84→    }
    85→}
    86→

<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>

//
//  WorkSession.swift
//  ProductivityHub-iOS
//
//  Work/focus session tracking (from FocusWriter)
//

import Foundation
import SwiftUI

enum SessionType: String, Codable, CaseIterable {
    case deepWork = "Deep Work"
    case pomodoro = "Pomodoro"
    case break5 = "Short Break"
    case break15 = "Long Break"

    var defaultDuration: TimeInterval {
        switch self {
        case .deepWork: return 2 * 60 * 60 // 2 hours
        case .pomodoro: return 25 * 60 // 25 minutes
        case .break5: return 5 * 60 // 5 minutes
        case .break15: return 15 * 60 // 15 minutes
        }
    }

    // Legacy color property for backwards compatibility
    var color: String {
        switch self {
        case .deepWork: return "blue"
        case .pomodoro: return "green"
        case .break5, .break15: return "orange"
        }
    }

    // Adaptive color that works well in both light and dark mode
    var adaptiveColor: Color {
        switch self {
        case .deepWork:
            return Color.blue
        case .pomodoro:
            return Color.green
        case .break5, .break15:
            return Color.orange
        }
    }

    // Alias for consistency with other models
    var swiftUIColor: Color { adaptiveColor }
}

struct WorkSession: Identifiable, Codable, Hashable {
    var id: UUID = UUID()
    var sessionType: SessionType
    var duration: TimeInterval // Actual duration in seconds
    var targetDuration: TimeInterval // Planned duration
    var linkedTaskIds: [UUID] = [] // Link to Tasks
    var notes: String = ""
    var startTime: Date
    var endTime: Date?
    var wasCompleted: Bool = false // Did they finish the full session?

    // Computed properties
    var isActive: Bool {
        endTime == nil
    }

    var completionPercentage: Double {
        guard targetDuration > 0 else { return 0 }
        return min(duration / targetDuration, 1.0) * 100
    }

    var formattedDuration: String {
        let hours = Int(duration) / 3600
        let minutes = Int(duration) / 60 % 60
        let seconds = Int(duration) % 60

        if hours > 0 {
            return String(format: "%dh %02dm %02ds", hours, minutes, seconds)
        } else if minutes > 0 {
            return String(format: "%dm %02ds", minutes, seconds)
        } else {
            return String(format: "%ds", seconds)
        }
    }
}