From 31722120b7b980eb00388f82903b19a7c8cd04eb Mon Sep 17 00:00:00 2001 From: Xubin Ren <52506698+Re-bin@users.noreply.github.com> Date: Sun, 31 May 2026 17:00:42 +0800 Subject: [PATCH] feat(webui): polish native host experience --- webui/src/App.tsx | 63 +++++++++++------ webui/src/components/Sidebar.tsx | 3 +- .../src/components/settings/SettingsView.tsx | 9 ++- webui/src/components/thread/ThreadHeader.tsx | 32 +++++---- webui/src/components/thread/ThreadShell.tsx | 3 + .../src/components/thread/ThreadViewport.tsx | 2 +- .../thread/activity/FileEditRow.tsx | 28 +++++--- webui/src/globals.css | 69 +++++++++++++++++++ webui/src/lib/nanobot-client.ts | 19 +++-- webui/src/lib/runtime.ts | 21 +++--- .../src/tests/agent-activity-cluster.test.tsx | 33 +++++++++ webui/src/tests/app-layout.test.tsx | 4 +- webui/src/tests/nanobot-client.test.ts | 56 +++++++++++++++ webui/src/tests/settings-view.test.tsx | 34 +++++++++ 14 files changed, 316 insertions(+), 60 deletions(-) diff --git a/webui/src/App.tsx b/webui/src/App.tsx index 1dec4a6f9..6119a79b1 100644 --- a/webui/src/App.tsx +++ b/webui/src/App.tsx @@ -218,7 +218,7 @@ function HostChrome({ )} ) : ( -
+ )} ); @@ -252,7 +252,19 @@ export default function App() { refreshed.token, refreshed.ws_url, ); + const refreshedSurface = refreshed.runtime_surface + ? toRuntimeSurface(refreshed.runtime_surface) + : runtimeSurface; + const refreshedHost = createRuntimeHost( + refreshedSurface, + refreshed.runtime_capabilities, + ); const tokenExpiresAt = bootstrapTokenExpiresAt(refreshed.expires_in); + if (refreshedHost.socketFactory) { + client.updateUrl(refreshedUrl, refreshedHost.socketFactory); + } else { + client.updateUrl(refreshedUrl); + } setState((current) => current.status === "ready" && current.client === client ? { @@ -260,10 +272,7 @@ export default function App() { token: refreshed.token, tokenExpiresAt, modelName: refreshed.model_name ?? current.modelName, - runtimeSurface: - refreshed.runtime_surface - ? toRuntimeSurface(refreshed.runtime_surface) - : current.runtimeSurface, + runtimeSurface: refreshedSurface, } : current, ); @@ -307,8 +316,16 @@ export default function App() { try { const boot = await fetchBootstrap("", bootstrapSecretRef.current); const url = deriveWsUrl(boot.ws_path, boot.token, boot.ws_url); + const runtimeSurface = boot.runtime_surface + ? toRuntimeSurface(boot.runtime_surface) + : state.runtimeSurface; + const runtimeHost = createRuntimeHost(runtimeSurface, boot.runtime_capabilities); const tokenExpiresAt = bootstrapTokenExpiresAt(boot.expires_in); - client.updateUrl(url); + if (runtimeHost.socketFactory) { + client.updateUrl(url, runtimeHost.socketFactory); + } else { + client.updateUrl(url); + } setState((current) => current.status === "ready" && current.client === client ? { @@ -316,9 +333,7 @@ export default function App() { token: boot.token, tokenExpiresAt, modelName: boot.model_name ?? current.modelName, - runtimeSurface: boot.runtime_surface - ? toRuntimeSurface(boot.runtime_surface) - : current.runtimeSurface, + runtimeSurface, } : current, ); @@ -1058,12 +1073,19 @@ function Shell({ const showHostChrome = isNativeHostSetupSurface; const showMainSidebar = view !== "settings"; + useEffect(() => { + document.documentElement.classList.toggle("native-host", showHostChrome); + return () => { + document.documentElement.classList.remove("native-host"); + }; + }, [showHostChrome]); + return (