@seed-ship/mcp-ui-solid 6.17.0 → 6.18.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.
- package/CHANGELOG.md +28 -0
- package/README.md +50 -3
- package/dist/adapters/index.d.ts +2 -0
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/presentation.cjs +141 -0
- package/dist/adapters/presentation.cjs.map +1 -0
- package/dist/adapters/presentation.d.ts +68 -0
- package/dist/adapters/presentation.d.ts.map +1 -0
- package/dist/adapters/presentation.js +141 -0
- package/dist/adapters/presentation.js.map +1 -0
- package/dist/adapters.cjs +4 -0
- package/dist/adapters.cjs.map +1 -1
- package/dist/adapters.d.cts +2 -0
- package/dist/adapters.d.ts +2 -0
- package/dist/adapters.js +4 -0
- package/dist/adapters.js.map +1 -1
- package/dist/components/ChartJSRenderer.cjs +165 -65
- package/dist/components/ChartJSRenderer.cjs.map +1 -1
- package/dist/components/ChartJSRenderer.d.ts.map +1 -1
- package/dist/components/ChartJSRenderer.js +168 -68
- package/dist/components/ChartJSRenderer.js.map +1 -1
- package/dist/components/ComponentToolbar.cjs.map +1 -1
- package/dist/components/ComponentToolbar.d.ts.map +1 -1
- package/dist/components/ComponentToolbar.js.map +1 -1
- package/dist/components/EditableLayoutContext.cjs +17 -0
- package/dist/components/EditableLayoutContext.cjs.map +1 -0
- package/dist/components/EditableLayoutContext.d.ts +5 -0
- package/dist/components/EditableLayoutContext.d.ts.map +1 -0
- package/dist/components/EditableLayoutContext.js +17 -0
- package/dist/components/EditableLayoutContext.js.map +1 -0
- package/dist/components/EditableUIResourceRenderer.cjs +87 -84
- package/dist/components/EditableUIResourceRenderer.cjs.map +1 -1
- package/dist/components/EditableUIResourceRenderer.d.ts.map +1 -1
- package/dist/components/EditableUIResourceRenderer.js +87 -84
- package/dist/components/EditableUIResourceRenderer.js.map +1 -1
- package/dist/components/ElicitationForm.cjs.map +1 -1
- package/dist/components/ElicitationForm.js.map +1 -1
- package/dist/components/FeedbackInline.cjs +0 -1
- package/dist/components/FeedbackInline.cjs.map +1 -1
- package/dist/components/FeedbackInline.js +0 -1
- package/dist/components/FeedbackInline.js.map +1 -1
- package/dist/components/GraphRenderer.cjs.map +1 -1
- package/dist/components/GraphRenderer.d.ts.map +1 -1
- package/dist/components/GraphRenderer.js.map +1 -1
- package/dist/components/GridRenderer.cjs +12 -6
- package/dist/components/GridRenderer.cjs.map +1 -1
- package/dist/components/GridRenderer.d.ts.map +1 -1
- package/dist/components/GridRenderer.js +13 -7
- package/dist/components/GridRenderer.js.map +1 -1
- package/dist/components/MapRenderer.cjs.map +1 -1
- package/dist/components/MapRenderer.d.ts.map +1 -1
- package/dist/components/MapRenderer.js.map +1 -1
- package/dist/components/PresentationFeedback.cjs +0 -1
- package/dist/components/PresentationFeedback.cjs.map +1 -1
- package/dist/components/PresentationFeedback.js +0 -1
- package/dist/components/PresentationFeedback.js.map +1 -1
- package/dist/components/UIResourceRenderer.cjs +37 -22
- package/dist/components/UIResourceRenderer.cjs.map +1 -1
- package/dist/components/UIResourceRenderer.d.ts.map +1 -1
- package/dist/components/UIResourceRenderer.js +38 -23
- package/dist/components/UIResourceRenderer.js.map +1 -1
- package/dist/components/VerifiedText.cjs +0 -4
- package/dist/components/VerifiedText.cjs.map +1 -1
- package/dist/components/VerifiedText.d.ts.map +1 -1
- package/dist/components/VerifiedText.js +0 -4
- package/dist/components/VerifiedText.js.map +1 -1
- package/dist/components/chart-data-table.cjs +73 -0
- package/dist/components/chart-data-table.cjs.map +1 -0
- package/dist/components/chart-data-table.d.ts +23 -0
- package/dist/components/chart-data-table.d.ts.map +1 -0
- package/dist/components/chart-data-table.js +73 -0
- package/dist/components/chart-data-table.js.map +1 -0
- package/dist/context/MCPUIStringsContext.cjs +10 -9
- package/dist/context/MCPUIStringsContext.cjs.map +1 -1
- package/dist/context/MCPUIStringsContext.d.ts +17 -5
- package/dist/context/MCPUIStringsContext.d.ts.map +1 -1
- package/dist/context/MCPUIStringsContext.js +11 -10
- package/dist/context/MCPUIStringsContext.js.map +1 -1
- package/dist/hooks/createResponsiveGrid.cjs +37 -0
- package/dist/hooks/createResponsiveGrid.cjs.map +1 -0
- package/dist/hooks/createResponsiveGrid.d.ts +6 -0
- package/dist/hooks/createResponsiveGrid.d.ts.map +1 -0
- package/dist/hooks/createResponsiveGrid.js +37 -0
- package/dist/hooks/createResponsiveGrid.js.map +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp-ui-spec/dist/schemas.cjs +13 -1
- package/dist/mcp-ui-spec/dist/schemas.cjs.map +1 -1
- package/dist/mcp-ui-spec/dist/schemas.js +13 -1
- package/dist/mcp-ui-spec/dist/schemas.js.map +1 -1
- package/dist/services/chat-bus.cjs.map +1 -1
- package/dist/services/chat-bus.d.ts.map +1 -1
- package/dist/services/chat-bus.js.map +1 -1
- package/dist/services/chat-prompt-controller.cjs.map +1 -1
- package/dist/services/chat-prompt-controller.d.ts.map +1 -1
- package/dist/services/chat-prompt-controller.js.map +1 -1
- package/dist/services/component-registry.cjs +162 -22
- package/dist/services/component-registry.cjs.map +1 -1
- package/dist/services/component-registry.d.ts +6 -3
- package/dist/services/component-registry.d.ts.map +1 -1
- package/dist/services/component-registry.js +162 -22
- package/dist/services/component-registry.js.map +1 -1
- package/dist/services/telemetry.cjs +0 -1
- package/dist/services/telemetry.cjs.map +1 -1
- package/dist/services/telemetry.js +0 -1
- package/dist/services/telemetry.js.map +1 -1
- package/dist/services/validation.cjs +21 -7
- package/dist/services/validation.cjs.map +1 -1
- package/dist/services/validation.d.ts.map +1 -1
- package/dist/services/validation.js +21 -7
- package/dist/services/validation.js.map +1 -1
- package/dist/types/index.d.ts +43 -8
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types.d.cts +43 -8
- package/dist/types.d.ts +43 -8
- package/docs/briefs/VISUALIZATION-FOUNDATION-2026-09-14.md +105 -0
- package/package.json +4 -4
- package/src/adapters/index.ts +18 -0
- package/src/adapters/presentation.test.ts +114 -0
- package/src/adapters/presentation.ts +229 -0
- package/src/components/ChartJSRenderer.test.tsx +325 -0
- package/src/components/ChartJSRenderer.tsx +162 -27
- package/src/components/ChatPrompt.test.tsx +1 -1
- package/src/components/ComponentToolbar.tsx +1 -1
- package/src/components/EditableLayoutContext.tsx +12 -0
- package/src/components/EditableUIResourceRenderer.tsx +11 -8
- package/src/components/ElicitationForm.tsx +1 -1
- package/src/components/FeedbackInline.tsx +1 -1
- package/src/components/GraphRenderer.test.tsx +1 -2
- package/src/components/GraphRenderer.tsx +0 -2
- package/src/components/GridRenderer.tsx +6 -4
- package/src/components/MapRenderer.tsx +0 -3
- package/src/components/PresentationFeedback.tsx +1 -1
- package/src/components/ResponsiveGrid.test.tsx +166 -0
- package/src/components/TableRenderer.pagination.test.tsx +31 -0
- package/src/components/UIResourceRenderer.tsx +14 -9
- package/src/components/VerifiedText.tsx +0 -5
- package/src/components/chart-data-table.ts +97 -0
- package/src/context/MCPUIStringsContext.test.tsx +60 -0
- package/src/context/MCPUIStringsContext.tsx +31 -12
- package/src/hooks/createResponsiveGrid.ts +41 -0
- package/src/services/chat-bus.test.ts +1 -1
- package/src/services/chat-bus.ts +0 -2
- package/src/services/chat-prompt-controller.ts +0 -8
- package/src/services/component-registry.test.ts +81 -2
- package/src/services/component-registry.ts +167 -23
- package/src/services/telemetry.ts +1 -1
- package/src/services/validation.test.ts +1 -14
- package/src/services/validation.ts +23 -6
- package/src/services/visualization-validation.test.ts +42 -0
- package/src/types/index.ts +50 -9
- package/src/types/visualization-contracts.test.ts +66 -0
- package/src/utils/perf.test.ts +3 -3
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Component Registry Service
|
|
3
|
-
* Phase 0: Static registry with
|
|
3
|
+
* Phase 0: Static registry with Chart and Table definitions
|
|
4
4
|
* Phase 1: Dynamic registry populated from /api/mcp/tools/list
|
|
5
5
|
*
|
|
6
6
|
* Provides component schemas for LLM prompt engineering
|
|
@@ -10,20 +10,23 @@ import type { ComponentRegistryEntry, ComponentType } from '../types'
|
|
|
10
10
|
import { DEFAULT_RESOURCE_LIMITS } from './validation'
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
13
|
+
* Chart Component Registry Entry
|
|
14
|
+
*
|
|
15
|
+
* The historical export name is retained for source compatibility. The
|
|
16
|
+
* renderer is local-first: Chart.js is the native implementation, while the
|
|
17
|
+
* external QuickChart fallback is a host-level opt-in.
|
|
15
18
|
*/
|
|
16
19
|
export const QuickchartRegistry: ComponentRegistryEntry = {
|
|
17
20
|
type: 'chart',
|
|
18
|
-
name: '
|
|
21
|
+
name: 'Chart.js',
|
|
19
22
|
description:
|
|
20
|
-
'Render charts
|
|
23
|
+
'Render charts locally with Chart.js. Supports bar, line, pie, doughnut, radar, scatter, bubble, and polarArea charts, including point and time-series data. An external QuickChart fallback is available only when the host explicitly opts in.',
|
|
21
24
|
schema: {
|
|
22
25
|
type: 'object',
|
|
23
26
|
properties: {
|
|
24
27
|
type: {
|
|
25
28
|
type: 'string',
|
|
26
|
-
enum: ['bar', 'line', 'pie', 'doughnut', 'radar', 'scatter'],
|
|
29
|
+
enum: ['bar', 'line', 'pie', 'doughnut', 'radar', 'scatter', 'bubble', 'polarArea'],
|
|
27
30
|
description: 'Chart type',
|
|
28
31
|
},
|
|
29
32
|
title: {
|
|
@@ -45,8 +48,22 @@ export const QuickchartRegistry: ComponentRegistryEntry = {
|
|
|
45
48
|
properties: {
|
|
46
49
|
label: { type: 'string' },
|
|
47
50
|
data: {
|
|
48
|
-
|
|
49
|
-
|
|
51
|
+
// Empty arrays match both branches; mirror Zod's inclusive union.
|
|
52
|
+
anyOf: [
|
|
53
|
+
{ type: 'array', items: { type: 'number' } },
|
|
54
|
+
{
|
|
55
|
+
type: 'array',
|
|
56
|
+
items: {
|
|
57
|
+
type: 'object',
|
|
58
|
+
properties: {
|
|
59
|
+
x: { oneOf: [{ type: 'string' }, { type: 'number' }] },
|
|
60
|
+
y: { type: 'number' },
|
|
61
|
+
r: { type: 'number', minimum: 0 },
|
|
62
|
+
},
|
|
63
|
+
required: ['x', 'y'],
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
],
|
|
50
67
|
},
|
|
51
68
|
backgroundColor: {
|
|
52
69
|
oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }],
|
|
@@ -55,6 +72,8 @@ export const QuickchartRegistry: ComponentRegistryEntry = {
|
|
|
55
72
|
oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }],
|
|
56
73
|
},
|
|
57
74
|
borderWidth: { type: 'number' },
|
|
75
|
+
fill: { oneOf: [{ type: 'boolean' }, { type: 'string' }] },
|
|
76
|
+
tension: { type: 'number' },
|
|
58
77
|
},
|
|
59
78
|
required: ['label', 'data'],
|
|
60
79
|
},
|
|
@@ -64,8 +83,35 @@ export const QuickchartRegistry: ComponentRegistryEntry = {
|
|
|
64
83
|
},
|
|
65
84
|
options: {
|
|
66
85
|
type: 'object',
|
|
67
|
-
description: 'Chart.js options for customization',
|
|
86
|
+
description: 'Supported Chart.js options for customization',
|
|
87
|
+
properties: {
|
|
88
|
+
responsive: { type: 'boolean' },
|
|
89
|
+
maintainAspectRatio: { type: 'boolean' },
|
|
90
|
+
tension: { type: 'number' },
|
|
91
|
+
scales: {},
|
|
92
|
+
plugins: {},
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
renderer: {
|
|
96
|
+
type: 'string',
|
|
97
|
+
enum: ['native', 'iframe', 'auto'],
|
|
98
|
+
description: 'Renderer preference; external iframe use still requires host opt-in',
|
|
99
|
+
},
|
|
100
|
+
exportable: { type: 'boolean', description: 'Show the PNG export action' },
|
|
101
|
+
timeAxis: {
|
|
102
|
+
type: 'object',
|
|
103
|
+
description:
|
|
104
|
+
'Time-series x-axis configuration; the host must install and register a compatible Chart.js date adapter',
|
|
105
|
+
properties: {
|
|
106
|
+
parser: { type: 'string' },
|
|
107
|
+
unit: { type: 'string', enum: ['day', 'week', 'month', 'quarter', 'year'] },
|
|
108
|
+
tooltipFormat: { type: 'string' },
|
|
109
|
+
min: { type: 'string' },
|
|
110
|
+
max: { type: 'string' },
|
|
111
|
+
},
|
|
68
112
|
},
|
|
113
|
+
height: { type: 'string', description: 'Chart container height as a CSS value' },
|
|
114
|
+
className: { type: 'string' },
|
|
69
115
|
},
|
|
70
116
|
required: ['type', 'data'],
|
|
71
117
|
},
|
|
@@ -128,7 +174,7 @@ export const TableRegistry: ComponentRegistryEntry = {
|
|
|
128
174
|
type: 'table',
|
|
129
175
|
name: 'DataTable',
|
|
130
176
|
description:
|
|
131
|
-
'Render tabular data with sortable columns and
|
|
177
|
+
'Render tabular data with sortable columns, client-side search, context-aware paging, virtualization, and export controls. Best for structured records with up to 100 rows per payload.',
|
|
132
178
|
schema: {
|
|
133
179
|
type: 'object',
|
|
134
180
|
properties: {
|
|
@@ -160,12 +206,68 @@ export const TableRegistry: ComponentRegistryEntry = {
|
|
|
160
206
|
},
|
|
161
207
|
pagination: {
|
|
162
208
|
type: 'object',
|
|
209
|
+
description: 'Legacy server-pagination metadata',
|
|
163
210
|
properties: {
|
|
164
211
|
currentPage: { type: 'number' },
|
|
165
212
|
pageSize: { type: 'number' },
|
|
166
213
|
totalRows: { type: 'number' },
|
|
167
214
|
},
|
|
168
215
|
},
|
|
216
|
+
searchable: {
|
|
217
|
+
type: 'boolean',
|
|
218
|
+
description: 'Show the client-side search field (default: true)',
|
|
219
|
+
},
|
|
220
|
+
searchPlaceholder: { type: 'string', description: 'Search field placeholder' },
|
|
221
|
+
pageSize: {
|
|
222
|
+
type: 'integer',
|
|
223
|
+
minimum: 0,
|
|
224
|
+
description: 'Rows per page in expanded mode (default: 25; 0 disables client paging)',
|
|
225
|
+
},
|
|
226
|
+
chatPageSize: {
|
|
227
|
+
type: 'integer',
|
|
228
|
+
minimum: 0,
|
|
229
|
+
description: 'Rows per page in chat mode (default: min(10, pageSize); 0 disables client paging)',
|
|
230
|
+
},
|
|
231
|
+
initialPage: {
|
|
232
|
+
type: 'integer',
|
|
233
|
+
minimum: 0,
|
|
234
|
+
description: 'Initial zero-based client page',
|
|
235
|
+
},
|
|
236
|
+
virtualize: {
|
|
237
|
+
oneOf: [
|
|
238
|
+
{ type: 'boolean' },
|
|
239
|
+
{
|
|
240
|
+
type: 'object',
|
|
241
|
+
properties: {
|
|
242
|
+
enabled: { type: 'boolean' },
|
|
243
|
+
rowHeight: { type: 'integer', minimum: 1 },
|
|
244
|
+
overscan: { type: 'integer', minimum: 0 },
|
|
245
|
+
threshold: { type: 'integer', minimum: 1 },
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
],
|
|
249
|
+
},
|
|
250
|
+
exportable: {
|
|
251
|
+
oneOf: [
|
|
252
|
+
{ type: 'boolean' },
|
|
253
|
+
{
|
|
254
|
+
type: 'object',
|
|
255
|
+
properties: {
|
|
256
|
+
formats: {
|
|
257
|
+
type: 'array',
|
|
258
|
+
items: { type: 'string', enum: ['csv', 'tsv', 'json'] },
|
|
259
|
+
},
|
|
260
|
+
filename: { type: 'string' },
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
],
|
|
264
|
+
},
|
|
265
|
+
className: { type: 'string' },
|
|
266
|
+
citationMap: { type: 'object', description: 'Citation metadata keyed by marker id' },
|
|
267
|
+
maxHeight: {
|
|
268
|
+
oneOf: [{ type: 'string' }, { type: 'number' }],
|
|
269
|
+
description: "Inline table height cap; use 'auto' to disable it",
|
|
270
|
+
},
|
|
169
271
|
},
|
|
170
272
|
required: ['columns', 'rows'],
|
|
171
273
|
},
|
|
@@ -868,19 +970,36 @@ export const GraphRegistry: ComponentRegistryEntry = {
|
|
|
868
970
|
type: 'graph',
|
|
869
971
|
name: 'NodeLinkGraph',
|
|
870
972
|
description:
|
|
871
|
-
'Render a node-link graph (entities and their relationships) with @antv/g6. Best for provenance/source chains, dependency or process graphs, and ontology-lite entity/relation views.
|
|
973
|
+
'Render a node-link graph (entities and their relationships) with the optional @antv/g6 peer. Best for provenance/source chains, dependency or process graphs, and ontology-lite entity/relation views.',
|
|
872
974
|
schema: {
|
|
873
975
|
type: 'object',
|
|
874
976
|
properties: {
|
|
977
|
+
title: { type: 'string' },
|
|
875
978
|
nodes: {
|
|
876
979
|
type: 'array',
|
|
877
980
|
description: 'Graph nodes (at least one required)',
|
|
981
|
+
minItems: 1,
|
|
878
982
|
items: {
|
|
879
983
|
type: 'object',
|
|
880
984
|
properties: {
|
|
881
|
-
id: { type: 'string' },
|
|
985
|
+
id: { type: 'string', minLength: 1 },
|
|
882
986
|
label: { type: 'string' },
|
|
883
|
-
|
|
987
|
+
type: { type: 'string' },
|
|
988
|
+
size: {
|
|
989
|
+
oneOf: [
|
|
990
|
+
{ type: 'number' },
|
|
991
|
+
{
|
|
992
|
+
type: 'array',
|
|
993
|
+
items: [{ type: 'number' }, { type: 'number' }],
|
|
994
|
+
additionalItems: false,
|
|
995
|
+
minItems: 2,
|
|
996
|
+
maxItems: 2,
|
|
997
|
+
},
|
|
998
|
+
],
|
|
999
|
+
},
|
|
1000
|
+
weight: { type: 'number' },
|
|
1001
|
+
style: { type: 'object' },
|
|
1002
|
+
data: { type: 'object' },
|
|
884
1003
|
},
|
|
885
1004
|
required: ['id'],
|
|
886
1005
|
},
|
|
@@ -891,20 +1010,46 @@ export const GraphRegistry: ComponentRegistryEntry = {
|
|
|
891
1010
|
items: {
|
|
892
1011
|
type: 'object',
|
|
893
1012
|
properties: {
|
|
894
|
-
source: { type: 'string' },
|
|
895
|
-
target: { type: 'string' },
|
|
1013
|
+
source: { type: 'string', minLength: 1 },
|
|
1014
|
+
target: { type: 'string', minLength: 1 },
|
|
896
1015
|
label: { type: 'string' },
|
|
1016
|
+
type: { type: 'string' },
|
|
897
1017
|
weight: { type: 'number' },
|
|
1018
|
+
style: { type: 'object' },
|
|
1019
|
+
data: { type: 'object' },
|
|
898
1020
|
},
|
|
899
1021
|
required: ['source', 'target'],
|
|
900
1022
|
},
|
|
901
1023
|
},
|
|
902
1024
|
layout: {
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
1025
|
+
oneOf: [
|
|
1026
|
+
{
|
|
1027
|
+
type: 'string',
|
|
1028
|
+
enum: ['force', 'dagre', 'mindmap', 'tree', 'circular', 'grid', 'concentric'],
|
|
1029
|
+
},
|
|
1030
|
+
{
|
|
1031
|
+
type: 'object',
|
|
1032
|
+
properties: {
|
|
1033
|
+
type: {
|
|
1034
|
+
type: 'string',
|
|
1035
|
+
enum: ['force', 'dagre', 'mindmap', 'tree', 'circular', 'grid', 'concentric'],
|
|
1036
|
+
},
|
|
1037
|
+
options: { type: 'object' },
|
|
1038
|
+
},
|
|
1039
|
+
required: ['type'],
|
|
1040
|
+
},
|
|
1041
|
+
],
|
|
1042
|
+
description: 'Layout shorthand or layout-plus-options object',
|
|
1043
|
+
},
|
|
1044
|
+
height: { type: 'string' },
|
|
1045
|
+
width: { type: 'string' },
|
|
1046
|
+
rendererPref: { type: 'string', enum: ['canvas'] },
|
|
1047
|
+
fitView: { type: 'boolean' },
|
|
1048
|
+
enableZoom: { type: 'boolean' },
|
|
1049
|
+
enableDrag: { type: 'boolean' },
|
|
1050
|
+
enableSelect: { type: 'boolean' },
|
|
1051
|
+
tooltip: { type: 'boolean' },
|
|
1052
|
+
className: { type: 'string' },
|
|
908
1053
|
},
|
|
909
1054
|
required: ['nodes'],
|
|
910
1055
|
},
|
|
@@ -916,11 +1061,10 @@ export const GraphRegistry: ComponentRegistryEntry = {
|
|
|
916
1061
|
type: 'graph',
|
|
917
1062
|
position: { colStart: 1, colSpan: 12 },
|
|
918
1063
|
params: {
|
|
919
|
-
directed: true,
|
|
920
1064
|
layout: 'dagre',
|
|
921
1065
|
nodes: [
|
|
922
|
-
{ id: 'claim', label: 'Population = 522 250',
|
|
923
|
-
{ id: 'source', label: 'INSEE 2021',
|
|
1066
|
+
{ id: 'claim', label: 'Population = 522 250', weight: 2 },
|
|
1067
|
+
{ id: 'source', label: 'INSEE 2021', weight: 1 },
|
|
924
1068
|
],
|
|
925
1069
|
edges: [{ source: 'claim', target: 'source', label: 'derived from' }],
|
|
926
1070
|
},
|
|
@@ -110,7 +110,7 @@ export function createTelemetryDispatcher(
|
|
|
110
110
|
const result = sink(batch)
|
|
111
111
|
// Promise rejections are silenced too (fail-open, §M.6.1).
|
|
112
112
|
if (result && typeof (result as Promise<void>).then === 'function') {
|
|
113
|
-
|
|
113
|
+
(result as Promise<void>).catch(() => {
|
|
114
114
|
/* silent */
|
|
115
115
|
})
|
|
116
116
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* without UNKNOWN_COMPONENT_TYPE errors.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { describe, it, expect
|
|
8
|
+
import { describe, it, expect } from 'vitest';
|
|
9
9
|
import {
|
|
10
10
|
validateComponent,
|
|
11
11
|
validateChartComponent,
|
|
@@ -27,19 +27,6 @@ function makeComponent(type: ComponentType, params: Record<string, any> = {}): U
|
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
/** Types that have explicit validation cases in validateComponent */
|
|
31
|
-
const VALIDATED_TYPES: ComponentType[] = [
|
|
32
|
-
'chart',
|
|
33
|
-
'table',
|
|
34
|
-
'metric',
|
|
35
|
-
'text',
|
|
36
|
-
'iframe',
|
|
37
|
-
'image',
|
|
38
|
-
'link',
|
|
39
|
-
'action',
|
|
40
|
-
'artifact',
|
|
41
|
-
];
|
|
42
|
-
|
|
43
30
|
/** Types that hit the default case (no specific validation) */
|
|
44
31
|
const PASSTHROUGH_TYPES: ComponentType[] = [
|
|
45
32
|
'code',
|
|
@@ -417,9 +417,12 @@ export function validateChartComponent(
|
|
|
417
417
|
}
|
|
418
418
|
// Detect point-based charts (scatter/bubble) or object data (time-series line)
|
|
419
419
|
const chartType = params.type || 'bar';
|
|
420
|
-
const
|
|
421
|
-
|
|
422
|
-
|
|
420
|
+
const usesPoints = (data: unknown[]) => data.some(value =>
|
|
421
|
+
typeof value === 'object' && value !== null && 'x' in value
|
|
422
|
+
);
|
|
423
|
+
const hasObjectData = params.data.datasets.some(dataset =>
|
|
424
|
+
Array.isArray(dataset.data) && usesPoints(dataset.data)
|
|
425
|
+
);
|
|
423
426
|
const isPointChart = chartType === 'scatter' || chartType === 'bubble' || hasObjectData;
|
|
424
427
|
|
|
425
428
|
// Labels required only for categorical charts (not scatter/bubble/time-series)
|
|
@@ -453,11 +456,12 @@ export function validateChartComponent(
|
|
|
453
456
|
}
|
|
454
457
|
|
|
455
458
|
// Length mismatch check — only for categorical charts, skip empty datasets
|
|
456
|
-
if (
|
|
459
|
+
if (chartType !== 'scatter' && chartType !== 'bubble' && Array.isArray(params.data.labels)) {
|
|
457
460
|
const expectedLength = params.data.labels.length;
|
|
458
461
|
for (const [index, dataset] of params.data.datasets.entries()) {
|
|
459
462
|
if (
|
|
460
463
|
Array.isArray(dataset.data) &&
|
|
464
|
+
!usesPoints(dataset.data) &&
|
|
461
465
|
dataset.data.length > 0 &&
|
|
462
466
|
dataset.data.length !== expectedLength
|
|
463
467
|
) {
|
|
@@ -473,14 +477,16 @@ export function validateChartComponent(
|
|
|
473
477
|
// Data type validation — numbers for categorical, {x,y} objects for point charts
|
|
474
478
|
for (const [index, dataset] of params.data.datasets.entries()) {
|
|
475
479
|
if (!Array.isArray(dataset.data)) continue;
|
|
480
|
+
const datasetIsPointChart = chartType === 'scatter' || chartType === 'bubble' || usesPoints(dataset.data);
|
|
476
481
|
for (const [dataIndex, value] of dataset.data.entries()) {
|
|
477
|
-
if (
|
|
482
|
+
if (datasetIsPointChart) {
|
|
478
483
|
const vObj = value as any;
|
|
479
484
|
if (
|
|
480
485
|
typeof value !== 'object' ||
|
|
481
486
|
value === null ||
|
|
482
487
|
vObj.x == null ||
|
|
483
|
-
typeof vObj.y !== 'number'
|
|
488
|
+
typeof vObj.y !== 'number' ||
|
|
489
|
+
(vObj.r !== undefined && (typeof vObj.r !== 'number' || !Number.isFinite(vObj.r) || vObj.r < 0))
|
|
484
490
|
) {
|
|
485
491
|
errors.push({
|
|
486
492
|
path: `params.data.datasets[${index}].data[${dataIndex}]`,
|
|
@@ -515,6 +521,17 @@ export function validateTableComponent(
|
|
|
515
521
|
): ValidationResult {
|
|
516
522
|
const errors: ValidationResult['errors'] = [];
|
|
517
523
|
|
|
524
|
+
for (const key of ['pageSize', 'chatPageSize', 'initialPage'] as const) {
|
|
525
|
+
const value = params[key];
|
|
526
|
+
if (value !== undefined && (typeof value !== 'number' || !Number.isInteger(value) || value < 0)) {
|
|
527
|
+
errors.push({
|
|
528
|
+
path: `params.${key}`,
|
|
529
|
+
message: `${key} must be a nonnegative integer`,
|
|
530
|
+
code: 'INVALID_PAGINATION',
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
518
535
|
// Validate row count
|
|
519
536
|
if (params.rows.length > limits.maxTableRows) {
|
|
520
537
|
errors.push({
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { validateComponent } from './validation'
|
|
3
|
+
import type { UIComponent } from '../types'
|
|
4
|
+
|
|
5
|
+
describe('visualization rendering boundary', () => {
|
|
6
|
+
it.each([false, true])('validates each dataset independently (points first: %s)', pointsFirst => {
|
|
7
|
+
const numeric = { label: 'Numeric', data: [1, 2] }
|
|
8
|
+
const points = { label: 'Points', data: [{ x: 1, y: 2, r: 3 }] }
|
|
9
|
+
const component: UIComponent = {
|
|
10
|
+
id: 'mixed-chart', type: 'chart', position: { colStart: 1, colSpan: 12 },
|
|
11
|
+
params: { type: 'line', data: { labels: ['A', 'B'], datasets: pointsFirst ? [points, numeric] : [numeric, points] } },
|
|
12
|
+
}
|
|
13
|
+
expect(validateComponent(component).valid).toBe(true)
|
|
14
|
+
points.data[0].r = -1
|
|
15
|
+
expect(validateComponent(component).valid).toBe(false)
|
|
16
|
+
})
|
|
17
|
+
for (const key of ['pageSize', 'chatPageSize', 'initialPage']) {
|
|
18
|
+
it.each([-1, 1.5, NaN, Infinity, '10'])(`rejects invalid ${key}: %s`, value => {
|
|
19
|
+
const component = {
|
|
20
|
+
id: 'table-bounds', type: 'table', position: { colStart: 1, colSpan: 12 },
|
|
21
|
+
params: { columns: [{ key: 'name', label: 'Name' }], rows: [], [key]: value },
|
|
22
|
+
} as UIComponent
|
|
23
|
+
const result = validateComponent(component)
|
|
24
|
+
expect(result.valid).toBe(false)
|
|
25
|
+
expect(result.errors).toContainEqual(expect.objectContaining({ path: `params.${key}` }))
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
it.each([-1, NaN, Infinity, '3', null])('rejects invalid bubble radius: %s', r => {
|
|
29
|
+
const component = {
|
|
30
|
+
id: 'bubble-bounds', type: 'chart', position: { colStart: 1, colSpan: 12 },
|
|
31
|
+
params: { type: 'bubble', data: { datasets: [{ label: 'Bubbles', data: [{ x: 1, y: 2, r }] }] } },
|
|
32
|
+
} as unknown as UIComponent
|
|
33
|
+
expect(validateComponent(component).valid).toBe(false)
|
|
34
|
+
})
|
|
35
|
+
it.each([undefined, 0, 4])('accepts optional/nonnegative bubble radius: %s', r => {
|
|
36
|
+
const component: UIComponent = {
|
|
37
|
+
id: 'bubble-valid', type: 'chart', position: { colStart: 1, colSpan: 12 },
|
|
38
|
+
params: { type: 'bubble', data: { datasets: [{ label: 'Bubbles', data: [{ x: 1, y: 2, r }] }] } },
|
|
39
|
+
}
|
|
40
|
+
expect(validateComponent(component).valid).toBe(true)
|
|
41
|
+
})
|
|
42
|
+
})
|
package/src/types/index.ts
CHANGED
|
@@ -17,7 +17,7 @@ import type { ComponentType, LatLngPoint } from '@seed-ship/mcp-ui-spec'
|
|
|
17
17
|
export type { ComponentType, LatLngPoint }
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
* Chart types
|
|
20
|
+
* Chart types rendered natively with Chart.js.
|
|
21
21
|
*/
|
|
22
22
|
export type ChartType = 'bar' | 'line' | 'pie' | 'doughnut' | 'radar' | 'scatter' | 'bubble' | 'polarArea'
|
|
23
23
|
|
|
@@ -81,7 +81,7 @@ export interface ChartComponentParams {
|
|
|
81
81
|
labels: string[]
|
|
82
82
|
datasets: Array<{
|
|
83
83
|
label: string
|
|
84
|
-
data: number[] | Array<{ x: string | number; y: number }>
|
|
84
|
+
data: number[] | Array<{ x: string | number; y: number; r?: number }>
|
|
85
85
|
backgroundColor?: string | string[]
|
|
86
86
|
borderColor?: string | string[]
|
|
87
87
|
borderWidth?: number
|
|
@@ -101,8 +101,9 @@ export interface ChartComponentParams {
|
|
|
101
101
|
/**
|
|
102
102
|
* Force renderer type (Sprint 4)
|
|
103
103
|
* - 'native': Use Chart.js directly (requires chart.js peer dependency)
|
|
104
|
-
* - 'iframe':
|
|
105
|
-
* - 'auto': Use native
|
|
104
|
+
* - 'iframe': Request the external QuickChart renderer (requires host opt-in)
|
|
105
|
+
* - 'auto': Use native Chart.js when available; otherwise degrade locally
|
|
106
|
+
* unless the host explicitly enables the external fallback (default)
|
|
106
107
|
*/
|
|
107
108
|
renderer?: 'native' | 'iframe' | 'auto'
|
|
108
109
|
/**
|
|
@@ -112,6 +113,7 @@ export interface ChartComponentParams {
|
|
|
112
113
|
/**
|
|
113
114
|
* Time-series axis configuration (v3.1.0).
|
|
114
115
|
* When set, x-axis labels are parsed as dates.
|
|
116
|
+
* The host must install and register a compatible Chart.js date adapter.
|
|
115
117
|
*/
|
|
116
118
|
timeAxis?: {
|
|
117
119
|
/** Date format for parsing labels (Chart.js adapter format, e.g. 'yyyy-MM-dd') */
|
|
@@ -190,6 +192,16 @@ export interface TableComponentParams {
|
|
|
190
192
|
pageSize: number
|
|
191
193
|
totalRows: number
|
|
192
194
|
}
|
|
195
|
+
/** Show the accent-insensitive client-side search field (default: true). */
|
|
196
|
+
searchable?: boolean
|
|
197
|
+
/** Placeholder displayed in the client-side search field. */
|
|
198
|
+
searchPlaceholder?: string
|
|
199
|
+
/** Rows per page in expanded mode (default: 25). Set 0 to disable paging. */
|
|
200
|
+
pageSize?: number
|
|
201
|
+
/** Rows per page in chat mode (default: min(10, pageSize)). Set 0 to disable paging. */
|
|
202
|
+
chatPageSize?: number
|
|
203
|
+
/** Initial zero-based page for client-side paging. */
|
|
204
|
+
initialPage?: number
|
|
193
205
|
/**
|
|
194
206
|
* Enable table virtualization for large datasets (Sprint Ultimate U.3)
|
|
195
207
|
* - true: Enable with default options
|
|
@@ -1019,7 +1031,11 @@ export interface GraphNode {
|
|
|
1019
1031
|
/** Stable node id — referenced by edges. */
|
|
1020
1032
|
id: string
|
|
1021
1033
|
label?: string
|
|
1022
|
-
|
|
1034
|
+
type?: string
|
|
1035
|
+
size?: number | [number, number]
|
|
1036
|
+
style?: Record<string, unknown>
|
|
1037
|
+
data?: Record<string, unknown>
|
|
1038
|
+
/** @deprecated Compatibility field outside the canonical graph spec. */
|
|
1023
1039
|
group?: string
|
|
1024
1040
|
/** Generic ranking/importance signal (opaque to the lib). */
|
|
1025
1041
|
weight?: number
|
|
@@ -1033,31 +1049,56 @@ export interface GraphEdge {
|
|
|
1033
1049
|
/** Must match a `GraphNode.id`. */
|
|
1034
1050
|
target: string
|
|
1035
1051
|
label?: string
|
|
1052
|
+
type?: string
|
|
1036
1053
|
weight?: number
|
|
1054
|
+
style?: Record<string, unknown>
|
|
1055
|
+
data?: Record<string, unknown>
|
|
1037
1056
|
/** Passthrough G6 edge props (style, data, …). */
|
|
1038
1057
|
[key: string]: unknown
|
|
1039
1058
|
}
|
|
1040
1059
|
|
|
1041
|
-
|
|
1060
|
+
/** Canonical layout names shared with `GraphLayoutNameSchema`. */
|
|
1061
|
+
export type GraphLayoutName =
|
|
1062
|
+
| 'force'
|
|
1063
|
+
| 'dagre'
|
|
1064
|
+
| 'mindmap'
|
|
1065
|
+
| 'tree'
|
|
1066
|
+
| 'circular'
|
|
1067
|
+
| 'grid'
|
|
1068
|
+
| 'concentric'
|
|
1069
|
+
|
|
1070
|
+
export type GraphLayout =
|
|
1071
|
+
| GraphLayoutName
|
|
1072
|
+
| { type: GraphLayoutName; options?: Record<string, unknown> }
|
|
1073
|
+
/** @deprecated `radial` predates the public spec; retained for source compatibility. */
|
|
1074
|
+
| 'radial'
|
|
1042
1075
|
|
|
1043
1076
|
/**
|
|
1044
1077
|
* Generic node-link graph component params. Domain-neutral by design — the
|
|
1045
1078
|
* meaning of `weight`/`group` is decided by the consumer.
|
|
1046
1079
|
*/
|
|
1047
1080
|
export interface GraphComponentParams {
|
|
1081
|
+
title?: string
|
|
1048
1082
|
/** Nodes (at least one expected at runtime). */
|
|
1049
1083
|
nodes: GraphNode[]
|
|
1050
1084
|
edges?: GraphEdge[]
|
|
1051
1085
|
layout?: GraphLayout
|
|
1052
|
-
/**
|
|
1086
|
+
/** @deprecated Accepted for source compatibility but not used by the renderer. */
|
|
1053
1087
|
directed?: boolean
|
|
1054
1088
|
/**
|
|
1055
1089
|
* Rendering hint only — NOT wired; the renderer always uses the G6 canvas
|
|
1056
1090
|
* default (cf. audit P0). Kept for forward-compat with the spec.
|
|
1057
1091
|
*/
|
|
1058
1092
|
rendererPref?: 'canvas' | 'svg'
|
|
1059
|
-
|
|
1060
|
-
|
|
1093
|
+
/** CSS length in the canonical spec; numeric values remain for source compatibility. */
|
|
1094
|
+
height?: string | number
|
|
1095
|
+
/** CSS length in the canonical spec; numeric values remain for source compatibility. */
|
|
1096
|
+
width?: string | number
|
|
1097
|
+
fitView?: boolean
|
|
1098
|
+
enableZoom?: boolean
|
|
1099
|
+
enableDrag?: boolean
|
|
1100
|
+
enableSelect?: boolean
|
|
1101
|
+
tooltip?: boolean
|
|
1061
1102
|
/** Custom CSS class. */
|
|
1062
1103
|
className?: string
|
|
1063
1104
|
/** Passthrough for forward-compat graph options. */
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import type {
|
|
3
|
+
ChartComponentParams,
|
|
4
|
+
ChartType,
|
|
5
|
+
GraphComponentParams,
|
|
6
|
+
GraphLayout,
|
|
7
|
+
GraphLayoutName,
|
|
8
|
+
TableComponentParams,
|
|
9
|
+
} from './index'
|
|
10
|
+
|
|
11
|
+
describe('public visualization contracts', () => {
|
|
12
|
+
it('types all eight chart variants, point data, and time-axis options', () => {
|
|
13
|
+
const chartTypes: ChartType[] = [
|
|
14
|
+
'bar',
|
|
15
|
+
'line',
|
|
16
|
+
'pie',
|
|
17
|
+
'doughnut',
|
|
18
|
+
'radar',
|
|
19
|
+
'scatter',
|
|
20
|
+
'bubble',
|
|
21
|
+
'polarArea',
|
|
22
|
+
]
|
|
23
|
+
const params: ChartComponentParams = {
|
|
24
|
+
type: 'bubble',
|
|
25
|
+
data: {
|
|
26
|
+
labels: [],
|
|
27
|
+
datasets: [{ label: 'Points', data: [{ x: '2026-09-14', y: 3, r: 8 }] }],
|
|
28
|
+
},
|
|
29
|
+
timeAxis: { unit: 'day', parser: 'yyyy-MM-dd' },
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
expect(chartTypes).toHaveLength(8)
|
|
33
|
+
expect(params.timeAxis?.unit).toBe('day')
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('types the table search and context-aware paging controls', () => {
|
|
37
|
+
const params: TableComponentParams = {
|
|
38
|
+
columns: [{ key: 'name', label: 'Name', sortable: true }],
|
|
39
|
+
rows: [{ name: 'Ada' }],
|
|
40
|
+
searchable: true,
|
|
41
|
+
searchPlaceholder: 'Filter records',
|
|
42
|
+
pageSize: 30,
|
|
43
|
+
chatPageSize: 10,
|
|
44
|
+
initialPage: 0,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
expect(params).toMatchObject({ searchable: true, pageSize: 30, chatPageSize: 10 })
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('types the seven canonical graph layouts and object-form options', () => {
|
|
51
|
+
const canonical: GraphLayoutName[] = [
|
|
52
|
+
'force',
|
|
53
|
+
'dagre',
|
|
54
|
+
'mindmap',
|
|
55
|
+
'tree',
|
|
56
|
+
'circular',
|
|
57
|
+
'grid',
|
|
58
|
+
'concentric',
|
|
59
|
+
]
|
|
60
|
+
const layout: GraphLayout = { type: 'dagre', options: { rankdir: 'TB' } }
|
|
61
|
+
const params: GraphComponentParams = { nodes: [{ id: 'root' }], layout }
|
|
62
|
+
|
|
63
|
+
expect(canonical).toHaveLength(7)
|
|
64
|
+
expect(params.layout).toEqual(layout)
|
|
65
|
+
})
|
|
66
|
+
})
|
package/src/utils/perf.test.ts
CHANGED
|
@@ -47,13 +47,13 @@ describe('performance marks (v5.4.0 — B.4)', () => {
|
|
|
47
47
|
})
|
|
48
48
|
|
|
49
49
|
it('mark functions are no-ops when performance is undefined (SSR-safe)', () => {
|
|
50
|
-
const originalPerf = (globalThis as any).performance
|
|
51
|
-
|
|
50
|
+
const originalPerf = (globalThis as any).performance;
|
|
51
|
+
(globalThis as any).performance = undefined
|
|
52
52
|
try {
|
|
53
53
|
expect(() => markRenderStart('ssr')).not.toThrow()
|
|
54
54
|
expect(() => markRenderEnd('ssr')).not.toThrow()
|
|
55
55
|
} finally {
|
|
56
|
-
|
|
56
|
+
(globalThis as any).performance = originalPerf
|
|
57
57
|
}
|
|
58
58
|
})
|
|
59
59
|
})
|