@miragon/mcp-toolkit-ui 2.6.0 → 2.8.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/README.md +14 -5
- package/dist/app/adapt-data-widget.d.ts +1 -22
- package/dist/app/adapt-data-widget.d.ts.map +1 -1
- package/dist/app/adapt-data-widget.js +1 -35
- package/dist/app/adapt-data-widget.js.map +1 -1
- package/dist/app/builder/BuilderBanners.d.ts +7 -2
- package/dist/app/builder/BuilderBanners.d.ts.map +1 -1
- package/dist/app/builder/BuilderBanners.js +8 -3
- package/dist/app/builder/BuilderBanners.js.map +1 -1
- package/dist/app/builder/CanvasRow.d.ts +4 -2
- package/dist/app/builder/CanvasRow.d.ts.map +1 -1
- package/dist/app/builder/CanvasRow.js +29 -17
- package/dist/app/builder/CanvasRow.js.map +1 -1
- package/dist/app/builder/CatalogueSheet.d.ts.map +1 -1
- package/dist/app/builder/CatalogueSheet.js +14 -6
- package/dist/app/builder/CatalogueSheet.js.map +1 -1
- package/dist/app/builder/CellPropsSheet.d.ts +7 -3
- package/dist/app/builder/CellPropsSheet.d.ts.map +1 -1
- package/dist/app/builder/CellPropsSheet.js +10 -3
- package/dist/app/builder/CellPropsSheet.js.map +1 -1
- package/dist/app/builder/DiscardDialog.d.ts +18 -0
- package/dist/app/builder/DiscardDialog.d.ts.map +1 -0
- package/dist/app/builder/DiscardDialog.js +18 -0
- package/dist/app/builder/DiscardDialog.js.map +1 -0
- package/dist/app/builder/EmptyHint.d.ts +4 -0
- package/dist/app/builder/EmptyHint.d.ts.map +1 -1
- package/dist/app/builder/EmptyHint.js +6 -1
- package/dist/app/builder/EmptyHint.js.map +1 -1
- package/dist/app/builder/LayoutSurface.d.ts +33 -9
- package/dist/app/builder/LayoutSurface.d.ts.map +1 -1
- package/dist/app/builder/LayoutSurface.js +74 -23
- package/dist/app/builder/LayoutSurface.js.map +1 -1
- package/dist/app/builder/PipelineStrip.d.ts +5 -2
- package/dist/app/builder/PipelineStrip.d.ts.map +1 -1
- package/dist/app/builder/PipelineStrip.js +33 -15
- package/dist/app/builder/PipelineStrip.js.map +1 -1
- package/dist/app/builder/SaveDialog.d.ts +9 -1
- package/dist/app/builder/SaveDialog.d.ts.map +1 -1
- package/dist/app/builder/SaveDialog.js +9 -3
- package/dist/app/builder/SaveDialog.js.map +1 -1
- package/dist/app/builder/Toolbar.d.ts +14 -4
- package/dist/app/builder/Toolbar.d.ts.map +1 -1
- package/dist/app/builder/Toolbar.js +24 -7
- package/dist/app/builder/Toolbar.js.map +1 -1
- package/dist/app/builder/Workspace.d.ts +23 -3
- package/dist/app/builder/Workspace.d.ts.map +1 -1
- package/dist/app/builder/Workspace.js +58 -8
- package/dist/app/builder/Workspace.js.map +1 -1
- package/dist/app/builder/builder-model.d.ts +146 -3
- package/dist/app/builder/builder-model.d.ts.map +1 -1
- package/dist/app/builder/builder-model.js +196 -5
- package/dist/app/builder/builder-model.js.map +1 -1
- package/dist/app/builder/labels.d.ts +13 -62
- package/dist/app/builder/labels.d.ts.map +1 -1
- package/dist/app/builder/labels.js +9 -58
- package/dist/app/builder/labels.js.map +1 -1
- package/dist/app/builder/reveal.d.ts +10 -0
- package/dist/app/builder/reveal.d.ts.map +1 -0
- package/dist/app/builder/reveal.js +10 -0
- package/dist/app/builder/reveal.js.map +1 -0
- package/dist/app/builder/status-chip.d.ts.map +1 -1
- package/dist/app/builder/status-chip.js +3 -2
- package/dist/app/builder/status-chip.js.map +1 -1
- package/dist/app/builder/use-builder-catalogue.d.ts +5 -1
- package/dist/app/builder/use-builder-catalogue.d.ts.map +1 -1
- package/dist/app/builder/use-builder-catalogue.js +50 -17
- package/dist/app/builder/use-builder-catalogue.js.map +1 -1
- package/dist/app/builder/use-builder-session.d.ts +30 -0
- package/dist/app/builder/use-builder-session.d.ts.map +1 -0
- package/dist/app/builder/use-builder-session.js +58 -0
- package/dist/app/builder/use-builder-session.js.map +1 -0
- package/dist/app/builder/use-display-resolver.d.ts +17 -0
- package/dist/app/builder/use-display-resolver.d.ts.map +1 -0
- package/dist/app/builder/use-display-resolver.js +16 -0
- package/dist/app/builder/use-display-resolver.js.map +1 -0
- package/dist/app/builder/use-save-dashboard.d.ts +3 -1
- package/dist/app/builder/use-save-dashboard.d.ts.map +1 -1
- package/dist/app/builder/use-save-dashboard.js +3 -1
- package/dist/app/builder/use-save-dashboard.js.map +1 -1
- package/dist/app/index.d.ts +8 -3
- package/dist/app/index.d.ts.map +1 -1
- package/dist/app/index.js +8 -2
- package/dist/app/index.js.map +1 -1
- package/dist/app/layout-builder.d.ts +18 -25
- package/dist/app/layout-builder.d.ts.map +1 -1
- package/dist/app/layout-builder.js +84 -78
- package/dist/app/layout-builder.js.map +1 -1
- package/dist/app/mcp-app-view-chrome.d.ts +103 -20
- package/dist/app/mcp-app-view-chrome.d.ts.map +1 -1
- package/dist/app/mcp-app-view-chrome.js +125 -40
- package/dist/app/mcp-app-view-chrome.js.map +1 -1
- package/dist/app/mcp-app-view-states.d.ts +23 -0
- package/dist/app/mcp-app-view-states.d.ts.map +1 -0
- package/dist/app/mcp-app-view-states.js +55 -0
- package/dist/app/mcp-app-view-states.js.map +1 -0
- package/dist/app/mcp-app-view.d.ts +72 -5
- package/dist/app/mcp-app-view.d.ts.map +1 -1
- package/dist/app/mcp-app-view.js +241 -141
- package/dist/app/mcp-app-view.js.map +1 -1
- package/dist/app/mcp-toolkit-app.d.ts +8 -2
- package/dist/app/mcp-toolkit-app.d.ts.map +1 -1
- package/dist/app/mcp-toolkit-app.js +33 -4
- package/dist/app/mcp-toolkit-app.js.map +1 -1
- package/dist/app/mcp-use-host-bridge.d.ts +15 -6
- package/dist/app/mcp-use-host-bridge.d.ts.map +1 -1
- package/dist/app/mcp-use-host-bridge.js +51 -22
- package/dist/app/mcp-use-host-bridge.js.map +1 -1
- package/dist/app/model-context.d.ts +71 -0
- package/dist/app/model-context.d.ts.map +1 -0
- package/dist/app/model-context.js +101 -0
- package/dist/app/model-context.js.map +1 -0
- package/dist/app/use-build-mode-focus.d.ts +33 -0
- package/dist/app/use-build-mode-focus.d.ts.map +1 -0
- package/dist/app/use-build-mode-focus.js +59 -0
- package/dist/app/use-build-mode-focus.js.map +1 -0
- package/dist/app/use-view-actions.d.ts +31 -0
- package/dist/app/use-view-actions.d.ts.map +1 -0
- package/dist/app/use-view-actions.js +81 -0
- package/dist/app/use-view-actions.js.map +1 -0
- package/dist/app/use-view-lifecycle.d.ts +42 -0
- package/dist/app/use-view-lifecycle.d.ts.map +1 -0
- package/dist/app/use-view-lifecycle.js +81 -0
- package/dist/app/use-view-lifecycle.js.map +1 -0
- package/dist/app/view-lifecycle.d.ts +185 -0
- package/dist/app/view-lifecycle.d.ts.map +1 -0
- package/dist/app/view-lifecycle.js +207 -0
- package/dist/app/view-lifecycle.js.map +1 -0
- package/dist/app/widget-fixture.d.ts +10 -3
- package/dist/app/widget-fixture.d.ts.map +1 -1
- package/dist/app/widget-fixture.js +21 -8
- package/dist/app/widget-fixture.js.map +1 -1
- package/dist/app/widget-props-sheet.d.ts +6 -10
- package/dist/app/widget-props-sheet.d.ts.map +1 -1
- package/dist/app/widget-props-sheet.js +25 -20
- package/dist/app/widget-props-sheet.js.map +1 -1
- package/dist/app/widget-renderer.d.ts +41 -1
- package/dist/app/widget-renderer.d.ts.map +1 -1
- package/dist/app/widget-renderer.js +82 -31
- package/dist/app/widget-renderer.js.map +1 -1
- package/dist/components/DrillButton.d.ts +9 -6
- package/dist/components/DrillButton.d.ts.map +1 -1
- package/dist/components/DrillButton.js +9 -6
- package/dist/components/DrillButton.js.map +1 -1
- package/dist/components/EmptyState.d.ts +33 -0
- package/dist/components/EmptyState.d.ts.map +1 -0
- package/dist/components/EmptyState.js +19 -0
- package/dist/components/EmptyState.js.map +1 -0
- package/dist/components/ErrorState.d.ts +53 -0
- package/dist/components/ErrorState.d.ts.map +1 -0
- package/dist/components/ErrorState.js +77 -0
- package/dist/components/ErrorState.js.map +1 -0
- package/dist/components/FilterBar.d.ts +16 -5
- package/dist/components/FilterBar.d.ts.map +1 -1
- package/dist/components/FilterBar.js +13 -5
- package/dist/components/FilterBar.js.map +1 -1
- package/dist/components/GroupCard.d.ts.map +1 -1
- package/dist/components/GroupCard.js +2 -1
- package/dist/components/GroupCard.js.map +1 -1
- package/dist/components/KpiGrid.d.ts +70 -18
- package/dist/components/KpiGrid.d.ts.map +1 -1
- package/dist/components/KpiGrid.js +112 -23
- package/dist/components/KpiGrid.js.map +1 -1
- package/dist/components/ListFooter.d.ts +32 -4
- package/dist/components/ListFooter.d.ts.map +1 -1
- package/dist/components/ListFooter.js +35 -6
- package/dist/components/ListFooter.js.map +1 -1
- package/dist/components/PagedListFooter.d.ts +57 -0
- package/dist/components/PagedListFooter.d.ts.map +1 -0
- package/dist/components/PagedListFooter.js +27 -0
- package/dist/components/PagedListFooter.js.map +1 -0
- package/dist/components/Pills.d.ts +36 -12
- package/dist/components/Pills.d.ts.map +1 -1
- package/dist/components/Pills.js +29 -8
- package/dist/components/Pills.js.map +1 -1
- package/dist/components/SectionHeading.d.ts +3 -1
- package/dist/components/SectionHeading.d.ts.map +1 -1
- package/dist/components/SectionHeading.js +6 -2
- package/dist/components/SectionHeading.js.map +1 -1
- package/dist/components/Toast.d.ts +43 -0
- package/dist/components/Toast.d.ts.map +1 -0
- package/dist/components/Toast.js +106 -0
- package/dist/components/Toast.js.map +1 -0
- package/dist/components/WidgetHeader.d.ts +34 -10
- package/dist/components/WidgetHeader.d.ts.map +1 -1
- package/dist/components/WidgetHeader.js +11 -6
- package/dist/components/WidgetHeader.js.map +1 -1
- package/dist/hooks/index.d.ts +4 -4
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -1
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/tool-result-recovery-model.d.ts +155 -0
- package/dist/hooks/tool-result-recovery-model.d.ts.map +1 -0
- package/dist/hooks/tool-result-recovery-model.js +273 -0
- package/dist/hooks/tool-result-recovery-model.js.map +1 -0
- package/dist/hooks/use-tool-query.d.ts +56 -14
- package/dist/hooks/use-tool-query.d.ts.map +1 -1
- package/dist/hooks/use-tool-query.js +102 -25
- package/dist/hooks/use-tool-query.js.map +1 -1
- package/dist/hooks/use-tool-result-recovery.d.ts +61 -44
- package/dist/hooks/use-tool-result-recovery.d.ts.map +1 -1
- package/dist/hooks/use-tool-result-recovery.js +62 -110
- package/dist/hooks/use-tool-result-recovery.js.map +1 -1
- package/dist/hooks/use-view-data.d.ts +55 -7
- package/dist/hooks/use-view-data.d.ts.map +1 -1
- package/dist/hooks/use-view-data.js +81 -11
- package/dist/hooks/use-view-data.js.map +1 -1
- package/dist/hooks/use-view-tool-query.d.ts +12 -7
- package/dist/hooks/use-view-tool-query.d.ts.map +1 -1
- package/dist/hooks/use-view-tool-query.js +15 -14
- package/dist/hooks/use-view-tool-query.js.map +1 -1
- package/dist/index.d.ts +27 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +29 -7
- package/dist/index.js.map +1 -1
- package/dist/lib/focus-return.d.ts +61 -0
- package/dist/lib/focus-return.d.ts.map +1 -0
- package/dist/lib/focus-return.js +46 -0
- package/dist/lib/focus-return.js.map +1 -0
- package/dist/lib/focus-ring.d.ts +18 -0
- package/dist/lib/focus-ring.d.ts.map +1 -0
- package/dist/lib/focus-ring.js +18 -0
- package/dist/lib/focus-ring.js.map +1 -0
- package/dist/lib/icon-stroke.d.ts +12 -0
- package/dist/lib/icon-stroke.d.ts.map +1 -0
- package/dist/lib/icon-stroke.js +12 -0
- package/dist/lib/icon-stroke.js.map +1 -0
- package/dist/lib/parse-tool-result.d.ts +23 -1
- package/dist/lib/parse-tool-result.d.ts.map +1 -1
- package/dist/lib/parse-tool-result.js +38 -2
- package/dist/lib/parse-tool-result.js.map +1 -1
- package/dist/lib/toast-model.d.ts +86 -0
- package/dist/lib/toast-model.d.ts.map +1 -0
- package/dist/lib/toast-model.js +102 -0
- package/dist/lib/toast-model.js.map +1 -0
- package/dist/lib/tone-utils.d.ts +95 -9
- package/dist/lib/tone-utils.d.ts.map +1 -1
- package/dist/lib/tone-utils.js +100 -9
- package/dist/lib/tone-utils.js.map +1 -1
- package/dist/lib/tool-query.d.ts +107 -0
- package/dist/lib/tool-query.d.ts.map +1 -0
- package/dist/lib/tool-query.js +182 -0
- package/dist/lib/tool-query.js.map +1 -0
- package/dist/lib/tool-result-observer.d.ts +108 -0
- package/dist/lib/tool-result-observer.d.ts.map +1 -0
- package/dist/lib/tool-result-observer.js +158 -0
- package/dist/lib/tool-result-observer.js.map +1 -0
- package/dist/lib/typography.d.ts +37 -0
- package/dist/lib/typography.d.ts.map +1 -0
- package/dist/lib/typography.js +41 -0
- package/dist/lib/typography.js.map +1 -0
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +11 -1
- package/dist/lib/utils.js.map +1 -1
- package/dist/locale/index.d.ts +3 -1
- package/dist/locale/index.d.ts.map +1 -1
- package/dist/locale/index.js +2 -1
- package/dist/locale/index.js.map +1 -1
- package/dist/locale/locale-provider.d.ts +42 -1
- package/dist/locale/locale-provider.d.ts.map +1 -1
- package/dist/locale/locale-provider.js +44 -3
- package/dist/locale/locale-provider.js.map +1 -1
- package/dist/locale/message-types.d.ts +336 -0
- package/dist/locale/message-types.d.ts.map +1 -0
- package/dist/locale/message-types.js +7 -0
- package/dist/locale/message-types.js.map +1 -0
- package/dist/locale/messages-de.d.ts +9 -0
- package/dist/locale/messages-de.d.ts.map +1 -0
- package/dist/locale/messages-de.js +185 -0
- package/dist/locale/messages-de.js.map +1 -0
- package/dist/locale/messages-en.d.ts +8 -0
- package/dist/locale/messages-en.d.ts.map +1 -0
- package/dist/locale/messages-en.js +184 -0
- package/dist/locale/messages-en.js.map +1 -0
- package/dist/locale/toolkit-messages.d.ts +57 -0
- package/dist/locale/toolkit-messages.d.ts.map +1 -0
- package/dist/locale/toolkit-messages.js +103 -0
- package/dist/locale/toolkit-messages.js.map +1 -0
- package/dist/primitives/alert.d.ts +1 -1
- package/dist/primitives/alert.d.ts.map +1 -1
- package/dist/primitives/alert.js +6 -2
- package/dist/primitives/alert.js.map +1 -1
- package/dist/primitives/badge.d.ts +1 -1
- package/dist/primitives/badge.d.ts.map +1 -1
- package/dist/primitives/badge.js +19 -6
- package/dist/primitives/badge.js.map +1 -1
- package/dist/primitives/button.d.ts +1 -1
- package/dist/primitives/button.d.ts.map +1 -1
- package/dist/primitives/button.js +12 -8
- package/dist/primitives/button.js.map +1 -1
- package/dist/primitives/card.js +4 -4
- package/dist/primitives/card.js.map +1 -1
- package/dist/primitives/dialog.d.ts +7 -3
- package/dist/primitives/dialog.d.ts.map +1 -1
- package/dist/primitives/dialog.js +22 -8
- package/dist/primitives/dialog.js.map +1 -1
- package/dist/primitives/input.d.ts.map +1 -1
- package/dist/primitives/input.js +4 -1
- package/dist/primitives/input.js.map +1 -1
- package/dist/primitives/scroll-area.js +1 -1
- package/dist/primitives/scroll-area.js.map +1 -1
- package/dist/primitives/select.d.ts.map +1 -1
- package/dist/primitives/select.js +6 -3
- package/dist/primitives/select.js.map +1 -1
- package/dist/primitives/sheet.d.ts +3 -1
- package/dist/primitives/sheet.d.ts.map +1 -1
- package/dist/primitives/sheet.js +17 -7
- package/dist/primitives/sheet.js.map +1 -1
- package/dist/primitives/skeleton.js +1 -1
- package/dist/primitives/skeleton.js.map +1 -1
- package/dist/primitives/switch.d.ts.map +1 -1
- package/dist/primitives/switch.js +2 -1
- package/dist/primitives/switch.js.map +1 -1
- package/dist/primitives/tabs.d.ts.map +1 -1
- package/dist/primitives/tabs.js +5 -3
- package/dist/primitives/tabs.js.map +1 -1
- package/dist/providers/query-provider.d.ts +32 -1
- package/dist/providers/query-provider.d.ts.map +1 -1
- package/dist/providers/query-provider.js +53 -4
- package/dist/providers/query-provider.js.map +1 -1
- package/dist/render-cases-primitives.js +1 -1
- package/dist/render-cases-primitives.js.map +1 -1
- package/dist/render-cases.d.ts.map +1 -1
- package/dist/render-cases.js +48 -6
- package/dist/render-cases.js.map +1 -1
- package/dist/render-effects.d.ts +42 -0
- package/dist/render-effects.d.ts.map +1 -0
- package/dist/render-effects.js +81 -0
- package/dist/render-effects.js.map +1 -0
- package/dist/render-scan.d.ts +25 -0
- package/dist/render-scan.d.ts.map +1 -0
- package/dist/render-scan.js +66 -0
- package/dist/render-scan.js.map +1 -0
- package/dist/theme/cd-tokens.d.ts +66 -0
- package/dist/theme/cd-tokens.d.ts.map +1 -0
- package/dist/theme/cd-tokens.js +64 -0
- package/dist/theme/cd-tokens.js.map +1 -0
- package/dist/theme/create-theme.d.ts +214 -39
- package/dist/theme/create-theme.d.ts.map +1 -1
- package/dist/theme/create-theme.js +204 -13
- package/dist/theme/create-theme.js.map +1 -1
- package/dist/theme/fonts.d.ts +38 -0
- package/dist/theme/fonts.d.ts.map +1 -0
- package/dist/theme/fonts.js +56 -0
- package/dist/theme/fonts.js.map +1 -0
- package/dist/theme/host-fonts.d.ts +39 -0
- package/dist/theme/host-fonts.d.ts.map +1 -0
- package/dist/theme/host-fonts.js +44 -0
- package/dist/theme/host-fonts.js.map +1 -0
- package/dist/theme/index.d.ts +8 -1
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/index.js +5 -1
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/portal-container.d.ts +16 -0
- package/dist/theme/portal-container.d.ts.map +1 -0
- package/dist/theme/portal-container.js +19 -0
- package/dist/theme/portal-container.js.map +1 -0
- package/dist/theme/presets.d.ts +24 -9
- package/dist/theme/presets.d.ts.map +1 -1
- package/dist/theme/presets.js +44 -11
- package/dist/theme/presets.js.map +1 -1
- package/dist/theme/theme-provider.d.ts +27 -9
- package/dist/theme/theme-provider.d.ts.map +1 -1
- package/dist/theme/theme-provider.js +42 -20
- package/dist/theme/theme-provider.js.map +1 -1
- package/package.json +11 -5
- package/src/app/adapt-data-widget.tsx +2 -42
- package/src/app/builder/BuilderBanners.tsx +26 -13
- package/src/app/builder/CanvasRow.tsx +79 -32
- package/src/app/builder/CatalogueSheet.tsx +58 -35
- package/src/app/builder/CellPropsSheet.tsx +17 -2
- package/src/app/builder/DiscardDialog.tsx +59 -0
- package/src/app/builder/EmptyHint.tsx +16 -11
- package/src/app/builder/LayoutSurface.tsx +207 -67
- package/src/app/builder/PipelineStrip.tsx +265 -156
- package/src/app/builder/SaveDialog.tsx +25 -14
- package/src/app/builder/Toolbar.tsx +154 -57
- package/src/app/builder/Workspace.tsx +226 -56
- package/src/app/builder/builder-feedback.test.tsx +75 -0
- package/src/app/builder/builder-model.test.ts +5 -0
- package/src/app/builder/builder-model.ts +346 -6
- package/src/app/builder/builder-model.ux.test.ts +456 -0
- package/src/app/builder/builder-ux.test.tsx +844 -0
- package/src/app/builder/labels.ts +15 -121
- package/src/app/builder/reveal.ts +10 -0
- package/src/app/builder/status-chip.tsx +8 -4
- package/src/app/builder/use-builder-catalogue.ts +62 -19
- package/src/app/builder/use-builder-session.ts +68 -0
- package/src/app/builder/use-display-resolver.ts +24 -0
- package/src/app/builder/use-save-dashboard.ts +5 -0
- package/src/app/index.ts +20 -3
- package/src/app/layout-builder.tsx +193 -186
- package/src/app/mcp-app-view-chrome.test.tsx +487 -14
- package/src/app/mcp-app-view-chrome.tsx +328 -100
- package/src/app/mcp-app-view-states.tsx +140 -0
- package/src/app/mcp-app-view.effects.test.tsx +292 -0
- package/src/app/mcp-app-view.test.tsx +478 -0
- package/src/app/mcp-app-view.tsx +447 -190
- package/src/app/mcp-toolkit-app.test.tsx +40 -0
- package/src/app/mcp-toolkit-app.tsx +39 -8
- package/src/app/mcp-use-host-bridge.test.tsx +180 -0
- package/src/app/mcp-use-host-bridge.tsx +67 -24
- package/src/app/model-context.test.tsx +116 -0
- package/src/app/model-context.tsx +125 -0
- package/src/app/use-build-mode-focus.ts +62 -0
- package/src/app/use-view-actions.ts +89 -0
- package/src/app/use-view-lifecycle.ts +122 -0
- package/src/app/view-lifecycle.test.ts +443 -0
- package/src/app/view-lifecycle.ts +334 -0
- package/src/app/widget-fixture.effects.test.tsx +51 -0
- package/src/app/widget-fixture.render.test.tsx +81 -0
- package/src/app/widget-fixture.tsx +34 -11
- package/src/app/widget-props-sheet.tsx +67 -39
- package/src/app/widget-renderer.render.test.tsx +131 -0
- package/src/app/widget-renderer.tsx +136 -38
- package/src/components/DrillButton.tsx +24 -10
- package/src/components/EmptyState.tsx +83 -0
- package/src/components/ErrorState.tsx +204 -0
- package/src/components/FilterBar.test.tsx +82 -0
- package/src/components/FilterBar.tsx +56 -27
- package/src/components/GroupCard.tsx +3 -2
- package/src/components/KpiGrid.test.tsx +279 -0
- package/src/components/KpiGrid.tsx +329 -91
- package/src/components/ListFooter.test.tsx +116 -0
- package/src/components/ListFooter.tsx +110 -12
- package/src/components/PagedListFooter.test.tsx +140 -0
- package/src/components/PagedListFooter.tsx +107 -0
- package/src/components/Pills.test.tsx +77 -0
- package/src/components/Pills.tsx +78 -16
- package/src/components/SectionHeading.tsx +9 -3
- package/src/components/Toast.tsx +206 -0
- package/src/components/WidgetHeader.test.tsx +78 -0
- package/src/components/WidgetHeader.tsx +63 -16
- package/src/components/feedback-states.test.tsx +314 -0
- package/src/components/tone-figures.test.tsx +58 -0
- package/src/fonts.css +19 -0
- package/src/globals.css +9 -165
- package/src/hooks/index.ts +6 -3
- package/src/hooks/tool-result-recovery-model.test.ts +744 -0
- package/src/hooks/tool-result-recovery-model.ts +418 -0
- package/src/hooks/use-tool-query.effects.test.tsx +205 -0
- package/src/hooks/use-tool-query.ts +156 -33
- package/src/hooks/use-tool-result-recovery.effects.test.tsx +141 -0
- package/src/hooks/use-tool-result-recovery.test.ts +28 -5
- package/src/hooks/use-tool-result-recovery.ts +101 -131
- package/src/hooks/use-view-data.test.tsx +169 -0
- package/src/hooks/use-view-data.ts +131 -10
- package/src/hooks/use-view-tool-query.ts +25 -19
- package/src/i18n-coverage.test.tsx +425 -0
- package/src/index.ts +102 -13
- package/src/lib/focus-return.test.ts +288 -0
- package/src/lib/focus-return.ts +96 -0
- package/src/lib/focus-ring.ts +20 -0
- package/src/lib/icon-stroke.test.ts +59 -0
- package/src/lib/icon-stroke.ts +13 -0
- package/src/lib/parse-tool-result.test.ts +46 -1
- package/src/lib/parse-tool-result.ts +40 -2
- package/src/lib/toast-model.test.ts +233 -0
- package/src/lib/toast-model.ts +165 -0
- package/src/lib/tone-utils.test.ts +125 -16
- package/src/lib/tone-utils.ts +144 -15
- package/src/lib/tool-query.test.ts +569 -0
- package/src/lib/tool-query.ts +253 -0
- package/src/lib/tool-result-observer.test.ts +296 -0
- package/src/lib/tool-result-observer.ts +211 -0
- package/src/lib/typography.test.ts +45 -0
- package/src/lib/typography.ts +47 -0
- package/src/lib/utils.ts +12 -1
- package/src/locale/index.ts +14 -1
- package/src/locale/locale-provider.test.tsx +87 -0
- package/src/locale/locale-provider.tsx +90 -3
- package/src/locale/message-types.ts +337 -0
- package/src/locale/messages-de.ts +198 -0
- package/src/locale/messages-en.ts +196 -0
- package/src/locale/toolkit-messages.test.ts +349 -0
- package/src/locale/toolkit-messages.ts +143 -0
- package/src/no-hardcoded-copy.test.ts +432 -0
- package/src/primitives/alert.tsx +7 -2
- package/src/primitives/badge.tsx +19 -7
- package/src/primitives/button.tsx +13 -8
- package/src/primitives/card.tsx +4 -4
- package/src/primitives/dialog.tsx +42 -8
- package/src/primitives/input.tsx +5 -3
- package/src/primitives/scroll-area.tsx +1 -1
- package/src/primitives/select.tsx +7 -4
- package/src/primitives/sheet.tsx +33 -6
- package/src/primitives/skeleton.tsx +1 -1
- package/src/primitives/switch.tsx +4 -2
- package/src/primitives/tabs.tsx +9 -6
- package/src/providers/query-provider.test.tsx +154 -0
- package/src/providers/query-provider.tsx +66 -5
- package/src/render-cases-primitives.tsx +1 -1
- package/src/render-cases.tsx +82 -6
- package/src/render-effects.tsx +96 -0
- package/src/render-scan.ts +75 -0
- package/src/theme/cd-tokens.generated.css +48 -0
- package/src/theme/cd-tokens.test.ts +109 -0
- package/src/theme/cd-tokens.ts +67 -0
- package/src/theme/create-theme.test.ts +253 -5
- package/src/theme/create-theme.ts +341 -41
- package/src/theme/fonts.test.ts +85 -0
- package/src/theme/fonts.ts +72 -0
- package/src/theme/host-fonts.test.ts +135 -0
- package/src/theme/host-fonts.ts +52 -0
- package/src/theme/index.ts +15 -1
- package/src/theme/portal-container.ts +20 -0
- package/src/theme/presets.ts +44 -11
- package/src/theme/theme-css.test.ts +619 -0
- package/src/theme/theme-provider.test.tsx +82 -0
- package/src/theme/theme-provider.tsx +60 -24
- package/src/theme.css +344 -0
- package/ui-catalog.json +783 -49
|
@@ -16,6 +16,10 @@ import {
|
|
|
16
16
|
SheetTitle,
|
|
17
17
|
} from "../primitives/sheet.js"
|
|
18
18
|
import { Switch } from "../primitives/switch.js"
|
|
19
|
+
import { useToolkitMessages } from "../locale/locale-provider.js"
|
|
20
|
+
import type { PropsSheetMessages } from "../locale/toolkit-messages.js"
|
|
21
|
+
import { propFieldText, type PropFieldText } from "./builder/builder-model.js"
|
|
22
|
+
import { useDisplayResolver } from "./builder/use-display-resolver.js"
|
|
19
23
|
|
|
20
24
|
/**
|
|
21
25
|
* Tiny JSON-Schema-driven editor for a layout cell's `props`. The
|
|
@@ -29,6 +33,12 @@ import { Switch } from "../primitives/switch.js"
|
|
|
29
33
|
* - Nested objects/arrays/$ref/anyOf are not supported — the field
|
|
30
34
|
* shows an "edit JSON manually" hint so authors know to fall back.
|
|
31
35
|
*
|
|
36
|
+
* Each field is labelled in user words: the property's `x-display` title
|
|
37
|
+
* (`PROPS_SCHEMA_DISPLAY_KEYWORD`, a key or per-locale text), else its JSON
|
|
38
|
+
* Schema `title`, else its name; the raw property name then follows as a
|
|
39
|
+
* quiet hint, and the help line is the `x-display` summary or the
|
|
40
|
+
* `description`.
|
|
41
|
+
*
|
|
32
42
|
* The sheet writes a clean `Record<string, unknown>` (empty values
|
|
33
43
|
* stripped) and reports `undefined` when the user clears every field, so
|
|
34
44
|
* the cell's `props` field stays absent rather than `{}` — matches the
|
|
@@ -37,6 +47,7 @@ import { Switch } from "../primitives/switch.js"
|
|
|
37
47
|
|
|
38
48
|
interface JsonSchemaProperty {
|
|
39
49
|
type?: string
|
|
50
|
+
title?: string
|
|
40
51
|
description?: string
|
|
41
52
|
enum?: unknown[]
|
|
42
53
|
default?: unknown
|
|
@@ -48,25 +59,8 @@ interface JsonSchemaObject {
|
|
|
48
59
|
required?: string[]
|
|
49
60
|
}
|
|
50
61
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
description?: string
|
|
54
|
-
apply?: string
|
|
55
|
-
cancel?: string
|
|
56
|
-
reset?: string
|
|
57
|
-
noProps?: string
|
|
58
|
-
unsetOption?: string
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const DEFAULT_LABELS: Required<WidgetPropsSheetLabels> = {
|
|
62
|
-
title: "Configure widget",
|
|
63
|
-
description: "Set per-instance props for this widget.",
|
|
64
|
-
apply: "Apply",
|
|
65
|
-
cancel: "Cancel",
|
|
66
|
-
reset: "Reset",
|
|
67
|
-
noProps: "This widget takes no per-instance props.",
|
|
68
|
-
unsetOption: "(unset)",
|
|
69
|
-
}
|
|
62
|
+
/** Override strings of the sheet; each defaults to the locale's `propsSheet` catalog. */
|
|
63
|
+
export type WidgetPropsSheetLabels = Partial<PropsSheetMessages>
|
|
70
64
|
|
|
71
65
|
const UNSET_SENTINEL = "__unset__"
|
|
72
66
|
|
|
@@ -74,6 +68,8 @@ export interface WidgetPropsSheetProps {
|
|
|
74
68
|
open: boolean
|
|
75
69
|
onOpenChange: (open: boolean) => void
|
|
76
70
|
widgetId: string
|
|
71
|
+
/** The widget's human name (its title); the id then shows as a quiet hint under it. */
|
|
72
|
+
widgetTitle?: string
|
|
77
73
|
schema: Record<string, unknown> | undefined
|
|
78
74
|
value: Record<string, unknown> | undefined
|
|
79
75
|
onApply: (next: Record<string, unknown> | undefined) => void
|
|
@@ -84,12 +80,15 @@ export function WidgetPropsSheet({
|
|
|
84
80
|
open,
|
|
85
81
|
onOpenChange,
|
|
86
82
|
widgetId,
|
|
83
|
+
widgetTitle,
|
|
87
84
|
schema,
|
|
88
85
|
value,
|
|
89
86
|
onApply,
|
|
90
87
|
labels,
|
|
91
88
|
}: WidgetPropsSheetProps) {
|
|
92
|
-
const
|
|
89
|
+
const messages = useToolkitMessages()
|
|
90
|
+
const { locale, resolveKey } = useDisplayResolver()
|
|
91
|
+
const L = { ...messages.propsSheet, ...labels }
|
|
93
92
|
const obj = (schema ?? {}) as JsonSchemaObject
|
|
94
93
|
const [draft, setDraft] = useState<Record<string, unknown>>(value ?? {})
|
|
95
94
|
|
|
@@ -121,9 +120,10 @@ export function WidgetPropsSheet({
|
|
|
121
120
|
<SheetContent className="w-[400px] sm:max-w-md">
|
|
122
121
|
<SheetHeader>
|
|
123
122
|
<SheetTitle>{L.title}</SheetTitle>
|
|
124
|
-
<SheetDescription>
|
|
125
|
-
<span
|
|
126
|
-
{
|
|
123
|
+
<SheetDescription className="flex flex-col items-start gap-1">
|
|
124
|
+
<span>{L.description}</span>
|
|
125
|
+
{widgetTitle && <span className="text-foreground font-medium">{widgetTitle}</span>}
|
|
126
|
+
<span className="bg-muted text-small rounded px-1 font-mono">{widgetId}</span>
|
|
127
127
|
</SheetDescription>
|
|
128
128
|
</SheetHeader>
|
|
129
129
|
<div className="flex flex-col gap-4 px-4 py-4">
|
|
@@ -134,9 +134,11 @@ export function WidgetPropsSheet({
|
|
|
134
134
|
<PropertyField
|
|
135
135
|
key={key}
|
|
136
136
|
name={key}
|
|
137
|
+
text={propFieldText(key, prop as Record<string, unknown>, locale, resolveKey)}
|
|
137
138
|
prop={prop}
|
|
138
139
|
value={draft[key]}
|
|
139
140
|
unsetLabel={L.unsetOption}
|
|
141
|
+
unsupportedLabel={L.unsupportedType}
|
|
140
142
|
onChange={(v) =>
|
|
141
143
|
setDraft((prev) => {
|
|
142
144
|
if (v === undefined) {
|
|
@@ -169,15 +171,19 @@ export function WidgetPropsSheet({
|
|
|
169
171
|
|
|
170
172
|
function PropertyField({
|
|
171
173
|
name,
|
|
174
|
+
text,
|
|
172
175
|
prop,
|
|
173
176
|
value,
|
|
174
177
|
unsetLabel,
|
|
178
|
+
unsupportedLabel,
|
|
175
179
|
onChange,
|
|
176
180
|
}: {
|
|
177
181
|
name: string
|
|
182
|
+
text: PropFieldText
|
|
178
183
|
prop: JsonSchemaProperty
|
|
179
184
|
value: unknown
|
|
180
185
|
unsetLabel: string
|
|
186
|
+
unsupportedLabel: (type: string) => string
|
|
181
187
|
onChange: (v: unknown) => void
|
|
182
188
|
}) {
|
|
183
189
|
const id = `prop-${name}`
|
|
@@ -186,12 +192,12 @@ function PropertyField({
|
|
|
186
192
|
const options = prop.enum.filter((v): v is string => typeof v === "string")
|
|
187
193
|
const current = typeof value === "string" && value !== "" ? value : UNSET_SENTINEL
|
|
188
194
|
return (
|
|
189
|
-
<FieldShell id={id}
|
|
195
|
+
<FieldShell id={id} text={text}>
|
|
190
196
|
<Select
|
|
191
197
|
value={current}
|
|
192
198
|
onValueChange={(v) => onChange(v === UNSET_SENTINEL ? undefined : v)}
|
|
193
199
|
>
|
|
194
|
-
<SelectTrigger id={id}>
|
|
200
|
+
<SelectTrigger id={id} aria-describedby={helpId(id, text)}>
|
|
195
201
|
<SelectValue placeholder={unsetLabel} />
|
|
196
202
|
</SelectTrigger>
|
|
197
203
|
<SelectContent>
|
|
@@ -210,9 +216,10 @@ function PropertyField({
|
|
|
210
216
|
if (prop.type === "string") {
|
|
211
217
|
const current = typeof value === "string" ? value : ""
|
|
212
218
|
return (
|
|
213
|
-
<FieldShell id={id}
|
|
219
|
+
<FieldShell id={id} text={text}>
|
|
214
220
|
<Input
|
|
215
221
|
id={id}
|
|
222
|
+
aria-describedby={helpId(id, text)}
|
|
216
223
|
value={current}
|
|
217
224
|
onChange={(e) => onChange(e.target.value === "" ? undefined : e.target.value)}
|
|
218
225
|
/>
|
|
@@ -223,9 +230,10 @@ function PropertyField({
|
|
|
223
230
|
if (prop.type === "number" || prop.type === "integer") {
|
|
224
231
|
const current = typeof value === "number" ? String(value) : ""
|
|
225
232
|
return (
|
|
226
|
-
<FieldShell id={id}
|
|
233
|
+
<FieldShell id={id} text={text}>
|
|
227
234
|
<Input
|
|
228
235
|
id={id}
|
|
236
|
+
aria-describedby={helpId(id, text)}
|
|
229
237
|
type="number"
|
|
230
238
|
value={current}
|
|
231
239
|
onChange={(e) => {
|
|
@@ -243,9 +251,10 @@ function PropertyField({
|
|
|
243
251
|
if (prop.type === "boolean") {
|
|
244
252
|
const current = value === true
|
|
245
253
|
return (
|
|
246
|
-
<FieldShell id={id}
|
|
254
|
+
<FieldShell id={id} text={text}>
|
|
247
255
|
<Switch
|
|
248
256
|
id={id}
|
|
257
|
+
aria-describedby={helpId(id, text)}
|
|
249
258
|
checked={current}
|
|
250
259
|
onCheckedChange={(checked) => onChange(checked || undefined)}
|
|
251
260
|
/>
|
|
@@ -254,32 +263,51 @@ function PropertyField({
|
|
|
254
263
|
}
|
|
255
264
|
|
|
256
265
|
return (
|
|
257
|
-
<FieldShell id={id}
|
|
258
|
-
<p className="text-muted-foreground text-
|
|
259
|
-
|
|
266
|
+
<FieldShell id={id} text={text}>
|
|
267
|
+
<p className="text-muted-foreground text-small italic">
|
|
268
|
+
{unsupportedLabel(prop.type ?? "?")}
|
|
260
269
|
</p>
|
|
261
270
|
</FieldShell>
|
|
262
271
|
)
|
|
263
272
|
}
|
|
264
273
|
|
|
274
|
+
/** The id of a field's help line, when it has one (its `aria-describedby`). */
|
|
275
|
+
function helpId(id: string, text: PropFieldText): string | undefined {
|
|
276
|
+
return text.help ? `${id}-help` : undefined
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Label (the human title), the raw property name as a quiet hint beside it
|
|
281
|
+
* (outside the label, so the accessible name stays the title), the control,
|
|
282
|
+
* and the help line it is described by.
|
|
283
|
+
*/
|
|
265
284
|
function FieldShell({
|
|
266
285
|
id,
|
|
267
|
-
|
|
268
|
-
description,
|
|
286
|
+
text,
|
|
269
287
|
children,
|
|
270
288
|
}: {
|
|
271
289
|
id: string
|
|
272
|
-
|
|
273
|
-
description?: string
|
|
290
|
+
text: PropFieldText
|
|
274
291
|
children: ReactNode
|
|
275
292
|
}) {
|
|
276
293
|
return (
|
|
277
294
|
<div className="flex flex-col gap-1.5">
|
|
278
|
-
<
|
|
279
|
-
{
|
|
280
|
-
|
|
295
|
+
<div className="flex flex-wrap items-baseline gap-x-2">
|
|
296
|
+
<label htmlFor={id} className="text-sm font-medium">
|
|
297
|
+
{text.label}
|
|
298
|
+
</label>
|
|
299
|
+
{text.hint && (
|
|
300
|
+
<code data-slot="prop-name" className="text-muted-foreground text-small font-mono">
|
|
301
|
+
{text.hint}
|
|
302
|
+
</code>
|
|
303
|
+
)}
|
|
304
|
+
</div>
|
|
281
305
|
{children}
|
|
282
|
-
{
|
|
306
|
+
{text.help ? (
|
|
307
|
+
<p id={helpId(id, text)} className="text-muted-foreground text-small">
|
|
308
|
+
{text.help}
|
|
309
|
+
</p>
|
|
310
|
+
) : null}
|
|
283
311
|
</div>
|
|
284
312
|
)
|
|
285
313
|
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { renderToStaticMarkup } from "react-dom/server"
|
|
2
|
+
import { describe, expect, it, vi } from "vitest"
|
|
3
|
+
import type { WidgetProps } from "@miragon/mcp-toolkit-core"
|
|
4
|
+
import { LocaleProvider } from "../locale/locale-provider.js"
|
|
5
|
+
import { TOOLKIT_MESSAGES } from "../locale/toolkit-messages.js"
|
|
6
|
+
import { decodeEntities } from "../render-scan.js"
|
|
7
|
+
import {
|
|
8
|
+
buildPipelineContext,
|
|
9
|
+
RenderedWidget,
|
|
10
|
+
resetKeysChanged,
|
|
11
|
+
WidgetCrashFallback,
|
|
12
|
+
WidgetErrorBoundary,
|
|
13
|
+
WidgetRenderer,
|
|
14
|
+
} from "./widget-renderer.js"
|
|
15
|
+
|
|
16
|
+
const de = TOOLKIT_MESSAGES.de.renderer
|
|
17
|
+
|
|
18
|
+
function Hello({ keys }: WidgetProps) {
|
|
19
|
+
return <p>hello {typeof keys.who === "string" ? keys.who : ""}</p>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe("WidgetRenderer fallbacks (toolkit#176 N24, U19)", () => {
|
|
23
|
+
it("an unknown widget id renders a localized 'not available' note instead of vanishing", () => {
|
|
24
|
+
const html = renderToStaticMarkup(
|
|
25
|
+
<LocaleProvider locale="de">
|
|
26
|
+
<WidgetRenderer
|
|
27
|
+
layout={{ rows: [{ row: [{ widget: "demo:hello" }, { widget: "demo:gone" }] }] }}
|
|
28
|
+
keys={{ who: "ada" }}
|
|
29
|
+
errors={[]}
|
|
30
|
+
widgets={{ "demo:hello": Hello }}
|
|
31
|
+
/>
|
|
32
|
+
</LocaleProvider>,
|
|
33
|
+
)
|
|
34
|
+
expect(html).toContain("hello ada")
|
|
35
|
+
expect(decodeEntities(html)).toContain(de.widgetUnavailable)
|
|
36
|
+
expect(html).toContain('data-widget-id="demo:gone"')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it("the crash fallback names what happened, keeps the raw error under Details, offers a retry", () => {
|
|
40
|
+
const html = decodeEntities(
|
|
41
|
+
renderToStaticMarkup(
|
|
42
|
+
<LocaleProvider locale="de">
|
|
43
|
+
<WidgetCrashFallback widgetId="demo:x" error={new Error("kaputt")} onRetry={() => {}} />
|
|
44
|
+
</LocaleProvider>,
|
|
45
|
+
),
|
|
46
|
+
)
|
|
47
|
+
expect(html).toContain('role="alert"')
|
|
48
|
+
expect(html).toContain(de.widgetCrashed)
|
|
49
|
+
expect(html).toContain("demo:x: kaputt")
|
|
50
|
+
expect(html).toContain(`${TOOLKIT_MESSAGES.de.common.retry}</button>`)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it("the builder's empty-context hint is localized", () => {
|
|
54
|
+
const html = renderToStaticMarkup(
|
|
55
|
+
<LocaleProvider locale="de">
|
|
56
|
+
<RenderedWidget
|
|
57
|
+
widget={Hello}
|
|
58
|
+
widgetId="demo:hello"
|
|
59
|
+
props={{ keys: {}, context: { steps: {}, keys: {}, errors: [] } }}
|
|
60
|
+
showEmptyHint
|
|
61
|
+
/>
|
|
62
|
+
</LocaleProvider>,
|
|
63
|
+
)
|
|
64
|
+
expect(html).toContain(TOOLKIT_MESSAGES.de.renderer.noDataYet)
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
describe("WidgetErrorBoundary reset", () => {
|
|
69
|
+
it("resets a latched crash when its reset keys change, not on an unrelated update", () => {
|
|
70
|
+
const keysA = [{ a: 1 }]
|
|
71
|
+
const boundary = new WidgetErrorBoundary({ widgetId: "w", children: null, resetKeys: keysA })
|
|
72
|
+
const setState = vi.fn()
|
|
73
|
+
boundary.setState = setState
|
|
74
|
+
boundary.state = { error: new Error("boom") }
|
|
75
|
+
|
|
76
|
+
// Same keys (identity) → stays failed.
|
|
77
|
+
boundary.componentDidUpdate({ resetKeys: keysA })
|
|
78
|
+
expect(setState).not.toHaveBeenCalled()
|
|
79
|
+
|
|
80
|
+
// New data → a fresh render.
|
|
81
|
+
boundary.componentDidUpdate({ resetKeys: [{ a: 1 }] })
|
|
82
|
+
expect(setState).toHaveBeenCalledWith({ error: null })
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it("does nothing while healthy", () => {
|
|
86
|
+
const boundary = new WidgetErrorBoundary({ widgetId: "w", children: null, resetKeys: [1] })
|
|
87
|
+
const setState = vi.fn()
|
|
88
|
+
boundary.setState = setState
|
|
89
|
+
boundary.componentDidUpdate({ resetKeys: [2] })
|
|
90
|
+
expect(setState).not.toHaveBeenCalled()
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it("its retry clears the error", () => {
|
|
94
|
+
const boundary = new WidgetErrorBoundary({ widgetId: "w", children: null })
|
|
95
|
+
const setState = vi.fn()
|
|
96
|
+
boundary.setState = setState
|
|
97
|
+
boundary.reset()
|
|
98
|
+
expect(setState).toHaveBeenCalledWith({ error: null })
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
describe("resetKeysChanged", () => {
|
|
103
|
+
it("compares by length and entry identity", () => {
|
|
104
|
+
const a = { x: 1 }
|
|
105
|
+
expect(resetKeysChanged([a], [a])).toBe(false)
|
|
106
|
+
expect(resetKeysChanged(undefined, undefined)).toBe(false)
|
|
107
|
+
const same = [a]
|
|
108
|
+
expect(resetKeysChanged(same, same)).toBe(false)
|
|
109
|
+
expect(resetKeysChanged([a], [{ x: 1 }])).toBe(true)
|
|
110
|
+
expect(resetKeysChanged([a], [a, a])).toBe(true)
|
|
111
|
+
expect(resetKeysChanged(undefined, [a])).toBe(true)
|
|
112
|
+
expect(resetKeysChanged([a], undefined)).toBe(true)
|
|
113
|
+
expect(resetKeysChanged([Number.NaN], [Number.NaN])).toBe(false)
|
|
114
|
+
})
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
describe("buildPipelineContext", () => {
|
|
118
|
+
it("tags each step with its id and passes keys and errors through", () => {
|
|
119
|
+
const keys = { k: 1 }
|
|
120
|
+
const errors = [{ stepId: "s2", reason: "x" }]
|
|
121
|
+
const ctx = buildPipelineContext(
|
|
122
|
+
keys,
|
|
123
|
+
{ s1: { data: 1, keys: {}, _app: "a", _dataType: "a:t" } },
|
|
124
|
+
errors,
|
|
125
|
+
)
|
|
126
|
+
expect(ctx.steps.s1).toEqual({ data: 1, keys: {}, _app: "a", _dataType: "a:t", _step: "s1" })
|
|
127
|
+
expect(ctx.keys).toBe(keys)
|
|
128
|
+
expect(ctx.errors).toBe(errors)
|
|
129
|
+
expect(buildPipelineContext(keys, undefined, []).steps).toEqual({})
|
|
130
|
+
})
|
|
131
|
+
})
|
|
@@ -1,46 +1,136 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Component,
|
|
3
|
+
createRef,
|
|
4
|
+
useMemo,
|
|
5
|
+
type ComponentType,
|
|
6
|
+
type ReactNode,
|
|
7
|
+
type ErrorInfo,
|
|
8
|
+
} from "react"
|
|
2
9
|
import type { LayoutConfig, PipelineContext, RowDef, WidgetProps } from "@miragon/mcp-toolkit-core"
|
|
3
10
|
import { normalizeLayout } from "@miragon/mcp-toolkit-core"
|
|
11
|
+
import { ErrorState } from "../components/ErrorState.js"
|
|
4
12
|
import { GridLayout, GridItem } from "../components/GridLayout.js"
|
|
5
|
-
import {
|
|
13
|
+
import { useToolkitMessages } from "../locale/locale-provider.js"
|
|
6
14
|
import { Tabs, TabsList, TabsTrigger, TabsContent } from "../primitives/tabs.js"
|
|
7
15
|
|
|
8
16
|
export type WidgetComponent = ComponentType<WidgetProps>
|
|
9
17
|
|
|
18
|
+
/**
|
|
19
|
+
* True when two reset-key lists differ (length or any entry by identity).
|
|
20
|
+
* Exported for tests: it decides when a crashed widget gets a fresh render.
|
|
21
|
+
*/
|
|
22
|
+
export function resetKeysChanged(
|
|
23
|
+
prev: readonly unknown[] | undefined,
|
|
24
|
+
next: readonly unknown[] | undefined,
|
|
25
|
+
): boolean {
|
|
26
|
+
if (prev === next) return false
|
|
27
|
+
if (!prev || !next || prev.length !== next.length) return true
|
|
28
|
+
return prev.some((value, idx) => !Object.is(value, next[idx]))
|
|
29
|
+
}
|
|
30
|
+
|
|
10
31
|
/**
|
|
11
32
|
* Catches render-time crashes from a single widget so one broken widget
|
|
12
33
|
* can't take down the entire iframe view (or the builder canvas/preview).
|
|
13
34
|
*
|
|
35
|
+
* The fallback is localized, names what happened, keeps the raw error under
|
|
36
|
+
* "Details" and offers a retry. The boundary resets itself when its
|
|
37
|
+
* `resetKeys` change, so new data (a refresh, a new tool result, a builder
|
|
38
|
+
* edit) gets a fresh render instead of a latched failure (toolkit#176 N24).
|
|
39
|
+
* When a retry crashes again, keyboard focus moves to the new retry button
|
|
40
|
+
* instead of falling back to the document (the pressed one unmounted). After
|
|
41
|
+
* a retry that renders, the widget's own markup is unknown here, so focus
|
|
42
|
+
* returns to the document.
|
|
43
|
+
*
|
|
14
44
|
* Exported so the visual builder's canvas + preview render sites reuse the
|
|
15
45
|
* same isolation the rendered `WidgetRenderer` already had — every place a
|
|
16
46
|
* widget component is rendered goes through {@link RenderedWidget}, which
|
|
17
47
|
* wraps the component in this boundary.
|
|
18
48
|
*/
|
|
19
49
|
export class WidgetErrorBoundary extends Component<
|
|
20
|
-
{ widgetId: string; children: ReactNode },
|
|
50
|
+
{ widgetId: string; children: ReactNode; resetKeys?: readonly unknown[] },
|
|
21
51
|
{ error: Error | null }
|
|
22
52
|
> {
|
|
23
53
|
override state: { error: Error | null } = { error: null }
|
|
54
|
+
/** Set by the user's retry; consumed by the next update. */
|
|
55
|
+
private retryFocusOwed = false
|
|
56
|
+
private readonly fallbackRef = createRef<HTMLDivElement>()
|
|
24
57
|
static getDerivedStateFromError(error: Error) {
|
|
25
58
|
return { error }
|
|
26
59
|
}
|
|
27
60
|
override componentDidCatch(error: Error, info: ErrorInfo) {
|
|
28
61
|
console.error(`Widget ${this.props.widgetId} crashed:`, error, info)
|
|
29
62
|
}
|
|
63
|
+
override componentDidUpdate(prevProps: { resetKeys?: readonly unknown[] }) {
|
|
64
|
+
if (this.retryFocusOwed) {
|
|
65
|
+
this.retryFocusOwed = false
|
|
66
|
+
if (this.state.error) this.fallbackRef.current?.querySelector<HTMLElement>("button")?.focus()
|
|
67
|
+
}
|
|
68
|
+
if (this.state.error && resetKeysChanged(prevProps.resetKeys, this.props.resetKeys)) {
|
|
69
|
+
this.reset()
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
reset = () => {
|
|
73
|
+
this.setState({ error: null })
|
|
74
|
+
}
|
|
75
|
+
/** The fallback's retry: a reset that keeps keyboard focus if the widget crashes again. */
|
|
76
|
+
retry = () => {
|
|
77
|
+
this.retryFocusOwed = true
|
|
78
|
+
this.reset()
|
|
79
|
+
}
|
|
30
80
|
override render() {
|
|
31
81
|
if (this.state.error) {
|
|
32
82
|
return (
|
|
33
|
-
<
|
|
34
|
-
<
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
83
|
+
<div ref={this.fallbackRef} data-slot="widget-crash">
|
|
84
|
+
<WidgetCrashFallback
|
|
85
|
+
widgetId={this.props.widgetId}
|
|
86
|
+
error={this.state.error}
|
|
87
|
+
onRetry={this.retry}
|
|
88
|
+
/>
|
|
89
|
+
</div>
|
|
38
90
|
)
|
|
39
91
|
}
|
|
40
92
|
return this.props.children
|
|
41
93
|
}
|
|
42
94
|
}
|
|
43
95
|
|
|
96
|
+
/** The localized crash fallback of {@link WidgetErrorBoundary}. */
|
|
97
|
+
export function WidgetCrashFallback({
|
|
98
|
+
widgetId,
|
|
99
|
+
error,
|
|
100
|
+
onRetry,
|
|
101
|
+
}: {
|
|
102
|
+
widgetId: string
|
|
103
|
+
error: Error
|
|
104
|
+
onRetry: () => void
|
|
105
|
+
}) {
|
|
106
|
+
const messages = useToolkitMessages()
|
|
107
|
+
return (
|
|
108
|
+
<ErrorState
|
|
109
|
+
variant="inline"
|
|
110
|
+
title={messages.renderer.widgetCrashed}
|
|
111
|
+
details={`${widgetId}: ${error.message}`}
|
|
112
|
+
onRetry={onRetry}
|
|
113
|
+
/>
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** A layout cell names a widget the bundle does not contain: say so, quietly. */
|
|
118
|
+
export function UnavailableWidget({
|
|
119
|
+
widgetId,
|
|
120
|
+
className = "text-muted-foreground rounded-md border border-dashed p-4 text-sm",
|
|
121
|
+
}: {
|
|
122
|
+
widgetId: string
|
|
123
|
+
className?: string
|
|
124
|
+
}) {
|
|
125
|
+
const messages = useToolkitMessages()
|
|
126
|
+
return (
|
|
127
|
+
<div data-slot="widget-unavailable" data-widget-id={widgetId} className={className}>
|
|
128
|
+
<p>{messages.renderer.widgetUnavailable}</p>
|
|
129
|
+
<p className="text-small mt-1 font-mono break-all">{widgetId}</p>
|
|
130
|
+
</div>
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
|
|
44
134
|
/**
|
|
45
135
|
* True when the pipeline context carries no widget data at all — no step
|
|
46
136
|
* results and no keys. A widget rendered against an empty context typically
|
|
@@ -59,7 +149,7 @@ export function contextHasNoData(context: PipelineContext): boolean {
|
|
|
59
149
|
* The single render site for a layout cell's widget, shared across the
|
|
60
150
|
* rendered view (`RowsRenderer`), the builder canvas, and the builder
|
|
61
151
|
* preview so all three isolate crashes identically:
|
|
62
|
-
* - missing component → "not
|
|
152
|
+
* - missing component → a localized "not available in this version" note,
|
|
63
153
|
* - render crash → caught by {@link WidgetErrorBoundary} (Finding [1]),
|
|
64
154
|
* - (builder only, opt-in) empty pipeline context → a dezent
|
|
65
155
|
* "no data yet" hint (Finding [4]).
|
|
@@ -79,7 +169,7 @@ export function RenderedWidget({
|
|
|
79
169
|
props,
|
|
80
170
|
cellProps,
|
|
81
171
|
showEmptyHint = false,
|
|
82
|
-
notBundledClassName
|
|
172
|
+
notBundledClassName,
|
|
83
173
|
}: {
|
|
84
174
|
widget: WidgetComponent | undefined
|
|
85
175
|
widgetId: string
|
|
@@ -88,23 +178,26 @@ export function RenderedWidget({
|
|
|
88
178
|
showEmptyHint?: boolean
|
|
89
179
|
notBundledClassName?: string
|
|
90
180
|
}) {
|
|
91
|
-
if (!Widget) {
|
|
92
|
-
return <div className={notBundledClassName}>Widget "{widgetId}" not bundled.</div>
|
|
93
|
-
}
|
|
181
|
+
if (!Widget) return <UnavailableWidget widgetId={widgetId} className={notBundledClassName} />
|
|
94
182
|
if (showEmptyHint && contextHasNoData(props.context)) {
|
|
95
|
-
return
|
|
96
|
-
<div className={notBundledClassName}>
|
|
97
|
-
No data for this widget yet — it appears after a refresh.
|
|
98
|
-
</div>
|
|
99
|
-
)
|
|
183
|
+
return <NoDataYet className={notBundledClassName} />
|
|
100
184
|
}
|
|
101
185
|
return (
|
|
102
|
-
<WidgetErrorBoundary widgetId={widgetId}>
|
|
186
|
+
<WidgetErrorBoundary widgetId={widgetId} resetKeys={[props.context, props.keys, cellProps]}>
|
|
103
187
|
<Widget {...props} widgetProps={cellProps} />
|
|
104
188
|
</WidgetErrorBoundary>
|
|
105
189
|
)
|
|
106
190
|
}
|
|
107
191
|
|
|
192
|
+
function NoDataYet({
|
|
193
|
+
className = "text-muted-foreground rounded-md border border-dashed p-4 text-sm",
|
|
194
|
+
}: {
|
|
195
|
+
className?: string
|
|
196
|
+
}) {
|
|
197
|
+
const messages = useToolkitMessages()
|
|
198
|
+
return <div className={className}>{messages.renderer.noDataYet}</div>
|
|
199
|
+
}
|
|
200
|
+
|
|
108
201
|
export interface WidgetRendererProps {
|
|
109
202
|
layout: LayoutConfig
|
|
110
203
|
keys: Record<string, unknown>
|
|
@@ -137,14 +230,11 @@ function RowsRenderer({
|
|
|
137
230
|
<GridLayout key={rowIdx}>
|
|
138
231
|
{row.row.map((cell, cellIdx) => {
|
|
139
232
|
const WidgetComponent = widgets[cell.widget]
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
// Use a composite key so the same widget can appear in multiple
|
|
146
|
-
// cells of one row (e.g. side-by-side scoped dashboards) without
|
|
147
|
-
// colliding on React's reconciliation key.
|
|
233
|
+
// An unknown widget id renders the localized "not available"
|
|
234
|
+
// note in its cell instead of vanishing (an empty page reads as
|
|
235
|
+
// broken). The composite key lets the same widget appear in
|
|
236
|
+
// several cells of one row (e.g. side-by-side scoped dashboards)
|
|
237
|
+
// without colliding on React's reconciliation key.
|
|
148
238
|
return (
|
|
149
239
|
<GridItem key={`${cell.widget}-${cellIdx}`} span={cell.span ?? 12}>
|
|
150
240
|
<RenderedWidget
|
|
@@ -162,19 +252,27 @@ function RowsRenderer({
|
|
|
162
252
|
)
|
|
163
253
|
}
|
|
164
254
|
|
|
165
|
-
|
|
255
|
+
/**
|
|
256
|
+
* The pipeline context widgets read. `WidgetRenderer` rebuilds it only when
|
|
257
|
+
* the view data changes — its identity is the error boundaries' reset key.
|
|
258
|
+
*/
|
|
259
|
+
export function buildPipelineContext(
|
|
260
|
+
keys: Record<string, unknown>,
|
|
261
|
+
stepData: WidgetRendererProps["stepData"],
|
|
262
|
+
errors: WidgetRendererProps["errors"],
|
|
263
|
+
): PipelineContext {
|
|
166
264
|
const steps: Record<string, PipelineContext["steps"][string]> = {}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
steps[id] = { ...result, _step: id }
|
|
170
|
-
}
|
|
265
|
+
for (const [id, result] of Object.entries(stepData ?? {})) {
|
|
266
|
+
steps[id] = { ...result, _step: id }
|
|
171
267
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
268
|
+
return { steps, keys, errors }
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export function WidgetRenderer({ layout, keys, stepData, errors, widgets }: WidgetRendererProps) {
|
|
272
|
+
const widgetProps = useMemo<WidgetProps>(
|
|
273
|
+
() => ({ keys, context: buildPipelineContext(keys, stepData, errors) }),
|
|
274
|
+
[keys, stepData, errors],
|
|
275
|
+
)
|
|
178
276
|
const normalized = normalizeLayout(layout)
|
|
179
277
|
|
|
180
278
|
if ("tabs" in normalized) {
|