@seed-ship/mcp-ui-solid 6.8.0 → 6.8.2

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.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,53 @@ 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.8.2] - 2026-05-30
9
+
10
+ ### Fixed — `type:'map'` rejected when it renders purely from GeoJSON
11
+
12
+ `validateComponent` rejected a `type:'map'` as `INVALID_MAP` unless it had
13
+ `center` or `markers`. But since spec@5.2.0 a map can render purely from
14
+ `params.geojson`, named `layers`, or a `pmtiles` source (e.g. a Cadastre /
15
+ choropleth map with an auto-fit viewport and no markers). Those valid maps —
16
+ which `<MapRenderer>` draws fine — were blocked before reaching the renderer.
17
+
18
+ The validator now accepts a map carrying **any** of `center`, `markers`,
19
+ `geojson`, `layers`, or `pmtiles`, aligning the Solid check with
20
+ `MapComponentParamsSchema`. A genuinely empty map (none of those) is still
21
+ rejected. Per audit `docs/briefs/AUDIT-2026-05-30-visual-renderers-g6-ontology.md` (P1.1).
22
+
23
+ ## [6.8.1] - 2026-05-30
24
+
25
+ ### Fixed — `type:'graph'` crashed with "renderer is not a function"
26
+
27
+ `GraphRenderer` passed `renderer: 'canvas' | 'svg'` (a **string**) to the
28
+ G6 v5 `Graph` constructor. In G6 v5 `renderer` is a factory
29
+ `(layer) => IRenderer`, not a string (that was the v4 contract), so G6 threw
30
+ `renderer is not a function` — and because the default path also passed the
31
+ string `'canvas'`, **every** `type:'graph'` component crashed, not just the
32
+ `svg` one. This was an upstream bug in `mcp-ui-solid`, not a missing peer
33
+ dependency in consumer apps.
34
+
35
+ - Default (canvas): the `renderer` field is now **omitted** — G6 uses its
36
+ built-in canvas renderer (documented default `() => new CanvasRenderer()`).
37
+ - `rendererPref: 'svg'`: reserved but **not wired yet** — it degrades to the
38
+ canvas default with a one-time `console.warn` instead of injecting the
39
+ faulty string. A real G6 v5 SVG renderer needs the `@antv/g-svg` factory
40
+ (a transitive dep of `@antv/g6`, not statically resolvable at build time);
41
+ wiring it behind proper optional-peer resolution is a follow-up.
42
+
43
+ ### Tests
44
+
45
+ - New `GraphRenderer.g6.test.tsx` — the previously-missing "G6 available"
46
+ contract path (the gap that let this regression ship). Mocks only the
47
+ `Graph` constructor and asserts: `Graph` is constructed, `render()` is
48
+ called, the default config carries **no** `renderer` field, and a string
49
+ `renderer` is never passed (incl. the `svg` path).
50
+
51
+ Audit: `docs/briefs/AUDIT-2026-05-30-visual-renderers-g6-ontology.md` (P0).
52
+ The brief `BRIEF-graph-component-g6.md` was corrected to match (its example
53
+ and test expectation carried the same faulty string contract).
54
+
8
55
  ## [6.8.0] - 2026-05-22
9
56
 
10
57
  ### Changed — payload-size limit raised 50KB → 512KB
@@ -116,9 +116,11 @@ const GraphRenderer = (props) => {
116
116
  edges: p.edges ?? []
117
117
  },
118
118
  layout: resolveLayout(p),
119
- behaviors: resolveBehaviors(p),
120
- renderer: p.rendererPref === "svg" ? "svg" : "canvas"
119
+ behaviors: resolveBehaviors(p)
121
120
  };
121
+ if (p.rendererPref === "svg") {
122
+ console.warn('[MCP-UI] GraphRenderer: rendererPref "svg" is not yet supported; using the default canvas renderer.');
123
+ }
122
124
  if (p.fitView !== false) {
123
125
  config.autoFit = "view";
124
126
  }
