Explore os apps macOS existentes em ~/Apps/macOS/ para entender:
1. Estrutura de projetos Xcode usada
2. Padrões de UI/UX (SwiftUI ou AppKit)
3. Como eles integram com Safari/WebKit
4. Padrões de código Swift usados

Liste os apps existentes e analise a estrutura de pelo menos 2 deles.


Vou explorar os apps macOS existentes em ~/Apps/macOS/ para você. Deixe-me começar descobrindo o que existe nesse diretório.


Bash

ls -la ~/Apps/macOS/ 2>/dev/null || echo "Directory not found"

List macOS apps directory


find ~/Apps -maxdepth 2 -name "*.xcodeproj" -o -name "*.xcworkspace" 2>/dev/null | head -20

Find Xcode projects in Apps directory


total 32
drwxr-xr-x@ 34 neog  staff   1088 Jan  2 00:30 .
drwxr-xr-x@  6 neog  staff    192 Dec 30 20:24 ..
-rw-r--r--@  1 neog  staff  14340 Jan  1 05:53 .DS_Store
drwx------@ 24 neog  staff    768 Dec 30 21:42 17Licoes-macOS
drwxr-xr-x   7 neog  staff    224 Jan  2 00:45 AnnualOS
drwxr-xr-x   3 neog  staff     96 Dec 30 20:44 BRDataMiner
drwxr-xr-x@  5 neog  staff    160 Dec 24 02:52 FlightFinder-macOS
drwxr-xr-x@ 28 neog  staff    896 Dec 29 18:58 GhostWriter
drwxr-xr-x   5 neog  staff    160 Dec 30 02:12 InstagramToolkit
drwxr-xr-x   5 neog  staff    160 Jan  1 19:54 IranStrategyMatrix
drwxr-xr-x   3 neog  staff     96 Dec 30 20:15 MetaScheduler
drwxr-xr-x@ 12 neog  staff    384 Jan  1 04:16 NEOG-Awaken
drwxr-xr-x@  9 neog  staff    288 Dec 23 16:23 NEOG-Finance
drwxr-xr-x@  6 neog  staff    192 Dec 22 00:30 NEOG-Reflect
drwxr-xr-x@ 10 neog  staff    320 Dec 29 21:56 NEOGSystemControl
drwxr-xr-x@ 11 neog  staff    352 Dec 13 12:56 NEOGWorkspaceManager-macOS
drwxr-xr-x   5 neog  staff    160 Jan  1 07:10 PowerAndMoney
drwxr-xr-x  13 neog  staff    416 Dec 30 04:32 PredictiveHistory-macOS
drwxr-xr-x  10 neog  staff    320 Dec 31 00:34 PyRunner
drwxr-xr-x@  7 neog  staff    224 Dec 30 20:17 RentRescue4K
drwxr-xr-x@  6 neog  staff    192 Dec 29 03:16 RentRescue4K-Xcode
drwxr-xr-x@  8 neog  staff    256 Dec 29 18:58 ReputacaoDigital-macOS
drwxr-xr-x@ 12 neog  staff    384 Dec 24 00:01 SobrietyGuard-macOS
drwxr-xr-x@  9 neog  staff    288 Dec 28 23:05 StudioROI
drwxr-xr-x@  5 neog  staff    160 Dec 27 01:58 TypingMaster-macOS
drwxr-xr-x@  7 neog  staff    224 Dec 28 15:22 VPNMonitor-macOS
drwxr-xr-x@  6 neog  staff    192 Dec 23 04:14 WhatsAppAnalyzer-macOS
drwxr-xr-x@  9 neog  staff    288 Dec 20 22:38 WhatsAppValidator
drwxr-xr-x   7 neog  staff    224 Dec 30 00:59 XcodeProjectHub
drwxr-xr-x@ 23 neog  staff    736 Dec 30 23:52 YouTubeAudio-macOS
drwxr-xr-x@  4 neog  staff    128 Dec 26 14:56 YouTubeDownloader-macOS
drwxr-xr-x@  7 neog  staff    224 Dec 28 23:02 YouTubeManager-macOS
drwxr-xr-x   6 neog  staff    192 Dec 31 23:52 YouTubeStudyTracker
drwxr-xr-x@ 17 neog  staff    544 Dec 31 21:30 YouTubeTranscripts-macOS


Ótimo! Encontrei 34 apps macOS. Agora vou analisar a estrutura de alguns deles. Deixe-me escolher 2 apps mais complexos e úteis para análise:


ls -la ~/Apps/macOS/17Licoes-macOS/ | head -30

List structure of 17Licoes-macOS app


ls -la ~/Apps/macOS/YouTubeTranscripts-macOS/ | head -30

List structure of YouTubeTranscripts-macOS app


find ~/Apps/macOS/17Licoes-macOS -name "*.xcodeproj" -o -name "*.xcworkspace" | head -5

Find Xcode project files in 17Licoes


find ~/Apps/macOS/YouTubeTranscripts-macOS -name "*.xcodeproj" -o -name "*.xcworkspace" | head -5

Find Xcode project files in YouTubeTranscripts


total 104
drwx------@ 24 neog  staff   768 Dec 30 21:42 .
drwxr-xr-x@ 34 neog  staff  1088 Jan  2 00:30 ..
-rw-r--r--@  1 neog  staff  6148 Dec 30 21:40 .DS_Store
drwxr-xr-x@ 13 neog  staff   416 Dec 30 21:02 .build
drwxr-xr-x  12 neog  staff   384 Dec 30 21:02 .git
-rw-------   1 neog  staff   323 Dec 30 21:17 17Licoes.entitlements
drwx------@  5 neog  staff   160 Dec 30 21:37 17Licoes.xcodeproj
drwx------@  3 neog  staff    96 Dec 30 21:41 App
drwxr-xr-x   4 neog  staff   128 Dec 30 21:18 Assets.xcassets
drwx------@  9 neog  staff   288 Dec 30 21:47 Components
-rw-------   1 neog  staff  6718 Dec 30 21:42 ContentView.swift
drwx------@  3 neog  staff    96 Dec  2 18:08 Data
-rw-------   1 neog  staff   458 Dec 30 21:11 ExportOptions.plist
drwx------@  3 neog  staff    96 Dec 30 21:41 Extensions
drwx------@ 10 neog  staff   320 Dec  4 01:21 Features
-rw-------@  1 neog  staff  6712 Dec  2 18:11 IMPLEMENTATION_SUMMARY.md
drwx------@  8 neog  staff   256 Dec  4 01:23 Models
-rw-------@  1 neog  staff  2548 Dec  4 01:27 Package.swift
-rw-------@  1 neog  staff  3930 Dec  2 18:10 README.md
drwx------@  5 neog  staff   160 Dec 30 21:40 Services
drwx------@  3 neog  staff    96 Dec  2 19:16 Sources
-rw-------@  1 neog  staff   152 Dec  2 18:53 build.sh
-rw-------@  1 neog  staff   766 Dec  2 19:43 fix-and-build.sh
-rwx--x--x@  1 neog  staff   368 Dec  4 20:49 run.sh


total 88
drwxr-xr-x@ 17 neog  staff   544 Dec 31 21:30 .
drwxr-xr-x@ 34 neog  staff  1088 Jan  2 00:30 ..
-rw-r--r--@  1 neog  staff  6148 Dec 31 20:48 .DS_Store
drwxr-xr-x@ 15 neog  staff   480 Dec 28 14:54 .build
drwxr-xr-x@ 12 neog  staff   384 Dec 31 20:47 .git
-rw-------@  1 neog  staff   712 Dec 21 14:44 .gitignore
drwxr-xr-x   4 neog  staff   128 Dec 30 04:16 .swiftpm
-rw-------   1 neog  staff   458 Dec 31 21:30 ExportOptions.plist
-rw-------@  1 neog  staff  1068 Dec 21 15:56 LICENSE
-rw-------@  1 neog  staff   469 Dec  3 13:49 Package.swift
-rw-------@  1 neog  staff  4729 Dec 21 15:54 README.md
drwx------@  3 neog  staff    96 Dec  8 02:05 Scripts
drwxr-xr-x@ 11 neog  staff   352 Dec 31 21:17 Sources
-rw-------@  1 neog  staff  4928 Dec 21 14:02 WHISPER-SETUP.md
drwxr-xr-x@  5 neog  staff   160 Dec 30 02:21 YouTubeTranscripts.xcodeproj
drwxr-xr-x@  9 neog  staff   288 Dec 27 00:52 build
-rw-------@  1 neog  staff   709 Dec 26 20:49 project.yml


/Users/neog/Apps/macOS/17Licoes-macOS/17Licoes.xcodeproj
/Users/neog/Apps/macOS/17Licoes-macOS/17Licoes.xcodeproj/project.xcworkspace


Excelente! Vou analisar a estrutura detalhada de ambos:


find ~/Apps/macOS/17Licoes-macOS -name "*.swift" -type f | head -20

List Swift files in 17Licoes-macOS


     1→// swift-tools-version: 5.9
     2→import PackageDescription
     3→
     4→let package = Package(
     5→    name: "17Licoes",
     6→    platforms: [
     7→        .macOS(.v14)
     8→    ],
     9→    products: [
    10→        .executable(
    11→            name: "17Licoes",
    12→            targets: ["17Licoes"]
    13→        )
    14→    ],
    15→    targets: [
    16→        .executableTarget(
    17→            name: "17Licoes",
    18→            path: ".",
    19→            exclude: [
    20→                "Package.swift",
    21→                "build.sh",
    22→                "Sources",
    23→                "fix-and-build.sh"
    24→            ],
    25→            sources: [
    26→                // Models
    27→                "Models/AllModels.swift",
    28→                "Models/Lesson.swift",
    29→                "Models/UserProgress.swift",
    30→                "Models/Note.swift",
    31→                "Models/ReadingSettings.swift",
    32→                "Models/GamificationProgress.swift",
    33→
    34→                // Data
    35→                "Data/LessonsData.swift",
    36→
    37→                // Extensions
    38→                "Extensions/Color+Hex.swift",
    39→
    40→                // Services
    41→                "Services/DataManager.swift",
    42→                "Services/NotificationManager.swift",
    43→                "Services/Logger.swift",
    44→
    45→                // Components
    46→                "Components/XPBadgeView.swift",
    47→                "Components/KeyConceptView.swift",
    48→                "Components/QuoteView.swift",
    49→                "Components/FrameworkBoxView.swift",
    50→                "Components/StyledListView.swift",
    51→                "Components/ProgressHeaderView.swift",
    52→                "Components/HighlightCreatorView.swift",
    53→
    54→                // Features - Lessons
    55→                "Features/Lessons/SidebarView.swift",
    56→                "Features/Lessons/LessonRowView.swift",
    57→                "Features/Lessons/LessonDetailView.swift",
    58→
    59→                // Features - Notes
    60→                "Features/Notes/NotesView.swift",
    61→
    62→                // Features - Summary
    63→                "Features/Summary/SummaryView.swift",
    64→
    65→                // Features - Gamification
    66→                "Features/Gamification/GamificationView.swift",
    67→
    68→                // Features - Learning
    69→                "Features/Learning/FlashcardsView.swift",
    70→
    71→                // Features - Settings
    72→                "Features/Settings/SettingsView.swift",
    73→
    74→                // Features - Stats
    75→                "Features/Stats/StatsView.swift",
    76→
    77→                // Features - Help
    78→                "Features/Help/KeyboardShortcutsView.swift",
    79→
    80→                // App
    81→                "App/App17LicoesApp.swift",
    82→                "ContentView.swift"
    83→            ]
    84→        )
    85→    ]
    86→)
    87→

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

