fix(ui): preserve pinned widget identity (#123721)

Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
ClawSweeper
2026-08-14 10:44:55 -07:00
committed by GitHub
parent 0cd18e1fb9
commit eab9cf5ea4
3 changed files with 39 additions and 7 deletions
+7 -4
View File
@@ -108,6 +108,10 @@ function normalizePreferredHeight(value: number | undefined): number | undefined
: undefined;
}
export function isCanvasBoardWidgetName(value: unknown): value is string {
return typeof value === "string" && /^[a-z0-9][a-z0-9._-]{0,63}$/u.test(value);
}
function coerceCanvasPreview(
record: Record<string, unknown> | undefined,
): CanvasPreview | undefined {
@@ -145,10 +149,9 @@ function coerceCanvasPreview(
const viewUrl = getRecordStringField(view, "url") ?? getRecordStringField(view, "entryUrl");
const viewId = getRecordStringField(view, "id") ?? getRecordStringField(view, "docId");
const requestedBoardWidgetName = getRecordStringField(view, "boardWidgetName");
const boardWidgetName =
requestedBoardWidgetName && /^[a-z0-9][a-z0-9._-]{0,63}$/u.test(requestedBoardWidgetName)
? requestedBoardWidgetName
: undefined;
const boardWidgetName = isCanvasBoardWidgetName(requestedBoardWidgetName)
? requestedBoardWidgetName
: undefined;
if (mcpAppViewId && viewId === mcpAppViewId) {
return {
kind: "canvas",
+24 -2
View File
@@ -347,7 +347,7 @@ describe("message-normalizer", () => {
]);
});
it("preserves a canvas preview sandbox ceiling from history", () => {
it("preserves canvas dashboard identity and sandbox ceiling from history", () => {
const result = normalizeMessage({
role: "assistant",
content: [
@@ -359,6 +359,7 @@ describe("message-normalizer", () => {
render: "url",
url: "/__openclaw__/canvas/documents/cv_widget/index.html",
sandbox: "scripts",
boardWidgetName: "release-status",
},
},
],
@@ -366,10 +367,31 @@ describe("message-normalizer", () => {
expect(result.content[0]).toMatchObject({
type: "canvas",
preview: { sandbox: "scripts" },
preview: { sandbox: "scripts", boardWidgetName: "release-status" },
});
});
it("drops invalid canvas dashboard identity from history", () => {
const result = normalizeMessage({
role: "assistant",
content: [
{
type: "canvas",
preview: {
kind: "canvas",
surface: "assistant_message",
render: "url",
url: "/__openclaw__/canvas/documents/cv_widget/index.html",
boardWidgetName: "Invalid widget name",
},
},
],
});
expect(result.content[0]).toMatchObject({ type: "canvas" });
expect(result.content[0]).not.toHaveProperty("preview.boardWidgetName");
});
it("ignores [embed] shortcodes inside fenced code blocks", () => {
const result = normalizeMessage({
role: "assistant",
+8 -1
View File
@@ -5,7 +5,10 @@
import { mediaKindFromMime } from "@openclaw/media-core/constants";
import { asRecord as asMessageRecord, isRecord } from "@openclaw/normalization-core/record-coerce";
import { stripInboundMetadata } from "../../../../src/auto-reply/reply/strip-inbound-meta.js";
import { extractCanvasShortcodes } from "../../../../src/chat/canvas-render.js";
import {
extractCanvasShortcodes,
isCanvasBoardWidgetName,
} from "../../../../src/chat/canvas-render.js";
import {
isToolCallContentType,
isToolResultContentType,
@@ -111,6 +114,9 @@ function coerceCanvasPreview(
return null;
}
const mcpApp = isRecord(preview.mcpApp) ? preview.mcpApp : undefined;
const boardWidgetName = isCanvasBoardWidgetName(preview.boardWidgetName)
? preview.boardWidgetName
: undefined;
return {
kind: "canvas",
surface: "assistant_message",
@@ -126,6 +132,7 @@ function coerceCanvasPreview(
...(preview.sandbox === "strict" || preview.sandbox === "scripts"
? { sandbox: preview.sandbox }
: {}),
...(boardWidgetName ? { boardWidgetName } : {}),
...(typeof mcpApp?.viewId === "string" && mcpApp.viewId.trim()
? {
mcpApp: {