From bebbc877db3b2b35a4682ead72e5ed795b7a7cb8 Mon Sep 17 00:00:00 2001 From: rzen Date: Wed, 29 Jul 2026 21:32:20 -0400 Subject: [PATCH] =?UTF-8?q?The=20held=20window=20chrome=20survives=20detac?= =?UTF-8?q?h=20=E2=80=94=20toolbars=20and=20the=20board-info=20widget=20re?= =?UTF-8?q?nder=20again?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No board window has shown its NSToolbar or the titlebar board-info chevron since m6: on macOS 26, SwiftUI stages a scene's content against a provisional window, then dismantles and re-makes the window-sensing background view while moving content into the real window. HostedWindowController.detach() treated that dismantle as the window's death and discarded the held titlebar accessory and toolbar controller — so the real window attached moments later to empty slots. Observed live via instrumented launch: install arrives with no window, detach follows (slots cleared), then the real window attaches. detach() now removes the chrome from the window but retains the slots — their lifetime is the controller's, not the sensing view's — and attach() reinstalls whatever is held. Verified on a fixture launch: the board window carries its toolbar and the leading accessory. Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY --- Kanban/App/WindowAccessor.swift | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/Kanban/App/WindowAccessor.swift b/Kanban/App/WindowAccessor.swift index d759fd7..10c0a36 100644 --- a/Kanban/App/WindowAccessor.swift +++ b/Kanban/App/WindowAccessor.swift @@ -113,15 +113,20 @@ final class HostedWindowController: NSObject, NSWindowDelegate { applyToolbarIfPossible() } - /// Puts the previous delegate back, and takes the titlebar accessory and toolbar back out. - /// Called when the hosting view goes away; the delegate half is a no-op if something else has - /// since taken the delegate, because stomping a third party's would be the bug this whole file - /// exists to avoid. + /// Puts the previous delegate back and takes the titlebar accessory and toolbar off the window — + /// **without discarding them**. The delegate half is a no-op if something else has since taken + /// the delegate, because stomping a third party's would be the bug this whole file exists to + /// avoid. + /// + /// The held chrome survives a detach deliberately: its lifetime is this controller's, not the + /// sensing view's. SwiftUI dismantles and re-makes the background representable while it moves a + /// scene's content into its final window (observed on macOS 26: install arrives before any + /// window, a dismantle follows, and only *then* does the real window attach) — so chrome + /// discarded here would never reach the window it was made for. `attach` reinstalls whatever is + /// held; a controller that is genuinely done takes its slots down with it. func detach() { removeTitlebarAccessory() - titlebarAccessory = nil removeToolbar() - toolbarController = nil guard let window, window.delegate === self else { return } window.delegate = previousDelegate self.window = nil