// swift-tools-version: 5.9
import PackageDescription

let package = Package(
    name: "17Licoes",
    platforms: [
        .macOS(.v14)
    ],
    products: [
        .executable(
            name: "17Licoes",
            targets: ["17Licoes"]
        )
    ],
    targets: [
        .executableTarget(
            name: "17Licoes",
            path: ".",
            exclude: [
                "Package.swift",
                "build.sh",
                "Sources",
                "fix-and-build.sh"
            ],
            sources: [
                // Models
                "Models/AllModels.swift",
                "Models/Lesson.swift",
                "Models/UserProgress.swift",
                "Models/Note.swift",
                "Models/ReadingSettings.swift",
                "Models/GamificationProgress.swift",

                // Data
                "Data/LessonsData.swift",

                // Extensions
                "Extensions/Color+Hex.swift",

                // Services
                "Services/DataManager.swift",
                "Services/NotificationManager.swift",
                "Services/Logger.swift",

                // Components
                "Components/XPBadgeView.swift",
                "Components/KeyConceptView.swift",
                "Components/QuoteView.swift",
                "Components/FrameworkBoxView.swift",
                "Components/StyledListView.swift",
                "Components/ProgressHeaderView.swift",
                "Components/HighlightCreatorView.swift",

                // Features - Lessons
                "Features/Lessons/SidebarView.swift",
                "Features/Lessons/LessonRowView.swift",
                "Features/Lessons/LessonDetailView.swift",

                // Features - Notes
                "Features/Notes/NotesView.swift",

                // Features - Summary
                "Features/Summary/SummaryView.swift",

                // Features - Gamification
                "Features/Gamification/GamificationView.swift",

                // Features - Learning
                "Features/Learning/FlashcardsView.swift",

                // Features - Settings
                "Features/Settings/SettingsView.swift",

                // Features - Stats
                "Features/Stats/StatsView.swift",

                // Features - Help
                "Features/Help/KeyboardShortcutsView.swift",

                // App
                "App/App17LicoesApp.swift",
                "ContentView.swift"
            ]
        )
    ]
)


     1→// swift-tools-version: 5.9
     2→import PackageDescription
     3→
     4→let package = Package(
     5→    name: "YouTubeTranscripts",
     6→    platforms: [.macOS(.v14)],
     7→    products: [
     8→        .executable(name: "YouTubeTranscripts", targets: ["YouTubeTranscripts"])
     9→    ],
    10→    targets: [
    11→        .executableTarget(
    12→            name: "YouTubeTranscripts",
    13→            path: "Sources",
    14→            swiftSettings: [
    15→                .unsafeFlags(["-parse-as-library"])
    16→            ]
    17→        )
    18→    ]
    19→)
    20→

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

// swift-tools-version: 5.9
import PackageDescription

let package = Package(
    name: "YouTubeTranscripts",
    platforms: [.macOS(.v14)],
    products: [
        .executable(name: "YouTubeTranscripts", targets: ["YouTubeTranscripts"])
    ],
    targets: [
        .executableTarget(
            name: "YouTubeTranscripts",
            path: "Sources",
            swiftSettings: [
                .unsafeFlags(["-parse-as-library"])
            ]
        )
    ]
)


     1→import SwiftUI
     2→
     3→enum NavigationDestination: Hashable {
     4→    case lesson(Lesson)
     5→    case summary
     6→    case flashcards
     7→    case gamification
     8→}
     9→
    10→struct ContentView: View {
    11→    @EnvironmentObject var dataManager: DataManager
    12→    @EnvironmentObject var themeManager: ThemeManager
    13→    @State private var selectedLesson: Lesson?
    14→    @State private var columnVisibility = NavigationSplitViewVisibility.all
    15→    @State private var showingSettings = false
    16→    @State private var showingFlashcards = false
    17→    @State private var showingGamification = false
    18→    @State private var showingKeyboardShortcuts = false
    19→    @State private var currentView: NavigationDestination = .summary
    20→
    21→    var body: some View {
    22→        NavigationSplitView(columnVisibility: $columnVisibility) {
    23→            VStack(spacing: 0) {
    24→                SidebarView(selectedLesson: $selectedLesson)
    25→
    26→                Divider()
    27→
    28→                // Bottom Navigation
    29→                HStack(spacing: 16) {
    30→                    // Gamification Button
    31→                    SidebarActionButton(
    32→                        icon: "trophy.fill",
    33→                        label: "XP",
    34→                        badge: "\(dataManager.gamification.totalXP)",
    35→                        action: { showingGamification = true }
    36→                    )
    37→
    38→                    // Flashcards Button
    39→                    SidebarActionButton(
    40→                        icon: "rectangle.on.rectangle.angled",
    41→                        label: "Cards",
    42→                        badge: nil,
    43→                        action: { showingFlashcards = true }
    44→                    )
    45→
    46→                    // Streak Display
    47→                    StreakMiniView(streak: dataManager.gamification.currentStreak)
    48→
    49→                    Spacer()
    50→
    51→                    #if os(macOS)
    52→                    // Help Button (macOS only - keyboard shortcuts)
    53→                    Button(action: { showingKeyboardShortcuts = true }) {
    54→                        Image(systemName: "keyboard")
    55→                            .foregroundColor(.secondary)
    56→                    }
    57→                    .buttonStyle(.plain)
    58→                    .help("Atalhos de Teclado")
    59→                    #endif
    60→
    61→                    // Settings Button
    62→                    Button(action: { showingSettings = true }) {
    63→                        Image(systemName: "gearshape")
    64→                            .foregroundColor(.secondary)
    65→                    }
    66→                    .buttonStyle(.plain)
    67→                    #if os(macOS)
    68→                    .help("Configuracoes")
    69→                    #endif
    70→                }
    71→                .padding(.horizontal, 12)
    72→                .padding(.vertical, 8)
    73→                .background(themeManager.isDark ? Color.darkBgSecondary : Color.lightBgSecondary)
    74→            }
    75→            #if os(macOS)
    76→            .frame(minWidth: 280, idealWidth: 320)
    77→            #endif
    78→        } detail: {
    79→            if let lesson = selectedLesson {
    80→                LessonDetailView(lesson: lesson)
    81→            } else {
    82→                SummaryView(selectedLesson: $selectedLesson)
    83→            }
    84→        }
    85→        .sheet(isPresented: $showingSettings) {
    86→            SettingsView()
    87→                .environmentObject(dataManager)
    88→                .environmentObject(themeManager)
    89→        }
    90→        .sheet(isPresented: $showingFlashcards) {
    91→            FlashcardsView()
    92→                .environmentObject(dataManager)
    93→                .environmentObject(themeManager)
    94→                #if os(macOS)
    95→                .frame(minWidth: 600, minHeight: 500)
    96→                #endif
    97→        }
    98→        .sheet(isPresented: $showingGamification) {
    99→            GamificationView()
   100→                .environmentObject(dataManager)
   101→                .environmentObject(themeManager)
   102→                #if os(macOS)
   103→                .frame(minWidth: 700, minHeight: 500)
   104→                #endif
   105→        }
   106→        #if os(macOS)
   107→        .sheet(isPresented: $showingKeyboardShortcuts) {
   108→            KeyboardShortcutsView()
   109→                .environmentObject(themeManager)
   110→        }
   111→        #endif
   112→        .onAppear {
   113→            // Update streak on app launch
   114→            dataManager.updateStreak()
   115→        }
   116→        .onChange(of: selectedLesson) { _, _ in
   117→            // Track lesson changes
   118→        }
   119→    }
   120→}
   121→
   122→// MARK: - Sidebar Action Button
   123→
   124→struct SidebarActionButton: View {
   125→    let icon: String
   126→    let label: String
   127→    let badge: String?
   128→    let action: () -> Void
   129→    @EnvironmentObject var themeManager: ThemeManager
   130→
   131→    var body: some View {
   132→        Button(action: action) {
   133→            VStack(spacing: 2) {
   134→                ZStack(alignment: .topTrailing) {
   135→                    Image(systemName: icon)
   136→                        .font(.system(size: 18))
   137→                        .foregroundColor(.accentGold)
   138→
   139→                    if let badge = badge, !badge.isEmpty {
   140→                        Text(badge)
   141→                            .font(.system(size: 8, weight: .bold))
   142→                            .foregroundColor(.white)
   143→                            .padding(.horizontal, 4)
   144→                            .padding(.vertical, 1)
   145→                            .background(Color.accentGold)
   146→                            .cornerRadius(6)
   147→                            .offset(x: 8, y: -4)
   148→                    }
   149→                }
   150→
   151→                Text(label)
   152→                    .font(.caption2)
   153→                    .foregroundColor(.secondary)
   154→            }
   155→        }
   156→        .buttonStyle(.plain)
   157→    }
   158→}
   159→
   160→// MARK: - Streak Mini View
   161→
   162→struct StreakMiniView: View {
   163→    let streak: Int
   164→    @EnvironmentObject var themeManager: ThemeManager
   165→
   166→    var body: some View {
   167→        HStack(spacing: 4) {
   168→            Image(systemName: streak > 0 ? "flame.fill" : "flame")
   169→                .font(.system(size: 14))
   170→                .foregroundColor(streak > 0 ? .orange : .gray)
   171→
   172→            Text("\(streak)")
   173→                .font(.system(size: 12, weight: .semibold))
   174→                .foregroundColor(streak > 0 ? .orange : .secondary)
   175→        }
   176→        .padding(.horizontal, 8)
   177→        .padding(.vertical, 4)
   178→        .background(
   179→            streak > 0 ?
   180→            Color.orange.opacity(0.15) :
   181→            Color.clear
   182→        )
   183→        .cornerRadius(12)
   184→        #if os(macOS)
   185→        .help("Streak de \(streak) dias")
   186→        #endif
   187→    }
   188→}
   189→
   190→// MARK: - Focus Mode Modifier
   191→
   192→struct FocusModeModifier: ViewModifier {
   193→    @EnvironmentObject var dataManager: DataManager
   194→
   195→    func body(content: Content) -> some View {
   196→        if dataManager.isFocusModeEnabled {
   197→            content
   198→                .navigationSplitViewColumnWidth(0)
   199→        } else {
   200→            content
   201→        }
   202→    }
   203→}
   204→
   205→extension View {
   206→    func focusMode() -> some View {
   207→        modifier(FocusModeModifier())
   208→    }
   209→}
   210→

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