@@ -1 +1 @@
1
- {"version":3,"file":"GraphRenderer.cjs","sources":["../../src/components/GraphRenderer.tsx"],"sourcesContent":["/**\n * GraphRenderer (v6.0.0) — generic node-link visualization powered by\n * `@antv/g6 ^5` (peer-optional). Same lazy-load pattern as\n * `ChartJSRenderer` and `MapRenderer` : the heavy lib is dynamically\n * imported only on first mount, and apps that don't install the peer\n * see an informative fallback instead of a crash.\n *\n * Spec : `@seed-ship/mcp-ui-spec@5.0.4` exports `GraphComponentParamsSchema`,\n * `GraphNode`, `GraphEdge`, `GraphLayoutName`, `GraphLayout`,\n * `GraphComponentParams` — the shape consumed here. Domain semantics\n * (`weight` etc.) are opaque to this renderer ; consumers decide what\n * the values mean.\n *\n * Copy + export : the renderer ships with `<ExpandableWrapper>` (default\n * copy = JSON of `{nodes, edges}`) plus a 3-format export menu — **PNG**\n * (visual snapshot via the underlying canvas/SVG), **Mermaid** (markdown\n * / GitHub-renderable `flowchart` syntax), **JSON** (raw reimportable\n * data). All three are computed lazily on click.\n */\n\nimport { Component, createSignal, onCleanup, onMount, Show, For } from 'solid-js'\nimport type { UIComponent } from '../types'\nimport type { GraphComponentParams, GraphLayout, GraphNode, GraphEdge } from '@seed-ship/mcp-ui-spec'\nimport { ExpandableWrapper, useExpanded } from './ExpandableWrapper'\nimport { PortalDropdownMenu } from './PortalDropdownMenu'\n\n// Module-scoped lazy import promise — first call triggers the dynamic\n// import, subsequent calls reuse the resolved module.\nlet g6ModulePromise: Promise<typeof import('@antv/g6')> | undefined\n\n/**\n * Whether the `@antv/g6` peer dependency is installed and importable.\n * Resolves to `true` when the lib is available, `false` otherwise.\n *\n * Mirrors `isChartJSAvailable()` from `ChartJSRenderer`.\n */\nexport async function isG6Available(): Promise<boolean> {\n try {\n if (!g6ModulePromise) {\n g6ModulePromise = import('@antv/g6')\n }\n await g6ModulePromise\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Resolve the spec layout shorthand or object form into the config object\n * G6 v5 expects. When `layout` is omitted, picks `'force'` if edges are\n * present (universal default) or `'circular'` otherwise.\n */\nfunction resolveLayout(params: GraphComponentParams): { type: string; [key: string]: unknown } {\n const layout: GraphLayout | undefined = params.layout\n if (layout === undefined) {\n const hasEdges = (params.edges?.length ?? 0) > 0\n return { type: hasEdges ? 'force' : 'circular' }\n }\n if (typeof layout === 'string') {\n return { type: layout }\n }\n // Object form: spread the passthrough options alongside `type`.\n return { type: layout.type, ...(layout.options ?? {}) }\n}\n\n/**\n * Build the G6 v5 `behaviors` array from the params interactivity flags.\n * Defaults : drag-canvas + zoom-canvas + drag-element + click-select.\n * Any flag set to `false` opts out.\n */\nfunction resolveBehaviors(params: GraphComponentParams): string[] {\n const behaviors: string[] = []\n if (params.enableDrag !== false) behaviors.push('drag-element')\n if (params.enableZoom !== false) {\n behaviors.push('zoom-canvas', 'drag-canvas')\n }\n if (params.enableSelect !== false) behaviors.push('click-select')\n return behaviors\n}\n\n/**\n * Pick a sensible Mermaid `flowchart` direction from the resolved layout.\n * `dagre` / `tree` / `mindmap` are top-down hierarchies → TD ; everything\n * else (force, concentric, circular, grid) → LR (default mermaid).\n */\nfunction mermaidDirection(layoutType: string): 'TD' | 'LR' {\n return layoutType === 'dagre' || layoutType === 'tree' || layoutType === 'mindmap' ? 'TD' : 'LR'\n}\n\n/**\n * Sanitize a string for use inside a Mermaid node label. Mermaid breaks\n * on raw quotes / brackets / pipes ; we strip the worst offenders.\n */\nfunction mermaidLabel(s: string): string {\n return s.replace(/[\"[\\]|]/g, '').replace(/\\s+/g, ' ').trim()\n}\n\n/**\n * Convert the graph data to Mermaid `flowchart` syntax. The edge label\n * carries the optional `weight` prefix when present (e.g. `|3| label`).\n */\nfunction toMermaid(params: GraphComponentParams): string {\n const layoutType = resolveLayout(params).type\n const dir = mermaidDirection(layoutType)\n const lines: string[] = [`flowchart ${dir}`]\n for (const n of params.nodes) {\n const label = mermaidLabel(n.label ?? n.id)\n lines.push(` ${n.id}[\"${label}\"]`)\n }\n for (const e of params.edges ?? []) {\n const labelParts: string[] = []\n if (e.weight !== undefined) labelParts.push(String(e.weight))\n if (e.label) labelParts.push(mermaidLabel(e.label))\n const labelText = labelParts.join(' · ')\n if (labelText) {\n lines.push(` ${e.source} -->|${labelText}| ${e.target}`)\n } else {\n lines.push(` ${e.source} --> ${e.target}`)\n }\n }\n return lines.join('\\n')\n}\n\nfunction toJSON(params: GraphComponentParams): string {\n return JSON.stringify({ nodes: params.nodes, edges: params.edges ?? [] }, null, 2)\n}\n\nfunction downloadBlob(content: string | Blob, filename: string, mimeType?: string): void {\n const blob = typeof content === 'string' ? new Blob([content], { type: mimeType ?? 'text/plain' }) : content\n const url = URL.createObjectURL(blob)\n const a = document.createElement('a')\n a.href = url\n a.download = filename\n document.body.appendChild(a)\n a.click()\n document.body.removeChild(a)\n URL.revokeObjectURL(url)\n}\n\nexport interface GraphRendererProps {\n component: UIComponent\n /**\n * Forwarded to the underlying `<ExpandableWrapper>` (v6.3.1).\n * @see ExpandableWrapperProps.toolbarVariant\n */\n toolbarVariant?: 'hover' | 'always-visible'\n}\n\nexport const GraphRenderer: Component<GraphRendererProps> = (props) => {\n const params = () => props.component.params as GraphComponentParams\n const isExpanded = useExpanded()\n const [available, setAvailable] = createSignal<boolean | null>(null)\n const [error, setError] = createSignal<string | undefined>()\n const [exportMenuOpen, setExportMenuOpen] = createSignal(false)\n let containerRef: HTMLDivElement | undefined\n // v6.4.0 — trigger ref consumed by <PortalDropdownMenu> for positioning\n let exportTriggerRef: HTMLButtonElement | undefined\n // Loosely typed because G6 is a peer-optional — we don't pull its\n // types into the bundle just to type a transient local handle.\n let graphInstance: any | undefined\n\n onMount(async () => {\n const g6Available = await isG6Available()\n setAvailable(g6Available)\n if (!g6Available || !containerRef) return\n\n try {\n const { Graph } = await g6ModulePromise!\n const p = params()\n const config: Record<string, unknown> = {\n container: containerRef,\n data: { nodes: p.nodes, edges: p.edges ?? [] },\n layout: resolveLayout(p),\n behaviors: resolveBehaviors(p),\n renderer: p.rendererPref === 'svg' ? 'svg' : 'canvas',\n }\n if (p.fitView !== false) {\n config.autoFit = 'view'\n }\n if (p.tooltip !== false) {\n // Built-in tooltip plugin — shows label + a compact dump of\n // node.data on hover. Consumers can opt out with `tooltip: false`.\n config.plugins = [\n {\n type: 'tooltip',\n getContent: (_evt: unknown, items: any[]) => {\n const item = items?.[0]\n if (!item) return ''\n const label = item.label ?? item.id ?? ''\n const data = item.data ? JSON.stringify(item.data) : ''\n return `<div style=\"padding:4px 8px\"><strong>${escapeHtml(String(label))}</strong>${\n data ? `<br><span style=\"font-size:11px;opacity:0.7\">${escapeHtml(data)}</span>` : ''\n }</div>`\n },\n },\n ]\n }\n graphInstance = new (Graph as any)(config)\n await graphInstance.render()\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to render graph')\n }\n })\n\n onCleanup(() => {\n try {\n graphInstance?.destroy()\n } catch {\n // G6 destroy can throw on already-destroyed instances or partial\n // init failures — silent because the component is unmounting anyway.\n }\n graphInstance = undefined\n })\n\n // ─── Export handlers ────────────────────────────────────────────────\n const handleExportJSON = () => {\n downloadBlob(toJSON(params()), `${graphFilenameStem(params())}.json`, 'application/json')\n setExportMenuOpen(false)\n }\n\n const handleExportMermaid = () => {\n downloadBlob(toMermaid(params()), `${graphFilenameStem(params())}.mmd`, 'text/plain')\n setExportMenuOpen(false)\n }\n\n const handleExportPNG = async () => {\n if (!graphInstance) return\n try {\n // G6 v5 exposes `toDataURL()` on the graph instance.\n const dataUrl: string = await graphInstance.toDataURL?.('image/png')\n if (!dataUrl) {\n // Fallback: try to grab the underlying canvas directly.\n const canvas = containerRef?.querySelector('canvas')\n if (canvas) {\n const url = (canvas as HTMLCanvasElement).toDataURL('image/png')\n await downloadDataUrl(url, `${graphFilenameStem(params())}.png`)\n } else {\n setError('PNG export not supported in current renderer mode')\n }\n } else {\n await downloadDataUrl(dataUrl, `${graphFilenameStem(params())}.png`)\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : 'PNG export failed')\n }\n setExportMenuOpen(false)\n }\n\n return (\n <Show\n when={available() === true}\n fallback={\n <Show\n when={available() === false}\n fallback={\n // Loading skeleton while we determine peer availability\n <div class=\"w-full p-4 bg-gray-50 dark:bg-gray-800/40 border border-gray-200 dark:border-gray-700 rounded-lg animate-pulse\">\n <div class=\"h-4 w-32 bg-gray-200 dark:bg-gray-700 rounded mb-2\" />\n <div class=\"h-3 w-48 bg-gray-200 dark:bg-gray-700 rounded\" />\n </div>\n }\n >\n <div class=\"w-full p-4 bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg\">\n <p class=\"text-sm font-medium text-yellow-900 dark:text-yellow-100\">\n Graph rendering unavailable\n </p>\n <p class=\"text-xs text-yellow-700 dark:text-yellow-300 mt-1\">\n Install <code>@antv/g6</code> peer dependency to render <code>type: \"graph\"</code> components.\n </p>\n </div>\n </Show>\n }\n >\n <ExpandableWrapper\n title={params().title ?? 'Graph'}\n copyData={toJSON(params())}\n copyLabel=\"Copy graph (JSON)\"\n toolbarVariant={props.toolbarVariant}\n >\n <div class={`relative w-full ${params().className ?? ''} ${\n isExpanded() ? 'flex-1 min-h-0 flex flex-col' : ''\n }`}>\n {/* Export menu — top-right, mirrors TableRenderer's pattern */}\n <div class=\"absolute right-2 top-2 z-10\">\n <button\n ref={exportTriggerRef}\n type=\"button\"\n onClick={() => setExportMenuOpen((v) => !v)}\n class=\"px-2 py-1 text-xs bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded shadow-sm hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300\"\n title=\"Export graph\"\n aria-label=\"Export graph\"\n aria-haspopup=\"menu\"\n aria-expanded={exportMenuOpen()}\n >\n Export ▾\n </button>\n <PortalDropdownMenu\n open={exportMenuOpen()}\n onClose={() => setExportMenuOpen(false)}\n trigger={exportTriggerRef}\n width={176}\n class=\"text-xs\"\n >\n <For each={[\n { label: 'Download PNG', onClick: handleExportPNG, hint: 'visual snapshot' },\n { label: 'Download Mermaid', onClick: handleExportMermaid, hint: 'markdown / GitHub' },\n { label: 'Download JSON', onClick: handleExportJSON, hint: 'raw data' },\n ]}>\n {(item) => (\n <button\n type=\"button\"\n onClick={item.onClick}\n class=\"w-full text-left px-3 py-2 hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 border-b border-gray-100 dark:border-gray-700 last:border-b-0\"\n >\n <div class=\"font-medium\">{item.label}</div>\n <div class=\"text-[10px] text-gray-500 dark:text-gray-400\">{item.hint}</div>\n </button>\n )}\n </For>\n </PortalDropdownMenu>\n </div>\n\n <div\n ref={containerRef}\n class={`bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden ${\n isExpanded() ? 'flex-1 min-h-0' : ''\n }`}\n style={\n isExpanded()\n ? `height: 100%; width: ${params().width ?? '100%'};`\n : `height: ${params().height ?? '400px'}; width: ${params().width ?? '100%'};`\n }\n />\n <Show when={error()}>\n <p class=\"text-xs text-red-600 dark:text-red-400 mt-1\">Render error: {error()}</p>\n </Show>\n </div>\n </ExpandableWrapper>\n </Show>\n )\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────\n\nfunction graphFilenameStem(params: GraphComponentParams): string {\n const base = (params.title ?? 'graph').replace(/[^a-z0-9-_]+/gi, '-').replace(/^-+|-+$/g, '')\n return base || 'graph'\n}\n\nasync function downloadDataUrl(dataUrl: string, filename: string): Promise<void> {\n const res = await fetch(dataUrl)\n const blob = await res.blob()\n downloadBlob(blob, filename)\n}\n\nfunction escapeHtml(s: string): string {\n return s.replace(/[&<>\"']/g, (c) => {\n switch (c) {\n case '&': return '&amp;'\n case '<': return '&lt;'\n case '>': return '&gt;'\n case '\"': return '&quot;'\n case \"'\": return '&#39;'\n default: return c\n }\n })\n}\n\n// Re-export for tests + consumers that want to compose their own export menu\nexport { toMermaid as graphToMermaid, toJSON as graphToJSON }\n"],"names":["g6ModulePromise","isG6Available","resolveLayout","params","layout","undefined","hasEdges","edges","length","type","options","resolveBehaviors","behaviors","enableDrag","push","enableZoom","enableSelect","mermaidDirection","layoutType","mermaidLabel","s","replace","trim","toMermaid","dir","lines","n","nodes","label","id","e","labelParts","weight","String","labelText","join","source","target","toJSON","JSON","stringify","downloadBlob","content","filename","mimeType","blob","Blob","url","URL","createObjectURL","a","document","createElement","href","download","body","appendChild","click","removeChild","revokeObjectURL","GraphRenderer","props","component","isExpanded","useExpanded","available","setAvailable","createSignal","error","setError","exportMenuOpen","setExportMenuOpen","containerRef","exportTriggerRef","graphInstance","onMount","g6Available","Graph","p","config","container","data","renderer","rendererPref","fitView","autoFit","tooltip","plugins","getContent","_evt","items","item","escapeHtml","render","err","Error","message","onCleanup","destroy","handleExportJSON","graphFilenameStem","handleExportMermaid","handleExportPNG","dataUrl","toDataURL","canvas","querySelector","downloadDataUrl","_$createComponent","Show","when","fallback","_$getNextElement","_tmpl$4","children","_tmpl$3","ExpandableWrapper","title","copyData","copyLabel","toolbarVariant","_el$","_tmpl$2","_el$2","firstChild","_el$3","_el$4","nextSibling","_el$5","_co$","_$getNextMarker","_el$6","_el$1","_el$10","_co$3","$$click","v","_ref$","_$use","_$insert","PortalDropdownMenu","open","onClose","trigger","width","For","each","onClick","hint","_el$13","_tmpl$5","_el$14","_el$15","_$addEventListener","_$runHydrationEvents","_ref$2","_el$7","_tmpl$","_el$8","_el$9","_el$0","_co$2","_$effect","_p$","_v$","className","_v$2","_v$3","_v$4","height","_$className","t","_$setAttribute","o","_$style","base","res","fetch","c","_$delegateEvents"],"mappings":"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"}
1
+ {"version":3,"file":"GraphRenderer.cjs","sources":["../../src/components/GraphRenderer.tsx"],"sourcesContent":["/**\n * GraphRenderer (v6.0.0) — generic node-link visualization powered by\n * `@antv/g6 ^5` (peer-optional). Same lazy-load pattern as\n * `ChartJSRenderer` and `MapRenderer` : the heavy lib is dynamically\n * imported only on first mount, and apps that don't install the peer\n * see an informative fallback instead of a crash.\n *\n * Spec : `@seed-ship/mcp-ui-spec@5.0.4` exports `GraphComponentParamsSchema`,\n * `GraphNode`, `GraphEdge`, `GraphLayoutName`, `GraphLayout`,\n * `GraphComponentParams` — the shape consumed here. Domain semantics\n * (`weight` etc.) are opaque to this renderer ; consumers decide what\n * the values mean.\n *\n * Copy + export : the renderer ships with `<ExpandableWrapper>` (default\n * copy = JSON of `{nodes, edges}`) plus a 3-format export menu — **PNG**\n * (visual snapshot via the underlying canvas/SVG), **Mermaid** (markdown\n * / GitHub-renderable `flowchart` syntax), **JSON** (raw reimportable\n * data). All three are computed lazily on click.\n */\n\nimport { Component, createSignal, onCleanup, onMount, Show, For } from 'solid-js';\nimport type { UIComponent } from '../types';\nimport type {\n GraphComponentParams,\n GraphLayout,\n GraphNode,\n GraphEdge,\n} from '@seed-ship/mcp-ui-spec';\nimport { ExpandableWrapper, useExpanded } from './ExpandableWrapper';\nimport { PortalDropdownMenu } from './PortalDropdownMenu';\n\n// Module-scoped lazy import promise — first call triggers the dynamic\n// import, subsequent calls reuse the resolved module.\nlet g6ModulePromise: Promise<typeof import('@antv/g6')> | undefined;\n\n/**\n * Whether the `@antv/g6` peer dependency is installed and importable.\n * Resolves to `true` when the lib is available, `false` otherwise.\n *\n * Mirrors `isChartJSAvailable()` from `ChartJSRenderer`.\n */\nexport async function isG6Available(): Promise<boolean> {\n try {\n if (!g6ModulePromise) {\n g6ModulePromise = import('@antv/g6');\n }\n await g6ModulePromise;\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Resolve the spec layout shorthand or object form into the config object\n * G6 v5 expects. When `layout` is omitted, picks `'force'` if edges are\n * present (universal default) or `'circular'` otherwise.\n */\nfunction resolveLayout(params: GraphComponentParams): { type: string; [key: string]: unknown } {\n const layout: GraphLayout | undefined = params.layout;\n if (layout === undefined) {\n const hasEdges = (params.edges?.length ?? 0) > 0;\n return { type: hasEdges ? 'force' : 'circular' };\n }\n if (typeof layout === 'string') {\n return { type: layout };\n }\n // Object form: spread the passthrough options alongside `type`.\n return { type: layout.type, ...(layout.options ?? {}) };\n}\n\n/**\n * Build the G6 v5 `Graph` constructor config from the component params.\n *\n * Pure (no DOM/lib side effects beyond reading `container`), so it can be\n * unit-tested directly without a jsdom render — this is the contract the\n * 2026-05-30 audit (P0.2) wants locked.\n *\n * ⚠️ G6 v5's `renderer` is a factory `(layer) => IRenderer`, NOT a string\n * (that was the v4 contract). Passing the string `'canvas'` / `'svg'` makes\n * G6 throw `renderer is not a function` — and because the default path used\n * to pass the string `'canvas'`, EVERY graph crashed, not just svg. So we\n * **omit `renderer` entirely**: G6 then uses its built-in canvas renderer\n * (documented default `() => new CanvasRenderer()`).\n *\n * `rendererPref: 'svg'` is reserved but NOT wired yet — a real G6 v5 SVG\n * renderer needs the `@antv/g-svg` factory (a transitive dep of `@antv/g6`,\n * not statically resolvable at build time). Until that's wired behind proper\n * optional-peer resolution, svg degrades to the canvas default with a\n * one-time warning. It must NEVER inject a string `renderer`.\n */\nexport function buildGraphConfig(\n p: GraphComponentParams,\n container?: HTMLElement\n): Record<string, unknown> {\n const config: Record<string, unknown> = {\n container,\n data: { nodes: p.nodes, edges: p.edges ?? [] },\n layout: resolveLayout(p),\n behaviors: resolveBehaviors(p),\n };\n\n if (p.rendererPref === 'svg') {\n console.warn(\n '[MCP-UI] GraphRenderer: rendererPref \"svg\" is not yet supported; using the default canvas renderer.'\n );\n }\n\n if (p.fitView !== false) {\n config.autoFit = 'view';\n }\n\n if (p.tooltip !== false) {\n config.plugins = [\n {\n type: 'tooltip',\n getContent: (_evt: unknown, items: any[]) => {\n const item = items?.[0];\n if (!item) return '';\n const label = item.label ?? item.id ?? '';\n const data = item.data ? JSON.stringify(item.data) : '';\n return `<div style=\"padding:4px 8px\"><strong>${escapeHtml(String(label))}</strong>${\n data ? `<br><span style=\"font-size:11px;opacity:0.7\">${escapeHtml(data)}</span>` : ''\n }</div>`;\n },\n },\n ];\n }\n\n return config;\n}\n\n/**\n * Build the G6 v5 `behaviors` array from the params interactivity flags.\n * Defaults : drag-canvas + zoom-canvas + drag-element + click-select.\n * Any flag set to `false` opts out.\n */\nfunction resolveBehaviors(params: GraphComponentParams): string[] {\n const behaviors: string[] = [];\n if (params.enableDrag !== false) behaviors.push('drag-element');\n if (params.enableZoom !== false) {\n behaviors.push('zoom-canvas', 'drag-canvas');\n }\n if (params.enableSelect !== false) behaviors.push('click-select');\n return behaviors;\n}\n\n/**\n * Pick a sensible Mermaid `flowchart` direction from the resolved layout.\n * `dagre` / `tree` / `mindmap` are top-down hierarchies → TD ; everything\n * else (force, concentric, circular, grid) → LR (default mermaid).\n */\nfunction mermaidDirection(layoutType: string): 'TD' | 'LR' {\n return layoutType === 'dagre' || layoutType === 'tree' || layoutType === 'mindmap' ? 'TD' : 'LR';\n}\n\n/**\n * Sanitize a string for use inside a Mermaid node label. Mermaid breaks\n * on raw quotes / brackets / pipes ; we strip the worst offenders.\n */\nfunction mermaidLabel(s: string): string {\n return s\n .replace(/[\"[\\]|]/g, '')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\n/**\n * Convert the graph data to Mermaid `flowchart` syntax. The edge label\n * carries the optional `weight` prefix when present (e.g. `|3| label`).\n */\nfunction toMermaid(params: GraphComponentParams): string {\n const layoutType = resolveLayout(params).type;\n const dir = mermaidDirection(layoutType);\n const lines: string[] = [`flowchart ${dir}`];\n for (const n of params.nodes) {\n const label = mermaidLabel(n.label ?? n.id);\n lines.push(` ${n.id}[\"${label}\"]`);\n }\n for (const e of params.edges ?? []) {\n const labelParts: string[] = [];\n if (e.weight !== undefined) labelParts.push(String(e.weight));\n if (e.label) labelParts.push(mermaidLabel(e.label));\n const labelText = labelParts.join(' · ');\n if (labelText) {\n lines.push(` ${e.source} -->|${labelText}| ${e.target}`);\n } else {\n lines.push(` ${e.source} --> ${e.target}`);\n }\n }\n return lines.join('\\n');\n}\n\nfunction toJSON(params: GraphComponentParams): string {\n return JSON.stringify({ nodes: params.nodes, edges: params.edges ?? [] }, null, 2);\n}\n\nfunction downloadBlob(content: string | Blob, filename: string, mimeType?: string): void {\n const blob =\n typeof content === 'string' ? new Blob([content], { type: mimeType ?? 'text/plain' }) : content;\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = filename;\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n URL.revokeObjectURL(url);\n}\n\nexport interface GraphRendererProps {\n component: UIComponent;\n /**\n * Forwarded to the underlying `<ExpandableWrapper>` (v6.3.1).\n * @see ExpandableWrapperProps.toolbarVariant\n */\n toolbarVariant?: 'hover' | 'always-visible';\n}\n\nexport const GraphRenderer: Component<GraphRendererProps> = (props) => {\n const params = () => props.component.params as GraphComponentParams;\n const isExpanded = useExpanded();\n const [available, setAvailable] = createSignal<boolean | null>(null);\n const [error, setError] = createSignal<string | undefined>();\n const [exportMenuOpen, setExportMenuOpen] = createSignal(false);\n let containerRef: HTMLDivElement | undefined;\n // v6.4.0 — trigger ref consumed by <PortalDropdownMenu> for positioning\n let exportTriggerRef: HTMLButtonElement | undefined;\n // Loosely typed because G6 is a peer-optional — we don't pull its\n // types into the bundle just to type a transient local handle.\n let graphInstance: any | undefined;\n\n onMount(async () => {\n const g6Available = await isG6Available();\n setAvailable(g6Available);\n if (!g6Available || !containerRef) return;\n\n try {\n const { Graph } = await g6ModulePromise!;\n const p = params();\n const config: Record<string, unknown> = {\n container: containerRef,\n data: { nodes: p.nodes, edges: p.edges ?? [] },\n layout: resolveLayout(p),\n behaviors: resolveBehaviors(p),\n };\n\n // G6 v5's `renderer` is a factory `(layer) => IRenderer`, NOT a string\n // (that was the v4 contract). Passing the string `'canvas'` / `'svg'`\n // makes G6 throw `renderer is not a function` — and because the default\n // path also passed the string `'canvas'`, EVERY graph crashed, not just\n // the svg one.\n //\n // Fix: omit `renderer` entirely. G6 then uses its built-in canvas\n // renderer (documented default `() => new CanvasRenderer()`), which is\n // what we want for every graph.\n //\n // `rendererPref: 'svg'` is NOT wired yet: a real G6 v5 SVG renderer\n // needs the `@antv/g-svg` factory, which is a *transitive* dep of\n // `@antv/g6` (not a declared peer) and isn't statically resolvable at\n // build time. Rather than ship a fragile/unresolvable import, we treat\n // svg as \"not yet supported\" and fall back to the canvas default with a\n // one-time warning. Tracked for a follow-up that wires the factory\n // behind a proper optional-peer resolution. (Never pass a string here.)\n if (p.rendererPref === 'svg') {\n console.warn(\n '[MCP-UI] GraphRenderer: rendererPref \"svg\" is not yet supported; using the default canvas renderer.'\n );\n }\n if (p.fitView !== false) {\n config.autoFit = 'view';\n }\n if (p.tooltip !== false) {\n // Built-in tooltip plugin — shows label + a compact dump of\n // node.data on hover. Consumers can opt out with `tooltip: false`.\n config.plugins = [\n {\n type: 'tooltip',\n getContent: (_evt: unknown, items: any[]) => {\n const item = items?.[0];\n if (!item) return '';\n const label = item.label ?? item.id ?? '';\n const data = item.data ? JSON.stringify(item.data) : '';\n return `<div style=\"padding:4px 8px\"><strong>${escapeHtml(String(label))}</strong>${\n data\n ? `<br><span style=\"font-size:11px;opacity:0.7\">${escapeHtml(data)}</span>`\n : ''\n }</div>`;\n },\n },\n ];\n }\n graphInstance = new (Graph as any)(config);\n await graphInstance.render();\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to render graph');\n }\n });\n\n onCleanup(() => {\n try {\n graphInstance?.destroy();\n } catch {\n // G6 destroy can throw on already-destroyed instances or partial\n // init failures — silent because the component is unmounting anyway.\n }\n graphInstance = undefined;\n });\n\n // ─── Export handlers ────────────────────────────────────────────────\n const handleExportJSON = () => {\n downloadBlob(toJSON(params()), `${graphFilenameStem(params())}.json`, 'application/json');\n setExportMenuOpen(false);\n };\n\n const handleExportMermaid = () => {\n downloadBlob(toMermaid(params()), `${graphFilenameStem(params())}.mmd`, 'text/plain');\n setExportMenuOpen(false);\n };\n\n const handleExportPNG = async () => {\n if (!graphInstance) return;\n try {\n // G6 v5 exposes `toDataURL()` on the graph instance.\n const dataUrl: string = await graphInstance.toDataURL?.('image/png');\n if (!dataUrl) {\n // Fallback: try to grab the underlying canvas directly.\n const canvas = containerRef?.querySelector('canvas');\n if (canvas) {\n const url = (canvas as HTMLCanvasElement).toDataURL('image/png');\n await downloadDataUrl(url, `${graphFilenameStem(params())}.png`);\n } else {\n setError('PNG export not supported in current renderer mode');\n }\n } else {\n await downloadDataUrl(dataUrl, `${graphFilenameStem(params())}.png`);\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : 'PNG export failed');\n }\n setExportMenuOpen(false);\n };\n\n return (\n <Show\n when={available() === true}\n fallback={\n <Show\n when={available() === false}\n fallback={\n // Loading skeleton while we determine peer availability\n <div class=\"w-full p-4 bg-gray-50 dark:bg-gray-800/40 border border-gray-200 dark:border-gray-700 rounded-lg animate-pulse\">\n <div class=\"h-4 w-32 bg-gray-200 dark:bg-gray-700 rounded mb-2\" />\n <div class=\"h-3 w-48 bg-gray-200 dark:bg-gray-700 rounded\" />\n </div>\n }\n >\n <div class=\"w-full p-4 bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg\">\n <p class=\"text-sm font-medium text-yellow-900 dark:text-yellow-100\">\n Graph rendering unavailable\n </p>\n <p class=\"text-xs text-yellow-700 dark:text-yellow-300 mt-1\">\n Install <code>@antv/g6</code> peer dependency to render <code>type: \"graph\"</code>{' '}\n components.\n </p>\n </div>\n </Show>\n }\n >\n <ExpandableWrapper\n title={params().title ?? 'Graph'}\n copyData={toJSON(params())}\n copyLabel=\"Copy graph (JSON)\"\n toolbarVariant={props.toolbarVariant}\n >\n <div\n class={`relative w-full ${params().className ?? ''} ${\n isExpanded() ? 'flex-1 min-h-0 flex flex-col' : ''\n }`}\n >\n {/* Export menu — top-right, mirrors TableRenderer's pattern */}\n <div class=\"absolute right-2 top-2 z-10\">\n <button\n ref={exportTriggerRef}\n type=\"button\"\n onClick={() => setExportMenuOpen((v) => !v)}\n class=\"px-2 py-1 text-xs bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded shadow-sm hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300\"\n title=\"Export graph\"\n aria-label=\"Export graph\"\n aria-haspopup=\"menu\"\n aria-expanded={exportMenuOpen()}\n >\n Export ▾\n </button>\n <PortalDropdownMenu\n open={exportMenuOpen()}\n onClose={() => setExportMenuOpen(false)}\n trigger={exportTriggerRef}\n width={176}\n class=\"text-xs\"\n >\n <For\n each={[\n { label: 'Download PNG', onClick: handleExportPNG, hint: 'visual snapshot' },\n {\n label: 'Download Mermaid',\n onClick: handleExportMermaid,\n hint: 'markdown / GitHub',\n },\n { label: 'Download JSON', onClick: handleExportJSON, hint: 'raw data' },\n ]}\n >\n {(item) => (\n <button\n type=\"button\"\n onClick={item.onClick}\n class=\"w-full text-left px-3 py-2 hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 border-b border-gray-100 dark:border-gray-700 last:border-b-0\"\n >\n <div class=\"font-medium\">{item.label}</div>\n <div class=\"text-[10px] text-gray-500 dark:text-gray-400\">{item.hint}</div>\n </button>\n )}\n </For>\n </PortalDropdownMenu>\n </div>\n\n <div\n ref={containerRef}\n class={`bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden ${\n isExpanded() ? 'flex-1 min-h-0' : ''\n }`}\n style={\n isExpanded()\n ? `height: 100%; width: ${params().width ?? '100%'};`\n : `height: ${params().height ?? '400px'}; width: ${params().width ?? '100%'};`\n }\n />\n <Show when={error()}>\n <p class=\"text-xs text-red-600 dark:text-red-400 mt-1\">Render error: {error()}</p>\n </Show>\n </div>\n </ExpandableWrapper>\n </Show>\n );\n};\n\n// ─── Helpers ──────────────────────────────────────────────────────────\n\nfunction graphFilenameStem(params: GraphComponentParams): string {\n const base = (params.title ?? 'graph').replace(/[^a-z0-9-_]+/gi, '-').replace(/^-+|-+$/g, '');\n return base || 'graph';\n}\n\nasync function downloadDataUrl(dataUrl: string, filename: string): Promise<void> {\n const res = await fetch(dataUrl);\n const blob = await res.blob();\n downloadBlob(blob, filename);\n}\n\nfunction escapeHtml(s: string): string {\n return s.replace(/[&<>\"']/g, (c) => {\n switch (c) {\n case '&':\n return '&amp;';\n case '<':\n return '&lt;';\n case '>':\n return '&gt;';\n case '\"':\n return '&quot;';\n case \"'\":\n return '&#39;';\n default:\n return c;\n }\n });\n}\n\n// Re-export for tests + consumers that want to compose their own export menu\nexport { toMermaid as graphToMermaid, toJSON as graphToJSON };\n"],"names":["g6ModulePromise","isG6Available","resolveLayout","params","layout","undefined","hasEdges","edges","length","type","options","resolveBehaviors","behaviors","enableDrag","push","enableZoom","enableSelect","mermaidDirection","layoutType","mermaidLabel","s","replace","trim","toMermaid","dir","lines","n","nodes","label","id","e","labelParts","weight","String","labelText","join","source","target","toJSON","JSON","stringify","downloadBlob","content","filename","mimeType","blob","Blob","url","URL","createObjectURL","a","document","createElement","href","download","body","appendChild","click","removeChild","revokeObjectURL","GraphRenderer","props","component","isExpanded","useExpanded","available","setAvailable","createSignal","error","setError","exportMenuOpen","setExportMenuOpen","containerRef","exportTriggerRef","graphInstance","onMount","g6Available","Graph","p","config","container","data","rendererPref","console","warn","fitView","autoFit","tooltip","plugins","getContent","_evt","items","item","escapeHtml","render","err","Error","message","onCleanup","destroy","handleExportJSON","graphFilenameStem","handleExportMermaid","handleExportPNG","dataUrl","toDataURL","canvas","querySelector","downloadDataUrl","_$createComponent","Show","when","fallback","_$getNextElement","_tmpl$4","children","_tmpl$3","ExpandableWrapper","title","copyData","copyLabel","toolbarVariant","_el$","_tmpl$2","_el$2","firstChild","_el$3","_el$4","nextSibling","_el$5","_co$","_$getNextMarker","_el$6","_el$1","_el$10","_co$3","$$click","v","_ref$","_$use","_$insert","PortalDropdownMenu","open","onClose","trigger","width","For","each","onClick","hint","_el$13","_tmpl$5","_el$14","_el$15","_$addEventListener","_$runHydrationEvents","_ref$2","_el$7","_tmpl$","_el$8","_el$9","_el$0","_co$2","_$effect","_p$","_v$","className","_v$2","_v$3","_v$4","height","_$className","t","_$setAttribute","o","_$style","base","res","fetch","c","_$delegateEvents"],"mappings":"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"}
@@ -27,6 +27,27 @@ import type { GraphComponentParams } from '@seed-ship/mcp-ui-spec';
27
27
  * Mirrors `isChartJSAvailable()` from `ChartJSRenderer`.
