@seed-ship/mcp-ui-solid 6.8.2 → 6.9.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 (39) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/components/ChartJSRenderer.cjs +27 -13
  3. package/dist/components/ChartJSRenderer.cjs.map +1 -1
  4. package/dist/components/ChartJSRenderer.d.ts.map +1 -1
  5. package/dist/components/ChartJSRenderer.js +28 -14
  6. package/dist/components/ChartJSRenderer.js.map +1 -1
  7. package/dist/components/DegradedFallback.cjs +73 -0
  8. package/dist/components/DegradedFallback.cjs.map +1 -0
  9. package/dist/components/DegradedFallback.d.ts +37 -0
  10. package/dist/components/DegradedFallback.d.ts.map +1 -0
  11. package/dist/components/DegradedFallback.js +73 -0
  12. package/dist/components/DegradedFallback.js.map +1 -0
  13. package/dist/components/GraphRenderer.cjs +30 -15
  14. package/dist/components/GraphRenderer.cjs.map +1 -1
  15. package/dist/components/GraphRenderer.d.ts.map +1 -1
  16. package/dist/components/GraphRenderer.js +31 -16
  17. package/dist/components/GraphRenderer.js.map +1 -1
  18. package/dist/components/MapRenderer.cjs +128 -107
  19. package/dist/components/MapRenderer.cjs.map +1 -1
  20. package/dist/components/MapRenderer.d.ts.map +1 -1
  21. package/dist/components/MapRenderer.js +129 -108
  22. package/dist/components/MapRenderer.js.map +1 -1
  23. package/dist/index.cjs +4 -4
  24. package/dist/index.js +1 -1
  25. package/dist/utils/degraded-projections.cjs +87 -0
  26. package/dist/utils/degraded-projections.cjs.map +1 -0
  27. package/dist/utils/degraded-projections.d.ts +64 -0
  28. package/dist/utils/degraded-projections.d.ts.map +1 -0
  29. package/dist/utils/degraded-projections.js +87 -0
  30. package/dist/utils/degraded-projections.js.map +1 -0
  31. package/package.json +1 -1
  32. package/src/components/ChartJSRenderer.tsx +94 -85
  33. package/src/components/DegradedFallback.test.tsx +61 -0
  34. package/src/components/DegradedFallback.tsx +93 -0
  35. package/src/components/GraphRenderer.tsx +26 -4
  36. package/src/components/MapRenderer.tsx +446 -392
  37. package/src/utils/degraded-projections.test.ts +113 -0
  38. package/src/utils/degraded-projections.ts +149 -0
  39. package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,40 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [6.9.0] - 2026-05-31
9
+
10
+ ### Added — renderer fallback ladder (no silent blanks)
11
+
12
+ Every heavy renderer now follows the same three-rung contract (audit
13
+ `docs/briefs/AUDIT-2026-05-30-visual-renderers-g6-ontology.md`, P2.5):
14
+
15
+ 1. **native** render when the peer lib is available and succeeds;
16
+ 2. **degraded but useful** view when the native render throws — a visible
17
+ notice plus a plain data table, so the user still sees the underlying
18
+ data instead of a blank space;
19
+ 3. a `component:error` **telemetry** event (`detail.degraded = true`) so the
20
+ failure is observable by hosts that mount `<MCPUITelemetryProvider>`.
21
+
22
+ Previously a render-path failure left an empty canvas / blank container
23
+ (graph, chart) or a bare red error string (map). Now:
24
+
25
+ - **`graph`** (`<GraphRenderer>`) — on G6 render error, degrades to an
26
+ edge table (Source / Target / Label) or, when there are no edges, a node
27
+ list. The export menu (PNG / Mermaid / JSON) stays usable.
28
+ - **`map`** (`<MapRenderer>`) — the Leaflet drawing block is now wrapped;
29
+ on failure it degrades to a coordinate table (markers + GeoJSON features
30
+ → Type / Lat / Lng / Info).
31
+ - **`chart`** (`<ChartJSRenderer>`) — on Chart.js render error, degrades to
32
+ a series table (labels × datasets) instead of a blank canvas.
33
+
34
+ New exports: `DegradedFallback` component (+ `DegradedFallbackProps`) for
35
+ hosts that want to build consistent fallbacks of their own. New pure
36
+ projection helpers (`graphToDegradedTable`, `mapToDegradedTable`,
37
+ `chartToDegradedTable`) under `src/utils/degraded-projections`.
38
+
39
+ `source_card` is intentionally out of scope here — it is not yet a real
40
+ `UIResourceRenderer` type (tracked separately as audit P2.6).
41
+
8
42
  ## [6.8.2] - 2026-05-30
9
43
 
10
44
  ### Fixed — `type:'map'` rejected when it renders purely from GeoJSON
@@ -25,7 +25,10 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
25
25
  const web = require("solid-js/web");
26
26
  const solidJs = require("solid-js");
27
27
  const ExpandableWrapper = require("./ExpandableWrapper.cjs");
28
- var _tmpl$ = /* @__PURE__ */ web.template(`<h3 class="text-sm font-semibold text-gray-900 dark:text-white">`), _tmpl$2 = /* @__PURE__ */ web.template(`<button class="opacity-0 group-hover:opacity-60 hover:!opacity-100 px-2 py-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-all shadow-sm"title="Download PNG"aria-label="Download chart as PNG"><svg class="w-3 h-3 text-gray-500 dark:text-gray-400"fill=none viewBox="0 0 24 24"stroke=currentColor><path stroke-linecap=round stroke-linejoin=round stroke-width=2 d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z">`), _tmpl$3 = /* @__PURE__ */ web.template(`<div class="flex items-center justify-between mb-3 flex-shrink-0"><!$><!/><!$><!/>`), _tmpl$4 = /* @__PURE__ */ web.template(`<div class="absolute inset-0 flex items-center justify-center bg-white/80 dark:bg-gray-800/80"><div class="flex flex-col items-center gap-2"><div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div><span class="text-sm text-gray-500 dark:text-gray-400">Loading chart...`), _tmpl$5 = /* @__PURE__ */ web.template(`<div class="absolute inset-0 flex items-center justify-center p-4 bg-white dark:bg-gray-800"><div class=text-center><div class="inline-flex items-center justify-center w-12 h-12 rounded-full bg-red-100 dark:bg-red-900/20 mb-3"><svg class="w-6 h-6 text-red-600 dark:text-red-400"fill=none viewBox="0 0 24 24"stroke=currentColor><path stroke-linecap=round stroke-linejoin=round stroke-width=2 d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path></svg></div><p class="text-red-600 dark:text-red-400 text-sm font-medium">Chart Error</p><p class="text-gray-600 dark:text-gray-400 text-xs mt-1 max-w-xs">`), _tmpl$6 = /* @__PURE__ */ web.template(`<div><!$><!/><!$><!/><!$><!/><div><canvas>`);
28
+ const DegradedFallback = require("./DegradedFallback.cjs");
29
+ const degradedProjections = require("../utils/degraded-projections.cjs");
30
+ const MCPUITelemetryContext = require("../context/MCPUITelemetryContext.cjs");
31
+ var _tmpl$ = /* @__PURE__ */ web.template(`<h3 class="text-sm font-semibold text-gray-900 dark:text-white">`), _tmpl$2 = /* @__PURE__ */ web.template(`<button class="opacity-0 group-hover:opacity-60 hover:!opacity-100 px-2 py-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-all shadow-sm"title="Download PNG"aria-label="Download chart as PNG"><svg class="w-3 h-3 text-gray-500 dark:text-gray-400"fill=none viewBox="0 0 24 24"stroke=currentColor><path stroke-linecap=round stroke-linejoin=round stroke-width=2 d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z">`), _tmpl$3 = /* @__PURE__ */ web.template(`<div class="flex items-center justify-between mb-3 flex-shrink-0"><!$><!/><!$><!/>`), _tmpl$4 = /* @__PURE__ */ web.template(`<div class="absolute inset-0 flex items-center justify-center bg-white/80 dark:bg-gray-800/80"><div class="flex flex-col items-center gap-2"><div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div><span class="text-sm text-gray-500 dark:text-gray-400">Loading chart...`), _tmpl$5 = /* @__PURE__ */ web.template(`<div><!$><!/><!$><!/><!$><!/><div><canvas>`);
29
32
  let ChartJS = null;
30
33
  let chartJSLoadPromise = null;
31
34
  const loadChartJS = async () => {
@@ -56,6 +59,7 @@ const ChartJSRenderer = (props) => {
56
59
  let chartInstance;
57
60
  const params = () => props.component.params;
58
61
  const isExpanded = ExpandableWrapper.useExpanded();
62
+ const telemetry = MCPUITelemetryContext.useTelemetry();
59
63
  const exportEnabled = () => params().exportable !== false;
60
64
  const copyDataJSON = () => JSON.stringify({
61
65
  type: params().type,
@@ -70,7 +74,7 @@ const ChartJSRenderer = (props) => {
70
74
  a.click();
71
75
  };
72
76
  solidJs.createEffect(async () => {
73
- var _a, _b, _c, _d, _e;
77
+ var _a, _b, _c, _d, _e, _f;
74
78
  if (!canvasRef) return;
75
79
  const chartParams = params();
76
80
  setIsLoading(true);
@@ -125,7 +129,14 @@ const ChartJSRenderer = (props) => {
125
129
  const error2 = err instanceof Error ? err : new Error("Chart rendering failed");
126
130
  setError(error2.message);
127
131
  setIsLoading(false);
128
- (_e = props.onError) == null ? void 0 : _e.call(props, error2);
132
+ telemetry == null ? void 0 : telemetry.dispatch({
133
+ type: "render:error",
134
+ errorMessage: error2.message,
135
+ id: ((_e = props.component) == null ? void 0 : _e.id) ?? "",
136
+ componentType: "chart",
137
+ ts: Date.now()
138
+ });
139
+ (_f = props.onError) == null ? void 0 : _f.call(props, error2);
129
140
  }
130
141
  });
131
142
  solidJs.onCleanup(() => {
@@ -146,7 +157,7 @@ const ChartJSRenderer = (props) => {
146
157
  return props.toolbarVariant;
147
158
  },
148
159
  get children() {
149
- var _el$ = web.getNextElement(_tmpl$6), _el$15 = _el$.firstChild, [_el$16, _co$3] = web.getNextMarker(_el$15.nextSibling), _el$17 = _el$16.nextSibling, [_el$18, _co$4] = web.getNextMarker(_el$17.nextSibling), _el$19 = _el$18.nextSibling, [_el$20, _co$5] = web.getNextMarker(_el$19.nextSibling), _el$13 = _el$20.nextSibling, _el$14 = _el$13.firstChild;
160
+ var _el$ = web.getNextElement(_tmpl$5), _el$10 = _el$.firstChild, [_el$11, _co$3] = web.getNextMarker(_el$10.nextSibling), _el$12 = _el$11.nextSibling, [_el$13, _co$4] = web.getNextMarker(_el$12.nextSibling), _el$14 = _el$13.nextSibling, [_el$15, _co$5] = web.getNextMarker(_el$14.nextSibling), _el$0 = _el$15.nextSibling, _el$1 = _el$0.firstChild;
150
161
  web.insert(_el$, web.createComponent(solidJs.Show, {
151
162
  get when() {
152
163
  return params().title || exportEnabled();
@@ -176,7 +187,7 @@ const ChartJSRenderer = (props) => {
176
187
  }), _el$8, _co$2);
177
188
  return _el$2;
178
189
  }
179
- }), _el$16, _co$3);
190
+ }), _el$11, _co$3);
180
191
  web.insert(_el$, web.createComponent(solidJs.Show, {
181
192
  get when() {
182
193
  return isLoading();
@@ -184,19 +195,22 @@ const ChartJSRenderer = (props) => {
184
195
  get children() {
185
196
  return web.getNextElement(_tmpl$4);
186
197
  }
187
- }), _el$18, _co$4);
198
+ }), _el$13, _co$4);
188
199
  web.insert(_el$, web.createComponent(solidJs.Show, {
189
200
  get when() {
190
201
  return error();
191
202
  },
192
203
  get children() {
193
- var _el$0 = web.getNextElement(_tmpl$5), _el$1 = _el$0.firstChild, _el$10 = _el$1.firstChild, _el$11 = _el$10.nextSibling, _el$12 = _el$11.nextSibling;
194
- web.insert(_el$12, error);
195
- return _el$0;
204
+ return web.createComponent(DegradedFallback.DegradedFallback, web.mergeProps({
205
+ get message() {
206
+ return `Chart rendering failed: ${error()}`;
207
+ },
208
+ caption: "Showing the chart data as a table — the interactive chart is unavailable."
209
+ }, () => degradedProjections.chartToDegradedTable(params() ?? {})));
196
210
  }
197
- }), _el$20, _co$5);
211
+ }), _el$15, _co$5);
198
212
  var _ref$ = canvasRef;
