File size: 4,392 Bytes
e18726e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// Canvas-render tests cover [embed] shortcode extraction and text stripping.
import { describe, expect, it } from "vitest";
import {
  extractCanvasFromDetails,
  extractCanvasFromText,
  extractCanvasShortcodes,
} from "./canvas-render.ts";

describe("extractCanvasFromText", () => {
  it("preserves a valid pinned board widget identity", () => {
    expect(
      extractCanvasFromText(
        JSON.stringify({
          kind: "canvas",
          view: {
            id: "cv_status",
            url: "/__openclaw__/canvas/documents/cv_status/index.html",
            boardWidgetName: "release-status",
          },
        }),
      ),
    ).toMatchObject({ viewId: "cv_status", boardWidgetName: "release-status" });
  });

  it("preserves node-panel presentation metadata", () => {
    expect(
      extractCanvasFromText(
        JSON.stringify({
          kind: "canvas",
          presentation: { target: "node_panel", title: "Status" },
          view: { id: "cv_status", url: "/__openclaw__/canvas/documents/cv_status/index.html" },
        }),
      ),
    ).toMatchObject({ surface: "node_panel", viewId: "cv_status", title: "Status" });
  });

  it("extracts safe MCP App preview metadata from tool details", () => {
    expect(
      extractCanvasFromDetails({
        mcpAppPreview: {
          kind: "canvas",
          view: { id: "cv_app" },
          presentation: { target: "assistant_message", sandbox: "scripts" },
          mcpApp: {
            viewId: "cv_app",
            serverName: "demo",
            toolName: "show",
            uiResourceUri: "ui://demo/app",
            toolCallId: "call-1",
            originSessionKey: "agent:main:main",
            resultMetaState: "unavailable",
          },
        },
      }),
    ).toMatchObject({
      viewId: "cv_app",
      mcpApp: {
        viewId: "cv_app",
        serverName: "demo",
        toolName: "show",
        uiResourceUri: "ui://demo/app",
        toolCallId: "call-1",
        originSessionKey: "agent:main:main",
        resultMetaState: "unavailable",
      },
    });
  });

  it("keeps MCP App previews opaque while preserving model-visible results", () => {
    const preview = extractCanvasFromText(
      JSON.stringify({
        kind: "canvas",
        view: { id: "cv_app" },
        presentation: { target: "assistant_message", sandbox: "scripts" },
        mcpApp: { viewId: "cv_app" },
        result: [{ type: "text", text: "model-visible result" }],
      }),
    );

    expect(preview).toMatchObject({
      viewId: "cv_app",
      sandbox: "scripts",
      mcpApp: { viewId: "cv_app" },
    });
  });
});

describe("extractCanvasShortcodes", () => {
  it("does not let a self-closing embed start a greedy block match", () => {
    // Regression: the block regex used to greedily swallow the span from a
    // self-closing "[embed ... /]" open tag up to a later stray "[/embed]",
    // deleting the visible text in between (" keep me ") from channel delivery.
    const input = '[embed url="https://a.com" /] keep me [/embed]';
    const { text, previews } = extractCanvasShortcodes(input);

    expect(previews).toHaveLength(1);
    expect(previews[0]?.url).toBe("https://a.com");
    // The visible text between the self-closing embed and the stray close
    // marker must be preserved, not silently stripped.
    expect(text).toContain("keep me");
    expect(text).toBe(" keep me [/embed]");
  });

  it("still extracts a normal block embed and strips only the shortcode span", () => {
    const input = 'before [embed ref="doc1"] hi [/embed] after';
    const { text, previews } = extractCanvasShortcodes(input);

    expect(previews).toHaveLength(1);
    expect(previews[0]?.viewId).toBe("doc1");
    expect(text).toBe("before  after");
  });

  it("still extracts a plain self-closing embed and keeps surrounding text", () => {
    const input = 'see [embed url="https://b.com" /] end';
    const { text, previews } = extractCanvasShortcodes(input);

    expect(previews).toHaveLength(1);
    expect(previews[0]?.url).toBe("https://b.com");
    expect(text).toBe("see  end");
  });
});

it("removes a shortcode without rewriting surrounding literal whitespace", () => {
  const source = '    a\n\n\n    b\n\n[embed ref="doc1" /]';
  expect(extractCanvasShortcodes(source)).toMatchObject({
    text: "    a\n\n\n    b\n\n",
    previews: [{ viewId: "doc1" }],
  });
});