Skip to content

perf(window): defer inspector NSHostingController content until first expansion#1031

Merged
datlechin merged 2 commits intomainfrom
perf/lazy-inspector-hosting
May 6, 2026
Merged

perf(window): defer inspector NSHostingController content until first expansion#1031
datlechin merged 2 commits intomainfrom
perf/lazy-inspector-hosting

Conversation

@datlechin
Copy link
Copy Markdown
Member

Summary

Step 1A of the pure-SwiftUI scene migration (Phase C-2 in the perf refactor plan).

MainSplitViewController.viewDidLoad instantiates 3 NSHostingControllers (sidebar, detail, inspector) — one SwiftUI rendering root each. Each root negotiates intrinsic sizes via NSHostingView.SizeConstraints.update(from:)ViewGraph.sizeThatFits(_:) during the window's first display cycle, contributing to the 1.19s connect-window-open spike captured in the Time Profiler trace.

The inspector starts collapsed by default (UserDefaults.bool(forKey: inspectorPresentedKey) = false), so its SwiftUI body (UnifiedRightPanelView with column inspector, query result summary, etc.) is built but never shown — pure waste at launch.

Fix

Apple-pattern lazy view-controller content. The inspector's NSHostingController is created with a placeholder Color.clear root view when collapsed; the heavy UnifiedRightPanelView body is materialized via inspectorHosting.rootView = AnyView(buildInspectorView()) only on first showInspector() call. If the user had inspector expanded last session (inspectorPresentedKey == true), content materializes immediately at launch — same as before.

NSHostingController.rootView is documented as get/set on Apple's docs, so this is the supported swap pattern. SwiftUI re-evaluates the view graph from scratch when the rootView is replaced.

private var hasMaterializedInspector = false

// viewDidLoad:
let initialInspectorContent: AnyView
if inspectorPresented {
    initialInspectorContent = AnyView(buildInspectorView())
    hasMaterializedInspector = true
} else {
    initialInspectorContent = AnyView(Color.clear)
}
inspectorHosting = NSHostingController(rootView: initialInspectorContent)


private func materializeInspectorIfNeeded() {
    guard !hasMaterializedInspector, let inspectorHosting else { return }
    hasMaterializedInspector = true
    inspectorHosting.rootView = AnyView(buildInspectorView())
}

func showInspector() {
    materializeInspectorIfNeeded()
    inspectorSplitItem?.animator().isCollapsed = false
    UserDefaults.standard.set(true, forKey: Self.inspectorPresentedKey)
}

This also establishes the lazy-rootView swap pattern that step 1B (toolbar move to SwiftUI) and step 1C (NavigationSplitView replacement) will reuse.

Test plan

  • Cold launch with inspectorPresentedKey = false (default): main window opens, inspector pane shows nothing collapsed (same UX), Cmd+Option+I expands → content appears
  • Cold launch with inspectorPresentedKey = true (had inspector open last session): inspector content visible immediately at launch (same as before)
  • Toggle inspector via toolbar / menu / shortcut: works, content persists
  • Multi-window: each window's inspector materializes independently on first expansion
  • swiftlint --strict on changed file: 0 violations
  • xcodebuild Debug arm64: BUILD SUCCEEDED

Why this is step 1A, not the full refactor

The full migration to pure SwiftUI scenes (NavigationSplitView + .toolbar + .inspector) replacing MainSplitViewController is a multi-week project. Per the planning discussion in this thread, the safer path is incremental: (1A) lazy inspector ← this PR, (1B) toolbar to SwiftUI .toolbar { ToolbarContent }, (1C) NavigationSplitView shell. Each step compiles, ships, and is independently reversible.

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@datlechin datlechin merged commit 62433ff into main May 6, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant