@seed-ship/mcp-ui-solid 2.2.9 → 2.2.11
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/dist/components/ArtifactRenderer.cjs +4 -3
- package/dist/components/ArtifactRenderer.cjs.map +1 -1
- package/dist/components/ArtifactRenderer.js +4 -3
- package/dist/components/ArtifactRenderer.js.map +1 -1
- package/dist/components/CodeBlockRenderer.cjs +6 -1
- package/dist/components/CodeBlockRenderer.cjs.map +1 -1
- package/dist/components/CodeBlockRenderer.d.ts.map +1 -1
- package/dist/components/CodeBlockRenderer.js +6 -1
- package/dist/components/CodeBlockRenderer.js.map +1 -1
- package/dist/components/UIResourceRenderer.cjs +267 -263
- package/dist/components/UIResourceRenderer.cjs.map +1 -1
- package/dist/components/UIResourceRenderer.d.ts.map +1 -1
- package/dist/components/UIResourceRenderer.js +267 -263
- package/dist/components/UIResourceRenderer.js.map +1 -1
- package/dist/services/validation.cjs +115 -15
- package/dist/services/validation.cjs.map +1 -1
- package/dist/services/validation.d.ts.map +1 -1
- package/dist/services/validation.js +115 -15
- package/dist/services/validation.js.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types.d.cts +1 -1
- package/dist/types.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/ArtifactRenderer.tsx +3 -3
- package/src/components/CodeBlockRenderer.tsx +7 -1
- package/src/components/UIResourceRenderer.tsx +9 -0
- package/src/services/validation.test.ts +142 -2
- package/src/services/validation.ts +135 -17
- package/src/types/index.ts +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -237,9 +237,29 @@ export function validateChartComponent(
|
|
|
237
237
|
): ValidationResult {
|
|
238
238
|
const errors: ValidationResult['errors'] = []
|
|
239
239
|
|
|
240
|
+
// Guard: params.data must exist with labels + datasets
|
|
241
|
+
if (!params?.data) {
|
|
242
|
+
return { valid: false, errors: [{ path: 'params.data', message: 'Missing chart data object', code: 'MISSING_DATA' }] }
|
|
243
|
+
}
|
|
244
|
+
if (!Array.isArray(params.data.datasets)) {
|
|
245
|
+
return { valid: false, errors: [{ path: 'params.data.datasets', message: 'Missing or invalid datasets array', code: 'MISSING_DATASETS' }] }
|
|
246
|
+
}
|
|
247
|
+
// Detect point-based charts (scatter/bubble) or object data (time-series line)
|
|
248
|
+
const chartType = params.type || 'bar'
|
|
249
|
+
const firstDataPoint = params.data.datasets[0]?.data?.[0]
|
|
250
|
+
const hasObjectData = typeof firstDataPoint === 'object' && firstDataPoint !== null && 'x' in firstDataPoint
|
|
251
|
+
const isPointChart = chartType === 'scatter' || chartType === 'bubble' || hasObjectData
|
|
252
|
+
|
|
253
|
+
// Labels required only for categorical charts (not scatter/bubble/time-series)
|
|
254
|
+
if (!isPointChart) {
|
|
255
|
+
if (!Array.isArray(params.data.labels)) {
|
|
256
|
+
return { valid: false, errors: [{ path: 'params.data.labels', message: 'Missing or invalid labels array', code: 'MISSING_LABELS' }] }
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
240
260
|
// Validate data points count
|
|
241
261
|
const totalDataPoints = params.data.datasets.reduce(
|
|
242
|
-
(sum, dataset) => sum + dataset.data.length,
|
|
262
|
+
(sum, dataset) => sum + (Array.isArray(dataset.data) ? dataset.data.length : 0),
|
|
243
263
|
0
|
|
244
264
|
)
|
|
245
265
|
|
|
@@ -251,27 +271,41 @@ export function validateChartComponent(
|
|
|
251
271
|
})
|
|
252
272
|
}
|
|
253
273
|
|
|
254
|
-
//
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
274
|
+
// Length mismatch check — only for categorical charts, skip empty datasets
|
|
275
|
+
if (!isPointChart && Array.isArray(params.data.labels)) {
|
|
276
|
+
const expectedLength = params.data.labels.length
|
|
277
|
+
for (const [index, dataset] of params.data.datasets.entries()) {
|
|
278
|
+
if (Array.isArray(dataset.data) && dataset.data.length > 0 && dataset.data.length !== expectedLength) {
|
|
279
|
+
errors.push({
|
|
280
|
+
path: `params.data.datasets[${index}]`,
|
|
281
|
+
message: `Dataset length mismatch: expected ${expectedLength}, got ${dataset.data.length}`,
|
|
282
|
+
code: 'DATA_LENGTH_MISMATCH',
|
|
283
|
+
})
|
|
284
|
+
}
|
|
263
285
|
}
|
|
264
286
|
}
|
|
265
287
|
|
|
266
|
-
//
|
|
288
|
+
// Data type validation — numbers for categorical, {x,y} objects for point charts
|
|
267
289
|
for (const [index, dataset] of params.data.datasets.entries()) {
|
|
290
|
+
if (!Array.isArray(dataset.data)) continue
|
|
268
291
|
for (const [dataIndex, value] of dataset.data.entries()) {
|
|
269
|
-
if (
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
292
|
+
if (isPointChart) {
|
|
293
|
+
const vObj = value as any
|
|
294
|
+
if (typeof value !== 'object' || value === null || vObj.x == null || typeof vObj.y !== 'number') {
|
|
295
|
+
errors.push({
|
|
296
|
+
path: `params.data.datasets[${index}].data[${dataIndex}]`,
|
|
297
|
+
message: `Invalid point data: expected {x, y} object`,
|
|
298
|
+
code: 'INVALID_POINT_DATA',
|
|
299
|
+
})
|
|
300
|
+
}
|
|
301
|
+
} else {
|
|
302
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
303
|
+
errors.push({
|
|
304
|
+
path: `params.data.datasets[${index}].data[${dataIndex}]`,
|
|
305
|
+
message: `Invalid data value: ${value} (must be finite number)`,
|
|
306
|
+
code: 'INVALID_DATA_TYPE',
|
|
307
|
+
})
|
|
308
|
+
}
|
|
275
309
|
}
|
|
276
310
|
}
|
|
277
311
|
}
|
|
@@ -449,6 +483,11 @@ export function validateComponent(
|
|
|
449
483
|
const limits = options?.limits ?? DEFAULT_RESOURCE_LIMITS
|
|
450
484
|
const errors: ValidationResult['errors'] = []
|
|
451
485
|
|
|
486
|
+
// Guard: params must exist
|
|
487
|
+
if (!component.params) {
|
|
488
|
+
return { valid: false, errors: [{ path: 'params', message: 'Missing component params', code: 'MISSING_PARAMS' }] }
|
|
489
|
+
}
|
|
490
|
+
|
|
452
491
|
// Validate grid position
|
|
453
492
|
const gridResult = validateGridPosition(component.position)
|
|
454
493
|
if (!gridResult.valid) {
|
|
@@ -566,6 +605,85 @@ export function validateComponent(
|
|
|
566
605
|
break
|
|
567
606
|
}
|
|
568
607
|
|
|
608
|
+
case 'video': {
|
|
609
|
+
const videoParams = component.params as any
|
|
610
|
+
if (!videoParams.url) {
|
|
611
|
+
errors.push({ path: 'params', message: 'Video component must have url', code: 'INVALID_VIDEO' })
|
|
612
|
+
} else {
|
|
613
|
+
// Reuse iframe domain validation for video URLs
|
|
614
|
+
const videoResult = validateIframeDomain(videoParams.url, {
|
|
615
|
+
policy: options?.iframePolicy,
|
|
616
|
+
customDomains: options?.customIframeDomains,
|
|
617
|
+
})
|
|
618
|
+
if (!videoResult.valid) {
|
|
619
|
+
errors.push(...(videoResult.errors || []))
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
break
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
case 'carousel': {
|
|
626
|
+
const carouselParams = component.params as any
|
|
627
|
+
if (!Array.isArray(carouselParams.items) || carouselParams.items.length === 0) {
|
|
628
|
+
errors.push({ path: 'params.items', message: 'Carousel must have non-empty items array', code: 'EMPTY_CAROUSEL' })
|
|
629
|
+
}
|
|
630
|
+
break
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
case 'image-gallery': {
|
|
634
|
+
const galleryParams = component.params as any
|
|
635
|
+
if (!Array.isArray(galleryParams.images) || galleryParams.images.length === 0) {
|
|
636
|
+
errors.push({ path: 'params.images', message: 'Gallery must have non-empty images array', code: 'EMPTY_GALLERY' })
|
|
637
|
+
}
|
|
638
|
+
break
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
case 'form': {
|
|
642
|
+
const formParams = component.params as any
|
|
643
|
+
if (!Array.isArray(formParams.fields) || formParams.fields.length === 0) {
|
|
644
|
+
errors.push({ path: 'params.fields', message: 'Form must have non-empty fields array', code: 'EMPTY_FORM' })
|
|
645
|
+
}
|
|
646
|
+
break
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
case 'action-group': {
|
|
650
|
+
const agParams = component.params as any
|
|
651
|
+
if (!Array.isArray(agParams.actions) || agParams.actions.length === 0) {
|
|
652
|
+
errors.push({ path: 'params.actions', message: 'Action group must have non-empty actions array', code: 'EMPTY_ACTION_GROUP' })
|
|
653
|
+
}
|
|
654
|
+
break
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
case 'code': {
|
|
658
|
+
const codeParams = component.params as any
|
|
659
|
+
if (!codeParams.code) {
|
|
660
|
+
errors.push({ path: 'params.code', message: 'Code component must have code content', code: 'INVALID_CODE' })
|
|
661
|
+
}
|
|
662
|
+
break
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
case 'map': {
|
|
666
|
+
// Map can auto-detect center from markers, so center is not strictly required
|
|
667
|
+
const mapParams = component.params as any
|
|
668
|
+
if (!mapParams.center && (!Array.isArray(mapParams.markers) || mapParams.markers.length === 0)) {
|
|
669
|
+
errors.push({ path: 'params', message: 'Map must have center or markers', code: 'INVALID_MAP' })
|
|
670
|
+
}
|
|
671
|
+
break
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
case 'modal': {
|
|
675
|
+
// Modal is valid with minimal params (title optional, content can be children)
|
|
676
|
+
break
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
case 'artifact': {
|
|
680
|
+
const artifactParams = component.params as any
|
|
681
|
+
if (!artifactParams.content) {
|
|
682
|
+
errors.push({ path: 'params.content', message: 'Artifact must have content', code: 'INVALID_ARTIFACT' })
|
|
683
|
+
}
|
|
684
|
+
break
|
|
685
|
+
}
|
|
686
|
+
|
|
569
687
|
default:
|
|
570
688
|
// Known types without specific validation pass through — renderer handles errors
|
|
571
689
|
// Truly unknown types (e.g. typos in streamed JSON) are rejected
|
package/src/types/index.ts
CHANGED
|
@@ -32,7 +32,7 @@ export type ComponentType =
|
|
|
32
32
|
/**
|
|
33
33
|
* Chart types (powered by Quickchart)
|
|
34
34
|
*/
|
|
35
|
-
export type ChartType = 'bar' | 'line' | 'pie' | 'doughnut' | 'radar' | 'scatter'
|
|
35
|
+
export type ChartType = 'bar' | 'line' | 'pie' | 'doughnut' | 'radar' | 'scatter' | 'bubble' | 'polarArea'
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
38
|
* Grid layout specification (12-column system)
|