From 11fe5abaa919246e3adfe113d56bab54ff52b992 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 12 Aug 2026 12:35:20 -0700 Subject: [PATCH] fix(ui): refresh picker topology after node changes (#122774) --- .../new-session-page.places-live.e2e.test.ts | 23 ++++++++++++++++--- ui/src/pages/new-session/new-session-page.ts | 15 +++++++----- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/ui/src/e2e/new-session-page.places-live.e2e.test.ts b/ui/src/e2e/new-session-page.places-live.e2e.test.ts index 9c3c1628a35d..c3dba4e539f9 100644 --- a/ui/src/e2e/new-session-page.places-live.e2e.test.ts +++ b/ui/src/e2e/new-session-page.places-live.e2e.test.ts @@ -66,7 +66,13 @@ suite.define(() => { }, ], }, - "environments.list": { environments: [], profiles: [] }, + "environments.list": { + environments: [ + { id: "gateway", type: "local", status: "available" }, + { id: "node:existing-mac", type: "node", status: "available" }, + ], + profiles: [], + }, }, }); @@ -96,6 +102,14 @@ suite.define(() => { }, ], }); + await gateway.setMethodResponse("environments.list", { + environments: [ + { id: "gateway", type: "local", status: "available" }, + { id: "node:existing-mac", type: "node", status: "available" }, + { id: "node:new-mac", type: "node", status: "available" }, + ], + profiles: [], + }); await gateway.emitGatewayEvent("presence", { presence: [ { deviceId: "existing-mac", mode: "node", reason: "connect", ts: 1 }, @@ -106,12 +120,15 @@ suite.define(() => { await expect .poll(async () => (await gateway.getRequests("node.list")).length) .toBeGreaterThan(nodeRequests); + await expect + .poll(async () => (await gateway.getRequests("environments.list")).length) + .toBeGreaterThan(environmentRequests); await place.getByRole("button", { name: "New Mac" }).waitFor(); await place.getByText("This gateway", { exact: true }).waitFor(); await place.getByText("Your devices", { exact: true }).waitFor(); expect(await place.getAttribute("open")).not.toBeNull(); - expect(await gateway.getRequests("environments.list")).toHaveLength(environmentRequests); + const refreshedEnvironmentRequests = (await gateway.getRequests("environments.list")).length; await gateway.setMethodResponse("environments.list", { environments: [], profiles: [{ id: "aws", providerId: "crabbox", trust: "disposable" }], @@ -123,7 +140,7 @@ suite.define(() => { }); await expect .poll(async () => (await gateway.getRequests("environments.list")).length) - .toBeGreaterThan(environmentRequests); + .toBeGreaterThan(refreshedEnvironmentRequests); await place.getByText("Cloud", { exact: true }).waitFor(); await place.getByRole("button", { name: "Cloud ยท aws" }).waitFor(); expect(await place.getAttribute("open")).not.toBeNull(); diff --git a/ui/src/pages/new-session/new-session-page.ts b/ui/src/pages/new-session/new-session-page.ts index 849282d0e84f..ee0b02fc5a2b 100644 --- a/ui/src/pages/new-session/new-session-page.ts +++ b/ui/src/pages/new-session/new-session-page.ts @@ -184,17 +184,14 @@ class NewSessionPage extends OpenClawLightDomElement { if (this.context?.gateway !== gateway) { return; } - if (event.event === "config.changed") { - void this.gateway.refreshCloudProfiles(); - return; - } if ( + event.event === "config.changed" || event.event === "node.pair.requested" || event.event === "node.pair.resolved" || event.event === "device.pair.requested" || event.event === "device.pair.resolved" ) { - void this.place.refreshNodes(); + this.refreshPlaceTopology(); return; } const presence = event.event === "presence" ? readPresence(event.payload) : null; @@ -204,7 +201,7 @@ class NewSessionPage extends OpenClawLightDomElement { const signature = presenceConnectivitySignature(presence); if (signature !== this.presenceSignature) { this.presenceSignature = signature; - void this.place.refreshNodes(); + this.refreshPlaceTopology(); } }); }, @@ -223,6 +220,12 @@ class NewSessionPage extends OpenClawLightDomElement { ); } + // Device visibility intersects both catalogs, so topology changes must refresh them together. + private refreshPlaceTopology() { + void this.place.refreshNodes(); + void this.gateway.refreshCloudProfiles(); + } + handleEvent(event: Event) { const pickers = this.querySelectorAll( ".chat-controls__inline-select[open]",