import SwiftUI

enum NavigationDestination: Hashable {
    case lesson(Lesson)
    case summary
    case flashcards
    case gamification
}

struct ContentView: View {
    @EnvironmentObject var dataManager: DataManager
    @EnvironmentObject var themeManager: ThemeManager
    @State private var selectedLesson: Lesson?
    @State private var columnVisibility = NavigationSplitViewVisibility.all
    @State private var showingSettings = false
    @State private var showingFlashcards = false
    @State private var showingGamification = false
    @State private var showingKeyboardShortcuts = false
    @State private var currentView: NavigationDestination = .summary

    var body: some View {
        NavigationSplitView(columnVisibility: $columnVisibility) {
            VStack(spacing: 0) {
                SidebarView(selectedLesson: $selectedLesson)

                Divider()

                // Bottom Navigation
                HStack(spacing: 16) {
                    // Gamification Button
                    SidebarActionButton(
                        icon: "trophy.fill",
                        label: "XP",
                        badge: "\(dataManager.gamification.totalXP)",
                        action: { showingGamification = true }
                    )

                    // Flashcards Button
                    SidebarActionButton(
                        icon: "rectangle.on.rectangle.angled",
                        label: "Cards",
                        badge: nil,
                        action: { showingFlashcards = true }
                    )

                    // Streak Display
                    StreakMiniView(streak: dataManager.gamification.currentStreak)

                    Spacer()

                    #if os(macOS)
                    // Help Button (macOS only - keyboard shortcuts)
                    Button(action: { showingKeyboardShortcuts = true }) {
                        Image(systemName: "keyboard")
                            .foregroundColor(.secondary)
                    }
                    .buttonStyle(.plain)
                    .help("Atalhos de Teclado")
                    #endif

                    // Settings Button
                    Button(action: { showingSettings = true }) {
                        Image(systemName: "gearshape")
                            .foregroundColor(.secondary)
                    }
                    .buttonStyle(.plain)
                    #if os(macOS)
                    .help("Configuracoes")
                    #endif
                }
                .padding(.horizontal, 12)
                .padding(.vertical, 8)
                .background(themeManager.isDark ? Color.darkBgSecondary : Color.lightBgSecondary)
            }
            #if os(macOS)
            .frame(minWidth: 280, idealWidth: 320)
            #endif
        } detail: {
            if let lesson = selectedLesson {
                LessonDetailView(lesson: lesson)
            } else {
                SummaryView(selectedLesson: $selectedLesson)
            }
        }
        .sheet(isPresented: $showingSettings) {
            SettingsView()
                .environmentObject(dataManager)
                .environmentObject(themeManager)
        }
        .sheet(isPresented: $showingFlashcards) {
            FlashcardsView()
                .environmentObject(dataManager)
                .environmentObject(themeManager)
                #if os(macOS)
                .frame(minWidth: 600, minHeight: 500)
                #endif
        }
        .sheet(isPresented: $showingGamification) {
            GamificationView()
                .environmentObject(dataManager)
                .environmentObject(themeManager)
                #if os(macOS)
                .frame(minWidth: 700, minHeight: 500)
                #endif
        }
        #if os(macOS)
        .sheet(isPresented: $showingKeyboardShortcuts) {
            KeyboardShortcutsView()
                .environmentObject(themeManager)
        }
        #endif
        .onAppear {
            // Update streak on app launch
            dataManager.updateStreak()
        }
        .onChange(of: selectedLesson) { _, _ in
            // Track lesson changes
        }
    }
}

// MARK: - Sidebar Action Button

struct SidebarActionButton: View {
    let icon: String
    let label: String
    let badge: String?
    let action: () -> Void
    @EnvironmentObject var themeManager: ThemeManager

    var body: some View {
        Button(action: action) {
            VStack(spacing: 2) {
                ZStack(alignment: .topTrailing) {
                    Image(systemName: icon)
                        .font(.system(size: 18))
                        .foregroundColor(.accentGold)

                    if let badge = badge, !badge.isEmpty {
                        Text(badge)
                            .font(.system(size: 8, weight: .bold))
                            .foregroundColor(.white)
                            .padding(.horizontal, 4)
                            .padding(.vertical, 1)
                            .background(Color.accentGold)
                            .cornerRadius(6)
                            .offset(x: 8, y: -4)
                    }
                }

                Text(label)
                    .font(.caption2)
                    .foregroundColor(.secondary)
            }
        }
        .buttonStyle(.plain)
    }
}

// MARK: - Streak Mini View

struct StreakMiniView: View {
    let streak: Int
    @EnvironmentObject var themeManager: ThemeManager

    var body: some View {
        HStack(spacing: 4) {
            Image(systemName: streak > 0 ? "flame.fill" : "flame")
                .font(.system(size: 14))
                .foregroundColor(streak > 0 ? .orange : .gray)

            Text("\(streak)")
                .font(.system(size: 12, weight: .semibold))
                .foregroundColor(streak > 0 ? .orange : .secondary)
        }
        .padding(.horizontal, 8)
        .padding(.vertical, 4)
        .background(
            streak > 0 ?
            Color.orange.opacity(0.15) :
            Color.clear
        )
        .cornerRadius(12)
        #if os(macOS)
        .help("Streak de \(streak) dias")
        #endif
    }
}

// MARK: - Focus Mode Modifier

struct FocusModeModifier: ViewModifier {
    @EnvironmentObject var dataManager: DataManager

    func body(content: Content) -> some View {
        if dataManager.isFocusModeEnabled {
            content
                .navigationSplitViewColumnWidth(0)
        } else {
            content
        }
    }
}

extension View {
    func focusMode() -> some View {
        modifier(FocusModeModifier())
    }
}


Agora vou analisar a estrutura do YouTubeTranscripts e ver exemplos de código:


find ~/Apps/macOS/YouTubeTranscripts-macOS/Sources -name "*.swift" -type f

List Swift files in YouTubeTranscripts Sources


List Sources directory structure


     1→import SwiftUI
     2→
     3→@main
     4→struct App17LicoesApp: App {
     5→    @StateObject private var dataManager = DataManager.shared
     6→    @StateObject private var themeManager = ThemeManager.shared
     7→
     8→    var body: some Scene {
     9→        WindowGroup {
    10→            ContentView()
    11→                .environmentObject(dataManager)
    12→                .environmentObject(themeManager)
    13→                #if os(macOS)
    14→                .frame(minWidth: 900, minHeight: 600)
    15→                #endif
    16→        }
    17→        #if os(macOS)
    18→        .commands {
    19→            CommandGroup(replacing: .newItem) {}
    20→
    21→            CommandMenu("Licoes") {
    22→                Button("Resumo de Todas as Licoes") {
    23→                    // Navigate to summary view
    24→                }
    25→                .keyboardShortcut("s", modifiers: .command)
    26→
    27→                Divider()
    28→
    29→                Button("Limpar Progresso de Leitura") {
    30→                    dataManager.clearProgress()
    31→                }
    32→                .keyboardShortcut("r", modifiers: [.command, .shift])
    33→            }
    34→
    35→            CommandMenu("Notas") {
    36→                Button("Exportar Todas as Notas") {
    37→                    dataManager.exportAllNotes()
    38→                }
    39→                .keyboardShortcut("e", modifiers: [.command, .shift])
    40→
    41→                Button("Limpar Todas as Notas") {
    42→                    dataManager.clearAllNotes()
    43→                }
    44→            }
    45→
    46→            CommandMenu("Aparencia") {
    47→                Button("Alternar Tema Claro/Escuro") {
    48→                    themeManager.toggle()
    49→                }
    50→                .keyboardShortcut("t", modifiers: [.command, .shift])
    51→            }
    52→        }
    53→        #endif
    54→    }
    55→}
    56→

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

import SwiftUI

@main
struct App17LicoesApp: App {
    @StateObject private var dataManager = DataManager.shared
    @StateObject private var themeManager = ThemeManager.shared

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(dataManager)
                .environmentObject(themeManager)
                #if os(macOS)
                .frame(minWidth: 900, minHeight: 600)
                #endif
        }
        #if os(macOS)
        .commands {
            CommandGroup(replacing: .newItem) {}

            CommandMenu("Licoes") {
                Button("Resumo de Todas as Licoes") {
                    // Navigate to summary view
                }
                .keyboardShortcut("s", modifiers: .command)

                Divider()

                Button("Limpar Progresso de Leitura") {
                    dataManager.clearProgress()
                }
                .keyboardShortcut("r", modifiers: [.command, .shift])
            }

            CommandMenu("Notas") {
                Button("Exportar Todas as Notas") {
                    dataManager.exportAllNotes()
                }
                .keyboardShortcut("e", modifiers: [.command, .shift])

                Button("Limpar Todas as Notas") {
                    dataManager.clearAllNotes()
                }
            }

            CommandMenu("Aparencia") {
                Button("Alternar Tema Claro/Escuro") {
                    themeManager.toggle()
                }
                .keyboardShortcut("t", modifiers: [.command, .shift])
            }
        }
        #endif
    }
}


