From 7be5d78fd09a90ca37290f2b2a99c51b1b3043e6 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 16 Jul 2026 21:56:57 -0700 Subject: [PATCH] feat(workspaces): add full-bleed single-widget tabs (#109627) * feat(workspaces): add full-bleed app tabs * fix(workspaces): harden full-bleed rendering * fix(workspaces): eliminate full-bleed iframe gap * fix(workspaces): keep tab layout type internal --------- Co-authored-by: Eva --- docs/web/workspaces.md | 6 + extensions/workspaces/src/cli.ts | 15 +++ extensions/workspaces/src/gateway.ts | 11 +- extensions/workspaces/src/schema.test.ts | 18 +++ extensions/workspaces/src/schema.ts | 16 ++- extensions/workspaces/src/tools.test.ts | 2 +- extensions/workspaces/src/tools.ts | 14 +- ui/src/components/workspace-custom-widget.ts | 2 +- ui/src/components/workspace-widget-cell.ts | 6 +- ui/src/e2e/workspace-full-bleed.e2e.test.ts | 135 +++++++++++++++++++ ui/src/lib/workspace/index.ts | 1 + ui/src/lib/workspace/types.ts | 2 + ui/src/pages/plugin/workspace-view.test.ts | 13 ++ ui/src/pages/plugin/workspace-view.ts | 83 +++++++++--- ui/src/styles/workspace.css | 15 +++ 15 files changed, 308 insertions(+), 31 deletions(-) create mode 100644 ui/src/e2e/workspace-full-bleed.e2e.test.ts diff --git a/docs/web/workspaces.md b/docs/web/workspaces.md index 57f1e4e1b145..1cfcaa372957 100644 --- a/docs/web/workspaces.md +++ b/docs/web/workspaces.md @@ -65,6 +65,11 @@ lists only pending entries from the Workspaces custom-widget registry. Its Appro Reject controls are disabled unless the current connection holds `operator.approvals`; it does not expose exec, plugin, or system-agent approvals. +Tabs use the 12-column widget grid by default. A tab containing zero or one widget can +instead use the `full` layout, which removes the widget card chrome and lets the widget +fill the tab. Switch layouts with `openclaw workspaces tabs full ` and +`openclaw workspaces tabs grid `. + Widgets declare data through **bindings**, they never fetch on their own: | Binding | Resolves to | @@ -110,6 +115,7 @@ per-invocation confirmation quoting the exact text, and passes a rate limit. ```sh openclaw workspaces tabs list openclaw workspaces tabs create --title Financials +openclaw workspaces tabs full financials openclaw workspaces widget-scaffold revenue-chart --title "Revenue Chart" openclaw workspaces widget-approve revenue-chart ``` diff --git a/extensions/workspaces/src/cli.ts b/extensions/workspaces/src/cli.ts index c11fb256c997..c9b6cce5abd3 100644 --- a/extensions/workspaces/src/cli.ts +++ b/extensions/workspaces/src/cli.ts @@ -287,6 +287,21 @@ export function registerWorkspaceCli(options: RegisterWorkspaceCliOptions): void }); } + for (const [verb, tabLayout] of [ + ["grid", "grid"], + ["full", "full"], + ] as const) { + addGatewayOptions( + tabs.command(verb).argument("", "Tab slug").description(`Use ${tabLayout} layout`), + ).action(async (slug: string, commandOptions: GatewayOptions) => { + const result = await callWorkspaceGateway("workspaces.tab.update", commandOptions, { + slug, + patch: { layout: tabLayout }, + }); + writeTabs(readWorkspaceResult(result).doc, commandOptions); + }); + } + addGatewayOptions( widgets .command("list") diff --git a/extensions/workspaces/src/gateway.ts b/extensions/workspaces/src/gateway.ts index 032f0c5784bf..0affa054f25e 100644 --- a/extensions/workspaces/src/gateway.ts +++ b/extensions/workspaces/src/gateway.ts @@ -276,8 +276,10 @@ function readRequiredBoolean(record: Record, key: string): bool return value; } -function readTabPatch(value: unknown): Partial> { - const patch = readParams(value, ["title", "icon", "hidden"]); +function readTabPatch( + value: unknown, +): Partial> { + const patch = readParams(value, ["title", "icon", "hidden", "layout"]); const title = readOptionalString(patch, "title"); if (title !== undefined && (title.length < 1 || title.length > 80)) { throw new Error("patch.title must be 1-80 characters"); @@ -287,10 +289,15 @@ function readTabPatch(value: unknown): Partial