@seed-ship/mcp-ui-solid 5.5.1 → 5.7.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 (69) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/dist/components/FormRenderer.cjs +13 -2
  3. package/dist/components/FormRenderer.cjs.map +1 -1
  4. package/dist/components/FormRenderer.d.ts.map +1 -1
  5. package/dist/components/FormRenderer.js +13 -2
  6. package/dist/components/FormRenderer.js.map +1 -1
  7. package/dist/components/GenerativeUIErrorBoundary.cjs +11 -0
  8. package/dist/components/GenerativeUIErrorBoundary.cjs.map +1 -1
  9. package/dist/components/GenerativeUIErrorBoundary.d.ts.map +1 -1
  10. package/dist/components/GenerativeUIErrorBoundary.js +11 -0
  11. package/dist/components/GenerativeUIErrorBoundary.js.map +1 -1
  12. package/dist/components/StreamingUIRenderer.cjs +49 -3
  13. package/dist/components/StreamingUIRenderer.cjs.map +1 -1
  14. package/dist/components/StreamingUIRenderer.d.ts.map +1 -1
  15. package/dist/components/StreamingUIRenderer.js +51 -5
  16. package/dist/components/StreamingUIRenderer.js.map +1 -1
  17. package/dist/components/UIResourceRenderer.cjs +102 -14
  18. package/dist/components/UIResourceRenderer.cjs.map +1 -1
  19. package/dist/components/UIResourceRenderer.d.ts +36 -1
  20. package/dist/components/UIResourceRenderer.d.ts.map +1 -1
  21. package/dist/components/UIResourceRenderer.js +104 -16
  22. package/dist/components/UIResourceRenderer.js.map +1 -1
  23. package/dist/context/MCPUITelemetryContext.cjs +25 -0
  24. package/dist/context/MCPUITelemetryContext.cjs.map +1 -0
  25. package/dist/context/MCPUITelemetryContext.d.ts +36 -0
  26. package/dist/context/MCPUITelemetryContext.d.ts.map +1 -0
  27. package/dist/context/MCPUITelemetryContext.js +25 -0
  28. package/dist/context/MCPUITelemetryContext.js.map +1 -0
  29. package/dist/index.cjs +7 -0
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.d.cts +7 -0
  32. package/dist/index.d.ts +7 -0
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +8 -1
  35. package/dist/index.js.map +1 -1
  36. package/dist/mcp-ui-spec/dist/schemas.cjs +25 -6
  37. package/dist/mcp-ui-spec/dist/schemas.cjs.map +1 -1
  38. package/dist/mcp-ui-spec/dist/schemas.js +25 -6
  39. package/dist/mcp-ui-spec/dist/schemas.js.map +1 -1
  40. package/dist/services/telemetry.cjs +56 -0
  41. package/dist/services/telemetry.cjs.map +1 -0
  42. package/dist/services/telemetry.d.ts +87 -0
  43. package/dist/services/telemetry.d.ts.map +1 -0
  44. package/dist/services/telemetry.js +56 -0
  45. package/dist/services/telemetry.js.map +1 -0
  46. package/dist/services/validation.cjs +25 -24
  47. package/dist/services/validation.cjs.map +1 -1
  48. package/dist/services/validation.d.ts.map +1 -1
  49. package/dist/services/validation.js +26 -25
  50. package/dist/services/validation.js.map +1 -1
  51. package/dist/types/index.d.ts +26 -0
  52. package/dist/types/index.d.ts.map +1 -1
  53. package/dist/types.d.cts +26 -0
  54. package/dist/types.d.ts +26 -0
  55. package/docs/briefs/BRIEF-citations-in-table-cells.md +365 -0
  56. package/package.json +3 -3
  57. package/src/components/FormRenderer.tsx +14 -0
  58. package/src/components/GenerativeUIErrorBoundary.tsx +17 -1
  59. package/src/components/StreamingUIRenderer.tsx +55 -3
  60. package/src/components/TableRenderer.citation.test.tsx +157 -0
  61. package/src/components/UIResourceRenderer.tsx +212 -15
  62. package/src/context/MCPUITelemetryContext.test.tsx +119 -0
  63. package/src/context/MCPUITelemetryContext.tsx +71 -0
  64. package/src/index.ts +20 -0
  65. package/src/services/telemetry.test.ts +134 -0
  66. package/src/services/telemetry.ts +149 -0
  67. package/src/services/validation.ts +43 -41
  68. package/src/types/index.ts +30 -0
  69. package/tsconfig.tsbuildinfo +1 -1
