Skip to content

Modular Dashboard — Wave 2+ roadmap (tracking) #101136

Description

@100yenadmin

The dashboard your agent builds with you. A layout-as-data Control-UI surface where an AI agent (or a human) composes tabs and widgets through one validated control plane — and agent-authored widgets run live inside a provably-safe sandbox. This issue is the master hub for the whole initiative: the architecture, every PR, the merge order, and how to try and test it.

📦 One package, shipped as a reviewable stack. 15 PRs, all green except one inherited check (see Review & test below). Demo (agent scaffolds a custom revenue-chart widget → approval card → sandboxed mount → live update, then the human keeps working):

End-to-end demo: agent builds a sandboxed dashboard widget, operator approves, it mounts and updates live


Architecture — one store, three faces, two widget tiers

Everything that mutates a dashboard funnels through one validated store, so the agent, the CLI, and human drag/drop are the same guarded control plane. The agent never touches ui/ source — it composes only through the control plane a human uses.

flowchart TB
  subgraph Authors["Who composes a dashboard — same validated control plane"]
    A1["🤖 AI agent · agent tools<br/>(dashboard_tabs_create, _widget_scaffold, …)"]
    A2["👤 Human · CLI<br/>(openclaw dashboard …)"]
    A3["👤 Human · Control UI<br/>(drag / drop / collapse)"]
  end
  A1 --> GW
  A2 --> GW
  A3 --> GW
  GW["Gateway RPC<br/>dashboard.workspace.* · widget.* · data.read"]
  GW --> STORE
  STORE["🔒 DashboardStore<br/>async mutex · atomic file write · hand-written validators<br/>20-entry undo ring · 256 KB cap"]
  STORE --> DOC[["workspace.json<br/>layout-as-data: tabs · widgets · prefs · registry"]]
  STORE -- "plugin.dashboard.changed (broadcast)" --> UI["🖥️ Control UI · Workspaces tab"]
  UI --> GRID["Tabs → widget grid"]
  GRID --> BUILTIN["✅ Builtin widgets — trusted tier<br/>charts · preview · notes · approvals · status"]
  GRID --> CUSTOM["🧩 Custom widgets — sandboxed tier<br/>iframe sandbox=allow-scripts · opaque origin<br/>CSP connect-src 'none' · approval-gated"]
  CUSTOM -. "postMessage bridge (parent resolves bindings)" .-> UI
Loading

The two-tier capability ladder (the mental model):

  • Builtin widgets — the trusted "native" tier. First-party, full-capability: preview, charts, log-tail, approvals, status. Maintainers + community contribute these via a small widget SDK.
  • Custom widgets — the sandboxed tier. Agent/user-authored, safe by construction (iframe sandbox="allow-scripts", opaque origin, CSP connect-src 'none', approval-gated). Games, bespoke tools, one-off apps — built live by the agent.
  • Write-back — the multiplier. A jailed, approval-gated setState turns both tiers from views into stateful apps: games persist, editors save, a full-bleed tab becomes a real web app.

Custom-widget lifecycle — why agent-authored code is safe to run

sequenceDiagram
  participant Agent as 🤖 Agent
  participant Store as DashboardStore
  participant Op as 👤 Operator
  participant Route as Static route (auth:"plugin")
  participant Frame as Sandboxed iframe
  Agent->>Store: dashboard_widget_scaffold (manifest + index.html)
  Store-->>Op: registry status:"pending" — placeholder card, NO iframe exists yet
  Op->>Store: Approve (routes through plugin-approvals)
  Store-->>Route: status:"approved" → assets now served (pending/rejected → 404 server-side)
  Route-->>Frame: index.html + locked CSP (connect-src 'none')
  Frame->>UI: request only the bindings its manifest declared
  Note over Frame,UI: no origin · no network · no gateway line —<br/>the trusted parent resolves bindings on the widget's behalf
Loading

🔍 Review & test the whole package

