fix(webui): honor native host chrome

This commit is contained in:
Xubin Ren
2026-09-03 09:21:48 +08:00
parent e881790caf
commit f5a2a41771
2 changed files with 40 additions and 5 deletions
+12 -5
View File
@@ -79,6 +79,7 @@ import {
} from "@/lib/api";
import {
createRuntimeHost,
isNativeRuntime,
toRuntimeSurface,
} from "@/lib/runtime";
import { projectNameFromPath, scopeWithAccessMode } from "@/lib/workspace";
@@ -778,6 +779,14 @@ function formatPairingExpiry(seconds: number | null | undefined): string {
return `${Math.ceil(seconds / 60)} min`;
}
function resolveRuntimeSurface(
surface: RuntimeSurface | null | undefined,
fallback: RuntimeSurface,
): RuntimeSurface {
if (isNativeRuntime(surface)) return "native";
return surface ? toRuntimeSurface(surface) : fallback;
}
export default function App() {
const { t } = useTranslation();
const [state, setState] = useState<BootState>({ status: "loading" });
@@ -787,9 +796,7 @@ export default function App() {
async (client: NanobotClient, fallbackSurface: RuntimeSurface) => {
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)
: fallbackSurface;
const runtimeSurface = resolveRuntimeSurface(boot.runtime_surface, fallbackSurface);
const runtimeHost = createRuntimeHost(runtimeSurface, boot.runtime_capabilities);
const tokenExpiresAt = boot.expires_in
? bootstrapTokenExpiresAt(boot.expires_in)
@@ -827,7 +834,7 @@ export default function App() {
if (cancelled) return;
if (secret) saveSecret(secret);
const url = deriveWsUrl(boot.ws_path, boot.token, boot.ws_url);
const runtimeSurface = toRuntimeSurface(boot.runtime_surface);
const runtimeSurface = resolveRuntimeSurface(boot.runtime_surface, "browser");
const runtimeHost = createRuntimeHost(runtimeSurface, boot.runtime_capabilities);
const client = new NanobotClient({
url,
@@ -1086,7 +1093,7 @@ function Shell({
const temporarySessionsRef = useRef<Record<string, ChatSummary>>({});
const effectiveRuntimeSurface =
settingsSnapshot?.surface ?? settingsSnapshot?.runtime_surface ?? runtimeSurface;
const showHostChrome = effectiveRuntimeSurface === "native";
const showHostChrome = isNativeRuntime(effectiveRuntimeSurface);
const showMainSidebar = view !== "settings";
const activeTemporarySession = activeKey ? temporarySessions[activeKey] ?? null : null;
const temporaryChatId = activeTemporarySession?.chatId ?? null;
+28
View File
@@ -310,6 +310,7 @@ describe("App layout", () => {
sessionUpdateHandlers.clear();
sidebarStateUpdateHandlers.clear();
window.history.replaceState(null, "", "/");
Reflect.deleteProperty(window, "nanobotHost");
setNavigatorPlatform("Linux x86_64");
localStorage.removeItem("nanobot-webui.sidebar");
localStorage.removeItem("nanobot-webui.sidebar.completed-runs.v1");
@@ -335,6 +336,7 @@ describe("App layout", () => {
afterEach(() => {
cleanup();
Reflect.deleteProperty(window, "nanobotHost");
vi.useRealTimers();
vi.unstubAllGlobals();
});
@@ -1655,6 +1657,32 @@ describe("App layout", () => {
await waitFor(() => expect(flowSidebar).toHaveStyle({ width: "272px" }));
});
it("uses native chrome when the host bridge overrides browser gateway metadata", async () => {
Reflect.set(window, "nanobotHost", { pickFolder: vi.fn() });
vi.mocked(fetchBootstrap).mockResolvedValue({
token: "tok",
api_token: "api-tok",
ws_path: "/",
expires_in: 300,
runtime_surface: "browser",
});
mockFetchRoutes({
"/api/settings": {
...baseSettingsPayload(),
surface: "browser",
runtime_surface: "browser",
},
});
render(<App />);
await waitFor(() => expect(connectSpy).toHaveBeenCalled());
await waitFor(() => {
expect(screen.getByTestId("sidebar-brand-mark")).toHaveClass("mt-5");
});
expect(document.documentElement).toHaveClass("native-host");
});
it("switches to the next session when deleting the active chat", async () => {
mockSessions = [
{