@grackle-ai/web-components 0.116.0 → 0.118.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/.rush/temp/{f238c174d295031bec7f186733732e0fd7e4b9a5.tar.log → 54cccf3e758698925ef11c80c60777421e1be435.tar.log} +14 -13
  2. package/.rush/temp/{f238c174d295031bec7f186733732e0fd7e4b9a5.untar.log → 54cccf3e758698925ef11c80c60777421e1be435.untar.log} +2 -2
  3. package/.rush/temp/chunked-rush-logs/web-components._phase_build.chunks.jsonl +11 -4
  4. package/.rush/temp/chunked-rush-logs/web-components._phase_test.chunks.jsonl +20 -19
  5. package/.rush/temp/{f5301e6a84109dcec06242d178df01e555a83456.tar.log → e5461b19665f030d269f07cf8797f2bcfeac1bf7.tar.log} +2 -2
  6. package/.rush/temp/{f5301e6a84109dcec06242d178df01e555a83456.untar.log → e5461b19665f030d269f07cf8797f2bcfeac1bf7.untar.log} +2 -2
  7. package/.rush/temp/operation/_phase_build/all.log +11 -4
  8. package/.rush/temp/operation/_phase_build/log-chunks.jsonl +11 -4
  9. package/.rush/temp/operation/_phase_build/state.json +1 -1
  10. package/.rush/temp/operation/_phase_test/all.log +20 -19
  11. package/.rush/temp/operation/_phase_test/log-chunks.jsonl +20 -19
  12. package/.rush/temp/operation/_phase_test/state.json +1 -1
  13. package/.rush/temp/shrinkwrap-deps.json +15 -3
  14. package/README.md +1 -1
  15. package/config/rush-project.json +1 -1
  16. package/dist/index.js +3289 -3276
  17. package/package.json +4 -2
  18. package/rush-logs/web-components._phase_build.cache.log +2 -2
  19. package/rush-logs/web-components._phase_build.log +11 -4
  20. package/rush-logs/web-components._phase_test.cache.log +1 -1
  21. package/rush-logs/web-components._phase_test.log +20 -19
  22. package/src/components/display/EventRenderer.stories.tsx +22 -0
  23. package/src/components/display/EventRenderer.tsx +28 -4
  24. package/src/mcp-runtime/index.tsx +99 -0
  25. package/temp/build/lint/_eslint-5eVG3S6w.json +7 -3
  26. package/vite.config.ts +46 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grackle-ai/web-components",
3
- "version": "0.116.0",
3
+ "version": "0.118.0",
4
4
  "description": "Presentational React component library for the Grackle web UI",
5
5
  "license": "MIT",
6
6
  "sideEffects": [
@@ -41,12 +41,13 @@
41
41
  "d3-zoom": "^3.0.0",
42
42
  "react": "^19.0.0",
43
43
  "react-dom": "^19.0.0",
44
+ "react-live": "^4.1.8",
44
45
  "react-markdown": "^9.0.3",
45
46
  "rehype-prism-plus": "^2.0.0",
46
47
  "remark-gfm": "^4.0.0",
47
48
  "lucide-react": "~0.474.0",
48
49
  "react-router": "^7.0.0",
49
- "@grackle-ai/common": "0.116.0"
50
+ "@grackle-ai/common": "0.118.0"
50
51
  },
