From d0293649cd343784118606acb15bded7cd5b9ee8 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 26 Dec 2025 22:48:58 +0100 Subject: [PATCH] fix(macos): refresh menu sessions without resizing --- CHANGELOG.md | 1 + .../Clawdis/MenuSessionsInjector.swift | 29 ++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c6d1128b..c38630e8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,6 +70,7 @@ - Remote SSH tunnels now get health checks; Debug → Ports highlights unhealthy tunnels and offers Reset SSH tunnel. - Menu bar session/node sections no longer reflow while open, keeping hover highlights aligned. - Menu hover highlights now span the full width (including submenu arrows). +- Menu session rows now refresh while open without width changes (no more stuck “Loading sessions…”). ### Nodes & Canvas - Debug status overlay gated and toggleable on macOS/iOS/Android nodes. diff --git a/apps/macos/Sources/Clawdis/MenuSessionsInjector.swift b/apps/macos/Sources/Clawdis/MenuSessionsInjector.swift index 743f5f8ce..9d440c8dd 100644 --- a/apps/macos/Sources/Clawdis/MenuSessionsInjector.swift +++ b/apps/macos/Sources/Clawdis/MenuSessionsInjector.swift @@ -16,6 +16,7 @@ final class MenuSessionsInjector: NSObject, NSMenuDelegate { private var nodesLoadTask: Task? private var isMenuOpen = false private var lastKnownMenuWidth: CGFloat? + private var menuOpenWidth: CGFloat? private var cachedSnapshot: SessionStoreSnapshot? private var cachedErrorText: String? @@ -48,27 +49,38 @@ final class MenuSessionsInjector: NSObject, NSMenuDelegate { func menuWillOpen(_ menu: NSMenu) { self.originalDelegate?.menuWillOpen?(menu) self.isMenuOpen = true + self.menuOpenWidth = self.currentMenuWidth(for: menu) self.inject(into: menu) self.injectNodes(into: menu) - // Refresh in background for the next open (but do not re-inject while open). + // Refresh in background for the next open; keep width stable while open. self.loadTask?.cancel() self.loadTask = Task { [weak self] in guard let self else { return } await self.refreshCache(force: false) + await MainActor.run { + guard self.isMenuOpen else { return } + self.inject(into: menu) + self.injectNodes(into: menu) + } } self.nodesLoadTask?.cancel() self.nodesLoadTask = Task { [weak self] in guard let self else { return } await self.nodesStore.refresh() + await MainActor.run { + guard self.isMenuOpen else { return } + self.injectNodes(into: menu) + } } } func menuDidClose(_ menu: NSMenu) { self.originalDelegate?.menuDidClose?(menu) self.isMenuOpen = false + self.menuOpenWidth = nil self.loadTask?.cancel() self.nodesLoadTask?.cancel() } @@ -736,6 +748,9 @@ final class MenuSessionsInjector: NSObject, NSMenuDelegate { } private func initialWidth(for menu: NSMenu) -> CGFloat { + if let openWidth = self.menuOpenWidth { + return max(300, openWidth) + } let candidates: [CGFloat] = [ menu.size.width, menu.minimumWidth, @@ -785,9 +800,21 @@ final class MenuSessionsInjector: NSObject, NSMenuDelegate { } private func captureMenuWidthIfAvailable(from view: NSView) { + guard !self.isMenuOpen else { return } guard let width = view.window?.contentView?.bounds.width, width > 0 else { return } self.lastKnownMenuWidth = max(300, width) } + + private func currentMenuWidth(for menu: NSMenu) -> CGFloat { + let candidates: [CGFloat] = [ + menu.size.width, + menu.minimumWidth, + self.lastKnownMenuWidth ?? 0, + self.fallbackWidth, + ] + let resolved = candidates.max() ?? self.fallbackWidth + return max(300, resolved) + } } #if DEBUG