fix(canvas): keep dark-theme widget frames transparent (#160940)

In the dark theme every inline show_widget frame showed an opaque slab
behind the widget, so its content looked flush against a box with no
padding. The sandbox proxy page between the Control UI and the widget
document declared no color-scheme, so Chromium painted an opaque canvas
wherever the light proxy sat between dark documents. The proxy now
adopts the host theme mode from the widget theme messages it already
forwards.

The visualize skill also told agents to inspect rendered widgets with
their own browser tools, which reach a sign-in page instead of the
viewer's Control UI. It now says so and points at the existing
post-render script error reports.
This commit is contained in:
Peter Steinberger 2026-09-28 21:52:00 -07:00 • committed by GitHub
parent 00c09b79fc
commit 06120a1ef9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 24 additions and 4 deletions

View file

@ -142,10 +142,14 @@ initialization and outside that host.
## Verify and deliver
Fix reported inline-script syntax errors and call the tool again. Inspect the
rendered result with available browser or device tools: verify libraries and
fonts loaded, important controls work, and content fits the intended width and
theme. For live dashboards, exercise the data read in the actual pinned frame.
Fix reported inline-script syntax errors and call the tool again. Script errors
thrown after an inline widget renders in the Control UI are reported back to this
session. Agent browser tools normally run a separate profile without the viewer's
Control UI session, so opening the chat there reaches sign-in, not the widget; do
not use them to inspect inline widgets. When a browser or device tool can load the
rendered surface itself, verify libraries and fonts loaded, important controls
work, and content fits the intended width and theme. For live dashboards, exercise
the data read in the actual pinned frame.
Strict embed mode disables scripts. Report any concrete visual or platform
verification gap; successful hosting alone proves neither rendering nor data access.

View file

@ -1,5 +1,6 @@
import { createHash } from "node:crypto";
import { asOptionalRecord as asRecord } from "@openclaw/normalization-core/record-coerce";
import { WIDGET_THEME_MESSAGE_TYPE } from "../shared/widget-theme.js";
export type SandboxHostCsp = {
connectDomains?: string[];
@ -316,6 +317,12 @@ function buildSandboxHostProxyHtml(csp?: SandboxHostCsp): string {
return;
}
if (typeof event.data?.method === "string" && event.data.method.startsWith("ui/notifications/sandbox-")) return;
// A frame whose root color-scheme differs from its embedding element gets
// an opaque UA canvas. Follow the host's widget theme mode so this shell
// and the themed widget document both stay transparent.
if (event.data?.type === ${JSON.stringify(WIDGET_THEME_MESSAGE_TYPE)} && (event.data.mode === "light" || event.data.mode === "dark")) {
document.documentElement.style.colorScheme = event.data.mode;
}
inner.contentWindow?.postMessage(event.data, "*");
return;
}

View file

@ -442,6 +442,15 @@ suite.define(() => {
inline.locator("html").evaluate((root) => getComputedStyle(root).colorScheme),
)
.toBe("dark");
// A light proxy between dark documents paints an opaque UA canvas.
await expect
.poll(() =>
outer
.contentFrame()
.locator("html")
.evaluate((root) => getComputedStyle(root).colorScheme),
)
.toBe("dark");
await expect
.poll(() =>
board.locator("html").evaluate((root) => getComputedStyle(root).colorScheme),