199
- typeof _ref$ === "function" ? web.use(_ref$, _el$14) : canvasRef = _el$14;
213
+ typeof _ref$ === "function" ? web.use(_ref$, _el$1) : canvasRef = _el$1;
200
214
  web.effect((_p$) => {
201
215
  var _v$ = `relative w-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden p-4 group ${isExpanded() ? "flex-1 min-h-0 flex flex-col" : ""}`, _v$2 = `w-full ${isExpanded() ? "flex-1 min-h-0" : ""}`, _v$3 = error() ? {
202
216
  display: "none"
@@ -208,8 +222,8 @@ const ChartJSRenderer = (props) => {
208
222
  display: "block"
209
223
  };
210
224
  _v$ !== _p$.e && web.className(_el$, _p$.e = _v$);
211
- _v$2 !== _p$.t && web.className(_el$13, _p$.t = _v$2);
212
- _p$.a = web.style(_el$13, _v$3, _p$.a);
225
+ _v$2 !== _p$.t && web.className(_el$0, _p$.t = _v$2);
226
+ _p$.a = web.style(_el$0, _v$3, _p$.a);
213
227
  return _p$;
214
228
  }, {
215
229
  e: void 0,
@@ -1 +1 @@
1
- {"version":3,"file":"ChartJSRenderer.cjs","sources":["../../src/components/ChartJSRenderer.tsx"],"sourcesContent":["/**\n * ChartJSRenderer - Native Chart.js rendering\n * Sprint 4: State & Charts\n *\n * Requires chart.js peer dependency:\n * ```\n * pnpm add chart.js\n * ```\n */\n\nimport { Component, createEffect, onCleanup, createSignal, Show } from 'solid-js'\nimport type { UIComponent, ChartComponentParams } from '../types'\nimport { ExpandableWrapper, useExpanded } from './ExpandableWrapper'\n\n// Lazy load Chart.js to avoid bundling if not used\nlet ChartJS: any = null\nlet chartJSLoadPromise: Promise<any> | null = null\n\nconst loadChartJS = async () => {\n if (ChartJS) return ChartJS\n\n if (!chartJSLoadPromise) {\n chartJSLoadPromise = import('chart.js/auto')\n .then((module) => {\n ChartJS = module.default || module.Chart\n return ChartJS\n })\n .catch((err) => {\n chartJSLoadPromise = null\n throw err\n })\n }\n\n return chartJSLoadPromise\n}\n\n/**\n * Check if Chart.js is available\n */\nexport async function isChartJSAvailable(): Promise<boolean> {\n try {\n await loadChartJS()\n return true\n } catch {\n return false\n }\n}\n\nexport interface ChartJSRendererProps {\n /**\n * UIComponent with chart params\n */\n component: UIComponent\n\n /**\n * Error callback\n */\n onError?: (error: Error) => void\n\n /**\n * Forwarded to the underlying `<ExpandableWrapper>` (v6.3.1).\n * @see ExpandableWrapperProps.toolbarVariant\n */\n toolbarVariant?: 'hover' | 'always-visible'\n}\n\n/**\n * Native Chart.js renderer component\n *\n * @example\n * ```tsx\n * const chartComponent: UIComponent = {\n * id: 'revenue-chart',\n * type: 'chart',\n * position: { colStart: 1, colSpan: 6 },\n * params: {\n * type: 'bar',\n * title: 'Monthly Revenue',\n * data: {\n * labels: ['Jan', 'Feb', 'Mar'],\n * datasets: [{ label: 'Revenue', data: [100, 200, 150] }]\n * },\n * renderer: 'native',\n * },\n * }\n * <ChartJSRenderer component={chartComponent} />\n * ```\n */\nexport const ChartJSRenderer: Component<ChartJSRendererProps> = (props) => {\n const [isLoading, setIsLoading] = createSignal(true)\n const [error, setError] = createSignal<string>()\n let canvasRef: HTMLCanvasElement | undefined\n let chartInstance: any\n\n const params = () => props.component.params as ChartComponentParams\n const isExpanded = useExpanded()\n\n // v6.1.0 — export visibility :\n // - undefined / true → button shown (new default, was opt-in)\n // - false → button hidden (explicit opt-out, unchanged)\n const exportEnabled = () => params().exportable !== false\n\n // v6.1.0 — copy data for the ExpandableWrapper modal-header copy button.\n // Lazy-stringified each time the button is clicked.\n const copyDataJSON = () => JSON.stringify({ type: params().type, data: params().data }, null, 2)\n\n // Chart PNG export\n const handleExportPNG = () => {\n if (!canvasRef) return\n const url = canvasRef.toDataURL('image/png')\n const a = document.createElement('a')\n a.href = url\n a.download = `${(params().title || 'chart').replace(/\\s+/g, '-').toLowerCase()}.png`\n a.click()\n }\n\n // Create/update chart when params change\n createEffect(async () => {\n if (!canvasRef) return\n\n // Access params to track dependencies\n const chartParams = params()\n\n setIsLoading(true)\n setError(undefined)\n\n try {\n const Chart = await loadChartJS()\n\n // Destroy previous instance\n if (chartInstance) {\n chartInstance.destroy()\n chartInstance = null\n }\n\n // Build options, merging time-axis config if present (v3.1.0)\n const baseOptions: any = {\n responsive: true,\n maintainAspectRatio: false,\n ...chartParams.options,\n plugins: {\n ...chartParams.options?.plugins,\n legend: {\n display: true,\n position: 'bottom',\n ...chartParams.options?.plugins?.legend,\n },\n },\n }\n\n // Time-series axis (v3.1.0)\n if (chartParams.timeAxis) {\n const ta = chartParams.timeAxis\n baseOptions.scales = {\n ...baseOptions.scales,\n x: {\n ...baseOptions.scales?.x,\n type: 'time',\n time: {\n parser: ta.parser,\n unit: ta.unit,\n tooltipFormat: ta.tooltipFormat,\n },\n ...(ta.min ? { min: ta.min } : {}),\n ...(ta.max ? { max: ta.max } : {}),\n },\n }\n }\n\n // Create new chart\n chartInstance = new Chart(canvasRef, {\n type: chartParams.type,\n data: chartParams.data,\n options: baseOptions,\n })\n\n setIsLoading(false)\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Chart rendering failed')\n setError(error.message)\n setIsLoading(false)\n props.onError?.(error)\n }\n })\n\n // Cleanup on unmount\n onCleanup(() => {\n if (chartInstance) {\n chartInstance.destroy()\n chartInstance = null\n }\n })\n\n return (\n <ExpandableWrapper\n title={params().title || 'Chart'}\n copyData={copyDataJSON()}\n copyLabel=\"Copy chart data (JSON)\"\n toolbarVariant={props.toolbarVariant}\n >\n <div class={`relative w-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden p-4 group ${\n isExpanded() ? 'flex-1 min-h-0 flex flex-col' : ''\n }`}>\n <Show when={params().title || exportEnabled()}>\n <div class=\"flex items-center justify-between mb-3 flex-shrink-0\">\n <Show when={params().title}>\n <h3 class=\"text-sm font-semibold text-gray-900 dark:text-white\">\n {params().title}\n </h3>\n </Show>\n <Show when={exportEnabled()}>\n <button\n onClick={handleExportPNG}\n class=\"opacity-0 group-hover:opacity-60 hover:!opacity-100 px-2 py-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-all shadow-sm\"\n title=\"Download PNG\"\n aria-label=\"Download chart as PNG\"\n >\n <svg class=\"w-3 h-3 text-gray-500 dark:text-gray-400\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z\" />\n </svg>\n </button>\n </Show>\n </div>\n </Show>\n\n <Show when={isLoading()}>\n <div class=\"absolute inset-0 flex items-center justify-center bg-white/80 dark:bg-gray-800/80\">\n <div class=\"flex flex-col items-center gap-2\">\n <div class=\"animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600\" />\n <span class=\"text-sm text-gray-500 dark:text-gray-400\">Loading chart...</span>\n </div>\n </div>\n </Show>\n\n <Show when={error()}>\n <div class=\"absolute inset-0 flex items-center justify-center p-4 bg-white dark:bg-gray-800\">\n <div class=\"text-center\">\n <div class=\"inline-flex items-center justify-center w-12 h-12 rounded-full bg-red-100 dark:bg-red-900/20 mb-3\">\n <svg\n class=\"w-6 h-6 text-red-600 dark:text-red-400\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n </div>\n <p class=\"text-red-600 dark:text-red-400 text-sm font-medium\">Chart Error</p>\n <p class=\"text-gray-600 dark:text-gray-400 text-xs mt-1 max-w-xs\">{error()}</p>\n </div>\n </div>\n </Show>\n\n <div\n class={`w-full ${isExpanded() ? 'flex-1 min-h-0' : ''}`}\n style={\n error()\n ? { display: 'none' }\n : isExpanded()\n ? { height: '100%', display: 'block' }\n : { height: params().height || '250px', display: 'block' }\n }\n >\n <canvas ref={canvasRef} />\n </div>\n </div>\n </ExpandableWrapper>\n )\n}\n"],"names":["ChartJS","chartJSLoadPromise","loadChartJS","then","module","default","Chart","catch","err","isChartJSAvailable","ChartJSRenderer","props","isLoading","setIsLoading","createSignal","error","setError","canvasRef","chartInstance","params","component","isExpanded","useExpanded","exportEnabled","exportable","copyDataJSON","JSON","stringify","type","data","handleExportPNG","url","toDataURL","a","document","createElement","href","download","title","replace","toLowerCase","click","createEffect","chartParams","undefined","destroy","baseOptions","responsive","maintainAspectRatio","options","plugins","legend","display","position","timeAxis","ta","scales","x","time","parser","unit","tooltipFormat","min","max","Error","message","onError","onCleanup","_$createComponent","ExpandableWrapper","copyData","copyLabel","toolbarVariant","children","_el$","_$getNextElement","_tmpl$6","_el$15","firstChild","_el$16","_co$3","_$getNextMarker","nextSibling","_el$17","_el$18","_co$4","_el$19","_el$20","_co$5","_el$13","_el$14","_$insert","Show","when","_el$2","_tmpl$3","_el$5","_el$6","_co$","_el$7","_el$8","_co$2","_el$3","_tmpl$","_el$4","_tmpl$2","$$click","_$runHydrationEvents","_tmpl$4","_el$0","_tmpl$5","_el$1","_el$10","_el$11","_el$12","_ref$","_$use","_$effect","_p$","_v$","_v$2","_v$3","height","e","_$className","t","_$style","_$delegateEvents"],"mappings":"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"}
1
+ {"version":3,"file":"ChartJSRenderer.cjs","sources":["../../src/components/ChartJSRenderer.tsx"],"sourcesContent":["/**\n * ChartJSRenderer - Native Chart.js rendering\n * Sprint 4: State & Charts\n *\n * Requires chart.js peer dependency:\n * ```\n * pnpm add chart.js\n * ```\n */\n\nimport { Component, createEffect, onCleanup, createSignal, Show } from 'solid-js';\nimport type { UIComponent, ChartComponentParams } from '../types';\nimport { ExpandableWrapper, useExpanded } from './ExpandableWrapper';\nimport { DegradedFallback } from './DegradedFallback';\nimport { chartToDegradedTable } from '../utils/degraded-projections';\nimport { useTelemetry } from '../context/MCPUITelemetryContext';\n\n// Lazy load Chart.js to avoid bundling if not used\nlet ChartJS: any = null;\nlet chartJSLoadPromise: Promise<any> | null = null;\n\nconst loadChartJS = async () => {\n if (ChartJS) return ChartJS;\n\n if (!chartJSLoadPromise) {\n chartJSLoadPromise = import('chart.js/auto')\n .then((module) => {\n ChartJS = module.default || module.Chart;\n return ChartJS;\n })\n .catch((err) => {\n chartJSLoadPromise = null;\n throw err;\n });\n }\n\n return chartJSLoadPromise;\n};\n\n/**\n * Check if Chart.js is available\n */\nexport async function isChartJSAvailable(): Promise<boolean> {\n try {\n await loadChartJS();\n return true;\n } catch {\n return false;\n }\n}\n\nexport interface ChartJSRendererProps {\n /**\n * UIComponent with chart params\n */\n component: UIComponent;\n\n /**\n * Error callback\n */\n onError?: (error: Error) => void;\n\n /**\n * Forwarded to the underlying `<ExpandableWrapper>` (v6.3.1).\n * @see ExpandableWrapperProps.toolbarVariant\n */\n toolbarVariant?: 'hover' | 'always-visible';\n}\n\n/**\n * Native Chart.js renderer component\n *\n * @example\n * ```tsx\n * const chartComponent: UIComponent = {\n * id: 'revenue-chart',\n * type: 'chart',\n * position: { colStart: 1, colSpan: 6 },\n * params: {\n * type: 'bar',\n * title: 'Monthly Revenue',\n * data: {\n * labels: ['Jan', 'Feb', 'Mar'],\n * datasets: [{ label: 'Revenue', data: [100, 200, 150] }]\n * },\n * renderer: 'native',\n * },\n * }\n * <ChartJSRenderer component={chartComponent} />\n * ```\n */\nexport const ChartJSRenderer: Component<ChartJSRendererProps> = (props) => {\n const [isLoading, setIsLoading] = createSignal(true);\n const [error, setError] = createSignal<string>();\n let canvasRef: HTMLCanvasElement | undefined;\n let chartInstance: any;\n\n const params = () => props.component.params as ChartComponentParams;\n const isExpanded = useExpanded();\n const telemetry = useTelemetry();\n\n // v6.1.0 — export visibility :\n // - undefined / true → button shown (new default, was opt-in)\n // - false → button hidden (explicit opt-out, unchanged)\n const exportEnabled = () => params().exportable !== false;\n\n // v6.1.0 — copy data for the ExpandableWrapper modal-header copy button.\n // Lazy-stringified each time the button is clicked.\n const copyDataJSON = () => JSON.stringify({ type: params().type, data: params().data }, null, 2);\n\n // Chart PNG export\n const handleExportPNG = () => {\n if (!canvasRef) return;\n const url = canvasRef.toDataURL('image/png');\n const a = document.createElement('a');\n a.href = url;\n a.download = `${(params().title || 'chart').replace(/\\s+/g, '-').toLowerCase()}.png`;\n a.click();\n };\n\n // Create/update chart when params change\n createEffect(async () => {\n if (!canvasRef) return;\n\n // Access params to track dependencies\n const chartParams = params();\n\n setIsLoading(true);\n setError(undefined);\n\n try {\n const Chart = await loadChartJS();\n\n // Destroy previous instance\n if (chartInstance) {\n chartInstance.destroy();\n chartInstance = null;\n }\n\n // Build options, merging time-axis config if present (v3.1.0)\n const baseOptions: any = {\n responsive: true,\n maintainAspectRatio: false,\n ...chartParams.options,\n plugins: {\n ...chartParams.options?.plugins,\n legend: {\n display: true,\n position: 'bottom',\n ...chartParams.options?.plugins?.legend,\n },\n },\n };\n\n // Time-series axis (v3.1.0)\n if (chartParams.timeAxis) {\n const ta = chartParams.timeAxis;\n baseOptions.scales = {\n ...baseOptions.scales,\n x: {\n ...baseOptions.scales?.x,\n type: 'time',\n time: {\n parser: ta.parser,\n unit: ta.unit,\n tooltipFormat: ta.tooltipFormat,\n },\n ...(ta.min ? { min: ta.min } : {}),\n ...(ta.max ? { max: ta.max } : {}),\n },\n };\n }\n\n // Create new chart\n chartInstance = new Chart(canvasRef, {\n type: chartParams.type,\n data: chartParams.data,\n options: baseOptions,\n });\n\n setIsLoading(false);\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Chart rendering failed');\n setError(error.message);\n setIsLoading(false);\n // Fallback ladder (P2.5): record the failure so it's observable, then\n // degrade to the series table below instead of a blank canvas.\n telemetry?.dispatch({\n type: 'render:error',\n errorMessage: error.message,\n id: props.component?.id ?? '',\n componentType: 'chart',\n ts: Date.now(),\n });\n props.onError?.(error);\n }\n });\n\n // Cleanup on unmount\n onCleanup(() => {\n if (chartInstance) {\n chartInstance.destroy();\n chartInstance = null;\n }\n });\n\n return (\n <ExpandableWrapper\n title={params().title || 'Chart'}\n copyData={copyDataJSON()}\n copyLabel=\"Copy chart data (JSON)\"\n toolbarVariant={props.toolbarVariant}\n >\n <div\n class={`relative w-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden p-4 group ${\n isExpanded() ? 'flex-1 min-h-0 flex flex-col' : ''\n }`}\n >\n <Show when={params().title || exportEnabled()}>\n <div class=\"flex items-center justify-between mb-3 flex-shrink-0\">\n <Show when={params().title}>\n <h3 class=\"text-sm font-semibold text-gray-900 dark:text-white\">{params().title}</h3>\n </Show>\n <Show when={exportEnabled()}>\n <button\n onClick={handleExportPNG}\n class=\"opacity-0 group-hover:opacity-60 hover:!opacity-100 px-2 py-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-all shadow-sm\"\n title=\"Download PNG\"\n aria-label=\"Download chart as PNG\"\n >\n <svg\n class=\"w-3 h-3 text-gray-500 dark:text-gray-400\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z\"\n />\n </svg>\n </button>\n </Show>\n </div>\n </Show>\n\n <Show when={isLoading()}>\n <div class=\"absolute inset-0 flex items-center justify-center bg-white/80 dark:bg-gray-800/80\">\n <div class=\"flex flex-col items-center gap-2\">\n <div class=\"animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600\" />\n <span class=\"text-sm text-gray-500 dark:text-gray-400\">Loading chart...</span>\n </div>\n </div>\n </Show>\n\n {/* Fallback ladder (P2.5): degrade to a series table on render error\n instead of a bare \"Chart Error\" message. */}\n <Show when={error()}>\n <DegradedFallback\n message={`Chart rendering failed: ${error()}`}\n caption=\"Showing the chart data as a table — the interactive chart is unavailable.\"\n {...chartToDegradedTable(params() ?? {})}\n />\n </Show>\n\n <div\n class={`w-full ${isExpanded() ? 'flex-1 min-h-0' : ''}`}\n style={\n error()\n ? { display: 'none' }\n : isExpanded()\n ? { height: '100%', display: 'block' }\n : { height: params().height || '250px', display: 'block' }\n }\n >\n <canvas ref={canvasRef} />\n </div>\n </div>\n </ExpandableWrapper>\n );\n};\n"],"names":["ChartJS","chartJSLoadPromise","loadChartJS","then","module","default","Chart","catch","err","isChartJSAvailable","ChartJSRenderer","props","isLoading","setIsLoading","createSignal","error","setError","canvasRef","chartInstance","params","component","isExpanded","useExpanded","telemetry","useTelemetry","exportEnabled","exportable","copyDataJSON","JSON","stringify","type","data","handleExportPNG","url","toDataURL","a","document","createElement","href","download","title","replace","toLowerCase","click","createEffect","chartParams","undefined","destroy","baseOptions","responsive","maintainAspectRatio","options","plugins","legend","display","position","timeAxis","ta","scales","x","time","parser","unit","tooltipFormat","min","max","Error","message","dispatch","errorMessage","id","componentType","ts","Date","now","onError","onCleanup","_$createComponent","ExpandableWrapper","copyData","copyLabel","toolbarVariant","children","_el$","_$getNextElement","_tmpl$5","_el$10","firstChild","_el$11","_co$3","_$getNextMarker","nextSibling","_el$12","_el$13","_co$4","_el$14","_el$15","_co$5","_el$0","_el$1","_$insert","Show","when","_el$2","_tmpl$3","_el$5","_el$6","_co$","_el$7","_el$8","_co$2","_el$3","_tmpl$","_el$4","_tmpl$2","$$click","_$runHydrationEvents","_tmpl$4","DegradedFallback","_$mergeProps","caption","chartToDegradedTable","_ref$","_$use","_$effect","_p$","_v$","_v$2","_v$3","height","e","_$className","t","_$style","_$delegateEvents"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ChartJSRenderer.d.ts","sourceRoot":"","sources":["../../src/components/ChartJSRenderer.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,SAAS,EAA+C,MAAM,UAAU,CAAA;AACjF,OAAO,KAAK,EAAE,WAAW,EAAwB,MAAM,UAAU,CAAA;AAyBjE;;GAEG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,OAAO,CAAC,CAO3D;AAED,MAAM,WAAW,oBAAoB;IACnC;;OAEG;IACH,SAAS,EAAE,WAAW,CAAA;IAEtB;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;IAEhC;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,GAAG,gBAAgB,CAAA;CAC5C;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,eAAe,EAAE,SAAS,CAAC,oBAAoB,CAyL3D,CAAA"}
1
+ {"version":3,"file":"ChartJSRenderer.d.ts","sourceRoot":"","sources":["../../src/components/ChartJSRenderer.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,SAAS,EAA+C,MAAM,UAAU,CAAC;AAClF,OAAO,KAAK,EAAE,WAAW,EAAwB,MAAM,UAAU,CAAC;AA4BlE;;GAEG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,OAAO,CAAC,CAO3D;AAED,MAAM,WAAW,oBAAoB;IACnC;;OAEG;IACH,SAAS,EAAE,WAAW,CAAC;IAEvB;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAEjC;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,GAAG,gBAAgB,CAAC;CAC7C;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,eAAe,EAAE,SAAS,CAAC,oBAAoB,CA+L3D,CAAC"}
@@ -1,7 +1,10 @@
1
- import { delegateEvents, createComponent, getNextElement, template, getNextMarker, insert, runHydrationEvents, effect, className, style, use } from "solid-js/web";
1
+ import { delegateEvents, createComponent, getNextElement, template, getNextMarker, insert, runHydrationEvents, mergeProps, effect, className, style, use } from "solid-js/web";
2
2
  import { createSignal, createEffect, onCleanup, Show } from "solid-js";
3
3
  import { useExpanded, ExpandableWrapper } from "./ExpandableWrapper.js";
4
- var _tmpl$ = /* @__PURE__ */ template(`<h3 class="text-sm font-semibold text-gray-900 dark:text-white">`), _tmpl$2 = /* @__PURE__ */ template(`<button class="opacity-0 group-hover:opacity-60 hover:!opacity-100 px-2 py-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-all shadow-sm"title="Download PNG"aria-label="Download chart as PNG"><svg class="w-3 h-3 text-gray-500 dark:text-gray-400"fill=none viewBox="0 0 24 24"stroke=currentColor><path stroke-linecap=round stroke-linejoin=round stroke-width=2 d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z">`), _tmpl$3 = /* @__PURE__ */ template(`<div class="flex items-center justify-between mb-3 flex-shrink-0"><!$><!/><!$><!/>`), _tmpl$4 = /* @__PURE__ */ template(`<div class="absolute inset-0 flex items-center justify-center bg-white/80 dark:bg-gray-800/80"><div class="flex flex-col items-center gap-2"><div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div><span class="text-sm text-gray-500 dark:text-gray-400">Loading chart...`), _tmpl$5 = /* @__PURE__ */ template(`<div class="absolute inset-0 flex items-center justify-center p-4 bg-white dark:bg-gray-800"><div class=text-center><div class="inline-flex items-center justify-center w-12 h-12 rounded-full bg-red-100 dark:bg-red-900/20 mb-3"><svg class="w-6 h-6 text-red-600 dark:text-red-400"fill=none viewBox="0 0 24 24"stroke=currentColor><path stroke-linecap=round stroke-linejoin=round stroke-width=2 d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path></svg></div><p class="text-red-600 dark:text-red-400 text-sm font-medium">Chart Error</p><p class="text-gray-600 dark:text-gray-400 text-xs mt-1 max-w-xs">`), _tmpl$6 = /* @__PURE__ */ template(`<div><!$><!/><!$><!/><!$><!/><div><canvas>`);
4
+ import { DegradedFallback } from "./DegradedFallback.js";
5
+ import { chartToDegradedTable } from "../utils/degraded-projections.js";
6
+ import { useTelemetry } from "../context/MCPUITelemetryContext.js";
7
+ var _tmpl$ = /* @__PURE__ */ template(`<h3 class="text-sm font-semibold text-gray-900 dark:text-white">`), _tmpl$2 = /* @__PURE__ */ template(`<button class="opacity-0 group-hover:opacity-60 hover:!opacity-100 px-2 py-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-all shadow-sm"title="Download PNG"aria-label="Download chart as PNG"><svg class="w-3 h-3 text-gray-500 dark:text-gray-400"fill=none viewBox="0 0 24 24"stroke=currentColor><path stroke-linecap=round stroke-linejoin=round stroke-width=2 d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z">`), _tmpl$3 = /* @__PURE__ */ template(`<div class="flex items-center justify-between mb-3 flex-shrink-0"><!$><!/><!$><!/>`), _tmpl$4 = /* @__PURE__ */ template(`<div class="absolute inset-0 flex items-center justify-center bg-white/80 dark:bg-gray-800/80"><div class="flex flex-col items-center gap-2"><div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div><span class="text-sm text-gray-500 dark:text-gray-400">Loading chart...`), _tmpl$5 = /* @__PURE__ */ template(`<div><!$><!/><!$><!/><!$><!/><div><canvas>`);
5
8
  let ChartJS = null;
6
9
  let chartJSLoadPromise = null;
7
10
  const loadChartJS = async () => {
@@ -32,6 +35,7 @@ const ChartJSRenderer = (props) => {
32
35
  let chartInstance;
33
36
  const params = () => props.component.params;
34
37
  const isExpanded = useExpanded();
38
+ const telemetry = useTelemetry();
35
39
  const exportEnabled = () => params().exportable !== false;
36
40
  const copyDataJSON = () => JSON.stringify({
37
41
  type: params().type,
@@ -46,7 +50,7 @@ const ChartJSRenderer = (props) => {
46
50
  a.click();
47
51
  };
48
52
  createEffect(async () => {
49
- var _a, _b, _c, _d, _e;
53
+ var _a, _b, _c, _d, _e, _f;
50
54
  if (!canvasRef) return;
51
55
  const chartParams = params();
52
56
  setIsLoading(true);
@@ -101,7 +105,14 @@ const ChartJSRenderer = (props) => {
101
105
  const error2 = err instanceof Error ? err : new Error("Chart rendering failed");
102
106
  setError(error2.message);
103
107
  setIsLoading(false);
104
- (_e = props.onError) == null ? void 0 : _e.call(props, error2);
108
+ telemetry == null ? void 0 : telemetry.dispatch({
109
+ type: "render:error",
110
+ errorMessage: error2.message,
111
+ id: ((_e = props.component) == null ? void 0 : _e.id) ?? "",
112
+ componentType: "chart",
113
+ ts: Date.now()
114
+ });
115
+ (_f = props.onError) == null ? void 0 : _f.call(props, error2);
105
116
  }
106
117
  });
107
118
  onCleanup(() => {
@@ -122,7 +133,7 @@ const ChartJSRenderer = (props) => {
122
133
  return props.toolbarVariant;
123
134
  },
124
135
  get children() {
125
- var _el$ = getNextElement(_tmpl$6), _el$15 = _el$.firstChild, [_el$16, _co$3] = getNextMarker(_el$15.nextSibling), _el$17 = _el$16.nextSibling, [_el$18, _co$4] = getNextMarker(_el$17.nextSibling), _el$19 = _el$18.nextSibling, [_el$20, _co$5] = getNextMarker(_el$19.nextSibling), _el$13 = _el$20.nextSibling, _el$14 = _el$13.firstChild;
136
+ var _el$ = getNextElement(_tmpl$5), _el$10 = _el$.firstChild, [_el$11, _co$3] = getNextMarker(_el$10.nextSibling), _el$12 = _el$11.nextSibling, [_el$13, _co$4] = getNextMarker(_el$12.nextSibling), _el$14 = _el$13.nextSibling, [_el$15, _co$5] = getNextMarker(_el$14.nextSibling), _el$0 = _el$15.nextSibling, _el$1 = _el$0.firstChild;
126
137
  insert(_el$, createComponent(Show, {
127
138
  get when() {
128
139
  return params().title || exportEnabled();
@@ -152,7 +163,7 @@ const ChartJSRenderer = (props) => {
152
163
  }), _el$8, _co$2);
153
164
  return _el$2;
154
165
  }
155
- }), _el$16, _co$3);
166
+ }), _el$11, _co$3);
156
167
  insert(_el$, createComponent(Show, {
157
168
  get when() {
158
169
  return isLoading();
@@ -160,19 +171,22 @@ const ChartJSRenderer = (props) => {
160
171
  get children() {
161
172
  return getNextElement(_tmpl$4);
162
173
  }
163
- }), _el$18, _co$4);
174
+ }), _el$13, _co$4);
164
175
  insert(_el$, createComponent(Show, {
165
176
  get when() {
166
177
  return error();
167
178
  },
168
179
  get children() {
169
- var _el$0 = getNextElement(_tmpl$5), _el$1 = _el$0.firstChild, _el$10 = _el$1.firstChild, _el$11 = _el$10.nextSibling, _el$12 = _el$11.nextSibling;
170
- insert(_el$12, error);
171
- return _el$0;
180
+ return createComponent(DegradedFallback, mergeProps({
181
+ get message() {
182
+ return `Chart rendering failed: ${error()}`;
183
+ },
184
+ caption: "Showing the chart data as a table — the interactive chart is unavailable."
185
+ }, () => chartToDegradedTable(params() ?? {})));
172
186
  }
173
- }), _el$20, _co$5);
187
+ }), _el$15, _co$5);
174
188
  var _ref$ = canvasRef;
175
- typeof _ref$ === "function" ? use(_ref$, _el$14) : canvasRef = _el$14;
189
+ typeof _ref$ === "function" ? use(_ref$, _el$1) : canvasRef = _el$1;
176
190
  effect((_p$) => {
177
191
  var _v$ = `relative w-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden p-4 group ${isExpanded() ? "flex-1 min-h-0 flex flex-col" : ""}`, _v$2 = `w-full ${isExpanded() ? "flex-1 min-h-0" : ""}`, _v$3 = error() ? {
178
192
  display: "none"
@@ -184,8 +198,8 @@ const ChartJSRenderer = (props) => {
184
198
  display: "block"
185
199
  };
186
200
  _v$ !== _p$.e && className(_el$, _p$.e = _v$);
187
- _v$2 !== _p$.t && className(_el$13, _p$.t = _v$2);
188
- _p$.a = style(_el$13, _v$3, _p$.a);
201
+ _v$2 !== _p$.t && className(_el$0, _p$.t = _v$2);
202
+ _p$.a = style(_el$0, _v$3, _p$.a);
189
203
  return _p$;
190
204
  }, {
191
205
  e: void 0,
@@ -1 +1 @@
1
- {"version":3,"file":"ChartJSRenderer.js","sources":["../../src/components/ChartJSRenderer.tsx"],"sourcesContent":["/**\n * ChartJSRenderer - Native Chart.js rendering\n * Sprint 4: State & Charts\n *\n * Requires chart.js peer dependency:\n * ```\n * pnpm add chart.js\n * ```\n */\n\nimport { Component, createEffect, onCleanup, createSignal, Show } from 'solid-js'\nimport type { UIComponent, ChartComponentParams } from '../types'\nimport { ExpandableWrapper, useExpanded } from './ExpandableWrapper'\n\n// Lazy load Chart.js to avoid bundling if not used\nlet ChartJS: any = null\nlet chartJSLoadPromise: Promise<any> | null = null\n\nconst loadChartJS = async () => {\n if (ChartJS) return ChartJS\n\n if (!chartJSLoadPromise) {\n chartJSLoadPromise = import('chart.js/auto')\n .then((module) => {\n ChartJS = module.default || module.Chart\n return ChartJS\n })\n .catch((err) => {\n chartJSLoadPromise = null\n throw err\n })\n }\n\n return chartJSLoadPromise\n}\n\n/**\n * Check if Chart.js is available\n */\nexport async function isChartJSAvailable(): Promise<boolean> {\n try {\n await loadChartJS()\n return true\n } catch {\n return false\n }\n}\n\nexport interface ChartJSRendererProps {\n /**\n * UIComponent with chart params\n */\n component: UIComponent\n\n /**\n * Error callback\n */\n onError?: (error: Error) => void\n\n /**\n * Forwarded to the underlying `<ExpandableWrapper>` (v6.3.1).\n * @see ExpandableWrapperProps.toolbarVariant\n */\n toolbarVariant?: 'hover' | 'always-visible'\n}\n\n/**\n * Native Chart.js renderer component\n *\n * @example\n * ```tsx\n * const chartComponent: UIComponent = {\n * id: 'revenue-chart',\n * type: 'chart',\n * position: { colStart: 1, colSpan: 6 },\n * params: {\n * type: 'bar',\n * title: 'Monthly Revenue',\n * data: {\n * labels: ['Jan', 'Feb', 'Mar'],\n * datasets: [{ label: 'Revenue', data: [100, 200, 150] }]\n * },\n * renderer: 'native',\n * },\n * }\n * <ChartJSRenderer component={chartComponent} />\n * ```\n */\nexport const ChartJSRenderer: Component<ChartJSRendererProps> = (props) => {\n const [isLoading, setIsLoading] = createSignal(true)\n const [error, setError] = createSignal<string>()\n let canvasRef: HTMLCanvasElement | undefined\n let chartInstance: any\n\n const params = () => props.component.params as ChartComponentParams\n const isExpanded = useExpanded()\n\n // v6.1.0 — export visibility :\n // - undefined / true → button shown (new default, was opt-in)\n // - false → button hidden (explicit opt-out, unchanged)\n const exportEnabled = () => params().exportable !== false\n\n // v6.1.0 — copy data for the ExpandableWrapper modal-header copy button.\n // Lazy-stringified each time the button is clicked.\n const copyDataJSON = () => JSON.stringify({ type: params().type, data: params().data }, null, 2)\n\n // Chart PNG export\n const handleExportPNG = () => {\n if (!canvasRef) return\n const url = canvasRef.toDataURL('image/png')\n const a = document.createElement('a')\n a.href = url\n a.download = `${(params().title || 'chart').replace(/\\s+/g, '-').toLowerCase()}.png`\n a.click()\n }\n\n // Create/update chart when params change\n createEffect(async () => {\n if (!canvasRef) return\n\n // Access params to track dependencies\n const chartParams = params()\n\n setIsLoading(true)\n setError(undefined)\n\n try {\n const Chart = await loadChartJS()\n\n // Destroy previous instance\n if (chartInstance) {\n chartInstance.destroy()\n chartInstance = null\n }\n\n // Build options, merging time-axis config if present (v3.1.0)\n const baseOptions: any = {\n responsive: true,\n maintainAspectRatio: false,\n ...chartParams.options,\n plugins: {\n ...chartParams.options?.plugins,\n legend: {\n display: true,\n position: 'bottom',\n ...chartParams.options?.plugins?.legend,\n },\n },\n }\n\n // Time-series axis (v3.1.0)\n if (chartParams.timeAxis) {\n const ta = chartParams.timeAxis\n baseOptions.scales = {\n ...baseOptions.scales,\n x: {\n ...baseOptions.scales?.x,\n type: 'time',\n time: {\n parser: ta.parser,\n unit: ta.unit,\n tooltipFormat: ta.tooltipFormat,\n },\n ...(ta.min ? { min: ta.min } : {}),\n ...(ta.max ? { max: ta.max } : {}),\n },\n }\n }\n\n // Create new chart\n chartInstance = new Chart(canvasRef, {\n type: chartParams.type,\n data: chartParams.data,\n options: baseOptions,\n })\n\n setIsLoading(false)\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Chart rendering failed')\n setError(error.message)\n setIsLoading(false)\n props.onError?.(error)\n }\n })\n\n // Cleanup on unmount\n onCleanup(() => {\n if (chartInstance) {\n chartInstance.destroy()\n chartInstance = null\n }\n })\n\n return (\n <ExpandableWrapper\n title={params().title || 'Chart'}\n copyData={copyDataJSON()}\n copyLabel=\"Copy chart data (JSON)\"\n toolbarVariant={props.toolbarVariant}\n >\n <div class={`relative w-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden p-4 group ${\n isExpanded() ? 'flex-1 min-h-0 flex flex-col' : ''\n }`}>\n <Show when={params().title || exportEnabled()}>\n <div class=\"flex items-center justify-between mb-3 flex-shrink-0\">\n <Show when={params().title}>\n <h3 class=\"text-sm font-semibold text-gray-900 dark:text-white\">\n {params().title}\n </h3>\n </Show>\n <Show when={exportEnabled()}>\n <button\n onClick={handleExportPNG}\n class=\"opacity-0 group-hover:opacity-60 hover:!opacity-100 px-2 py-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-all shadow-sm\"\n title=\"Download PNG\"\n aria-label=\"Download chart as PNG\"\n >\n <svg class=\"w-3 h-3 text-gray-500 dark:text-gray-400\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z\" />\n </svg>\n </button>\n </Show>\n </div>\n </Show>\n\n <Show when={isLoading()}>\n <div class=\"absolute inset-0 flex items-center justify-center bg-white/80 dark:bg-gray-800/80\">\n <div class=\"flex flex-col items-center gap-2\">\n <div class=\"animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600\" />\n <span class=\"text-sm text-gray-500 dark:text-gray-400\">Loading chart...</span>\n </div>\n </div>\n </Show>\n\n <Show when={error()}>\n <div class=\"absolute inset-0 flex items-center justify-center p-4 bg-white dark:bg-gray-800\">\n <div class=\"text-center\">\n <div class=\"inline-flex items-center justify-center w-12 h-12 rounded-full bg-red-100 dark:bg-red-900/20 mb-3\">\n <svg\n class=\"w-6 h-6 text-red-600 dark:text-red-400\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n </div>\n <p class=\"text-red-600 dark:text-red-400 text-sm font-medium\">Chart Error</p>\n <p class=\"text-gray-600 dark:text-gray-400 text-xs mt-1 max-w-xs\">{error()}</p>\n </div>\n </div>\n </Show>\n\n <div\n class={`w-full ${isExpanded() ? 'flex-1 min-h-0' : ''}`}\n style={\n error()\n ? { display: 'none' }\n : isExpanded()\n ? { height: '100%', display: 'block' }\n : { height: params().height || '250px', display: 'block' }\n }\n >\n <canvas ref={canvasRef} />\n </div>\n </div>\n </ExpandableWrapper>\n )\n}\n"],"names":["ChartJS","chartJSLoadPromise","loadChartJS","then","module","default","Chart","catch","err","isChartJSAvailable","ChartJSRenderer","props","isLoading","setIsLoading","createSignal","error","setError","canvasRef","chartInstance","params","component","isExpanded","useExpanded","exportEnabled","exportable","copyDataJSON","JSON","stringify","type","data","handleExportPNG","url","toDataURL","a","document","createElement","href","download","title","replace","toLowerCase","click","createEffect","chartParams","undefined","destroy","baseOptions","responsive","maintainAspectRatio","options","plugins","legend","display","position","timeAxis","ta","scales","x","time","parser","unit","tooltipFormat","min","max","Error","message","onError","onCleanup","_$createComponent","ExpandableWrapper","copyData","copyLabel","toolbarVariant","children","_el$","_$getNextElement","_tmpl$6","_el$15","firstChild","_el$16","_co$3","_$getNextMarker","nextSibling","_el$17","_el$18","_co$4","_el$19","_el$20","_co$5","_el$13","_el$14","_$insert","Show","when","_el$2","_tmpl$3","_el$5","_el$6","_co$","_el$7","_el$8","_co$2","_el$3","_tmpl$","_el$4","_tmpl$2","$$click","_$runHydrationEvents","_tmpl$4","_el$0","_tmpl$5","_el$1","_el$10","_el$11","_el$12","_ref$","_$use","_$effect","_p$","_v$","_v$2","_v$3","height","e","_$className","t","_$style","_$delegateEvents"],"mappings":";;;;AAeA,IAAIA,UAAe;AACnB,IAAIC,qBAA0C;AAE9C,MAAMC,cAAc,YAAY;AAC9B,MAAIF,QAAS,QAAOA;AAEpB,MAAI,CAACC,oBAAoB;AACvBA,yBAAqB,OAAO,eAAe,EACxCE,KAAMC,CAAAA,WAAW;AAChBJ,gBAAUI,OAAOC,WAAWD,OAAOE;AACnC,aAAON;AAAAA,IACT,CAAC,EACAO,MAAOC,CAAAA,QAAQ;AACdP,2BAAqB;AACrB,YAAMO;AAAAA,IACR,CAAC;AAAA,EACL;AAEA,SAAOP;AACT;AAKA,eAAsBQ,qBAAuC;AAC3D,MAAI;AACF,UAAMP,YAAAA;AACN,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AA0CO,MAAMQ,kBAAoDC,CAAAA,UAAU;AACzE,QAAM,CAACC,WAAWC,YAAY,IAAIC,aAAa,IAAI;AACnD,QAAM,CAACC,OAAOC,QAAQ,IAAIF,aAAAA;AAC1B,MAAIG;AACJ,MAAIC;AAEJ,QAAMC,SAASA,MAAMR,MAAMS,UAAUD;AACrC,QAAME,aAAaC,YAAAA;AAKnB,QAAMC,gBAAgBA,MAAMJ,OAAAA,EAASK,eAAe;AAIpD,QAAMC,eAAeA,MAAMC,KAAKC,UAAU;AAAA,IAAEC,MAAMT,SAASS;AAAAA,IAAMC,MAAMV,SAASU;AAAAA,EAAAA,GAAQ,MAAM,CAAC;AAG/F,QAAMC,kBAAkBA,MAAM;AAC5B,QAAI,CAACb,UAAW;AAChB,UAAMc,MAAMd,UAAUe,UAAU,WAAW;AAC3C,UAAMC,IAAIC,SAASC,cAAc,GAAG;AACpCF,MAAEG,OAAOL;AACTE,MAAEI,WAAW,IAAIlB,OAAAA,EAASmB,SAAS,SAASC,QAAQ,QAAQ,GAAG,EAAEC,YAAAA,CAAa;AAC9EP,MAAEQ,MAAAA;AAAAA,EACJ;AAGAC,eAAa,YAAY;;AACvB,QAAI,CAACzB,UAAW;AAGhB,UAAM0B,cAAcxB,OAAAA;AAEpBN,iBAAa,IAAI;AACjBG,aAAS4B,MAAS;AAElB,QAAI;AACF,YAAMtC,QAAQ,MAAMJ,YAAAA;AAGpB,UAAIgB,eAAe;AACjBA,sBAAc2B,QAAAA;AACd3B,wBAAgB;AAAA,MAClB;AAGA,YAAM4B,cAAmB;AAAA,QACvBC,YAAY;AAAA,QACZC,qBAAqB;AAAA,QACrB,GAAGL,YAAYM;AAAAA,QACfC,SAAS;AAAA,UACP,IAAGP,iBAAYM,YAAZN,mBAAqBO;AAAAA,UACxBC,QAAQ;AAAA,YACNC,SAAS;AAAA,YACTC,UAAU;AAAA,YACV,IAAGV,uBAAYM,YAAZN,mBAAqBO,YAArBP,mBAA8BQ;AAAAA,UAAAA;AAAAA,QACnC;AAAA,MACF;AAIF,UAAIR,YAAYW,UAAU;AACxB,cAAMC,KAAKZ,YAAYW;AACvBR,oBAAYU,SAAS;AAAA,UACnB,GAAGV,YAAYU;AAAAA,UACfC,GAAG;AAAA,YACD,IAAGX,iBAAYU,WAAZV,mBAAoBW;AAAAA,YACvB7B,MAAM;AAAA,YACN8B,MAAM;AAAA,cACJC,QAAQJ,GAAGI;AAAAA,cACXC,MAAML,GAAGK;AAAAA,cACTC,eAAeN,GAAGM;AAAAA,YAAAA;AAAAA,YAEpB,GAAIN,GAAGO,MAAM;AAAA,cAAEA,KAAKP,GAAGO;AAAAA,YAAAA,IAAQ,CAAA;AAAA,YAC/B,GAAIP,GAAGQ,MAAM;AAAA,cAAEA,KAAKR,GAAGQ;AAAAA,YAAAA,IAAQ,CAAA;AAAA,UAAC;AAAA,QAClC;AAAA,MAEJ;AAGA7C,sBAAgB,IAAIZ,MAAMW,WAAW;AAAA,QACnCW,MAAMe,YAAYf;AAAAA,QAClBC,MAAMc,YAAYd;AAAAA,QAClBoB,SAASH;AAAAA,MAAAA,CACV;AAEDjC,mBAAa,KAAK;AAAA,IACpB,SAASL,KAAK;AACZ,YAAMO,SAAQP,eAAewD,QAAQxD,MAAM,IAAIwD,MAAM,wBAAwB;AAC7EhD,eAASD,OAAMkD,OAAO;AACtBpD,mBAAa,KAAK;AAClBF,kBAAMuD,YAANvD,+BAAgBI;AAAAA,IAClB;AAAA,EACF,CAAC;AAGDoD,YAAU,MAAM;AACd,QAAIjD,eAAe;AACjBA,oBAAc2B,QAAAA;AACd3B,sBAAgB;AAAA,IAClB;AAAA,EACF,CAAC;AAED,SAAAkD,gBACGC,mBAAiB;AAAA,IAAA,IAChB/B,QAAK;AAAA,aAAEnB,OAAAA,EAASmB,SAAS;AAAA,IAAO;AAAA,IAAA,IAChCgC,WAAQ;AAAA,aAAE7C,aAAAA;AAAAA,IAAc;AAAA,IACxB8C,WAAS;AAAA,IAAA,IACTC,iBAAc;AAAA,aAAE7D,MAAM6D;AAAAA,IAAc;AAAA,IAAA,IAAAC,WAAA;AAAA,UAAAC,OAAAC,eAAAC,OAAA,GAAAC,SAAAH,KAAAI,YAAA,CAAAC,QAAAC,KAAA,IAAAC,cAAAJ,OAAAK,WAAA,GAAAC,SAAAJ,OAAAG,aAAA,CAAAE,QAAAC,KAAA,IAAAJ,cAAAE,OAAAD,WAAA,GAAAI,SAAAF,OAAAF,aAAA,CAAAK,QAAAC,KAAA,IAAAP,cAAAK,OAAAJ,WAAA,GAAAO,SAAAF,OAAAL,aAAAQ,SAAAD,OAAAX;AAAAa,aAAAjB,MAAAN,gBAKjCwB,MAAI;AAAA,QAAA,IAACC,OAAI;AAAA,iBAAE1E,OAAAA,EAASmB,SAASf,cAAAA;AAAAA,QAAe;AAAA,QAAA,IAAAkD,WAAA;AAAA,cAAAqB,QAAAnB,eAAAoB,OAAA,GAAAC,QAAAF,MAAAhB,YAAA,CAAAmB,OAAAC,IAAA,IAAAjB,cAAAe,MAAAd,WAAA,GAAAiB,QAAAF,MAAAf,aAAA,CAAAkB,OAAAC,KAAA,IAAApB,cAAAkB,MAAAjB,WAAA;AAAAS,iBAAAG,OAAA1B,gBAExCwB,MAAI;AAAA,YAAA,IAACC,OAAI;AAAA,qBAAE1E,SAASmB;AAAAA,YAAK;AAAA,YAAA,IAAAmC,WAAA;AAAA,kBAAA6B,QAAA3B,eAAA4B,MAAA;AAAAZ,qBAAAW,OAAA,MAErBnF,OAAAA,EAASmB,KAAK;AAAA,qBAAAgE;AAAAA,YAAA;AAAA,UAAA,CAAA,GAAAL,OAAAC,IAAA;AAAAP,iBAAAG,OAAA1B,gBAGlBwB,MAAI;AAAA,YAAA,IAACC,OAAI;AAAA,qBAAEtE,cAAAA;AAAAA,YAAe;AAAA,YAAA,IAAAkD,WAAA;AAAA,kBAAA+B,QAAA7B,eAAA8B,OAAA;AAAAD,oBAAAE,UAEd5E;AAAe6E,iCAAAA;AAAA,qBAAAH;AAAAA,YAAA;AAAA,UAAA,CAAA,GAAAJ,OAAAC,KAAA;AAAA,iBAAAP;AAAAA,QAAA;AAAA,MAAA,CAAA,GAAAf,QAAAC,KAAA;AAAAW,aAAAjB,MAAAN,gBAa/BwB,MAAI;AAAA,QAAA,IAACC,OAAI;AAAA,iBAAEjF,UAAAA;AAAAA,QAAW;AAAA,QAAA,IAAA6D,WAAA;AAAA,iBAAAE,eAAAiC,OAAA;AAAA,QAAA;AAAA,MAAA,CAAA,GAAAxB,QAAAC,KAAA;AAAAM,aAAAjB,MAAAN,gBAStBwB,MAAI;AAAA,QAAA,IAACC,OAAI;AAAA,iBAAE9E,MAAAA;AAAAA,QAAO;AAAA,QAAA,IAAA0D,WAAA;AAAA,cAAAoC,QAAAlC,eAAAmC,OAAA,GAAAC,QAAAF,MAAA/B,YAAAkC,SAAAD,MAAAjC,YAAAmC,SAAAD,OAAA9B,aAAAgC,SAAAD,OAAA/B;AAAAS,iBAAAuB,QAmBsDnG,KAAK;AAAA,iBAAA8F;AAAAA,QAAA;AAAA,MAAA,CAAA,GAAAtB,QAAAC,KAAA;AAAA,UAAA2B,QAe/DlG;AAAS,aAAAkG,UAAA,aAAAC,IAAAD,OAAAzB,MAAA,IAATzE,YAASyE;AAAA2B,aAAAC,CAAAA,QAAA;AAAA,YAAAC,MApEd,wIACVlG,WAAAA,IAAe,iCAAiC,EAAE,IAClDmG,OAyDS,UAAUnG,WAAAA,IAAe,mBAAmB,EAAE,IAAEoG,OAErD1G,UACI;AAAA,UAAEqC,SAAS;AAAA,QAAA,IACX/B,eACE;AAAA,UAAEqG,QAAQ;AAAA,UAAQtE,SAAS;AAAA,QAAA,IAC3B;AAAA,UAAEsE,QAAQvG,SAASuG,UAAU;AAAA,UAAStE,SAAS;AAAA,QAAA;AAASmE,gBAAAD,IAAAK,KAAAC,UAAAlD,MAAA4C,IAAAK,IAAAJ,GAAA;AAAAC,iBAAAF,IAAAO,KAAAD,UAAAnC,QAAA6B,IAAAO,IAAAL,IAAA;AAAAF,YAAArF,IAAA6F,MAAArC,QAAAgC,MAAAH,IAAArF,CAAA;AAAA,eAAAqF;AAAAA,MAAA,GAAA;AAAA,QAAAK,GAAA/E;AAAAA,QAAAiF,GAAAjF;AAAAA,QAAAX,GAAAW;AAAAA,MAAAA,CAAA;AAAA,aAAA8B;AAAAA,IAAA;AAAA,EAAA,CAAA;AAQ1E;AAACqD,eAAA,CAAA,OAAA,CAAA;"}
1
+ {"version":3,"file":"ChartJSRenderer.js","sources":["../../src/components/ChartJSRenderer.tsx"],"sourcesContent":["/**\n * ChartJSRenderer - Native Chart.js rendering\n * Sprint 4: State & Charts\n *\n * Requires chart.js peer dependency:\n * ```\n * pnpm add chart.js\n * ```\n */\n\nimport { Component, createEffect, onCleanup, createSignal, Show } from 'solid-js';\nimport type { UIComponent, ChartComponentParams } from '../types';\nimport { ExpandableWrapper, useExpanded } from './ExpandableWrapper';\nimport { DegradedFallback } from './DegradedFallback';\nimport { chartToDegradedTable } from '../utils/degraded-projections';\nimport { useTelemetry } from '../context/MCPUITelemetryContext';\n\n// Lazy load Chart.js to avoid bundling if not used\nlet ChartJS: any = null;\nlet chartJSLoadPromise: Promise<any> | null = null;\n\nconst loadChartJS = async () => {\n if (ChartJS) return ChartJS;\n\n if (!chartJSLoadPromise) {\n chartJSLoadPromise = import('chart.js/auto')\n .then((module) => {\n ChartJS = module.default || module.Chart;\n return ChartJS;\n })\n .catch((err) => {\n chartJSLoadPromise = null;\n throw err;\n });\n }\n\n return chartJSLoadPromise;\n};\n\n/**\n * Check if Chart.js is available\n */\nexport async function isChartJSAvailable(): Promise<boolean> {\n try {\n await loadChartJS();\n return true;\n } catch {\n return false;\n }\n}\n\nexport interface ChartJSRendererProps {\n /**\n * UIComponent with chart params\n */\n component: UIComponent;\n\n /**\n * Error callback\n */\n onError?: (error: Error) => void;\n\n /**\n * Forwarded to the underlying `<ExpandableWrapper>` (v6.3.1).\n * @see ExpandableWrapperProps.toolbarVariant\n */\n toolbarVariant?: 'hover' | 'always-visible';\n}\n\n/**\n * Native Chart.js renderer component\n *\n * @example\n * ```tsx\n * const chartComponent: UIComponent = {\n * id: 'revenue-chart',\n * type: 'chart',\n * position: { colStart: 1, colSpan: 6 },\n * params: {\n * type: 'bar',\n * title: 'Monthly Revenue',\n * data: {\n * labels: ['Jan', 'Feb', 'Mar'],\n * datasets: [{ label: 'Revenue', data: [100, 200, 150] }]\n * },\n * renderer: 'native',\n * },\n * }\n * <ChartJSRenderer component={chartComponent} />\n * ```\n */\nexport const ChartJSRenderer: Component<ChartJSRendererProps> = (props) => {\n const [isLoading, setIsLoading] = createSignal(true);\n const [error, setError] = createSignal<string>();\n let canvasRef: HTMLCanvasElement | undefined;\n let chartInstance: any;\n\n const params = () => props.component.params as ChartComponentParams;\n const isExpanded = useExpanded();\n const telemetry = useTelemetry();\n\n // v6.1.0 — export visibility :\n // - undefined / true → button shown (new default, was opt-in)\n // - false → button hidden (explicit opt-out, unchanged)\n const exportEnabled = () => params().exportable !== false;\n\n // v6.1.0 — copy data for the ExpandableWrapper modal-header copy button.\n // Lazy-stringified each time the button is clicked.\n const copyDataJSON = () => JSON.stringify({ type: params().type, data: params().data }, null, 2);\n\n // Chart PNG export\n const handleExportPNG = () => {\n if (!canvasRef) return;\n const url = canvasRef.toDataURL('image/png');\n const a = document.createElement('a');\n a.href = url;\n a.download = `${(params().title || 'chart').replace(/\\s+/g, '-').toLowerCase()}.png`;\n a.click();\n };\n\n // Create/update chart when params change\n createEffect(async () => {\n if (!canvasRef) return;\n\n // Access params to track dependencies\n const chartParams = params();\n\n setIsLoading(true);\n setError(undefined);\n\n try {\n const Chart = await loadChartJS();\n\n // Destroy previous instance\n if (chartInstance) {\n chartInstance.destroy();\n chartInstance = null;\n }\n\n // Build options, merging time-axis config if present (v3.1.0)\n const baseOptions: any = {\n responsive: true,\n maintainAspectRatio: false,\n ...chartParams.options,\n plugins: {\n ...chartParams.options?.plugins,\n legend: {\n display: true,\n position: 'bottom',\n ...chartParams.options?.plugins?.legend,\n },\n },\n };\n\n // Time-series axis (v3.1.0)\n if (chartParams.timeAxis) {\n const ta = chartParams.timeAxis;\n baseOptions.scales = {\n ...baseOptions.scales,\n x: {\n ...baseOptions.scales?.x,\n type: 'time',\n time: {\n parser: ta.parser,\n unit: ta.unit,\n tooltipFormat: ta.tooltipFormat,\n },\n ...(ta.min ? { min: ta.min } : {}),\n ...(ta.max ? { max: ta.max } : {}),\n },\n };\n }\n\n // Create new chart\n chartInstance = new Chart(canvasRef, {\n type: chartParams.type,\n data: chartParams.data,\n options: baseOptions,\n });\n\n setIsLoading(false);\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Chart rendering failed');\n setError(error.message);\n setIsLoading(false);\n // Fallback ladder (P2.5): record the failure so it's observable, then\n // degrade to the series table below instead of a blank canvas.\n telemetry?.dispatch({\n type: 'render:error',\n errorMessage: error.message,\n id: props.component?.id ?? '',\n componentType: 'chart',\n ts: Date.now(),\n });\n props.onError?.(error);\n }\n });\n\n // Cleanup on unmount\n onCleanup(() => {\n if (chartInstance) {\n chartInstance.destroy();\n chartInstance = null;\n }\n });\n\n return (\n <ExpandableWrapper\n title={params().title || 'Chart'}\n copyData={copyDataJSON()}\n copyLabel=\"Copy chart data (JSON)\"\n toolbarVariant={props.toolbarVariant}\n >\n <div\n class={`relative w-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden p-4 group ${\n isExpanded() ? 'flex-1 min-h-0 flex flex-col' : ''\n }`}\n >\n <Show when={params().title || exportEnabled()}>\n <div class=\"flex items-center justify-between mb-3 flex-shrink-0\">\n <Show when={params().title}>\n <h3 class=\"text-sm font-semibold text-gray-900 dark:text-white\">{params().title}</h3>\n </Show>\n <Show when={exportEnabled()}>\n <button\n onClick={handleExportPNG}\n class=\"opacity-0 group-hover:opacity-60 hover:!opacity-100 px-2 py-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-all shadow-sm\"\n title=\"Download PNG\"\n aria-label=\"Download chart as PNG\"\n >\n <svg\n class=\"w-3 h-3 text-gray-500 dark:text-gray-400\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z\"\n />\n </svg>\n </button>\n </Show>\n </div>\n </Show>\n\n <Show when={isLoading()}>\n <div class=\"absolute inset-0 flex items-center justify-center bg-white/80 dark:bg-gray-800/80\">\n <div class=\"flex flex-col items-center gap-2\">\n <div class=\"animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600\" />\n <span class=\"text-sm text-gray-500 dark:text-gray-400\">Loading chart...</span>\n </div>\n </div>\n </Show>\n\n {/* Fallback ladder (P2.5): degrade to a series table on render error\n instead of a bare \"Chart Error\" message. */}\n <Show when={error()}>\n <DegradedFallback\n message={`Chart rendering failed: ${error()}`}\n caption=\"Showing the chart data as a table — the interactive chart is unavailable.\"\n {...chartToDegradedTable(params() ?? {})}\n />\n </Show>\n\n <div\n class={`w-full ${isExpanded() ? 'flex-1 min-h-0' : ''}`}\n style={\n error()\n ? { display: 'none' }\n : isExpanded()\n ? { height: '100%', display: 'block' }\n : { height: params().height || '250px', display: 'block' }\n }\n >\n <canvas ref={canvasRef} />\n </div>\n </div>\n </ExpandableWrapper>\n );\n};\n"],"names":["ChartJS","chartJSLoadPromise","loadChartJS","then","module","default","Chart","catch","err","isChartJSAvailable","ChartJSRenderer","props","isLoading","setIsLoading","createSignal","error","setError","canvasRef","chartInstance","params","component","isExpanded","useExpanded","telemetry","useTelemetry","exportEnabled","exportable","copyDataJSON","JSON","stringify","type","data","handleExportPNG","url","toDataURL","a","document","createElement","href","download","title","replace","toLowerCase","click","createEffect","chartParams","undefined","destroy","baseOptions","responsive","maintainAspectRatio","options","plugins","legend","display","position","timeAxis","ta","scales","x","time","parser","unit","tooltipFormat","min","max","Error","message","dispatch","errorMessage","id","componentType","ts","Date","now","onError","onCleanup","_$createComponent","ExpandableWrapper","copyData","copyLabel","toolbarVariant","children","_el$","_$getNextElement","_tmpl$5","_el$10","firstChild","_el$11","_co$3","_$getNextMarker","nextSibling","_el$12","_el$13","_co$4","_el$14","_el$15","_co$5","_el$0","_el$1","_$insert","Show","when","_el$2","_tmpl$3","_el$5","_el$6","_co$","_el$7","_el$8","_co$2","_el$3","_tmpl$","_el$4","_tmpl$2","$$click","_$runHydrationEvents","_tmpl$4","DegradedFallback","_$mergeProps","caption","chartToDegradedTable","_ref$","_$use","_$effect","_p$","_v$","_v$2","_v$3","height","e","_$className","t","_$style","_$delegateEvents"],"mappings":"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"}
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const web = require("solid-js/web");
4
+ const solidJs = require("solid-js");
5
+ var _tmpl$ = /* @__PURE__ */ web.template(`<div class="mt-2 max-h-64 overflow-auto rounded border border-amber-200 dark:border-amber-800"><table class="w-full border-collapse text-left text-xs"><thead class="sticky top-0 bg-amber-100 dark:bg-amber-900/40"><tr></tr></thead><tbody>`), _tmpl$2 = /* @__PURE__ */ web.template(`<p class="mt-1 text-[10px] text-amber-600 dark:text-amber-400">+<!$><!/> more rows not shown.`), _tmpl$3 = /* @__PURE__ */ web.template(`<div class="w-full rounded-lg border border-amber-200 bg-amber-50 p-3 dark:border-amber-800 dark:bg-amber-900/20"role=alert><p class="text-sm font-medium text-amber-900 dark:text-amber-100"></p><p class="mt-0.5 text-xs text-amber-700 dark:text-amber-300"></p><!$><!/>`), _tmpl$4 = /* @__PURE__ */ web.template(`<th class="px-2 py-1 font-medium text-amber-900 dark:text-amber-100">`), _tmpl$5 = /* @__PURE__ */ web.template(`<tr class="border-t border-amber-100 dark:border-amber-800/60">`), _tmpl$6 = /* @__PURE__ */ web.template(`<td class="px-2 py-1 text-amber-800 dark:text-amber-200">`);
6
+ const DegradedFallback = (props) => {
7
+ const maxRows = () => props.maxRows ?? 50;
8
+ const allRows = () => props.rows ?? [];
9
+ const shownRows = () => allRows().slice(0, maxRows());
10
+ const hiddenCount = () => Math.max(0, allRows().length - shownRows().length);
11
+ const hasTable = () => {
12
+ var _a;
13
+ return (((_a = props.columns) == null ? void 0 : _a.length) ?? 0) > 0 && allRows().length > 0;
14
+ };
15
+ return (() => {
16
+ var _el$ = web.getNextElement(_tmpl$3), _el$2 = _el$.firstChild, _el$3 = _el$2.nextSibling, _el$12 = _el$3.nextSibling, [_el$13, _co$2] = web.getNextMarker(_el$12.nextSibling);
17
+ web.insert(_el$2, () => props.message);
18
+ web.insert(_el$3, () => props.caption ?? "Showing the underlying data — the interactive view is unavailable.");
19
+ web.insert(_el$, web.createComponent(solidJs.Show, {
20
+ get when() {
21
+ return hasTable();
22
+ },
23
+ get children() {
24
+ return [(() => {
25
+ var _el$4 = web.getNextElement(_tmpl$), _el$5 = _el$4.firstChild, _el$6 = _el$5.firstChild, _el$7 = _el$6.firstChild, _el$8 = _el$6.nextSibling;
26
+ web.insert(_el$7, web.createComponent(solidJs.For, {
27
+ get each() {
28
+ return props.columns;
29
+ },
30
+ children: (col) => (() => {
31
+ var _el$14 = web.getNextElement(_tmpl$4);
32
+ web.insert(_el$14, col);
33
+ return _el$14;
34
+ })()
35
+ }));
36
+ web.insert(_el$8, web.createComponent(solidJs.For, {
37
+ get each() {
38
+ return shownRows();
39
+ },
40
+ children: (row) => (() => {
41
+ var _el$15 = web.getNextElement(_tmpl$5);
42
+ web.insert(_el$15, web.createComponent(solidJs.For, {
43
+ get each() {
44
+ return props.columns;
45
+ },
46
+ children: (_col, i) => (() => {
47
+ var _el$16 = web.getNextElement(_tmpl$6);
48
+ web.insert(_el$16, () => String(row[i()] ?? ""));
49
+ return _el$16;
50
+ })()
51
+ }));
52
+ return _el$15;
53
+ })()
54
+ }));
55
+ return _el$4;
56
+ })(), web.createComponent(solidJs.Show, {
57
+ get when() {
58
+ return hiddenCount() > 0;
59
+ },
60
+ get children() {
61
+ var _el$9 = web.getNextElement(_tmpl$2), _el$0 = _el$9.firstChild, _el$10 = _el$0.nextSibling, [_el$11, _co$] = web.getNextMarker(_el$10.nextSibling);
62
+ _el$11.nextSibling;
63
+ web.insert(_el$9, hiddenCount, _el$11, _co$);
64
+ return _el$9;
65
+ }
66
+ })];
67
+ }
68
+ }), _el$13, _co$2);
69
+ return _el$;
70
+ })();
71
+ };
72
+ exports.DegradedFallback = DegradedFallback;
73
+ //# sourceMappingURL=DegradedFallback.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DegradedFallback.cjs","sources":["../../src/components/DegradedFallback.tsx"],"sourcesContent":["/**\n * DegradedFallback — the middle rung of the renderer fallback ladder\n * (audit 2026-05-30, P2.5).\n *\n * Each heavy renderer (graph / map / chart) follows the same contract:\n * 1. native render when its peer lib is available and succeeds;\n * 2. **degraded but useful** view when the native render throws — this\n * component: a visible notice + a plain data table so the user still\n * sees the underlying data instead of a blank space;\n * 3. (the caller also emits a `component:error` telemetry event).\n *\n * Pure / presentational — no peer deps, no side effects — so a render-path\n * failure in a heavy lib can never cascade into the fallback itself, and it\n * is trivially unit-testable. Rows/cells are rendered as text; callers are\n * responsible for stringifying complex cell values.\n */\n\nimport { Component, For, Show } from 'solid-js';\n\nexport interface DegradedFallbackProps {\n /** Short, human-readable reason the native render was skipped/failed. */\n message: string;\n /**\n * Column headers for the degraded data table. When omitted (or empty),\n * only the notice banner is shown.\n */\n columns?: string[];\n /** Row data — each row is an array of cells aligned to `columns`. */\n rows?: Array<Array<string | number>>;\n /**\n * Caption under the table. Defaults to a generic\n * \"interactive view unavailable\" line.\n */\n caption?: string;\n /** Max rows to render before truncating (default 50). */\n maxRows?: number;\n}\n\nexport const DegradedFallback: Component<DegradedFallbackProps> = (props) => {\n const maxRows = () => props.maxRows ?? 50;\n const allRows = () => props.rows ?? [];\n const shownRows = () => allRows().slice(0, maxRows());\n const hiddenCount = () => Math.max(0, allRows().length - shownRows().length);\n const hasTable = () => (props.columns?.length ?? 0) > 0 && allRows().length > 0;\n\n return (\n <div\n class=\"w-full rounded-lg border border-amber-200 bg-amber-50 p-3 dark:border-amber-800 dark:bg-amber-900/20\"\n role=\"alert\"\n >\n <p class=\"text-sm font-medium text-amber-900 dark:text-amber-100\">{props.message}</p>\n <p class=\"mt-0.5 text-xs text-amber-700 dark:text-amber-300\">\n {props.caption ?? 'Showing the underlying data — the interactive view is unavailable.'}\n </p>\n\n <Show when={hasTable()}>\n <div class=\"mt-2 max-h-64 overflow-auto rounded border border-amber-200 dark:border-amber-800\">\n <table class=\"w-full border-collapse text-left text-xs\">\n <thead class=\"sticky top-0 bg-amber-100 dark:bg-amber-900/40\">\n <tr>\n <For each={props.columns}>\n {(col) => (\n <th class=\"px-2 py-1 font-medium text-amber-900 dark:text-amber-100\">{col}</th>\n )}\n </For>\n </tr>\n </thead>\n <tbody>\n <For each={shownRows()}>\n {(row) => (\n <tr class=\"border-t border-amber-100 dark:border-amber-800/60\">\n <For each={props.columns}>\n {(_col, i) => (\n <td class=\"px-2 py-1 text-amber-800 dark:text-amber-200\">\n {String(row[i()] ?? '')}\n </td>\n )}\n </For>\n </tr>\n )}\n </For>\n </tbody>\n </table>\n </div>\n <Show when={hiddenCount() > 0}>\n <p class=\"mt-1 text-[10px] text-amber-600 dark:text-amber-400\">\n +{hiddenCount()} more rows not shown.\n </p>\n </Show>\n </Show>\n </div>\n );\n};\n"],"names":["DegradedFallback","props","maxRows","allRows","rows","shownRows","slice","hiddenCount","Math","max","length","hasTable","columns","_el$","_$getNextElement","_tmpl$3","_el$2","firstChild","_el$3","nextSibling","_el$12","_el$13","_co$2","_$getNextMarker","_$insert","message","caption","_$createComponent","Show","when","children","_el$4","_tmpl$","_el$5","_el$6","_el$7","_el$8","For","each","col","_el$14","_tmpl$4","row","_el$15","_tmpl$5","_col","i","_el$16","_tmpl$6","String","_el$9","_tmpl$2","_el$0","_el$10","_el$11","_co$"],"mappings":";;;;;AAsCO,MAAMA,mBAAsDC,CAAAA,UAAU;AAC3E,QAAMC,UAAUA,MAAMD,MAAMC,WAAW;AACvC,QAAMC,UAAUA,MAAMF,MAAMG,QAAQ,CAAA;AACpC,QAAMC,YAAYA,MAAMF,QAAAA,EAAUG,MAAM,GAAGJ,SAAS;AACpD,QAAMK,cAAcA,MAAMC,KAAKC,IAAI,GAAGN,UAAUO,SAASL,UAAAA,EAAYK,MAAM;AAC3E,QAAMC,WAAWA,MAAAA;;AAAOV,yBAAMW,YAANX,mBAAeS,WAAU,KAAK,KAAKP,UAAUO,SAAS;AAAA;AAE9E,UAAA,MAAA;AAAA,QAAAG,OAAAC,IAAAA,eAAAC,OAAA,GAAAC,QAAAH,KAAAI,YAAAC,QAAAF,MAAAG,aAAAC,SAAAF,MAAAC,aAAA,CAAAE,QAAAC,KAAA,IAAAC,IAAAA,cAAAH,OAAAD,WAAA;AAAAK,QAAAA,OAAAR,OAAA,MAKuEf,MAAMwB,OAAO;AAAAD,QAAAA,OAAAN,OAAA,MAE7EjB,MAAMyB,WAAW,oEAAoE;AAAAF,eAAAX,MAAAc,IAAAA,gBAGvFC,cAAI;AAAA,MAAA,IAACC,OAAI;AAAA,eAAElB,SAAAA;AAAAA,MAAU;AAAA,MAAA,IAAAmB,WAAA;AAAA,eAAA,EAAA,MAAA;AAAA,cAAAC,QAAAjB,IAAAA,eAAAkB,MAAA,GAAAC,QAAAF,MAAAd,YAAAiB,QAAAD,MAAAhB,YAAAkB,QAAAD,MAAAjB,YAAAmB,QAAAF,MAAAf;AAAAK,qBAAAW,OAAAR,IAAAA,gBAKXU,aAAG;AAAA,YAAA,IAACC,OAAI;AAAA,qBAAErC,MAAMW;AAAAA,YAAO;AAAA,YAAAkB,UACpBS,UAAG,MAAA;AAAA,kBAAAC,SAAA1B,IAAAA,eAAA2B,OAAA;AAAAjB,kBAAAA,OAAAgB,QACmED,GAAG;AAAA,qBAAAC;AAAAA,YAAA,GAAA;AAAA,UAAA,CAC1E,CAAA;AAAAhB,qBAAAY,OAAAT,IAAAA,gBAKJU,aAAG;AAAA,YAAA,IAACC,OAAI;AAAA,qBAAEjC,UAAAA;AAAAA,YAAW;AAAA,YAAAyB,UAClBY,UAAG,MAAA;AAAA,kBAAAC,SAAA7B,IAAAA,eAAA8B,OAAA;AAAApB,yBAAAmB,QAAAhB,IAAAA,gBAEAU,aAAG;AAAA,gBAAA,IAACC,OAAI;AAAA,yBAAErC,MAAMW;AAAAA,gBAAO;AAAA,gBAAAkB,UACrBA,CAACe,MAAMC,OAAC,MAAA;AAAA,sBAAAC,SAAAjC,IAAAA,eAAAkC,OAAA;AAAAxB,sBAAAA,OAAAuB,QAAA,MAEJE,OAAOP,IAAII,GAAG,KAAK,EAAE,CAAC;AAAA,yBAAAC;AAAAA,gBAAA,GAAA;AAAA,cAAA,CAE1B,CAAA;AAAA,qBAAAJ;AAAAA,YAAA,GAAA;AAAA,UAAA,CAGN,CAAA;AAAA,iBAAAZ;AAAAA,QAAA,GAAA,GAAAJ,IAAAA,gBAKRC,cAAI;AAAA,UAAA,IAACC,OAAI;AAAA,mBAAEtB,gBAAgB;AAAA,UAAC;AAAA,UAAA,IAAAuB,WAAA;AAAA,gBAAAoB,QAAApC,IAAAA,eAAAqC,OAAA,GAAAC,QAAAF,MAAAjC,YAAAoC,SAAAD,MAAAjC,aAAA,CAAAmC,QAAAC,IAAA,IAAAhC,IAAAA,cAAA8B,OAAAlC,WAAA;AAAAmC,mBAAAnC;AAAAK,gBAAAA,OAAA0B,OAEvB3C,aAAW+C,QAAAC,IAAA;AAAA,mBAAAL;AAAAA,UAAA;AAAA,QAAA,CAAA,CAAA;AAAA,MAAA;AAAA,IAAA,CAAA,GAAA7B,QAAAC,KAAA;AAAA,WAAAT;AAAAA,EAAA,GAAA;AAMzB;;"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * DegradedFallback — the middle rung of the renderer fallback ladder
3
+ * (audit 2026-05-30, P2.5).
4
+ *
5
+ * Each heavy renderer (graph / map / chart) follows the same contract:
6
+ * 1. native render when its peer lib is available and succeeds;
7
+ * 2. **degraded but useful** view when the native render throws — this
8
+ * component: a visible notice + a plain data table so the user still
9
+ * sees the underlying data instead of a blank space;
10
+ * 3. (the caller also emits a `component:error` telemetry event).
11
+ *
12
+ * Pure / presentational — no peer deps, no side effects — so a render-path
13
+ * failure in a heavy lib can never cascade into the fallback itself, and it
14
+ * is trivially unit-testable. Rows/cells are rendered as text; callers are
15
+ * responsible for stringifying complex cell values.
16
+ */
17
+ import { Component } from 'solid-js';
18
+ export interface DegradedFallbackProps {
19
+ /** Short, human-readable reason the native render was skipped/failed. */
20
+ message: string;
21
+ /**
22
+ * Column headers for the degraded data table. When omitted (or empty),
23
+ * only the notice banner is shown.
24
+ */
25
+ columns?: string[];
26
+ /** Row data — each row is an array of cells aligned to `columns`. */
27
+ rows?: Array<Array<string | number>>;
28
+ /**
29
+ * Caption under the table. Defaults to a generic
30
+ * "interactive view unavailable" line.
31
+ */
32
+ caption?: string;
33
+ /** Max rows to render before truncating (default 50). */
34
+ maxRows?: number;
35
+ }
36
+ export declare const DegradedFallback: Component<DegradedFallbackProps>;
37
+ //# sourceMappingURL=DegradedFallback.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DegradedFallback.d.ts","sourceRoot":"","sources":["../../src/components/DegradedFallback.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,SAAS,EAAa,MAAM,UAAU,CAAC;AAEhD,MAAM,WAAW,qBAAqB;IACpC,yEAAyE;IACzE,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,qEAAqE;IACrE,IAAI,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC;IACrC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yDAAyD;IACzD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,eAAO,MAAM,gBAAgB,EAAE,SAAS,CAAC,qBAAqB,CAsD7D,CAAC"}