import IndieAbout import SwiftData import SwiftUI @main struct NotesApp: App { @State private var appServices: AppServices? @State private var pendingBackupURL: URL? @Environment(\.scenePhase) private var scenePhase var body: some Scene { WindowGroup { Group { if let appServices { ContentView() .modelContainer(appServices.modelContainer) .environment(appServices.syncEngine) .environment(appServices.contextService) .environmentObject(appServices.backupController) } else { ProgressView() } } .task { if appServices == nil { let services = await AppServices() appServices = services await services.startDeferredServices() if let pending = pendingBackupURL { pendingBackupURL = nil restore(pending, with: services) } } } .onOpenURL { url in handle(url: url) } .onChange(of: scenePhase) { _, phase in guard phase == .active, let appServices else { return } let syncEngine = appServices.syncEngine let contextService = appServices.contextService Task { // Fresh context whenever the user comes back to the app — // recall ranking should reflect where they are right now. await contextService.refresh() } Task { switch syncEngine.iCloudStatus { case .unavailable: // The user typically fixes iCloud in Settings and // switches straight back — retry on foreground. await syncEngine.connect() case .available: // Pull in records that synced down (and drop ones // deleted elsewhere) while the app was backgrounded. // Skip during a restore — it stops the monitors and // rebuilds the cache itself; a reconcile racing the // file swap would import a half-restored state. if !appServices.backupController.isRestoring { await syncEngine.reconcile() } case .checking: break } } } } #if os(macOS) .defaultSize(width: 900, height: 700) .commands { IndieAboutCommand(configuration: .init( documents: [ .license(filename: "LICENSE", extension: "md"), .custom(title: "Changelog", filename: "CHANGELOG", extension: "md") ] )) } #endif } /// A tapped `.notesbackup` file (Files, AirDrop, Mail) restores the /// backup. If it arrives before services finish launching, it's queued. private func handle(url: URL) { guard url.isFileURL, url.pathExtension == "notesbackup" else { return } if let appServices { restore(url, with: appServices) } else { pendingBackupURL = url } } private func restore(_ url: URL, with services: AppServices) { let didScope = url.startAccessingSecurityScopedResource() Task { defer { if didScope { url.stopAccessingSecurityScopedResource() } } try? await services.backupController.restoreBackup(from: url) } } }