From 61aa3a53edc66ff797d16e82509fcc5595bb745c Mon Sep 17 00:00:00 2001 From: Zoltan Papp Date: Tue, 26 May 2026 23:13:34 +0200 Subject: [PATCH] tray: re-enable Exit Node menu item when candidates arrive post-connect MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The parent Exit Node item's enablement was only refreshed on icon/status transitions. The daemon ships peer routes in a later snapshot than the Connected status text, so after a profile switch the candidate list flips empty to non-empty while the status string is unchanged — leaving the item greyed and the freshly painted rows unreachable. Re-evaluate enablement in the exitNodesChanged branch too. --- client/ui/tray.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/client/ui/tray.go b/client/ui/tray.go index 242d22457..950967d55 100644 --- a/client/ui/tray.go +++ b/client/ui/tray.go @@ -727,6 +727,18 @@ func (t *Tray) applyStatus(st services.Status) { go t.loadProfiles() } if exitNodesChanged { + // Re-evaluate the parent item's enablement here too, not only in + // the iconChanged block above: the daemon ships peer routes in a + // later snapshot than the Connected status text (networks=[] first, + // then populated), so the candidate list can flip empty→non-empty + // while the status string is unchanged. Without this the parent + // "Exit Node" item stays greyed from when it was last set on the + // status transition and the freshly painted rows are unreachable. + // Set before rebuildExitNodes' SetMenu so the rebuild reads the + // updated enabled state at NSMenuItem construction time. + if t.exitNodeItem != nil { + t.exitNodeItem.SetEnabled(connected && len(exitNodes) > 0) + } t.rebuildExitNodes(exitNodes) } if daemonVersionChanged && t.daemonVersionItem != nil {