@seed-ship/mcp-ui-solid 5.3.1 → 5.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/dist/components/StreamingUIRenderer.cjs +106 -90
  3. package/dist/components/StreamingUIRenderer.cjs.map +1 -1
  4. package/dist/components/StreamingUIRenderer.d.ts +7 -0
  5. package/dist/components/StreamingUIRenderer.d.ts.map +1 -1
  6. package/dist/components/StreamingUIRenderer.js +107 -91
  7. package/dist/components/StreamingUIRenderer.js.map +1 -1
  8. package/dist/components/UIResourceRenderer.cjs +101 -82
  9. package/dist/components/UIResourceRenderer.cjs.map +1 -1
  10. package/dist/components/UIResourceRenderer.d.ts +23 -0
  11. package/dist/components/UIResourceRenderer.d.ts.map +1 -1
  12. package/dist/components/UIResourceRenderer.js +102 -83
  13. package/dist/components/UIResourceRenderer.js.map +1 -1
  14. package/dist/index.cjs +7 -0
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.cts +3 -0
  17. package/dist/index.d.ts +3 -0
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +7 -0
  20. package/dist/index.js.map +1 -1
  21. package/dist/mcp-ui-spec/dist/schemas.cjs +493 -0
  22. package/dist/mcp-ui-spec/dist/schemas.cjs.map +1 -0
  23. package/dist/mcp-ui-spec/dist/schemas.js +493 -0
  24. package/dist/mcp-ui-spec/dist/schemas.js.map +1 -0
  25. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/ZodError.cjs +118 -0
  26. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/ZodError.cjs.map +1 -0
  27. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/ZodError.js +118 -0
  28. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/ZodError.js.map +1 -0
  29. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/errors.cjs +10 -0
  30. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/errors.cjs.map +1 -0
  31. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/errors.js +10 -0
  32. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/errors.js.map +1 -0
  33. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/errorUtil.cjs +8 -0
  34. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/errorUtil.cjs.map +1 -0
  35. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/errorUtil.js +9 -0
  36. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/errorUtil.js.map +1 -0
  37. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.cjs +122 -0
  38. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.cjs.map +1 -0
  39. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js +122 -0
  40. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js.map +1 -0
  41. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/util.cjs +137 -0
  42. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/util.cjs.map +1 -0
  43. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/util.js +139 -0
  44. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/util.js.map +1 -0
  45. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/locales/en.cjs +105 -0
  46. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/locales/en.cjs.map +1 -0
  47. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/locales/en.js +106 -0
  48. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/locales/en.js.map +1 -0
  49. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.cjs +3229 -0
  50. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.cjs.map +1 -0
  51. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.js +3230 -0
  52. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.js.map +1 -0
  53. package/dist/services/validation.cjs +70 -152
  54. package/dist/services/validation.cjs.map +1 -1
  55. package/dist/services/validation.d.ts.map +1 -1
  56. package/dist/services/validation.js +70 -152
  57. package/dist/services/validation.js.map +1 -1
  58. package/dist/utils/logger.cjs +26 -4
  59. package/dist/utils/logger.cjs.map +1 -1
  60. package/dist/utils/logger.d.ts +30 -3
  61. package/dist/utils/logger.d.ts.map +1 -1
  62. package/dist/utils/logger.js +27 -5
  63. package/dist/utils/logger.js.map +1 -1
  64. package/dist/utils/perf.cjs +34 -0
  65. package/dist/utils/perf.cjs.map +1 -0
  66. package/dist/utils/perf.d.ts +19 -0
  67. package/dist/utils/perf.d.ts.map +1 -0
  68. package/dist/utils/perf.js +34 -0
  69. package/dist/utils/perf.js.map +1 -0
  70. package/package.json +3 -2
  71. package/src/components/StreamingUIRenderer.tsx +54 -2
  72. package/src/components/UIResourceRenderer.errorMode.test.tsx +95 -0
  73. package/src/components/UIResourceRenderer.tsx +72 -4
  74. package/src/index.ts +7 -0
  75. package/src/services/validation.spec-migration.test.ts +207 -0
  76. package/src/services/validation.ts +132 -178
  77. package/src/utils/logger.test.ts +130 -0
  78. package/src/utils/logger.ts +60 -7
  79. package/src/utils/perf.test.ts +59 -0
  80. package/src/utils/perf.ts +50 -0
  81. package/tsconfig.tsbuildinfo +1 -1
@@ -1,3 +1,4 @@
1
+ import { ArtifactComponentParamsSchema, CodeComponentParamsSchema, ActionGroupParamsSchema, ImageGalleryParamsSchema, CarouselComponentParamsSchema, VideoComponentParamsSchema, ActionParamsSchema, LinkComponentParamsSchema, ImageComponentParamsSchema, IframeComponentParamsSchema, TextComponentParamsSchema, MetricComponentParamsSchema } from "../mcp-ui-spec/dist/schemas.js";
1
2
  const KNOWN_COMPONENT_TYPES = /* @__PURE__ */ new Set([
2
3
  "chart",
3
4
  "table",
@@ -19,6 +20,27 @@ const KNOWN_COMPONENT_TYPES = /* @__PURE__ */ new Set([
19
20
  "code",
20
21
  "map"
21
22
  ]);
23
+ const SPEC_VALIDATORS = {
24
+ metric: { schema: MetricComponentParamsSchema, legacyCode: "INVALID_METRIC" },
25
+ text: { schema: TextComponentParamsSchema, legacyCode: "INVALID_TEXT" },
26
+ iframe: { schema: IframeComponentParamsSchema, legacyCode: "INVALID_IFRAME" },
27
+ image: { schema: ImageComponentParamsSchema, legacyCode: "INVALID_IMAGE" },
28
+ link: { schema: LinkComponentParamsSchema, legacyCode: "INVALID_LINK" },
29
+ action: { schema: ActionParamsSchema, legacyCode: "INVALID_ACTION" },
30
+ video: { schema: VideoComponentParamsSchema, legacyCode: "INVALID_VIDEO" },
31
+ carousel: { schema: CarouselComponentParamsSchema, legacyCode: "EMPTY_CAROUSEL" },
32
+ "image-gallery": { schema: ImageGalleryParamsSchema, legacyCode: "EMPTY_GALLERY" },
33
+ "action-group": { schema: ActionGroupParamsSchema, legacyCode: "EMPTY_ACTION_GROUP" },
34
+ code: { schema: CodeComponentParamsSchema, legacyCode: "INVALID_CODE" },
35
+ artifact: { schema: ArtifactComponentParamsSchema, legacyCode: "INVALID_ARTIFACT" }
36
+ };
37
+ function mapZodIssuesToErrors(issues, legacyCode) {
38
+ return issues.map((issue) => ({
39
+ path: issue.path.length > 0 ? `params.${issue.path.join(".")}` : "params",
40
+ message: issue.message,
41
+ code: legacyCode
42
+ }));
43
+ }
22
44
  const DEFAULT_RESOURCE_LIMITS = {
23
45
  maxDataPoints: 1e3,
24
46
  maxTableRows: 100,
@@ -417,6 +439,7 @@ function getIframeSandbox(url, options) {
417
439
  return baseSandbox;
418
440
  }
419
441
  function validateComponent(component, options) {
442
+ var _a;
420
443
  const limits = (options == null ? void 0 : options.limits) ?? DEFAULT_RESOURCE_LIMITS;
421
444
  const errors = [];
422
445
  if (!component.params) {
@@ -430,171 +453,66 @@ function validateComponent(component, options) {
430
453
  if (!sizeResult.valid) {
431
454
  errors.push(...sizeResult.errors || []);
432
455
  }
433
- switch (component.type) {
434
- case "chart": {
435
- const chartResult = validateChartComponent(component.params, limits);
436
- if (!chartResult.valid) {
437
- errors.push(...chartResult.errors || []);
438
- }
439
- break;
440
- }
441
- case "table": {
442
- const tableResult = validateTableComponent(component.params, limits);
443
- if (!tableResult.valid) {
444
- errors.push(...tableResult.errors || []);
445
- }
446
- break;
447
- }
448
- case "metric": {
449
- const metricParams = component.params;
450
- if (!metricParams.title || !metricParams.value) {
451
- errors.push({
452
- path: "params",
453
- message: "Metric must have title and value",
454
- code: "INVALID_METRIC"
455
- });
456
- }
457
- break;
458
- }
459
- case "text": {
460
- const textParams = component.params;
461
- if (!textParams.content) {
462
- errors.push({
463
- path: "params",
464
- message: "Text component must have content",
465
- code: "INVALID_TEXT"
466
- });
467
- }
468
- break;
456
+ const specValidator = SPEC_VALIDATORS[component.type];
457
+ if (specValidator) {
458
+ const result = specValidator.schema.safeParse(component.params);
459
+ if (!result.success) {
460
+ errors.push(...mapZodIssuesToErrors(result.error.issues, specValidator.legacyCode));
469
461
  }
470
- case "iframe": {
471
- const iframeParams = component.params;
472
- if (!iframeParams.url) {
473
- errors.push({
474
- path: "params",
475
- message: "Iframe component must have url",
476
- code: "INVALID_IFRAME"
477
- });
478
- } else {
479
- const iframeResult = validateIframeDomain(iframeParams.url, {
462
+ if (result.success && (component.type === "iframe" || component.type === "video")) {
463
+ const url = (_a = component.params) == null ? void 0 : _a.url;
464
+ if (typeof url === "string") {
465
+ const domainResult = validateIframeDomain(url, {
480
466
  policy: options == null ? void 0 : options.iframePolicy,
481
467
  customDomains: options == null ? void 0 : options.customIframeDomains
482
468
  });
483
- if (!iframeResult.valid) {
484
- errors.push(...iframeResult.errors || []);
469
+ if (!domainResult.valid) {
470
+ errors.push(...domainResult.errors || []);
485
471
  }
486
472
  }
487
- break;
488
- }
489
- case "image": {
490
- const imageParams = component.params;
491
- if (!imageParams.url) {
492
- errors.push({
493
- path: "params",
494
- message: "Image component must have url",
495
- code: "INVALID_IMAGE"
496
- });
497
- }
498
- break;
499
- }
500
- case "link": {
501
- const linkParams = component.params;
502
- if (!linkParams.url) {
503
- errors.push({
504
- path: "params",
505
- message: "Link component must have url",
506
- code: "INVALID_LINK"
507
- });
508
- }
509
- break;
510
- }
511
- case "action": {
512
- const actionParams = component.params;
513
- if (!actionParams.label) {
514
- errors.push({
515
- path: "params",
516
- message: "Action component must have label",
517
- code: "INVALID_ACTION"
518
- });
519
- }
520
- break;
521
473
  }
522
- case "video": {
523
- const videoParams = component.params;
524
- if (!videoParams.url) {
525
- errors.push({ path: "params", message: "Video component must have url", code: "INVALID_VIDEO" });
526
- } else {
527
- const videoResult = validateIframeDomain(videoParams.url, {
528
- policy: options == null ? void 0 : options.iframePolicy,
529
- customDomains: options == null ? void 0 : options.customIframeDomains
530
- });
531
- if (!videoResult.valid) {
532
- errors.push(...videoResult.errors || []);
474
+ } else {
475
+ switch (component.type) {
476
+ case "chart": {
477
+ const chartResult = validateChartComponent(component.params, limits);
478
+ if (!chartResult.valid) {
479
+ errors.push(...chartResult.errors || []);
533
480
  }
481
+ break;
534
482
  }
535
- break;
536
- }
537
- case "carousel": {
538
- const carouselParams = component.params;
539
- if (!Array.isArray(carouselParams.items) || carouselParams.items.length === 0) {
540
- errors.push({ path: "params.items", message: "Carousel must have non-empty items array", code: "EMPTY_CAROUSEL" });
541
- }
542
- break;
543
- }
544
- case "image-gallery": {
545
- const galleryParams = component.params;
546
- if (!Array.isArray(galleryParams.images) || galleryParams.images.length === 0) {
547
- errors.push({ path: "params.images", message: "Gallery must have non-empty images array", code: "EMPTY_GALLERY" });
548
- }
549
- break;
550
- }
551
- case "form": {
552
- const formParams = component.params;
553
- if (!Array.isArray(formParams.fields) || formParams.fields.length === 0) {
554
- errors.push({ path: "params.fields", message: "Form must have non-empty fields array", code: "EMPTY_FORM" });
555
- }
556
- break;
557
- }
558
- case "action-group": {
559
- const agParams = component.params;
560
- if (!Array.isArray(agParams.actions) || agParams.actions.length === 0) {
561
- errors.push({ path: "params.actions", message: "Action group must have non-empty actions array", code: "EMPTY_ACTION_GROUP" });
562
- }
563
- break;
564
- }
565
- case "code": {
566
- const codeParams = component.params;
567
- if (!codeParams.code) {
568
- errors.push({ path: "params.code", message: "Code component must have code content", code: "INVALID_CODE" });
483
+ case "table": {
484
+ const tableResult = validateTableComponent(component.params, limits);
485
+ if (!tableResult.valid) {
486
+ errors.push(...tableResult.errors || []);
487
+ }
488
+ break;
569
489
  }
570
- break;
571
- }
572
- case "map": {
573
- const mapParams = component.params;
574
- if (!mapParams.center && (!Array.isArray(mapParams.markers) || mapParams.markers.length === 0)) {
575
- errors.push({ path: "params", message: "Map must have center or markers", code: "INVALID_MAP" });
490
+ case "form": {
491
+ const formParams = component.params;
492
+ if (!Array.isArray(formParams.fields) || formParams.fields.length === 0) {
493
+ errors.push({ path: "params.fields", message: "Form must have non-empty fields array", code: "EMPTY_FORM" });
494
+ }
495
+ break;
576
496
  }
577
- break;
578
- }
579
- case "modal": {
580
- break;
581
- }
582
- case "artifact": {
583
- const artifactParams = component.params;
584
- if (!artifactParams.content) {
585
- errors.push({ path: "params.content", message: "Artifact must have content", code: "INVALID_ARTIFACT" });
497
+ case "map": {
498
+ const mapParams = component.params;
499
+ if (!mapParams.center && (!Array.isArray(mapParams.markers) || mapParams.markers.length === 0)) {
500
+ errors.push({ path: "params", message: "Map must have center or markers", code: "INVALID_MAP" });
501
+ }
502
+ break;
586
503
  }
587
- break;
504
+ case "modal":
505
+ break;
506
+ default:
507
+ if (!KNOWN_COMPONENT_TYPES.has(component.type)) {
508
+ errors.push({
509
+ path: "type",
510
+ message: `Unknown component type: ${component.type}`,
511
+ code: "UNKNOWN_COMPONENT_TYPE"
512
+ });
513
+ }
514
+ break;
588
515
  }
589
- default:
590
- if (!KNOWN_COMPONENT_TYPES.has(component.type)) {
591
- errors.push({
592
- path: "type",
593
- message: `Unknown component type: ${component.type}`,
594
- code: "UNKNOWN_COMPONENT_TYPE"
595
- });
596
- }
597
- break;
598
516
  }
599
517
  return {
600
518
  valid: errors.length === 0,
@@ -1 +1 @@
1
- {"version":3,"file":"validation.js","sources":["../../src/services/validation.ts"],"sourcesContent":["/**\n * Component Validation Service\n * Phase 0: Resource Limits & Schema Validation\n *\n * Validates LLM-generated components against:\n * - JSON schema\n * - Resource limits (data points, payload size, grid bounds)\n * - Security constraints (domain whitelist, XSS prevention)\n */\n\nimport type {\n UIComponent,\n UILayout,\n ValidationResult,\n ResourceLimits,\n ChartComponentParams,\n TableComponentParams,\n FormFieldParams,\n IframePolicy,\n ValidationOptions,\n ComponentType,\n} from '../types'\n\n/**\n * All known ComponentType values — used to distinguish known-but-unvalidated\n * types (pass through) from truly unknown strings (reject).\n */\nconst KNOWN_COMPONENT_TYPES: Set<string> = new Set<ComponentType>([\n 'chart', 'table', 'metric', 'text', 'grid', 'iframe', 'image', 'link',\n 'action', 'footer', 'carousel', 'artifact', 'form', 'modal',\n 'action-group', 'image-gallery', 'video', 'code', 'map',\n])\n\n/**\n * Default resource limits (configurable via env)\n */\nexport const DEFAULT_RESOURCE_LIMITS: ResourceLimits = {\n maxDataPoints: 1000,\n maxTableRows: 100,\n maxPayloadSize: 50 * 1024, // 50KB\n renderTimeout: 5000, // 5 seconds\n}\n\n/**\n * Default allowed iframe domains (whitelist)\n * Must match CSP frame-src directive\n * Updated Sprint 7: Added code, design, docs, and map providers\n *\n * This list is exported for transparency and can be extended via ValidationOptions\n */\nexport const DEFAULT_IFRAME_DOMAINS = [\n // Charts\n 'quickchart.io',\n 'www.quickchart.io',\n\n // Deposium\n 'deposium.com',\n 'deposium.vip',\n 'deposium.ai',\n\n // Development\n 'localhost',\n\n // Video providers (Sprint 5)\n 'youtube.com',\n 'www.youtube.com',\n 'youtube-nocookie.com',\n 'www.youtube-nocookie.com',\n 'youtu.be',\n 'vimeo.com',\n 'player.vimeo.com',\n\n // Code playgrounds (Sprint 7)\n 'codepen.io',\n 'codesandbox.io',\n 'stackblitz.com',\n 'jsfiddle.net',\n\n // Design tools (Sprint 7)\n 'figma.com',\n 'www.figma.com',\n 'miro.com',\n\n // Google services (Sprint 7)\n 'docs.google.com',\n 'drive.google.com',\n 'sheets.google.com',\n 'slides.google.com',\n 'maps.google.com',\n 'www.google.com',\n 'datastudio.google.com',\n 'lookerstudio.google.com',\n\n // Productivity (Sprint 7)\n 'airtable.com',\n 'notion.so',\n 'www.notion.so',\n\n // Maps (Sprint 7)\n 'openstreetmap.org',\n 'www.openstreetmap.org',\n\n // Analytics/Dashboards (Sprint 7)\n 'public.tableau.com',\n 'app.powerbi.com',\n 'observablehq.com',\n\n // Diagrams & Whiteboards (v2.0.0)\n 'mermaid.live',\n 'excalidraw.com',\n 'lucidchart.com',\n 'lucid.app',\n\n // Video - Business (v2.0.0)\n 'loom.com',\n 'www.loom.com',\n 'cloudflarestream.com',\n 'streamable.com',\n\n // Code repositories (v2.0.0)\n 'github.com',\n 'gist.github.com',\n 'gitlab.com',\n 'replit.com',\n 'glitch.com',\n\n // Business tools (v2.0.0)\n 'calendly.com',\n 'typeform.com',\n 'cal.com',\n\n // Design (v2.0.0)\n 'canva.com',\n\n // Deploy previews (v2.0.0)\n 'vercel.app',\n 'netlify.app',\n\n // E-commerce (v2.0.0)\n 'amazon.com',\n 'amazon.fr',\n 'amazon.de',\n 'amazon.co.uk',\n 'amazon.es',\n 'amazon.it',\n 'amazon.ca',\n 'amazon.co.jp',\n 'images-amazon.com',\n 'media-amazon.com',\n 'ws-na.amazon-adsystem.com',\n\n // MCP Connectors — embed-capable services (v2.2.7)\n 'gamma.app',\n 'www.gamma.app',\n 'app.hubspot.com',\n 'share.hubspot.com',\n 'www.data.gouv.fr',\n 'data.gouv.fr',\n 'clinicaltrials.gov',\n 'www.clinicaltrials.gov',\n 'linear.app',\n 'www.linear.app',\n\n // Payment platforms (v2.2.12)\n 'polar.sh',\n 'www.polar.sh',\n 'checkout.stripe.com',\n 'js.stripe.com',\n 'billing.stripe.com',\n 'buy.stripe.com',\n 'connect.stripe.com',\n 'invoice.stripe.com',\n]\n\n/**\n * Trusted iframe domains that require allow-same-origin to function.\n * These domains need access to their own cookies/storage for auth.\n * All other whitelisted domains get a restrictive sandbox without allow-same-origin.\n */\nexport const TRUSTED_IFRAME_DOMAINS = [\n // Deposium (own domains)\n 'deposium.com',\n 'deposium.vip',\n 'deposium.ai',\n 'localhost',\n\n // Google services (need auth cookies)\n 'docs.google.com',\n 'drive.google.com',\n 'sheets.google.com',\n 'slides.google.com',\n 'maps.google.com',\n 'datastudio.google.com',\n 'lookerstudio.google.com',\n\n // Productivity (need auth)\n 'notion.so',\n 'www.notion.so',\n 'airtable.com',\n 'figma.com',\n 'www.figma.com',\n 'miro.com',\n\n // Payment (need auth + cookies for checkout)\n 'polar.sh',\n 'www.polar.sh',\n 'checkout.stripe.com',\n 'js.stripe.com',\n 'billing.stripe.com',\n 'buy.stripe.com',\n 'connect.stripe.com',\n 'invoice.stripe.com',\n\n // Business tools (need auth)\n 'app.hubspot.com',\n 'share.hubspot.com',\n 'app.powerbi.com',\n 'linear.app',\n 'www.linear.app',\n 'calendly.com',\n 'typeform.com',\n 'cal.com',\n 'canva.com',\n]\n\n/**\n * Validate grid position bounds (1-12 columns)\n */\nexport function validateGridPosition(position: UIComponent['position']): ValidationResult {\n const errors: ValidationResult['errors'] = []\n\n // ✅ PHASE 3 FIX: Defensive check for undefined position\n if (!position) {\n return {\n valid: false,\n errors: [\n {\n path: 'position',\n message: 'Position is required',\n code: 'MISSING_POSITION',\n },\n ],\n }\n }\n\n if (position.colStart < 1 || position.colStart > 12) {\n errors.push({\n path: 'position.colStart',\n message: 'Column start must be between 1 and 12',\n code: 'INVALID_GRID_COL_START',\n })\n }\n\n if (position.colSpan < 1 || position.colSpan > 12) {\n errors.push({\n path: 'position.colSpan',\n message: 'Column span must be between 1 and 12',\n code: 'INVALID_GRID_COL_SPAN',\n })\n }\n\n if (position.colStart + position.colSpan - 1 > 12) {\n errors.push({\n path: 'position',\n message: 'Column start + span exceeds grid width (12)',\n code: 'GRID_OVERFLOW',\n })\n }\n\n if (position.rowStart !== undefined && position.rowStart < 1) {\n errors.push({\n path: 'position.rowStart',\n message: 'Row start must be >= 1',\n code: 'INVALID_GRID_ROW_START',\n })\n }\n\n if (position.rowSpan !== undefined && position.rowSpan < 1) {\n errors.push({\n path: 'position.rowSpan',\n message: 'Row span must be >= 1',\n code: 'INVALID_GRID_ROW_SPAN',\n })\n }\n\n return {\n valid: errors.length === 0,\n errors: errors.length > 0 ? errors : undefined,\n }\n}\n\n/**\n * Validate chart component against resource limits\n */\nexport function validateChartComponent(\n params: ChartComponentParams,\n limits: ResourceLimits = DEFAULT_RESOURCE_LIMITS\n): ValidationResult {\n const errors: ValidationResult['errors'] = []\n\n // Guard: params.data must exist with labels + datasets\n if (!params?.data) {\n return { valid: false, errors: [{ path: 'params.data', message: 'Missing chart data object', code: 'MISSING_DATA' }] }\n }\n if (!Array.isArray(params.data.datasets)) {\n return { valid: false, errors: [{ path: 'params.data.datasets', message: 'Missing or invalid datasets array', code: 'MISSING_DATASETS' }] }\n }\n // Detect point-based charts (scatter/bubble) or object data (time-series line)\n const chartType = params.type || 'bar'\n const firstDataPoint = params.data.datasets[0]?.data?.[0]\n const hasObjectData = typeof firstDataPoint === 'object' && firstDataPoint !== null && 'x' in firstDataPoint\n const isPointChart = chartType === 'scatter' || chartType === 'bubble' || hasObjectData\n\n // Labels required only for categorical charts (not scatter/bubble/time-series)\n if (!isPointChart) {\n if (!Array.isArray(params.data.labels)) {\n return { valid: false, errors: [{ path: 'params.data.labels', message: 'Missing or invalid labels array', code: 'MISSING_LABELS' }] }\n }\n }\n\n // Validate data points count\n const totalDataPoints = params.data.datasets.reduce(\n (sum, dataset) => sum + (Array.isArray(dataset.data) ? dataset.data.length : 0),\n 0\n )\n\n if (totalDataPoints > limits.maxDataPoints) {\n errors.push({\n path: 'params.data',\n message: `Chart exceeds max data points: ${totalDataPoints} > ${limits.maxDataPoints}`,\n code: 'RESOURCE_LIMIT_EXCEEDED',\n })\n }\n\n // Length mismatch check — only for categorical charts, skip empty datasets\n if (!isPointChart && Array.isArray(params.data.labels)) {\n const expectedLength = params.data.labels.length\n for (const [index, dataset] of params.data.datasets.entries()) {\n if (Array.isArray(dataset.data) && dataset.data.length > 0 && dataset.data.length !== expectedLength) {\n errors.push({\n path: `params.data.datasets[${index}]`,\n message: `Dataset length mismatch: expected ${expectedLength}, got ${dataset.data.length}`,\n code: 'DATA_LENGTH_MISMATCH',\n })\n }\n }\n }\n\n // Data type validation — numbers for categorical, {x,y} objects for point charts\n for (const [index, dataset] of params.data.datasets.entries()) {\n if (!Array.isArray(dataset.data)) continue\n for (const [dataIndex, value] of dataset.data.entries()) {\n if (isPointChart) {\n const vObj = value as any\n if (typeof value !== 'object' || value === null || vObj.x == null || typeof vObj.y !== 'number') {\n errors.push({\n path: `params.data.datasets[${index}].data[${dataIndex}]`,\n message: `Invalid point data: expected {x, y} object`,\n code: 'INVALID_POINT_DATA',\n })\n }\n } else {\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n errors.push({\n path: `params.data.datasets[${index}].data[${dataIndex}]`,\n message: `Invalid data value: ${value} (must be finite number)`,\n code: 'INVALID_DATA_TYPE',\n })\n }\n }\n }\n }\n\n return {\n valid: errors.length === 0,\n errors: errors.length > 0 ? errors : undefined,\n }\n}\n\n/**\n * Validate table component against resource limits\n */\nexport function validateTableComponent(\n params: TableComponentParams,\n limits: ResourceLimits = DEFAULT_RESOURCE_LIMITS\n): ValidationResult {\n const errors: ValidationResult['errors'] = []\n\n // Validate row count\n if (params.rows.length > limits.maxTableRows) {\n errors.push({\n path: 'params.rows',\n message: `Table exceeds max rows: ${params.rows.length} > ${limits.maxTableRows}`,\n code: 'RESOURCE_LIMIT_EXCEEDED',\n })\n }\n\n // Validate columns\n if (params.columns.length === 0) {\n errors.push({\n path: 'params.columns',\n message: 'Table must have at least one column',\n code: 'EMPTY_COLUMNS',\n })\n }\n\n // Validate column keys are unique\n const columnKeys = new Set<string>()\n for (const [index, column] of params.columns.entries()) {\n if (columnKeys.has(column.key)) {\n errors.push({\n path: `params.columns[${index}]`,\n message: `Duplicate column key: ${column.key}`,\n code: 'DUPLICATE_COLUMN_KEY',\n })\n }\n columnKeys.add(column.key)\n }\n\n // Validate rows have valid data for defined columns\n for (const [rowIndex, row] of params.rows.entries()) {\n for (const column of params.columns) {\n if (!(column.key in row)) {\n errors.push({\n path: `params.rows[${rowIndex}]`,\n message: `Missing column key: ${column.key}`,\n code: 'MISSING_COLUMN_DATA',\n })\n }\n }\n }\n\n return {\n valid: errors.length === 0,\n errors: errors.length > 0 ? errors : undefined,\n }\n}\n\n/**\n * Validate payload size\n */\nexport function validatePayloadSize(\n component: UIComponent,\n limits: ResourceLimits = DEFAULT_RESOURCE_LIMITS\n): ValidationResult {\n const payloadSize = JSON.stringify(component).length\n\n if (payloadSize > limits.maxPayloadSize) {\n return {\n valid: false,\n errors: [\n {\n path: 'component',\n message: `Payload size exceeds limit: ${payloadSize} > ${limits.maxPayloadSize} bytes`,\n code: 'PAYLOAD_TOO_LARGE',\n },\n ],\n }\n }\n\n return { valid: true }\n}\n\n/**\n * Sanitize string to prevent XSS\n * Basic implementation - DOMPurify used at render time\n */\nexport function sanitizeString(input: string): string {\n return input\n .replace(/<script\\b[^<]*(?:(?!<\\/script>)<[^<]*)*<\\/script>/gi, '')\n .replace(/on\\w+=\"[^\"]*\"/gi, '')\n .replace(/javascript:/gi, '')\n}\n\n/**\n * Validate iframe domain against whitelist\n *\n * @param url - The URL to validate\n * @param options - Optional validation options\n * @param options.policy - 'strict' (default), 'extend', or 'allow-all'\n * @param options.customDomains - Additional domains when policy is 'extend'\n */\nexport function validateIframeDomain(\n url: string,\n options?: { policy?: IframePolicy; customDomains?: string[] }\n): ValidationResult {\n // If allow-all, skip validation\n if (options?.policy === 'allow-all') {\n return { valid: true }\n }\n\n try {\n const parsedUrl = new URL(url)\n const domain = parsedUrl.hostname\n\n // Build effective whitelist\n let effectiveWhitelist = DEFAULT_IFRAME_DOMAINS\n if (options?.policy === 'extend' && options.customDomains) {\n effectiveWhitelist = [...DEFAULT_IFRAME_DOMAINS, ...options.customDomains]\n }\n\n const isAllowed = effectiveWhitelist.some(\n (allowed) => domain === allowed || domain.endsWith(`.${allowed}`) || allowed === 'localhost'\n )\n\n if (!isAllowed) {\n return {\n valid: false,\n errors: [\n {\n path: 'url',\n message: `Domain not whitelisted: ${domain}`,\n code: 'DOMAIN_NOT_WHITELISTED',\n },\n ],\n }\n }\n\n return { valid: true }\n } catch (error) {\n return {\n valid: false,\n errors: [\n {\n path: 'url',\n message: 'Invalid URL format',\n code: 'INVALID_URL',\n },\n ],\n }\n }\n}\n\n/**\n * Get the appropriate sandbox attribute for an iframe URL.\n *\n * Trusted domains (Google, Deposium, payment, auth-requiring services) get\n * `allow-same-origin` so they can access their own cookies/storage.\n * All other whitelisted domains get a restrictive sandbox without it,\n * preventing access to the parent page's localStorage/cookies.\n *\n * @param url - The iframe URL\n * @param options - Optional custom trusted domains\n * @returns sandbox attribute string\n */\nexport function getIframeSandbox(\n url: string,\n options?: { customTrustedDomains?: string[] }\n): string {\n const baseSandbox = 'allow-scripts allow-popups'\n\n try {\n const domain = new URL(url).hostname\n let trustedList = TRUSTED_IFRAME_DOMAINS\n if (options?.customTrustedDomains) {\n trustedList = [...TRUSTED_IFRAME_DOMAINS, ...options.customTrustedDomains]\n }\n\n const isTrusted = trustedList.some(\n (trusted) => domain === trusted || domain.endsWith(`.${trusted}`)\n )\n\n if (isTrusted) {\n return `${baseSandbox} allow-same-origin allow-forms`\n }\n } catch {\n // Invalid URL — use restrictive sandbox\n }\n\n return baseSandbox\n}\n\n/**\n * Validate entire component\n *\n * @param component - The component to validate\n * @param options - Optional validation options (limits, iframePolicy, customIframeDomains)\n */\nexport function validateComponent(\n component: UIComponent,\n options?: ValidationOptions\n): ValidationResult {\n const limits = options?.limits ?? DEFAULT_RESOURCE_LIMITS\n const errors: ValidationResult['errors'] = []\n\n // Guard: params must exist\n if (!component.params) {\n return { valid: false, errors: [{ path: 'params', message: 'Missing component params', code: 'MISSING_PARAMS' }] }\n }\n\n // Validate grid position\n const gridResult = validateGridPosition(component.position)\n if (!gridResult.valid) {\n errors.push(...(gridResult.errors || []))\n }\n\n // Validate payload size\n const sizeResult = validatePayloadSize(component, limits)\n if (!sizeResult.valid) {\n errors.push(...(sizeResult.errors || []))\n }\n\n // Type-specific validation\n switch (component.type) {\n case 'chart': {\n const chartResult = validateChartComponent(component.params as ChartComponentParams, limits)\n if (!chartResult.valid) {\n errors.push(...(chartResult.errors || []))\n }\n break\n }\n\n case 'table': {\n const tableResult = validateTableComponent(component.params as TableComponentParams, limits)\n if (!tableResult.valid) {\n errors.push(...(tableResult.errors || []))\n }\n break\n }\n\n case 'metric': {\n // Basic validation for metrics\n const metricParams = component.params as any\n if (!metricParams.title || !metricParams.value) {\n errors.push({\n path: 'params',\n message: 'Metric must have title and value',\n code: 'INVALID_METRIC',\n })\n }\n break\n }\n\n case 'text': {\n // Basic validation for text\n const textParams = component.params as any\n if (!textParams.content) {\n errors.push({\n path: 'params',\n message: 'Text component must have content',\n code: 'INVALID_TEXT',\n })\n }\n break\n }\n\n case 'iframe': {\n // Basic validation for iframe\n const iframeParams = component.params as any\n if (!iframeParams.url) {\n errors.push({\n path: 'params',\n message: 'Iframe component must have url',\n code: 'INVALID_IFRAME',\n })\n } else {\n // Validate iframe domain against whitelist\n const iframeResult = validateIframeDomain(iframeParams.url, {\n policy: options?.iframePolicy,\n customDomains: options?.customIframeDomains,\n })\n if (!iframeResult.valid) {\n errors.push(...(iframeResult.errors || []))\n }\n }\n break\n }\n\n case 'image': {\n // Basic validation for image\n const imageParams = component.params as any\n if (!imageParams.url) {\n errors.push({\n path: 'params',\n message: 'Image component must have url',\n code: 'INVALID_IMAGE',\n })\n }\n break\n }\n\n case 'link': {\n // Basic validation for link\n const linkParams = component.params as any\n if (!linkParams.url) {\n errors.push({\n path: 'params',\n message: 'Link component must have url',\n code: 'INVALID_LINK',\n })\n }\n break\n }\n\n case 'action': {\n // Basic validation for action\n const actionParams = component.params as any\n if (!actionParams.label) {\n errors.push({\n path: 'params',\n message: 'Action component must have label',\n code: 'INVALID_ACTION',\n })\n }\n break\n }\n\n case 'video': {\n const videoParams = component.params as any\n if (!videoParams.url) {\n errors.push({ path: 'params', message: 'Video component must have url', code: 'INVALID_VIDEO' })\n } else {\n // Reuse iframe domain validation for video URLs\n const videoResult = validateIframeDomain(videoParams.url, {\n policy: options?.iframePolicy,\n customDomains: options?.customIframeDomains,\n })\n if (!videoResult.valid) {\n errors.push(...(videoResult.errors || []))\n }\n }\n break\n }\n\n case 'carousel': {\n const carouselParams = component.params as any\n if (!Array.isArray(carouselParams.items) || carouselParams.items.length === 0) {\n errors.push({ path: 'params.items', message: 'Carousel must have non-empty items array', code: 'EMPTY_CAROUSEL' })\n }\n break\n }\n\n case 'image-gallery': {\n const galleryParams = component.params as any\n if (!Array.isArray(galleryParams.images) || galleryParams.images.length === 0) {\n errors.push({ path: 'params.images', message: 'Gallery must have non-empty images array', code: 'EMPTY_GALLERY' })\n }\n break\n }\n\n case 'form': {\n const formParams = component.params as any\n if (!Array.isArray(formParams.fields) || formParams.fields.length === 0) {\n errors.push({ path: 'params.fields', message: 'Form must have non-empty fields array', code: 'EMPTY_FORM' })\n }\n break\n }\n\n case 'action-group': {\n const agParams = component.params as any\n if (!Array.isArray(agParams.actions) || agParams.actions.length === 0) {\n errors.push({ path: 'params.actions', message: 'Action group must have non-empty actions array', code: 'EMPTY_ACTION_GROUP' })\n }\n break\n }\n\n case 'code': {\n const codeParams = component.params as any\n if (!codeParams.code) {\n errors.push({ path: 'params.code', message: 'Code component must have code content', code: 'INVALID_CODE' })\n }\n break\n }\n\n case 'map': {\n // Map can auto-detect center from markers, so center is not strictly required\n const mapParams = component.params as any\n if (!mapParams.center && (!Array.isArray(mapParams.markers) || mapParams.markers.length === 0)) {\n errors.push({ path: 'params', message: 'Map must have center or markers', code: 'INVALID_MAP' })\n }\n break\n }\n\n case 'modal': {\n // Modal is valid with minimal params (title optional, content can be children)\n break\n }\n\n case 'artifact': {\n const artifactParams = component.params as any\n if (!artifactParams.content) {\n errors.push({ path: 'params.content', message: 'Artifact must have content', code: 'INVALID_ARTIFACT' })\n }\n break\n }\n\n default:\n // Known types without specific validation pass through — renderer handles errors\n // Truly unknown types (e.g. typos in streamed JSON) are rejected\n if (!KNOWN_COMPONENT_TYPES.has(component.type)) {\n errors.push({\n path: 'type',\n message: `Unknown component type: ${component.type}`,\n code: 'UNKNOWN_COMPONENT_TYPE',\n })\n }\n break\n }\n\n return {\n valid: errors.length === 0,\n errors: errors.length > 0 ? errors : undefined,\n }\n}\n\n/**\n * Validate entire layout\n *\n * @param layout - The layout to validate\n * @param options - Optional validation options (limits, iframePolicy, customIframeDomains)\n */\nexport function validateLayout(\n layout: UILayout,\n options?: ValidationOptions\n): ValidationResult {\n const errors: ValidationResult['errors'] = []\n\n // Validate component count\n if (layout.components.length === 0) {\n errors.push({\n path: 'components',\n message: 'Layout must have at least one component',\n code: 'EMPTY_LAYOUT',\n })\n }\n\n if (layout.components.length > 12) {\n errors.push({\n path: 'components',\n message: `Layout exceeds max components: ${layout.components.length} > 12`,\n code: 'TOO_MANY_COMPONENTS',\n })\n }\n\n // Validate each component\n for (const [index, component] of layout.components.entries()) {\n const result = validateComponent(component, options)\n if (!result.valid) {\n errors.push(\n ...(result.errors?.map((error) => ({\n ...error,\n path: `components[${index}].${error.path}`,\n })) || [])\n )\n }\n }\n\n // Validate grid configuration\n if (layout.grid.columns !== 12) {\n errors.push({\n path: 'grid.columns',\n message: 'Grid must have 12 columns (Bootstrap-like)',\n code: 'INVALID_GRID_COLUMNS',\n })\n }\n\n return {\n valid: errors.length === 0,\n errors: errors.length > 0 ? errors : undefined,\n }\n}\n\n/**\n * Validate a single form field value against field rules\n */\nexport function validateFieldValue(\n value: any,\n field: FormFieldParams\n): { valid: boolean; error?: string } {\n // Required check\n if (field.required) {\n if (value === undefined || value === null || value === '') {\n return { valid: false, error: `${field.label || field.name} is required` }\n }\n if (field.type === 'checkbox' && value !== true) {\n return { valid: false, error: `${field.label || field.name} must be checked` }\n }\n }\n\n // Skip further validation if value is empty and not required\n if (value === undefined || value === null || value === '') {\n return { valid: true }\n }\n\n // Type-specific validation\n switch (field.type) {\n case 'text':\n case 'textarea':\n case 'password':\n if (field.minLength && String(value).length < field.minLength) {\n return { valid: false, error: `Minimum ${field.minLength} characters required` }\n }\n if (field.maxLength && String(value).length > field.maxLength) {\n return { valid: false, error: `Maximum ${field.maxLength} characters allowed` }\n }\n if (field.pattern && !new RegExp(field.pattern).test(String(value))) {\n return { valid: false, error: 'Invalid format' }\n }\n break\n\n case 'email':\n if (!/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(String(value))) {\n return { valid: false, error: 'Invalid email address' }\n }\n break\n\n case 'number': {\n const numValue = Number(value)\n if (isNaN(numValue)) {\n return { valid: false, error: 'Must be a valid number' }\n }\n if (field.min !== undefined && numValue < field.min) {\n return { valid: false, error: `Minimum value is ${field.min}` }\n }\n if (field.max !== undefined && numValue > field.max) {\n return { valid: false, error: `Maximum value is ${field.max}` }\n }\n break\n }\n\n case 'date':\n if (field.minDate && value < field.minDate) {\n return { valid: false, error: `Date must be after ${field.minDate}` }\n }\n if (field.maxDate && value > field.maxDate) {\n return { valid: false, error: `Date must be before ${field.maxDate}` }\n }\n break\n\n case 'select':\n case 'radio':\n // Validate that value is one of the options\n if (field.options && field.options.length > 0) {\n const validValues = field.options.map((opt) => opt.value)\n if (!validValues.includes(String(value))) {\n return { valid: false, error: 'Please select a valid option' }\n }\n }\n break\n }\n\n // valueFormat validation (v4.3.0) — runs after type-specific checks\n if (field.valueFormat && value !== undefined && value !== null && value !== '') {\n const vals = Array.isArray(value) ? value : [String(value)]\n for (const v of vals) {\n if (!new RegExp(field.valueFormat).test(v)) {\n return { valid: false, error: field.valueFormatHint || `Invalid format (expected: ${field.valueFormat})` }\n }\n }\n }\n\n return { valid: true }\n}\n\n/**\n * Validate entire form data against field definitions\n */\nexport function validateFormData(\n data: Record<string, any>,\n fields: FormFieldParams[]\n): { valid: boolean; errors: Record<string, string> } {\n const errors: Record<string, string> = {}\n\n for (const field of fields) {\n const result = validateFieldValue(data[field.name], field)\n if (!result.valid && result.error) {\n errors[field.name] = result.error\n }\n }\n\n return {\n valid: Object.keys(errors).length === 0,\n errors,\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"validation.js","sources":["../../src/services/validation.ts"],"sourcesContent":["/**\n * Component Validation Service\n * Phase 0: Resource Limits & Schema Validation\n *\n * Validates LLM-generated components against:\n * - JSON schema\n * - Resource limits (data points, payload size, grid bounds)\n * - Security constraints (domain whitelist, XSS prevention)\n */\n\nimport type { ZodIssue, ZodSchema } from 'zod'\nimport {\n MetricComponentParamsSchema,\n TextComponentParamsSchema,\n IframeComponentParamsSchema,\n ImageComponentParamsSchema,\n LinkComponentParamsSchema,\n CarouselComponentParamsSchema,\n ArtifactComponentParamsSchema,\n ActionParamsSchema,\n VideoComponentParamsSchema,\n ImageGalleryParamsSchema,\n ActionGroupParamsSchema,\n CodeComponentParamsSchema,\n} from '@seed-ship/mcp-ui-spec'\nimport type {\n UIComponent,\n UILayout,\n ValidationResult,\n ResourceLimits,\n ChartComponentParams,\n TableComponentParams,\n FormFieldParams,\n IframePolicy,\n ValidationOptions,\n ComponentType,\n} from '../types'\n\n/**\n * All known ComponentType values — used to distinguish known-but-unvalidated\n * types (pass through) from truly unknown strings (reject).\n */\nconst KNOWN_COMPONENT_TYPES: Set<string> = new Set<ComponentType>([\n 'chart', 'table', 'metric', 'text', 'grid', 'iframe', 'image', 'link',\n 'action', 'footer', 'carousel', 'artifact', 'form', 'modal',\n 'action-group', 'image-gallery', 'video', 'code', 'map',\n])\n\n/**\n * Spec-driven validation dispatch table (B.1 — v5.5.0).\n *\n * For each ComponentType where we delegate shape validation to a Zod schema\n * from `@seed-ship/mcp-ui-spec`, this table maps:\n * - the schema to safeParse against\n * - the legacy error code to emit when shape parsing fails (preserves the\n * pre-v5.5.0 `errors[].code` API contract — see MCP-UI-AUDIT-2026-04-26.md\n * §I.3.a + §J.1)\n *\n * Types deliberately omitted (kept on the imperative path):\n * - `chart`, `table` — have rich imperative validators with their own\n * codes (MISSING_DATA, DATA_LENGTH_MISMATCH, RESOURCE_LIMIT_EXCEEDED, …)\n * - `form` — spec FormFieldSchema has strict regex on field\n * names that could reject LLM-generated payloads. Conservative.\n * - `map` — spec center is `tuple([number, number])`; production\n * payloads use `{lat, lng}` objects. Avoid backward-compat regression.\n * - `modal` — all params are optional; nothing to enforce.\n * - `grid`, `footer`, `composite` — pass-through, validated elsewhere.\n */\nconst SPEC_VALIDATORS: Partial<Record<ComponentType, { schema: ZodSchema; legacyCode: string }>> = {\n metric: { schema: MetricComponentParamsSchema, legacyCode: 'INVALID_METRIC' },\n text: { schema: TextComponentParamsSchema, legacyCode: 'INVALID_TEXT' },\n iframe: { schema: IframeComponentParamsSchema, legacyCode: 'INVALID_IFRAME' },\n image: { schema: ImageComponentParamsSchema, legacyCode: 'INVALID_IMAGE' },\n link: { schema: LinkComponentParamsSchema, legacyCode: 'INVALID_LINK' },\n action: { schema: ActionParamsSchema, legacyCode: 'INVALID_ACTION' },\n video: { schema: VideoComponentParamsSchema, legacyCode: 'INVALID_VIDEO' },\n carousel: { schema: CarouselComponentParamsSchema, legacyCode: 'EMPTY_CAROUSEL' },\n 'image-gallery': { schema: ImageGalleryParamsSchema, legacyCode: 'EMPTY_GALLERY' },\n 'action-group': { schema: ActionGroupParamsSchema, legacyCode: 'EMPTY_ACTION_GROUP' },\n code: { schema: CodeComponentParamsSchema, legacyCode: 'INVALID_CODE' },\n artifact: { schema: ArtifactComponentParamsSchema, legacyCode: 'INVALID_ARTIFACT' },\n}\n\n/**\n * Map a Zod issue list to the legacy `ValidationError[]` shape.\n *\n * Preserves the pre-v5.5.0 contract: `path` always begins with `params`,\n * `code` is the per-type legacy code (so consumers that filtered by\n * `errors[].code === 'EMPTY_CAROUSEL'` keep working), `message` is Zod's\n * native human-readable message.\n */\nfunction mapZodIssuesToErrors(\n issues: readonly ZodIssue[],\n legacyCode: string\n): NonNullable<ValidationResult['errors']> {\n return issues.map((issue) => ({\n path: issue.path.length > 0 ? `params.${issue.path.join('.')}` : 'params',\n message: issue.message,\n code: legacyCode,\n }))\n}\n\n/**\n * Default resource limits (configurable via env)\n */\nexport const DEFAULT_RESOURCE_LIMITS: ResourceLimits = {\n maxDataPoints: 1000,\n maxTableRows: 100,\n maxPayloadSize: 50 * 1024, // 50KB\n renderTimeout: 5000, // 5 seconds\n}\n\n/**\n * Default allowed iframe domains (whitelist)\n * Must match CSP frame-src directive\n * Updated Sprint 7: Added code, design, docs, and map providers\n *\n * This list is exported for transparency and can be extended via ValidationOptions\n */\nexport const DEFAULT_IFRAME_DOMAINS = [\n // Charts\n 'quickchart.io',\n 'www.quickchart.io',\n\n // Deposium\n 'deposium.com',\n 'deposium.vip',\n 'deposium.ai',\n\n // Development\n 'localhost',\n\n // Video providers (Sprint 5)\n 'youtube.com',\n 'www.youtube.com',\n 'youtube-nocookie.com',\n 'www.youtube-nocookie.com',\n 'youtu.be',\n 'vimeo.com',\n 'player.vimeo.com',\n\n // Code playgrounds (Sprint 7)\n 'codepen.io',\n 'codesandbox.io',\n 'stackblitz.com',\n 'jsfiddle.net',\n\n // Design tools (Sprint 7)\n 'figma.com',\n 'www.figma.com',\n 'miro.com',\n\n // Google services (Sprint 7)\n 'docs.google.com',\n 'drive.google.com',\n 'sheets.google.com',\n 'slides.google.com',\n 'maps.google.com',\n 'www.google.com',\n 'datastudio.google.com',\n 'lookerstudio.google.com',\n\n // Productivity (Sprint 7)\n 'airtable.com',\n 'notion.so',\n 'www.notion.so',\n\n // Maps (Sprint 7)\n 'openstreetmap.org',\n 'www.openstreetmap.org',\n\n // Analytics/Dashboards (Sprint 7)\n 'public.tableau.com',\n 'app.powerbi.com',\n 'observablehq.com',\n\n // Diagrams & Whiteboards (v2.0.0)\n 'mermaid.live',\n 'excalidraw.com',\n 'lucidchart.com',\n 'lucid.app',\n\n // Video - Business (v2.0.0)\n 'loom.com',\n 'www.loom.com',\n 'cloudflarestream.com',\n 'streamable.com',\n\n // Code repositories (v2.0.0)\n 'github.com',\n 'gist.github.com',\n 'gitlab.com',\n 'replit.com',\n 'glitch.com',\n\n // Business tools (v2.0.0)\n 'calendly.com',\n 'typeform.com',\n 'cal.com',\n\n // Design (v2.0.0)\n 'canva.com',\n\n // Deploy previews (v2.0.0)\n 'vercel.app',\n 'netlify.app',\n\n // E-commerce (v2.0.0)\n 'amazon.com',\n 'amazon.fr',\n 'amazon.de',\n 'amazon.co.uk',\n 'amazon.es',\n 'amazon.it',\n 'amazon.ca',\n 'amazon.co.jp',\n 'images-amazon.com',\n 'media-amazon.com',\n 'ws-na.amazon-adsystem.com',\n\n // MCP Connectors — embed-capable services (v2.2.7)\n 'gamma.app',\n 'www.gamma.app',\n 'app.hubspot.com',\n 'share.hubspot.com',\n 'www.data.gouv.fr',\n 'data.gouv.fr',\n 'clinicaltrials.gov',\n 'www.clinicaltrials.gov',\n 'linear.app',\n 'www.linear.app',\n\n // Payment platforms (v2.2.12)\n 'polar.sh',\n 'www.polar.sh',\n 'checkout.stripe.com',\n 'js.stripe.com',\n 'billing.stripe.com',\n 'buy.stripe.com',\n 'connect.stripe.com',\n 'invoice.stripe.com',\n]\n\n/**\n * Trusted iframe domains that require allow-same-origin to function.\n * These domains need access to their own cookies/storage for auth.\n * All other whitelisted domains get a restrictive sandbox without allow-same-origin.\n */\nexport const TRUSTED_IFRAME_DOMAINS = [\n // Deposium (own domains)\n 'deposium.com',\n 'deposium.vip',\n 'deposium.ai',\n 'localhost',\n\n // Google services (need auth cookies)\n 'docs.google.com',\n 'drive.google.com',\n 'sheets.google.com',\n 'slides.google.com',\n 'maps.google.com',\n 'datastudio.google.com',\n 'lookerstudio.google.com',\n\n // Productivity (need auth)\n 'notion.so',\n 'www.notion.so',\n 'airtable.com',\n 'figma.com',\n 'www.figma.com',\n 'miro.com',\n\n // Payment (need auth + cookies for checkout)\n 'polar.sh',\n 'www.polar.sh',\n 'checkout.stripe.com',\n 'js.stripe.com',\n 'billing.stripe.com',\n 'buy.stripe.com',\n 'connect.stripe.com',\n 'invoice.stripe.com',\n\n // Business tools (need auth)\n 'app.hubspot.com',\n 'share.hubspot.com',\n 'app.powerbi.com',\n 'linear.app',\n 'www.linear.app',\n 'calendly.com',\n 'typeform.com',\n 'cal.com',\n 'canva.com',\n]\n\n/**\n * Validate grid position bounds (1-12 columns)\n */\nexport function validateGridPosition(position: UIComponent['position']): ValidationResult {\n const errors: ValidationResult['errors'] = []\n\n // ✅ PHASE 3 FIX: Defensive check for undefined position\n if (!position) {\n return {\n valid: false,\n errors: [\n {\n path: 'position',\n message: 'Position is required',\n code: 'MISSING_POSITION',\n },\n ],\n }\n }\n\n if (position.colStart < 1 || position.colStart > 12) {\n errors.push({\n path: 'position.colStart',\n message: 'Column start must be between 1 and 12',\n code: 'INVALID_GRID_COL_START',\n })\n }\n\n if (position.colSpan < 1 || position.colSpan > 12) {\n errors.push({\n path: 'position.colSpan',\n message: 'Column span must be between 1 and 12',\n code: 'INVALID_GRID_COL_SPAN',\n })\n }\n\n if (position.colStart + position.colSpan - 1 > 12) {\n errors.push({\n path: 'position',\n message: 'Column start + span exceeds grid width (12)',\n code: 'GRID_OVERFLOW',\n })\n }\n\n if (position.rowStart !== undefined && position.rowStart < 1) {\n errors.push({\n path: 'position.rowStart',\n message: 'Row start must be >= 1',\n code: 'INVALID_GRID_ROW_START',\n })\n }\n\n if (position.rowSpan !== undefined && position.rowSpan < 1) {\n errors.push({\n path: 'position.rowSpan',\n message: 'Row span must be >= 1',\n code: 'INVALID_GRID_ROW_SPAN',\n })\n }\n\n return {\n valid: errors.length === 0,\n errors: errors.length > 0 ? errors : undefined,\n }\n}\n\n/**\n * Validate chart component against resource limits\n */\nexport function validateChartComponent(\n params: ChartComponentParams,\n limits: ResourceLimits = DEFAULT_RESOURCE_LIMITS\n): ValidationResult {\n const errors: ValidationResult['errors'] = []\n\n // Guard: params.data must exist with labels + datasets\n if (!params?.data) {\n return { valid: false, errors: [{ path: 'params.data', message: 'Missing chart data object', code: 'MISSING_DATA' }] }\n }\n if (!Array.isArray(params.data.datasets)) {\n return { valid: false, errors: [{ path: 'params.data.datasets', message: 'Missing or invalid datasets array', code: 'MISSING_DATASETS' }] }\n }\n // Detect point-based charts (scatter/bubble) or object data (time-series line)\n const chartType = params.type || 'bar'\n const firstDataPoint = params.data.datasets[0]?.data?.[0]\n const hasObjectData = typeof firstDataPoint === 'object' && firstDataPoint !== null && 'x' in firstDataPoint\n const isPointChart = chartType === 'scatter' || chartType === 'bubble' || hasObjectData\n\n // Labels required only for categorical charts (not scatter/bubble/time-series)\n if (!isPointChart) {\n if (!Array.isArray(params.data.labels)) {\n return { valid: false, errors: [{ path: 'params.data.labels', message: 'Missing or invalid labels array', code: 'MISSING_LABELS' }] }\n }\n }\n\n // Validate data points count\n const totalDataPoints = params.data.datasets.reduce(\n (sum, dataset) => sum + (Array.isArray(dataset.data) ? dataset.data.length : 0),\n 0\n )\n\n if (totalDataPoints > limits.maxDataPoints) {\n errors.push({\n path: 'params.data',\n message: `Chart exceeds max data points: ${totalDataPoints} > ${limits.maxDataPoints}`,\n code: 'RESOURCE_LIMIT_EXCEEDED',\n })\n }\n\n // Length mismatch check — only for categorical charts, skip empty datasets\n if (!isPointChart && Array.isArray(params.data.labels)) {\n const expectedLength = params.data.labels.length\n for (const [index, dataset] of params.data.datasets.entries()) {\n if (Array.isArray(dataset.data) && dataset.data.length > 0 && dataset.data.length !== expectedLength) {\n errors.push({\n path: `params.data.datasets[${index}]`,\n message: `Dataset length mismatch: expected ${expectedLength}, got ${dataset.data.length}`,\n code: 'DATA_LENGTH_MISMATCH',\n })\n }\n }\n }\n\n // Data type validation — numbers for categorical, {x,y} objects for point charts\n for (const [index, dataset] of params.data.datasets.entries()) {\n if (!Array.isArray(dataset.data)) continue\n for (const [dataIndex, value] of dataset.data.entries()) {\n if (isPointChart) {\n const vObj = value as any\n if (typeof value !== 'object' || value === null || vObj.x == null || typeof vObj.y !== 'number') {\n errors.push({\n path: `params.data.datasets[${index}].data[${dataIndex}]`,\n message: `Invalid point data: expected {x, y} object`,\n code: 'INVALID_POINT_DATA',\n })\n }\n } else {\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n errors.push({\n path: `params.data.datasets[${index}].data[${dataIndex}]`,\n message: `Invalid data value: ${value} (must be finite number)`,\n code: 'INVALID_DATA_TYPE',\n })\n }\n }\n }\n }\n\n return {\n valid: errors.length === 0,\n errors: errors.length > 0 ? errors : undefined,\n }\n}\n\n/**\n * Validate table component against resource limits\n */\nexport function validateTableComponent(\n params: TableComponentParams,\n limits: ResourceLimits = DEFAULT_RESOURCE_LIMITS\n): ValidationResult {\n const errors: ValidationResult['errors'] = []\n\n // Validate row count\n if (params.rows.length > limits.maxTableRows) {\n errors.push({\n path: 'params.rows',\n message: `Table exceeds max rows: ${params.rows.length} > ${limits.maxTableRows}`,\n code: 'RESOURCE_LIMIT_EXCEEDED',\n })\n }\n\n // Validate columns\n if (params.columns.length === 0) {\n errors.push({\n path: 'params.columns',\n message: 'Table must have at least one column',\n code: 'EMPTY_COLUMNS',\n })\n }\n\n // Validate column keys are unique\n const columnKeys = new Set<string>()\n for (const [index, column] of params.columns.entries()) {\n if (columnKeys.has(column.key)) {\n errors.push({\n path: `params.columns[${index}]`,\n message: `Duplicate column key: ${column.key}`,\n code: 'DUPLICATE_COLUMN_KEY',\n })\n }\n columnKeys.add(column.key)\n }\n\n // Validate rows have valid data for defined columns\n for (const [rowIndex, row] of params.rows.entries()) {\n for (const column of params.columns) {\n if (!(column.key in row)) {\n errors.push({\n path: `params.rows[${rowIndex}]`,\n message: `Missing column key: ${column.key}`,\n code: 'MISSING_COLUMN_DATA',\n })\n }\n }\n }\n\n return {\n valid: errors.length === 0,\n errors: errors.length > 0 ? errors : undefined,\n }\n}\n\n/**\n * Validate payload size\n */\nexport function validatePayloadSize(\n component: UIComponent,\n limits: ResourceLimits = DEFAULT_RESOURCE_LIMITS\n): ValidationResult {\n const payloadSize = JSON.stringify(component).length\n\n if (payloadSize > limits.maxPayloadSize) {\n return {\n valid: false,\n errors: [\n {\n path: 'component',\n message: `Payload size exceeds limit: ${payloadSize} > ${limits.maxPayloadSize} bytes`,\n code: 'PAYLOAD_TOO_LARGE',\n },\n ],\n }\n }\n\n return { valid: true }\n}\n\n/**\n * Sanitize string to prevent XSS\n * Basic implementation - DOMPurify used at render time\n */\nexport function sanitizeString(input: string): string {\n return input\n .replace(/<script\\b[^<]*(?:(?!<\\/script>)<[^<]*)*<\\/script>/gi, '')\n .replace(/on\\w+=\"[^\"]*\"/gi, '')\n .replace(/javascript:/gi, '')\n}\n\n/**\n * Validate iframe domain against whitelist\n *\n * @param url - The URL to validate\n * @param options - Optional validation options\n * @param options.policy - 'strict' (default), 'extend', or 'allow-all'\n * @param options.customDomains - Additional domains when policy is 'extend'\n */\nexport function validateIframeDomain(\n url: string,\n options?: { policy?: IframePolicy; customDomains?: string[] }\n): ValidationResult {\n // If allow-all, skip validation\n if (options?.policy === 'allow-all') {\n return { valid: true }\n }\n\n try {\n const parsedUrl = new URL(url)\n const domain = parsedUrl.hostname\n\n // Build effective whitelist\n let effectiveWhitelist = DEFAULT_IFRAME_DOMAINS\n if (options?.policy === 'extend' && options.customDomains) {\n effectiveWhitelist = [...DEFAULT_IFRAME_DOMAINS, ...options.customDomains]\n }\n\n const isAllowed = effectiveWhitelist.some(\n (allowed) => domain === allowed || domain.endsWith(`.${allowed}`) || allowed === 'localhost'\n )\n\n if (!isAllowed) {\n return {\n valid: false,\n errors: [\n {\n path: 'url',\n message: `Domain not whitelisted: ${domain}`,\n code: 'DOMAIN_NOT_WHITELISTED',\n },\n ],\n }\n }\n\n return { valid: true }\n } catch (error) {\n return {\n valid: false,\n errors: [\n {\n path: 'url',\n message: 'Invalid URL format',\n code: 'INVALID_URL',\n },\n ],\n }\n }\n}\n\n/**\n * Get the appropriate sandbox attribute for an iframe URL.\n *\n * Trusted domains (Google, Deposium, payment, auth-requiring services) get\n * `allow-same-origin` so they can access their own cookies/storage.\n * All other whitelisted domains get a restrictive sandbox without it,\n * preventing access to the parent page's localStorage/cookies.\n *\n * @param url - The iframe URL\n * @param options - Optional custom trusted domains\n * @returns sandbox attribute string\n */\nexport function getIframeSandbox(\n url: string,\n options?: { customTrustedDomains?: string[] }\n): string {\n const baseSandbox = 'allow-scripts allow-popups'\n\n try {\n const domain = new URL(url).hostname\n let trustedList = TRUSTED_IFRAME_DOMAINS\n if (options?.customTrustedDomains) {\n trustedList = [...TRUSTED_IFRAME_DOMAINS, ...options.customTrustedDomains]\n }\n\n const isTrusted = trustedList.some(\n (trusted) => domain === trusted || domain.endsWith(`.${trusted}`)\n )\n\n if (isTrusted) {\n return `${baseSandbox} allow-same-origin allow-forms`\n }\n } catch {\n // Invalid URL — use restrictive sandbox\n }\n\n return baseSandbox\n}\n\n/**\n * Validate entire component\n *\n * @param component - The component to validate\n * @param options - Optional validation options (limits, iframePolicy, customIframeDomains)\n */\nexport function validateComponent(\n component: UIComponent,\n options?: ValidationOptions\n): ValidationResult {\n const limits = options?.limits ?? DEFAULT_RESOURCE_LIMITS\n const errors: ValidationResult['errors'] = []\n\n // Guard: params must exist\n if (!component.params) {\n return { valid: false, errors: [{ path: 'params', message: 'Missing component params', code: 'MISSING_PARAMS' }] }\n }\n\n // Validate grid position\n const gridResult = validateGridPosition(component.position)\n if (!gridResult.valid) {\n errors.push(...(gridResult.errors || []))\n }\n\n // Validate payload size\n const sizeResult = validatePayloadSize(component, limits)\n if (!sizeResult.valid) {\n errors.push(...(sizeResult.errors || []))\n }\n\n // Type-specific validation (B.1 — v5.5.0).\n //\n // 12 types delegate shape validation to Zod schemas in `mcp-ui-spec` via\n // SPEC_VALIDATORS. The 5 remaining types stay imperative because they\n // need cross-field consistency, resource limits, or backward-compat logic\n // that pure Zod can't express without `.refine()` (see SPEC_VALIDATORS docstring).\n const specValidator = SPEC_VALIDATORS[component.type]\n if (specValidator) {\n const result = specValidator.schema.safeParse(component.params)\n if (!result.success) {\n errors.push(...mapZodIssuesToErrors(result.error.issues, specValidator.legacyCode))\n }\n // Iframe + video: chain the domain whitelist post-check ONLY when the\n // shape parse succeeded (i.e. url is present and a string). Skipping the\n // domain check when shape failed avoids cascading errors on the same field.\n if (result.success && (component.type === 'iframe' || component.type === 'video')) {\n const url = (component.params as { url?: string })?.url\n if (typeof url === 'string') {\n const domainResult = validateIframeDomain(url, {\n policy: options?.iframePolicy,\n customDomains: options?.customIframeDomains,\n })\n if (!domainResult.valid) {\n errors.push(...(domainResult.errors || []))\n }\n }\n }\n } else {\n // Imperative path for chart/table/form/map/modal/grid/footer/composite.\n switch (component.type) {\n case 'chart': {\n const chartResult = validateChartComponent(component.params as ChartComponentParams, limits)\n if (!chartResult.valid) {\n errors.push(...(chartResult.errors || []))\n }\n break\n }\n\n case 'table': {\n const tableResult = validateTableComponent(component.params as TableComponentParams, limits)\n if (!tableResult.valid) {\n errors.push(...(tableResult.errors || []))\n }\n break\n }\n\n case 'form': {\n const formParams = component.params as { fields?: unknown[] }\n if (!Array.isArray(formParams.fields) || formParams.fields.length === 0) {\n errors.push({ path: 'params.fields', message: 'Form must have non-empty fields array', code: 'EMPTY_FORM' })\n }\n break\n }\n\n case 'map': {\n // Map can auto-detect center from markers, so center is not strictly required.\n // Spec MapComponentParamsSchema would be too strict (tuple-only center) — kept imperative.\n const mapParams = component.params as { center?: unknown; markers?: unknown[] }\n if (!mapParams.center && (!Array.isArray(mapParams.markers) || mapParams.markers.length === 0)) {\n errors.push({ path: 'params', message: 'Map must have center or markers', code: 'INVALID_MAP' })\n }\n break\n }\n\n case 'modal':\n // Modal is valid with minimal params (title optional, content can be children).\n break\n\n default:\n // Known types without specific validation pass through — renderer handles errors.\n // Truly unknown types (e.g. typos in streamed JSON) are rejected.\n if (!KNOWN_COMPONENT_TYPES.has(component.type)) {\n errors.push({\n path: 'type',\n message: `Unknown component type: ${component.type}`,\n code: 'UNKNOWN_COMPONENT_TYPE',\n })\n }\n break\n }\n }\n\n return {\n valid: errors.length === 0,\n errors: errors.length > 0 ? errors : undefined,\n }\n}\n\n/**\n * Validate entire layout\n *\n * @param layout - The layout to validate\n * @param options - Optional validation options (limits, iframePolicy, customIframeDomains)\n */\nexport function validateLayout(\n layout: UILayout,\n options?: ValidationOptions\n): ValidationResult {\n const errors: ValidationResult['errors'] = []\n\n // Validate component count\n if (layout.components.length === 0) {\n errors.push({\n path: 'components',\n message: 'Layout must have at least one component',\n code: 'EMPTY_LAYOUT',\n })\n }\n\n if (layout.components.length > 12) {\n errors.push({\n path: 'components',\n message: `Layout exceeds max components: ${layout.components.length} > 12`,\n code: 'TOO_MANY_COMPONENTS',\n })\n }\n\n // Validate each component\n for (const [index, component] of layout.components.entries()) {\n const result = validateComponent(component, options)\n if (!result.valid) {\n errors.push(\n ...(result.errors?.map((error) => ({\n ...error,\n path: `components[${index}].${error.path}`,\n })) || [])\n )\n }\n }\n\n // Validate grid configuration\n if (layout.grid.columns !== 12) {\n errors.push({\n path: 'grid.columns',\n message: 'Grid must have 12 columns (Bootstrap-like)',\n code: 'INVALID_GRID_COLUMNS',\n })\n }\n\n return {\n valid: errors.length === 0,\n errors: errors.length > 0 ? errors : undefined,\n }\n}\n\n/**\n * Validate a single form field value against field rules\n */\nexport function validateFieldValue(\n value: any,\n field: FormFieldParams\n): { valid: boolean; error?: string } {\n // Required check\n if (field.required) {\n if (value === undefined || value === null || value === '') {\n return { valid: false, error: `${field.label || field.name} is required` }\n }\n if (field.type === 'checkbox' && value !== true) {\n return { valid: false, error: `${field.label || field.name} must be checked` }\n }\n }\n\n // Skip further validation if value is empty and not required\n if (value === undefined || value === null || value === '') {\n return { valid: true }\n }\n\n // Type-specific validation\n switch (field.type) {\n case 'text':\n case 'textarea':\n case 'password':\n if (field.minLength && String(value).length < field.minLength) {\n return { valid: false, error: `Minimum ${field.minLength} characters required` }\n }\n if (field.maxLength && String(value).length > field.maxLength) {\n return { valid: false, error: `Maximum ${field.maxLength} characters allowed` }\n }\n if (field.pattern && !new RegExp(field.pattern).test(String(value))) {\n return { valid: false, error: 'Invalid format' }\n }\n break\n\n case 'email':\n if (!/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(String(value))) {\n return { valid: false, error: 'Invalid email address' }\n }\n break\n\n case 'number': {\n const numValue = Number(value)\n if (isNaN(numValue)) {\n return { valid: false, error: 'Must be a valid number' }\n }\n if (field.min !== undefined && numValue < field.min) {\n return { valid: false, error: `Minimum value is ${field.min}` }\n }\n if (field.max !== undefined && numValue > field.max) {\n return { valid: false, error: `Maximum value is ${field.max}` }\n }\n break\n }\n\n case 'date':\n if (field.minDate && value < field.minDate) {\n return { valid: false, error: `Date must be after ${field.minDate}` }\n }\n if (field.maxDate && value > field.maxDate) {\n return { valid: false, error: `Date must be before ${field.maxDate}` }\n }\n break\n\n case 'select':\n case 'radio':\n // Validate that value is one of the options\n if (field.options && field.options.length > 0) {\n const validValues = field.options.map((opt) => opt.value)\n if (!validValues.includes(String(value))) {\n return { valid: false, error: 'Please select a valid option' }\n }\n }\n break\n }\n\n // valueFormat validation (v4.3.0) — runs after type-specific checks\n if (field.valueFormat && value !== undefined && value !== null && value !== '') {\n const vals = Array.isArray(value) ? value : [String(value)]\n for (const v of vals) {\n if (!new RegExp(field.valueFormat).test(v)) {\n return { valid: false, error: field.valueFormatHint || `Invalid format (expected: ${field.valueFormat})` }\n }\n }\n }\n\n return { valid: true }\n}\n\n/**\n * Validate entire form data against field definitions\n */\nexport function validateFormData(\n data: Record<string, any>,\n fields: FormFieldParams[]\n): { valid: boolean; errors: Record<string, string> } {\n const errors: Record<string, string> = {}\n\n for (const field of fields) {\n const result = validateFieldValue(data[field.name], field)\n if (!result.valid && result.error) {\n errors[field.name] = result.error\n }\n }\n\n return {\n valid: Object.keys(errors).length === 0,\n errors,\n }\n}\n"],"names":[],"mappings":";AA0CA,MAAM,4CAAyC,IAAmB;AAAA,EAChE;AAAA,EAAS;AAAA,EAAS;AAAA,EAAU;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAU;AAAA,EAAS;AAAA,EAC/D;AAAA,EAAU;AAAA,EAAU;AAAA,EAAY;AAAA,EAAY;AAAA,EAAQ;AAAA,EACpD;AAAA,EAAgB;AAAA,EAAiB;AAAA,EAAS;AAAA,EAAQ;AACpD,CAAC;AAsBD,MAAM,kBAA6F;AAAA,EACjG,QAAQ,EAAE,QAAQ,6BAA6B,YAAY,iBAAA;AAAA,EAC3D,MAAM,EAAE,QAAQ,2BAA2B,YAAY,eAAA;AAAA,EACvD,QAAQ,EAAE,QAAQ,6BAA6B,YAAY,iBAAA;AAAA,EAC3D,OAAO,EAAE,QAAQ,4BAA4B,YAAY,gBAAA;AAAA,EACzD,MAAM,EAAE,QAAQ,2BAA2B,YAAY,eAAA;AAAA,EACvD,QAAQ,EAAE,QAAQ,oBAAoB,YAAY,iBAAA;AAAA,EAClD,OAAO,EAAE,QAAQ,4BAA4B,YAAY,gBAAA;AAAA,EACzD,UAAU,EAAE,QAAQ,+BAA+B,YAAY,iBAAA;AAAA,EAC/D,iBAAiB,EAAE,QAAQ,0BAA0B,YAAY,gBAAA;AAAA,EACjE,gBAAgB,EAAE,QAAQ,yBAAyB,YAAY,qBAAA;AAAA,EAC/D,MAAM,EAAE,QAAQ,2BAA2B,YAAY,eAAA;AAAA,EACvD,UAAU,EAAE,QAAQ,+BAA+B,YAAY,mBAAA;AACjE;AAUA,SAAS,qBACP,QACA,YACyC;AACzC,SAAO,OAAO,IAAI,CAAC,WAAW;AAAA,IAC5B,MAAM,MAAM,KAAK,SAAS,IAAI,UAAU,MAAM,KAAK,KAAK,GAAG,CAAC,KAAK;AAAA,IACjE,SAAS,MAAM;AAAA,IACf,MAAM;AAAA,EAAA,EACN;AACJ;AAKO,MAAM,0BAA0C;AAAA,EACrD,eAAe;AAAA,EACf,cAAc;AAAA,EACd,gBAAgB,KAAK;AAAA;AAAA,EACrB,eAAe;AAAA;AACjB;AASO,MAAM,yBAAyB;AAAA;AAAA,EAEpC;AAAA,EACA;AAAA;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAGA;AAAA;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAGA;AAAA,EACA;AAAA;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAGA;AAAA;AAAA,EAGA;AAAA,EACA;AAAA;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAOO,MAAM,yBAAyB;AAAA;AAAA,EAEpC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAC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"}
@@ -1,6 +1,26 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const isDev = typeof process !== "undefined" && process.env.NODE_ENV !== "production";
3
+ let debugOverride = null;
4
+ function readEnvFlag() {
5
+ if (typeof process !== "undefined" && process.env) {
6
+ if (process.env.MCP_UI_DEBUG === "true") return true;
7
+ if (process.env.NODE_ENV !== "production") return true;
8
+ }
9
+ if (typeof globalThis !== "undefined" && globalThis.__MCP_UI_DEBUG__ === true) {
10
+ return true;
11
+ }
12
+ return false;
13
+ }
14
+ function isDebugActive() {
15
+ if (debugOverride !== null) return debugOverride;
16
+ return readEnvFlag();
17
+ }
18
+ function setDebugMode(enabled) {
19
+ debugOverride = enabled;
20
+ }
21
+ function isDebugEnabled() {
22
+ return isDebugActive();
23
+ }
4
24
  function formatLogMessage(feature, message, context) {
5
25
  const contextStr = context ? ` ${JSON.stringify(context)}` : "";
6
26
  return `[@seed-ship/mcp-ui-solid:${feature}] ${message}${contextStr}`;
@@ -8,12 +28,12 @@ function formatLogMessage(feature, message, context) {
8
28
  function createLogger(feature) {
9
29
  return {
10
30
  info(message, context) {
11
- if (isDev) {
31
+ if (isDebugActive()) {
12
32
  console.info(formatLogMessage(feature, message, context));
13
33
  }
14
34
  },
15
35
  warn(message, context) {
16
- if (isDev) {
36
+ if (isDebugActive()) {
17
37
  console.warn(formatLogMessage(feature, message, context));
18
38
  }
19
39
  },
@@ -21,11 +41,13 @@ function createLogger(feature) {
21
41
  console.error(formatLogMessage(feature, message, context));
22
42
  },
23
43
  debug(message, context) {
24
- if (isDev) {
44
+ if (isDebugActive()) {
25
45
  console.debug(formatLogMessage(feature, message, context));
26
46
  }
27
47
  }
28
48
  };
29
49
  }
30
50
  exports.createLogger = createLogger;
51
+ exports.isDebugEnabled = isDebugEnabled;
52
+ exports.setDebugMode = setDebugMode;
31
53
  //# sourceMappingURL=logger.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"logger.cjs","sources":["../../src/utils/logger.ts"],"sourcesContent":["/**\n * Simple internal logger utility\n *\n * Provides basic logging functionality for the package.\n * Consumers can disable logging by setting NODE_ENV=production\n * or by implementing their own logging solution.\n */\n\nconst isDev = typeof process !== 'undefined' && process.env.NODE_ENV !== 'production'\n\nexport interface Logger {\n info(message: string, context?: Record<string, unknown>): void\n warn(message: string, context?: Record<string, unknown>): void\n error(message: string, context?: Record<string, unknown>): void\n debug(message: string, context?: Record<string, unknown>): void\n}\n\nfunction formatLogMessage(\n feature: string,\n message: string,\n context?: Record<string, unknown>\n): string {\n const contextStr = context ? ` ${JSON.stringify(context)}` : ''\n return `[@seed-ship/mcp-ui-solid:${feature}] ${message}${contextStr}`\n}\n\n/**\n * Creates a feature-scoped logger\n *\n * @param feature - Feature name for log prefixing\n * @returns Logger instance\n *\n * @example\n * ```typescript\n * const logger = createLogger('my-component')\n * logger.info('Component mounted', { componentId: '123' })\n * ```\n */\nexport function createLogger(feature: string): Logger {\n return {\n info(message: string, context?: Record<string, unknown>) {\n if (isDev) {\n console.info(formatLogMessage(feature, message, context))\n }\n },\n\n warn(message: string, context?: Record<string, unknown>) {\n if (isDev) {\n console.warn(formatLogMessage(feature, message, context))\n }\n },\n\n error(message: string, context?: Record<string, unknown>) {\n // Always log errors, even in production\n console.error(formatLogMessage(feature, message, context))\n },\n\n debug(message: string, context?: Record<string, unknown>) {\n if (isDev) {\n console.debug(formatLogMessage(feature, message, context))\n }\n },\n }\n}\n\n/**\n * No-op logger for testing or when logging is disabled\n */\nexport const noopLogger: Logger = {\n info: () => {},\n warn: () => {},\n error: () => {},\n debug: () => {},\n}\n"],"names":[],"mappings":";;AAQA,MAAM,QAAQ,OAAO,YAAY,eAAe,QAAQ,IAAI,aAAa;AASzE,SAAS,iBACP,SACA,SACA,SACQ;AACR,QAAM,aAAa,UAAU,IAAI,KAAK,UAAU,OAAO,CAAC,KAAK;AAC7D,SAAO,4BAA4B,OAAO,KAAK,OAAO,GAAG,UAAU;AACrE;AAcO,SAAS,aAAa,SAAyB;AACpD,SAAO;AAAA,IACL,KAAK,SAAiB,SAAmC;AACvD,UAAI,OAAO;AACT,gBAAQ,KAAK,iBAAiB,SAAS,SAAS,OAAO,CAAC;AAAA,MAC1D;AAAA,IACF;AAAA,IAEA,KAAK,SAAiB,SAAmC;AACvD,UAAI,OAAO;AACT,gBAAQ,KAAK,iBAAiB,SAAS,SAAS,OAAO,CAAC;AAAA,MAC1D;AAAA,IACF;AAAA,IAEA,MAAM,SAAiB,SAAmC;AAExD,cAAQ,MAAM,iBAAiB,SAAS,SAAS,OAAO,CAAC;AAAA,IAC3D;AAAA,IAEA,MAAM,SAAiB,SAAmC;AACxD,UAAI,OAAO;AACT,gBAAQ,MAAM,iBAAiB,SAAS,SAAS,OAAO,CAAC;AAAA,MAC3D;AAAA,IACF;AAAA,EAAA;AAEJ;;"}
1
+ {"version":3,"file":"logger.cjs","sources":["../../src/utils/logger.ts"],"sourcesContent":["/**\n * Simple internal logger utility\n *\n * Logging is enabled when EITHER:\n * 1. `process.env.NODE_ENV !== 'production'` (dev build), OR\n * 2. `process.env.MCP_UI_DEBUG === 'true'` (server-side opt-in for prod), OR\n * 3. `globalThis.__MCP_UI_DEBUG__ === true` (browser-side runtime toggle), OR\n * 4. `setDebugMode(true)` has been called from app code.\n *\n * `error` always logs regardless of mode.\n *\n * @see setDebugMode, isDebugEnabled — runtime controls (v5.4.0)\n */\n\ndeclare global {\n // Browser-side runtime flag — settable from devtools console:\n // `globalThis.__MCP_UI_DEBUG__ = true`\n // eslint-disable-next-line no-var\n var __MCP_UI_DEBUG__: boolean | undefined\n}\n\nlet debugOverride: boolean | null = null\n\nfunction readEnvFlag(): boolean {\n if (typeof process !== 'undefined' && process.env) {\n if (process.env.MCP_UI_DEBUG === 'true') return true\n if (process.env.NODE_ENV !== 'production') return true\n }\n if (typeof globalThis !== 'undefined' && globalThis.__MCP_UI_DEBUG__ === true) {\n return true\n }\n return false\n}\n\nfunction isDebugActive(): boolean {\n if (debugOverride !== null) return debugOverride\n return readEnvFlag()\n}\n\n/**\n * Programmatically enable/disable verbose logging at runtime.\n *\n * Pass `null` to clear the override and fall back to env-based detection.\n *\n * @example\n * ```ts\n * import { setDebugMode } from '@seed-ship/mcp-ui-solid'\n * setDebugMode(true) // turn on verbose logs\n * setDebugMode(false) // turn off (overrides NODE_ENV=development)\n * setDebugMode(null) // restore env-based behavior\n * ```\n */\nexport function setDebugMode(enabled: boolean | null): void {\n debugOverride = enabled\n}\n\n/**\n * Whether verbose logging is currently active (env + override combined).\n */\nexport function isDebugEnabled(): boolean {\n return isDebugActive()\n}\n\nexport interface Logger {\n info(message: string, context?: Record<string, unknown>): void\n warn(message: string, context?: Record<string, unknown>): void\n error(message: string, context?: Record<string, unknown>): void\n debug(message: string, context?: Record<string, unknown>): void\n}\n\nfunction formatLogMessage(\n feature: string,\n message: string,\n context?: Record<string, unknown>\n): string {\n const contextStr = context ? ` ${JSON.stringify(context)}` : ''\n return `[@seed-ship/mcp-ui-solid:${feature}] ${message}${contextStr}`\n}\n\n/**\n * Creates a feature-scoped logger\n *\n * @param feature - Feature name for log prefixing\n * @returns Logger instance\n *\n * @example\n * ```typescript\n * const logger = createLogger('my-component')\n * logger.info('Component mounted', { componentId: '123' })\n * ```\n */\nexport function createLogger(feature: string): Logger {\n return {\n info(message: string, context?: Record<string, unknown>) {\n if (isDebugActive()) {\n console.info(formatLogMessage(feature, message, context))\n }\n },\n\n warn(message: string, context?: Record<string, unknown>) {\n if (isDebugActive()) {\n console.warn(formatLogMessage(feature, message, context))\n }\n },\n\n error(message: string, context?: Record<string, unknown>) {\n // Always log errors, even in production\n console.error(formatLogMessage(feature, message, context))\n },\n\n debug(message: string, context?: Record<string, unknown>) {\n if (isDebugActive()) {\n console.debug(formatLogMessage(feature, message, context))\n }\n },\n }\n}\n\n/**\n * No-op logger for testing or when logging is disabled\n */\nexport const noopLogger: Logger = {\n info: () => {},\n warn: () => {},\n error: () => {},\n debug: () => {},\n}\n"],"names":[],"mappings":";;AAqBA,IAAI,gBAAgC;AAEpC,SAAS,cAAuB;AAC9B,MAAI,OAAO,YAAY,eAAe,QAAQ,KAAK;AACjD,QAAI,QAAQ,IAAI,iBAAiB,OAAQ,QAAO;AAChD,QAAI,QAAQ,IAAI,aAAa,aAAc,QAAO;AAAA,EACpD;AACA,MAAI,OAAO,eAAe,eAAe,WAAW,qBAAqB,MAAM;AAC7E,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEA,SAAS,gBAAyB;AAChC,MAAI,kBAAkB,KAAM,QAAO;AACnC,SAAO,YAAA;AACT;AAeO,SAAS,aAAa,SAA+B;AAC1D,kBAAgB;AAClB;AAKO,SAAS,iBAA0B;AACxC,SAAO,cAAA;AACT;AASA,SAAS,iBACP,SACA,SACA,SACQ;AACR,QAAM,aAAa,UAAU,IAAI,KAAK,UAAU,OAAO,CAAC,KAAK;AAC7D,SAAO,4BAA4B,OAAO,KAAK,OAAO,GAAG,UAAU;AACrE;AAcO,SAAS,aAAa,SAAyB;AACpD,SAAO;AAAA,IACL,KAAK,SAAiB,SAAmC;AACvD,UAAI,iBAAiB;AACnB,gBAAQ,KAAK,iBAAiB,SAAS,SAAS,OAAO,CAAC;AAAA,MAC1D;AAAA,IACF;AAAA,IAEA,KAAK,SAAiB,SAAmC;AACvD,UAAI,iBAAiB;AACnB,gBAAQ,KAAK,iBAAiB,SAAS,SAAS,OAAO,CAAC;AAAA,MAC1D;AAAA,IACF;AAAA,IAEA,MAAM,SAAiB,SAAmC;AAExD,cAAQ,MAAM,iBAAiB,SAAS,SAAS,OAAO,CAAC;AAAA,IAC3D;AAAA,IAEA,MAAM,SAAiB,SAAmC;AACxD,UAAI,iBAAiB;AACnB,gBAAQ,MAAM,iBAAiB,SAAS,SAAS,OAAO,CAAC;AAAA,MAC3D;AAAA,IACF;AAAA,EAAA;AAEJ;;;;"}
@@ -1,10 +1,37 @@
1
1
  /**
2
2
  * Simple internal logger utility
3
3
  *
4
- * Provides basic logging functionality for the package.
5
- * Consumers can disable logging by setting NODE_ENV=production
6
- * or by implementing their own logging solution.
4
+ * Logging is enabled when EITHER:
5
+ * 1. `process.env.NODE_ENV !== 'production'` (dev build), OR
6
+ * 2. `process.env.MCP_UI_DEBUG === 'true'` (server-side opt-in for prod), OR
7
+ * 3. `globalThis.__MCP_UI_DEBUG__ === true` (browser-side runtime toggle), OR
8
+ * 4. `setDebugMode(true)` has been called from app code.
9
+ *
10
+ * `error` always logs regardless of mode.
11
+ *
12
+ * @see setDebugMode, isDebugEnabled — runtime controls (v5.4.0)
13
+ */
14
+ declare global {
15
+ var __MCP_UI_DEBUG__: boolean | undefined;
16
+ }
17
+ /**
18
+ * Programmatically enable/disable verbose logging at runtime.
19
+ *
20
+ * Pass `null` to clear the override and fall back to env-based detection.
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * import { setDebugMode } from '@seed-ship/mcp-ui-solid'
25
+ * setDebugMode(true) // turn on verbose logs
26
+ * setDebugMode(false) // turn off (overrides NODE_ENV=development)
27
+ * setDebugMode(null) // restore env-based behavior
28
+ * ```
29
+ */
30
+ export declare function setDebugMode(enabled: boolean | null): void;
31
+ /**
32
+ * Whether verbose logging is currently active (env + override combined).
7
33
  */
34
+ export declare function isDebugEnabled(): boolean;
8
35
  export interface Logger {
9
36
  info(message: string, context?: Record<string, unknown>): void;
10
37
  warn(message: string, context?: Record<string, unknown>): void;
@@ -1 +1 @@
1
- {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IAC9D,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IAC9D,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IAC/D,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;CAChE;AAWD;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAyBpD;AAED;;GAEG;AACH,eAAO,MAAM,UAAU,EAAE,MAKxB,CAAA"}
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,CAAC,MAAM,CAAC;IAIb,IAAI,gBAAgB,EAAE,OAAO,GAAG,SAAS,CAAA;CAC1C;AAoBD;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,GAAG,IAAI,CAE1D;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,OAAO,CAExC;AAED,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IAC9D,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IAC9D,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IAC/D,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;CAChE;AAWD;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAyBpD;AAED;;GAEG;AACH,eAAO,MAAM,UAAU,EAAE,MAKxB,CAAA"}