51
52
  "devDependencies": {
52
53
  "@rushstack/heft": "1.2.7",
@@ -60,6 +61,7 @@
60
61
  "@types/react-dom": "^19.0.0",
61
62
  "sass": "^1.86.0",
62
63
  "vite": "^6.4.2",
64
+ "vite-plugin-css-injected-by-js": "^4.0.1",
63
65
  "vitest": "^3.1.1",
64
66
  "@testing-library/react": "^16.3.2",
65
67
  "jsdom": "^29.0.0",
@@ -1,4 +1,4 @@
1
1
  Build cache hit.
2
- Cache key: f238c174d295031bec7f186733732e0fd7e4b9a5
3
- Clearing cached folders: dist, storybook-static, .rush/temp/operation/_phase_build
2
+ Cache key: 54cccf3e758698925ef11c80c60777421e1be435
3
+ Clearing cached folders: dist, storybook-static, mcp-app-runtime, .rush/temp/operation/_phase_build
4
4
  Successfully restored output from the build cache.
@@ -12,8 +12,15 @@ rendering chunks...
12
12
  computing gzip size...
13
13
  dist/index.css 155.14 kB │ gzip: 20.34 kB
14
14
  dist/McpAppWidget-CSX2W2Vb.js 205.28 kB │ gzip: 47.19 kB
15
- dist/index.js 1,374.93 kB │ gzip: 352.80 kB
16
- ✓ built in 5.75s
15
+ dist/index.js 1,375.56 kB │ gzip: 353.02 kB
16
+ ✓ built in 5.83s
17
+ vite v6.4.2 building for production...
18
+ transforming...
19
+ ✓ 2870 modules transformed.
20
+ rendering chunks...
21
+ computing gzip size...
22
+ mcp-app-runtime/runtime.js 1,243.84 kB │ gzip: 267.73 kB
23
+ ✓ built in 4.99s
17
24
  [build:vite-build] Vite build completed.
18
- ---- build finished (50.985s) ----
19
- -------------------- Finished (50.988s) --------------------
25
+ ---- build finished (85.920s) ----
26
+ -------------------- Finished (85.925s) --------------------
@@ -1,4 +1,4 @@
1
1
  Build cache hit.
2
- Cache key: f5301e6a84109dcec06242d178df01e555a83456
2
+ Cache key: e5461b19665f030d269f07cf8797f2bcfeac1bf7
3
3
  Clearing cached folders: .rush/temp/operation/_phase_test
4
4
  Successfully restored output from the build cache.
@@ -6,29 +6,30 @@ The provided list of phases does not contain all phase dependencies. You may nee
6
6
  RUN v3.2.4 /home/runner/work/grackle/grackle/packages/web-components
7
7
 
8
8
  ✓ src/utils/sessionEvents.test.ts (14 tests) 46ms
9
- ✓ src/utils/eventContent.test.ts (38 tests) 129ms
10
- ✓ src/utils/dashboard.test.ts (4 tests) 9ms
11
- ✓ src/utils/streamCoordination.test.ts (10 tests) 20ms
12
- ✓ src/utils/route-config.test.ts (16 tests) 27ms
13
- ✓ src/utils/breadcrumbs.test.ts (15 tests) 15ms
14
- ✓ src/utils/scrollUtils.test.ts (11 tests) 21ms
15
- ✓ src/components/tools/classifyTool.test.ts (6 tests) 11ms
16
- ✓ src/components/tools/toolCardHelpers.test.ts (10 tests) 25ms
17
- ✓ src/utils/assetUrl.test.ts (3 tests) 14ms
18
- ✓ src/components/display/extractText.test.tsx (8 tests) 22ms
19
- ✓ src/hooks/useEventSelection.test.ts (13 tests) 180ms
9
+ ✓ src/utils/eventContent.test.ts (38 tests) 65ms
10
+ ✓ src/utils/dashboard.test.ts (4 tests) 14ms
11
+ ✓ src/utils/streamCoordination.test.ts (10 tests) 34ms
12
+ ✓ src/utils/route-config.test.ts (16 tests) 38ms
13
+ ✓ src/utils/breadcrumbs.test.ts (15 tests) 90ms
14
+ ✓ src/utils/scrollUtils.test.ts (11 tests) 31ms
15
+ ✓ src/components/tools/classifyTool.test.ts (6 tests) 15ms
16
+ ✓ src/utils/assetUrl.test.ts (3 tests) 27ms
17
+ ✓ src/components/tools/toolCardHelpers.test.ts (10 tests) 19ms
18
+ ✓ src/components/display/extractText.test.tsx (8 tests) 12ms
20
19
  ✓ src/components/editable/useEditableField.test.tsx (17 tests) 159ms
21
- ✓ src/components/notifications/UpdateBanner.test.tsx (4 tests) 95ms
22
- ✓ src/utils/grackleHostStyleVariables.test.ts (2 tests) 195ms
23
- ✓ src/components/display/McpAppWidget.test.tsx (3 tests) 361ms
20
+ ✓ src/hooks/useEventSelection.test.ts (13 tests) 271ms
21
+ ✓ src/components/display/McpAppWidget.test.tsx (3 tests) 312ms
22
+ ✓ src/components/notifications/UpdateBanner.test.tsx (4 tests) 217ms
23
+ ✓ src/utils/grackleHostStyleVariables.test.ts (2 tests) 337ms
24
+ ✓ grackleHostStyleVariables > always returns the MCP-standard fallback variables 327ms
24
25
 
25
26
  Test Files 16 passed (16)
26
27
  Tests 174 passed (174)
27
- Start at 23:49:50
28
- Duration 15.38s (transform 2.97s, setup 0ms, collect 14.70s, tests 1.33s, environment 12.10s, prepare 5.21s)
28
+ Start at 14:54:51
29
+ Duration 15.79s (transform 2.65s, setup 0ms, collect 17.82s, tests 1.69s, environment 12.03s, prepare 5.23s)
29
30
 
30
31
  [test:vitest] Vitest completed.
31
- [test:storybook-test] Starting Storybook static server on port 45217...
32
+ [test:storybook-test] Starting Storybook static server on port 46639...
32
33
  [test:storybook-test] Storybook server ready. Running interaction tests...
33
34
  jest-haste-map: duplicate manual mock found: adapter-manager
34
35
  The following files share their name; please delete one of them:
@@ -121,5 +122,5 @@ jest-haste-map: duplicate manual mock found: utils/network
121
122
  * <rootDir>/packages/server/src/__mocks__/utils/network.ts
122
123
 
123
124
  [test:storybook-test] Storybook interaction tests completed.
124
- ---- test finished (74.932s) ----
125
- -------------------- Finished (74.942s) --------------------
125
+ ---- test finished (114.090s) ----
126
+ -------------------- Finished (114.096s) --------------------
@@ -216,6 +216,28 @@ export const AgentWidgetEvent: Story = {
216
216
  },
217
217
  };
