fix(ui): show each chat panel button once, in the pane header (#122507)

* test(ui): advertise terminal and browser panels in the mock harness

The mocked Control UI never advertised browser.request or terminal.open and
left terminalEnabled false, so the chat header's panel toggles were invisible
in the harness and could not be visually verified.

* fix(ui): give the chat pane header the only panel toggle row

The session workspace rail header rendered Terminal, Browser, Ask OpenClaw and
Changes alongside its own dock/refresh/collapse controls. Terminal and Changes
already lived in the chat pane header, so both rendered twice at once, while
Browser was reachable only from inside a files rail and Ask OpenClaw sat in a
per-session rail despite being a global surface that already owns a sidebar
entry (settings route 'custodian').

The rail header now owns workspace-file actions only. Browser moves up to the
pane header beside Terminal and into the narrow-header overflow menu; the
duplicated Terminal and Changes buttons and the Ask OpenClaw toggle are gone.

Production LOC: +21 -84.

* test(ui): stop the vite stub from shadowing the mock bootstrap config

ui/vite.config.ts registers a placeholder /control-ui-config.json middleware
and config-file plugins load before inline ones, so the mock gateway plugin's
bootstrap body never reached the app and every scenario bootstrap field was
silently dropped. Marking the mock plugin 'pre' lets it answer first.
This commit is contained in:
Peter Steinberger
2026-08-12 00:13:13 -07:00
committed by GitHub
parent d3696f8d76
commit 6f39f953ec
7 changed files with 92 additions and 104 deletions
+11 -1
View File
@@ -1472,8 +1472,10 @@ async function createChatPickerScenario(
// Advertised Gateway methods gate session actions (see
// ui/src/lib/session-method-access.ts). Omitting the mutation methods left
// every session context-menu row disabled, so the harness could not show
// the menu operators actually see.
// the menu operators actually see. browser.request/terminal.open likewise
// gate the chat header's panel toggles, which stayed invisible here.
featureMethods: [
"browser.request",
"chat.metadata",
"chat.startup",
"question.list",
@@ -1493,7 +1495,11 @@ async function createChatPickerScenario(
"sessions.catalog.list",
"sessions.catalog.read",
"system.info",
"terminal.open",
],
// Terminal has a second gate beyond the advertised method (see
// ui/src/lib/terminal-availability.ts).
terminalEnabled: true,
historyMessages: buildScrollableChatHistory(baseTime),
// Lights up the footer facepile and who's-online roster; the email-only
// entry keeps the roster's no-display-name row exercised.
@@ -2436,6 +2442,10 @@ function createMockGatewayPlugin(scenario: ControlUiMockGatewayScenario): Plugin
res.end(bootstrapBody);
});
},
// ui/vite.config.ts registers a placeholder bootstrap-config middleware and
// config-file plugins load first, so without "pre" its stub answers every
// request and the scenario's bootstrap fields never reach the app.
enforce: "pre",
name: "openclaw-control-ui-mock-gateway",
transformIndexHtml(html) {
return html.replace(