2025-09-25 09:00:22 +00:00
|
|
|
import SwiftUI
|
2026-02-07 10:51:52 +00:00
|
|
|
#if canImport(FoundationModels)
|
2025-09-25 09:01:45 +00:00
|
|
|
import FoundationModels
|
2026-02-07 10:51:52 +00:00
|
|
|
#endif
|
|
|
|
|
#if os(macOS)
|
2026-01-25 12:46:33 +00:00
|
|
|
import AppKit
|
2026-02-07 10:51:52 +00:00
|
|
|
#endif
|
2026-02-12 22:20:39 +00:00
|
|
|
#if os(iOS)
|
|
|
|
|
import UIKit
|
|
|
|
|
#endif
|
2026-01-25 12:46:33 +00:00
|
|
|
|
2026-02-07 10:51:52 +00:00
|
|
|
#if os(macOS)
|
2026-03-09 16:47:50 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
/// MARK: - Types
|
|
|
|
|
|
2026-01-25 12:46:33 +00:00
|
|
|
final class AppDelegate: NSObject, NSApplicationDelegate {
|
2026-02-08 23:15:47 +00:00
|
|
|
weak var viewModel: EditorViewModel? {
|
|
|
|
|
didSet {
|
|
|
|
|
guard let viewModel else { return }
|
|
|
|
|
Task { @MainActor in
|
|
|
|
|
self.flushPendingURLs(into: viewModel)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-16 13:39:27 +00:00
|
|
|
weak var appUpdateManager: AppUpdateManager?
|
2026-02-08 23:15:47 +00:00
|
|
|
private var pendingOpenURLs: [URL] = []
|
2026-01-25 12:46:33 +00:00
|
|
|
|
|
|
|
|
func application(_ application: NSApplication, open urls: [URL]) {
|
|
|
|
|
Task { @MainActor in
|
2026-02-08 23:43:57 +00:00
|
|
|
for url in urls {
|
|
|
|
|
if let existing = WindowViewModelRegistry.shared.viewModel(containing: url) {
|
|
|
|
|
_ = existing.viewModel.focusTabIfOpen(for: url)
|
|
|
|
|
if let window = NSApp.window(withWindowNumber: existing.windowNumber) {
|
|
|
|
|
window.makeKeyAndOrderFront(nil)
|
|
|
|
|
NSApp.activate(ignoringOtherApps: true)
|
|
|
|
|
}
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
let target = WindowViewModelRegistry.shared.activeViewModel() ?? self.viewModel
|
|
|
|
|
if let target {
|
|
|
|
|
target.openFile(url: url)
|
|
|
|
|
} else {
|
|
|
|
|
self.pendingOpenURLs.append(url)
|
|
|
|
|
}
|
2026-01-25 12:46:33 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-08 23:15:47 +00:00
|
|
|
|
2026-02-08 23:43:57 +00:00
|
|
|
func applicationShouldOpenUntitledFile(_ sender: NSApplication) -> Bool {
|
|
|
|
|
return NSApp.windows.isEmpty && pendingOpenURLs.isEmpty
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-16 13:39:27 +00:00
|
|
|
func applicationWillTerminate(_ notification: Notification) {
|
|
|
|
|
appUpdateManager?.applicationWillTerminate()
|
2026-03-03 09:47:45 +00:00
|
|
|
RuntimeReliabilityMonitor.shared.markGracefulTermination()
|
2026-02-16 13:39:27 +00:00
|
|
|
}
|
|
|
|
|
|
2026-02-08 23:15:47 +00:00
|
|
|
@MainActor
|
|
|
|
|
private func flushPendingURLs(into viewModel: EditorViewModel) {
|
|
|
|
|
guard !pendingOpenURLs.isEmpty else { return }
|
|
|
|
|
let urls = pendingOpenURLs
|
|
|
|
|
pendingOpenURLs.removeAll()
|
|
|
|
|
urls.forEach { viewModel.openFile(url: $0) }
|
|
|
|
|
}
|
2026-01-25 12:46:33 +00:00
|
|
|
}
|
2026-02-07 18:46:08 +00:00
|
|
|
|
|
|
|
|
private struct DetachedWindowContentView: View {
|
2026-02-25 13:07:05 +00:00
|
|
|
@State private var viewModel = EditorViewModel()
|
2026-02-11 10:20:17 +00:00
|
|
|
@ObservedObject var supportPurchaseManager: SupportPurchaseManager
|
2026-02-14 13:24:01 +00:00
|
|
|
@ObservedObject var appUpdateManager: AppUpdateManager
|
2026-02-07 18:46:08 +00:00
|
|
|
@Binding var showGrokError: Bool
|
|
|
|
|
@Binding var grokErrorMessage: String
|
|
|
|
|
|
|
|
|
|
var body: some View {
|
2026-02-28 18:26:00 +00:00
|
|
|
ContentView(startupBehavior: .forceBlankDocument)
|
2026-02-25 13:07:05 +00:00
|
|
|
.environment(viewModel)
|
2026-02-11 10:20:17 +00:00
|
|
|
.environmentObject(supportPurchaseManager)
|
2026-02-14 13:24:01 +00:00
|
|
|
.environmentObject(appUpdateManager)
|
2026-02-07 18:46:08 +00:00
|
|
|
.environment(\.showGrokError, $showGrokError)
|
|
|
|
|
.environment(\.grokErrorMessage, $grokErrorMessage)
|
|
|
|
|
.frame(minWidth: 600, minHeight: 400)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-07 10:51:52 +00:00
|
|
|
#endif
|
2025-09-25 09:00:22 +00:00
|
|
|
|
|
|
|
|
@main
|
|
|
|
|
struct NeonVisionEditorApp: App {
|
2026-02-25 13:07:05 +00:00
|
|
|
@State private var viewModel = EditorViewModel()
|
2026-02-11 10:20:17 +00:00
|
|
|
@StateObject private var supportPurchaseManager = SupportPurchaseManager()
|
2026-02-14 13:24:01 +00:00
|
|
|
@StateObject private var appUpdateManager = AppUpdateManager()
|
2026-02-12 22:20:39 +00:00
|
|
|
@AppStorage("SettingsAppearance") private var appearance: String = "system"
|
2026-03-17 17:40:32 +00:00
|
|
|
@Environment(\.scenePhase) private var scenePhase
|
|
|
|
|
private let mainStartupBehavior: ContentView.StartupBehavior
|
|
|
|
|
private let startupSafeModeMessage: String?
|
|
|
|
|
@State private var didMarkLaunchCompleted: Bool = false
|
2026-02-07 10:51:52 +00:00
|
|
|
#if os(macOS)
|
2026-02-06 18:59:53 +00:00
|
|
|
@Environment(\.openWindow) private var openWindow
|
2026-01-17 11:11:26 +00:00
|
|
|
@State private var useAppleIntelligence: Bool = true
|
2026-02-05 21:30:21 +00:00
|
|
|
@State private var appleAIStatus: String = "Apple Intelligence: Checking…"
|
|
|
|
|
@State private var appleAIRoundTripMS: Double? = nil
|
2026-01-25 12:46:33 +00:00
|
|
|
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
|
2026-02-07 10:51:52 +00:00
|
|
|
#endif
|
|
|
|
|
@State private var showGrokError: Bool = false
|
|
|
|
|
@State private var grokErrorMessage: String = ""
|
|
|
|
|
|
2026-02-12 22:20:39 +00:00
|
|
|
private var preferredAppearance: ColorScheme? {
|
2026-02-12 23:58:32 +00:00
|
|
|
ReleaseRuntimePolicy.preferredColorScheme(for: appearance)
|
2026-02-12 22:20:39 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-17 17:40:32 +00:00
|
|
|
private func completeLaunchReliabilityTrackingIfNeeded() {
|
|
|
|
|
guard !didMarkLaunchCompleted else { return }
|
|
|
|
|
didMarkLaunchCompleted = true
|
|
|
|
|
RuntimeReliabilityMonitor.shared.markLaunchCompleted()
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-12 22:20:39 +00:00
|
|
|
#if os(macOS)
|
|
|
|
|
private var appKitAppearance: NSAppearance? {
|
|
|
|
|
switch appearance {
|
|
|
|
|
case "light":
|
|
|
|
|
return NSAppearance(named: .aqua)
|
|
|
|
|
case "dark":
|
|
|
|
|
return NSAppearance(named: .darkAqua)
|
|
|
|
|
default:
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func applyGlobalAppearanceOverride() {
|
|
|
|
|
let override = appKitAppearance
|
|
|
|
|
NSApp.appearance = override
|
|
|
|
|
for window in NSApp.windows {
|
|
|
|
|
window.appearance = override
|
|
|
|
|
window.invalidateShadow()
|
|
|
|
|
window.displayIfNeeded()
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-12 22:49:15 +00:00
|
|
|
|
2026-02-20 15:43:14 +00:00
|
|
|
private func applyMacWindowTabbingPolicy() {
|
|
|
|
|
// Use app-native file tab pills only; disable NSWindow tab bar to avoid duplicate tab systems.
|
|
|
|
|
NSWindow.allowsAutomaticWindowTabbing = false
|
|
|
|
|
for window in NSApp.windows {
|
|
|
|
|
window.tabbingMode = .disallowed
|
2026-02-12 22:49:15 +00:00
|
|
|
}
|
2026-02-20 17:17:52 +00:00
|
|
|
hideNativeTabBarMenuItems()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func hideNativeTabBarMenuItems() {
|
|
|
|
|
guard let mainMenu = NSApp.mainMenu else { return }
|
|
|
|
|
let targets = ["Show Tab Bar", "Hide Tab Bar", "Move Tab to New Window", "Merge All Windows"]
|
|
|
|
|
|
|
|
|
|
func filter(menu: NSMenu) {
|
|
|
|
|
for item in menu.items {
|
|
|
|
|
if let submenu = item.submenu {
|
|
|
|
|
filter(menu: submenu)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
menu.items.removeAll { item in
|
|
|
|
|
targets.contains(item.title)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
filter(menu: mainMenu)
|
2026-02-12 22:49:15 +00:00
|
|
|
}
|
2026-02-12 22:20:39 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#if os(iOS)
|
|
|
|
|
private var userInterfaceStyle: UIUserInterfaceStyle {
|
|
|
|
|
switch appearance {
|
|
|
|
|
case "light":
|
|
|
|
|
return .light
|
|
|
|
|
case "dark":
|
|
|
|
|
return .dark
|
|
|
|
|
default:
|
|
|
|
|
return .unspecified
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func applyIOSAppearanceOverride() {
|
|
|
|
|
let style = userInterfaceStyle
|
|
|
|
|
UIApplication.shared.connectedScenes
|
|
|
|
|
.compactMap { $0 as? UIWindowScene }
|
|
|
|
|
.forEach { scene in
|
|
|
|
|
scene.windows.forEach { window in
|
|
|
|
|
if window.overrideUserInterfaceStyle != style {
|
|
|
|
|
window.overrideUserInterfaceStyle = style
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2026-02-07 22:56:52 +00:00
|
|
|
init() {
|
2026-02-11 10:20:17 +00:00
|
|
|
let defaults = UserDefaults.standard
|
2026-03-09 19:39:32 +00:00
|
|
|
let launchCountKey = "AppLaunchCountV1"
|
|
|
|
|
defaults.set(defaults.integer(forKey: launchCountKey) + 1, forKey: launchCountKey)
|
2026-02-11 10:20:17 +00:00
|
|
|
// Default editor behavior:
|
|
|
|
|
// - keep line numbers on
|
|
|
|
|
// - keep style/space visualization toggles off unless user enables them in Settings
|
|
|
|
|
defaults.register(defaults: [
|
|
|
|
|
"SettingsShowLineNumbers": true,
|
|
|
|
|
"SettingsHighlightCurrentLine": false,
|
2026-02-12 22:20:39 +00:00
|
|
|
"SettingsHighlightMatchingBrackets": false,
|
|
|
|
|
"SettingsShowScopeGuides": false,
|
|
|
|
|
"SettingsHighlightScopeBackground": false,
|
2026-02-11 10:20:17 +00:00
|
|
|
"SettingsLineWrapEnabled": false,
|
|
|
|
|
"SettingsShowInvisibleCharacters": false,
|
2026-02-12 22:20:39 +00:00
|
|
|
"SettingsUseSystemFont": false,
|
2026-02-11 10:20:17 +00:00
|
|
|
"SettingsIndentStyle": "spaces",
|
|
|
|
|
"SettingsIndentWidth": 4,
|
|
|
|
|
"SettingsAutoIndent": true,
|
|
|
|
|
"SettingsAutoCloseBrackets": false,
|
|
|
|
|
"SettingsTrimTrailingWhitespace": false,
|
|
|
|
|
"SettingsTrimWhitespaceForSyntaxDetection": false,
|
|
|
|
|
"SettingsCompletionEnabled": false,
|
|
|
|
|
"SettingsCompletionFromDocument": false,
|
2026-02-12 22:20:39 +00:00
|
|
|
"SettingsCompletionFromSyntax": false,
|
|
|
|
|
"SettingsReopenLastSession": true,
|
2026-03-16 10:20:48 +00:00
|
|
|
"SettingsOpenWithBlankDocument": false,
|
2026-02-12 22:20:39 +00:00
|
|
|
"SettingsDefaultNewFileLanguage": "plain",
|
|
|
|
|
"SettingsConfirmCloseDirtyTab": true,
|
2026-02-14 13:24:01 +00:00
|
|
|
"SettingsConfirmClearEditor": true,
|
|
|
|
|
"SettingsAutoCheckForUpdates": true,
|
|
|
|
|
"SettingsUpdateCheckInterval": AppUpdateCheckInterval.daily.rawValue,
|
|
|
|
|
"SettingsAutoDownloadUpdates": false
|
2026-02-11 10:20:17 +00:00
|
|
|
])
|
2026-02-25 13:14:38 +00:00
|
|
|
let vimResetMigrationKey = "SettingsMigrationVimModeResetV1"
|
|
|
|
|
if !defaults.bool(forKey: vimResetMigrationKey) {
|
|
|
|
|
// One-time safety reset: avoid stale NORMAL-mode state making editor appear non-editable.
|
|
|
|
|
defaults.set(false, forKey: "EditorVimModeEnabled")
|
|
|
|
|
defaults.set(true, forKey: vimResetMigrationKey)
|
|
|
|
|
}
|
2026-02-11 10:20:17 +00:00
|
|
|
let whitespaceMigrationKey = "SettingsMigrationWhitespaceGlyphResetV1"
|
|
|
|
|
if !defaults.bool(forKey: whitespaceMigrationKey) {
|
|
|
|
|
defaults.set(false, forKey: "SettingsShowInvisibleCharacters")
|
|
|
|
|
defaults.set(false, forKey: "NSShowAllInvisibles")
|
|
|
|
|
defaults.set(false, forKey: "NSShowControlCharacters")
|
|
|
|
|
defaults.set(true, forKey: whitespaceMigrationKey)
|
|
|
|
|
}
|
2026-03-03 09:47:45 +00:00
|
|
|
RuntimeReliabilityMonitor.shared.markLaunch()
|
2026-03-17 17:40:32 +00:00
|
|
|
let safeModeDecision = RuntimeReliabilityMonitor.shared.consumeSafeModeLaunchDecision()
|
|
|
|
|
self.mainStartupBehavior = safeModeDecision.isEnabled ? .safeMode : .standard
|
|
|
|
|
self.startupSafeModeMessage = safeModeDecision.message
|
2026-03-03 09:47:45 +00:00
|
|
|
RuntimeReliabilityMonitor.shared.startMainThreadWatchdog()
|
|
|
|
|
EditorPerformanceMonitor.shared.markLaunchConfigured()
|
2026-02-07 22:56:52 +00:00
|
|
|
}
|
|
|
|
|
|
2026-02-08 09:58:46 +00:00
|
|
|
#if os(macOS)
|
|
|
|
|
private var activeWindowNumber: Int? {
|
|
|
|
|
NSApp.keyWindow?.windowNumber ?? NSApp.mainWindow?.windowNumber
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private var activeEditorViewModel: EditorViewModel {
|
|
|
|
|
WindowViewModelRegistry.shared.activeViewModel() ?? viewModel
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func postWindowCommand(_ name: Notification.Name, object: Any? = nil) {
|
|
|
|
|
var userInfo: [AnyHashable: Any] = [:]
|
|
|
|
|
if let activeWindowNumber {
|
|
|
|
|
userInfo[EditorCommandUserInfo.windowNumber] = activeWindowNumber
|
|
|
|
|
}
|
|
|
|
|
NotificationCenter.default.post(
|
|
|
|
|
name: name,
|
|
|
|
|
object: object,
|
|
|
|
|
userInfo: userInfo.isEmpty ? nil : userInfo
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2025-09-25 09:00:22 +00:00
|
|
|
var body: some Scene {
|
2026-02-07 10:51:52 +00:00
|
|
|
#if os(macOS)
|
2025-09-25 09:00:22 +00:00
|
|
|
WindowGroup {
|
2026-03-17 17:40:32 +00:00
|
|
|
ContentView(
|
|
|
|
|
startupBehavior: mainStartupBehavior,
|
|
|
|
|
safeModeMessage: startupSafeModeMessage
|
|
|
|
|
)
|
2026-02-25 13:07:05 +00:00
|
|
|
.environment(viewModel)
|
2026-02-11 10:20:17 +00:00
|
|
|
.environmentObject(supportPurchaseManager)
|
2026-02-14 13:24:01 +00:00
|
|
|
.environmentObject(appUpdateManager)
|
2026-02-16 13:39:27 +00:00
|
|
|
.onAppear {
|
|
|
|
|
appDelegate.viewModel = viewModel
|
|
|
|
|
appDelegate.appUpdateManager = appUpdateManager
|
|
|
|
|
}
|
2026-02-12 22:20:39 +00:00
|
|
|
.onAppear { applyGlobalAppearanceOverride() }
|
2026-02-20 15:43:14 +00:00
|
|
|
.onAppear { applyMacWindowTabbingPolicy() }
|
2026-02-12 22:20:39 +00:00
|
|
|
.onChange(of: appearance) { _, _ in applyGlobalAppearanceOverride() }
|
2025-09-25 09:01:45 +00:00
|
|
|
.environment(\.showGrokError, $showGrokError)
|
|
|
|
|
.environment(\.grokErrorMessage, $grokErrorMessage)
|
2026-02-19 08:09:35 +00:00
|
|
|
.tint(.blue)
|
2026-02-12 22:20:39 +00:00
|
|
|
.preferredColorScheme(preferredAppearance)
|
2026-03-17 17:40:32 +00:00
|
|
|
.onChange(of: scenePhase) { _, newPhase in
|
|
|
|
|
guard newPhase == .active else { return }
|
|
|
|
|
completeLaunchReliabilityTrackingIfNeeded()
|
|
|
|
|
}
|
2025-09-25 09:01:45 +00:00
|
|
|
.frame(minWidth: 600, minHeight: 400)
|
|
|
|
|
.task {
|
2026-03-17 17:40:32 +00:00
|
|
|
completeLaunchReliabilityTrackingIfNeeded()
|
|
|
|
|
guard mainStartupBehavior != .safeMode else { return }
|
2026-02-14 13:24:01 +00:00
|
|
|
if ReleaseRuntimePolicy.isUpdaterEnabledForCurrentDistribution {
|
|
|
|
|
appUpdateManager.startAutomaticChecks()
|
|
|
|
|
}
|
2026-02-09 11:15:22 +00:00
|
|
|
#if USE_FOUNDATION_MODELS && canImport(FoundationModels)
|
2026-02-05 21:30:21 +00:00
|
|
|
do {
|
|
|
|
|
let start = Date()
|
|
|
|
|
_ = try await AppleFM.appleFMHealthCheck()
|
|
|
|
|
let end = Date()
|
|
|
|
|
appleAIStatus = "Apple Intelligence: Ready"
|
|
|
|
|
appleAIRoundTripMS = end.timeIntervalSince(start) * 1000.0
|
2026-02-25 13:07:05 +00:00
|
|
|
AIActivityLog.record(
|
|
|
|
|
"Startup AI health check succeeded (\(String(format: "%.1f", appleAIRoundTripMS ?? 0)) ms).",
|
|
|
|
|
source: "Startup"
|
|
|
|
|
)
|
2026-02-05 21:30:21 +00:00
|
|
|
} catch {
|
|
|
|
|
appleAIStatus = "Apple Intelligence: Error — \(error.localizedDescription)"
|
|
|
|
|
appleAIRoundTripMS = nil
|
2026-02-25 13:07:05 +00:00
|
|
|
AIActivityLog.record(
|
|
|
|
|
"Startup AI health check failed: \(error.localizedDescription)",
|
|
|
|
|
level: .error,
|
|
|
|
|
source: "Startup"
|
|
|
|
|
)
|
2026-02-05 21:30:21 +00:00
|
|
|
}
|
|
|
|
|
#else
|
|
|
|
|
appleAIStatus = "Apple Intelligence: Unavailable (build without USE_FOUNDATION_MODELS)"
|
2026-02-25 13:07:05 +00:00
|
|
|
AIActivityLog.record(
|
|
|
|
|
"Startup AI health check unavailable (built without USE_FOUNDATION_MODELS).",
|
|
|
|
|
level: .warning,
|
|
|
|
|
source: "Startup"
|
|
|
|
|
)
|
2026-02-05 21:30:21 +00:00
|
|
|
#endif
|
2025-09-25 09:00:22 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
.defaultSize(width: 1000, height: 600)
|
2026-02-08 23:43:57 +00:00
|
|
|
.handlesExternalEvents(matching: ["*"])
|
2026-02-06 18:59:53 +00:00
|
|
|
|
|
|
|
|
WindowGroup("New Window", id: "blank-window") {
|
2026-02-07 18:46:08 +00:00
|
|
|
DetachedWindowContentView(
|
2026-02-11 10:20:17 +00:00
|
|
|
supportPurchaseManager: supportPurchaseManager,
|
2026-02-14 13:24:01 +00:00
|
|
|
appUpdateManager: appUpdateManager,
|
2026-02-07 18:46:08 +00:00
|
|
|
showGrokError: $showGrokError,
|
|
|
|
|
grokErrorMessage: $grokErrorMessage
|
|
|
|
|
)
|
2026-02-12 22:20:39 +00:00
|
|
|
.onAppear { applyGlobalAppearanceOverride() }
|
2026-02-20 15:43:14 +00:00
|
|
|
.onAppear { applyMacWindowTabbingPolicy() }
|
2026-02-12 22:20:39 +00:00
|
|
|
.onChange(of: appearance) { _, _ in applyGlobalAppearanceOverride() }
|
2026-02-19 08:09:35 +00:00
|
|
|
.tint(.blue)
|
2026-02-12 22:20:39 +00:00
|
|
|
.preferredColorScheme(preferredAppearance)
|
2026-02-06 18:59:53 +00:00
|
|
|
}
|
|
|
|
|
.defaultSize(width: 1000, height: 600)
|
2026-02-08 23:43:57 +00:00
|
|
|
.handlesExternalEvents(matching: [])
|
2026-02-06 18:59:53 +00:00
|
|
|
|
2026-02-12 22:20:39 +00:00
|
|
|
Settings {
|
2026-03-26 18:19:45 +00:00
|
|
|
ConfiguredSettingsView(
|
2026-02-20 15:43:14 +00:00
|
|
|
supportsOpenInTabs: false,
|
2026-03-26 18:19:45 +00:00
|
|
|
supportsTranslucency: true,
|
|
|
|
|
supportPurchaseManager: supportPurchaseManager,
|
|
|
|
|
appUpdateManager: appUpdateManager
|
2026-02-20 15:43:14 +00:00
|
|
|
)
|
2026-02-12 22:20:39 +00:00
|
|
|
.onAppear { applyGlobalAppearanceOverride() }
|
2026-02-20 15:43:14 +00:00
|
|
|
.onAppear { applyMacWindowTabbingPolicy() }
|
2026-02-12 22:20:39 +00:00
|
|
|
.onChange(of: appearance) { _, _ in applyGlobalAppearanceOverride() }
|
2026-02-19 08:09:35 +00:00
|
|
|
.tint(.blue)
|
2026-02-12 22:20:39 +00:00
|
|
|
.preferredColorScheme(preferredAppearance)
|
2026-02-11 10:20:17 +00:00
|
|
|
}
|
|
|
|
|
|
2026-02-25 13:07:05 +00:00
|
|
|
Window("AI Activity Log", id: "ai-logs") {
|
|
|
|
|
AIActivityLogView()
|
|
|
|
|
.frame(minWidth: 720, minHeight: 420)
|
|
|
|
|
.preferredColorScheme(preferredAppearance)
|
|
|
|
|
.tint(.blue)
|
|
|
|
|
}
|
|
|
|
|
.defaultSize(width: 860, height: 520)
|
|
|
|
|
.handlesExternalEvents(matching: [])
|
|
|
|
|
|
2026-02-24 14:44:43 +00:00
|
|
|
MenuBarExtra("Welcome Tour", systemImage: "sparkles.rectangle.stack") {
|
|
|
|
|
Button {
|
|
|
|
|
postWindowCommand(.showWelcomeTourRequested)
|
|
|
|
|
} label: {
|
|
|
|
|
Label("Show Welcome Tour", systemImage: "sparkles.rectangle.stack")
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-09 19:39:32 +00:00
|
|
|
Button {
|
|
|
|
|
postWindowCommand(.showEditorHelpRequested)
|
|
|
|
|
} label: {
|
|
|
|
|
Label("Editor Help…", systemImage: "questionmark.circle")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Button {
|
|
|
|
|
postWindowCommand(.showSupportPromptRequested)
|
|
|
|
|
} label: {
|
|
|
|
|
Label("Support Neon Vision Editor…", systemImage: "heart.circle.fill")
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-24 14:44:43 +00:00
|
|
|
Divider()
|
|
|
|
|
|
2026-02-27 17:13:12 +00:00
|
|
|
SettingsLink {
|
2026-02-24 14:44:43 +00:00
|
|
|
Label("Settings…", systemImage: "gearshape")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ReleaseRuntimePolicy.isUpdaterEnabledForCurrentDistribution {
|
|
|
|
|
Button {
|
|
|
|
|
postWindowCommand(.showUpdaterRequested, object: true)
|
|
|
|
|
} label: {
|
|
|
|
|
Label("Check for Updates…", systemImage: "arrow.triangle.2.circlepath.circle")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-25 09:00:22 +00:00
|
|
|
.commands {
|
2026-02-25 13:07:05 +00:00
|
|
|
NeonVisionMacAppCommands(
|
|
|
|
|
activeEditorViewModel: { activeEditorViewModel },
|
2026-03-08 12:20:14 +00:00
|
|
|
hasActiveEditorWindow: { WindowViewModelRegistry.shared.activeViewModel() != nil },
|
2026-02-25 13:07:05 +00:00
|
|
|
openNewWindow: { openWindow(id: "blank-window") },
|
|
|
|
|
openAIDiagnosticsWindow: { openWindow(id: "ai-logs") },
|
|
|
|
|
postWindowCommand: { name, object in
|
|
|
|
|
postWindowCommand(name, object: object)
|
|
|
|
|
},
|
|
|
|
|
isUpdaterEnabled: ReleaseRuntimePolicy.isUpdaterEnabledForCurrentDistribution,
|
2026-03-15 14:56:58 +00:00
|
|
|
recentFilesProvider: { RecentFilesStore.items(limit: 10) },
|
|
|
|
|
clearRecentFiles: { RecentFilesStore.clearUnpinned() },
|
2026-02-25 13:07:05 +00:00
|
|
|
useAppleIntelligence: $useAppleIntelligence,
|
|
|
|
|
appleAIStatus: $appleAIStatus,
|
|
|
|
|
appleAIRoundTripMS: $appleAIRoundTripMS,
|
|
|
|
|
showGrokError: $showGrokError,
|
|
|
|
|
grokErrorMessage: $grokErrorMessage
|
|
|
|
|
)
|
2025-09-25 09:00:22 +00:00
|
|
|
}
|
2026-02-07 10:51:52 +00:00
|
|
|
#else
|
|
|
|
|
WindowGroup {
|
2026-03-17 17:40:32 +00:00
|
|
|
ContentView(
|
|
|
|
|
startupBehavior: mainStartupBehavior,
|
|
|
|
|
safeModeMessage: startupSafeModeMessage
|
|
|
|
|
)
|
2026-02-25 13:07:05 +00:00
|
|
|
.environment(viewModel)
|
2026-02-11 10:20:17 +00:00
|
|
|
.environmentObject(supportPurchaseManager)
|
2026-02-14 13:24:01 +00:00
|
|
|
.environmentObject(appUpdateManager)
|
2026-02-07 10:51:52 +00:00
|
|
|
.environment(\.showGrokError, $showGrokError)
|
|
|
|
|
.environment(\.grokErrorMessage, $grokErrorMessage)
|
2026-02-14 20:57:32 +00:00
|
|
|
.tint(.blue)
|
2026-02-12 22:20:39 +00:00
|
|
|
.onAppear { applyIOSAppearanceOverride() }
|
2026-03-17 17:40:32 +00:00
|
|
|
.onChange(of: scenePhase) { _, newPhase in
|
|
|
|
|
guard newPhase == .active else { return }
|
|
|
|
|
completeLaunchReliabilityTrackingIfNeeded()
|
|
|
|
|
}
|
2026-03-03 09:47:45 +00:00
|
|
|
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willTerminateNotification)) { _ in
|
|
|
|
|
RuntimeReliabilityMonitor.shared.markGracefulTermination()
|
|
|
|
|
}
|
2026-02-12 22:20:39 +00:00
|
|
|
.onChange(of: appearance) { _, _ in applyIOSAppearanceOverride() }
|
|
|
|
|
.preferredColorScheme(preferredAppearance)
|
2026-03-17 17:40:32 +00:00
|
|
|
.task {
|
|
|
|
|
completeLaunchReliabilityTrackingIfNeeded()
|
|
|
|
|
}
|
2026-02-07 10:51:52 +00:00
|
|
|
}
|
2026-02-22 12:38:31 +00:00
|
|
|
.commands {
|
|
|
|
|
CommandGroup(replacing: .undoRedo) {
|
|
|
|
|
Button("Undo") {
|
|
|
|
|
UIApplication.shared.sendAction(Selector(("undo:")), to: nil, from: nil, for: nil)
|
|
|
|
|
}
|
|
|
|
|
.keyboardShortcut("z", modifiers: .command)
|
|
|
|
|
|
|
|
|
|
Button("Redo") {
|
|
|
|
|
UIApplication.shared.sendAction(Selector(("redo:")), to: nil, from: nil, for: nil)
|
|
|
|
|
}
|
|
|
|
|
.keyboardShortcut("z", modifiers: [.command, .shift])
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-07 10:51:52 +00:00
|
|
|
#endif
|
2025-09-25 09:00:22 +00:00
|
|
|
}
|
2026-02-11 10:20:17 +00:00
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-25 09:01:45 +00:00
|
|
|
struct ShowGrokErrorKey: EnvironmentKey {
|
|
|
|
|
static let defaultValue: Binding<Bool> = .constant(false)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct GrokErrorMessageKey: EnvironmentKey {
|
|
|
|
|
static let defaultValue: Binding<String> = .constant("")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extension EnvironmentValues {
|
|
|
|
|
var showGrokError: Binding<Bool> {
|
|
|
|
|
get { self[ShowGrokErrorKey.self] }
|
|
|
|
|
set { self[ShowGrokErrorKey.self] = newValue }
|
|
|
|
|
}
|
2026-02-07 10:51:52 +00:00
|
|
|
|
2025-09-25 09:01:45 +00:00
|
|
|
var grokErrorMessage: Binding<String> {
|
|
|
|
|
get { self[GrokErrorMessageKey.self] }
|
|
|
|
|
set { self[GrokErrorMessageKey.self] = newValue }
|
|
|
|
|
}
|
|
|
|
|
}
|