diff --git a/src/cli/help-exit.process.test.ts b/src/cli/help-exit.process.test.ts index 5d2b27df816b..6ef15d166b03 100644 --- a/src/cli/help-exit.process.test.ts +++ b/src/cli/help-exit.process.test.ts @@ -498,26 +498,12 @@ describe("JSON console style process output", () => { expect(() => parseJsonLines(result.stdout)).toThrow(); }); - it.each([ - { - name: "missing container value", - args: ["--container"], - message: "--container requires a value", - }, - { - name: "missing profile value", - args: ["--profile"], - message: "--profile requires a value", - }, - { - name: "container/profile conflict", - args: ["--container", "demo", "--profile", "work", "status"], - message: "--container cannot be combined with --profile/--dev", - }, - ])("structures entry validation for $name", async ({ args, message }) => { + it("structures entry validation errors", async () => { let failure: CliProcessFailure | undefined; try { - await runCliProcess({ args, config: loggingConfig }); + // One cold process proves entry-level JSON formatting. Profile parsing and + // container/profile conflicts have dedicated unit and process coverage below. + await runCliProcess({ args: ["--container"], config: loggingConfig }); } catch (error) { failure = error as CliProcessFailure; } @@ -526,7 +512,10 @@ describe("JSON console style process output", () => { expect(failure?.stdout ?? "").toBe(""); expect(parseJsonLines(failure?.stderr ?? "")).toEqual( expect.arrayContaining([ - expect.objectContaining({ level: "error", message: expect.stringContaining(message) }), + expect.objectContaining({ + level: "error", + message: expect.stringContaining("--container requires a value"), + }), ]), ); }); diff --git a/ui/src/pages/chat/chat-responsive.browser.test.ts b/ui/src/pages/chat/chat-responsive.browser.test.ts index 953ad15cc8c6..dfb53ccbeba3 100644 --- a/ui/src/pages/chat/chat-responsive.browser.test.ts +++ b/ui/src/pages/chat/chat-responsive.browser.test.ts @@ -586,6 +586,53 @@ describeBrowserLayout.concurrent("chat responsive browser layout", () => { } }); + it("keeps the native gateway picker as compact as sidebar menus", async () => { + const page = await openBrowserPage(800, 600); + try { + const splitViewCss = readStyleSheet("ui/src/styles/chat/split-view.css"); + await page.setContent( + ` + + + Local Gateway + + `, + ); + + const styles = await page.evaluate(() => { + const dropdown = document.querySelector(".chat-pane__gateway-menu")!; + const menu = dropdown.shadowRoot!.querySelector('[part="menu"]')!; + const item = dropdown.querySelector(".chat-pane__gateway-menu-item")!; + const menuStyle = getComputedStyle(menu); + const itemStyle = getComputedStyle(item); + return { + menu: { + borderRadius: menuStyle.borderRadius, + padding: menuStyle.padding, + }, + item: { + borderRadius: itemStyle.borderRadius, + fontSize: itemStyle.fontSize, + minHeight: itemStyle.minHeight, + padding: itemStyle.padding, + }, + }; + }); + + expect(styles).toEqual({ + menu: { borderRadius: "8px", padding: "6px" }, + item: { + borderRadius: "8px", + fontSize: "13px", + minHeight: "30px", + padding: "0px 8px", + }, + }); + } finally { + await closeBrowserPage(page); + } + }); + it("pins the collapsed session rail to the pane header edge", async () => { const page = await openBrowserPage(922, 282); try { diff --git a/ui/src/pages/chat/components/chat-pane-header.test.ts b/ui/src/pages/chat/components/chat-pane-header.test.ts index 423345b5f40b..c311f0a3675d 100644 --- a/ui/src/pages/chat/components/chat-pane-header.test.ts +++ b/ui/src/pages/chat/components/chat-pane-header.test.ts @@ -120,6 +120,7 @@ describe("chat pane header", () => { const { container } = mount({ nativeGateways: nativeGateways(gatewaySnapshot) }); const rows = container.querySelectorAll(".chat-pane__gateway-item"); expect(rows).toHaveLength(2); + expect(container.querySelectorAll(".chat-pane__gateway-menu-item")).toHaveLength(4); expect(rows[0]?.textContent).toContain("Local Gateway"); expect(rows[0]?.textContent).toContain("primary"); expect(rows[0]?.querySelector(".chat-pane__gateway-check")).not.toBeNull(); diff --git a/ui/src/pages/chat/components/chat-pane-header.ts b/ui/src/pages/chat/components/chat-pane-header.ts index 2fa2f28ad010..79b3464c0599 100644 --- a/ui/src/pages/chat/components/chat-pane-header.ts +++ b/ui/src/pages/chat/components/chat-pane-header.ts @@ -177,7 +177,7 @@ function renderGatewayPicker(props: ChatPaneHeaderProps) { ${snapshot.gateways.map((gateway) => { const selected = gateway.id === snapshot.currentId; return html` !setPrimaryDisabled && capability.setPrimary(current.id)} >${t("chat.sessionHeader.gatewayPicker.setPrimary")} - capability.openSettings()} + capability.openSettings()} >${t("chat.sessionHeader.gatewayPicker.openSettings")} diff --git a/ui/src/styles/chat/split-view.css b/ui/src/styles/chat/split-view.css index 50aa3df64ce7..098251481d1e 100644 --- a/ui/src/styles/chat/split-view.css +++ b/ui/src/styles/chat/split-view.css @@ -221,6 +221,23 @@ openclaw-chat-pane { min-width: 0; } +.chat-pane__gateway-menu::part(menu) { + padding: 6px; + border: 1px solid color-mix(in srgb, var(--border-strong) 78%, transparent); + border-radius: 8px; + background: var(--bg-elevated); + box-shadow: var(--shadow-lg); +} + +.chat-pane__gateway-menu-item { + min-height: 30px; + padding: 0 8px; + border-radius: 8px; + color: var(--text); + font: inherit; + font-size: 13px; +} + .chat-pane__gateway-chip { display: inline-flex; max-width: 170px;