total 16
drwxr-xr-x@ 11 neog  staff   352 Dec 31 21:17 .
drwxr-xr-x@ 17 neog  staff   544 Dec 31 21:30 ..
drwxr-xr-x@  3 neog  staff    96 Dec 28 14:40 App
drwxr-xr-x@  5 neog  staff   160 Dec  3 02:21 Assets.xcassets
drwxr-xr-x@  9 neog  staff   288 Dec 28 14:41 Core
-rw-------   1 neog  staff  3669 Dec 31 21:17 Info.plist
drwxr-xr-x@  5 neog  staff   160 Dec 28 14:48 Models
drwxr-xr-x@ 10 neog  staff   320 Dec 28 14:47 Services
drwx------@  5 neog  staff   160 Dec 28 14:44 ViewModels
drwxr-xr-x@ 12 neog  staff   384 Dec 28 14:51 Views
-rw-------@  1 neog  staff   384 Dec 26 20:39 YouTubeTranscripts.entitlements


     1→import SwiftUI
     2→
     3→enum LessonCategory: String, Codable, CaseIterable {
     4→    case mentalidade = "Mentalidade"
     5→    case diagnostico = "Diagnóstico"
     6→    case mindset = "Mindset"
     7→    case execucao = "Execução"
     8→    case lideranca = "Liderança"
     9→    case estrategia = "Estratégia"
    10→    case vendas = "Vendas"
    11→    case marketing = "Marketing"
    12→    case operacoes = "Operações"
    13→    case financas = "Finanças"
    14→
    15→    var displayName: String {
    16→        return self.rawValue
    17→    }
    18→
    19→    var icon: String {
    20→        switch self {
    21→        case .mentalidade: return "brain.head.profile"
    22→        case .diagnostico: return "stethoscope"
    23→        case .mindset: return "lightbulb.fill"
    24→        case .execucao: return "bolt.fill"
    25→        case .lideranca: return "person.3.fill"
    26→        case .estrategia: return "map.fill"
    27→        case .vendas: return "dollarsign.circle.fill"
    28→        case .marketing: return "megaphone.fill"
    29→        case .operacoes: return "gearshape.2.fill"
    30→        case .financas: return "chart.line.uptrend.xyaxis"
    31→        }
    32→    }
    33→
    34→    var color: Color {
    35→        switch self {
    36→        case .mentalidade: return Color(hex: "FF6B6B")
    37→        case .diagnostico: return Color(hex: "4ECDC4")
    38→        case .mindset: return Color(hex: "FFD93D")
    39→        case .execucao: return Color(hex: "95E1D3")
    40→        case .lideranca: return Color(hex: "F38181")
    41→        case .estrategia: return Color(hex: "AA96DA")
    42→        case .vendas: return Color(hex: "FCBAD3")
    43→        case .marketing: return Color(hex: "A8E6CF")
    44→        case .operacoes: return Color(hex: "FFD3B6")
    45→        case .financas: return Color(hex: "C7CEEA")
    46→        }
    47→    }
    48→}
    49→
    50→enum ContentBlock: Codable, Equatable, Hashable {
    51→    case paragraph(String)
    52→    case highlight(String)
    53→    case quote(text: String, author: String)
    54→    case list(items: [String], numbered: Bool)
    55→    case subsection(title: String, content: String)
    56→    case framework(title: String, items: [FrameworkItem])
    57→
    58→    enum CodingKeys: String, CodingKey {
    59→        case type, text, author, items, numbered, title, content, frameworkItems
    60→    }
    61→
    62→    init(from decoder: Decoder) throws {
    63→        let container = try decoder.container(keyedBy: CodingKeys.self)
    64→        let type = try container.decode(String.self, forKey: .type)
    65→
    66→        switch type {
    67→        case "paragraph":
    68→            let text = try container.decode(String.self, forKey: .text)
    69→            self = .paragraph(text)
    70→        case "highlight":
    71→            let text = try container.decode(String.self, forKey: .text)
    72→            self = .highlight(text)
    73→        case "quote":
    74→            let text = try container.decode(String.self, forKey: .text)
    75→            let author = try container.decode(String.self, forKey: .author)
    76→            self = .quote(text: text, author: author)
    77→        case "list":
    78→            let items = try container.decode([String].self, forKey: .items)
    79→            let numbered = try container.decode(Bool.self, forKey: .numbered)
    80→            self = .list(items: items, numbered: numbered)
    81→        case "subsection":
    82→            let title = try container.decode(String.self, forKey: .title)
    83→            let content = try container.decode(String.self, forKey: .content)
    84→            self = .subsection(title: title, content: content)
    85→        case "framework":
    86→            let title = try container.decode(String.self, forKey: .title)
    87→            let items = try container.decode([FrameworkItem].self, forKey: .frameworkItems)
    88→            self = .framework(title: title, items: items)
    89→        default:
    90→            throw DecodingError.dataCorruptedError(forKey: .type, in: container, debugDescription: "Unknown content block type")
    91→        }
    92→    }
    93→
    94→    func encode(to encoder: Encoder) throws {
    95→        var container = encoder.container(keyedBy: CodingKeys.self)
    96→
    97→        switch self {
    98→        case .paragraph(let text):
    99→            try container.encode("paragraph", forKey: .type)
   100→            try container.encode(text, forKey: .text)
   101→        case .highlight(let text):
   102→            try container.encode("highlight", forKey: .type)
   103→            try container.encode(text, forKey: .text)
   104→        case .quote(let text, let author):
   105→            try container.encode("quote", forKey: .type)
   106→            try container.encode(text, forKey: .text)
   107→            try container.encode(author, forKey: .author)
   108→        case .list(let items, let numbered):
   109→            try container.encode("list", forKey: .type)
   110→            try container.encode(items, forKey: .items)
   111→            try container.encode(numbered, forKey: .numbered)
   112→        case .subsection(let title, let content):
   113→            try container.encode("subsection", forKey: .type)
   114→            try container.encode(title, forKey: .title)
   115→            try container.encode(content, forKey: .content)
   116→        case .framework(let title, let items):
   117→            try container.encode("framework", forKey: .type)
   118→            try container.encode(title, forKey: .title)
   119→            try container.encode(items, forKey: .frameworkItems)
   120→        }
   121→    }
   122→}
   123→
   124→struct KeyConcept: Codable, Equatable, Hashable {
   125→    let title: String
   126→    let description: String
   127→}
   128→
   129→struct FrameworkItem: Codable, Equatable, Hashable, Identifiable {
   130→    let id: UUID
   131→    let label: String
   132→    let description: String
   133→
   134→    init(label: String, description: String) {
   135→        self.id = UUID()
   136→        self.label = label
   137→        self.description = description
   138→    }
   139→}
   140→
   141→struct Lesson: Identifiable, Codable, Equatable, Hashable {
   142→    let id: UUID
   143→    let number: Int
   144→    let category: LessonCategory
   145→    let title: String
   146→    let keyConcept: KeyConcept
   147→    let content: [ContentBlock]
   148→    let estimatedReadingMinutes: Int
   149→
   150→    init(
   151→        number: Int,
   152→        category: LessonCategory,
   153→        title: String,
   154→        keyConcept: KeyConcept,
   155→        content: [ContentBlock],
   156→        estimatedReadingMinutes: Int = 5
   157→    ) {
   158→        self.id = UUID()
   159→        self.number = number
   160→        self.category = category
   161→        self.title = title
   162→        self.keyConcept = keyConcept
   163→        self.content = content
   164→        self.estimatedReadingMinutes = estimatedReadingMinutes
   165→    }
   166→}
   167→

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

import SwiftUI

enum LessonCategory: String, Codable, CaseIterable {
    case mentalidade = "Mentalidade"
    case diagnostico = "Diagnóstico"
    case mindset = "Mindset"
    case execucao = "Execução"
    case lideranca = "Liderança"
    case estrategia = "Estratégia"
    case vendas = "Vendas"
    case marketing = "Marketing"
    case operacoes = "Operações"
    case financas = "Finanças"

    var displayName: String {
        return self.rawValue
    }

    var icon: String {
        switch self {
        case .mentalidade: return "brain.head.profile"
        case .diagnostico: return "stethoscope"
        case .mindset: return "lightbulb.fill"
        case .execucao: return "bolt.fill"
        case .lideranca: return "person.3.fill"
        case .estrategia: return "map.fill"
        case .vendas: return "dollarsign.circle.fill"
        case .marketing: return "megaphone.fill"
        case .operacoes: return "gearshape.2.fill"
        case .financas: return "chart.line.uptrend.xyaxis"
        }
    }

    var color: Color {
        switch self {
        case .mentalidade: return Color(hex: "FF6B6B")
        case .diagnostico: return Color(hex: "4ECDC4")
        case .mindset: return Color(hex: "FFD93D")
        case .execucao: return Color(hex: "95E1D3")
        case .lideranca: return Color(hex: "F38181")
        case .estrategia: return Color(hex: "AA96DA")
        case .vendas: return Color(hex: "FCBAD3")
        case .marketing: return Color(hex: "A8E6CF")
        case .operacoes: return Color(hex: "FFD3B6")
        case .financas: return Color(hex: "C7CEEA")
        }
    }
}

enum ContentBlock: Codable, Equatable, Hashable {
    case paragraph(String)
    case highlight(String)
    case quote(text: String, author: String)
    case list(items: [String], numbered: Bool)
    case subsection(title: String, content: String)
    case framework(title: String, items: [FrameworkItem])

    enum CodingKeys: String, CodingKey {
        case type, text, author, items, numbered, title, content, frameworkItems
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let type = try container.decode(String.self, forKey: .type)

        switch type {
        case "paragraph":
            let text = try container.decode(String.self, forKey: .text)
            self = .paragraph(text)
        case "highlight":
            let text = try container.decode(String.self, forKey: .text)
            self = .highlight(text)
        case "quote":
            let text = try container.decode(String.self, forKey: .text)
            let author = try container.decode(String.self, forKey: .author)
            self = .quote(text: text, author: author)
        case "list":
            let items = try container.decode([String].self, forKey: .items)
            let numbered = try container.decode(Bool.self, forKey: .numbered)
            self = .list(items: items, numbered: numbered)
        case "subsection":
            let title = try container.decode(String.self, forKey: .title)
            let content = try container.decode(String.self, forKey: .content)
            self = .subsection(title: title, content: content)
        case "framework":
            let title = try container.decode(String.self, forKey: .title)
            let items = try container.decode([FrameworkItem].self, forKey: .frameworkItems)
            self = .framework(title: title, items: items)
        default:
            throw DecodingError.dataCorruptedError(forKey: .type, in: container, debugDescription: "Unknown content block type")
        }
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)

