Control UI
Use OpenClaw's typed UI tools for state and layout. Use browser automation only
to inspect or interact with rendered pixels.
Mental model
- A sidebar item is a session, not a dashboard page.
- Each session owns one board. The board contains tabs and widgets.
- Pinning a session keeps it in the sidebar. Sessions with a stored board appear
in the
/dashboards gallery.
screen changes connected Control UI layout and navigation. It does not read
the page or take screenshots.
sessions_list finds sessions. sessions renames, groups, pins, or archives
them. It does not edit board content.
dashboard reads and arranges the current session's board and registered
plugin widgets. show_widget authors or updates custom HTML/SVG widgets.
Do not replace these operations with shell calls or raw Gateway RPC when the
typed tool exists.
Start safely
- Identify the target session and whether the Control UI is local/direct or
published through Tailscale or another HTTPS proxy.
- If the user wants a dedicated sidebar dashboard, choose or create its
visible session before authoring any widget. A tab is only a partition
inside one session's board; it never creates a sidebar row.
- If the current turn is running in a temporary or parent session, hand the
dashboard task to the intended visible session first. Board widgets are
keyed by session and cannot be moved to another session afterward.
- Read current state before changing it:
- use
sessions_list to resolve the session;
- use
dashboard with action: "read" for the current session;
- inspect existing tabs, stable widget names, owners, sizes, and panel layout.
- If the task targets another session's board, move the work into that session.
Dashboard tools intentionally operate on the current session.
Read hosting.md before opening or repairing a remote
Control UI. Read dashboards.md before creating or
restructuring a board.
Use the visualize skill for widget composition, libraries, interactive code
explanations, and dashboard content. Keep session and board organization here.
Navigate and arrange the UI
Use screen for deterministic client commands:
navigate to open a session by sessionKey;
sidebar_show / sidebar_hide for the session sidebar;
split_right / split_down, focus, and close_pane for panes;
terminal_show / terminal_hide and browser_show / browser_hide for
docked panels.
screen broadcasts to every connected Control UI that advertises UI commands;
it cannot select one browser tab. Confirm the blast radius when several clients
may be open. If it reports no capable client, ask the user to open the Control
UI and retry.
Use the in-app browser or an available browser-control tool when the task needs
DOM inspection, clicking, typing, or screenshots. Reuse the existing signed-in
Control UI tab when possible.
Build a session dashboard
- Call
dashboard read. Reuse suitable tabs and widget names.
- Create or rename tabs with
tab_create / tab_update; use short lowercase
slug IDs.
- Add content with the correct owner:
- native text, metrics, tables, charts, and links:
show_widget with
pin: true and report;
- self-contained custom HTML/SVG or registered-source content:
show_widget with pin: true;
- trusted plugin widgets:
dashboard widget_put with an advertised
pluginKind.
Do not embed Grafana or another external application in an <iframe> inside
show_widget; widget sandboxes reject child-frame URLs and navigation. Use
an ordinary user-clicked link to open the application, fetch an exact HTTPS
API through declared capabilities.netOrigins, use a Gateway data binding,
or install/use a trusted plugin widget instead.
- Use a stable
name when calling show_widget. Reusing the same name with
new widget_code updates the widget in place.
- Arrange with
widget_move, widget_resize, and tab reordering. Prefer size
presets and board order over pixel placement.
- Pin the current session with
sessions patch when it should stay prominent.
- Call
dashboard focus_tab to show the intended tab in the dashboard side
panel. If no Control UI is connected, the command returns unavailable; have
the user open the session and retry.
- Choose the presentation after focusing the tab.
set_presentation with
presentation: "expanded" expands the dashboard; "split" shows it beside
chat in the current panel layout. The human can use
Expand side panel for a full-width dashboard, Collapse to bring chat
back, or close the panel for chat alone.
Never create a fake top-level page for a dashboard. For a dedicated dashboard,
use a dedicated session, pin that session, and build its board from inside it.
Update without breaking the board
- Read first and mutate the smallest unit.
- Keep stable tab IDs and widget names unless replacement is intentional.
- Do not change a widget's content owner in place. Remove it, then recreate it
with the new owner or registered kind.
- Capability grants are bound to exact widget bytes and revision. Changed
content may require approval again.
- Resetting a conversation preserves its board. Deleting the session deletes
the board.
Verify the result
Use two layers of proof:
- State:
dashboard read shows the expected tabs, widgets, names, owners,
order, and sizes; sessions_list shows the expected session
metadata.
- Rendered UI: open the actual Control UI, confirm the correct session and
dashboard panel, then inspect or screenshot the widget frame. Exercise any
important controls.
An HTTP 200 for the shell or widget route is transport proof, not visual proof.
Do not claim the dashboard works until the sandboxed frame renders.
For widgets that fetch live data, exercise that fetch from the rendered frame.
A host-side curl does not prove the browser received the required capability
grant or accepted the endpoint's CORS and mixed-content policy.
If no browser-control or connected-client inspection is available, report the
dashboard as published but visually unverified instead of claiming success.
Recovery order
When a widget is blank or stale:
- confirm the expected session, tab, widget name, and revision with
dashboard read;
- classify the hosting path using hosting.md;
- verify the Control UI origin, the separate widget sandbox origin, and the
protocol used on every reverse-proxy hop;
- if widget JavaScript reports
Failed to fetch, verify its exact HTTPS origin
is declared and granted in capabilities.netOrigins, then check browser CORS
and mixed-content errors;
- after a Gateway restart or routing change, republish/update the widget so the
browser receives a fresh ticketed frame URL;
- reload the Control UI and verify the rendered frame, not only route status.
1---2name: control-ui3description: Operate and troubleshoot the OpenClaw Control UI: navigate connected clients, organize sessions, build session dashboards, and handle direct or Tailscale-hosted Gateways.4---56# Control UI78Use OpenClaw's typed UI tools for state and layout. Use browser automation only9to inspect or interact with rendered pixels.1011## Mental model1213- A sidebar item is a **session**, not a dashboard page.14- Each session owns one board. The board contains tabs and widgets.15- Pinning a session keeps it in the sidebar. Sessions with a stored board appear16 in the `/dashboards` gallery.17- `screen` changes connected Control UI layout and navigation. It does not read18 the page or take screenshots.19- `sessions_list` finds sessions. `sessions` renames, groups, pins, or archives20 them. It does not edit board content.21- `dashboard` reads and arranges the current session's board and registered22 plugin widgets. `show_widget` authors or updates custom HTML/SVG widgets.2324Do not replace these operations with shell calls or raw Gateway RPC when the25typed tool exists.2627## Start safely28291. Identify the target session and whether the Control UI is local/direct or30 published through Tailscale or another HTTPS proxy.31 - If the user wants a dedicated sidebar dashboard, choose or create its32 visible session before authoring any widget. A tab is only a partition33 inside one session's board; it never creates a sidebar row.34 - If the current turn is running in a temporary or parent session, hand the35 dashboard task to the intended visible session first. Board widgets are36 keyed by session and cannot be moved to another session afterward.372. Read current state before changing it:38 - use `sessions_list` to resolve the session;39 - use `dashboard` with `action: "read"` for the current session;40 - inspect existing tabs, stable widget names, owners, sizes, and panel layout.413. If the task targets another session's board, move the work into that session.42 Dashboard tools intentionally operate on the current session.4344Read [hosting.md](references/hosting.md) before opening or repairing a remote45Control UI. Read [dashboards.md](references/dashboards.md) before creating or46restructuring a board.47Use the `visualize` skill for widget composition, libraries, interactive code48explanations, and dashboard content. Keep session and board organization here.4950## Navigate and arrange the UI5152Use `screen` for deterministic client commands:5354- `navigate` to open a session by `sessionKey`;55- `sidebar_show` / `sidebar_hide` for the session sidebar;56- `split_right` / `split_down`, `focus`, and `close_pane` for panes;57- `terminal_show` / `terminal_hide` and `browser_show` / `browser_hide` for58 docked panels.5960`screen` broadcasts to every connected Control UI that advertises UI commands;61it cannot select one browser tab. Confirm the blast radius when several clients62may be open. If it reports no capable client, ask the user to open the Control63UI and retry.6465Use the in-app browser or an available browser-control tool when the task needs66DOM inspection, clicking, typing, or screenshots. Reuse the existing signed-in67Control UI tab when possible.6869## Build a session dashboard70711. Call `dashboard read`. Reuse suitable tabs and widget names.722. Create or rename tabs with `tab_create` / `tab_update`; use short lowercase73 slug IDs.743. Add content with the correct owner:75 - native text, metrics, tables, charts, and links: `show_widget` with76 `pin: true` and `report`;77 - self-contained custom HTML/SVG or registered-source content:78 `show_widget` with `pin: true`;79 - trusted plugin widgets: `dashboard widget_put` with an advertised80 `pluginKind`.81 Do not embed Grafana or another external application in an `<iframe>` inside82 `show_widget`; widget sandboxes reject child-frame URLs and navigation. Use83 an ordinary user-clicked link to open the application, fetch an exact HTTPS84 API through declared `capabilities.netOrigins`, use a Gateway data binding,85 or install/use a trusted plugin widget instead.864. Use a stable `name` when calling `show_widget`. Reusing the same name with87 new `widget_code` updates the widget in place.885. Arrange with `widget_move`, `widget_resize`, and tab reordering. Prefer size89 presets and board order over pixel placement.906. Pin the current session with `sessions patch` when it should stay prominent.917. Call `dashboard focus_tab` to show the intended tab in the dashboard side92 panel. If no Control UI is connected, the command returns unavailable; have93 the user open the session and retry.948. Choose the presentation after focusing the tab. `set_presentation` with95 `presentation: "expanded"` expands the dashboard; `"split"` shows it beside96 chat in the current panel layout. The human can use97 **Expand side panel** for a full-width dashboard, **Collapse** to bring chat98 back, or close the panel for chat alone.99100Never create a fake top-level page for a dashboard. For a dedicated dashboard,101use a dedicated session, pin that session, and build its board from inside it.102103## Update without breaking the board104105- Read first and mutate the smallest unit.106- Keep stable tab IDs and widget names unless replacement is intentional.107- Do not change a widget's content owner in place. Remove it, then recreate it108 with the new owner or registered kind.109- Capability grants are bound to exact widget bytes and revision. Changed110 content may require approval again.111- Resetting a conversation preserves its board. Deleting the session deletes112 the board.113114## Verify the result115116Use two layers of proof:1171181. **State:** `dashboard read` shows the expected tabs, widgets, names, owners,119 order, and sizes; `sessions_list` shows the expected session120 metadata.1212. **Rendered UI:** open the actual Control UI, confirm the correct session and122 dashboard panel, then inspect or screenshot the widget frame. Exercise any123 important controls.124125An HTTP 200 for the shell or widget route is transport proof, not visual proof.126Do not claim the dashboard works until the sandboxed frame renders.127For widgets that fetch live data, exercise that fetch from the rendered frame.128A host-side `curl` does not prove the browser received the required capability129grant or accepted the endpoint's CORS and mixed-content policy.130If no browser-control or connected-client inspection is available, report the131dashboard as published but visually unverified instead of claiming success.132133## Recovery order134135When a widget is blank or stale:1361371. confirm the expected session, tab, widget name, and revision with138 `dashboard read`;1392. classify the hosting path using [hosting.md](references/hosting.md);1403. verify the Control UI origin, the separate widget sandbox origin, and the141 protocol used on every reverse-proxy hop;1424. if widget JavaScript reports `Failed to fetch`, verify its exact HTTPS origin143 is declared and granted in `capabilities.netOrigins`, then check browser CORS144 and mixed-content errors;1455. after a Gateway restart or routing change, republish/update the widget so the146 browser receives a fresh ticketed frame URL;1476. reload the Control UI and verify the rendered frame, not only route status.