218
218
 
219
+ /** GenUX React runtime (#1268): rendererKind "grackle-react" — `html` is JSX source. */
220
+ export const ReactRuntimeWidgetEvent: Story = {
221
+ args: {
222
+ event: makeEvent({
223
+ eventType: "widget",
224
+ content: JSON.stringify({
225
+ resourceUri: "",
226
+ toolName: "component_show",
227
+ rendererKind: "grackle-react",
228
+ html: "render(<Button>{props.label}</Button>)",
229
+ csp: { resourceDomains: ["http://localhost:6007"], connectDomains: ["http://localhost:6007"], allowUnsafeEval: true },
230
+ toolInput: { label: "Hi" },
231
+ }),
232
+ }),
233
+ sandboxProxyUrl: "http://localhost:6007/sandbox.html",
234
+ },
235
+ play: async ({ canvas }) => {
236
+ // The grackle-react branch builds a runtime bootstrap and mounts the host iframe.
237
+ await expect(await canvas.findByTestId("mcp-app-widget")).toBeInTheDocument();
238
+ },
239
+ };
240
+
219
241
  /** An unknown rendererKind falls back to the default event card (no crash). */
220
242
  export const UnknownRendererKindWidget: Story = {
221
243
  args: {
@@ -226,9 +226,10 @@ export function EventRenderer({ event, toolUseCtx, settled, sandboxProxyUrl }: P
226
226
  let payload: {
227
227
  html?: string;
228
228
  rendererKind?: string;
229
- // `allowInlineScripts` is a Grackle extension to the upstream CSP type
230
- // (agent-authored widgets, #1239); it is forwarded verbatim to the sandbox.
231
- csp?: McpUiResourceCsp & { allowInlineScripts?: boolean };
229
+ // `allowInlineScripts`/`allowUnsafeEval` are Grackle extensions to the
230
+ // upstream CSP type (agent-authored widgets #1239; React runtime #1268);
231
+ // forwarded verbatim to the sandbox.
232
+ csp?: McpUiResourceCsp & { allowInlineScripts?: boolean; allowUnsafeEval?: boolean };
232
233
  toolInput?: Record<string, unknown>;
233
234
  toolResult?: CallToolResult;
234
235
  } = {};
@@ -236,8 +237,31 @@ export function EventRenderer({ event, toolUseCtx, settled, sandboxProxyUrl }: P
236
237
  payload = JSON.parse(event.content) as typeof payload;
237
238
  } catch { /* malformed widget payload — fall back */ }
238
239
  // Dispatch on rendererKind (default "mcp-app-html" for back-compat). This
239
- // switch is the seam for future declarative renderers (e.g. adaptive-card).
240
+ // switch is the seam for declarative/runtime renderers.
240
241
  const rendererKind: string = payload.rendererKind ?? "mcp-app-html";
242
+ // GenUX React runtime (#1268): payload.html is JSX *source* (not a full
243
+ // document). Render it via a bootstrap that loads the runtime bundle from the
244
+ // sandbox origin; the source + props are delivered as tool input. The runtime
245
+ // transpiles + renders the component against the Grackle component library.
246
+ // An absolute runtime.js URL is required — the inner iframe is written via
247
+ // doc.write (about:blank base), so a relative "/runtime.js" would not resolve.
248
+ if (rendererKind === "grackle-react" && payload.html) {
249
+ const sandboxOrigin: string = new URL(sandboxProxyUrl, window.location.href).origin;
250
+ const bootstrap: string =
251
+ `<!doctype html><html><head><meta charset="utf-8"></head>` +
252
+ `<body><div id="grackle-root"></div>` +
253
+ `<script type="module" src="${sandboxOrigin}/runtime.js"></script></body></html>`;
254
+ return (
255
+ <Suspense fallback={<DefaultEvent content="Loading widget..." />}>
256
+ <McpAppWidget
257
+ widgetHtml={bootstrap}
258
+ sandboxProxyUrl={sandboxProxyUrl}
259
+ csp={payload.csp}
260
+ toolInput={{ source: payload.html, props: payload.toolInput ?? {} }}
261
+ />
262
+ </Suspense>
263
+ );
264
+ }
241
265
  if (rendererKind !== "mcp-app-html" || !payload.html) {
242
266
  return <DefaultEvent content={event.content} />;
243
267
  }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Grackle React runtime (#1268) — the single MCP Apps resource (`ui://grackle/runtime`)
3
+ * that renders agent-authored React/JSX inline in the chat.
4
+ *
5
+ * Bundled self-contained (React + react-live + the curated Grackle component set +
6
+ * the ext-apps guest bridge) by `vite.config.ts` into `mcp-app-runtime/runtime.js`
7
+ * and served from the sandbox origin. The host (`McpAppWidget`) writes a tiny
8
+ * bootstrap that loads this script, then delivers `{ source, props }` as MCP Apps
9
+ * tool input. The runtime transpiles + evaluates the JSX via react-live against the
10
+ * component scope and paints it. This is the load-bearing slice of the GenUX
11
+ * component-registry vision (render-by-source; the registry comes in later phases).
12
+ */
13
+ import { useState, type JSX } from "react";
14
+ import * as React from "react";
15
+ import { createRoot } from "react-dom/client";
16
+ import { LiveProvider, LivePreview, LiveError } from "react-live";
17
+ import { useApp, useHostStyleVariables } from "@modelcontextprotocol/ext-apps/react";
18
+ import {
19
+ Button, SplitButton, Callout, Spinner,
20
+ Skeleton, SkeletonText, SkeletonCard, Tooltip, CopyButton,
21
+ } from "../index.js";
22
+
23
+ /**
24
+ * Curated, context-free presentational components exposed to agent JSX. Phase 0
25
+ * keeps this a small, safe set — components that require router or `useGrackle()`
26
+ * context are intentionally excluded. The full, discoverable catalog arrives in a
27
+ * later phase (#1265).
28
+ */
29
+ const COMPONENT_SCOPE: Readonly<Record<string, unknown>> = {
30
+ Button, SplitButton, Callout, Spinner,
31
+ Skeleton, SkeletonText, SkeletonCard, Tooltip, CopyButton,
32
+ };
33
+
34
+ /** Identity reported to the host during the MCP Apps guest handshake. */
35
+ const APP_INFO: Readonly<{ name: string; version: string }> = {
36
+ name: "GrackleReactRuntime",
37
+ version: "0.1.0",
38
+ };
39
+
40
+ /** The agent-supplied render request delivered via MCP Apps tool input. */
41
+ interface RenderInput {
42
+ /** JSX source; must call `render(<Component {...props}/>)` (react-live noInline). */
43
+ source: string;
44
+ /** Data bound into the component, available as `props` in the JSX scope. */
45
+ props: Record<string, unknown>;
46
+ }
47
+
48
+ /** Connects the guest bridge, receives the JSX + props, and renders via react-live. */
49
+ function Runtime(): JSX.Element {
50
+ const [input, setInput] = useState<RenderInput | undefined>(undefined);
51
+
52
+ const { app, error } = useApp({
53
+ appInfo: APP_INFO,
54
+ capabilities: {},
55
+ onAppCreated: (createdApp): void => {
56
+ createdApp.ontoolinput = (params): void => {
57
+ const args = (params.arguments ?? {}) as { source?: unknown; props?: unknown };
58
+ setInput({
59
+ source: typeof args.source === "string" ? args.source : "",
60
+ props:
61
+ args.props !== null && typeof args.props === "object"
62
+ ? (args.props as Record<string, unknown>)
63
+ : {},
64
+ });
65
+ };
66
+ },
67
+ });
68
+
69
+ // Mirror the host theme + style tokens onto this document (light-dark + CSS vars).
70
+ useHostStyleVariables(app, app?.getHostContext());
71
+
72
+ if (error) {
73
+ return <div style={{ color: "var(--color-text-danger, #c00)" }}>Runtime error: {error.message}</div>;
74
+ }
75
+ if (!input) {
76
+ return <div style={{ opacity: 0.6, padding: "0.5rem" }}>Loading component…</div>;
77
+ }
78
+
79
+ // react-live transpiles the JSX (sucrase) and evaluates it with `new Function`
80
+ // (requires `script-src 'unsafe-eval'`, set on this resource's sandbox CSP). The
81
+ // origin-isolated sandbox + restricted connect-src keep that safe (#1268).
82
+ const scope: Record<string, unknown> = { React, props: input.props, ...COMPONENT_SCOPE };
83
+ return (
84
+ <LiveProvider code={input.source} noInline scope={scope}>
85
+ <LivePreview />
86
+ <LiveError />
87
+ </LiveProvider>
88
+ );
89
+ }
90
+
91
+ /** Mount the runtime into the bootstrap's root element. */
92
+ function main(): void {
93
+ const rootEl: HTMLElement | null = document.getElementById("grackle-root");
94
+ if (rootEl) {
95
+ createRoot(rootEl).render(<Runtime />);
96
+ }
97
+ }
98
+
99
+ main();
@@ -151,7 +151,7 @@
151
151
  ],
152
152
  [
153
153
  "components/display/EventRenderer.tsx",
154
- "gezM0fuun/x3AxO8ftjhNIYySUI=_7W3TvvwZxl0TGST6/dyQa8DKDDc="
154
+ "B+twjFgJO8XtuVnM2EFI4ajaFm0=_7W3TvvwZxl0TGST6/dyQa8DKDDc="
155
155
  ],
156
156
  [
157
157
  "components/display/ConfirmDialog.tsx",
@@ -519,7 +519,7 @@
519
519
  ],
520
520
  [
521
521
  "components/display/EventRenderer.stories.tsx",
522
- "IHG2XMzYHwhC8ZSq4jhuqyExkIM=_7W3TvvwZxl0TGST6/dyQa8DKDDc="
522
+ "G6j6nQxaMUuRqBcEnlmV5Osc/Zw=_7W3TvvwZxl0TGST6/dyQa8DKDDc="
523
523
  ],
524
524
  [
525
525
  "components/display/EventStream.stories.tsx",
@@ -785,6 +785,10 @@
785
785
  "hooks/useEventSelection.test.ts",
786
786
  "/ggsjovG/PzoHNtCbMzOcwYEa/k=_orHdc0vDZqoYfD6TIDl1Za3EAL4="
787
787
  ],
788
+ [
789
+ "mcp-runtime/index.tsx",
790
+ "mzEV0k70r0bzk8cfyPwZgUtu9YU=_7W3TvvwZxl0TGST6/dyQa8DKDDc="
791
+ ],
788
792
  [
789
793
  "utils/assetUrl.test.ts",
790
794
  "IaZp6S9n0W2AQtaTJgpZV3+L9sQ=_orHdc0vDZqoYfD6TIDl1Za3EAL4="
@@ -822,5 +826,5 @@
822
826
  "tcFLEiIFvYYLO/1jT41tcJ4CIX4=_orHdc0vDZqoYfD6TIDl1Za3EAL4="
823
827
  ]
824
828
  ],
825
- "filesHash": "fdzO9hSTLfwPLjMg24UcAg"
829
+ "filesHash": "DNaGWodZdPGqzAPxzYjKFQ"
826
830
  }
package/vite.config.ts CHANGED
@@ -1,9 +1,53 @@
1
- import { defineConfig } from "vite";
1
+ import { defineConfig, build, type Plugin } from "vite";
2
2
  import react from "@vitejs/plugin-react";
3
+ import cssInjectedByJsPlugin from "vite-plugin-css-injected-by-js";
3
4
  import { resolve } from "node:path";
4
5
 
6
+ /**
7
+ * Second, self-contained build for the MCP Apps React runtime (#1268). Unlike the
8
+ * main library build (which externalizes React + `@grackle-ai/*` so the web SPA
9
+ * provides them), the runtime is a standalone bundle served from the sandbox
10
+ * origin: it bundles React, the curated Grackle component set, react-live, and the
11
+ * ext-apps guest bridge into one `mcp-app-runtime/runtime.js`, with CSS injected
12
+ * via JS (the sandbox loads a single script). Run from the main build's
13
+ * `closeBundle`; `configFile: false` keeps the nested build from re-loading this
14
+ * config (no recursion).
15
+ */
16
+ function buildRuntimeBundle(): Plugin {
17
+ return {
18
+ name: "grackle-build-mcp-runtime",
19
+ closeBundle: {
20
+ sequential: true,
21
+ async handler(): Promise<void> {
22
+ await build({
23
+ configFile: false,
24
+ root: __dirname,
25
+ define: { "process.env.NODE_ENV": JSON.stringify("production") },
26
+ plugins: [react(), cssInjectedByJsPlugin()],
27
+ build: {
28
+ outDir: resolve(__dirname, "mcp-app-runtime"),
29
+ emptyOutDir: true,
30
+ minify: true,
31
+ lib: {
32
+ entry: resolve(__dirname, "src/mcp-runtime/index.tsx"),
33
+ formats: ["es"],
34
+ fileName: () => "runtime.js",
35
+ },
36
+ // No `external`: bundle React + the component library into one file.
37
+ // `inlineDynamicImports` forces a SINGLE self-contained file — the
38
+ // sandbox server only serves `/runtime.js`, never split chunks.
39
+ rollupOptions: {
40
+ output: { inlineDynamicImports: true },
41
+ },
42
+ },
43
+ });
44
+ },
45
+ },
46
+ };
47
+ }
48
+
5
49
  export default defineConfig({
6
- plugins: [react()],
50
+ plugins: [react(), buildRuntimeBundle()],
7
51
  build: {
8
52
  lib: {
9
53
  entry: resolve(__dirname, "src/index.ts"),