        switch self {
        case .paragraph(let text):
            try container.encode("paragraph", forKey: .type)
            try container.encode(text, forKey: .text)
        case .highlight(let text):
            try container.encode("highlight", forKey: .type)
            try container.encode(text, forKey: .text)
        case .quote(let text, let author):
            try container.encode("quote", forKey: .type)
            try container.encode(text, forKey: .text)
            try container.encode(author, forKey: .author)
        case .list(let items, let numbered):
            try container.encode("list", forKey: .type)
            try container.encode(items, forKey: .items)
            try container.encode(numbered, forKey: .numbered)
        case .subsection(let title, let content):
            try container.encode("subsection", forKey: .type)
            try container.encode(title, forKey: .title)
            try container.encode(content, forKey: .content)
        case .framework(let title, let items):
            try container.encode("framework", forKey: .type)
            try container.encode(title, forKey: .title)
            try container.encode(items, forKey: .frameworkItems)
        }
    }
}

struct KeyConcept: Codable, Equatable, Hashable {
    let title: String
    let description: String
}

struct FrameworkItem: Codable, Equatable, Hashable, Identifiable {
    let id: UUID
    let label: String
    let description: String

    init(label: String, description: String) {
        self.id = UUID()
        self.label = label
        self.description = description
    }
}

struct Lesson: Identifiable, Codable, Equatable, Hashable {
    let id: UUID
    let number: Int
    let category: LessonCategory
    let title: String
    let keyConcept: KeyConcept
    let content: [ContentBlock]
    let estimatedReadingMinutes: Int

    init(
        number: Int,
        category: LessonCategory,
        title: String,
        keyConcept: KeyConcept,
        content: [ContentBlock],
        estimatedReadingMinutes: Int = 5
    ) {
        self.id = UUID()
        self.number = number
        self.category = category
        self.title = title
        self.keyConcept = keyConcept
        self.content = content
        self.estimatedReadingMinutes = estimatedReadingMinutes
    }
}


Continuando com a análise:


find ~/Apps/macOS -name "*.entitlements" | head -5

Find entitlements files in macOS apps


     1→import SwiftUI
     2→import SwiftData
     3→
     4→/// Flag to track if app is using in-memory storage (data won't persist)
     5→var isUsingInMemoryStorage = false
     6→
     7→@main
     8→struct YouTubeTranscriptsApp: App {
     9→    @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    10→
    11→    var sharedModelContainer: ModelContainer = {
    12→        let schema = Schema([SavedVideo.self, Course.self, CourseVideo.self])
    13→
    14→        // Create app data directory if it doesn't exist
    15→        let appSupportURL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
    16→        let appDataURL = appSupportURL.appendingPathComponent("YouTubeTranscripts", isDirectory: true)
    17→
    18→        do {
    19→            try FileManager.default.createDirectory(at: appDataURL, withIntermediateDirectories: true)
    20→        } catch {
    21→            Task {
    22→                await AppLogger.shared.error("Could not create app data directory: \(error)", category: .app)
    23→            }
    24→        }
    25→
    26→        let storeURL = appDataURL.appendingPathComponent("YouTubeTranscripts.store")
    27→
    28→        let modelConfiguration = ModelConfiguration(
    29→            schema: schema,
    30→            url: storeURL,
    31→            allowsSave: true
    32→        )
    33→
    34→        do {
    35→            return try ModelContainer(for: schema, configurations: [modelConfiguration])
    36→        } catch {
    37→            // Fallback to in-memory if persistent storage fails
    38→            Task {
    39→                await AppLogger.shared.critical("Persistent store failed, using in-memory: \(error)", category: .app)
    40→            }
    41→            isUsingInMemoryStorage = true
    42→            let memoryConfig = ModelConfiguration(schema: schema, isStoredInMemoryOnly: true)
    43→            do {
    44→                return try ModelContainer(for: schema, configurations: [memoryConfig])
    45→            } catch {
    46→                fatalError("Cannot create model container: \(error.localizedDescription)")
    47→            }
    48→        }
    49→    }()
    50→
    51→    var body: some Scene {
    52→        WindowGroup {
    53→            ContentView()
    54→        }
    55→        .modelContainer(sharedModelContainer)
    56→        .commands {
    57→            CommandGroup(replacing: .appSettings) {
    58→                Button("Settings...") {
    59→                    NSApp.sendAction(Selector(("showSettingsWindow:")), to: nil, from: nil)
    60→                }
    61→                .keyboardShortcut(",", modifiers: [.command])
    62→            }
    63→
    64→            // File Menu
    65→            CommandGroup(after: .newItem) {
    66→                Button("New Transcript") {
    67→                    sendCommandCompat(.newTranscript)
    68→                }
    69→                .keyboardShortcut("n", modifiers: [.command])
    70→
    71→                Divider()
    72→
    73→                Button("Export Transcript...") {
    74→                    sendCommandCompat(.exportTranscript)
    75→                }
    76→                .keyboardShortcut("e", modifiers: [.command])
    77→
    78→                Button("Export as Markdown...") {
    79→                    sendCommandCompat(.exportMarkdown)
    80→                }
    81→                .keyboardShortcut("e", modifiers: [.command, .shift])
    82→
    83→                Button("Export as PDF...") {
    84→                    sendCommandCompat(.exportPDF)
    85→                }
    86→                .keyboardShortcut("p", modifiers: [.command, .shift])
    87→
    88→                Button("Open in Browser") {
    89→                    sendCommandCompat(.openInBrowser)
    90→                }
    91→                .keyboardShortcut("b", modifiers: [.command])
    92→
    93→                Divider()
    94→
    95→                Button("Delete Video") {
    96→                    sendCommandCompat(.deleteVideo)
    97→                }
    98→                .keyboardShortcut(.delete, modifiers: [.command])
    99→            }
   100→
   101→            // Edit Menu
   102→            CommandGroup(after: .pasteboard) {
   103→                Divider()
   104→
   105→                Button("Find in Transcript") {
   106→                    sendCommandCompat(.findInTranscript)
   107→                }
   108→                .keyboardShortcut("f", modifiers: [.command])
   109→
   110→                Button("Copy Transcript") {
   111→                    sendCommandCompat(.copyTranscript)
   112→                }
   113→                .keyboardShortcut("c", modifiers: [.command, .shift])
   114→
   115→                Divider()
   116→
   117→                Button("Highlight Selection") {
   118→                    sendCommandCompat(.highlightYellow)
   119→                }
   120→                .keyboardShortcut("h", modifiers: [.command])
   121→
   122→                Button("Underline Selection") {
   123→                    sendCommandCompat(.underlineSelection)
   124→                }
   125→                .keyboardShortcut("u", modifiers: [.command])
   126→
   127→                Button("Clear All Annotations") {
   128→                    sendCommandCompat(.clearAnnotations)
   129→                }
   130→                .keyboardShortcut("h", modifiers: [.command, .shift])
   131→            }
   132→
   133→            // View Menu
   134→            CommandGroup(after: .toolbar) {
   135→                Button("Increase Font Size") {
   136→                    sendCommandCompat(.increaseFontSize)
   137→                }
   138→                .keyboardShortcut("+", modifiers: [.command])
   139→
   140→                Button("Decrease Font Size") {
   141→                    sendCommandCompat(.decreaseFontSize)
   142→                }
   143→                .keyboardShortcut("-", modifiers: [.command])
   144→
   145→                Button("Reset Font Size") {
   146→                    sendCommandCompat(.resetFontSize)
   147→                }
   148→                .keyboardShortcut("0", modifiers: [.command])
   149→
   150→                Divider()
   151→
   152→                Button("Toggle Notes Panel") {
   153→                    sendCommandCompat(.toggleNotesPanel)
   154→                }
   155→                .keyboardShortcut("n", modifiers: [.command, .shift])
   156→
   157→                Button("Generate AI Summary") {
   158→                    sendCommandCompat(.generateAISummary)
   159→                }
   160→                .keyboardShortcut("s", modifiers: [.command, .shift])
   161→            }
   162→
   163→            // Playback Menu (TTS)
   164→            CommandMenu("Playback") {
   165→                Button("Play/Pause") {
   166→                    sendCommandCompat(.ttsPlayPause)
   167→                }
   168→                .keyboardShortcut(" ", modifiers: [])
   169→
   170→                Button("Stop") {
   171→                    sendCommandCompat(.ttsStop)
   172→                }
   173→                .keyboardShortcut(.escape, modifiers: [])
   174→
   175→                Divider()
   176→
   177→                Button("Skip Forward") {
   178→                    sendCommandCompat(.ttsSkipForward)
   179→                }
   180→                .keyboardShortcut(.rightArrow, modifiers: [.command])
   181→
   182→                Button("Skip Backward") {
   183→                    sendCommandCompat(.ttsSkipBackward)
   184→                }
   185→                .keyboardShortcut(.leftArrow, modifiers: [.command])
   186→
   187→                Divider()
   188→
   189→                Button("Speed Up") {
   190→                    sendCommandCompat(.ttsSpeedUp)
   191→                }
   192→                .keyboardShortcut("]", modifiers: [.command])
   193→
   194→                Button("Speed Down") {
   195→                    sendCommandCompat(.ttsSpeedDown)
   196→                }
   197→                .keyboardShortcut("[", modifiers: [.command])
   198→            }
   199→
   200→            // Developer Menu
   201→            CommandMenu("Developer") {
   202→                Button("Import Palantir Transcript") {
   203→                    sendCommandCompat(.importWhisperTranscript)
   204→                }
   205→                .keyboardShortcut("i", modifiers: [.command, .option, .shift])
   206→            }
   207→
   208→            // Help Menu
   209→            CommandGroup(replacing: .help) {
   210→                Button("Open Diagnostics") {
   211→                    sendCommandCompat(.openDiagnostics)
   212→                }
   213→                .keyboardShortcut("d", modifiers: [.command, .option])
   214→
   215→                Divider()
   216→
   217→                Button("Export Logs...") {
   218→                    sendCommandCompat(.exportLogs)
   219→                }
   220→
   221→                Button("Export Analytics...") {
   222→                    sendCommandCompat(.exportAnalytics)
   223→                }
   224→            }
   225→        }
   226→
   227→        Settings {
   228→            SettingsView()
   229→        }
   230→
   231→        // Diagnostics Window
   232→        Window("Diagnostics", id: "diagnostics") {
   233→            DiagnosticsView()
   234→        }
   235→        .defaultSize(width: 900, height: 700)
   236→    }
   237→}
   238→
   239→// MARK: - App Delegate
   240→
   241→class AppDelegate: NSObject, NSApplicationDelegate {
   242→    func applicationDidFinishLaunching(_ notification: Notification) {
   243→        // Initialize logging (session automatically started in AppLogger.init)
   244→        Task {
   245→            await AppLogger.shared.info("Application launched", category: .app)
   246→        }
   247→
   248→        // Setup crash handler
   249→        setupCrashHandler()
   250→    }
   251→
   252→    func applicationWillTerminate(_ notification: Notification) {
   253→        Task {
   254→            await AppLogger.shared.info("Application terminating", category: .app)
   255→            await AppLogger.shared.endSession()
   256→        }
   257→    }
   258→
   259→    func applicationDidBecomeActive(_ notification: Notification) {
   260→        trackEvent(.appBecameActive)
   261→    }
   262→
   263→    func applicationDidResignActive(_ notification: Notification) {
   264→        trackEvent(.appResignedActive)
   265→    }
   266→
   267→    private func setupCrashHandler() {
   268→        NSSetUncaughtExceptionHandler { exception in
   269→            let message = "Uncaught exception: \(exception.name.rawValue) - \(exception.reason ?? "Unknown")"
   270→            // Write to stderr (async-signal-safe)
   271→            FileHandle.standardError.write(Data("[CRASH] \(message)\n".utf8))
   272→
   273→            // Try to save crash info (best effort, may not complete)
   274→            Task {
   275→                await AppLogger.shared.endSession(crashReason: message)
   276→            }
   277→        }
   278→
   279→        // Note: Signal handlers removed - they cannot safely call non-async-signal-safe functions.
   280→        // macOS crash reporter handles SIGABRT/SIGSEGV automatically.
   281→    }
   282→}
   283→
   284→// MARK: - Notifications
   285→
   286→extension Notification.Name {
   287→    // File operations
   288→    static let newTranscript = Notification.Name("newTranscript")
   289→    static let exportTranscript = Notification.Name("exportTranscript")
   290→    static let exportMarkdown = Notification.Name("exportMarkdown")
   291→    static let exportPDF = Notification.Name("exportPDF")
   292→    static let openInBrowser = Notification.Name("openInBrowser")
   293→    static let deleteVideo = Notification.Name("deleteVideo")
   294→    static let findInTranscript = Notification.Name("findInTranscript")
   295→    static let copyTranscript = Notification.Name("copyTranscript")
   296→    static let increaseFontSize = Notification.Name("increaseFontSize")
   297→    static let decreaseFontSize = Notification.Name("decreaseFontSize")
   298→    static let resetFontSize = Notification.Name("resetFontSize")
   299→    // Annotation shortcuts
   300→    static let highlightYellow = Notification.Name("highlightYellow")
   301→    static let underlineSelection = Notification.Name("underlineSelection")
   302→    static let clearAnnotations = Notification.Name("clearAnnotations")
   303→    // Notes panel shortcuts
   304→    static let toggleNotesPanel = Notification.Name("toggleNotesPanel")
   305→    static let generateAISummary = Notification.Name("generateAISummary")
   306→    // TTS/Playback shortcuts
   307→    static let ttsPlayPause = Notification.Name("ttsPlayPause")
   308→    static let ttsStop = Notification.Name("ttsStop")
   309→    static let ttsSkipForward = Notification.Name("ttsSkipForward")
   310→    static let ttsSkipBackward = Notification.Name("ttsSkipBackward")
   311→    static let ttsSpeedUp = Notification.Name("ttsSpeedUp")
   312→    static let ttsSpeedDown = Notification.Name("ttsSpeedDown")
   313→    // Diagnostics
   314→    static let openDiagnostics = Notification.Name("openDiagnostics")
   315→    static let exportLogs = Notification.Name("exportLogs")
   316→    static let exportAnalytics = Notification.Name("exportAnalytics")
   317→    // Developer
   318→    static let importWhisperTranscript = Notification.Name("importWhisperTranscript")
   319→    // Courses
   320→    static let importCourse = Notification.Name("importCourse")
   321→    static let startCourseProcessing = Notification.Name("startCourseProcessing")
   322→    static let pauseCourseProcessing = Notification.Name("pauseCourseProcessing")
   323→}
   324→

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

