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(
+ `
+
+ `,
+ );
+
+ 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`
- capability.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;