@seed-ship/mcp-ui-solid 6.15.0 → 6.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +68 -0
- package/README.md +20 -3
- package/dist/_virtual/leaflet.markercluster-src.cjs +30 -7
- package/dist/_virtual/leaflet.markercluster-src.cjs.map +1 -1
- package/dist/_virtual/leaflet.markercluster-src.js +28 -5
- package/dist/_virtual/leaflet.markercluster-src.js.map +1 -1
- package/dist/_virtual/{leaflet.markercluster-src2.cjs → leaflet2.markercluster-src.cjs} +1 -1
- package/dist/_virtual/leaflet2.markercluster-src.cjs.map +1 -0
- package/dist/_virtual/{leaflet.markercluster-src2.js → leaflet2.markercluster-src.js} +1 -1
- package/dist/_virtual/leaflet2.markercluster-src.js.map +1 -0
- package/dist/adapters/connector.js +1 -1
- package/dist/components/ActionGroupRenderer.js +1 -1
- package/dist/components/ActionRenderer.js +1 -1
- package/dist/components/AgentCard.js +1 -1
- package/dist/components/AgentHandoff.js +1 -1
- package/dist/components/ArtifactRenderer.js +1 -1
- package/dist/components/AutocompleteDropdown.cjs +1 -2
- package/dist/components/AutocompleteDropdown.cjs.map +1 -1
- package/dist/components/AutocompleteDropdown.js +2 -3
- package/dist/components/AutocompleteFormField.cjs +1 -2
- package/dist/components/AutocompleteFormField.cjs.map +1 -1
- package/dist/components/AutocompleteFormField.js +2 -3
- package/dist/components/BriefingDiff.js +1 -1
- package/dist/components/CarouselRenderer.js +1 -1
- package/dist/components/ChartJSRenderer.js +1 -1
- package/dist/components/ChatPrompt.js +1 -1
- package/dist/components/CodeBlockRenderer.js +1 -1
- package/dist/components/ComponentToolbar.js +1 -1
- package/dist/components/DataPreviewSection.js +1 -1
- package/dist/components/DegradedFallback.cjs.map +1 -1
- package/dist/components/DegradedFallback.d.ts +1 -1
- package/dist/components/DegradedFallback.js +1 -1
- package/dist/components/DegradedFallback.js.map +1 -1
- package/dist/components/DraggableGridItem.cjs +1 -2
- package/dist/components/DraggableGridItem.cjs.map +1 -1
- package/dist/components/DraggableGridItem.js +2 -3
- package/dist/components/EditableUIResourceRenderer.cjs +1 -2
- package/dist/components/EditableUIResourceRenderer.cjs.map +1 -1
- package/dist/components/EditableUIResourceRenderer.js +2 -3
- package/dist/components/ExpandableWrapper.js +2 -2
- package/dist/components/FeedbackInline.js +1 -1
- package/dist/components/FooterRenderer.js +1 -1
- package/dist/components/FormFieldRenderer.js +1 -1
- package/dist/components/FormRenderer.js +1 -1
- package/dist/components/GenerativeUIErrorBoundary.js +1 -1
- package/dist/components/GhostText.cjs +1 -2
- package/dist/components/GhostText.cjs.map +1 -1
- package/dist/components/GhostText.js +2 -3
- package/dist/components/GraphRenderer.cjs +2 -19
- package/dist/components/GraphRenderer.cjs.map +1 -1
- package/dist/components/GraphRenderer.d.ts.map +1 -1
- package/dist/components/GraphRenderer.js +2 -19
- package/dist/components/GraphRenderer.js.map +1 -1
- package/dist/components/GridRenderer.cjs +1 -2
- package/dist/components/GridRenderer.cjs.map +1 -1
- package/dist/components/GridRenderer.js +2 -3
- package/dist/components/ImageGalleryRenderer.js +1 -1
- package/dist/components/LightboxOverlay.js +1 -1
- package/dist/components/MapRenderer.cjs +10 -11
- package/dist/components/MapRenderer.cjs.map +1 -1
- package/dist/components/MapRenderer.d.ts +6 -1
- package/dist/components/MapRenderer.d.ts.map +1 -1
- package/dist/components/MapRenderer.js +7 -8
- package/dist/components/MapRenderer.js.map +1 -1
- package/dist/components/ModalRenderer.js +1 -1
- package/dist/components/PortalDropdownMenu.js +1 -1
- package/dist/components/PresentationFeedback.js +1 -1
- package/dist/components/RenderContext.js +1 -1
- package/dist/components/ResizeHandle.cjs +1 -2
- package/dist/components/ResizeHandle.cjs.map +1 -1
- package/dist/components/ResizeHandle.js +2 -3
- package/dist/components/ScratchpadPanel.js +1 -1
- package/dist/components/SplitStepper.js +1 -1
- package/dist/components/StreamingUIRenderer.js +1 -1
- package/dist/components/UIResourceRenderer.cjs +12 -1
- package/dist/components/UIResourceRenderer.cjs.map +1 -1
- package/dist/components/UIResourceRenderer.d.ts.map +1 -1
- package/dist/components/UIResourceRenderer.js +13 -2
- package/dist/components/UIResourceRenderer.js.map +1 -1
- package/dist/components/VerifiedText.js +1 -1
- package/dist/components/VideoRenderer.js +1 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/core/component.cjs +1 -1
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/core/component.js +1 -1
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/axis/axis.cjs +1 -1
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/axis/axis.js +1 -1
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/axis/constant.cjs +1 -1
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/axis/constant.js +1 -1
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/axis/guides/grid.cjs +2 -2
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/axis/guides/grid.js +2 -2
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/axis/guides/labels.cjs +6 -6
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/axis/guides/labels.js +6 -6
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/axis/guides/line.cjs +1 -1
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/axis/guides/line.js +1 -1
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/axis/guides/ticks.cjs +2 -2
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/axis/guides/ticks.js +2 -2
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/axis/guides/title.cjs +1 -1
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/axis/guides/title.js +1 -1
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/axis/guides/utils.cjs +1 -1
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/axis/guides/utils.js +1 -1
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/grid/index.cjs +3 -3
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/grid/index.js +3 -3
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/indicator/indicator.cjs +4 -4
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/indicator/indicator.js +4 -4
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/legend/category/item.cjs +5 -5
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/legend/category/item.js +5 -5
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/legend/category/items.cjs +5 -5
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/legend/category/items.js +5 -5
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/legend/category.cjs +2 -2
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/legend/category.js +2 -2
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/legend/continuous/handle.cjs +1 -1
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/legend/continuous/handle.js +1 -1
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/legend/title/index.cjs +3 -3
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/legend/title/index.js +3 -3
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/marker/utils.cjs +1 -1
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/marker/utils.js +1 -1
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/navigator/index.cjs +5 -5
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/navigator/index.js +5 -5
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/poptip/index.cjs +2 -2
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/poptip/index.js +2 -2
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/select/option.cjs +2 -2
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/select/option.js +2 -2
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/select/select.cjs +2 -2
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/select/select.js +2 -2
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/slider/handle.cjs +2 -2
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/slider/handle.js +2 -2
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/slider/index.cjs +4 -4
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/slider/index.js +4 -4
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/sparkline/columns.cjs +1 -1
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/sparkline/columns.js +1 -1
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/sparkline/index.cjs +2 -2
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/sparkline/index.js +2 -2
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/sparkline/lines.cjs +1 -1
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/sparkline/lines.js +1 -1
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/sparkline/utils.cjs +2 -2
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/sparkline/utils.js +2 -2
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/timebar/controller.cjs +2 -2
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/timebar/controller.js +2 -2
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/timebar/handle.cjs +1 -1
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/timebar/handle.js +1 -1
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/timebar/icons.cjs +4 -4
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/timebar/icons.js +4 -4
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/timebar/timebar.cjs +3 -3
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/timebar/timebar.js +3 -3
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/tooltip/index.cjs +1 -1
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/ui/tooltip/index.js +1 -1
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/util/selection.cjs +1 -1
- package/dist/node_modules/.pnpm/@antv_component@2.1.11/node_modules/@antv/component/esm/util/selection.js +1 -1
- package/dist/node_modules/.pnpm/@antv_g-lite@2.7.0/node_modules/@antv/g-lite/dist/index.esm.js +1 -1
- package/dist/node_modules/.pnpm/@antv_g6@5.1.0/node_modules/@antv/g6/esm/behaviors/scroll-canvas.cjs +1 -1
- package/dist/node_modules/.pnpm/@antv_g6@5.1.0/node_modules/@antv/g6/esm/behaviors/scroll-canvas.js +1 -1
- package/dist/node_modules/.pnpm/@antv_g6@5.1.0/node_modules/@antv/g6/esm/elements/combos/base-combo.cjs +1 -1
- package/dist/node_modules/.pnpm/@antv_g6@5.1.0/node_modules/@antv/g6/esm/elements/combos/base-combo.js +1 -1
- package/dist/node_modules/.pnpm/@antv_g6@5.1.0/node_modules/@antv/g6/esm/elements/edges/base-edge.cjs +1 -1
- package/dist/node_modules/.pnpm/@antv_g6@5.1.0/node_modules/@antv/g6/esm/elements/edges/base-edge.js +1 -1
- package/dist/node_modules/.pnpm/@antv_g6@5.1.0/node_modules/@antv/g6/esm/index.cjs +58 -58
- package/dist/node_modules/.pnpm/@antv_g6@5.1.0/node_modules/@antv/g6/esm/plugins/legend.cjs +1 -1
- package/dist/node_modules/.pnpm/@antv_g6@5.1.0/node_modules/@antv/g6/esm/plugins/legend.js +1 -1
- package/dist/node_modules/.pnpm/@antv_g6@5.1.0/node_modules/@antv/g6/esm/plugins/minimap/index.cjs +1 -1
- package/dist/node_modules/.pnpm/@antv_g6@5.1.0/node_modules/@antv/g6/esm/plugins/minimap/index.js +1 -1
- package/dist/node_modules/.pnpm/@antv_g6@5.1.0/node_modules/@antv/g6/esm/plugins/snapline/index.cjs +1 -1
- package/dist/node_modules/.pnpm/@antv_g6@5.1.0/node_modules/@antv/g6/esm/plugins/snapline/index.js +1 -1
- package/dist/node_modules/.pnpm/@antv_g6@5.1.0/node_modules/@antv/g6/esm/plugins/tooltip.cjs +1 -1
- package/dist/node_modules/.pnpm/@antv_g6@5.1.0/node_modules/@antv/g6/esm/plugins/tooltip.js +1 -1
- package/dist/node_modules/.pnpm/@antv_g6@5.1.0/node_modules/@antv/g6/esm/registry/build-in.cjs +1 -1
- package/dist/node_modules/.pnpm/@antv_g6@5.1.0/node_modules/@antv/g6/esm/registry/build-in.js +1 -1
- package/dist/node_modules/.pnpm/@antv_g6@5.1.0/node_modules/@antv/g6/esm/runtime/data.cjs +1 -1
- package/dist/node_modules/.pnpm/@antv_g6@5.1.0/node_modules/@antv/g6/esm/runtime/data.js +1 -1
- package/dist/node_modules/.pnpm/@antv_g6@5.1.0/node_modules/@antv/g6/esm/runtime/element.cjs +1 -1
- package/dist/node_modules/.pnpm/@antv_g6@5.1.0/node_modules/@antv/g6/esm/runtime/element.js +1 -1
- package/dist/node_modules/.pnpm/@antv_g6@5.1.0/node_modules/@antv/g6/esm/runtime/graph.cjs +1 -1
- package/dist/node_modules/.pnpm/@antv_g6@5.1.0/node_modules/@antv/g6/esm/runtime/graph.js +1 -1
- package/dist/node_modules/.pnpm/@antv_g6@5.1.0/node_modules/@antv/g6/esm/runtime/layout.cjs +2 -2
- package/dist/node_modules/.pnpm/@antv_g6@5.1.0/node_modules/@antv/g6/esm/runtime/layout.js +2 -2
- package/dist/node_modules/.pnpm/@antv_g6@5.1.0/node_modules/@antv/g6/esm/runtime/viewport.cjs +1 -1
- package/dist/node_modules/.pnpm/@antv_g6@5.1.0/node_modules/@antv/g6/esm/runtime/viewport.js +1 -1
- package/dist/node_modules/.pnpm/@antv_g6@5.1.0/node_modules/@antv/g6/esm/utils/element.cjs +1 -1
- package/dist/node_modules/.pnpm/@antv_g6@5.1.0/node_modules/@antv/g6/esm/utils/element.js +1 -1
- package/dist/node_modules/.pnpm/@antv_g6@5.1.0/node_modules/@antv/g6/esm/utils/point.js +1 -1
- package/dist/node_modules/.pnpm/@antv_layout@2.0.0/node_modules/@antv/layout/lib/algorithm/antv-dagre/rank/index.js +1 -1
- package/dist/node_modules/.pnpm/@antv_layout@2.0.0/node_modules/@antv/layout/lib/algorithm/concentric/index.js +1 -1
- package/dist/node_modules/.pnpm/@antv_scale@0.4.16/node_modules/@antv/scale/esm/scales/continuous.cjs +1 -1
- package/dist/node_modules/.pnpm/@antv_scale@0.4.16/node_modules/@antv/scale/esm/scales/continuous.js +1 -1
- package/dist/node_modules/.pnpm/@tanstack_solid-virtual@3.13.18_solid-js@1.9.11/node_modules/@tanstack/solid-virtual/dist/esm/index.cjs +13 -0
- package/dist/node_modules/.pnpm/@tanstack_solid-virtual@3.13.18_solid-js@1.9.11/node_modules/@tanstack/solid-virtual/dist/esm/index.cjs.map +1 -1
- package/dist/node_modules/.pnpm/@tanstack_solid-virtual@3.13.18_solid-js@1.9.11/node_modules/@tanstack/solid-virtual/dist/esm/index.js +15 -2
- package/dist/node_modules/.pnpm/@tanstack_solid-virtual@3.13.18_solid-js@1.9.11/node_modules/@tanstack/solid-virtual/dist/esm/index.js.map +1 -1
- package/dist/node_modules/.pnpm/@tanstack_virtual-core@3.13.18/node_modules/@tanstack/virtual-core/dist/esm/index.cjs +64 -0
- package/dist/node_modules/.pnpm/@tanstack_virtual-core@3.13.18/node_modules/@tanstack/virtual-core/dist/esm/index.cjs.map +1 -1
- package/dist/node_modules/.pnpm/@tanstack_virtual-core@3.13.18/node_modules/@tanstack/virtual-core/dist/esm/index.js +66 -2
- package/dist/node_modules/.pnpm/@tanstack_virtual-core@3.13.18/node_modules/@tanstack/virtual-core/dist/esm/index.js.map +1 -1
- package/dist/node_modules/.pnpm/@tanstack_virtual-core@3.13.18/node_modules/@tanstack/virtual-core/dist/esm/utils.cjs +1 -1
- package/dist/node_modules/.pnpm/@tanstack_virtual-core@3.13.18/node_modules/@tanstack/virtual-core/dist/esm/utils.cjs.map +1 -1
- package/dist/node_modules/.pnpm/@tanstack_virtual-core@3.13.18/node_modules/@tanstack/virtual-core/dist/esm/utils.js +1 -1
- package/dist/node_modules/.pnpm/@tanstack_virtual-core@3.13.18/node_modules/@tanstack/virtual-core/dist/esm/utils.js.map +1 -1
- package/dist/node_modules/.pnpm/color-string@1.9.1/node_modules/color-string/index.cjs +6 -6
- package/dist/node_modules/.pnpm/{dompurify@3.4.1 → dompurify@3.4.13}/node_modules/dompurify/dist/purify.es.cjs +671 -233
- package/dist/node_modules/.pnpm/dompurify@3.4.13/node_modules/dompurify/dist/purify.es.cjs.map +1 -0
- package/dist/node_modules/.pnpm/{dompurify@3.4.1 → dompurify@3.4.13}/node_modules/dompurify/dist/purify.es.js +671 -233
- package/dist/node_modules/.pnpm/dompurify@3.4.13/node_modules/dompurify/dist/purify.es.js.map +1 -0
- package/dist/node_modules/.pnpm/leaflet.markercluster@1.5.3_leaflet@1.9.4/node_modules/leaflet.markercluster/dist/leaflet.markercluster-src.cjs +7 -7
- package/dist/node_modules/.pnpm/leaflet.markercluster@1.5.3_leaflet@1.9.4/node_modules/leaflet.markercluster/dist/leaflet.markercluster-src.cjs.map +1 -1
- package/dist/node_modules/.pnpm/leaflet.markercluster@1.5.3_leaflet@1.9.4/node_modules/leaflet.markercluster/dist/leaflet.markercluster-src.js +7 -7
- package/dist/node_modules/.pnpm/leaflet.markercluster@1.5.3_leaflet@1.9.4/node_modules/leaflet.markercluster/dist/leaflet.markercluster-src.js.map +1 -1
- package/dist/node_modules/.pnpm/leaflet@1.9.4/node_modules/leaflet/dist/leaflet-src.cjs +83 -83
- package/dist/node_modules/.pnpm/leaflet@1.9.4/node_modules/leaflet/dist/leaflet-src.cjs.map +1 -1
- package/dist/node_modules/.pnpm/leaflet@1.9.4/node_modules/leaflet/dist/leaflet-src.js +83 -83
- package/dist/node_modules/.pnpm/leaflet@1.9.4/node_modules/leaflet/dist/leaflet-src.js.map +1 -1
- package/dist/plugins/groq.cjs +1 -2
- package/dist/plugins/groq.cjs.map +1 -1
- package/dist/plugins/groq.js +1 -2
- package/dist/plugins/rest.cjs +1 -2
- package/dist/plugins/rest.cjs.map +1 -1
- package/dist/plugins/rest.js +1 -2
- package/dist/plugins/supabase.cjs +1 -2
- package/dist/plugins/supabase.cjs.map +1 -1
- package/dist/plugins/supabase.js +1 -2
- package/dist/services/component-registry.cjs +223 -7
- package/dist/services/component-registry.cjs.map +1 -1
- package/dist/services/component-registry.d.ts +0 -3
- package/dist/services/component-registry.d.ts.map +1 -1
- package/dist/services/component-registry.js +223 -7
- package/dist/services/component-registry.js.map +1 -1
- package/dist/services/validation.cjs +1 -23
- package/dist/services/validation.cjs.map +1 -1
- package/dist/services/validation.d.ts.map +1 -1
- package/dist/services/validation.js +2 -24
- package/dist/services/validation.js.map +1 -1
- package/dist/types/index.d.ts +12 -6
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types.d.cts +12 -6
- package/dist/types.d.ts +12 -6
- package/dist/utils/escape-html.cjs +10 -0
- package/dist/utils/escape-html.cjs.map +1 -0
- package/dist/utils/escape-html.d.ts +13 -0
- package/dist/utils/escape-html.d.ts.map +1 -0
- package/dist/utils/escape-html.js +10 -0
- package/dist/utils/escape-html.js.map +1 -0
- package/docs/briefs/BRIEF-graph-component-g6.md +10 -0
- package/package.json +2 -2
- package/src/components/DegradedFallback.tsx +1 -1
- package/src/components/GraphRenderer.tsx +1 -19
- package/src/components/MapRenderer.security.test.ts +55 -2
- package/src/components/MapRenderer.tsx +15 -13
- package/src/components/UIResourceRenderer.tsx +8 -0
- package/src/components/UnsupportedComponent.test.tsx +28 -1
- package/src/components/index.ts +3 -0
- package/src/hooks/useStreamingUI.test.ts +194 -0
- package/src/index.ts +1 -0
- package/src/services/component-registry.test.ts +104 -2
- package/src/services/component-registry.ts +236 -7
- package/src/services/validation.test.ts +26 -0
- package/src/services/validation.ts +12 -24
- package/src/types/index.ts +12 -26
- package/src/types/map-types.test.ts +20 -0
- package/src/utils/escape-html.test.ts +30 -0
- package/src/utils/escape-html.ts +24 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/_virtual/leaflet.markercluster-src2.cjs.map +0 -1
- package/dist/_virtual/leaflet.markercluster-src2.js.map +0 -1
- package/dist/node_modules/.pnpm/dompurify@3.4.1/node_modules/dompurify/dist/purify.es.cjs.map +0 -1
- package/dist/node_modules/.pnpm/dompurify@3.4.1/node_modules/dompurify/dist/purify.es.js.map +0 -1
|
@@ -30,6 +30,7 @@ import { ExpandableWrapper, useExpanded } from './ExpandableWrapper';
|
|
|
30
30
|
import { PortalDropdownMenu } from './PortalDropdownMenu';
|
|
31
31
|
import { DegradedFallback } from './DegradedFallback';
|
|
32
32
|
import { graphToDegradedTable } from '../utils/degraded-projections';
|
|
33
|
+
import { escapeHtml } from '../utils/escape-html';
|
|
33
34
|
import { useTelemetry } from '../context/MCPUITelemetryContext';
|
|
34
35
|
|
|
35
36
|
// Module-scoped lazy import promise — first call triggers the dynamic
|
|
@@ -479,24 +480,5 @@ async function downloadDataUrl(dataUrl: string, filename: string): Promise<void>
|
|
|
479
480
|
downloadBlob(blob, filename);
|
|
480
481
|
}
|
|
481
482
|
|
|
482
|
-
function escapeHtml(s: string): string {
|
|
483
|
-
return s.replace(/[&<>"']/g, (c) => {
|
|
484
|
-
switch (c) {
|
|
485
|
-
case '&':
|
|
486
|
-
return '&';
|
|
487
|
-
case '<':
|
|
488
|
-
return '<';
|
|
489
|
-
case '>':
|
|
490
|
-
return '>';
|
|
491
|
-
case '"':
|
|
492
|
-
return '"';
|
|
493
|
-
case "'":
|
|
494
|
-
return ''';
|
|
495
|
-
default:
|
|
496
|
-
return c;
|
|
497
|
-
}
|
|
498
|
-
});
|
|
499
|
-
}
|
|
500
|
-
|
|
501
483
|
// Re-export for tests + consumers that want to compose their own export menu
|
|
502
484
|
export { toMermaid as graphToMermaid, toJSON as graphToJSON };
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
* and not exercisable in jsdom).
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { describe, it, expect } from 'vitest';
|
|
12
|
-
import { popupSafeText, buildPopupContent } from './MapRenderer';
|
|
11
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
12
|
+
import { addGeoJSONLayer, popupSafeText, buildPopupContent } from './MapRenderer';
|
|
13
13
|
|
|
14
14
|
const XSS = '<img src=x onerror=alert(1)>';
|
|
15
15
|
|
|
@@ -81,3 +81,56 @@ describe('buildPopupContent — GeoJSON popup (P1.2)', () => {
|
|
|
81
81
|
expect(buildPopupContent({}, { titleField: 'a' })).toBeNull();
|
|
82
82
|
});
|
|
83
83
|
});
|
|
84
|
+
|
|
85
|
+
describe('addGeoJSONLayer — trusted-host wiring (UI-MAP-0a)', () => {
|
|
86
|
+
function renderPopup(allowHtml = false) {
|
|
87
|
+
const bindPopup = vi.fn();
|
|
88
|
+
const addTo = vi.fn();
|
|
89
|
+
type GeoJSONOptions = {
|
|
90
|
+
onEachFeature: (
|
|
91
|
+
feature: { properties: Record<string, unknown> },
|
|
92
|
+
featureLayer: { bindPopup: typeof bindPopup }
|
|
93
|
+
) => void;
|
|
94
|
+
};
|
|
95
|
+
const leaflet = {
|
|
96
|
+
geoJSON: vi.fn((_geojson: unknown, options: GeoJSONOptions) => {
|
|
97
|
+
options.onEachFeature(
|
|
98
|
+
{ properties: { name: '<script>evil</script>' } },
|
|
99
|
+
{ bindPopup }
|
|
100
|
+
);
|
|
101
|
+
return { addTo };
|
|
102
|
+
}),
|
|
103
|
+
circleMarker: vi.fn(),
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
addGeoJSONLayer(
|
|
107
|
+
{},
|
|
108
|
+
leaflet,
|
|
109
|
+
{ type: 'FeatureCollection', features: [] },
|
|
110
|
+
undefined,
|
|
111
|
+
{ template: '<b>{{name}}</b>' },
|
|
112
|
+
allowHtml
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
return { bindPopup, addTo };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
it('ignores raw template HTML on the default untrusted path', () => {
|
|
119
|
+
const { bindPopup, addTo } = renderPopup();
|
|
120
|
+
const html = bindPopup.mock.calls[0][0] as string;
|
|
121
|
+
|
|
122
|
+
expect(html).not.toContain('<b>');
|
|
123
|
+
expect(html).not.toContain('<script>');
|
|
124
|
+
expect(html).toContain('<script>');
|
|
125
|
+
expect(addTo).toHaveBeenCalledOnce();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('honors trusted template structure while escaping substituted values', () => {
|
|
129
|
+
const { bindPopup } = renderPopup(true);
|
|
130
|
+
const html = bindPopup.mock.calls[0][0] as string;
|
|
131
|
+
|
|
132
|
+
expect(html).toContain('<b>');
|
|
133
|
+
expect(html).toContain('<script>');
|
|
134
|
+
expect(html).not.toContain('<script>');
|
|
135
|
+
});
|
|
136
|
+
});
|
|
@@ -18,6 +18,7 @@ import type {
|
|
|
18
18
|
import { ExpandableWrapper, useExpanded } from './ExpandableWrapper';
|
|
19
19
|
import { DegradedFallback } from './DegradedFallback';
|
|
20
20
|
import { mapToDegradedTable } from '../utils/degraded-projections';
|
|
21
|
+
import { escapeHtml } from '../utils/escape-html';
|
|
21
22
|
import { useTelemetry } from '../context/MCPUITelemetryContext';
|
|
22
23
|
|
|
23
24
|
// Lazy load leaflet (it doesn't support SSR well)
|
|
@@ -172,14 +173,6 @@ export function buildPopupContent(
|
|
|
172
173
|
return parts.join('<br/>');
|
|
173
174
|
}
|
|
174
175
|
|
|
175
|
-
function escapeHtml(str: string): string {
|
|
176
|
-
return str
|
|
177
|
-
.replace(/&/g, '&')
|
|
178
|
-
.replace(/</g, '<')
|
|
179
|
-
.replace(/>/g, '>')
|
|
180
|
-
.replace(/"/g, '"');
|
|
181
|
-
}
|
|
182
|
-
|
|
183
176
|
/**
|
|
184
177
|
* Resolve a marker's tooltip/popup string for binding (audit P1.2). Leaflet
|
|
185
178
|
* renders bound strings as HTML; `marker.tooltip` / `marker.popup` come from
|
|
@@ -207,12 +200,13 @@ export function bindMarkerContent(
|
|
|
207
200
|
* Add a GeoJSON layer to the map with style and popup support.
|
|
208
201
|
* Returns the layer for bounds calculation.
|
|
209
202
|
*/
|
|
210
|
-
function addGeoJSONLayer(
|
|
203
|
+
export function addGeoJSONLayer(
|
|
211
204
|
mapInst: any,
|
|
212
205
|
leaflet: any,
|
|
213
206
|
geojson: unknown,
|
|
214
207
|
style?: MapGeoJSONStyle,
|
|
215
|
-
popup?: MapPopupConfig
|
|
208
|
+
popup?: MapPopupConfig,
|
|
209
|
+
allowHtml = false
|
|
216
210
|
): any {
|
|
217
211
|
const styleFn = buildStyleFn(style);
|
|
218
212
|
|
|
@@ -231,7 +225,7 @@ function addGeoJSONLayer(
|
|
|
231
225
|
});
|
|
232
226
|
},
|
|
233
227
|
onEachFeature: (feature: any, featureLayer: any) => {
|
|
234
|
-
const html = buildPopupContent(feature, popup);
|
|
228
|
+
const html = buildPopupContent(feature, popup, allowHtml);
|
|
235
229
|
if (html) {
|
|
236
230
|
featureLayer.bindPopup(html, { maxWidth: 300 });
|
|
237
231
|
}
|
|
@@ -420,7 +414,14 @@ export const MapRenderer: Component<MapRendererProps> = (props) => {
|
|
|
420
414
|
|
|
421
415
|
// ─── GeoJSON (v3.1.0) ───────────────────────
|
|
422
416
|
if (p?.geojson) {
|
|
423
|
-
const geoLayer = addGeoJSONLayer(
|
|
417
|
+
const geoLayer = addGeoJSONLayer(
|
|
418
|
+
mapInstance,
|
|
419
|
+
L,
|
|
420
|
+
p.geojson,
|
|
421
|
+
p.geojsonStyle,
|
|
422
|
+
p.popup,
|
|
423
|
+
allowHtml()
|
|
424
|
+
);
|
|
424
425
|
allBoundsLayers.push(geoLayer);
|
|
425
426
|
}
|
|
426
427
|
|
|
@@ -434,7 +435,8 @@ export const MapRenderer: Component<MapRendererProps> = (props) => {
|
|
|
434
435
|
L,
|
|
435
436
|
layerDef.geojson,
|
|
436
437
|
layerDef.style || p?.geojsonStyle,
|
|
437
|
-
layerDef.popup || p?.popup
|
|
438
|
+
layerDef.popup || p?.popup,
|
|
439
|
+
allowHtml()
|
|
438
440
|
);
|
|
439
441
|
|
|
440
442
|
overlays[layerDef.name] = geoLayer;
|
|
@@ -1600,6 +1600,14 @@ function ComponentRenderer(props: {
|
|
|
1600
1600
|
<Show when={props.component.type === 'footer'}>
|
|
1601
1601
|
<FooterRenderer params={props.component.params as any} />
|
|
1602
1602
|
</Show>
|
|
1603
|
+
{/* `composite` is a UILayout discriminator, not a leaf renderer — a
|
|
1604
|
+
top-level composite is handled as a layout (see `layout()` above). If
|
|
1605
|
+
one arrives as a `components[]` entry or a streamed component it has no
|
|
1606
|
+
leaf branch and now passes the (spec-derived) validation gate, so
|
|
1607
|
+
surface the visible notice instead of a silent blank (audit P1.6). */}
|
|
1608
|
+
<Show when={props.component.type === 'composite'}>
|
|
1609
|
+
<UnsupportedComponentFallback component={props.component} />
|
|
1610
|
+
</Show>
|
|
1603
1611
|
</GenerativeUIErrorBoundary>
|
|
1604
1612
|
)
|
|
1605
1613
|
}
|
|
@@ -11,7 +11,7 @@ import { describe, it, expect, afterEach } from 'vitest'
|
|
|
11
11
|
import { render, cleanup, waitFor } from '@solidjs/testing-library'
|
|
12
12
|
import { UIResourceRenderer } from './UIResourceRenderer'
|
|
13
13
|
import { MCPUITelemetryProvider } from '../context/MCPUITelemetryContext'
|
|
14
|
-
import type { UIComponent } from '../types'
|
|
14
|
+
import type { UIComponent, UILayout } from '../types'
|
|
15
15
|
|
|
16
16
|
afterEach(cleanup)
|
|
17
17
|
|
|
@@ -59,6 +59,33 @@ describe('Unsupported component never renders blank (P1.6)', () => {
|
|
|
59
59
|
})
|
|
60
60
|
})
|
|
61
61
|
|
|
62
|
+
it('shows the notice for a composite nested as a components[] entry (not a silent blank)', async () => {
|
|
63
|
+
// A top-level composite is handled as a layout; but a composite that arrives
|
|
64
|
+
// as a leaf inside a layout's components[] has no dispatch branch. Since it
|
|
65
|
+
// now passes the spec-derived validation gate, it must surface the visible
|
|
66
|
+
// notice rather than render nothing (audit P1.6, Codex review follow-up).
|
|
67
|
+
const layoutWithNestedComposite: UILayout = {
|
|
68
|
+
id: 'L',
|
|
69
|
+
grid: { columns: 12, gap: '1rem' },
|
|
70
|
+
components: [
|
|
71
|
+
{
|
|
72
|
+
id: 'nested',
|
|
73
|
+
type: 'composite',
|
|
74
|
+
position: { colStart: 1, colSpan: 12 },
|
|
75
|
+
params: {},
|
|
76
|
+
} as UIComponent,
|
|
77
|
+
],
|
|
78
|
+
}
|
|
79
|
+
const { container } = render(() => (
|
|
80
|
+
<UIResourceRenderer content={layoutWithNestedComposite} errorMode="silent" />
|
|
81
|
+
))
|
|
82
|
+
|
|
83
|
+
await waitFor(() => {
|
|
84
|
+
expect(container.textContent ?? '').toContain('Unsupported component type')
|
|
85
|
+
})
|
|
86
|
+
expect(container.textContent).toContain('composite')
|
|
87
|
+
})
|
|
88
|
+
|
|
62
89
|
it('renders a standalone footer component (previously a silent blank)', async () => {
|
|
63
90
|
const footer: UIComponent = {
|
|
64
91
|
id: 'f1',
|
package/src/components/index.ts
CHANGED
|
@@ -27,6 +27,9 @@ export type { GenerativeUIErrorBoundaryProps } from './GenerativeUIErrorBoundary
|
|
|
27
27
|
export { FooterRenderer } from './FooterRenderer'
|
|
28
28
|
export type { FooterComponentParams } from './FooterRenderer'
|
|
29
29
|
|
|
30
|
+
// NOTE: kept as a published `./components` subpath export for backward compat.
|
|
31
|
+
// This is a legacy stub superseded by the inline ActionRenderer in
|
|
32
|
+
// UIResourceRenderer; slated for removal in the next MAJOR (not a minor).
|
|
30
33
|
export { ActionRenderer } from './ActionRenderer'
|
|
31
34
|
export type { ActionRendererProps } from './ActionRenderer'
|
|
32
35
|
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useStreamingUI — behavioral coverage for the SSE streaming engine.
|
|
3
|
+
*
|
|
4
|
+
* The hook does NOT use `EventSource` (the field of that name is vestigial);
|
|
5
|
+
* it POSTs to /api/mcp/generative-ui-stream and reads an SSE-formatted body via
|
|
6
|
+
* `response.body.getReader()`. These tests stub `fetch` + the reader to drive
|
|
7
|
+
* scripted `status` / `component` / `complete` / `error` events and assert the
|
|
8
|
+
* four behaviors that had zero coverage: progressive accumulation, out-of-order
|
|
9
|
+
* buffering/reorder, terminal (non-recoverable) error, and recoverable-error
|
|
10
|
+
* reconnect with backoff.
|
|
11
|
+
*
|
|
12
|
+
* The hook auto-starts streaming on creation, so `fetch` is stubbed before the
|
|
13
|
+
* hook is instantiated inside a reactive root.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { describe, it, expect, vi, afterEach } from 'vitest'
|
|
17
|
+
import { createRoot } from 'solid-js'
|
|
18
|
+
import { useStreamingUI, type UseStreamingUIOptions } from './useStreamingUI'
|
|
19
|
+
|
|
20
|
+
const T = '2026-07-03T00:00:00.000Z'
|
|
21
|
+
|
|
22
|
+
/** One SSE message: an `event:` line + a `data:` line + a blank terminator. */
|
|
23
|
+
function sse(event: string, data: unknown): string {
|
|
24
|
+
return `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** A `component` event payload with the given sequence id. */
|
|
28
|
+
function comp(seq: number, type = 'metric') {
|
|
29
|
+
return {
|
|
30
|
+
componentId: `c${seq}`,
|
|
31
|
+
sequenceId: seq,
|
|
32
|
+
component: { id: `c${seq}`, type, position: { colStart: 1, colSpan: 12 }, params: {} },
|
|
33
|
+
position: { colStart: 1, colSpan: 12 },
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** A `complete` event payload (CompleteMetadata). */
|
|
38
|
+
function completeMeta() {
|
|
39
|
+
return {
|
|
40
|
+
layoutId: 'L1',
|
|
41
|
+
componentsCount: 2,
|
|
42
|
+
executionTimeMs: 1234,
|
|
43
|
+
firstTokenMs: 100,
|
|
44
|
+
provider: 'mock',
|
|
45
|
+
model: 'test',
|
|
46
|
+
cached: false,
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* A `fetch` stub. Each entry in `callSpecs` scripts one call (subsequent calls
|
|
52
|
+
* reuse the last entry) — either an HTTP failure (`ok: false`) or a readable
|
|
53
|
+
* body that yields the given SSE chunks then closes.
|
|
54
|
+
*/
|
|
55
|
+
function makeFetchStub(callSpecs: Array<{ ok?: boolean; errorBody?: unknown; chunks?: string[] }>) {
|
|
56
|
+
let call = 0
|
|
57
|
+
return vi.fn(async () => {
|
|
58
|
+
const spec = callSpecs[Math.min(call, callSpecs.length - 1)]
|
|
59
|
+
call++
|
|
60
|
+
if (spec.ok === false) {
|
|
61
|
+
return { ok: false, json: async () => spec.errorBody ?? { message: 'http error' } }
|
|
62
|
+
}
|
|
63
|
+
const encoder = new TextEncoder()
|
|
64
|
+
const chunks = spec.chunks ?? []
|
|
65
|
+
let i = 0
|
|
66
|
+
const reader = {
|
|
67
|
+
read: async () =>
|
|
68
|
+
i < chunks.length
|
|
69
|
+
? { done: false, value: encoder.encode(chunks[i++]) }
|
|
70
|
+
: { done: true, value: undefined },
|
|
71
|
+
}
|
|
72
|
+
return { ok: true, body: { getReader: () => reader } }
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const disposers: Array<() => void> = []
|
|
77
|
+
|
|
78
|
+
/** Instantiate the hook inside a reactive root; returns its public API. */
|
|
79
|
+
function renderHook(options: UseStreamingUIOptions) {
|
|
80
|
+
let api!: ReturnType<typeof useStreamingUI>
|
|
81
|
+
createRoot((dispose) => {
|
|
82
|
+
disposers.push(dispose)
|
|
83
|
+
api = useStreamingUI(options)
|
|
84
|
+
})
|
|
85
|
+
return api
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
describe('useStreamingUI', () => {
|
|
89
|
+
afterEach(() => {
|
|
90
|
+
while (disposers.length) disposers.pop()!()
|
|
91
|
+
vi.unstubAllGlobals()
|
|
92
|
+
vi.clearAllMocks()
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('accumulates components in sequence order and finalizes on complete', async () => {
|
|
96
|
+
const onComplete = vi.fn()
|
|
97
|
+
const onComponentReceived = vi.fn()
|
|
98
|
+
vi.stubGlobal(
|
|
99
|
+
'fetch',
|
|
100
|
+
makeFetchStub([
|
|
101
|
+
{
|
|
102
|
+
chunks: [
|
|
103
|
+
sse('status', { message: 'Working', timestamp: T, totalComponents: 2 }),
|
|
104
|
+
sse('component', comp(0, 'metric')),
|
|
105
|
+
sse('component', comp(1, 'text')),
|
|
106
|
+
sse('complete', completeMeta()),
|
|
107
|
+
],
|
|
108
|
+
},
|
|
109
|
+
])
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
const api = renderHook({ query: 'revenue', onComplete, onComponentReceived })
|
|
113
|
+
|
|
114
|
+
await vi.waitFor(() => expect(api.metadata()).not.toBeNull())
|
|
115
|
+
|
|
116
|
+
expect(api.components().map((c) => c.id)).toEqual(['c0', 'c1'])
|
|
117
|
+
expect(api.progress().totalCount).toBe(2)
|
|
118
|
+
expect(api.progress().receivedCount).toBe(2)
|
|
119
|
+
expect(api.metadata()?.executionTime).toBe(1234) // executionTimeMs aliased
|
|
120
|
+
expect(api.isStreaming()).toBe(false)
|
|
121
|
+
expect(api.isLoading()).toBe(false)
|
|
122
|
+
expect(onComponentReceived).toHaveBeenCalledTimes(2)
|
|
123
|
+
expect(onComplete).toHaveBeenCalledTimes(1)
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('buffers an out-of-order component and flushes in sequence order', async () => {
|
|
127
|
+
vi.stubGlobal(
|
|
128
|
+
'fetch',
|
|
129
|
+
makeFetchStub([
|
|
130
|
+
{
|
|
131
|
+
chunks: [
|
|
132
|
+
sse('status', { message: 's', timestamp: T }),
|
|
133
|
+
sse('component', comp(1, 'text')), // arrives before its predecessor
|
|
134
|
+
sse('component', comp(0, 'metric')), // unblocks the buffer
|
|
135
|
+
sse('complete', completeMeta()),
|
|
136
|
+
],
|
|
137
|
+
},
|
|
138
|
+
])
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
const api = renderHook({ query: 'q' })
|
|
142
|
+
|
|
143
|
+
await vi.waitFor(() => expect(api.components().length).toBe(2))
|
|
144
|
+
// Despite arriving 1-then-0, they surface in sequence order.
|
|
145
|
+
expect(api.components().map((c) => c.id)).toEqual(['c0', 'c1'])
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it('surfaces a non-recoverable error and does not reconnect', async () => {
|
|
149
|
+
const onError = vi.fn()
|
|
150
|
+
const fetchStub = makeFetchStub([
|
|
151
|
+
{ chunks: [sse('error', { error: 'fatal', message: 'boom', recoverable: false })] },
|
|
152
|
+
])
|
|
153
|
+
vi.stubGlobal('fetch', fetchStub)
|
|
154
|
+
|
|
155
|
+
const api = renderHook({ query: 'q', onError })
|
|
156
|
+
|
|
157
|
+
await vi.waitFor(() => expect(api.error()).not.toBeNull())
|
|
158
|
+
expect(api.error()?.message).toBe('boom')
|
|
159
|
+
expect(api.isStreaming()).toBe(false)
|
|
160
|
+
expect(api.isLoading()).toBe(false)
|
|
161
|
+
expect(onError).toHaveBeenCalledTimes(1)
|
|
162
|
+
|
|
163
|
+
// A non-recoverable error schedules no retry — fetch stays at one call.
|
|
164
|
+
await new Promise((r) => setTimeout(r, 30))
|
|
165
|
+
expect(fetchStub).toHaveBeenCalledTimes(1)
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
it('reconnects with backoff after a recoverable error, then recovers', async () => {
|
|
169
|
+
const onError = vi.fn()
|
|
170
|
+
const fetchStub = makeFetchStub([
|
|
171
|
+
// 1st connection: recoverable error → schedules a reconnect (backoff 1000ms)
|
|
172
|
+
{ chunks: [sse('error', { error: 'net', message: 'transient', recoverable: true })] },
|
|
173
|
+
// 2nd connection: succeeds
|
|
174
|
+
{
|
|
175
|
+
chunks: [
|
|
176
|
+
sse('status', { message: 'retry', timestamp: T }),
|
|
177
|
+
sse('component', comp(0, 'metric')),
|
|
178
|
+
sse('complete', completeMeta()),
|
|
179
|
+
],
|
|
180
|
+
},
|
|
181
|
+
])
|
|
182
|
+
vi.stubGlobal('fetch', fetchStub)
|
|
183
|
+
|
|
184
|
+
const api = renderHook({ query: 'q', onError })
|
|
185
|
+
|
|
186
|
+
await vi.waitFor(() => expect(onError).toHaveBeenCalledTimes(1))
|
|
187
|
+
// The reconnect fires a second fetch and the stream recovers.
|
|
188
|
+
await vi.waitFor(() => expect(fetchStub).toHaveBeenCalledTimes(2), { timeout: 2500 })
|
|
189
|
+
await vi.waitFor(() => expect(api.metadata()).not.toBeNull(), { timeout: 2500 })
|
|
190
|
+
|
|
191
|
+
expect(api.error()).toBeNull() // reconnect resets error state
|
|
192
|
+
expect(api.components().map((c) => c.id)).toEqual(['c0'])
|
|
193
|
+
})
|
|
194
|
+
})
|
package/src/index.ts
CHANGED
|
@@ -3,9 +3,28 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { describe, it, expect } from 'vitest'
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
validateAgainstRegistry,
|
|
8
|
+
getComponentEntry,
|
|
9
|
+
ComponentRegistry,
|
|
10
|
+
MapRegistry,
|
|
11
|
+
} from './component-registry'
|
|
7
12
|
import type { ComponentType } from '../types'
|
|
8
|
-
import { ComponentTypeSchema } from '@seed-ship/mcp-ui-spec'
|
|
13
|
+
import { ComponentTypeSchema, MapComponentParamsSchema } from '@seed-ship/mcp-ui-spec'
|
|
14
|
+
|
|
15
|
+
interface RegistrySchemaNode {
|
|
16
|
+
type?: string
|
|
17
|
+
enum?: string[]
|
|
18
|
+
oneOf?: RegistrySchemaNode[]
|
|
19
|
+
anyOf?: RegistrySchemaNode[]
|
|
20
|
+
properties?: Record<string, RegistrySchemaNode>
|
|
21
|
+
required?: string[]
|
|
22
|
+
items?: RegistrySchemaNode | RegistrySchemaNode[]
|
|
23
|
+
additionalItems?: boolean
|
|
24
|
+
minItems?: number
|
|
25
|
+
maxItems?: number
|
|
26
|
+
minLength?: number
|
|
27
|
+
}
|
|
9
28
|
|
|
10
29
|
/** All 20 component types in the registry */
|
|
11
30
|
const ALL_TYPES: ComponentType[] = [
|
|
@@ -41,6 +60,89 @@ describe('registry ↔ schema parity (P1.5)', () => {
|
|
|
41
60
|
})
|
|
42
61
|
})
|
|
43
62
|
|
|
63
|
+
describe('MapRegistry ↔ map spec parity (UI-MAP-0a)', () => {
|
|
64
|
+
const properties = MapRegistry.schema.properties as Record<string, RegistrySchemaNode>
|
|
65
|
+
const center = properties.center as { oneOf: unknown[] }
|
|
66
|
+
const markers = properties.markers as {
|
|
67
|
+
items: {
|
|
68
|
+
properties: Record<string, unknown> & { position: { oneOf: unknown[] } }
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const markerProperties = markers.items.properties
|
|
72
|
+
|
|
73
|
+
it('advertises every root field accepted by MapComponentParamsSchema', () => {
|
|
74
|
+
expect(Object.keys(properties).sort()).toEqual(Object.keys(MapComponentParamsSchema.shape).sort())
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('uses the canonical marker names and keeps host trust out of the payload', () => {
|
|
78
|
+
expect(markerProperties).toHaveProperty('tooltip')
|
|
79
|
+
expect(markerProperties).not.toHaveProperty('title')
|
|
80
|
+
expect(properties).not.toHaveProperty('allowHtmlPopups')
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('advertises both tuple and object LatLng forms', () => {
|
|
84
|
+
expect(center.oneOf).toHaveLength(2)
|
|
85
|
+
expect(markerProperties.position.oneOf).toHaveLength(2)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('advertises only the GeoJSON root shapes accepted by the runtime spec', () => {
|
|
89
|
+
const branches = properties.geojson.oneOf ?? []
|
|
90
|
+
const branchesByType = Object.fromEntries(
|
|
91
|
+
branches.map((branch) => {
|
|
92
|
+
const types = branch.properties?.type.enum ?? []
|
|
93
|
+
return [types.length === 1 ? types[0] : 'Geometry', branch]
|
|
94
|
+
})
|
|
95
|
+
) as Record<string, RegistrySchemaNode>
|
|
96
|
+
|
|
97
|
+
expect(Object.keys(branchesByType).sort()).toEqual(['Feature', 'FeatureCollection', 'Geometry'])
|
|
98
|
+
expect(branchesByType.FeatureCollection.required).toEqual(['type', 'features'])
|
|
99
|
+
expect(branchesByType.Feature.required).toEqual(['type', 'geometry'])
|
|
100
|
+
expect(branchesByType.Geometry.required).toEqual(['type'])
|
|
101
|
+
expect(branchesByType.Geometry.properties?.type.enum).toEqual([
|
|
102
|
+
'Point',
|
|
103
|
+
'MultiPoint',
|
|
104
|
+
'LineString',
|
|
105
|
+
'MultiLineString',
|
|
106
|
+
'Polygon',
|
|
107
|
+
'MultiPolygon',
|
|
108
|
+
'GeometryCollection',
|
|
109
|
+
])
|
|
110
|
+
expect(branchesByType.Geometry.properties?.coordinates.anyOf).toHaveLength(4)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('preserves choropleth tuple order and rejects empty layer names in the advertised schema', () => {
|
|
114
|
+
const scale = properties.geojsonStyle.properties?.choroplethScale
|
|
115
|
+
const tuple = scale?.items as RegistrySchemaNode
|
|
116
|
+
const tupleItems = tuple.items as RegistrySchemaNode[]
|
|
117
|
+
const layer = properties.layers.items as RegistrySchemaNode
|
|
118
|
+
|
|
119
|
+
expect(tupleItems).toEqual([{ type: 'number' }, { type: 'string' }])
|
|
120
|
+
expect(tuple.additionalItems).toBe(false)
|
|
121
|
+
expect(tuple.minItems).toBe(2)
|
|
122
|
+
expect(tuple.maxItems).toBe(2)
|
|
123
|
+
expect(tuple).not.toHaveProperty('prefixItems')
|
|
124
|
+
expect(layer.properties?.name.minLength).toBe(1)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it.each([
|
|
128
|
+
['empty GeoJSON objects', { geojson: {} }],
|
|
129
|
+
['reversed choropleth tuples', { geojsonStyle: { choroplethScale: [['red', 0]] } }],
|
|
130
|
+
[
|
|
131
|
+
'empty layer names',
|
|
132
|
+
{
|
|
133
|
+
layers: [
|
|
134
|
+
{
|
|
135
|
+
name: '',
|
|
136
|
+
geojson: { type: 'FeatureCollection', features: [] },
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
])('keeps runtime rejection coverage for %s', (_label, params) => {
|
|
142
|
+
expect(MapComponentParamsSchema.safeParse(params).success).toBe(false)
|
|
143
|
+
})
|
|
144
|
+
})
|
|
145
|
+
|
|
44
146
|
describe('ComponentRegistry', () => {
|
|
45
147
|
describe('registry completeness', () => {
|
|
46
148
|
it('has exactly 20 registered types', () => {
|