import SwiftUI
import SwiftData

/// Flag to track if app is using in-memory storage (data won't persist)
var isUsingInMemoryStorage = false

@main
struct YouTubeTranscriptsApp: App {
    @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

    var sharedModelContainer: ModelContainer = {
        let schema = Schema([SavedVideo.self, Course.self, CourseVideo.self])

        // Create app data directory if it doesn't exist
        let appSupportURL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
        let appDataURL = appSupportURL.appendingPathComponent("YouTubeTranscripts", isDirectory: true)

        do {
            try FileManager.default.createDirectory(at: appDataURL, withIntermediateDirectories: true)
        } catch {
            Task {
                await AppLogger.shared.error("Could not create app data directory: \(error)", category: .app)
            }
        }

        let storeURL = appDataURL.appendingPathComponent("YouTubeTranscripts.store")

        let modelConfiguration = ModelConfiguration(
            schema: schema,
            url: storeURL,
            allowsSave: true
        )

        do {
            return try ModelContainer(for: schema, configurations: [modelConfiguration])
        } catch {
            // Fallback to in-memory if persistent storage fails
            Task {
                await AppLogger.shared.critical("Persistent store failed, using in-memory: \(error)", category: .app)
            }
            isUsingInMemoryStorage = true
            let memoryConfig = ModelConfiguration(schema: schema, isStoredInMemoryOnly: true)
            do {
                return try ModelContainer(for: schema, configurations: [memoryConfig])
            } catch {
                fatalError("Cannot create model container: \(error.localizedDescription)")
            }
        }
    }()

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .modelContainer(sharedModelContainer)
        .commands {
            CommandGroup(replacing: .appSettings) {
                Button("Settings...") {
                    NSApp.sendAction(Selector(("showSettingsWindow:")), to: nil, from: nil)
                }
                .keyboardShortcut(",", modifiers: [.command])
            }

            // File Menu
            CommandGroup(after: .newItem) {
                Button("New Transcript") {
                    sendCommandCompat(.newTranscript)
                }
                .keyboardShortcut("n", modifiers: [.command])

                Divider()

                Button("Export Transcript...") {
                    sendCommandCompat(.exportTranscript)
                }
                .keyboardShortcut("e", modifiers: [.command])

                Button("Export as Markdown...") {
                    sendCommandCompat(.exportMarkdown)
                }
                .keyboardShortcut("e", modifiers: [.command, .shift])

                Button("Export as PDF...") {
                    sendCommandCompat(.exportPDF)
                }
                .keyboardShortcut("p", modifiers: [.command, .shift])

                Button("Open in Browser") {
                    sendCommandCompat(.openInBrowser)
                }
                .keyboardShortcut("b", modifiers: [.command])

                Divider()

                Button("Delete Video") {
                    sendCommandCompat(.deleteVideo)
                }
                .keyboardShortcut(.delete, modifiers: [.command])
            }

            // Edit Menu
            CommandGroup(after: .pasteboard) {
                Divider()

                Button("Find in Transcript") {
                    sendCommandCompat(.findInTranscript)
                }
                .keyboardShortcut("f", modifiers: [.command])

                Button("Copy Transcript") {
                    sendCommandCompat(.copyTranscript)
                }
                .keyboardShortcut("c", modifiers: [.command, .shift])

                Divider()

                Button("Highlight Selection") {
                    sendCommandCompat(.highlightYellow)
                }
                .keyboardShortcut("h", modifiers: [.command])

                Button("Underline Selection") {
                    sendCommandCompat(.underlineSelection)
                }
                .keyboardShortcut("u", modifiers: [.command])

                Button("Clear All Annotations") {
                    sendCommandCompat(.clearAnnotations)
                }
                .keyboardShortcut("h", modifiers: [.command, .shift])
            }

            // View Menu
            CommandGroup(after: .toolbar) {
                Button("Increase Font Size") {
                    sendCommandCompat(.increaseFontSize)
                }
                .keyboardShortcut("+", modifiers: [.command])

                Button("Decrease Font Size") {
                    sendCommandCompat(.decreaseFontSize)
                }
                .keyboardShortcut("-", modifiers: [.command])

                Button("Reset Font Size") {
                    sendCommandCompat(.resetFontSize)
                }
                .keyboardShortcut("0", modifiers: [.command])

                Divider()

                Button("Toggle Notes Panel") {
                    sendCommandCompat(.toggleNotesPanel)
                }
                .keyboardShortcut("n", modifiers: [.command, .shift])

                Button("Generate AI Summary") {
                    sendCommandCompat(.generateAISummary)
                }
                .keyboardShortcut("s", modifiers: [.command, .shift])
            }

            // Playback Menu (TTS)
            CommandMenu("Playback") {
                Button("Play/Pause") {
                    sendCommandCompat(.ttsPlayPause)
                }
                .keyboardShortcut(" ", modifiers: [])

                Button("Stop") {
                    sendCommandCompat(.ttsStop)
                }
                .keyboardShortcut(.escape, modifiers: [])

                Divider()

                Button("Skip Forward") {
                    sendCommandCompat(.ttsSkipForward)
                }
                .keyboardShortcut(.rightArrow, modifiers: [.command])

                Button("Skip Backward") {
                    sendCommandCompat(.ttsSkipBackward)
                }
                .keyboardShortcut(.leftArrow, modifiers: [.command])

                Divider()

                Button("Speed Up") {
                    sendCommandCompat(.ttsSpeedUp)
                }
                .keyboardShortcut("]", modifiers: [.command])

                Button("Speed Down") {
                    sendCommandCompat(.ttsSpeedDown)
                }
                .keyboardShortcut("[", modifiers: [.command])
            }

            // Developer Menu
            CommandMenu("Developer") {
                Button("Import Palantir Transcript") {
                    sendCommandCompat(.importWhisperTranscript)
                }
                .keyboardShortcut("i", modifiers: [.command, .option, .shift])
            }

            // Help Menu
            CommandGroup(replacing: .help) {
                Button("Open Diagnostics") {
                    sendCommandCompat(.openDiagnostics)
                }
                .keyboardShortcut("d", modifiers: [.command, .option])

                Divider()

                Button("Export Logs...") {
                    sendCommandCompat(.exportLogs)
                }

                Button("Export Analytics...") {
                    sendCommandCompat(.exportAnalytics)
                }
            }
        }

        Settings {
            SettingsView()
        }