@@ -57,7 +57,42 @@ export interface UIResourceRendererProps {
57
57
  * v4.3.8
58
58
  */
59
59
  export declare function highlightQuery(html: string, query: string): string;
60
- export declare function renderCellValue(value: any): string;
60
+ /**
61
+ * Citation context — opt-in input to `renderCellValue` (v5.7.0).
62
+ *
63
+ * When passed, `[N]`, `Citation [N]`, `[CITATION N]` and `[📄 CITATION N]`
64
+ * markers in the cell text are normalized then replaced with chip HTML.
65
+ * Chips carry `data-citation-page`, `data-citation-doc`, and
66
+ * `data-citation-verified` attributes (already in the DOMPurify whitelist
67
+ * since v5.6.0) so a host's `target.closest('[data-citation-page]')`
68
+ * delegated click handler routes the click to the source-doc panel.
69
+ *
70
+ * See `mcp-ui-solid/docs/briefs/BRIEF-citations-in-table-cells.md`.
71
+ */
72
+ export interface CitationCtx {
73
+ /**
74
+ * `Record<id, mapping>` keyed by the citation marker number (string-keyed
75
+ * because JSON serialization always produces strings; the runtime call
76
+ * sites accept either number or string ids and normalize internally).
77
+ */
78
+ map: Record<string | number, {
79
+ page: number | string;
80
+ file?: string;
81
+ file_id?: number | string;
82
+ }>;
83
+ /**
84
+ * Optional override returning sanitized chip HTML for one marker. Wins
85
+ * over the default `defaultCitationChip` shape. Function inputs are
86
+ * intentionally `any`-loose so consumers can swap shapes (e.g. web
87
+ * citations vs doc citations) without subtyping the entry shape here.
88
+ */
89
+ render?: (id: number, mapping: {
90
+ page: number | string;
91
+ file?: string;
92
+ file_id?: number | string;
93
+ } | undefined) => string;
94
+ }
95
+ export declare function renderCellValue(value: any, citationCtx?: CitationCtx): string;
61
96
  /**
62
97
  * Main UIResourceRenderer component
63
98
  */
@@ -1 +1 @@
1
- {"version":3,"file":"UIResourceRenderer.d.ts","sourceRoot":"","sources":["../../src/components/UIResourceRenderer.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,SAAS,EAA8D,MAAM,UAAU,CAAA;AAEhG,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,aAAa,EAA0B,MAAM,UAAU,CAAA;AAK5F;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,mBAAmB,GAAG,OAAO,GAAG,aAAa,GAAG,QAAQ,CAAA;AA8DpE;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC;;OAEG;IACH,OAAO,EAAE,WAAW,GAAG,QAAQ,CAAA;IAE/B;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAElB;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAA;IAExC;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IAEd;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,mBAAmB,CAAA;CAChC;AAiJD;;GAEG;AACH;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAYlE;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,GAAG,GAAG,MAAM,CA4ElD;AA2iCD;;GAEG;AACH,eAAO,MAAM,kBAAkB,EAAE,SAAS,CAAC,uBAAuB,CA6GjE,CAAA"}
1
+ {"version":3,"file":"UIResourceRenderer.d.ts","sourceRoot":"","sources":["../../src/components/UIResourceRenderer.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,SAAS,EAAyE,MAAM,UAAU,CAAA;AAE3G,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,aAAa,EAA0B,MAAM,UAAU,CAAA;AAM5F;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,mBAAmB,GAAG,OAAO,GAAG,aAAa,GAAG,QAAQ,CAAA;AA8DpE;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC;;OAEG;IACH,OAAO,EAAE,WAAW,GAAG,QAAQ,CAAA;IAE/B;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAElB;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAA;IAExC;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IAEd;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,mBAAmB,CAAA;CAChC;AAiJD;;GAEG;AACH;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAYlE;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,GAAG,EAAE,MAAM,CAAC,MAAM,GAAG,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC,CAAA;IACjG;;;;;OAKG;IACH,MAAM,CAAC,EAAE,CACP,EAAE,EAAE,MAAM,EACV,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,GAAG,SAAS,KACrF,MAAM,CAAA;CACZ;AA8DD,wBAAgB,eAAe,CAAC,KAAK,EAAE,GAAG,EAAE,WAAW,CAAC,EAAE,WAAW,GAAG,MAAM,CAkG7E;AA8nCD;;GAEG;AACH,eAAO,MAAM,kBAAkB,EAAE,SAAS,CAAC,uBAAuB,CA6GjE,CAAA"}
@@ -1,9 +1,10 @@
1
1
  import { delegateEvents, createComponent, getNextElement, template, getNextMarker, insert, effect, style, className, setProperty, setAttribute, runHydrationEvents, memo, isServer, use, addEventListener, classList, setStyleProperty } from "solid-js/web";
2
2
  import purify from "../node_modules/.pnpm/dompurify@3.4.1/node_modules/dompurify/dist/purify.es.js";
3
- import { createMemo, For, Show, onMount, createSignal, createEffect } from "solid-js";
3
+ import { createMemo, For, Show, onMount, onCleanup, createSignal, createEffect } from "solid-js";
4
4
  import { validateComponent, getIframeSandbox, DEFAULT_RESOURCE_LIMITS } from "../services/validation.js";
5
5
  import { GenerativeUIErrorBoundary } from "./GenerativeUIErrorBoundary.js";
6
- import { markRenderStart, markRenderEnd } from "../utils/perf.js";
6
+ import { markRenderStart, markRenderEnd, PERF_PREFIX } from "../utils/perf.js";
7
+ import { useTelemetry } from "../context/MCPUITelemetryContext.js";
7
8
  import { GridRenderer } from "./GridRenderer.js";
8
9
  import { FooterRenderer } from "./FooterRenderer.js";
9
10
  import { CarouselRenderer } from "./CarouselRenderer.js";
@@ -218,7 +219,27 @@ function highlightQuery(html, query) {
218
219
  return text.replace(regex, '<mark class="bg-yellow-200 dark:bg-[#222F49] text-inherit rounded px-0.5">$1</mark>');
219
220
  });
220
221
  }
221
- function renderCellValue(value) {
222
+ function defaultCitationChip(pageNum, fileName, verified = true) {
223
+ const safeDocName = encodeURIComponent(fileName || "");
224
+ const label = fileName ? `${fileName} - ${pageNum}` : `${pageNum}`;
225
+ if (!verified) {
226
+ return `<span class="citation-ref inline-flex items-center gap-0.5 align-middle opacity-60"><span class="text-gray-500 line-through">[${label}]</span></span>`;
227
+ }
228
+ return ['<span class="citation-ref inline-flex items-center gap-0.5 align-middle">', `<span class="text-gray-500">[${label}]</span>`, '<button class="inline-flex items-center ml-0.5 px-1 py-0.5 text-xs bg-gray-800 hover:bg-gray-700 border border-gray-600 hover:border-teal-500 rounded cursor-pointer transition-colors align-middle"', ` data-citation-page="${pageNum}"`, ` data-citation-doc="${safeDocName}"`, ' data-citation-verified="true"', ` title="View source - ${label}">`, '<svg class="w-3 h-3" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>', "</button>", "</span>"].join("");
229
+ }
230
+ function transformCellCitations(text, ctx) {
231
+ let out = text.replace(new RegExp("(?<![p.])\\[(\\d{1,2})\\](?!\\()", "g"), "[📄 CITATION $1]");
232
+ out = out.replace(/\bCitations?\s*\[(\d+)\]/gi, "[📄 CITATION $1]");
233
+ out = out.replace(/\[CITATION\s+(\d+)\]/gi, "[📄 CITATION $1]");
234
+ return out.replace(/[【[]\s*📄\s*CITATION\s*(\d+)\s*[】\]]/gi, (_m, idStr) => {
235
+ const id = parseInt(idStr, 10);
236
+ const mapping = ctx.map[id] ?? ctx.map[String(id)];
237
+ if (ctx.render) return ctx.render(id, mapping);
238
+ if (mapping) return defaultCitationChip(mapping.page, mapping.file ?? "", true);
239
+ return Object.keys(ctx.map).length > 0 ? "" : `[réf. ${id}]`;
240
+ });
241
+ }
242
+ function renderCellValue(value, citationCtx) {
222
243
  if (value === null || value === void 0) {
223
244
  return "-";
224
245
  }
@@ -254,20 +275,25 @@ function renderCellValue(value) {
254
275
  ADD_ATTR: ["target", "rel"]
255
276
  });
256
277
  }
257
- const hasHtml = /<[a-z][\s\S]*>/i.test(strValue);
258
- if (hasHtml) {
259
- return purify.sanitize(strValue, {
260
- ALLOWED_TAGS: ["a", "strong", "em", "b", "i", "code", "span", "br", "button", "svg", "path"],
261
- ALLOWED_ATTR: ["href", "target", "rel", "class", "data-citation-page", "data-citation-source", "data-citation-doc", "data-citation-verified", "title", "fill", "stroke", "viewBox", "stroke-linecap", "stroke-linejoin", "stroke-width", "d"],
262
- ADD_ATTR: ["target", "rel"]
263
- });
278
+ if (citationCtx) {
279
+ strValue = transformCellCitations(strValue, citationCtx);
264
280
  }
265
- const hasMarkdown = /[*_`[\]#]/.test(strValue);
281
+ const hasMarkdown = /[*_`#]/.test(strValue);
266
282
  if (hasMarkdown) {
267
283
  const parsed = k.parse(strValue, {
268
284
  async: false
269
285
  });
270
286
  return purify.sanitize(parsed, {
287
+ ALLOWED_TAGS: ["a", "strong", "em", "b", "i", "code", "span", "br", "button", "svg", "path", "p", "ul", "ol", "li", "pre", "blockquote", "h1", "h2", "h3", "h4", "h5", "h6"],
288
+ ALLOWED_ATTR: ["href", "target", "rel", "class", "data-citation-page", "data-citation-source", "data-citation-doc", "data-citation-verified", "title", "fill", "stroke", "viewBox", "stroke-linecap", "stroke-linejoin", "stroke-width", "d"],
289
+ ADD_ATTR: ["target", "rel"]
290
+ });
291
+ }
292
+ const hasHtml = /<[a-z][\s\S]*>/i.test(strValue);
293
+ if (hasHtml) {
294
+ return purify.sanitize(strValue, {
295
+ ALLOWED_TAGS: ["a", "strong", "em", "b", "i", "code", "span", "br", "button", "svg", "path"],
296
+ ALLOWED_ATTR: ["href", "target", "rel", "class", "data-citation-page", "data-citation-source", "data-citation-doc", "data-citation-verified", "title", "fill", "stroke", "viewBox", "stroke-linecap", "stroke-linejoin", "stroke-width", "d"],
271
297
  ADD_ATTR: ["target", "rel"]
272
298
  });
273
299
  }
@@ -276,6 +302,10 @@ function renderCellValue(value) {
276
302
  function TableRenderer(props) {
277
303
  const tableParams = props.component.params;
278
304
  let scrollContainerRef;
305
+ const citationCtx = tableParams.citationMap ? {
306
+ map: tableParams.citationMap,
307
+ render: tableParams.citationRender
308
+ } : void 0;
279
309
  const allRows = () => tableParams.rows || [];
280
310
  const columns = () => tableParams.columns || [];
281
311
  const [sortKey, setSortKey] = createSignal(null);
@@ -516,7 +546,7 @@ ${dataRows}`;
516
546
  },
517
547
  children: (column) => (() => {
518
548
  var _el$23 = getNextElement(_tmpl$10), _el$24 = _el$23.firstChild;
519
- effect(() => setProperty(_el$24, "innerHTML", highlightQuery(renderCellValue(row[column.key]), debouncedQuery())));
549
+ effect(() => setProperty(_el$24, "innerHTML", highlightQuery(renderCellValue(row[column.key], citationCtx), debouncedQuery())));
520
550
  return _el$23;
521
551
  })()
522
552
  }));
@@ -549,7 +579,7 @@ ${dataRows}`;
549
579
  },
550
580
  children: (column) => (() => {
551
581
  var _el$27 = getNextElement(_tmpl$10), _el$28 = _el$27.firstChild;
552
- effect(() => setProperty(_el$28, "innerHTML", highlightQuery(renderCellValue(row[column.key]), debouncedQuery())));
582
+ effect(() => setProperty(_el$28, "innerHTML", highlightQuery(renderCellValue(row[column.key], citationCtx), debouncedQuery())));
553
583
  return _el$27;
554
584
  })()
555
585
  }));
@@ -1053,9 +1083,44 @@ function LinkRenderer(props) {
1053
1083
  })();
