@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
|
@@ -1,41 +1,55 @@
|
|
|
1
|
-
import { useEffect,
|
|
2
|
-
import {
|
|
1
|
+
import { useEffect, useState, useSyncExternalStore } from "react"
|
|
2
|
+
import { installToolResultObserver, type ToolResultObserver } from "../lib/tool-result-observer.js"
|
|
3
|
+
import {
|
|
4
|
+
createRecoveryController,
|
|
5
|
+
type RecoveryPolicy,
|
|
6
|
+
type ToolResultRecoveryState,
|
|
7
|
+
} from "./tool-result-recovery-model.js"
|
|
8
|
+
|
|
9
|
+
export { decodeRecoveredResult } from "./tool-result-recovery-model.js"
|
|
10
|
+
export type {
|
|
11
|
+
RecoveryBlockReason,
|
|
12
|
+
RecoveryPolicy,
|
|
13
|
+
ToolResultRecoveryState,
|
|
14
|
+
ToolSafety,
|
|
15
|
+
} from "./tool-result-recovery-model.js"
|
|
3
16
|
|
|
4
17
|
/**
|
|
5
18
|
* Recovery for hosts that deliver the `ui/notifications/tool-result`
|
|
6
19
|
* notification without `structuredContent` (observed on claude.ai and Claude
|
|
7
|
-
* Desktop, which forward only the model-facing `content` blocks).
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* forever and the shell would idle on its loading skeleton.
|
|
20
|
+
* Desktop, which forward only the model-facing `content` blocks). mcp-use
|
|
21
|
+
* 2.x's `useToolContext` latch ignores such a content-only result, so its
|
|
22
|
+
* status stays `"pending"` forever and the shell would idle on its skeleton.
|
|
11
23
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* result has latched within
|
|
16
|
-
* {@link ToolResultRecoveryOptions.assumeStrippedAfterMs}, the result is
|
|
17
|
-
* assumed delivered-but-stripped. Either way the hook re-executes the
|
|
18
|
-
* originating tool once via the host bridge's official `callTool` — tool-call
|
|
19
|
-
* RESPONSES carry `structuredContent` intact on those hosts — and exposes the
|
|
20
|
-
* decoded payload. Spec-conforming hosts (e.g. the mcp-use inspector) deliver
|
|
21
|
-
* a valid payload up front, so the recovery never fires there.
|
|
24
|
+
* The hook re-executes the originating tool once via the host bridge's
|
|
25
|
+
* `callTool` — tool-call RESPONSES carry `structuredContent` intact on those
|
|
26
|
+
* hosts — and exposes the decoded payload. When it does so (toolkit#176):
|
|
22
27
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
28
|
+
* - **Only when the host stripped the result.** A passive observer of the
|
|
29
|
+
* host's lifecycle notifications (`installToolResultObserver`) sees the
|
|
30
|
+
* content-only result itself, so the recovery fires as it arrives, and a
|
|
31
|
+
* slow tool on a compliant host is never re-run (K13): no result has
|
|
32
|
+
* arrived because none was sent. Where the observer cannot vouch for that
|
|
33
|
+
* (no browser frame, or installed after the handshake), the 2.x grace timer
|
|
34
|
+
* ({@link ToolResultRecoveryOptions.assumeStrippedAfterMs}) still infers a
|
|
35
|
+
* stripped result, once the call's arguments arrived.
|
|
36
|
+
* - **With the call's complete arguments** (N21): the observer's complete
|
|
37
|
+
* `tool-input`, never a partial stream; an equal re-delivery is the same run.
|
|
38
|
+
* - **Never for a cancelled or failed call:** re-running repeats the failure
|
|
39
|
+
* or overrides the user's stop; the shell renders that state and offers an
|
|
40
|
+
* explicit retry.
|
|
41
|
+
* - **Only for a tool the policy allows** ({@link ToolResultRecoveryOptions.policy}):
|
|
42
|
+
* by default never one the host reports as writing (`readOnlyHint: false`
|
|
43
|
+
* or `destructiveHint: true` in `hostContext.toolInfo.tool.annotations`).
|
|
44
|
+
* A blocked recovery is reported (`blocked`) so the view can show its error.
|
|
27
45
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* repeat, and may rarely run concurrently with the original invocation when
|
|
31
|
-
* the grace timer beats a slow but conforming host.
|
|
46
|
+
* The decision and its timer live in `createRecoveryController`; this hook
|
|
47
|
+
* only binds it to React.
|
|
32
48
|
*/
|
|
33
49
|
|
|
34
50
|
/**
|
|
35
|
-
* Default grace period before a silent latch counts as a stripped result
|
|
36
|
-
*
|
|
37
|
-
* short enough that users on stripping hosts are not left staring at the
|
|
38
|
-
* skeleton noticeably.
|
|
51
|
+
* Default grace period before a silent latch counts as a stripped result —
|
|
52
|
+
* used only where the transport observer cannot vouch that nothing arrived.
|
|
39
53
|
*/
|
|
40
54
|
export const DEFAULT_ASSUME_STRIPPED_AFTER_MS = 2500
|
|
41
55
|
|
|
@@ -43,8 +57,7 @@ export interface ToolResultRecoveryOptions {
|
|
|
43
57
|
/**
|
|
44
58
|
* The latch fired (`useToolContext().status !== "pending"`) — a structured
|
|
45
59
|
* result, a tool error, or a host cancellation arrived. On hosts that strip
|
|
46
|
-
* `structuredContent` this never turns true
|
|
47
|
-
* that case.
|
|
60
|
+
* `structuredContent` this never turns true.
|
|
48
61
|
*/
|
|
49
62
|
resultReady: boolean
|
|
50
63
|
/**
|
|
@@ -53,12 +66,17 @@ export interface ToolResultRecoveryOptions {
|
|
|
53
66
|
* attempt already in flight is dropped. Defaults to `false`.
|
|
54
67
|
*/
|
|
55
68
|
cancelled?: boolean
|
|
56
|
-
/** The payload the host delivered (`useToolContext().toolOutput`). */
|
|
57
|
-
props: unknown
|
|
58
69
|
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
70
|
+
* The originating call latched a tool error (`useToolContext().status ===
|
|
71
|
+
* "error"` without a cancellation): an `isError` result or a failed call.
|
|
72
|
+
* Suppresses the recovery exactly like {@link cancelled} — the shell shows
|
|
73
|
+
* the error instead of silently re-running a call that just failed
|
|
74
|
+
* (toolkit#176). Defaults to `false`.
|
|
61
75
|
*/
|
|
76
|
+
errored?: boolean
|
|
77
|
+
/** The payload the host delivered (`useToolContext().toolOutput`). */
|
|
78
|
+
props: unknown
|
|
79
|
+
/** Validates a candidate payload. */
|
|
62
80
|
isValid: (value: unknown) => boolean
|
|
63
81
|
/** The tool arguments from `ui/notifications/tool-input` (`useToolContext().toolInput`). */
|
|
64
82
|
toolInput: Record<string, unknown> | null | undefined
|
|
@@ -68,23 +86,21 @@ export interface ToolResultRecoveryOptions {
|
|
|
68
86
|
callTool: (name: string, args: Record<string, unknown>) => Promise<unknown>
|
|
69
87
|
/**
|
|
70
88
|
* Grace period in ms after which a silent latch counts as a stripped
|
|
71
|
-
* result.
|
|
72
|
-
*
|
|
73
|
-
* arguments mean the invocation may not have finished yet. Defaults to
|
|
89
|
+
* result. Applies only where the transport observer cannot vouch that no
|
|
90
|
+
* result arrived (see above); restarts for new arguments. Defaults to
|
|
74
91
|
* {@link DEFAULT_ASSUME_STRIPPED_AFTER_MS}.
|
|
75
92
|
*/
|
|
76
93
|
assumeStrippedAfterMs?: number
|
|
94
|
+
/** Which tools may be re-executed on their own. Default `"auto"`. */
|
|
95
|
+
policy?: RecoveryPolicy
|
|
96
|
+
/**
|
|
97
|
+
* The transport observer. Defaults to the document's
|
|
98
|
+
* (`installToolResultObserver()`); `null` disables it, leaving the grace
|
|
99
|
+
* timer to decide alone (the 2.x behaviour).
|
|
100
|
+
*/
|
|
101
|
+
observer?: ToolResultObserver | null
|
|
77
102
|
}
|
|
78
103
|
|
|
79
|
-
export interface ToolResultRecoveryState<T> {
|
|
80
|
-
/** The recovered payload, `null` until the re-execution resolved (or when it failed). */
|
|
81
|
-
data: T | null
|
|
82
|
-
/** `true` when the single recovery attempt failed — the shell keeps its skeleton, no retry loop. */
|
|
83
|
-
failed: boolean
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const IDLE: ToolResultRecoveryState<never> = { data: null, failed: false }
|
|
87
|
-
|
|
88
104
|
/**
|
|
89
105
|
* Resolves the originating tool name from the host context's `toolInfo`.
|
|
90
106
|
* Reads the SEP-1865 shape first (`toolInfo.tool.name`), then the flattened
|
|
@@ -108,20 +124,19 @@ export function resolveRecoveryToolName(hostContext: unknown): string | undefine
|
|
|
108
124
|
}
|
|
109
125
|
|
|
110
126
|
/**
|
|
111
|
-
*
|
|
112
|
-
*
|
|
127
|
+
* Whether a latched or assumed-stripped result may be recovered at all: the
|
|
128
|
+
* call was neither cancelled nor failed. Kept for existing imports; the full
|
|
129
|
+
* rule (observer, policy, arguments) is `decideRecovery`.
|
|
130
|
+
* @deprecated Use `decideRecovery` from the recovery model.
|
|
113
131
|
*/
|
|
114
|
-
export function
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
return null
|
|
123
|
-
}
|
|
124
|
-
return decoded !== null && isValid(decoded) ? (decoded as T) : null
|
|
132
|
+
export function isRecoveryReady(state: {
|
|
133
|
+
resultReady: boolean
|
|
134
|
+
assumedReady: boolean
|
|
135
|
+
cancelled: boolean
|
|
136
|
+
errored: boolean
|
|
137
|
+
}): boolean {
|
|
138
|
+
if (state.cancelled || state.errored) return false
|
|
139
|
+
return state.resultReady || state.assumedReady
|
|
125
140
|
}
|
|
126
141
|
|
|
127
142
|
export function useToolResultRecovery<T>(
|
|
@@ -130,86 +145,41 @@ export function useToolResultRecovery<T>(
|
|
|
130
145
|
const {
|
|
131
146
|
resultReady,
|
|
132
147
|
cancelled = false,
|
|
148
|
+
errored = false,
|
|
133
149
|
props,
|
|
134
150
|
isValid,
|
|
135
151
|
toolInput,
|
|
136
152
|
hostContext,
|
|
137
153
|
callTool,
|
|
138
154
|
assumeStrippedAfterMs = DEFAULT_ASSUME_STRIPPED_AFTER_MS,
|
|
155
|
+
policy = "auto",
|
|
156
|
+
observer,
|
|
139
157
|
} = options
|
|
140
|
-
|
|
141
|
-
//
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
const toolName = resolveRecoveryToolName(hostContext)
|
|
148
|
-
|
|
149
|
-
// A stripped result is unobservable (the 2.x latch ignores content-only
|
|
150
|
-
// results), so infer it: once a recovery could fire but the latch has been
|
|
151
|
-
// silent for the grace period, treat the result as delivered-but-stripped.
|
|
152
|
-
// Re-armed on every toolInput identity change — streaming arguments mean
|
|
153
|
-
// the invocation may still be running, so the grace period starts over.
|
|
154
|
-
// The re-arm is a render-phase reset (not an effect) so the cascading-
|
|
155
|
-
// render lint stays satisfied; the timer effect below restarts anyway
|
|
156
|
-
// because toolInput is one of its dependencies.
|
|
157
|
-
const [assumedReady, setAssumedReady] = useState(false)
|
|
158
|
-
const [prevTimerInput, setPrevTimerInput] = useState<unknown>(toolInput)
|
|
159
|
-
if (prevTimerInput !== toolInput) {
|
|
160
|
-
setPrevTimerInput(toolInput)
|
|
161
|
-
if (assumedReady) setAssumedReady(false)
|
|
162
|
-
}
|
|
158
|
+
// One controller per mount; it survives StrictMode's effect re-run, so an
|
|
159
|
+
// in-flight call is never fired twice.
|
|
160
|
+
const [controller] = useState(() =>
|
|
161
|
+
createRecoveryController<T>({
|
|
162
|
+
observer: observer === undefined ? installToolResultObserver() : observer,
|
|
163
|
+
}),
|
|
164
|
+
)
|
|
163
165
|
useEffect(() => {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}, [resultReady, cancelled, toolName, toolInput, assumeStrippedAfterMs])
|
|
168
|
-
|
|
169
|
-
// A cancelled call never counts as ready, so the effect below invalidates
|
|
170
|
-
// any in-flight attempt the same way it does for a superseded run.
|
|
171
|
-
const ready = !cancelled && (resultReady || assumedReady)
|
|
172
|
-
|
|
166
|
+
controller.start()
|
|
167
|
+
return () => controller.stop()
|
|
168
|
+
}, [controller])
|
|
173
169
|
useEffect(() => {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
flightRef.current = (async (): Promise<ToolResultRecoveryState<T>> => {
|
|
187
|
-
try {
|
|
188
|
-
const result = await callTool(toolName, toolInput ?? {})
|
|
189
|
-
const data = decodeRecoveredResult<T>(result, isValid)
|
|
190
|
-
if (data !== null) return { data, failed: false }
|
|
191
|
-
console.warn(
|
|
192
|
-
`[mcp-toolkit] tool-result recovery: re-executing "${toolName}" returned no usable payload`,
|
|
193
|
-
)
|
|
194
|
-
return { data: null, failed: true }
|
|
195
|
-
} catch (err) {
|
|
196
|
-
console.warn(`[mcp-toolkit] tool-result recovery for "${toolName}" failed:`, err)
|
|
197
|
-
return { data: null, failed: true }
|
|
198
|
-
}
|
|
199
|
-
})()
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const flight = flightRef.current
|
|
203
|
-
let stale = false
|
|
204
|
-
void flight.then((outcome) => {
|
|
205
|
-
// Drop the outcome when this subscription was superseded or the flight
|
|
206
|
-
// was invalidated by a newer tool run.
|
|
207
|
-
if (!stale && flightRef.current === flight) setState(outcome)
|
|
170
|
+
controller.update({
|
|
171
|
+
resultReady,
|
|
172
|
+
cancelled,
|
|
173
|
+
errored,
|
|
174
|
+
props,
|
|
175
|
+
isValid,
|
|
176
|
+
toolInput,
|
|
177
|
+
hostContext,
|
|
178
|
+
callTool,
|
|
179
|
+
policy,
|
|
180
|
+
graceMs: assumeStrippedAfterMs,
|
|
181
|
+
resolveToolName: resolveRecoveryToolName,
|
|
208
182
|
})
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
}
|
|
212
|
-
}, [ready, props, isValid, toolName, toolInput, callTool])
|
|
213
|
-
|
|
214
|
-
return state
|
|
183
|
+
})
|
|
184
|
+
return useSyncExternalStore(controller.subscribe, controller.getState, controller.getState)
|
|
215
185
|
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { QueryClient } from "@tanstack/react-query"
|
|
2
|
+
import { renderToStaticMarkup } from "react-dom/server"
|
|
3
|
+
import { describe, expect, it, vi } from "vitest"
|
|
4
|
+
import { toolQueryKey } from "../lib/tool-query.js"
|
|
5
|
+
import { AppQueryProvider } from "../providers/query-provider.js"
|
|
6
|
+
import { useViewData, type UseViewDataOptions, type ViewDataResult } from "./use-view-data.js"
|
|
7
|
+
|
|
8
|
+
type Demo = { n: number }
|
|
9
|
+
type CallTool = (name: string, args: object) => Promise<unknown>
|
|
10
|
+
|
|
11
|
+
const KEY = ["use-view-data.test"] as const
|
|
12
|
+
const ARGS = { id: 1 }
|
|
13
|
+
|
|
14
|
+
/** Renders a probe once (SSR, the house path) and hands back the hook's result. */
|
|
15
|
+
function probe(
|
|
16
|
+
initialData: Demo | null,
|
|
17
|
+
ready: boolean,
|
|
18
|
+
callTool: CallTool,
|
|
19
|
+
key: string,
|
|
20
|
+
options?: UseViewDataOptions<Demo> & { client?: QueryClient; toolNameInQueryKey?: boolean },
|
|
21
|
+
): ViewDataResult<Demo> {
|
|
22
|
+
let result: ViewDataResult<Demo> | undefined
|
|
23
|
+
const { client, toolNameInQueryKey, ...hookOptions } = options ?? {}
|
|
24
|
+
function Probe() {
|
|
25
|
+
result = useViewData<Demo>(initialData, [...KEY, key], "demo_data", ARGS, ready, hookOptions)
|
|
26
|
+
return null
|
|
27
|
+
}
|
|
28
|
+
renderToStaticMarkup(
|
|
29
|
+
<AppQueryProvider callTool={callTool} client={client} toolNameInQueryKey={toolNameInQueryKey}>
|
|
30
|
+
<Probe />
|
|
31
|
+
</AppQueryProvider>,
|
|
32
|
+
)
|
|
33
|
+
if (!result) throw new Error("probe did not render")
|
|
34
|
+
return result
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
describe("useViewData", () => {
|
|
38
|
+
it("returns host-pushed data verbatim without fetching", () => {
|
|
39
|
+
const callTool = vi.fn<CallTool>(() => Promise.resolve({}))
|
|
40
|
+
const result = probe({ n: 1 }, true, callTool, "pushed")
|
|
41
|
+
expect(result.data).toEqual({ n: 1 })
|
|
42
|
+
expect(result.loading).toBe(false)
|
|
43
|
+
expect(result.error).toBeNull()
|
|
44
|
+
expect(callTool).not.toHaveBeenCalled()
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it("is not loading before it is ready", () => {
|
|
48
|
+
const result = probe(null, false, vi.fn<CallTool>(), "not-ready")
|
|
49
|
+
expect(result).toMatchObject({ data: null, loading: false, error: null })
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it("exposes the query's progress: failureCount, failureReason, isFetching, isPlaceholderData", () => {
|
|
53
|
+
const result = probe(null, false, vi.fn<CallTool>(), "progress")
|
|
54
|
+
expect(result).toMatchObject({
|
|
55
|
+
failureCount: 0,
|
|
56
|
+
failureReason: null,
|
|
57
|
+
isFetching: false,
|
|
58
|
+
isPlaceholderData: false,
|
|
59
|
+
})
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it("refetch calls the tool again: the retry an error state offers", async () => {
|
|
63
|
+
const callTool = vi.fn<CallTool>(() => Promise.resolve({ structuredContent: { n: 2 } }))
|
|
64
|
+
const result = probe(null, false, callTool, "refetch")
|
|
65
|
+
expect(callTool).not.toHaveBeenCalled()
|
|
66
|
+
result.refetch()
|
|
67
|
+
await vi.waitFor(() => {
|
|
68
|
+
expect(callTool).toHaveBeenCalledWith("demo_data", { id: 1 })
|
|
69
|
+
})
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it("enabled: false gates the feed next to ready; a ready feed without data is loading", () => {
|
|
73
|
+
const client = new QueryClient()
|
|
74
|
+
expect(probe(null, true, vi.fn<CallTool>(), "gated", { client, enabled: false }).loading).toBe(
|
|
75
|
+
false,
|
|
76
|
+
)
|
|
77
|
+
expect(probe(null, true, vi.fn<CallTool>(), "open", { client }).loading).toBe(true)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it("a function gate that says no idles like enabled: false, never an endless skeleton", () => {
|
|
81
|
+
const client = new QueryClient()
|
|
82
|
+
const closed = probe(null, true, vi.fn<CallTool>(), "fn-closed", {
|
|
83
|
+
client,
|
|
84
|
+
enabled: () => false,
|
|
85
|
+
})
|
|
86
|
+
expect(closed).toMatchObject({ data: null, loading: false, error: null })
|
|
87
|
+
const open = probe(null, true, vi.fn<CallTool>(), "fn-open", { client, enabled: () => true })
|
|
88
|
+
expect(open.loading).toBe(true)
|
|
89
|
+
// Not ready keeps it off whatever the gate says.
|
|
90
|
+
expect(
|
|
91
|
+
probe(null, false, vi.fn<CallTool>(), "fn-not-ready", { client, enabled: () => true })
|
|
92
|
+
.loading,
|
|
93
|
+
).toBe(false)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it("passes TanStack's options through: initialData-free placeholderData shows meanwhile", () => {
|
|
97
|
+
const client = new QueryClient()
|
|
98
|
+
const result = probe(null, true, vi.fn<CallTool>(), "placeholder", {
|
|
99
|
+
client,
|
|
100
|
+
placeholderData: { n: 0 },
|
|
101
|
+
})
|
|
102
|
+
expect(result).toMatchObject({ data: { n: 0 }, isPlaceholderData: true, loading: false })
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
describe('useViewData initialDataMode: "seed" (the live standalone view)', () => {
|
|
107
|
+
const cacheKey = (key: string) => toolQueryKey([...KEY, key], "demo_data", ARGS)
|
|
108
|
+
|
|
109
|
+
it("seeds the feed's cache entry with the host's payload and returns it", () => {
|
|
110
|
+
const client = new QueryClient()
|
|
111
|
+
const result = probe({ n: 1 }, true, vi.fn<CallTool>(), "seeded", {
|
|
112
|
+
client,
|
|
113
|
+
initialDataMode: "seed",
|
|
114
|
+
initialDataUpdatedAt: 1234,
|
|
115
|
+
})
|
|
116
|
+
expect(result).toMatchObject({ data: { n: 1 }, loading: false })
|
|
117
|
+
expect(client.getQueryData(cacheKey("seeded"))).toEqual({ n: 1 })
|
|
118
|
+
expect(client.getQueryState(cacheKey("seeded"))?.dataUpdatedAt).toBe(1234)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it("with toolNameInQueryKey the seed lands under the key carrying the tool name", () => {
|
|
122
|
+
const client = new QueryClient()
|
|
123
|
+
const result = probe({ n: 1 }, true, vi.fn<CallTool>(), "tool-key", {
|
|
124
|
+
client,
|
|
125
|
+
initialDataMode: "seed",
|
|
126
|
+
toolNameInQueryKey: true,
|
|
127
|
+
})
|
|
128
|
+
expect(result.data).toEqual({ n: 1 })
|
|
129
|
+
expect(client.getQueryData([...KEY, "tool-key", "demo_data", ARGS])).toEqual({ n: 1 })
|
|
130
|
+
expect(client.getQueryData(cacheKey("tool-key")), "never the 2.x key").toBeUndefined()
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it("static mode never writes the cache", () => {
|
|
134
|
+
const client = new QueryClient()
|
|
135
|
+
probe({ n: 1 }, true, vi.fn<CallTool>(), "static", { client })
|
|
136
|
+
expect(client.getQueryData(cacheKey("static"))).toBeUndefined()
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
it("the cached answer wins over the seed it came from once the feed answered anew", () => {
|
|
140
|
+
const client = new QueryClient()
|
|
141
|
+
probe({ n: 1 }, true, vi.fn<CallTool>(), "refetched", { client, initialDataMode: "seed" })
|
|
142
|
+
client.setQueryData(cacheKey("refetched"), { n: 2 })
|
|
143
|
+
const remounted = probe({ n: 1 }, true, vi.fn<CallTool>(), "refetched", {
|
|
144
|
+
client,
|
|
145
|
+
initialDataMode: "seed",
|
|
146
|
+
initialDataUpdatedAt: () => 99,
|
|
147
|
+
})
|
|
148
|
+
expect(remounted.data, "an old payload never rolls the answer back").toEqual({ n: 2 })
|
|
149
|
+
const delivered = probe({ n: 3 }, true, vi.fn<CallTool>(), "refetched", {
|
|
150
|
+
client,
|
|
151
|
+
initialDataMode: "seed",
|
|
152
|
+
})
|
|
153
|
+
expect(delivered.data, "a new delivery wins").toEqual({ n: 3 })
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
it("refetch over a seeded entry reads the feed again", async () => {
|
|
157
|
+
const client = new QueryClient()
|
|
158
|
+
const callTool = vi.fn<CallTool>(() => Promise.resolve({ structuredContent: { n: 2 } }))
|
|
159
|
+
const result = probe({ n: 1 }, true, callTool, "seed-refetch", {
|
|
160
|
+
client,
|
|
161
|
+
initialDataMode: "seed",
|
|
162
|
+
})
|
|
163
|
+
result.refetch()
|
|
164
|
+
await vi.waitFor(() => {
|
|
165
|
+
expect(client.getQueryData(cacheKey("seed-refetch"))).toEqual({ n: 2 })
|
|
166
|
+
})
|
|
167
|
+
expect(callTool).toHaveBeenCalledWith("demo_data", ARGS)
|
|
168
|
+
})
|
|
169
|
+
})
|
|
@@ -1,22 +1,74 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useCallback, useState } from "react"
|
|
2
|
+
import { hashKey, useQueryClient } from "@tanstack/react-query"
|
|
3
|
+
import { seedToolQuery, type UseToolQueryOptions } from "../lib/tool-query.js"
|
|
4
|
+
import { useToolQuery, useToolQueryKey } from "./use-tool-query.js"
|
|
2
5
|
|
|
3
6
|
export interface ViewDataResult<T> {
|
|
4
7
|
data: T | null
|
|
5
8
|
/** True while self-fetching with no data yet — the first paint of a cockpit cell. */
|
|
6
9
|
loading: boolean
|
|
7
10
|
error: Error | null
|
|
11
|
+
/**
|
|
12
|
+
* Fetch `tool` again now: the retry of an error state
|
|
13
|
+
* (`<ErrorState onRetry={refetch} … />`) or a manual refresh — a new read
|
|
14
|
+
* even while a failed fetch still waits on its retry backoff. In `"static"`
|
|
15
|
+
* mode host-pushed data, when present, stays what `data` returns.
|
|
16
|
+
*/
|
|
17
|
+
refetch: () => void
|
|
18
|
+
/** Failed attempts of the current fetch (0 once it succeeds). */
|
|
19
|
+
failureCount: number
|
|
20
|
+
/**
|
|
21
|
+
* The current fetch's last failed attempt — set from the first failure on,
|
|
22
|
+
* while the query still retries (`error` waits for the last retry).
|
|
23
|
+
*/
|
|
24
|
+
failureReason: Error | null
|
|
25
|
+
/** A fetch is in flight (the first one or a refetch). */
|
|
26
|
+
isFetching: boolean
|
|
27
|
+
/**
|
|
28
|
+
* `data` is a placeholder (`placeholderData`, e.g. `keepPreviousData`):
|
|
29
|
+
* the previous args' answer while the current args' fetch runs or failed.
|
|
30
|
+
*/
|
|
31
|
+
isPlaceholderData: boolean
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* How {@link useViewData} treats host-pushed `initialData`.
|
|
36
|
+
*
|
|
37
|
+
* - `"static"` (default): returned verbatim; the feed query stays off while
|
|
38
|
+
* it is present.
|
|
39
|
+
* - `"seed"`: it becomes the feed query's cached answer, and the query stays
|
|
40
|
+
* live (once `ready`): an invalidation after a write, a `refetch` or a
|
|
41
|
+
* retry re-reads the feed. A fresh seed is not refetched on mount (it is
|
|
42
|
+
* younger than `staleTime`); a payload delivered anew (content not seeded
|
|
43
|
+
* for the key before) replaces the cached answer, while a re-rendered or
|
|
44
|
+
* remounted old one never rolls a refetched answer back.
|
|
45
|
+
*/
|
|
46
|
+
export type ViewDataMode = "static" | "seed"
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Options of {@link useViewData}: TanStack's query options for the feed
|
|
50
|
+
* (`retry`, `retryDelay`, `staleTime`, `placeholderData` /
|
|
51
|
+
* `keepPreviousData`, `enabled` as an extra gate next to `ready`, …),
|
|
52
|
+
* plus how host data seeds it.
|
|
53
|
+
*/
|
|
54
|
+
export type UseViewDataOptions<T> = Omit<UseToolQueryOptions<T>, "initialData" | "select"> & {
|
|
55
|
+
initialDataMode?: ViewDataMode
|
|
8
56
|
}
|
|
9
57
|
|
|
10
58
|
/**
|
|
11
59
|
* The seam that lets one widget component serve both modes without a duplicate UI.
|
|
12
60
|
*
|
|
13
61
|
* Standalone (a `*_show_*` widget) the agent's tool result is handed in via
|
|
14
|
-
* `initialData` (from the view's stepData, e.g. through `adaptDataWidget`)
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
62
|
+
* `initialData` (from the view's stepData, e.g. through `adaptDataWidget`).
|
|
63
|
+
* Embedded in a self-fetching cockpit only scope params are passed, so the
|
|
64
|
+
* component fetches `tool` with `args` under `key`, gated on `ready` (i.e.
|
|
65
|
+
* the required id/key is present). Sibling widgets sharing `key` and `args`
|
|
66
|
+
* dedupe to a single call, so a composed view of N widgets over one data
|
|
67
|
+
* feed fetches once.
|
|
68
|
+
*
|
|
69
|
+
* By default host data is returned verbatim and switches the query off;
|
|
70
|
+
* `initialDataMode: "seed"` keeps the query live over it instead (see
|
|
71
|
+
* {@link ViewDataMode}). `options` passes TanStack's query options through.
|
|
20
72
|
*/
|
|
21
73
|
export function useViewData<T>(
|
|
22
74
|
initialData: T | null | undefined,
|
|
@@ -24,8 +76,77 @@ export function useViewData<T>(
|
|
|
24
76
|
tool: string,
|
|
25
77
|
args: Record<string, unknown>,
|
|
26
78
|
ready: boolean,
|
|
79
|
+
options?: UseViewDataOptions<T>,
|
|
27
80
|
): ViewDataResult<T> {
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
|
|
81
|
+
const { initialDataMode = "static", enabled, ...queryOptions } = options ?? {}
|
|
82
|
+
const seeding = initialDataMode === "seed"
|
|
83
|
+
const cacheKey = useStableKey(useToolQueryKey(key, tool, args))
|
|
84
|
+
useSeed(seeding ? initialData : null, cacheKey, queryOptions.initialDataUpdatedAt)
|
|
85
|
+
|
|
86
|
+
const active = ready && enabled !== false
|
|
87
|
+
const fetches = active && (seeding || !initialData)
|
|
88
|
+
const query = useToolQuery<T>([...key], tool, args, {
|
|
89
|
+
...queryOptions,
|
|
90
|
+
enabled: typeof enabled === "function" ? (q) => fetches && enabled(q) : fetches,
|
|
91
|
+
})
|
|
92
|
+
const data = seeding ? (query.data ?? initialData) : (initialData ?? query.data)
|
|
93
|
+
const refetch = useFreshRefetch(cacheKey, query.refetch)
|
|
94
|
+
return {
|
|
95
|
+
data: data ?? null,
|
|
96
|
+
// The query's own verdict: a function `enabled` that returns false idles
|
|
97
|
+
// like `enabled: false`, never an endless skeleton.
|
|
98
|
+
loading: !data && query.isEnabled && !query.isError,
|
|
99
|
+
error: query.error,
|
|
100
|
+
refetch,
|
|
101
|
+
failureCount: query.failureCount,
|
|
102
|
+
failureReason: query.failureReason,
|
|
103
|
+
isFetching: query.isFetching,
|
|
104
|
+
isPlaceholderData: query.isPlaceholderData,
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** `key`, kept by content: callers build `key`/`args` anew every render. */
|
|
109
|
+
function useStableKey(key: unknown[]): unknown[] {
|
|
110
|
+
const id = hashKey(key)
|
|
111
|
+
const [stable, setStable] = useState({ id, key })
|
|
112
|
+
if (stable.id !== id) setStable({ id, key })
|
|
113
|
+
return stable.id === id ? stable.key : key
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* `"seed"` mode: write the host's payload into the feed's cache entry in the
|
|
118
|
+
* render phase, before the query's observer reads the cache — what TanStack's
|
|
119
|
+
* own `initialData` does (see {@link seedToolQuery} for when it wins over a
|
|
120
|
+
* cached answer). The last processed seed skips the content check while the
|
|
121
|
+
* same object comes in render after render.
|
|
122
|
+
*/
|
|
123
|
+
function useSeed(
|
|
124
|
+
seed: unknown,
|
|
125
|
+
cacheKey: unknown[],
|
|
126
|
+
updatedAt: number | (() => number | undefined) | undefined,
|
|
127
|
+
): void {
|
|
128
|
+
const client = useQueryClient()
|
|
129
|
+
const keyId = hashKey(cacheKey)
|
|
130
|
+
const [seen, setSeen] = useState<{ seed: unknown; keyId: string } | null>(null)
|
|
131
|
+
if (seed == null || (seen?.seed === seed && seen.keyId === keyId)) return
|
|
132
|
+
const at = typeof updatedAt === "function" ? updatedAt() : updatedAt
|
|
133
|
+
const written = seedToolQuery(client, cacheKey, seed, at)
|
|
134
|
+
// Only a real change re-renders: an equal new object per render must not loop.
|
|
135
|
+
if (written || seen?.keyId !== keyId) setSeen({ seed, keyId })
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* A refetch that reads anew at once. TanStack restarts an in-flight fetch only
|
|
140
|
+
* over data (`cancelRefetch`); a key without data joins the running retry
|
|
141
|
+
* chain and waits out its backoff. So cancel that chain first, then read.
|
|
142
|
+
*/
|
|
143
|
+
function useFreshRefetch(cacheKey: unknown[], refetchQuery: () => Promise<unknown>): () => void {
|
|
144
|
+
const client = useQueryClient()
|
|
145
|
+
return useCallback(() => {
|
|
146
|
+
if (client.getQueryData(cacheKey) !== undefined) {
|
|
147
|
+
void refetchQuery()
|
|
148
|
+
return
|
|
149
|
+
}
|
|
150
|
+
void client.cancelQueries({ queryKey: cacheKey, exact: true }).then(() => refetchQuery())
|
|
151
|
+
}, [client, cacheKey, refetchQuery])
|
|
31
152
|
}
|