        // Diagnostics Window
        Window("Diagnostics", id: "diagnostics") {
            DiagnosticsView()
        }
        .defaultSize(width: 900, height: 700)
    }
}

// MARK: - App Delegate

class AppDelegate: NSObject, NSApplicationDelegate {
    func applicationDidFinishLaunching(_ notification: Notification) {
        // Initialize logging (session automatically started in AppLogger.init)
        Task {
            await AppLogger.shared.info("Application launched", category: .app)
        }

        // Setup crash handler
        setupCrashHandler()
    }

    func applicationWillTerminate(_ notification: Notification) {
        Task {
            await AppLogger.shared.info("Application terminating", category: .app)
            await AppLogger.shared.endSession()
        }
    }

    func applicationDidBecomeActive(_ notification: Notification) {
        trackEvent(.appBecameActive)
    }

    func applicationDidResignActive(_ notification: Notification) {
        trackEvent(.appResignedActive)
    }

    private func setupCrashHandler() {
        NSSetUncaughtExceptionHandler { exception in
            let message = "Uncaught exception: \(exception.name.rawValue) - \(exception.reason ?? "Unknown")"
            // Write to stderr (async-signal-safe)
            FileHandle.standardError.write(Data("[CRASH] \(message)\n".utf8))

            // Try to save crash info (best effort, may not complete)
            Task {
                await AppLogger.shared.endSession(crashReason: message)
            }
        }

        // Note: Signal handlers removed - they cannot safely call non-async-signal-safe functions.
        // macOS crash reporter handles SIGABRT/SIGSEGV automatically.
    }
}

// MARK: - Notifications

extension Notification.Name {
    // File operations
    static let newTranscript = Notification.Name("newTranscript")
    static let exportTranscript = Notification.Name("exportTranscript")
    static let exportMarkdown = Notification.Name("exportMarkdown")
    static let exportPDF = Notification.Name("exportPDF")
    static let openInBrowser = Notification.Name("openInBrowser")
    static let deleteVideo = Notification.Name("deleteVideo")
    static let findInTranscript = Notification.Name("findInTranscript")
    static let copyTranscript = Notification.Name("copyTranscript")
    static let increaseFontSize = Notification.Name("increaseFontSize")
    static let decreaseFontSize = Notification.Name("decreaseFontSize")
    static let resetFontSize = Notification.Name("resetFontSize")
    // Annotation shortcuts
    static let highlightYellow = Notification.Name("highlightYellow")
    static let underlineSelection = Notification.Name("underlineSelection")
    static let clearAnnotations = Notification.Name("clearAnnotations")
    // Notes panel shortcuts
    static let toggleNotesPanel = Notification.Name("toggleNotesPanel")
    static let generateAISummary = Notification.Name("generateAISummary")
    // TTS/Playback shortcuts
    static let ttsPlayPause = Notification.Name("ttsPlayPause")
    static let ttsStop = Notification.Name("ttsStop")
    static let ttsSkipForward = Notification.Name("ttsSkipForward")
    static let ttsSkipBackward = Notification.Name("ttsSkipBackward")
    static let ttsSpeedUp = Notification.Name("ttsSpeedUp")
    static let ttsSpeedDown = Notification.Name("ttsSpeedDown")
    // Diagnostics
    static let openDiagnostics = Notification.Name("openDiagnostics")
    static let exportLogs = Notification.Name("exportLogs")
    static let exportAnalytics = Notification.Name("exportAnalytics")
    // Developer
    static let importWhisperTranscript = Notification.Name("importWhisperTranscript")
    // Courses
    static let importCourse = Notification.Name("importCourse")
    static let startCourseProcessing = Notification.Name("startCourseProcessing")
    static let pauseCourseProcessing = Notification.Name("pauseCourseProcessing")
}


     1→import Foundation
     2→import SwiftData
     3→
     4→// MARK: - Course Model
     5→
     6→/// Represents a course (collection of videos from a YouTube channel)
     7→@Model
     8→final class Course {
     9→    var id: UUID
    10→    var name: String
    11→    var channelName: String
    12→    var channelURL: String
    13→    var createdAt: Date
    14→    var totalVideos: Int
    15→    var completedVideos: Int
    16→    var isProcessing: Bool
    17→    var currentVideoIndex: Int
    18→    var lastProcessedAt: Date?
    19→
    20→    @Relationship(deleteRule: .cascade, inverse: \CourseVideo.course)
    21→    var videos: [CourseVideo]
    22→
    23→    init(
    24→        name: String,
    25→        channelName: String,
    26→        channelURL: String,
    27→        totalVideos: Int = 0
    28→    ) {
    29→        self.id = UUID()
    30→        self.name = name
    31→        self.channelName = channelName
    32→        self.channelURL = channelURL
    33→        self.createdAt = Date()
    34→        self.totalVideos = totalVideos
    35→        self.completedVideos = 0
    36→        self.isProcessing = false
    37→        self.currentVideoIndex = 0
    38→        self.videos = []
    39→    }
    40→
    41→    // MARK: - Computed Properties
    42→
    43→    var progress: Double {
    44→        guard totalVideos > 0 else { return 0 }
    45→        return Double(completedVideos) / Double(totalVideos)
    46→    }
    47→
    48→    var progressPercentage: Int {
    49→        Int(progress * 100)
    50→    }
    51→
    52→    var pendingVideos: [CourseVideo] {
    53→        videos.filter { $0.status == .pending }.sorted { $0.orderIndex < $1.orderIndex }
    54→    }
    55→
    56→    var nextPendingVideo: CourseVideo? {
    57→        pendingVideos.first
    58→    }
    59→
    60→    var failedVideos: [CourseVideo] {
    61→        videos.filter { $0.status == .failed }
    62→    }
    63→
    64→    var sortedVideos: [CourseVideo] {
    65→        videos.sorted { $0.orderIndex < $1.orderIndex }
    66→    }
    67→
    68→    // MARK: - Methods
    69→
    70→    func updateProgress() {
    71→        completedVideos = videos.filter { $0.status == .completed }.count
    72→        totalVideos = videos.count
    73→    }
    74→}
    75→
    76→// MARK: - CourseVideo Model
    77→
    78→/// Represents a single video within a course
    79→@Model
    80→final class CourseVideo {
    81→    var id: UUID
    82→    var youtubeURL: String
    83→    var videoID: String
    84→    var title: String
    85→    var orderIndex: Int
    86→    var statusRaw: String
    87→    var errorMessage: String?
    88→    var processedAt: Date?
    89→    var savedVideoID: UUID?
    90→
    91→    var course: Course?
    92→
    93→    init(
    94→        youtubeURL: String,
    95→        videoID: String,
    96→        title: String,
    97→        orderIndex: Int
    98→    ) {
    99→        self.id = UUID()
   100→        self.youtubeURL = youtubeURL
   101→        self.videoID = videoID
   102→        self.title = title
   103→        self.orderIndex = orderIndex
   104→        self.statusRaw = VideoStatus.pending.rawValue
   105→    }
   106→
   107→    // MARK: - Status
   108→
   109→    enum VideoStatus: String, Codable {
   110→        case pending
   111→        case processing
   112→        case completed
   113→        case failed
   114→        case skipped
   115→    }
   116→
   117→    var status: VideoStatus {
   118→        get { VideoStatus(rawValue: statusRaw) ?? .pending }
   119→        set { statusRaw = newValue.rawValue }
   120→    }
   121→
   122→    // MARK: - Methods
   123→
   124→    func markAsProcessing() {
   125→        status = .processing
   126→    }
   127→
   128→    func markAsCompleted(savedVideoID: UUID) {
   129→        status = .completed
   130→        self.savedVideoID = savedVideoID
   131→        processedAt = Date()
   132→    }
   133→
   134→    func markAsFailed(error: String) {
   135→        status = .failed
   136→        errorMessage = error
   137→        processedAt = Date()
   138→    }
   139→
   140→    func markAsSkipped() {
   141→        status = .skipped
   142→        processedAt = Date()
   143→    }
   144→
   145→    func reset() {
   146→        status = .pending
   147→        errorMessage = nil
   148→        processedAt = nil
   149→        savedVideoID = nil
   150→    }
   151→}
   152→
   153→// MARK: - Video Import Data
   154→
   155→/// Structure for parsing JSON from Safari console
   156→struct VideoImportData: Codable {
   157→    let url: String
   158→    let title: String
   159→    let orderIndex: Int?
   160→
   161→    var videoID: String? {
   162→        // Extract video ID from URL
   163→        let patterns = [
   164→            #"(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([a-zA-Z0-9_-]{11})"#,
   165→            #"^([a-zA-Z0-9_-]{11})$"#
   166→        ]
   167→
   168→        for pattern in patterns {
   169→            if let regex = try? NSRegularExpression(pattern: pattern),
   170→               let match = regex.firstMatch(in: url, range: NSRange(url.startIndex..., in: url)),
   171→               let range = Range(match.range(at: 1), in: url) {
   172→                return String(url[range])
   173→            }
   174→        }
   175→        return nil
   176→    }
   177→}
   178→

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

import Foundation
import SwiftData

// MARK: - Course Model

/// Represents a course (collection of videos from a YouTube channel)
@Model
final class Course {
    var id: UUID
    var name: String
    var channelName: String
    var channelURL: String
    var createdAt: Date
    var totalVideos: Int
    var completedVideos: Int
    var isProcessing: Bool
    var currentVideoIndex: Int
    var lastProcessedAt: Date?

    @Relationship(deleteRule: .cascade, inverse: \CourseVideo.course)
    var videos: [CourseVideo]

    init(
        name: String,
        channelName: String,
        channelURL: String,
        totalVideos: Int = 0
    ) {
        self.id = UUID()
        self.name = name
        self.channelName = channelName
        self.channelURL = channelURL
        self.createdAt = Date()
        self.totalVideos = totalVideos
        self.completedVideos = 0
        self.isProcessing = false
        self.currentVideoIndex = 0
        self.videos = []
    }

    // MARK: - Computed Properties

    var progress: Double {
        guard totalVideos > 0 else { return 0 }
        return Double(completedVideos) / Double(totalVideos)
    }

    var progressPercentage: Int {
        Int(progress * 100)
    }

    var pendingVideos: [CourseVideo] {
        videos.filter { $0.status == .pending }.sorted { $0.orderIndex < $1.orderIndex }
    }

    var nextPendingVideo: CourseVideo? {
        pendingVideos.first
    }

    var failedVideos: [CourseVideo] {
        videos.filter { $0.status == .failed }
    }

