"No page loaded, no delegate callback fired, no error was set. There was no web view at all."
Two Bridges, Two Directions
NSHostingView/NSHostingControllerput a SwiftUI view inside AppKit. The AppKit side owns the hosting view's lifetime, exactly like any otherNSView. This direction rarely surprises anyone.NSViewRepresentableputs an AppKit view inside SwiftUI. SwiftUI callsmakeNSViewwhen the representable first appears in the view tree,updateNSViewwhen its inputs change, and tears the view down when the representable leaves the tree. ACoordinatorholds delegate state that should outlive individual updates.
That second direction has a consequence that is easy to forget: the AppKit object's lifetime belongs to SwiftUI's view tree. If the representable is behind an if, the object does not exist whenever the condition is false.
The Preview Pane That Was Never Built
A Mac agent client had a dock with a web preview pane. A headless smoke test drove it through the pane's model — bind a workspace, choose the tool, call openWorkspace(relativePath:) — and waited for the page to load. It waited forever. The pane sat behind a view-level gate that showed a placeholder when no session was bound, and the only place the WKWebView was created was inside that pane's makeNSView, which also handed the view to the model. With the pane unrendered, the model's weak var webView stayed nil, and webView?.load(…) succeeded at doing nothing.
Two fixes, both needed. The smoke test satisfies the real gate the way a person would — it opens a session — instead of poking the model behind the view's back. And it waits on an explicit isAttached before navigating, with a deadline that names the failure. A useful discriminating probe came out of it too: point the same smoke at an unreachable URL. If a web view exists, a failed-navigation callback arrives within a second; silence proves there is no web view.
The Gate Must Be the Model's Truth
A related bug hid in the same dock. The Files and Terminal panes showed "start a session first" whenever the model's local root was nil — but a remote session leaves that field nil by design, so the remote dock never rendered on the Mac, while every model-level test passed. The fix gave the model one readiness predicate (local root or remote session) and made the view call a static function of the model to decide whether to show the placeholder. The decision now lives in a function the tests can call and the view body cannot bypass.