@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
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `McpAppView` lifecycle as an explicit state machine (toolkit#176).
|
|
3
|
+
*
|
|
4
|
+
* Before the view payload exists the shell is in exactly one phase:
|
|
5
|
+
*
|
|
6
|
+
* waiting ──result──▶ result
|
|
7
|
+
* │ ╲
|
|
8
|
+
* │ ╲──tool error / failed recovery / unrecoverable result──▶ error ──retry──▶ waiting
|
|
9
|
+
* │ ╲
|
|
10
|
+
* │ ╲──stripped result the policy must not re-load──▶ error (blocked)
|
|
11
|
+
* │
|
|
12
|
+
* └──host cancelled──▶ cancelled (terminal: the user stopped it)
|
|
13
|
+
*
|
|
14
|
+
* `waiting` carries a `slow` flag once loading outlasts the slow-hint delay.
|
|
15
|
+
* An `isError` result (or a failed call) is an `error`, never an endless
|
|
16
|
+
* skeleton, and a retry re-runs the originating tool once on the user's click.
|
|
17
|
+
* A latched result that is no view on a host that names no originating tool
|
|
18
|
+
* is an `error` too: nothing could recover it, so waiting would never end.
|
|
19
|
+
* A `blocked` error is no failure: the tool ran, the host kept its result
|
|
20
|
+
* from the view, and the recovery policy forbids re-running it. It offers to
|
|
21
|
+
* load the view only for a tool the host does not report as writing, so one
|
|
22
|
+
* click never runs a write a second time (CI §9).
|
|
23
|
+
* Pure functions only: the hook in `use-view-lifecycle.ts` feeds them.
|
|
24
|
+
*/
|
|
25
|
+
import { parseToolResult } from "../lib/parse-tool-result.js"
|
|
26
|
+
|
|
27
|
+
/** `useToolContext().status` of the rendering call. */
|
|
28
|
+
export type RenderCallStatus = "pending" | "ready" | "error"
|
|
29
|
+
|
|
30
|
+
/** The user-triggered retry of the rendering call. */
|
|
31
|
+
export type RetryState =
|
|
32
|
+
| { status: "idle"; attempt: number }
|
|
33
|
+
| { status: "running"; attempt: number }
|
|
34
|
+
| { status: "failed"; attempt: number; reason: string | undefined }
|
|
35
|
+
|
|
36
|
+
export const RETRY_IDLE: RetryState = { status: "idle", attempt: 0 }
|
|
37
|
+
|
|
38
|
+
export type RetryAction =
|
|
39
|
+
{ type: "start" } | { type: "failed"; reason: string | undefined } | { type: "succeeded" }
|
|
40
|
+
|
|
41
|
+
/** start → running (next attempt); failed → failed with reason; succeeded → idle. */
|
|
42
|
+
export function retryReducer(state: RetryState, action: RetryAction): RetryState {
|
|
43
|
+
switch (action.type) {
|
|
44
|
+
case "start":
|
|
45
|
+
return { status: "running", attempt: state.attempt + 1 }
|
|
46
|
+
case "failed":
|
|
47
|
+
return { status: "failed", attempt: state.attempt, reason: action.reason }
|
|
48
|
+
case "succeeded":
|
|
49
|
+
return { status: "idle", attempt: state.attempt }
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface ViewLifecycleInput {
|
|
54
|
+
/** A renderable payload is in hand (host-delivered, recovered or retried). */
|
|
55
|
+
hasViewData: boolean
|
|
56
|
+
/** Status of the original rendering call. */
|
|
57
|
+
callStatus: RenderCallStatus
|
|
58
|
+
/** The host cancelled the original rendering call. */
|
|
59
|
+
cancelled: boolean
|
|
60
|
+
/** Message of the original call's error, when it failed. */
|
|
61
|
+
callErrorReason: string | undefined
|
|
62
|
+
/**
|
|
63
|
+
* The automatic stripped-result recovery ran and failed, or was needed but
|
|
64
|
+
* could not run because the host names no tool.
|
|
65
|
+
*/
|
|
66
|
+
recoveryFailed: boolean
|
|
67
|
+
/**
|
|
68
|
+
* The host did not pass the result to the view, and the recovery policy
|
|
69
|
+
* forbids re-running the tool: it ran, but the view never got its result.
|
|
70
|
+
* Default `false`.
|
|
71
|
+
*/
|
|
72
|
+
recoveryBlocked?: boolean
|
|
73
|
+
/**
|
|
74
|
+
* The host reports the originating tool as writing (`readOnlyHint: false`
|
|
75
|
+
* or `destructiveHint: true`). A blocked state then offers no retry: one
|
|
76
|
+
* click would run the write a second time. Default `false`.
|
|
77
|
+
*/
|
|
78
|
+
toolWrites?: boolean
|
|
79
|
+
/** The user-triggered retry. */
|
|
80
|
+
retryState: RetryState
|
|
81
|
+
/** Loading has outlasted the slow-hint delay. */
|
|
82
|
+
slow: boolean
|
|
83
|
+
/** The originating tool is known, so a retry can re-run it. */
|
|
84
|
+
canRetry: boolean
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* What an `error` phase stands for: `"failed"` (the call, the recovery or the
|
|
89
|
+
* retry failed) or `"blocked"` (the tool ran, but the host did not pass its
|
|
90
|
+
* result to the view, and the recovery policy keeps the view from re-running it).
|
|
91
|
+
*/
|
|
92
|
+
export type ViewErrorKind = "failed" | "blocked"
|
|
93
|
+
|
|
94
|
+
export type ViewLifecycle =
|
|
95
|
+
| { phase: "result" }
|
|
96
|
+
| { phase: "waiting"; slow: boolean }
|
|
97
|
+
| { phase: "cancelled" }
|
|
98
|
+
| {
|
|
99
|
+
phase: "error"
|
|
100
|
+
reason: string | undefined
|
|
101
|
+
canRetry: boolean
|
|
102
|
+
/** Absent for a failure; `"blocked"` for a result the view must not re-load. */
|
|
103
|
+
kind?: ViewErrorKind
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Resolve the single phase the view is in. Precedence: a payload always wins;
|
|
108
|
+
* a running retry is waiting; a failed retry is an error with ITS reason; a
|
|
109
|
+
* cancellation is terminal; a failed call is an error; a blocked recovery is
|
|
110
|
+
* a `blocked` error, retryable only for a tool not reported as writing; a
|
|
111
|
+
* failed recovery or a latched non-view result nothing can recover is an
|
|
112
|
+
* error; everything else is still waiting.
|
|
113
|
+
*/
|
|
114
|
+
export function resolveViewLifecycle(input: ViewLifecycleInput): ViewLifecycle {
|
|
115
|
+
if (input.hasViewData) return { phase: "result" }
|
|
116
|
+
const { retryState } = input
|
|
117
|
+
if (retryState.status === "running") return { phase: "waiting", slow: input.slow }
|
|
118
|
+
if (retryState.status === "failed") {
|
|
119
|
+
return { phase: "error", reason: retryState.reason, canRetry: input.canRetry }
|
|
120
|
+
}
|
|
121
|
+
if (input.cancelled) return { phase: "cancelled" }
|
|
122
|
+
if (input.callStatus === "error") {
|
|
123
|
+
return { phase: "error", reason: input.callErrorReason, canRetry: input.canRetry }
|
|
124
|
+
}
|
|
125
|
+
if (input.recoveryBlocked) {
|
|
126
|
+
const canRetry = input.canRetry && !input.toolWrites
|
|
127
|
+
return { phase: "error", reason: undefined, canRetry, kind: "blocked" }
|
|
128
|
+
}
|
|
129
|
+
if (input.recoveryFailed) return { phase: "error", reason: undefined, canRetry: input.canRetry }
|
|
130
|
+
// A latched result without a renderable view: the recovery re-runs the
|
|
131
|
+
// originating tool, so it can only help when the host names that tool.
|
|
132
|
+
if (input.callStatus === "ready" && !input.canRetry) {
|
|
133
|
+
return { phase: "error", reason: undefined, canRetry: false }
|
|
134
|
+
}
|
|
135
|
+
return { phase: "waiting", slow: input.slow }
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* The phase with its slow flag: a wait turns slow only while the wait that
|
|
140
|
+
* outlasted the delay (`slowWait`, a {@link waitKey}) is still the current one,
|
|
141
|
+
* so a new attempt starts without the hint.
|
|
142
|
+
*/
|
|
143
|
+
export function withSlowHint(
|
|
144
|
+
lifecycle: ViewLifecycle,
|
|
145
|
+
slowWait: string | null,
|
|
146
|
+
currentWait: string | null,
|
|
147
|
+
): ViewLifecycle {
|
|
148
|
+
return lifecycle.phase === "waiting" && slowWait !== null && slowWait === currentWait
|
|
149
|
+
? { phase: "waiting", slow: true }
|
|
150
|
+
: lifecycle
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Where keyboard focus goes after the user pressed retry (the button unmounts
|
|
155
|
+
* as the view starts waiting): the view frame while waiting, the new retry
|
|
156
|
+
* button when the retry failed, the frame again once the view or a
|
|
157
|
+
* cancellation replaced the state. `settled` ends the hand-off.
|
|
158
|
+
*/
|
|
159
|
+
export function retryFocusTarget(phase: ViewLifecycle["phase"]): {
|
|
160
|
+
target: "frame" | "retry"
|
|
161
|
+
settled: boolean
|
|
162
|
+
} {
|
|
163
|
+
if (phase === "waiting") return { target: "frame", settled: false }
|
|
164
|
+
if (phase === "error") return { target: "retry", settled: true }
|
|
165
|
+
return { target: "frame", settled: true }
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Identity of the current wait, so the slow-hint timer restarts for every new
|
|
170
|
+
* wait (the first load, then each retry attempt) and never for a finished one.
|
|
171
|
+
* `null` when the view is not waiting.
|
|
172
|
+
*/
|
|
173
|
+
export function waitKey(lifecycle: ViewLifecycle, retryState: RetryState): string | null {
|
|
174
|
+
return lifecycle.phase === "waiting" ? `${retryState.status}:${retryState.attempt}` : null
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** Default delay before the waiting placeholder adds its slow hint. */
|
|
178
|
+
export const DEFAULT_SLOW_HINT_MS = 8000
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* A decoded view call (a retry or a refresh), typed so a failure never turns
|
|
182
|
+
* into UI text: `isError` tells a tool error (with its raw `reason`, for the
|
|
183
|
+
* Details disclosure or the console) from a payload that is no view.
|
|
184
|
+
*/
|
|
185
|
+
export type ViewResultOutcome<T> =
|
|
186
|
+
{ ok: true; data: T } | { ok: false; isError: boolean; reason: string | undefined }
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Decode a view call's result: the payload when it is a complete view,
|
|
190
|
+
* otherwise a failure — a tool error with its `isError` text, or a payload
|
|
191
|
+
* the shell cannot render (no reason).
|
|
192
|
+
*/
|
|
193
|
+
export function decodeViewResult<T>(
|
|
194
|
+
result: unknown,
|
|
195
|
+
isValid: (value: unknown) => value is T,
|
|
196
|
+
): ViewResultOutcome<T> {
|
|
197
|
+
let decoded: unknown
|
|
198
|
+
try {
|
|
199
|
+
decoded = parseToolResult<unknown>(result)
|
|
200
|
+
} catch (err) {
|
|
201
|
+
return { ok: false, isError: true, reason: errorMessage(err) }
|
|
202
|
+
}
|
|
203
|
+
return isValid(decoded)
|
|
204
|
+
? { ok: true, data: decoded }
|
|
205
|
+
: { ok: false, isError: false, reason: undefined }
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** What {@link describeViewForModel} reports on. */
|
|
209
|
+
export interface ViewModelContextInput {
|
|
210
|
+
lifecycle: ViewLifecycle
|
|
211
|
+
/** The originating tool, when the host names it. */
|
|
212
|
+
toolName: string | undefined
|
|
213
|
+
/** The shown payload's failed data steps (result phase). */
|
|
214
|
+
stepErrors?: readonly { stepId: string; reason: string }[]
|
|
215
|
+
/** The step-error alert offers a retry (the view can refresh). */
|
|
216
|
+
stepsRetryable?: boolean
|
|
217
|
+
/**
|
|
218
|
+
* When the view re-loaded the shown payload itself (epoch ms, see
|
|
219
|
+
* {@link reloadedAtOf}); `null` for a payload the host delivered.
|
|
220
|
+
*/
|
|
221
|
+
reloadedAt?: number | null
|
|
222
|
+
/**
|
|
223
|
+
* How it re-loaded: it re-ran the tool because the host did not pass the
|
|
224
|
+
* result along (`"recovery"`, the default: its own recovery or the user's
|
|
225
|
+
* retry), the user refreshed the view (`"refresh"`), or the user applied an
|
|
226
|
+
* edit from the view builder, which ran the data steps again (`"builder"`).
|
|
227
|
+
*/
|
|
228
|
+
reloadedBy?: "recovery" | "refresh" | "builder"
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Where the shown payload came from: the host's tool result, the view's own
|
|
233
|
+
* re-execution when the host stripped it, the user's retry, a refresh, or the
|
|
234
|
+
* user's Apply in the view builder (its figures are the builder's own run of
|
|
235
|
+
* the data steps).
|
|
236
|
+
*/
|
|
237
|
+
export type ViewSource = "host" | "recovered" | "retry" | "refresh" | "builder"
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* When a payload counts as re-loaded by the view itself (N23): `at` when its
|
|
241
|
+
* figures can differ from the result the chat answered from, else `null`. A
|
|
242
|
+
* recovery, a refresh and a builder Apply always; a retry only after a call
|
|
243
|
+
* whose result reached the chat (a stripped or unusable one), never after a
|
|
244
|
+
* failed or cancelled call, of which the model never had figures. The host's
|
|
245
|
+
* own payload never.
|
|
246
|
+
*/
|
|
247
|
+
export function reloadedAtOf(source: ViewSource, at: number, callFailed: boolean): number | null {
|
|
248
|
+
if (source === "host") return null
|
|
249
|
+
if (source === "retry" && callFailed) return null
|
|
250
|
+
return at
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** How the view says, to the model, that it loaded the shown figures itself. */
|
|
254
|
+
const RELOADED_BY: Record<
|
|
255
|
+
NonNullable<ViewModelContextInput["reloadedBy"]>,
|
|
256
|
+
(at: string) => string
|
|
257
|
+
> = {
|
|
258
|
+
recovery: (at) =>
|
|
259
|
+
`re-ran the tool at ${at} because the host did not pass the original result to the view`,
|
|
260
|
+
refresh: (at) => `was refreshed by the user at ${at}`,
|
|
261
|
+
builder: (at) =>
|
|
262
|
+
`was edited by the user in the view builder, which loaded its data again at ${at}`,
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* The view's own line of model context (toolkit#176 K20): what failed and
|
|
267
|
+
* that the user sees an error instead of the view; a blocked recovery (the
|
|
268
|
+
* tool ran, its result never reached the view, and the model must not re-run
|
|
269
|
+
* it just to show the view); failed data steps; a payload the view re-loaded
|
|
270
|
+
* itself (a recovery, a retry, a refresh, a builder Apply), whose figures can
|
|
271
|
+
* differ from the tool result the model answered from (N23). `null` while
|
|
272
|
+
* there is nothing to report (waiting, or a plain host-delivered view).
|
|
273
|
+
* English: it is read by the model, never shown.
|
|
274
|
+
*/
|
|
275
|
+
export function describeViewForModel(input: ViewModelContextInput): string | null {
|
|
276
|
+
const subject = input.toolName ? `The view of ${input.toolName}` : "The view"
|
|
277
|
+
const { lifecycle } = input
|
|
278
|
+
if (lifecycle.phase === "error") {
|
|
279
|
+
if (lifecycle.kind === "blocked") {
|
|
280
|
+
return describeBlockedForModel(subject, input.toolName, lifecycle.canRetry)
|
|
281
|
+
}
|
|
282
|
+
const reason = lifecycle.reason ? ` (reason: ${lifecycle.reason})` : ""
|
|
283
|
+
const offer = lifecycle.canRetry ? "an error message with a retry button" : "an error message"
|
|
284
|
+
return `${subject} could not be shown${reason}. The user sees ${offer} instead of the view.`
|
|
285
|
+
}
|
|
286
|
+
if (lifecycle.phase === "cancelled") {
|
|
287
|
+
return `${subject} was cancelled before it rendered. The user sees a note that the call was cancelled.`
|
|
288
|
+
}
|
|
289
|
+
if (lifecycle.phase === "waiting") return null
|
|
290
|
+
const lines: string[] = []
|
|
291
|
+
if (input.reloadedAt != null) {
|
|
292
|
+
const at = new Date(input.reloadedAt).toISOString()
|
|
293
|
+
const how = RELOADED_BY[input.reloadedBy ?? "recovery"](at)
|
|
294
|
+
lines.push(
|
|
295
|
+
`${subject} ${how}; its figures are live as of then and can differ from the original result.`,
|
|
296
|
+
)
|
|
297
|
+
}
|
|
298
|
+
const failed = input.stepErrors ?? []
|
|
299
|
+
if (failed.length > 0) {
|
|
300
|
+
const steps = failed.map((e) => `${e.stepId}: ${e.reason}`).join("; ")
|
|
301
|
+
const lead = lines.length > 0 ? "Some" : `${subject} is shown, but some`
|
|
302
|
+
const offer = input.stepsRetryable ? "an alert with a retry button" : "an alert"
|
|
303
|
+
lines.push(`${lead} of its data could not be loaded (${steps}). The user sees ${offer}.`)
|
|
304
|
+
}
|
|
305
|
+
return lines.length > 0 ? lines.join(" ") : null
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* The model's line for a blocked recovery: the tool ran (nothing failed), the
|
|
310
|
+
* host kept its result from the view, the view did not re-run it, and the
|
|
311
|
+
* model must not re-run it just to show the view, a write least of all.
|
|
312
|
+
*/
|
|
313
|
+
function describeBlockedForModel(
|
|
314
|
+
subject: string,
|
|
315
|
+
toolName: string | undefined,
|
|
316
|
+
canRetry: boolean,
|
|
317
|
+
): string {
|
|
318
|
+
const tool = toolName ?? "the tool"
|
|
319
|
+
const offer = canRetry
|
|
320
|
+
? "a note with a button that loads the view"
|
|
321
|
+
: "a note to ask in the chat for the current state"
|
|
322
|
+
return (
|
|
323
|
+
`${subject} is not shown: ${tool} ran, but the host did not pass its result to the view, ` +
|
|
324
|
+
`and the view did not re-run it. The user sees ${offer} instead of the view. ` +
|
|
325
|
+
`Do not re-run ${tool} just to show the view.`
|
|
326
|
+
)
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/** A displayable message for anything thrown. */
|
|
330
|
+
export function errorMessage(err: unknown): string | undefined {
|
|
331
|
+
if (err instanceof Error) return err.message || undefined
|
|
332
|
+
if (typeof err === "string") return err || undefined
|
|
333
|
+
return undefined
|
|
334
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
import { useEffect } from "react"
|
|
3
|
+
import { afterEach, describe, expect, it } from "vitest"
|
|
4
|
+
import { useToast } from "../components/Toast.js"
|
|
5
|
+
import { TOOLKIT_MESSAGES } from "../locale/toolkit-messages.js"
|
|
6
|
+
import { renderWithEffects, type EffectRender } from "../render-effects.js"
|
|
7
|
+
import { WidgetFixtureHost } from "./widget-fixture.js"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The harness's toast stack inside the host's locale scope, with its effects
|
|
11
|
+
* running: a toast exists only after a widget's effect shows it, so the SSR
|
|
12
|
+
* suite (`widget-fixture.render.test.tsx`) never sees its words. Its one
|
|
13
|
+
* localized word is the dismiss button's name; it follows `globals.locale`
|
|
14
|
+
* like `McpAppView`'s stack does, which holds only while the `ToastProvider`
|
|
15
|
+
* sits inside the `ToolkitLocaleFallback`.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** A widget that reports a write's outcome as a toast once it mounts. */
|
|
19
|
+
function SavesOnMount() {
|
|
20
|
+
const toast = useToast()
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
toast.show({ tone: "success", title: "Saved" })
|
|
23
|
+
}, [toast])
|
|
24
|
+
return <p>widget</p>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let mounted: EffectRender | null = null
|
|
28
|
+
|
|
29
|
+
afterEach(async () => {
|
|
30
|
+
await mounted?.unmount()
|
|
31
|
+
mounted = null
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
/** The dismiss button's accessible name of the toast the widget raised. */
|
|
35
|
+
async function dismissLabel(globals?: Record<string, unknown>): Promise<string | null> {
|
|
36
|
+
mounted = await renderWithEffects(<WidgetFixtureHost widget={SavesOnMount} globals={globals} />)
|
|
37
|
+
const toast = mounted.container.querySelector('[data-slot="toast"]')
|
|
38
|
+
expect(toast?.textContent, "the widget's toast reached the stack").toContain("Saved")
|
|
39
|
+
return toast?.querySelector("button")?.getAttribute("aria-label") ?? null
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
describe("WidgetFixtureHost toast stack (effects)", () => {
|
|
43
|
+
it("names a toast's dismiss button in globals.locale", async () => {
|
|
44
|
+
expect(await dismissLabel({ locale: "de" })).toBe(TOOLKIT_MESSAGES.de.common.dismiss)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it("names it in English without a host locale", async () => {
|
|
48
|
+
expect(await dismissLabel()).toBe(TOOLKIT_MESSAGES.en.common.dismiss)
|
|
49
|
+
expect(TOOLKIT_MESSAGES.en.common.dismiss).not.toBe(TOOLKIT_MESSAGES.de.common.dismiss)
|
|
50
|
+
})
|
|
51
|
+
})
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { renderToStaticMarkup } from "react-dom/server"
|
|
2
|
+
import { describe, expect, it } from "vitest"
|
|
3
|
+
import { Check } from "lucide-react"
|
|
4
|
+
import { ListFooter } from "../components/ListFooter.js"
|
|
5
|
+
import { ICON_STROKE } from "../lib/icon-stroke.js"
|
|
6
|
+
import { LocaleProvider, useLocale } from "../locale/locale-provider.js"
|
|
7
|
+
import { useHostBridge } from "./host-bridge.js"
|
|
8
|
+
import { WidgetFixtureHost } from "./widget-fixture.js"
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The harness gives a widget what `McpAppView` gives it in a host, so a
|
|
12
|
+
* preview in the widget playground shows what a user sees: the toolkit's
|
|
13
|
+
* strings in the host locale (`globals.locale`), the host theme on the
|
|
14
|
+
* bridge (`globals.theme`), the CI icon stroke and a toast stack.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** A widget made of toolkit chrome: the list footer's text follows the toolkit locale. */
|
|
18
|
+
function Footer() {
|
|
19
|
+
return <ListFooter shown={8} total={1234} hasMore onLoadMore={() => {}} noun="Aufgaben" />
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const render = (globals?: Record<string, unknown>, widget = Footer) =>
|
|
23
|
+
renderToStaticMarkup(<WidgetFixtureHost widget={widget} globals={globals} />)
|
|
24
|
+
|
|
25
|
+
describe("WidgetFixtureHost host scope", () => {
|
|
26
|
+
it("renders the toolkit's strings and number format in globals.locale", () => {
|
|
27
|
+
const html = render({ locale: "de-DE" })
|
|
28
|
+
expect(html).toContain("Zeigt 8 von 1.234 Aufgaben")
|
|
29
|
+
expect(html).toContain("Mehr laden")
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it("defaults to English and falls back to it for a blank locale", () => {
|
|
33
|
+
for (const globals of [undefined, { locale: "" }, { locale: 42 }]) {
|
|
34
|
+
const html = render(globals)
|
|
35
|
+
expect(html).toContain("Showing 8 of 1,234 Aufgaben")
|
|
36
|
+
expect(html).toContain("Load more")
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it("lets a LocaleProvider around the harness win over globals.locale", () => {
|
|
41
|
+
const html = renderToStaticMarkup(
|
|
42
|
+
<LocaleProvider locale="de">
|
|
43
|
+
<WidgetFixtureHost widget={Footer} globals={{ locale: "en-US" }} />
|
|
44
|
+
</LocaleProvider>,
|
|
45
|
+
)
|
|
46
|
+
expect(html).toContain("Mehr laden")
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it("leaves useLocale() of the widget alone, like McpAppView does", () => {
|
|
50
|
+
function ShowsLocale() {
|
|
51
|
+
return <p>{useLocale()}</p>
|
|
52
|
+
}
|
|
53
|
+
expect(render({ locale: "de-DE" }, ShowsLocale)).toContain("<p>en</p>")
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it("reports globals.theme on the bridge", () => {
|
|
57
|
+
function ShowsTheme() {
|
|
58
|
+
return <p>{useHostBridge().theme}</p>
|
|
59
|
+
}
|
|
60
|
+
expect(render({ theme: "dark" }, ShowsTheme)).toContain("<p>dark</p>")
|
|
61
|
+
expect(render(undefined, ShowsTheme)).toContain("<p>light</p>")
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it("draws Lucide icons at the view's stroke", () => {
|
|
65
|
+
expect(render(undefined, () => <Check />)).toContain(`stroke-width="${ICON_STROKE}"`)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it("mounts the toast stack as the view's one polite live region", () => {
|
|
69
|
+
// Like McpAppView's: fixed attributes, no role and no name, so the first
|
|
70
|
+
// toast lands in a region the browser already tracks. Its words (the
|
|
71
|
+
// dismiss label in the host locale) need a toast, so effects:
|
|
72
|
+
// widget-fixture.effects.test.tsx.
|
|
73
|
+
const html = render()
|
|
74
|
+
const viewport = /<div data-slot="toast-viewport"[^>]*>/.exec(html)?.[0]
|
|
75
|
+
expect(viewport).toBeDefined()
|
|
76
|
+
expect(viewport).toContain('aria-live="polite"')
|
|
77
|
+
expect(viewport).toContain('aria-atomic="false"')
|
|
78
|
+
expect(viewport).not.toContain("aria-label")
|
|
79
|
+
expect(viewport).not.toContain("role=")
|
|
80
|
+
})
|
|
81
|
+
})
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { createContext, useContext, useMemo, type ComponentType, type ReactNode } from "react"
|
|
2
|
+
import { LucideProvider } from "lucide-react"
|
|
2
3
|
import type { PipelineContext, StepResult, WidgetProps } from "@miragon/mcp-toolkit-core"
|
|
4
|
+
import { ToastProvider } from "../components/Toast.js"
|
|
5
|
+
import { ICON_STROKE } from "../lib/icon-stroke.js"
|
|
6
|
+
import { ToolkitLocaleFallback } from "../locale/locale-provider.js"
|
|
3
7
|
import { AppQueryProvider } from "../providers/query-provider.js"
|
|
4
8
|
import { HostBridgeProvider, createStandaloneHostBridge } from "./host-bridge.js"
|
|
5
9
|
|
|
@@ -143,10 +147,10 @@ export function useFixtureHost(): FixtureHostValue | null {
|
|
|
143
147
|
}
|
|
144
148
|
|
|
145
149
|
/**
|
|
146
|
-
* Default host-context values the harness reports.
|
|
147
|
-
* the mcp-use 2.x move (the other host globals were consumed by
|
|
148
|
-
* `window.openai` shim); the object keeps its shape so existing
|
|
149
|
-
* setups that spread overrides keep compiling.
|
|
150
|
+
* Default host-context values the harness reports. `theme` and `locale` are
|
|
151
|
+
* read since the mcp-use 2.x move (the other host globals were consumed by
|
|
152
|
+
* the 1.x `window.openai` shim); the object keeps its shape so existing
|
|
153
|
+
* playground setups that spread overrides keep compiling.
|
|
150
154
|
*/
|
|
151
155
|
const DEFAULT_GLOBALS = {
|
|
152
156
|
theme: "light",
|
|
@@ -192,9 +196,12 @@ export interface WidgetFixtureHostProps {
|
|
|
192
196
|
*/
|
|
193
197
|
tools?: Record<string, FixtureToolEntry>
|
|
194
198
|
/**
|
|
195
|
-
* Host context overrides, merged over {@link DEFAULT_GLOBALS}.
|
|
196
|
-
*
|
|
197
|
-
*
|
|
199
|
+
* Host context overrides, merged over {@link DEFAULT_GLOBALS}. Two keys
|
|
200
|
+
* influence the simulated host: `theme` (`"light"` | `"dark"`, reported by
|
|
201
|
+
* `useHostBridge().theme`) and `locale` (a BCP 47 tag: the toolkit's own
|
|
202
|
+
* strings and number format follow it, exactly as under `McpAppView`; a
|
|
203
|
+
* `LocaleProvider` around or inside the widget still wins). The other keys
|
|
204
|
+
* are accepted for compatibility with existing playground setups.
|
|
198
205
|
*/
|
|
199
206
|
globals?: Record<string, unknown>
|
|
200
207
|
/**
|
|
@@ -247,6 +254,10 @@ export function buildFixtureWidgetProps(
|
|
|
247
254
|
* would (adapted widgets report through this bridge outside a real view).
|
|
248
255
|
* - **The toolkit `AppQueryProvider`** — so widgets that fetch via `useToolQuery`
|
|
249
256
|
* (e.g. generated `use*` hooks) resolve against the same registry.
|
|
257
|
+
* - **What `McpAppView` gives a widget** — the toolkit's strings in the host
|
|
258
|
+
* locale (`globals.locale`, as `McpAppView` does with the host's), the
|
|
259
|
+
* toast stack (`useToast()` shows real toasts) and the icon stroke of the
|
|
260
|
+
* view (2.5), so the preview matches what a host renders.
|
|
250
261
|
*
|
|
251
262
|
* ### Boundaries
|
|
252
263
|
* The harness simulates the **host-portable pattern** (`useHostBridge`,
|
|
@@ -282,10 +293,14 @@ export function WidgetFixtureHost({
|
|
|
282
293
|
// The standalone host bridge is the harness's single simulation source: it
|
|
283
294
|
// logs every verb through the `HostActionLog` channel for the playground's
|
|
284
295
|
// activity log. Depends on the callback props directly — a fresh Provider
|
|
285
|
-
// value on callback change is fine for a dev harness.
|
|
286
|
-
//
|
|
287
|
-
// `window.openai` shim).
|
|
296
|
+
// value on callback change is fine for a dev harness. `globals.theme` and
|
|
297
|
+
// `globals.locale` influence the simulated host (the remaining globals fed
|
|
298
|
+
// the 1.x `window.openai` shim).
|
|
288
299
|
const bridgeTheme = (globals?.theme ?? DEFAULT_GLOBALS.theme) === "dark" ? "dark" : "light"
|
|
300
|
+
const hostLocale =
|
|
301
|
+
typeof globals?.locale === "string" && globals.locale.trim() !== ""
|
|
302
|
+
? globals.locale
|
|
303
|
+
: DEFAULT_GLOBALS.locale
|
|
289
304
|
const bridge = useMemo(
|
|
290
305
|
() =>
|
|
291
306
|
createStandaloneHostBridge({
|
|
@@ -308,11 +323,19 @@ export function WidgetFixtureHost({
|
|
|
308
323
|
// widgets and raw `({ data })` widgets read their fixture from one mount.
|
|
309
324
|
const RenderWidget = Widget as ComponentType<Record<string, unknown>>
|
|
310
325
|
|
|
326
|
+
// The same scope `McpAppView` puts around a widget: the toolkit's strings
|
|
327
|
+
// in the host locale, the CI icon stroke and a toast stack.
|
|
311
328
|
return (
|
|
312
329
|
<FixtureHostContext.Provider value={value}>
|
|
313
330
|
<HostBridgeProvider bridge={bridge}>
|
|
314
331
|
<AppQueryProvider callTool={callTool}>
|
|
315
|
-
<
|
|
332
|
+
<ToolkitLocaleFallback locale={hostLocale}>
|
|
333
|
+
<LucideProvider strokeWidth={ICON_STROKE}>
|
|
334
|
+
<ToastProvider>
|
|
335
|
+
<RenderWidget {...widgetProps} data={data} />
|
|
336
|
+
</ToastProvider>
|
|
337
|
+
</LucideProvider>
|
|
338
|
+
</ToolkitLocaleFallback>
|
|
316
339
|
</AppQueryProvider>
|
|
317
340
|
</HostBridgeProvider>
|
|
318
341
|
</FixtureHostContext.Provider>
|