    var sortedVideos: [CourseVideo] {
        videos.sorted { $0.orderIndex < $1.orderIndex }
    }

    // MARK: - Methods

    func updateProgress() {
        completedVideos = videos.filter { $0.status == .completed }.count
        totalVideos = videos.count
    }
}

// MARK: - CourseVideo Model

/// Represents a single video within a course
@Model
final class CourseVideo {
    var id: UUID
    var youtubeURL: String
    var videoID: String
    var title: String
    var orderIndex: Int
    var statusRaw: String
    var errorMessage: String?
    var processedAt: Date?
    var savedVideoID: UUID?

    var course: Course?

    init(
        youtubeURL: String,
        videoID: String,
        title: String,
        orderIndex: Int
    ) {
        self.id = UUID()
        self.youtubeURL = youtubeURL
        self.videoID = videoID
        self.title = title
        self.orderIndex = orderIndex
        self.statusRaw = VideoStatus.pending.rawValue
    }

    // MARK: - Status

    enum VideoStatus: String, Codable {
        case pending
        case processing
        case completed
        case failed
        case skipped
    }

    var status: VideoStatus {
        get { VideoStatus(rawValue: statusRaw) ?? .pending }
        set { statusRaw = newValue.rawValue }
    }

    // MARK: - Methods

    func markAsProcessing() {
        status = .processing
    }

    func markAsCompleted(savedVideoID: UUID) {
        status = .completed
        self.savedVideoID = savedVideoID
        processedAt = Date()
    }

    func markAsFailed(error: String) {
        status = .failed
        errorMessage = error
        processedAt = Date()
    }

    func markAsSkipped() {
        status = .skipped
        processedAt = Date()
    }

    func reset() {
        status = .pending
        errorMessage = nil
        processedAt = nil
        savedVideoID = nil
    }
}

// MARK: - Video Import Data

/// Structure for parsing JSON from Safari console
struct VideoImportData: Codable {
    let url: String
    let title: String
    let orderIndex: Int?

    var videoID: String? {
        // Extract video ID from URL
        let patterns = [
            #"(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([a-zA-Z0-9_-]{11})"#,
            #"^([a-zA-Z0-9_-]{11})$"#
        ]

        for pattern in patterns {
            if let regex = try? NSRegularExpression(pattern: pattern),
               let match = regex.firstMatch(in: url, range: NSRange(url.startIndex..., in: url)),
               let range = Range(match.range(at: 1), in: url) {
                return String(url[range])
            }
        }
        return nil
    }
}


     1→import Foundation
     2→import Combine
     3→#if os(macOS)
     4→import AppKit
     5→#endif
     6→import UniformTypeIdentifiers
     7→
     8→class DataManager: ObservableObject {
     9→    static let shared = DataManager()
    10→
    11→    // MARK: - Published Properties
    12→
    13→    @Published var lessons: [Lesson] = []
    14→    @Published var userProgress: UserProgress = UserProgress()
    15→    @Published var notes: [Note] = []
    16→    @Published var gamification: GamificationProgress = GamificationProgress()
    17→    @Published var readingSettings: ReadingSettings = ReadingSettings()
    18→    @Published var highlights: [Highlight] = []
    19→    @Published var flashcards: [Flashcard] = []
    20→
    21→    // MARK: - Error Handling
    22→    @Published var lastError: DataError?
    23→    @Published var showingError: Bool = false
    24→
    25→    enum DataError: LocalizedError {
    26→        case saveFailed(String)
    27→        case loadFailed(String)
    28→        case importFailed(String)
    29→        case exportFailed(String)
    30→        case validationFailed(String)
    31→
    32→        var errorDescription: String? {
    33→            switch self {
    34→            case .saveFailed(let details): return "Erro ao salvar: \(details)"
    35→            case .loadFailed(let details): return "Erro ao carregar: \(details)"
    36→            case .importFailed(let details): return "Erro ao importar: \(details)"
    37→            case .exportFailed(let details): return "Erro ao exportar: \(details)"
    38→            case .validationFailed(let details): return "Validacao falhou: \(details)"
    39→            }
    40→        }
    41→    }
    42→
    43→    // MARK: - File URLs
    44→
    45→    private let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
    46→
    47→    private var progressPrimaryURL: URL { documentsURL.appendingPathComponent("17licoes_progress.json") }
    48→    private var progressBackup1URL: URL { documentsURL.appendingPathComponent("17licoes_progress_backup1.json") }
    49→    private var progressBackup2URL: URL { documentsURL.appendingPathComponent("17licoes_progress_backup2.json") }
    50→
    51→    private var notesPrimaryURL: URL { documentsURL.appendingPathComponent("17licoes_notes.json") }
    52→    private var notesBackup1URL: URL { documentsURL.appendingPathComponent("17licoes_notes_backup1.json") }
    53→    private var notesBackup2URL: URL { documentsURL.appendingPathComponent("17licoes_notes_backup2.json") }
    54→
    55→    private var gamificationURL: URL { documentsURL.appendingPathComponent("17licoes_gamification.json") }
    56→    private var settingsURL: URL { documentsURL.appendingPathComponent("17licoes_settings.json") }
    57→    private var highlightsURL: URL { documentsURL.appendingPathComponent("17licoes_highlights.json") }
    58→    private var flashcardsURL: URL { documentsURL.appendingPathComponent("17licoes_flashcards.json") }
    59→
    60→    // MARK: - Debounce
    61→
    62→    private var saveCancellable: AnyCancellable?
    63→    private let saveDebouncer = PassthroughSubject<Void, Never>()
    64→
    65→    // MARK: - Initialization
    66→
    67→    private init() {
    68→        lessons = LessonsData.allLessons
    69→        loadAll()
    70→        setupFlashcardsIfNeeded()
    71→
    72→        // Setup debounced save
    73→        saveCancellable = saveDebouncer
    74→            .debounce(for: .seconds(2), scheduler: DispatchQueue.main)
    75→            .sink { [weak self] _ in
    76→                self?.saveAll()
    77→            }
    78→    }
    79→
    80→    // MARK: - Progress Management
    81→
    82→    func toggleRead(_ lessonId: UUID) {
    83→        userProgress.toggleRead(lessonId)
    84→        gamification.recordLessonRead(lessonId)
    85→        checkAchievements()
    86→        triggerSave()
    87→    }
    88→
    89→    func markAsRead(_ lessonId: UUID) {
    90→        userProgress.markAsRead(lessonId)
    91→        gamification.recordLessonRead(lessonId)
    92→        checkAchievements()
    93→        triggerSave()
    94→    }
    95→
    96→    func toggleFavorite(_ lessonId: UUID) {
    97→        userProgress.toggleFavorite(lessonId)
    98→        triggerSave()
    99→    }
   100→

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

import Foundation
import Combine
#if os(macOS)
import AppKit
#endif
import UniformTypeIdentifiers

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

    // MARK: - Published Properties

    @Published var lessons: [Lesson] = []
    @Published var userProgress: UserProgress = UserProgress()
    @Published var notes: [Note] = []
    @Published var gamification: GamificationProgress = GamificationProgress()
    @Published var readingSettings: ReadingSettings = ReadingSettings()
    @Published var highlights: [Highlight] = []
    @Published var flashcards: [Flashcard] = []

    // MARK: - Error Handling
    @Published var lastError: DataError?
    @Published var showingError: Bool = false

    enum DataError: LocalizedError {
        case saveFailed(String)
        case loadFailed(String)
        case importFailed(String)
        case exportFailed(String)
        case validationFailed(String)

        var errorDescription: String? {
            switch self {
            case .saveFailed(let details): return "Erro ao salvar: \(details)"
            case .loadFailed(let details): return "Erro ao carregar: \(details)"
            case .importFailed(let details): return "Erro ao importar: \(details)"
            case .exportFailed(let details): return "Erro ao exportar: \(details)"
            case .validationFailed(let details): return "Validacao falhou: \(details)"
            }
        }
    }

    // MARK: - File URLs

    private let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]

    private var progressPrimaryURL: URL { documentsURL.appendingPathComponent("17licoes_progress.json") }
    private var progressBackup1URL: URL { documentsURL.appendingPathComponent("17licoes_progress_backup1.json") }
    private var progressBackup2URL: URL { documentsURL.appendingPathComponent("17licoes_progress_backup2.json") }

    private var notesPrimaryURL: URL { documentsURL.appendingPathComponent("17licoes_notes.json") }
    private var notesBackup1URL: URL { documentsURL.appendingPathComponent("17licoes_notes_backup1.json") }
    private var notesBackup2URL: URL { documentsURL.appendingPathComponent("17licoes_notes_backup2.json") }

    private var gamificationURL: URL { documentsURL.appendingPathComponent("17licoes_gamification.json") }
    private var settingsURL: URL { documentsURL.appendingPathComponent("17licoes_settings.json") }
    private var highlightsURL: URL { documentsURL.appendingPathComponent("17licoes_highlights.json") }
    private var flashcardsURL: URL { documentsURL.appendingPathComponent("17licoes_flashcards.json") }

    // MARK: - Debounce

    private var saveCancellable: AnyCancellable?
    private let saveDebouncer = PassthroughSubject<Void, Never>()

    // MARK: - Initialization

    private init() {
        lessons = LessonsData.allLessons
        loadAll()
        setupFlashcardsIfNeeded()

        // Setup debounced save
        saveCancellable = saveDebouncer
            .debounce(for: .seconds(2), scheduler: DispatchQueue.main)
            .sink { [weak self] _ in
                self?.saveAll()
            }
    }

    // MARK: - Progress Management

    func toggleRead(_ lessonId: UUID) {
        userProgress.toggleRead(lessonId)
        gamification.recordLessonRead(lessonId)
        checkAchievements()
        triggerSave()
    }

    func markAsRead(_ lessonId: UUID) {
        userProgress.markAsRead(lessonId)
        gamification.recordLessonRead(lessonId)
        checkAchievements()
        triggerSave()
    }

    func toggleFavorite(_ lessonId: UUID) {
        userProgress.toggleFavorite(lessonId)
        triggerSave()
    }


Excelente! Vou analisar um arquivo de entitlements e ver exemplos de WebKit:


grep -r "WebView\|WebKit\|WKWebView" ~/Apps/macOS --include="*.swift" | head -20

Search for WebKit usage in macOS apps


<tool_use_error>File does not exist. Did you mean YouTubeTranscripts.xcodeproj?</tool_use_error>

Error: File does not exist. Did you mean YouTubeTranscripts.xcodeproj?