28
28
  */
29
29
  export declare function isG6Available(): Promise<boolean>;
30
+ /**
31
+ * Build the G6 v5 `Graph` constructor config from the component params.
32
+ *
33
+ * Pure (no DOM/lib side effects beyond reading `container`), so it can be
34
+ * unit-tested directly without a jsdom render — this is the contract the
35
+ * 2026-05-30 audit (P0.2) wants locked.
36
+ *
37
+ * ⚠️ G6 v5's `renderer` is a factory `(layer) => IRenderer`, NOT a string
38
+ * (that was the v4 contract). Passing the string `'canvas'` / `'svg'` makes
39
+ * G6 throw `renderer is not a function` — and because the default path used
40
+ * to pass the string `'canvas'`, EVERY graph crashed, not just svg. So we
41
+ * **omit `renderer` entirely**: G6 then uses its built-in canvas renderer
42
+ * (documented default `() => new CanvasRenderer()`).
43
+ *
44
+ * `rendererPref: 'svg'` is reserved but NOT wired yet — a real G6 v5 SVG
45
+ * renderer needs the `@antv/g-svg` factory (a transitive dep of `@antv/g6`,
46
+ * not statically resolvable at build time). Until that's wired behind proper
47
+ * optional-peer resolution, svg degrades to the canvas default with a
48
+ * one-time warning. It must NEVER inject a string `renderer`.
49
+ */
50
+ export declare function buildGraphConfig(p: GraphComponentParams, container?: HTMLElement): Record<string, unknown>;
30
51
  /**
31
52
  * Convert the graph data to Mermaid `flowchart` syntax. The edge label
32
53
  * carries the optional `weight` prefix when present (e.g. `|3| label`).
@@ -1 +1 @@
1
- {"version":3,"file":"GraphRenderer.d.ts","sourceRoot":"","sources":["../../src/components/GraphRenderer.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,SAAS,EAA+C,MAAM,UAAU,CAAA;AACjF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAC3C,OAAO,KAAK,EAAE,oBAAoB,EAAqC,MAAM,wBAAwB,CAAA;AAQrG;;;;;GAKG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC,CAUtD;AAoDD;;;GAGG;AACH,iBAAS,SAAS,CAAC,MAAM,EAAE,oBAAoB,GAAG,MAAM,CAoBvD;AAED,iBAAS,MAAM,CAAC,MAAM,EAAE,oBAAoB,GAAG,MAAM,CAEpD;AAcD,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,WAAW,CAAA;IACtB;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,GAAG,gBAAgB,CAAA;CAC5C;AAED,eAAO,MAAM,aAAa,EAAE,SAAS,CAAC,kBAAkB,CAgMvD,CAAA;AA6BD,OAAO,EAAE,SAAS,IAAI,cAAc,EAAE,MAAM,IAAI,WAAW,EAAE,CAAA"}
1
+ {"version":3,"file":"GraphRenderer.d.ts","sourceRoot":"","sources":["../../src/components/GraphRenderer.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,SAAS,EAA+C,MAAM,UAAU,CAAC;AAClF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,KAAK,EACV,oBAAoB,EAIrB,MAAM,wBAAwB,CAAC;AAQhC;;;;;GAKG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC,CAUtD;AAoBD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,gBAAgB,CAC9B,CAAC,EAAE,oBAAoB,EACvB,SAAS,CAAC,EAAE,WAAW,GACtB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAoCzB;AAqCD;;;GAGG;AACH,iBAAS,SAAS,CAAC,MAAM,EAAE,oBAAoB,GAAG,MAAM,CAoBvD;AAED,iBAAS,MAAM,CAAC,MAAM,EAAE,oBAAoB,GAAG,MAAM,CAEpD;AAeD,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,WAAW,CAAC;IACvB;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,GAAG,gBAAgB,CAAC;CAC7C;AAED,eAAO,MAAM,aAAa,EAAE,SAAS,CAAC,kBAAkB,CAiOvD,CAAC;AAmCF,OAAO,EAAE,SAAS,IAAI,cAAc,EAAE,MAAM,IAAI,WAAW,EAAE,CAAC"}
@@ -114,9 +114,11 @@ const GraphRenderer = (props) => {
114
114
  edges: p.edges ?? []
115
115
  },
116
116
  layout: resolveLayout(p),
117
- behaviors: resolveBehaviors(p),
118
- renderer: p.rendererPref === "svg" ? "svg" : "canvas"
117
+ behaviors: resolveBehaviors(p)
119
118
  };
119
+ if (p.rendererPref === "svg") {
120
+ console.warn('[MCP-UI] GraphRenderer: rendererPref "svg" is not yet supported; using the default canvas renderer.');
121
+ }
120
122
  if (p.fitView !== false) {
121
123
  config.autoFit = "view";
122
124
  }
@@ -1 +1 @@
1
- {"version":3,"file":"GraphRenderer.js","sources":["../../src/components/GraphRenderer.tsx"],"sourcesContent":["/**\n * GraphRenderer (v6.0.0) — generic node-link visualization powered by\n * `@antv/g6 ^5` (peer-optional). Same lazy-load pattern as\n * `ChartJSRenderer` and `MapRenderer` : the heavy lib is dynamically\n * imported only on first mount, and apps that don't install the peer\n * see an informative fallback instead of a crash.\n *\n * Spec : `@seed-ship/mcp-ui-spec@5.0.4` exports `GraphComponentParamsSchema`,\n * `GraphNode`, `GraphEdge`, `GraphLayoutName`, `GraphLayout`,\n * `GraphComponentParams` — the shape consumed here. Domain semantics\n * (`weight` etc.) are opaque to this renderer ; consumers decide what\n * the values mean.\n *\n * Copy + export : the renderer ships with `<ExpandableWrapper>` (default\n * copy = JSON of `{nodes, edges}`) plus a 3-format export menu — **PNG**\n * (visual snapshot via the underlying canvas/SVG), **Mermaid** (markdown\n * / GitHub-renderable `flowchart` syntax), **JSON** (raw reimportable\n * data). All three are computed lazily on click.\n */\n\nimport { Component, createSignal, onCleanup, onMount, Show, For } from 'solid-js'\nimport type { UIComponent } from '../types'\nimport type { GraphComponentParams, GraphLayout, GraphNode, GraphEdge } from '@seed-ship/mcp-ui-spec'\nimport { ExpandableWrapper, useExpanded } from './ExpandableWrapper'\nimport { PortalDropdownMenu } from './PortalDropdownMenu'\n\n// Module-scoped lazy import promise — first call triggers the dynamic\n// import, subsequent calls reuse the resolved module.\nlet g6ModulePromise: Promise<typeof import('@antv/g6')> | undefined\n\n/**\n * Whether the `@antv/g6` peer dependency is installed and importable.\n * Resolves to `true` when the lib is available, `false` otherwise.\n *\n * Mirrors `isChartJSAvailable()` from `ChartJSRenderer`.\n */\nexport async function isG6Available(): Promise<boolean> {\n try {\n if (!g6ModulePromise) {\n g6ModulePromise = import('@antv/g6')\n }\n await g6ModulePromise\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Resolve the spec layout shorthand or object form into the config object\n * G6 v5 expects. When `layout` is omitted, picks `'force'` if edges are\n * present (universal default) or `'circular'` otherwise.\n */\nfunction resolveLayout(params: GraphComponentParams): { type: string; [key: string]: unknown } {\n const layout: GraphLayout | undefined = params.layout\n if (layout === undefined) {\n const hasEdges = (params.edges?.length ?? 0) > 0\n return { type: hasEdges ? 'force' : 'circular' }\n }\n if (typeof layout === 'string') {\n return { type: layout }\n }\n // Object form: spread the passthrough options alongside `type`.\n return { type: layout.type, ...(layout.options ?? {}) }\n}\n\n/**\n * Build the G6 v5 `behaviors` array from the params interactivity flags.\n * Defaults : drag-canvas + zoom-canvas + drag-element + click-select.\n * Any flag set to `false` opts out.\n */\nfunction resolveBehaviors(params: GraphComponentParams): string[] {\n const behaviors: string[] = []\n if (params.enableDrag !== false) behaviors.push('drag-element')\n if (params.enableZoom !== false) {\n behaviors.push('zoom-canvas', 'drag-canvas')\n }\n if (params.enableSelect !== false) behaviors.push('click-select')\n return behaviors\n}\n\n/**\n * Pick a sensible Mermaid `flowchart` direction from the resolved layout.\n * `dagre` / `tree` / `mindmap` are top-down hierarchies → TD ; everything\n * else (force, concentric, circular, grid) → LR (default mermaid).\n */\nfunction mermaidDirection(layoutType: string): 'TD' | 'LR' {\n return layoutType === 'dagre' || layoutType === 'tree' || layoutType === 'mindmap' ? 'TD' : 'LR'\n}\n\n/**\n * Sanitize a string for use inside a Mermaid node label. Mermaid breaks\n * on raw quotes / brackets / pipes ; we strip the worst offenders.\n */\nfunction mermaidLabel(s: string): string {\n return s.replace(/[\"[\\]|]/g, '').replace(/\\s+/g, ' ').trim()\n}\n\n/**\n * Convert the graph data to Mermaid `flowchart` syntax. The edge label\n * carries the optional `weight` prefix when present (e.g. `|3| label`).\n */\nfunction toMermaid(params: GraphComponentParams): string {\n const layoutType = resolveLayout(params).type\n const dir = mermaidDirection(layoutType)\n const lines: string[] = [`flowchart ${dir}`]\n for (const n of params.nodes) {\n const label = mermaidLabel(n.label ?? n.id)\n lines.push(` ${n.id}[\"${label}\"]`)\n }\n for (const e of params.edges ?? []) {\n const labelParts: string[] = []\n if (e.weight !== undefined) labelParts.push(String(e.weight))\n if (e.label) labelParts.push(mermaidLabel(e.label))\n const labelText = labelParts.join(' · ')\n if (labelText) {\n lines.push(` ${e.source} -->|${labelText}| ${e.target}`)\n } else {\n lines.push(` ${e.source} --> ${e.target}`)\n }\n }\n return lines.join('\\n')\n}\n\nfunction toJSON(params: GraphComponentParams): string {\n return JSON.stringify({ nodes: params.nodes, edges: params.edges ?? [] }, null, 2)\n}\n\nfunction downloadBlob(content: string | Blob, filename: string, mimeType?: string): void {\n const blob = typeof content === 'string' ? new Blob([content], { type: mimeType ?? 'text/plain' }) : content\n const url = URL.createObjectURL(blob)\n const a = document.createElement('a')\n a.href = url\n a.download = filename\n document.body.appendChild(a)\n a.click()\n document.body.removeChild(a)\n URL.revokeObjectURL(url)\n}\n\nexport interface GraphRendererProps {\n component: UIComponent\n /**\n * Forwarded to the underlying `<ExpandableWrapper>` (v6.3.1).\n * @see ExpandableWrapperProps.toolbarVariant\n */\n toolbarVariant?: 'hover' | 'always-visible'\n}\n\nexport const GraphRenderer: Component<GraphRendererProps> = (props) => {\n const params = () => props.component.params as GraphComponentParams\n const isExpanded = useExpanded()\n const [available, setAvailable] = createSignal<boolean | null>(null)\n const [error, setError] = createSignal<string | undefined>()\n const [exportMenuOpen, setExportMenuOpen] = createSignal(false)\n let containerRef: HTMLDivElement | undefined\n // v6.4.0 — trigger ref consumed by <PortalDropdownMenu> for positioning\n let exportTriggerRef: HTMLButtonElement | undefined\n // Loosely typed because G6 is a peer-optional — we don't pull its\n // types into the bundle just to type a transient local handle.\n let graphInstance: any | undefined\n\n onMount(async () => {\n const g6Available = await isG6Available()\n setAvailable(g6Available)\n if (!g6Available || !containerRef) return\n\n try {\n const { Graph } = await g6ModulePromise!\n const p = params()\n const config: Record<string, unknown> = {\n container: containerRef,\n data: { nodes: p.nodes, edges: p.edges ?? [] },\n layout: resolveLayout(p),\n behaviors: resolveBehaviors(p),\n renderer: p.rendererPref === 'svg' ? 'svg' : 'canvas',\n }\n if (p.fitView !== false) {\n config.autoFit = 'view'\n }\n if (p.tooltip !== false) {\n // Built-in tooltip plugin — shows label + a compact dump of\n // node.data on hover. Consumers can opt out with `tooltip: false`.\n config.plugins = [\n {\n type: 'tooltip',\n getContent: (_evt: unknown, items: any[]) => {\n const item = items?.[0]\n if (!item) return ''\n const label = item.label ?? item.id ?? ''\n const data = item.data ? JSON.stringify(item.data) : ''\n return `<div style=\"padding:4px 8px\"><strong>${escapeHtml(String(label))}</strong>${\n data ? `<br><span style=\"font-size:11px;opacity:0.7\">${escapeHtml(data)}</span>` : ''\n }</div>`\n },\n },\n ]\n }\n graphInstance = new (Graph as any)(config)\n await graphInstance.render()\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to render graph')\n }\n })\n\n onCleanup(() => {\n try {\n graphInstance?.destroy()\n } catch {\n // G6 destroy can throw on already-destroyed instances or partial\n // init failures — silent because the component is unmounting anyway.\n }\n graphInstance = undefined\n })\n\n // ─── Export handlers ────────────────────────────────────────────────\n const handleExportJSON = () => {\n downloadBlob(toJSON(params()), `${graphFilenameStem(params())}.json`, 'application/json')\n setExportMenuOpen(false)\n }\n\n const handleExportMermaid = () => {\n downloadBlob(toMermaid(params()), `${graphFilenameStem(params())}.mmd`, 'text/plain')\n setExportMenuOpen(false)\n }\n\n const handleExportPNG = async () => {\n if (!graphInstance) return\n try {\n // G6 v5 exposes `toDataURL()` on the graph instance.\n const dataUrl: string = await graphInstance.toDataURL?.('image/png')\n if (!dataUrl) {\n // Fallback: try to grab the underlying canvas directly.\n const canvas = containerRef?.querySelector('canvas')\n if (canvas) {\n const url = (canvas as HTMLCanvasElement).toDataURL('image/png')\n await downloadDataUrl(url, `${graphFilenameStem(params())}.png`)\n } else {\n setError('PNG export not supported in current renderer mode')\n }\n } else {\n await downloadDataUrl(dataUrl, `${graphFilenameStem(params())}.png`)\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : 'PNG export failed')\n }\n setExportMenuOpen(false)\n }\n\n return (\n <Show\n when={available() === true}\n fallback={\n <Show\n when={available() === false}\n fallback={\n // Loading skeleton while we determine peer availability\n <div class=\"w-full p-4 bg-gray-50 dark:bg-gray-800/40 border border-gray-200 dark:border-gray-700 rounded-lg animate-pulse\">\n <div class=\"h-4 w-32 bg-gray-200 dark:bg-gray-700 rounded mb-2\" />\n <div class=\"h-3 w-48 bg-gray-200 dark:bg-gray-700 rounded\" />\n </div>\n }\n >\n <div class=\"w-full p-4 bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg\">\n <p class=\"text-sm font-medium text-yellow-900 dark:text-yellow-100\">\n Graph rendering unavailable\n </p>\n <p class=\"text-xs text-yellow-700 dark:text-yellow-300 mt-1\">\n Install <code>@antv/g6</code> peer dependency to render <code>type: \"graph\"</code> components.\n </p>\n </div>\n </Show>\n }\n >\n <ExpandableWrapper\n title={params().title ?? 'Graph'}\n copyData={toJSON(params())}\n copyLabel=\"Copy graph (JSON)\"\n toolbarVariant={props.toolbarVariant}\n >\n <div class={`relative w-full ${params().className ?? ''} ${\n isExpanded() ? 'flex-1 min-h-0 flex flex-col' : ''\n }`}>\n {/* Export menu — top-right, mirrors TableRenderer's pattern */}\n <div class=\"absolute right-2 top-2 z-10\">\n <button\n ref={exportTriggerRef}\n type=\"button\"\n onClick={() => setExportMenuOpen((v) => !v)}\n class=\"px-2 py-1 text-xs bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded shadow-sm hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300\"\n title=\"Export graph\"\n aria-label=\"Export graph\"\n aria-haspopup=\"menu\"\n aria-expanded={exportMenuOpen()}\n >\n Export ▾\n </button>\n <PortalDropdownMenu\n open={exportMenuOpen()}\n onClose={() => setExportMenuOpen(false)}\n trigger={exportTriggerRef}\n width={176}\n class=\"text-xs\"\n >\n <For each={[\n { label: 'Download PNG', onClick: handleExportPNG, hint: 'visual snapshot' },\n { label: 'Download Mermaid', onClick: handleExportMermaid, hint: 'markdown / GitHub' },\n { label: 'Download JSON', onClick: handleExportJSON, hint: 'raw data' },\n ]}>\n {(item) => (\n <button\n type=\"button\"\n onClick={item.onClick}\n class=\"w-full text-left px-3 py-2 hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 border-b border-gray-100 dark:border-gray-700 last:border-b-0\"\n >\n <div class=\"font-medium\">{item.label}</div>\n <div class=\"text-[10px] text-gray-500 dark:text-gray-400\">{item.hint}</div>\n </button>\n )}\n </For>\n </PortalDropdownMenu>\n </div>\n\n <div\n ref={containerRef}\n class={`bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden ${\n isExpanded() ? 'flex-1 min-h-0' : ''\n }`}\n style={\n isExpanded()\n ? `height: 100%; width: ${params().width ?? '100%'};`\n : `height: ${params().height ?? '400px'}; width: ${params().width ?? '100%'};`\n }\n />\n <Show when={error()}>\n <p class=\"text-xs text-red-600 dark:text-red-400 mt-1\">Render error: {error()}</p>\n </Show>\n </div>\n </ExpandableWrapper>\n </Show>\n )\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────\n\nfunction graphFilenameStem(params: GraphComponentParams): string {\n const base = (params.title ?? 'graph').replace(/[^a-z0-9-_]+/gi, '-').replace(/^-+|-+$/g, '')\n return base || 'graph'\n}\n\nasync function downloadDataUrl(dataUrl: string, filename: string): Promise<void> {\n const res = await fetch(dataUrl)\n const blob = await res.blob()\n downloadBlob(blob, filename)\n}\n\nfunction escapeHtml(s: string): string {\n return s.replace(/[&<>\"']/g, (c) => {\n switch (c) {\n case '&': return '&amp;'\n case '<': return '&lt;'\n case '>': return '&gt;'\n case '\"': return '&quot;'\n case \"'\": return '&#39;'\n default: return c\n }\n })\n}\n\n// Re-export for tests + consumers that want to compose their own export menu\nexport { toMermaid as graphToMermaid, toJSON as graphToJSON }\n"],"names":["g6ModulePromise","isG6Available","resolveLayout","params","layout","undefined","hasEdges","edges","length","type","options","resolveBehaviors","behaviors","enableDrag","push","enableZoom","enableSelect","mermaidDirection","layoutType","mermaidLabel","s","replace","trim","toMermaid","dir","lines","n","nodes","label","id","e","labelParts","weight","String","labelText","join","source","target","toJSON","JSON","stringify","downloadBlob","content","filename","mimeType","blob","Blob","url","URL","createObjectURL","a","document","createElement","href","download","body","appendChild","click","removeChild","revokeObjectURL","GraphRenderer","props","component","isExpanded","useExpanded","available","setAvailable","createSignal","error","setError","exportMenuOpen","setExportMenuOpen","containerRef","exportTriggerRef","graphInstance","onMount","g6Available","Graph","p","config","container","data","renderer","rendererPref","fitView","autoFit","tooltip","plugins","getContent","_evt","items","item","escapeHtml","render","err","Error","message","onCleanup","destroy","handleExportJSON","graphFilenameStem","handleExportMermaid","handleExportPNG","dataUrl","toDataURL","canvas","querySelector","downloadDataUrl","_$createComponent","Show","when","fallback","_$getNextElement","_tmpl$4","children","_tmpl$3","ExpandableWrapper","title","copyData","copyLabel","toolbarVariant","_el$","_tmpl$2","_el$2","firstChild","_el$3","_el$4","nextSibling","_el$5","_co$","_$getNextMarker","_el$6","_el$1","_el$10","_co$3","$$click","v","_ref$","_$use","_$insert","PortalDropdownMenu","open","onClose","trigger","width","For","each","onClick","hint","_el$13","_tmpl$5","_el$14","_el$15","_$addEventListener","_$runHydrationEvents","_ref$2","_el$7","_tmpl$","_el$8","_el$9","_el$0","_co$2","_$effect","_p$","_v$","className","_v$2","_v$3","_v$4","height","_$className","t","_$setAttribute","o","_$style","base","res","fetch","c","_$delegateEvents"],"mappings":"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"}
1
+ {"version":3,"file":"GraphRenderer.js","sources":["../../src/components/GraphRenderer.tsx"],"sourcesContent":["/**\n * GraphRenderer (v6.0.0) — generic node-link visualization powered by\n * `@antv/g6 ^5` (peer-optional). Same lazy-load pattern as\n * `ChartJSRenderer` and `MapRenderer` : the heavy lib is dynamically\n * imported only on first mount, and apps that don't install the peer\n * see an informative fallback instead of a crash.\n *\n * Spec : `@seed-ship/mcp-ui-spec@5.0.4` exports `GraphComponentParamsSchema`,\n * `GraphNode`, `GraphEdge`, `GraphLayoutName`, `GraphLayout`,\n * `GraphComponentParams` — the shape consumed here. Domain semantics\n * (`weight` etc.) are opaque to this renderer ; consumers decide what\n * the values mean.\n *\n * Copy + export : the renderer ships with `<ExpandableWrapper>` (default\n * copy = JSON of `{nodes, edges}`) plus a 3-format export menu — **PNG**\n * (visual snapshot via the underlying canvas/SVG), **Mermaid** (markdown\n * / GitHub-renderable `flowchart` syntax), **JSON** (raw reimportable\n * data). All three are computed lazily on click.\n */\n\nimport { Component, createSignal, onCleanup, onMount, Show, For } from 'solid-js';\nimport type { UIComponent } from '../types';\nimport type {\n GraphComponentParams,\n GraphLayout,\n GraphNode,\n GraphEdge,\n} from '@seed-ship/mcp-ui-spec';\nimport { ExpandableWrapper, useExpanded } from './ExpandableWrapper';\nimport { PortalDropdownMenu } from './PortalDropdownMenu';\n\n// Module-scoped lazy import promise — first call triggers the dynamic\n// import, subsequent calls reuse the resolved module.\nlet g6ModulePromise: Promise<typeof import('@antv/g6')> | undefined;\n\n/**\n * Whether the `@antv/g6` peer dependency is installed and importable.\n * Resolves to `true` when the lib is available, `false` otherwise.\n *\n * Mirrors `isChartJSAvailable()` from `ChartJSRenderer`.\n */\nexport async function isG6Available(): Promise<boolean> {\n try {\n if (!g6ModulePromise) {\n g6ModulePromise = import('@antv/g6');\n }\n await g6ModulePromise;\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Resolve the spec layout shorthand or object form into the config object\n * G6 v5 expects. When `layout` is omitted, picks `'force'` if edges are\n * present (universal default) or `'circular'` otherwise.\n */\nfunction resolveLayout(params: GraphComponentParams): { type: string; [key: string]: unknown } {\n const layout: GraphLayout | undefined = params.layout;\n if (layout === undefined) {\n const hasEdges = (params.edges?.length ?? 0) > 0;\n return { type: hasEdges ? 'force' : 'circular' };\n }\n if (typeof layout === 'string') {\n return { type: layout };\n }\n // Object form: spread the passthrough options alongside `type`.\n return { type: layout.type, ...(layout.options ?? {}) };\n}\n\n/**\n * Build the G6 v5 `Graph` constructor config from the component params.\n *\n * Pure (no DOM/lib side effects beyond reading `container`), so it can be\n * unit-tested directly without a jsdom render — this is the contract the\n * 2026-05-30 audit (P0.2) wants locked.\n *\n * ⚠️ G6 v5's `renderer` is a factory `(layer) => IRenderer`, NOT a string\n * (that was the v4 contract). Passing the string `'canvas'` / `'svg'` makes\n * G6 throw `renderer is not a function` — and because the default path used\n * to pass the string `'canvas'`, EVERY graph crashed, not just svg. So we\n * **omit `renderer` entirely**: G6 then uses its built-in canvas renderer\n * (documented default `() => new CanvasRenderer()`).\n *\n * `rendererPref: 'svg'` is reserved but NOT wired yet — a real G6 v5 SVG\n * renderer needs the `@antv/g-svg` factory (a transitive dep of `@antv/g6`,\n * not statically resolvable at build time). Until that's wired behind proper\n * optional-peer resolution, svg degrades to the canvas default with a\n * one-time warning. It must NEVER inject a string `renderer`.\n */\nexport function buildGraphConfig(\n p: GraphComponentParams,\n container?: HTMLElement\n): Record<string, unknown> {\n const config: Record<string, unknown> = {\n container,\n data: { nodes: p.nodes, edges: p.edges ?? [] },\n layout: resolveLayout(p),\n behaviors: resolveBehaviors(p),\n };\n\n if (p.rendererPref === 'svg') {\n console.warn(\n '[MCP-UI] GraphRenderer: rendererPref \"svg\" is not yet supported; using the default canvas renderer.'\n );\n }\n\n if (p.fitView !== false) {\n config.autoFit = 'view';\n }\n\n if (p.tooltip !== false) {\n config.plugins = [\n {\n type: 'tooltip',\n getContent: (_evt: unknown, items: any[]) => {\n const item = items?.[0];\n if (!item) return '';\n const label = item.label ?? item.id ?? '';\n const data = item.data ? JSON.stringify(item.data) : '';\n return `<div style=\"padding:4px 8px\"><strong>${escapeHtml(String(label))}</strong>${\n data ? `<br><span style=\"font-size:11px;opacity:0.7\">${escapeHtml(data)}</span>` : ''\n }</div>`;\n },\n },\n ];\n }\n\n return config;\n}\n\n/**\n * Build the G6 v5 `behaviors` array from the params interactivity flags.\n * Defaults : drag-canvas + zoom-canvas + drag-element + click-select.\n * Any flag set to `false` opts out.\n */\nfunction resolveBehaviors(params: GraphComponentParams): string[] {\n const behaviors: string[] = [];\n if (params.enableDrag !== false) behaviors.push('drag-element');\n if (params.enableZoom !== false) {\n behaviors.push('zoom-canvas', 'drag-canvas');\n }\n if (params.enableSelect !== false) behaviors.push('click-select');\n return behaviors;\n}\n\n/**\n * Pick a sensible Mermaid `flowchart` direction from the resolved layout.\n * `dagre` / `tree` / `mindmap` are top-down hierarchies → TD ; everything\n * else (force, concentric, circular, grid) → LR (default mermaid).\n */\nfunction mermaidDirection(layoutType: string): 'TD' | 'LR' {\n return layoutType === 'dagre' || layoutType === 'tree' || layoutType === 'mindmap' ? 'TD' : 'LR';\n}\n\n/**\n * Sanitize a string for use inside a Mermaid node label. Mermaid breaks\n * on raw quotes / brackets / pipes ; we strip the worst offenders.\n */\nfunction mermaidLabel(s: string): string {\n return s\n .replace(/[\"[\\]|]/g, '')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\n/**\n * Convert the graph data to Mermaid `flowchart` syntax. The edge label\n * carries the optional `weight` prefix when present (e.g. `|3| label`).\n */\nfunction toMermaid(params: GraphComponentParams): string {\n const layoutType = resolveLayout(params).type;\n const dir = mermaidDirection(layoutType);\n const lines: string[] = [`flowchart ${dir}`];\n for (const n of params.nodes) {\n const label = mermaidLabel(n.label ?? n.id);\n lines.push(` ${n.id}[\"${label}\"]`);\n }\n for (const e of params.edges ?? []) {\n const labelParts: string[] = [];\n if (e.weight !== undefined) labelParts.push(String(e.weight));\n if (e.label) labelParts.push(mermaidLabel(e.label));\n const labelText = labelParts.join(' · ');\n if (labelText) {\n lines.push(` ${e.source} -->|${labelText}| ${e.target}`);\n } else {\n lines.push(` ${e.source} --> ${e.target}`);\n }\n }\n return lines.join('\\n');\n}\n\nfunction toJSON(params: GraphComponentParams): string {\n return JSON.stringify({ nodes: params.nodes, edges: params.edges ?? [] }, null, 2);\n}\n\nfunction downloadBlob(content: string | Blob, filename: string, mimeType?: string): void {\n const blob =\n typeof content === 'string' ? new Blob([content], { type: mimeType ?? 'text/plain' }) : content;\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = filename;\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n URL.revokeObjectURL(url);\n}\n\nexport interface GraphRendererProps {\n component: UIComponent;\n /**\n * Forwarded to the underlying `<ExpandableWrapper>` (v6.3.1).\n * @see ExpandableWrapperProps.toolbarVariant\n */\n toolbarVariant?: 'hover' | 'always-visible';\n}\n\nexport const GraphRenderer: Component<GraphRendererProps> = (props) => {\n const params = () => props.component.params as GraphComponentParams;\n const isExpanded = useExpanded();\n const [available, setAvailable] = createSignal<boolean | null>(null);\n const [error, setError] = createSignal<string | undefined>();\n const [exportMenuOpen, setExportMenuOpen] = createSignal(false);\n let containerRef: HTMLDivElement | undefined;\n // v6.4.0 — trigger ref consumed by <PortalDropdownMenu> for positioning\n let exportTriggerRef: HTMLButtonElement | undefined;\n // Loosely typed because G6 is a peer-optional — we don't pull its\n // types into the bundle just to type a transient local handle.\n let graphInstance: any | undefined;\n\n onMount(async () => {\n const g6Available = await isG6Available();\n setAvailable(g6Available);\n if (!g6Available || !containerRef) return;\n\n try {\n const { Graph } = await g6ModulePromise!;\n const p = params();\n const config: Record<string, unknown> = {\n container: containerRef,\n data: { nodes: p.nodes, edges: p.edges ?? [] },\n layout: resolveLayout(p),\n behaviors: resolveBehaviors(p),\n };\n\n // G6 v5's `renderer` is a factory `(layer) => IRenderer`, NOT a string\n // (that was the v4 contract). Passing the string `'canvas'` / `'svg'`\n // makes G6 throw `renderer is not a function` — and because the default\n // path also passed the string `'canvas'`, EVERY graph crashed, not just\n // the svg one.\n //\n // Fix: omit `renderer` entirely. G6 then uses its built-in canvas\n // renderer (documented default `() => new CanvasRenderer()`), which is\n // what we want for every graph.\n //\n // `rendererPref: 'svg'` is NOT wired yet: a real G6 v5 SVG renderer\n // needs the `@antv/g-svg` factory, which is a *transitive* dep of\n // `@antv/g6` (not a declared peer) and isn't statically resolvable at\n // build time. Rather than ship a fragile/unresolvable import, we treat\n // svg as \"not yet supported\" and fall back to the canvas default with a\n // one-time warning. Tracked for a follow-up that wires the factory\n // behind a proper optional-peer resolution. (Never pass a string here.)\n if (p.rendererPref === 'svg') {\n console.warn(\n '[MCP-UI] GraphRenderer: rendererPref \"svg\" is not yet supported; using the default canvas renderer.'\n );\n }\n if (p.fitView !== false) {\n config.autoFit = 'view';\n }\n if (p.tooltip !== false) {\n // Built-in tooltip plugin — shows label + a compact dump of\n // node.data on hover. Consumers can opt out with `tooltip: false`.\n config.plugins = [\n {\n type: 'tooltip',\n getContent: (_evt: unknown, items: any[]) => {\n const item = items?.[0];\n if (!item) return '';\n const label = item.label ?? item.id ?? '';\n const data = item.data ? JSON.stringify(item.data) : '';\n return `<div style=\"padding:4px 8px\"><strong>${escapeHtml(String(label))}</strong>${\n data\n ? `<br><span style=\"font-size:11px;opacity:0.7\">${escapeHtml(data)}</span>`\n : ''\n }</div>`;\n },\n },\n ];\n }\n graphInstance = new (Graph as any)(config);\n await graphInstance.render();\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to render graph');\n }\n });\n\n onCleanup(() => {\n try {\n graphInstance?.destroy();\n } catch {\n // G6 destroy can throw on already-destroyed instances or partial\n // init failures — silent because the component is unmounting anyway.\n }\n graphInstance = undefined;\n });\n\n // ─── Export handlers ────────────────────────────────────────────────\n const handleExportJSON = () => {\n downloadBlob(toJSON(params()), `${graphFilenameStem(params())}.json`, 'application/json');\n setExportMenuOpen(false);\n };\n\n const handleExportMermaid = () => {\n downloadBlob(toMermaid(params()), `${graphFilenameStem(params())}.mmd`, 'text/plain');\n setExportMenuOpen(false);\n };\n\n const handleExportPNG = async () => {\n if (!graphInstance) return;\n try {\n // G6 v5 exposes `toDataURL()` on the graph instance.\n const dataUrl: string = await graphInstance.toDataURL?.('image/png');\n if (!dataUrl) {\n // Fallback: try to grab the underlying canvas directly.\n const canvas = containerRef?.querySelector('canvas');\n if (canvas) {\n const url = (canvas as HTMLCanvasElement).toDataURL('image/png');\n await downloadDataUrl(url, `${graphFilenameStem(params())}.png`);\n } else {\n setError('PNG export not supported in current renderer mode');\n }\n } else {\n await downloadDataUrl(dataUrl, `${graphFilenameStem(params())}.png`);\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : 'PNG export failed');\n }\n setExportMenuOpen(false);\n };\n\n return (\n <Show\n when={available() === true}\n fallback={\n <Show\n when={available() === false}\n fallback={\n // Loading skeleton while we determine peer availability\n <div class=\"w-full p-4 bg-gray-50 dark:bg-gray-800/40 border border-gray-200 dark:border-gray-700 rounded-lg animate-pulse\">\n <div class=\"h-4 w-32 bg-gray-200 dark:bg-gray-700 rounded mb-2\" />\n <div class=\"h-3 w-48 bg-gray-200 dark:bg-gray-700 rounded\" />\n </div>\n }\n >\n <div class=\"w-full p-4 bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg\">\n <p class=\"text-sm font-medium text-yellow-900 dark:text-yellow-100\">\n Graph rendering unavailable\n </p>\n <p class=\"text-xs text-yellow-700 dark:text-yellow-300 mt-1\">\n Install <code>@antv/g6</code> peer dependency to render <code>type: \"graph\"</code>{' '}\n components.\n </p>\n </div>\n </Show>\n }\n >\n <ExpandableWrapper\n title={params().title ?? 'Graph'}\n copyData={toJSON(params())}\n copyLabel=\"Copy graph (JSON)\"\n toolbarVariant={props.toolbarVariant}\n >\n <div\n class={`relative w-full ${params().className ?? ''} ${\n isExpanded() ? 'flex-1 min-h-0 flex flex-col' : ''\n }`}\n >\n {/* Export menu — top-right, mirrors TableRenderer's pattern */}\n <div class=\"absolute right-2 top-2 z-10\">\n <button\n ref={exportTriggerRef}\n type=\"button\"\n onClick={() => setExportMenuOpen((v) => !v)}\n class=\"px-2 py-1 text-xs bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded shadow-sm hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300\"\n title=\"Export graph\"\n aria-label=\"Export graph\"\n aria-haspopup=\"menu\"\n aria-expanded={exportMenuOpen()}\n >\n Export ▾\n </button>\n <PortalDropdownMenu\n open={exportMenuOpen()}\n onClose={() => setExportMenuOpen(false)}\n trigger={exportTriggerRef}\n width={176}\n class=\"text-xs\"\n >\n <For\n each={[\n { label: 'Download PNG', onClick: handleExportPNG, hint: 'visual snapshot' },\n {\n label: 'Download Mermaid',\n onClick: handleExportMermaid,\n hint: 'markdown / GitHub',\n },\n { label: 'Download JSON', onClick: handleExportJSON, hint: 'raw data' },\n ]}\n >\n {(item) => (\n <button\n type=\"button\"\n onClick={item.onClick}\n class=\"w-full text-left px-3 py-2 hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 border-b border-gray-100 dark:border-gray-700 last:border-b-0\"\n >\n <div class=\"font-medium\">{item.label}</div>\n <div class=\"text-[10px] text-gray-500 dark:text-gray-400\">{item.hint}</div>\n </button>\n )}\n </For>\n </PortalDropdownMenu>\n </div>\n\n <div\n ref={containerRef}\n class={`bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden ${\n isExpanded() ? 'flex-1 min-h-0' : ''\n }`}\n style={\n isExpanded()\n ? `height: 100%; width: ${params().width ?? '100%'};`\n : `height: ${params().height ?? '400px'}; width: ${params().width ?? '100%'};`\n }\n />\n <Show when={error()}>\n <p class=\"text-xs text-red-600 dark:text-red-400 mt-1\">Render error: {error()}</p>\n </Show>\n </div>\n </ExpandableWrapper>\n </Show>\n );\n};\n\n// ─── Helpers ──────────────────────────────────────────────────────────\n\nfunction graphFilenameStem(params: GraphComponentParams): string {\n const base = (params.title ?? 'graph').replace(/[^a-z0-9-_]+/gi, '-').replace(/^-+|-+$/g, '');\n return base || 'graph';\n}\n\nasync function downloadDataUrl(dataUrl: string, filename: string): Promise<void> {\n const res = await fetch(dataUrl);\n const blob = await res.blob();\n downloadBlob(blob, filename);\n}\n\nfunction escapeHtml(s: string): string {\n return s.replace(/[&<>\"']/g, (c) => {\n switch (c) {\n case '&':\n return '&amp;';\n case '<':\n return '&lt;';\n case '>':\n return '&gt;';\n case '\"':\n return '&quot;';\n case \"'\":\n return '&#39;';\n default:\n return c;\n }\n });\n}\n\n// Re-export for tests + consumers that want to compose their own export menu\nexport { toMermaid as graphToMermaid, toJSON as graphToJSON };\n"],"names":["g6ModulePromise","isG6Available","resolveLayout","params","layout","undefined","hasEdges","edges","length","type","options","resolveBehaviors","behaviors","enableDrag","push","enableZoom","enableSelect","mermaidDirection","layoutType","mermaidLabel","s","replace","trim","toMermaid","dir","lines","n","nodes","label","id","e","labelParts","weight","String","labelText","join","source","target","toJSON","JSON","stringify","downloadBlob","content","filename","mimeType","blob","Blob","url","URL","createObjectURL","a","document","createElement","href","download","body","appendChild","click","removeChild","revokeObjectURL","GraphRenderer","props","component","isExpanded","useExpanded","available","setAvailable","createSignal","error","setError","exportMenuOpen","setExportMenuOpen","containerRef","exportTriggerRef","graphInstance","onMount","g6Available","Graph","p","config","container","data","rendererPref","console","warn","fitView","autoFit","tooltip","plugins","getContent","_evt","items","item","escapeHtml","render","err","Error","message","onCleanup","destroy","handleExportJSON","graphFilenameStem","handleExportMermaid","handleExportPNG","dataUrl","toDataURL","canvas","querySelector","downloadDataUrl","_$createComponent","Show","when","fallback","_$getNextElement","_tmpl$4","children","_tmpl$3","ExpandableWrapper","title","copyData","copyLabel","toolbarVariant","_el$","_tmpl$2","_el$2","firstChild","_el$3","_el$4","nextSibling","_el$5","_co$","_$getNextMarker","_el$6","_el$1","_el$10","_co$3","$$click","v","_ref$","_$use","_$insert","PortalDropdownMenu","open","onClose","trigger","width","For","each","onClick","hint","_el$13","_tmpl$5","_el$14","_el$15","_$addEventListener","_$runHydrationEvents","_ref$2","_el$7","_tmpl$","_el$8","_el$9","_el$0","_co$2","_$effect","_p$","_v$","className","_v$2","_v$3","_v$4","height","_$className","t","_$setAttribute","o","_$style","base","res","fetch","c","_$delegateEvents"],"mappings":"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"}
@@ -275,10 +275,22 @@ function validateChartComponent(params, limits = DEFAULT_RESOURCE_LIMITS) {
275
275
  var _a, _b;
276
276
  const errors = [];
277
277
  if (!(params == null ? void 0 : params.data)) {
278
- return { valid: false, errors: [{ path: "params.data", message: "Missing chart data object", code: "MISSING_DATA" }] };
278
+ return {
279
+ valid: false,
280
+ errors: [{ path: "params.data", message: "Missing chart data object", code: "MISSING_DATA" }]
281
+ };
279
282
  }
280
283
  if (!Array.isArray(params.data.datasets)) {
281
- return { valid: false, errors: [{ path: "params.data.datasets", message: "Missing or invalid datasets array", code: "MISSING_DATASETS" }] };
284
+ return {
285
+ valid: false,
286
+ errors: [
287
+ {
288
+ path: "params.data.datasets",
289
+ message: "Missing or invalid datasets array",
290
+ code: "MISSING_DATASETS"
291
+ }
292
+ ]
293
+ };
282
294
  }
283
295
  const chartType = params.type || "bar";
284
296
  const firstDataPoint = (_b = (_a = params.data.datasets[0]) == null ? void 0 : _a.data) == null ? void 0 : _b[0];
@@ -286,7 +298,16 @@ function validateChartComponent(params, limits = DEFAULT_RESOURCE_LIMITS) {
286
298
  const isPointChart = chartType === "scatter" || chartType === "bubble" || hasObjectData;
287
299
  if (!isPointChart) {
288
300
  if (!Array.isArray(params.data.labels)) {
289
- return { valid: false, errors: [{ path: "params.data.labels", message: "Missing or invalid labels array", code: "MISSING_LABELS" }] };
301
+ return {
302
+ valid: false,
303
+ errors: [
304
+ {
305
+ path: "params.data.labels",
306
+ message: "Missing or invalid labels array",
307
+ code: "MISSING_LABELS"
308
+ }
309
+ ]
310
+ };
290
311
  }
291
312
  }
292
313
  const totalDataPoints = params.data.datasets.reduce(
@@ -466,7 +487,10 @@ function validateComponent(component, options) {
466
487
  const limits = (options == null ? void 0 : options.limits) ?? DEFAULT_RESOURCE_LIMITS;
467
488
  const errors = [];
468
489
  if (!component.params) {
469
- return { valid: false, errors: [{ path: "params", message: "Missing component params", code: "MISSING_PARAMS" }] };
490
+ return {
491
+ valid: false,
492
+ errors: [{ path: "params", message: "Missing component params", code: "MISSING_PARAMS" }]
493
+ };
470
494
  }
471
495
  const gridResult = validateGridPosition(component.position);
472
496
  if (!gridResult.valid) {
@@ -497,10 +521,11 @@ function validateComponent(component, options) {
497
521
  }
498
522
  if (component.type === "map") {
499
523
  const mapParams = component.params;
500
- if (!mapParams.center && (!Array.isArray(mapParams.markers) || mapParams.markers.length === 0)) {
524
+ const hasContent = mapParams.center != null || Array.isArray(mapParams.markers) && mapParams.markers.length > 0 || mapParams.geojson != null || Array.isArray(mapParams.layers) && mapParams.layers.length > 0 || mapParams.pmtiles != null;
525
+ if (!hasContent) {
501
526
  errors.push({
502
527
  path: "params",
503
- message: "Map must have center or markers",
528
+ message: "Map must have center, markers, geojson, layers, or pmtiles",
504
529
  code: "INVALID_MAP"
505
530
  });
506
531
  }
@@ -509,14 +534,20 @@ function validateComponent(component, options) {
509
534
  } else {
510
535
  switch (component.type) {
511
536
  case "chart": {
512
- const chartResult = validateChartComponent(component.params, limits);
537
+ const chartResult = validateChartComponent(
538
+ component.params,
539
+ limits
540
+ );
513
541
  if (!chartResult.valid) {
514
542
  errors.push(...chartResult.errors || []);
515
543
  }
516
544
  break;
517
545
  }
518
546
  case "table": {
519
- const tableResult = validateTableComponent(component.params, limits);
547
+ const tableResult = validateTableComponent(
548
+ component.params,
549
+ limits
550
+ );
520
551
  if (!tableResult.valid) {
521
552
  errors.push(...tableResult.errors || []);
522
553
  }
@@ -646,7 +677,10 @@ function validateFieldValue(value, field) {
646
677
  const vals = Array.isArray(value) ? value : [String(value)];
647
678
  for (const v of vals) {
648
679
  if (!new RegExp(field.valueFormat).test(v)) {
649
- return { valid: false, error: field.valueFormatHint || `Invalid format (expected: ${field.valueFormat})` };
680
+ return {
681
+ valid: false,
682
+ error: field.valueFormatHint || `Invalid format (expected: ${field.valueFormat})`
683
+ };
650
684
  }
651
685
  }
652
686
  }