① The one maintainer action that unblocks everything. Every PR shows a single red check — dependency-guard — and most CI is skipping ("Authorize workflow actor"). Both clear with one touch: approve the fork-PR CI workflow + authorize the dependency-graph head SHA. No new package enters the graph (only the new plugin's own lockfile importer entry). That also runs the full e2e/integration lane = the runtime "real behavior proof."

② Merge order — bottom-up. All PRs are a cumulative stack on the still-unmerged Wave-1 base, so every one (including the base) shows GitHub CONFLICTING against main — this is uniform and expected, not a per-PR defect. Merge the foundation first (#101094#101097#101098; rebase the base once on latest main to clear the conflict), then each feature PR's conflict clears as its parent lands.

③ Run the whole feature in one branch. The base PR #101098 sits at the top of the foundation stack, so checking it out gives you the complete substrate in one branch:

gh pr checkout 101098
pnpm install && pnpm build
# start the gateway with the dashboard plugin enabled → open Control UI → the "Workspaces" tab

The default Overview-as-data workspace renders on first load. Then use the per-PR test map below.

④ Per-PR test map — how to exercise each feature.

Feature (PR) Do this Observe
Foundation #101094·#101097·#101098 Open Workspaces tab; openclaw dashboard tabs create --title Financials; ask an agent to scaffold a custom widget; Approve the pending card Tab appears live; sandboxed iframe mounts only after approval; pending/rejected assets 404
Write-back #101329 Approve a stateful custom widget (e.g. the notes starter); type + reload State persists via the jailed setState; a stale expectedVersion write is rejected
Preview #101354 Add a builtin:preview widget bound to a URL; reload Live embed renders in the sandbox; reload/verify controls work
Charts + SDK #101792 Add a builtin:chart widget (type: line/bar/area/sparkline/gauge) bound to a series Dependency-free SVG chart; follow docs/plugins/dashboard-widget-authoring.md to author your own
Ops widgets #101826 Add the agent-status + approvals builtins Live agent activity; approvals queue actionable from the dashboard
Notes #101829 Add the notes builtin; edit; reload Stateful markdown/todo persists (rides write-back)
Pub/sub #101841 Put two widgets on a tab that publish/subscribe Messages cross widgets only within the tab; other tabs isolated
Binding kinds #101878 Bind a widget with stream: / computed: Streams from the allowlisted gateway events; computed = fixed op set, no eval
Living Answers + action-form #101899 Add a builtin:action-form (template: "Deploy {env}"); submit Dispatches through the same operator-confirm + rate gate; ephemeral answer-widgets self-sweep; Pin makes permanent
Time-travel + blame #101900 Open workspace history; scrub versions; click a widget's provenance Version diff renders; provenance deep-links to the logbook session
Apps layer #101908 Toggle a tab to full-bleed; open the widget gallery Full-bleed "tab app"; gallery install routes through the L5 approval gate
Control hub #101913 With ≥2 agents/operators: group by agent; mark a tab private; watch presence Per-agent nesting; private tab is absent server-side from a non-owner's payload; live presence
Distribution #101919 Share a ?ws=<slug> link; Export then Import a workspace Deep link resolves + back/forward works; imported custom widgets land pending (never auto-approved)

Security-sensitive features each carry an in-PR adversarial review (sandbox breakout, pub/sub cross-tab isolation, binding stream-allowlist/no-eval, action-form no-injection, gallery no-SSRF, private-tab server-side visibility, import→approval gate).


✅ Why it's correct — the integration-seam story

Wave 1's UI and gateway were each unit-tested against the other's mock and never wired together, so three contract-drift bugs shipped green: file bindings threw, every human widget mutation (drag/collapse/hide/remove/pin) was dead against the real gateway, and the initial load rendered an empty dashboard. All three are fixed at the base with wire-contract regression tests + a new integration test (ui-gateway-integration.test.ts) that drives the real client → real gateway handlers → real store and fails on any regression. The dashboard now actually works against the real gateway — the mock-gateway demo had hidden that it didn't.


Roadmap & PR map

Wave 1 (foundation): #101094 (backend: workspace.json document + control plane) · #101097 (Workspaces UI + 9 builtins + Overview-as-data) · #101098 (sandboxed custom widgets + approval flow; also carries the 3 seam fixes + the integration test).

Flagship — the chat↔dashboard loop

Tier 0 — multipliers

Tier 1 — high-value builtins ("native" tier)

Tier 2 — the apps layer ("every tab is a web app")

Tier 3 — control-hub / multi-user

Tier 4 — distribution

Tracked on the desktop/native track (not this web-ui series)

How this stays lean

The agent never touches ui/ source — it composes dashboards only through the same validated gateway / CLI / agent-tool control plane a human uses. Every widget is either a first-party builtin or a sandboxed custom widget. Core stays a bundled plugin plus one Control-UI tab.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Normal backlog priority with limited blast radius.clawsweeper:needs-maintainer-reviewClawSweeper marked this issue as needing maintainer review before automation.clawsweeper:needs-product-decisionClawSweeper marked this issue as needing a product or behavior decision.clawsweeper:needs-security-reviewClawSweeper marked this issue as needing security-sensitive review.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.impact:securitySecurity boundary, credential, authz, sandbox, or sensitive-data risk.impact:ux-frictionUser-facing flow adds avoidable confusion or support burden without fully blocking progress.issue-rating: 🌊 off-meta tidepoolIssue quality rating does not apply to this item.maturity:stableIssue affects a taxonomy feature currently scored M4/M5.staleMarked as stale due to inactivity

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions