Modular Dashboard: agent-composable Control UI workspaces (tracking issue)
The dashboard your agent builds with you. OpenClaw's Control UI becomes a modular, agent-composable workspace: your agents create tabs, drop in widgets, and wire them to live data through the same guarded commands a human uses — no code, no redeploy. The thing no fixed dashboard (Codex included) can do.

▶ Full-quality recording (light / dark).
This issue tracks a 3-PR series. Each PR is independently reviewable, independently useful, and lands in order. Nothing here is a big-bang rewrite — it's a bundled plugin plus one bundled Control UI view, following the same shape #99930 (plugin tabs) just landed.
The gap
The Control UI is a fixed set of hand-coded views. Two audiences have outgrown that:
- Small-business operators want a business control hub they arrange themselves — financial tabs, per-agent workspaces, drag/drop, hide/collapse — and will never touch code.
- Daily-driver maintainers (a lot of them on Codex right now) want mission-control ergonomics: sessions, activity, approvals, and cron composed into one view, live, fast to dispatch from.
Both groups are asking the same thing in different words: let me arrange this myself.
The insight
Don't build them a settings panel. Give the agent the ability to compose the surface — via layout-as-data plus a guarded control plane — so humans and agents edit the same document, through the same validated commands. "Give me a financials tab" becomes a real capability: the agent creates the tab, adds widgets it authored, binds them to live data, and the operator keeps full drag/drop parity over the exact same layout.
How it works (architecture overview)
Four ideas carry the whole design. The per-PR bodies go deep on each; this is the shape.
1. Layout-as-data (workspace.json). The dashboard's entire state is one JSON document at <stateDir>/dashboard/workspace.json: tabs contain widgets, each widget carries a 12-column grid position, its data bindings, and provenance (createdBy: "user" | "system" | "agent:<id>"). Nothing about a workspace lives in code — so anything that can edit the document can compose the dashboard, and a human and an agent are editing the same bytes.
2. One control plane, three faces. Every mutation — from the Control UI, the openclaw dashboard … CLI, or an agent's dashboard_* tool — funnels through a single validated DashboardStore. One in-process async mutex serializes all callers; every write is schema-validated by hand-written guards (repo idiom, no zod at the RPC layer), applied whole-or-not-at-all, written atomically (temp + rename), and undoable (20-entry ring). Humans and agents literally cannot diverge, because there is exactly one code path that writes the file:
CLI ─┐
agent tool ─┼─▶ DashboardStore.mutate() ──▶ workspace.json
gateway RPC ─┘ (one mutex · one validator · atomic write)
│
└──▶ plugin.dashboard.changed ──▶ every operator UI live-updates
Each successful write broadcasts plugin.dashboard.changed, so an agent's edit appears in an already-open browser with no reload — and the operator can push back with drag/drop on the same document immediately.
3. Plugin-tab seam reuse (#99930). The whole surface is one bundled Control-UI tab, registered through the exact descriptor + BUNDLED_TAB_VIEWS mechanism logbook shipped in #99930. No new core UI mechanism is invented; this is the second consumer of that seam. Core ui/ changes are limited to five small additive edits — everything else is new plugin/UI files.
4. A hard trust boundary for custom widgets. Trusted builtins render as first-party Lit templates. Agent-authored widgets are untrusted code and are treated as hostile: they render in an <iframe sandbox="allow-scripts"> (opaque origin, never allow-same-origin), are served from a static-only, path-jailed, approval-gated route with a strict CSP (connect-src 'none' — no network, structurally), and reach data only through a versioned postMessage bridge where the trusted parent resolves the widget's declared bindings on its behalf. The widget never holds a credential and never talks to the gateway. An operator approval gate stands between "agent scaffolds a widget" and "an iframe ever mounts."
The proof
The series is backed by an end-to-end demo (the recording above): an agent creates a "Financials" tab live in an already-open browser (no reload), drops in built-in widgets bound to a data file, scaffolds a sandboxed custom widget, the operator approves it from a pending-card, it renders and stays live on data pushes — then the human drags things around, collapses/hides widgets, and the agent's own dashboard_workspace_get reflects the human's edits accurately. Same document, both directions.
The two ends of that story as stills — the default surface, and what the agent composes on top of it:


The receipts
This is what's already been asked for, repeatedly:
Each PR below states exactly which of these it closes or partially advances — none of them are claimed as fully closed by a single PR except where stated.
The discipline
This builds on recent direction, not against it:
Core stays lean: everything except one bundled tab view lives in a new plugin, extensions/dashboard. The agent never touches ui/ source — it only ever calls the same validated gateway methods/CLI/tools a human can call.
Try the whole thing in one checkout
For reviewers who want to run it before reading three diffs: because we don't have upstream write access to set GitHub-native stacked bases, the three PRs each target main with cumulative diffs — which means #101098 (the top of the stack) already is the full feature in one branch. Just gh pr checkout #101098 (or git fetch <fork> feat/modular-dashboard), start the gateway, open the Control UI, and see the whole thing work end to end. CI runs against the full integration on #101098. The review-and-merge units are still the three PRs below, reviewed base-first (#101094 → #101097 → #101098); #101098 doubles as the "check it out and run everything" branch.
The 3-PR roadmap
| PR |
Content |
Closes / anchors |
| #101094 — Backend: workspace document + control plane |
extensions/dashboard plugin: the workspace.json store, 14 gateway methods, CLI, 14 agent tools, change broadcasts. Zero core changes. |
Foundation for the series; references this issue |
| #101097 — Workspaces UI: shell + builtins + Overview-as-data + polish |
The bundled plugin-tab view: live tab strip, 12-column grid, drag/drop, resize, collapse/hide, per-cell error boundaries, live updates — plus 9 trusted built-in widgets (stat-card, markdown, table, iframe-embed, sessions, usage, cron, instances, activity) and the default workspace that replaces the old Overview with the same information as live data |
#66138; supersedes #77774 / #86460 as workspace content; partials #72545 / #76089 |
| #101098 — Sandboxed custom widgets + approval flow |
Agent-authored HTML widgets rendered in a locked-down iframe, a versioned postMessage bridge, and an operator approval gate before anything untrusted ever mounts |
#66983 / #68497; partials #27574 |
Order: PR1 → PR2 → PR3. Each depends on the prior and is independently mergeable/valuable on its own — none of them require the ones after it to be useful. Each PR stays inside VISION.md's ~5K reviewable-line guideline (source LOC, excluding generated i18n bundles).
What this deliberately is not
This is not the contested big-bang dashboard PR (#95339). That PR's shape is exactly what this series avoids: one huge diff, no staging, core changes throughout. Here, two of the three PRs touch only a new plugin directory; the only core code PR2 touches at all is five existing files — a BUNDLED_TAB_VIEWS entry in plugin-page.ts, a styles.css import, and a breadcrumb prop threaded through app-host.ts, app-topbar.ts, and dashboard-header.ts — plus the new plugin page/component files it adds under ui/src/pages/plugin/ and ui/src/lib/dashboard/. Touches almost no core code.
Verification posture across the series
Every PR ships with its own test suite (unit + integration, and E2E where relevant) and its full test suite passes before it is proposed here (CI runs on each PR). PR3 additionally carries a written adversarial-security-review summary, because it's the highest-security-surface layer in the project (see PR3's body for specifics — independent reviewers tried to refute the sandbox/path-jail/CSP model and could not, and the one real hole they did find is fixed with a regression test before this reaches upstream).
Each increment is well inside VISION.md's ~5K reviewable-line guideline: #101094 ~3,300 lines of source (11 files); #101097 ~4,000 lines of source (23 files) — the raw diff looks larger (~14K) only because it includes 61 generated i18n locale bundles (regenerated, never hand-edited) plus tests; #101098 ~1,400 lines of source (9 files).
Because we have no upstream write access to set GitHub-native stacked bases, the three PRs each target main with cumulative diffs — review base-first (#101094 → #101097 → #101098), reading each PR's new files as the delta. #101098, at the top of the stack, is also the full-feature branch to check out and run.
Cross-links: #101094, #101097, #101098.
Modular Dashboard: agent-composable Control UI workspaces (tracking issue)
▶ Full-quality recording (light / dark).
This issue tracks a 3-PR series. Each PR is independently reviewable, independently useful, and lands in order. Nothing here is a big-bang rewrite — it's a bundled plugin plus one bundled Control UI view, following the same shape #99930 (plugin tabs) just landed.
The gap
The Control UI is a fixed set of hand-coded views. Two audiences have outgrown that:
Both groups are asking the same thing in different words: let me arrange this myself.
The insight
Don't build them a settings panel. Give the agent the ability to compose the surface — via layout-as-data plus a guarded control plane — so humans and agents edit the same document, through the same validated commands. "Give me a financials tab" becomes a real capability: the agent creates the tab, adds widgets it authored, binds them to live data, and the operator keeps full drag/drop parity over the exact same layout.
How it works (architecture overview)
Four ideas carry the whole design. The per-PR bodies go deep on each; this is the shape.
1. Layout-as-data (
workspace.json). The dashboard's entire state is one JSON document at<stateDir>/dashboard/workspace.json: tabs contain widgets, each widget carries a 12-column grid position, its data bindings, and provenance (createdBy: "user" | "system" | "agent:<id>"). Nothing about a workspace lives in code — so anything that can edit the document can compose the dashboard, and a human and an agent are editing the same bytes.2. One control plane, three faces. Every mutation — from the Control UI, the
openclaw dashboard …CLI, or an agent'sdashboard_*tool — funnels through a single validatedDashboardStore. One in-process async mutex serializes all callers; every write is schema-validated by hand-written guards (repo idiom, no zod at the RPC layer), applied whole-or-not-at-all, written atomically (temp + rename), and undoable (20-entry ring). Humans and agents literally cannot diverge, because there is exactly one code path that writes the file:Each successful write broadcasts
plugin.dashboard.changed, so an agent's edit appears in an already-open browser with no reload — and the operator can push back with drag/drop on the same document immediately.3. Plugin-tab seam reuse (#99930). The whole surface is one bundled Control-UI tab, registered through the exact descriptor +
BUNDLED_TAB_VIEWSmechanism logbook shipped in #99930. No new core UI mechanism is invented; this is the second consumer of that seam. Coreui/changes are limited to five small additive edits — everything else is new plugin/UI files.4. A hard trust boundary for custom widgets. Trusted builtins render as first-party Lit templates. Agent-authored widgets are untrusted code and are treated as hostile: they render in an
<iframe sandbox="allow-scripts">(opaque origin, neverallow-same-origin), are served from a static-only, path-jailed, approval-gated route with a strict CSP (connect-src 'none'— no network, structurally), and reach data only through a versioned postMessage bridge where the trusted parent resolves the widget's declared bindings on its behalf. The widget never holds a credential and never talks to the gateway. An operator approval gate stands between "agent scaffolds a widget" and "an iframe ever mounts."The proof
The series is backed by an end-to-end demo (the recording above): an agent creates a "Financials" tab live in an already-open browser (no reload), drops in built-in widgets bound to a data file, scaffolds a sandboxed custom widget, the operator approves it from a pending-card, it renders and stays live on data pushes — then the human drags things around, collapses/hides widgets, and the agent's own
dashboard_workspace_getreflects the human's edits accurately. Same document, both directions.The two ends of that story as stills — the default surface, and what the agent composes on top of it:
The receipts
This is what's already been asked for, repeatedly:
sendPromptbridge capability (PR3).Each PR below states exactly which of these it closes or partially advances — none of them are claimed as fully closed by a single PR except where stated.
The discipline
This builds on recent direction, not against it:
BUNDLED_TAB_VIEWSmechanism feat(logbook): automatic work journal plugin with Control UI timeline tab #99930 introduced for logbook. No new core UI mechanism is invented; this is the second consumer of that seam.Core stays lean: everything except one bundled tab view lives in a new plugin,
extensions/dashboard. The agent never touchesui/source — it only ever calls the same validated gateway methods/CLI/tools a human can call.Try the whole thing in one checkout
For reviewers who want to run it before reading three diffs: because we don't have upstream write access to set GitHub-native stacked bases, the three PRs each target
mainwith cumulative diffs — which means #101098 (the top of the stack) already is the full feature in one branch. Justgh pr checkout #101098(orgit fetch <fork> feat/modular-dashboard), start the gateway, open the Control UI, and see the whole thing work end to end. CI runs against the full integration on #101098. The review-and-merge units are still the three PRs below, reviewed base-first (#101094 → #101097 → #101098); #101098 doubles as the "check it out and run everything" branch.The 3-PR roadmap
extensions/dashboardplugin: theworkspace.jsonstore, 14 gateway methods, CLI, 14 agent tools, change broadcasts. Zero core changes.Order: PR1 → PR2 → PR3. Each depends on the prior and is independently mergeable/valuable on its own — none of them require the ones after it to be useful. Each PR stays inside VISION.md's ~5K reviewable-line guideline (source LOC, excluding generated i18n bundles).
What this deliberately is not
This is not the contested big-bang dashboard PR (#95339). That PR's shape is exactly what this series avoids: one huge diff, no staging, core changes throughout. Here, two of the three PRs touch only a new plugin directory; the only core code PR2 touches at all is five existing files — a
BUNDLED_TAB_VIEWSentry inplugin-page.ts, astyles.cssimport, and a breadcrumb prop threaded throughapp-host.ts,app-topbar.ts, anddashboard-header.ts— plus the new plugin page/component files it adds underui/src/pages/plugin/andui/src/lib/dashboard/. Touches almost no core code.Verification posture across the series
Every PR ships with its own test suite (unit + integration, and E2E where relevant) and its full test suite passes before it is proposed here (CI runs on each PR). PR3 additionally carries a written adversarial-security-review summary, because it's the highest-security-surface layer in the project (see PR3's body for specifics — independent reviewers tried to refute the sandbox/path-jail/CSP model and could not, and the one real hole they did find is fixed with a regression test before this reaches upstream).
Each increment is well inside VISION.md's ~5K reviewable-line guideline: #101094 ~3,300 lines of source (11 files); #101097 ~4,000 lines of source (23 files) — the raw diff looks larger (~14K) only because it includes 61 generated i18n locale bundles (regenerated, never hand-edited) plus tests; #101098 ~1,400 lines of source (9 files).
Because we have no upstream write access to set GitHub-native stacked bases, the three PRs each target
mainwith cumulative diffs — review base-first (#101094 → #101097 → #101098), reading each PR's new files as the delta. #101098, at the top of the stack, is also the full-feature branch to check out and run.Cross-links: #101094, #101097, #101098.