fix(ui): tighten Labs settings spacing (#112038)

This commit is contained in:
Peter Steinberger
2026-07-21 01:28:32 -07:00
committed by GitHub
parent 9349bb4c41
commit 3d3fd97c7d
4 changed files with 43 additions and 14 deletions
+4 -3
View File
@@ -23,6 +23,7 @@ Experimental features are preview surfaces behind explicit flags. They need more
| Codex harness | `plugins.entries.codex.config.appServer.experimental.sandboxExecServer` | You want native Codex app-server 0.132.0 or newer to target an OpenClaw sandbox-backed exec-server instead of disabling Code Mode | [Codex harness reference](/plugins/codex-harness-reference#sandboxed-native-execution) |
| Structured planning tool | `tools.experimental.planTool` | You want the structured `update_plan` tool exposed for multi-step work tracking in compatible runtimes and UIs | [Gateway configuration reference](/gateway/config-tools#toolsexperimental) |
| Code Mode | `tools.codeMode.enabled` | You want compact code-orchestrated access to a hidden OpenClaw tool catalog | [Code Mode](/tools/code-mode) |
| Swarm | `tools.swarm.enabled` | You want Code Mode scripts to orchestrate bounded groups of sub-agents in parallel | [Swarm](/tools/swarm) |
## Control UI Labs
@@ -31,9 +32,9 @@ Control UI switch. Enabling or disabling a lab patches the canonical Gateway
config immediately; the page shows a restart hint only when a feature requires
one.
Code Mode is currently the only shipped Labs entry. Swarm is not exposed yet:
its config shape has not shipped, so Control UI does not write a speculative
key that would invalidate the operator's config.
Code Mode and Swarm are the currently shipped Labs entries. Both switches
write existing validated config keys and normally take effect for future agent
runs without restarting the Gateway.
## Local model lean mode
+1 -1
View File
@@ -244,7 +244,7 @@ select it to open the owning Approvals page.
- Settings navigation starts with Ask OpenClaw, then groups pages by attention: General, Appearance, and Notifications up top; Connections (Connection, Channels, Communications, Devices); Agents & Tools (Agents, AI & Agents, Model Providers, MCP, Automation, Labs); Privacy & Security (Security, Approvals); and System (Infrastructure, Advanced, Debug, Logs, About). General is a slim hub with model defaults, language, and gateway host stats; every other setting lives on exactly one page.
- Privacy & Security: curated rows for gateway auth, exec policy, browser enablement, tool profile, device auth, and mobile pairing, above the schema-backed `security`/`approvals` sections.
- Approvals includes newest-first, 30-day history for resolved exec, plugin, and system-agent requests. Filter by kind or page through older rows to review the decision, reason, source session, and resolver attribution recorded by the Gateway.
- Labs exposes shipped experimental switches. Code Mode is the current entry and saves `tools.codeMode.enabled` immediately; unshipped experiments do not appear or write speculative config keys.
- Labs exposes shipped experimental switches. Code Mode and Swarm are the current entries and save `tools.codeMode.enabled` and `tools.swarm.enabled` immediately; unshipped experiments do not appear or write speculative config keys.
- Notifications: browser web-push status, subscribe/unsubscribe, and a test send.
- Advanced: every config section without a curated home, plus the raw JSON5 editor (previously the General page's Advanced mode).
- Model Setup (`/settings/model-setup`) is a subpage of Model Providers, launched from its header.
+27 -10
View File
@@ -59,14 +59,18 @@ async function mountPage(sourceConfig: Record<string, unknown>): Promise<{
return { page, provider, runtimeConfig };
}
function codeModeToggle(page: LabsPageElement) {
const toggle = page.querySelector<HTMLElement & { checked: boolean }>("wa-switch");
function labToggle(page: LabsPageElement, index: number, label: string) {
const toggle = page.querySelectorAll<HTMLElement & { checked: boolean }>("wa-switch").item(index);
if (!toggle) {
throw new Error("Code Mode toggle not rendered");
throw new Error(`${label} toggle not rendered`);
}
return toggle;
}
function codeModeToggle(page: LabsPageElement) {
return labToggle(page, 0, "Code Mode");
}
describe("LabsPage", () => {
beforeEach(async () => {
await i18n.setLocale("en");
@@ -125,19 +129,32 @@ describe("LabsPage", () => {
expect(runtimeConfig.refresh).toHaveBeenCalledOnce();
});
it("writes true at the registered config path when enabling", async () => {
const { page, runtimeConfig } = await mountPage({
tools: { codeMode: { enabled: false } },
});
const toggle = codeModeToggle(page);
it.each([
{
label: "Code Mode",
index: 0,
sourceConfig: { tools: { codeMode: { enabled: false } } },
expectedPatch: { tools: { codeMode: { enabled: true } } },
note: "labs: update codeMode",
},
{
label: "Swarm",
index: 1,
sourceConfig: { tools: { swarm: { enabled: false } } },
expectedPatch: { tools: { swarm: { enabled: true } } },
note: "labs: update swarm",
},
])("writes true at the registered config path when enabling $label", async (testCase) => {
const { page, runtimeConfig } = await mountPage(testCase.sourceConfig);
const toggle = labToggle(page, testCase.index, testCase.label);
toggle.checked = true;
toggle.dispatchEvent(new Event("change", { bubbles: true, composed: true }));
await vi.waitFor(() => expect(runtimeConfig.patch).toHaveBeenCalledOnce());
expect(runtimeConfig.patch).toHaveBeenCalledWith({
raw: { tools: { codeMode: { enabled: true } } },
note: "labs: update codeMode",
raw: testCase.expectedPatch,
note: testCase.note,
});
});
});
+11
View File
@@ -76,12 +76,23 @@
gap: var(--space-8);
}
/* An intro belongs to the first section rather than forming a separate block.
Keep later sections at the normal --space-8 separation. */
.settings-page:has(> .settings-page__intro) {
gap: var(--space-5);
}
.settings-page:has(> .settings-page__intro) > .settings-section + .settings-section {
margin-top: calc(var(--space-8) - var(--space-5));
}
/* Table/list-heavy pages (sessions, automations, plugins) need more width. */
.settings-page--wide {
max-width: 1120px;
}
.settings-page__intro {
margin: 0;
font-size: var(--control-ui-text-sm);
line-height: 1.5;
color: var(--muted);