1054
1084
  }
1055
1085
  function ComponentRenderer(props) {
1056
- var _a, _b, _c;
1086
+ var _a, _b, _c, _d, _e, _f;
1057
1087
  markRenderStart(props.component.id);
1058
- onMount(() => markRenderEnd(props.component.id));
1088
+ const telemetry = useTelemetry();
1089
+ onMount(() => {
1090
+ markRenderEnd(props.component.id);
1091
+ if (telemetry) {
1092
+ const ts = Date.now();
1093
+ telemetry.dispatch({
1094
+ type: "component:mounted",
1095
+ id: props.component.id,
1096
+ componentType: props.component.type,
1097
+ ts
1098
+ });
1099
+ if (typeof performance !== "undefined" && typeof performance.getEntriesByName === "function") {
1100
+ const entries = performance.getEntriesByName(`${PERF_PREFIX}${props.component.id}:render`, "measure");
1101
+ const last = entries[entries.length - 1];
1102
+ if (last) {
1103
+ telemetry.dispatch({
1104
+ type: "component:rendered",
1105
+ id: props.component.id,
1106
+ componentType: props.component.type,
1107
+ durationMs: last.duration,
1108
+ ts
1109
+ });
1110
+ }
1111
+ }
1112
+ }
1113
+ });
1114
+ onCleanup(() => {
1115
+ if (telemetry) {
1116
+ telemetry.dispatch({
1117
+ type: "component:unmounted",
1118
+ id: props.component.id,
1119
+ componentType: props.component.type,
1120
+ ts: Date.now()
1121
+ });
1122
+ }
1123
+ });
1059
1124
  const validation = validateComponent(props.component);
1060
1125
  if (!validation.valid) {
1061
1126
  (_a = props.onError) == null ? void 0 : _a.call(props, {
@@ -1064,8 +1129,18 @@ function ComponentRenderer(props) {
1064
1129
  componentId: props.component.id,
1065
1130
  details: validation.errors
1066
1131
  });
1132
+ if (telemetry) {
1133
+ telemetry.dispatch({
1134
+ type: "validation:failed",
1135
+ id: props.component.id,
1136
+ componentType: props.component.type,
1137
+ errorCount: ((_b = validation.errors) == null ? void 0 : _b.length) ?? 0,
1138
+ firstErrorCode: ((_d = (_c = validation.errors) == null ? void 0 : _c[0]) == null ? void 0 : _d.code) ?? null,
1139
+ ts: Date.now()
1140
+ });
1141
+ }
1067
1142
  const mode = props.errorMode ?? "block";
1068
- const firstError = ((_c = (_b = validation.errors) == null ? void 0 : _b[0]) == null ? void 0 : _c.message) || "Unknown validation error";
1143
+ const firstError = ((_f = (_e = validation.errors) == null ? void 0 : _e[0]) == null ? void 0 : _f.message) || "Unknown validation error";
1069
1144
  if (mode === "silent") {
1070
1145
  return null;
1071
1146
  }
@@ -1317,7 +1392,20 @@ function ActionRenderer(props) {
1317
1392
  execute,
1318
1393
  isExecuting
1319
1394
  } = useAction();
1395
+ const telemetry = useTelemetry();
1396
+ function dispatchTelemetry() {
1397
+ if (!telemetry) return;
1398
+ const actionName = params.toolName ?? params.action ?? "unknown";
1399
+ telemetry.dispatch({
1400
+ type: "action:dispatched",
1401
+ id: props.component.id,
1402
+ componentType: "action",
1403
+ actionName,
1404
+ ts: Date.now()
1405
+ });
1406
+ }
1320
1407
  const handleClick = async (e) => {
1408
+ dispatchTelemetry();
1321
1409
  if (params.action === "tool-call" && params.toolName) {
1322
1410
  e.preventDefault();
1323
1411
  await execute(params.toolName, params.params || {});