From 67ac63c5564463f772457dce392a12e7f058d444 Mon Sep 17 00:00:00 2001 From: teamleaderleo Date: Sat, 26 Sep 2026 10:06:33 -0400 Subject: [PATCH] fix: keep the checklist popover when its detach close finishes after reattach On the owned Mac minis (macOS 26.5.1, real display) the checklist popover that a sidebar row reparent closes is still animating closed when the row is back in its window. viewDidMoveToWindow saw isShown == true, decided the popover had survived, and cleared the detach flag; the close then completed as an external dismissal and wrote presented=false. The presenter now reports a close in progress (popoverWillClose until popoverDidClose), and the section clears the detach flag only after the reattach settles with the popover still shown and not closing. A popover that truly survives the reparent still treats a later click-away as a real dismissal. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../SidebarRowSwiftUIPopoverPresenter.swift | 10 ++++++++++ .../SidebarWorkspaceRowChecklistSection.swift | 19 +++++++++++++++---- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/Sources/Sidebar/AppKitList/Cells/SidebarRowSwiftUIPopoverPresenter.swift b/Sources/Sidebar/AppKitList/Cells/SidebarRowSwiftUIPopoverPresenter.swift index 43008e112bb6..4994c8d6ddac 100644 --- a/Sources/Sidebar/AppKitList/Cells/SidebarRowSwiftUIPopoverPresenter.swift +++ b/Sources/Sidebar/AppKitList/Cells/SidebarRowSwiftUIPopoverPresenter.swift @@ -41,6 +41,11 @@ final class SidebarRowSwiftUIPopoverPresenter: NSObject, NSPopoverDelegate { var isShown: Bool { popover?.isShown == true } + /// True from `popoverWillClose` until `popoverDidClose`. An animated close + /// keeps `isShown` true until the animation ends, so containers that + /// must know whether a close already began check this as well. + private(set) var isClosing = false + func present( _ root: AnyView, relativeTo rect: NSRect, @@ -112,7 +117,12 @@ final class SidebarRowSwiftUIPopoverPresenter: NSObject, NSPopoverDelegate { PopoverKeyWindowElevator.promoteToKeyIfPossible(hostingController.view.window) } + func popoverWillClose(_ notification: Notification) { + isClosing = true + } + func popoverDidClose(_ notification: Notification) { + isClosing = false visibleUpdateScheduler.cancel() pendingRoot = nil popover = nil diff --git a/Sources/Sidebar/AppKitList/Cells/SidebarWorkspaceRowChecklistSection.swift b/Sources/Sidebar/AppKitList/Cells/SidebarWorkspaceRowChecklistSection.swift index 9e13a233b1fe..6fdeee968bff 100644 --- a/Sources/Sidebar/AppKitList/Cells/SidebarWorkspaceRowChecklistSection.swift +++ b/Sources/Sidebar/AppKitList/Cells/SidebarWorkspaceRowChecklistSection.swift @@ -493,10 +493,21 @@ final class SidebarRowChecklistSection: NSView { override func viewDidMoveToWindow() { super.viewDidMoveToWindow() - if window != nil, popoverPresenter.isShown { - // The popover survived the reparent, so a later close is a real - // click-away rather than a detach side effect. - popoverAnchorDetachedWhilePresented = false + if window != nil, popoverAnchorDetachedWhilePresented, popoverPresenter.isShown { + // The popover may have survived the reparent, in which case a + // later close is a real click-away rather than a detach side + // effect. Decide once the reattach settles: an animated popover + // stays `isShown` while its detach-induced close animates, and + // that close can finish after the anchor is back in the window. + let generation = popoverPresentationGeneration + DispatchQueue.main.async { [weak self] in + guard let self, + self.popoverPresentationGeneration == generation, + self.window != nil, + self.popoverPresenter.isShown, + !self.popoverPresenter.isClosing else { return } + self.popoverAnchorDetachedWhilePresented = false + } } if window != nil, pendingPopoverPresentation { needsLayout = true