@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.
Files changed (31) hide show
  1. package/dist/components/ArtifactRenderer.cjs +4 -3
  2. package/dist/components/ArtifactRenderer.cjs.map +1 -1
  3. package/dist/components/ArtifactRenderer.js +4 -3
  4. package/dist/components/ArtifactRenderer.js.map +1 -1
  5. package/dist/components/CodeBlockRenderer.cjs +6 -1
  6. package/dist/components/CodeBlockRenderer.cjs.map +1 -1
  7. package/dist/components/CodeBlockRenderer.d.ts.map +1 -1
  8. package/dist/components/CodeBlockRenderer.js +6 -1
  9. package/dist/components/CodeBlockRenderer.js.map +1 -1
  10. package/dist/components/UIResourceRenderer.cjs +267 -263
  11. package/dist/components/UIResourceRenderer.cjs.map +1 -1
  12. package/dist/components/UIResourceRenderer.d.ts.map +1 -1
  13. package/dist/components/UIResourceRenderer.js +267 -263
  14. package/dist/components/UIResourceRenderer.js.map +1 -1
  15. package/dist/services/validation.cjs +115 -15
  16. package/dist/services/validation.cjs.map +1 -1
  17. package/dist/services/validation.d.ts.map +1 -1
  18. package/dist/services/validation.js +115 -15
  19. package/dist/services/validation.js.map +1 -1
  20. package/dist/types/index.d.ts +1 -1
  21. package/dist/types/index.d.ts.map +1 -1
  22. package/dist/types.d.cts +1 -1
  23. package/dist/types.d.ts +1 -1
  24. package/package.json +1 -1
  25. package/src/components/ArtifactRenderer.tsx +3 -3
  26. package/src/components/CodeBlockRenderer.tsx +7 -1
  27. package/src/components/UIResourceRenderer.tsx +9 -0
  28. package/src/services/validation.test.ts +142 -2
  29. package/src/services/validation.ts +135 -17
  30. package/src/types/index.ts +1 -1
  31. 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
- // Validate labels match dataset length
255
- const expectedLength = params.data.labels.length
256
- for (const [index, dataset] of params.data.datasets.entries()) {
257
- if (dataset.data.length !== expectedLength) {
258
- errors.push({
259
- path: `params.data.datasets[${index}]`,
260
- message: `Dataset length mismatch: expected ${expectedLength}, got ${dataset.data.length}`,
261
- code: 'DATA_LENGTH_MISMATCH',
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
- // Validate numeric data
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 (typeof value !== 'number' || !Number.isFinite(value)) {
270
- errors.push({
271
- path: `params.data.datasets[${index}].data[${dataIndex}]`,
272
- message: `Invalid data value: ${value} (must be finite number)`,
273
- code: 'INVALID_DATA_TYPE',
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
@@ -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)