@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.
Files changed (511) hide show
  1. package/README.md +14 -5
  2. package/dist/app/adapt-data-widget.d.ts +1 -22
  3. package/dist/app/adapt-data-widget.d.ts.map +1 -1
  4. package/dist/app/adapt-data-widget.js +1 -35
  5. package/dist/app/adapt-data-widget.js.map +1 -1
  6. package/dist/app/builder/BuilderBanners.d.ts +7 -2
  7. package/dist/app/builder/BuilderBanners.d.ts.map +1 -1
  8. package/dist/app/builder/BuilderBanners.js +8 -3
  9. package/dist/app/builder/BuilderBanners.js.map +1 -1
  10. package/dist/app/builder/CanvasRow.d.ts +4 -2
  11. package/dist/app/builder/CanvasRow.d.ts.map +1 -1
  12. package/dist/app/builder/CanvasRow.js +29 -17
  13. package/dist/app/builder/CanvasRow.js.map +1 -1
  14. package/dist/app/builder/CatalogueSheet.d.ts.map +1 -1
  15. package/dist/app/builder/CatalogueSheet.js +14 -6
  16. package/dist/app/builder/CatalogueSheet.js.map +1 -1
  17. package/dist/app/builder/CellPropsSheet.d.ts +7 -3
  18. package/dist/app/builder/CellPropsSheet.d.ts.map +1 -1
  19. package/dist/app/builder/CellPropsSheet.js +10 -3
  20. package/dist/app/builder/CellPropsSheet.js.map +1 -1
  21. package/dist/app/builder/DiscardDialog.d.ts +18 -0
  22. package/dist/app/builder/DiscardDialog.d.ts.map +1 -0
  23. package/dist/app/builder/DiscardDialog.js +18 -0
  24. package/dist/app/builder/DiscardDialog.js.map +1 -0
  25. package/dist/app/builder/EmptyHint.d.ts +4 -0
  26. package/dist/app/builder/EmptyHint.d.ts.map +1 -1
  27. package/dist/app/builder/EmptyHint.js +6 -1
  28. package/dist/app/builder/EmptyHint.js.map +1 -1
  29. package/dist/app/builder/LayoutSurface.d.ts +33 -9
  30. package/dist/app/builder/LayoutSurface.d.ts.map +1 -1
  31. package/dist/app/builder/LayoutSurface.js +74 -23
  32. package/dist/app/builder/LayoutSurface.js.map +1 -1
  33. package/dist/app/builder/PipelineStrip.d.ts +5 -2
  34. package/dist/app/builder/PipelineStrip.d.ts.map +1 -1
  35. package/dist/app/builder/PipelineStrip.js +33 -15
  36. package/dist/app/builder/PipelineStrip.js.map +1 -1
  37. package/dist/app/builder/SaveDialog.d.ts +9 -1
  38. package/dist/app/builder/SaveDialog.d.ts.map +1 -1
  39. package/dist/app/builder/SaveDialog.js +9 -3
  40. package/dist/app/builder/SaveDialog.js.map +1 -1
  41. package/dist/app/builder/Toolbar.d.ts +14 -4
  42. package/dist/app/builder/Toolbar.d.ts.map +1 -1
  43. package/dist/app/builder/Toolbar.js +24 -7
  44. package/dist/app/builder/Toolbar.js.map +1 -1
  45. package/dist/app/builder/Workspace.d.ts +23 -3
  46. package/dist/app/builder/Workspace.d.ts.map +1 -1
  47. package/dist/app/builder/Workspace.js +58 -8
  48. package/dist/app/builder/Workspace.js.map +1 -1
  49. package/dist/app/builder/builder-model.d.ts +146 -3
  50. package/dist/app/builder/builder-model.d.ts.map +1 -1
  51. package/dist/app/builder/builder-model.js +196 -5
  52. package/dist/app/builder/builder-model.js.map +1 -1
  53. package/dist/app/builder/labels.d.ts +13 -62
  54. package/dist/app/builder/labels.d.ts.map +1 -1
  55. package/dist/app/builder/labels.js +9 -58
  56. package/dist/app/builder/labels.js.map +1 -1
  57. package/dist/app/builder/reveal.d.ts +10 -0
  58. package/dist/app/builder/reveal.d.ts.map +1 -0
  59. package/dist/app/builder/reveal.js +10 -0
  60. package/dist/app/builder/reveal.js.map +1 -0
  61. package/dist/app/builder/status-chip.d.ts.map +1 -1
  62. package/dist/app/builder/status-chip.js +3 -2
  63. package/dist/app/builder/status-chip.js.map +1 -1
  64. package/dist/app/builder/use-builder-catalogue.d.ts +5 -1
  65. package/dist/app/builder/use-builder-catalogue.d.ts.map +1 -1
  66. package/dist/app/builder/use-builder-catalogue.js +50 -17
  67. package/dist/app/builder/use-builder-catalogue.js.map +1 -1
  68. package/dist/app/builder/use-builder-session.d.ts +30 -0
  69. package/dist/app/builder/use-builder-session.d.ts.map +1 -0
  70. package/dist/app/builder/use-builder-session.js +58 -0
  71. package/dist/app/builder/use-builder-session.js.map +1 -0
  72. package/dist/app/builder/use-display-resolver.d.ts +17 -0
  73. package/dist/app/builder/use-display-resolver.d.ts.map +1 -0
  74. package/dist/app/builder/use-display-resolver.js +16 -0
  75. package/dist/app/builder/use-display-resolver.js.map +1 -0
  76. package/dist/app/builder/use-save-dashboard.d.ts +3 -1
  77. package/dist/app/builder/use-save-dashboard.d.ts.map +1 -1
  78. package/dist/app/builder/use-save-dashboard.js +3 -1
  79. package/dist/app/builder/use-save-dashboard.js.map +1 -1
  80. package/dist/app/index.d.ts +8 -3
  81. package/dist/app/index.d.ts.map +1 -1
  82. package/dist/app/index.js +8 -2
  83. package/dist/app/index.js.map +1 -1
  84. package/dist/app/layout-builder.d.ts +18 -25
  85. package/dist/app/layout-builder.d.ts.map +1 -1
  86. package/dist/app/layout-builder.js +84 -78
  87. package/dist/app/layout-builder.js.map +1 -1
  88. package/dist/app/mcp-app-view-chrome.d.ts +103 -20
  89. package/dist/app/mcp-app-view-chrome.d.ts.map +1 -1
  90. package/dist/app/mcp-app-view-chrome.js +125 -40
  91. package/dist/app/mcp-app-view-chrome.js.map +1 -1
  92. package/dist/app/mcp-app-view-states.d.ts +23 -0
  93. package/dist/app/mcp-app-view-states.d.ts.map +1 -0
  94. package/dist/app/mcp-app-view-states.js +55 -0
  95. package/dist/app/mcp-app-view-states.js.map +1 -0
  96. package/dist/app/mcp-app-view.d.ts +72 -5
  97. package/dist/app/mcp-app-view.d.ts.map +1 -1
  98. package/dist/app/mcp-app-view.js +241 -141
  99. package/dist/app/mcp-app-view.js.map +1 -1
  100. package/dist/app/mcp-toolkit-app.d.ts +8 -2
  101. package/dist/app/mcp-toolkit-app.d.ts.map +1 -1
  102. package/dist/app/mcp-toolkit-app.js +33 -4
  103. package/dist/app/mcp-toolkit-app.js.map +1 -1
  104. package/dist/app/mcp-use-host-bridge.d.ts +15 -6
  105. package/dist/app/mcp-use-host-bridge.d.ts.map +1 -1
  106. package/dist/app/mcp-use-host-bridge.js +51 -22
  107. package/dist/app/mcp-use-host-bridge.js.map +1 -1
  108. package/dist/app/model-context.d.ts +71 -0
  109. package/dist/app/model-context.d.ts.map +1 -0
  110. package/dist/app/model-context.js +101 -0
  111. package/dist/app/model-context.js.map +1 -0
  112. package/dist/app/use-build-mode-focus.d.ts +33 -0
  113. package/dist/app/use-build-mode-focus.d.ts.map +1 -0
  114. package/dist/app/use-build-mode-focus.js +59 -0
  115. package/dist/app/use-build-mode-focus.js.map +1 -0
  116. package/dist/app/use-view-actions.d.ts +31 -0
  117. package/dist/app/use-view-actions.d.ts.map +1 -0
  118. package/dist/app/use-view-actions.js +81 -0
  119. package/dist/app/use-view-actions.js.map +1 -0
  120. package/dist/app/use-view-lifecycle.d.ts +42 -0
  121. package/dist/app/use-view-lifecycle.d.ts.map +1 -0
  122. package/dist/app/use-view-lifecycle.js +81 -0
  123. package/dist/app/use-view-lifecycle.js.map +1 -0
  124. package/dist/app/view-lifecycle.d.ts +185 -0
  125. package/dist/app/view-lifecycle.d.ts.map +1 -0
  126. package/dist/app/view-lifecycle.js +207 -0
  127. package/dist/app/view-lifecycle.js.map +1 -0
  128. package/dist/app/widget-fixture.d.ts +10 -3
  129. package/dist/app/widget-fixture.d.ts.map +1 -1
  130. package/dist/app/widget-fixture.js +21 -8
  131. package/dist/app/widget-fixture.js.map +1 -1
  132. package/dist/app/widget-props-sheet.d.ts +6 -10
  133. package/dist/app/widget-props-sheet.d.ts.map +1 -1
  134. package/dist/app/widget-props-sheet.js +25 -20
  135. package/dist/app/widget-props-sheet.js.map +1 -1
  136. package/dist/app/widget-renderer.d.ts +41 -1
  137. package/dist/app/widget-renderer.d.ts.map +1 -1
  138. package/dist/app/widget-renderer.js +82 -31
  139. package/dist/app/widget-renderer.js.map +1 -1
  140. package/dist/components/DrillButton.d.ts +9 -6
  141. package/dist/components/DrillButton.d.ts.map +1 -1
  142. package/dist/components/DrillButton.js +9 -6
  143. package/dist/components/DrillButton.js.map +1 -1
  144. package/dist/components/EmptyState.d.ts +33 -0
  145. package/dist/components/EmptyState.d.ts.map +1 -0
  146. package/dist/components/EmptyState.js +19 -0
  147. package/dist/components/EmptyState.js.map +1 -0
  148. package/dist/components/ErrorState.d.ts +53 -0
  149. package/dist/components/ErrorState.d.ts.map +1 -0
  150. package/dist/components/ErrorState.js +77 -0
  151. package/dist/components/ErrorState.js.map +1 -0
  152. package/dist/components/FilterBar.d.ts +16 -5
  153. package/dist/components/FilterBar.d.ts.map +1 -1
  154. package/dist/components/FilterBar.js +13 -5
  155. package/dist/components/FilterBar.js.map +1 -1
  156. package/dist/components/GroupCard.d.ts.map +1 -1
  157. package/dist/components/GroupCard.js +2 -1
  158. package/dist/components/GroupCard.js.map +1 -1
  159. package/dist/components/KpiGrid.d.ts +70 -18
  160. package/dist/components/KpiGrid.d.ts.map +1 -1
  161. package/dist/components/KpiGrid.js +112 -23
  162. package/dist/components/KpiGrid.js.map +1 -1
  163. package/dist/components/ListFooter.d.ts +32 -4
  164. package/dist/components/ListFooter.d.ts.map +1 -1
  165. package/dist/components/ListFooter.js +35 -6
  166. package/dist/components/ListFooter.js.map +1 -1
  167. package/dist/components/PagedListFooter.d.ts +57 -0
  168. package/dist/components/PagedListFooter.d.ts.map +1 -0
  169. package/dist/components/PagedListFooter.js +27 -0
  170. package/dist/components/PagedListFooter.js.map +1 -0
  171. package/dist/components/Pills.d.ts +36 -12
  172. package/dist/components/Pills.d.ts.map +1 -1
  173. package/dist/components/Pills.js +29 -8
  174. package/dist/components/Pills.js.map +1 -1
  175. package/dist/components/SectionHeading.d.ts +3 -1
  176. package/dist/components/SectionHeading.d.ts.map +1 -1
  177. package/dist/components/SectionHeading.js +6 -2
  178. package/dist/components/SectionHeading.js.map +1 -1
  179. package/dist/components/Toast.d.ts +43 -0
  180. package/dist/components/Toast.d.ts.map +1 -0
  181. package/dist/components/Toast.js +106 -0
  182. package/dist/components/Toast.js.map +1 -0
  183. package/dist/components/WidgetHeader.d.ts +34 -10
  184. package/dist/components/WidgetHeader.d.ts.map +1 -1
  185. package/dist/components/WidgetHeader.js +11 -6
  186. package/dist/components/WidgetHeader.js.map +1 -1
  187. package/dist/hooks/index.d.ts +4 -4
  188. package/dist/hooks/index.d.ts.map +1 -1
  189. package/dist/hooks/index.js +1 -1
  190. package/dist/hooks/index.js.map +1 -1
  191. package/dist/hooks/tool-result-recovery-model.d.ts +155 -0
  192. package/dist/hooks/tool-result-recovery-model.d.ts.map +1 -0
  193. package/dist/hooks/tool-result-recovery-model.js +273 -0
  194. package/dist/hooks/tool-result-recovery-model.js.map +1 -0
  195. package/dist/hooks/use-tool-query.d.ts +56 -14
  196. package/dist/hooks/use-tool-query.d.ts.map +1 -1
  197. package/dist/hooks/use-tool-query.js +102 -25
  198. package/dist/hooks/use-tool-query.js.map +1 -1
  199. package/dist/hooks/use-tool-result-recovery.d.ts +61 -44
  200. package/dist/hooks/use-tool-result-recovery.d.ts.map +1 -1
  201. package/dist/hooks/use-tool-result-recovery.js +62 -110
  202. package/dist/hooks/use-tool-result-recovery.js.map +1 -1
  203. package/dist/hooks/use-view-data.d.ts +55 -7
  204. package/dist/hooks/use-view-data.d.ts.map +1 -1
  205. package/dist/hooks/use-view-data.js +81 -11
  206. package/dist/hooks/use-view-data.js.map +1 -1
  207. package/dist/hooks/use-view-tool-query.d.ts +12 -7
  208. package/dist/hooks/use-view-tool-query.d.ts.map +1 -1
  209. package/dist/hooks/use-view-tool-query.js +15 -14
  210. package/dist/hooks/use-view-tool-query.js.map +1 -1
  211. package/dist/index.d.ts +27 -13
  212. package/dist/index.d.ts.map +1 -1
  213. package/dist/index.js +29 -7
  214. package/dist/index.js.map +1 -1
  215. package/dist/lib/focus-return.d.ts +61 -0
  216. package/dist/lib/focus-return.d.ts.map +1 -0
  217. package/dist/lib/focus-return.js +46 -0
  218. package/dist/lib/focus-return.js.map +1 -0
  219. package/dist/lib/focus-ring.d.ts +18 -0
  220. package/dist/lib/focus-ring.d.ts.map +1 -0
  221. package/dist/lib/focus-ring.js +18 -0
  222. package/dist/lib/focus-ring.js.map +1 -0
  223. package/dist/lib/icon-stroke.d.ts +12 -0
  224. package/dist/lib/icon-stroke.d.ts.map +1 -0
  225. package/dist/lib/icon-stroke.js +12 -0
  226. package/dist/lib/icon-stroke.js.map +1 -0
  227. package/dist/lib/parse-tool-result.d.ts +23 -1
  228. package/dist/lib/parse-tool-result.d.ts.map +1 -1
  229. package/dist/lib/parse-tool-result.js +38 -2
  230. package/dist/lib/parse-tool-result.js.map +1 -1
  231. package/dist/lib/toast-model.d.ts +86 -0
  232. package/dist/lib/toast-model.d.ts.map +1 -0
  233. package/dist/lib/toast-model.js +102 -0
  234. package/dist/lib/toast-model.js.map +1 -0
  235. package/dist/lib/tone-utils.d.ts +95 -9
  236. package/dist/lib/tone-utils.d.ts.map +1 -1
  237. package/dist/lib/tone-utils.js +100 -9
  238. package/dist/lib/tone-utils.js.map +1 -1
  239. package/dist/lib/tool-query.d.ts +107 -0
  240. package/dist/lib/tool-query.d.ts.map +1 -0
  241. package/dist/lib/tool-query.js +182 -0
  242. package/dist/lib/tool-query.js.map +1 -0
  243. package/dist/lib/tool-result-observer.d.ts +108 -0
  244. package/dist/lib/tool-result-observer.d.ts.map +1 -0
  245. package/dist/lib/tool-result-observer.js +158 -0
  246. package/dist/lib/tool-result-observer.js.map +1 -0
  247. package/dist/lib/typography.d.ts +37 -0
  248. package/dist/lib/typography.d.ts.map +1 -0
  249. package/dist/lib/typography.js +41 -0
  250. package/dist/lib/typography.js.map +1 -0
  251. package/dist/lib/utils.d.ts.map +1 -1
  252. package/dist/lib/utils.js +11 -1
  253. package/dist/lib/utils.js.map +1 -1
  254. package/dist/locale/index.d.ts +3 -1
  255. package/dist/locale/index.d.ts.map +1 -1
  256. package/dist/locale/index.js +2 -1
  257. package/dist/locale/index.js.map +1 -1
  258. package/dist/locale/locale-provider.d.ts +42 -1
  259. package/dist/locale/locale-provider.d.ts.map +1 -1
  260. package/dist/locale/locale-provider.js +44 -3
  261. package/dist/locale/locale-provider.js.map +1 -1
  262. package/dist/locale/message-types.d.ts +336 -0
  263. package/dist/locale/message-types.d.ts.map +1 -0
  264. package/dist/locale/message-types.js +7 -0
  265. package/dist/locale/message-types.js.map +1 -0
  266. package/dist/locale/messages-de.d.ts +9 -0
  267. package/dist/locale/messages-de.d.ts.map +1 -0
  268. package/dist/locale/messages-de.js +185 -0
  269. package/dist/locale/messages-de.js.map +1 -0
  270. package/dist/locale/messages-en.d.ts +8 -0
  271. package/dist/locale/messages-en.d.ts.map +1 -0
  272. package/dist/locale/messages-en.js +184 -0
  273. package/dist/locale/messages-en.js.map +1 -0
  274. package/dist/locale/toolkit-messages.d.ts +57 -0
  275. package/dist/locale/toolkit-messages.d.ts.map +1 -0
  276. package/dist/locale/toolkit-messages.js +103 -0
  277. package/dist/locale/toolkit-messages.js.map +1 -0
  278. package/dist/primitives/alert.d.ts +1 -1
  279. package/dist/primitives/alert.d.ts.map +1 -1
  280. package/dist/primitives/alert.js +6 -2
  281. package/dist/primitives/alert.js.map +1 -1
  282. package/dist/primitives/badge.d.ts +1 -1
  283. package/dist/primitives/badge.d.ts.map +1 -1
  284. package/dist/primitives/badge.js +19 -6
  285. package/dist/primitives/badge.js.map +1 -1
  286. package/dist/primitives/button.d.ts +1 -1
  287. package/dist/primitives/button.d.ts.map +1 -1
  288. package/dist/primitives/button.js +12 -8
  289. package/dist/primitives/button.js.map +1 -1
  290. package/dist/primitives/card.js +4 -4
  291. package/dist/primitives/card.js.map +1 -1
  292. package/dist/primitives/dialog.d.ts +7 -3
  293. package/dist/primitives/dialog.d.ts.map +1 -1
  294. package/dist/primitives/dialog.js +22 -8
  295. package/dist/primitives/dialog.js.map +1 -1
  296. package/dist/primitives/input.d.ts.map +1 -1
  297. package/dist/primitives/input.js +4 -1
  298. package/dist/primitives/input.js.map +1 -1
  299. package/dist/primitives/scroll-area.js +1 -1
  300. package/dist/primitives/scroll-area.js.map +1 -1
  301. package/dist/primitives/select.d.ts.map +1 -1
  302. package/dist/primitives/select.js +6 -3
  303. package/dist/primitives/select.js.map +1 -1
  304. package/dist/primitives/sheet.d.ts +3 -1
  305. package/dist/primitives/sheet.d.ts.map +1 -1
  306. package/dist/primitives/sheet.js +17 -7
  307. package/dist/primitives/sheet.js.map +1 -1
  308. package/dist/primitives/skeleton.js +1 -1
  309. package/dist/primitives/skeleton.js.map +1 -1
  310. package/dist/primitives/switch.d.ts.map +1 -1
  311. package/dist/primitives/switch.js +2 -1
  312. package/dist/primitives/switch.js.map +1 -1
  313. package/dist/primitives/tabs.d.ts.map +1 -1
  314. package/dist/primitives/tabs.js +5 -3
  315. package/dist/primitives/tabs.js.map +1 -1
  316. package/dist/providers/query-provider.d.ts +32 -1
  317. package/dist/providers/query-provider.d.ts.map +1 -1
  318. package/dist/providers/query-provider.js +53 -4
  319. package/dist/providers/query-provider.js.map +1 -1
  320. package/dist/render-cases-primitives.js +1 -1
  321. package/dist/render-cases-primitives.js.map +1 -1
  322. package/dist/render-cases.d.ts.map +1 -1
  323. package/dist/render-cases.js +48 -6
  324. package/dist/render-cases.js.map +1 -1
  325. package/dist/render-effects.d.ts +42 -0
  326. package/dist/render-effects.d.ts.map +1 -0
  327. package/dist/render-effects.js +81 -0
  328. package/dist/render-effects.js.map +1 -0
  329. package/dist/render-scan.d.ts +25 -0
  330. package/dist/render-scan.d.ts.map +1 -0
  331. package/dist/render-scan.js +66 -0
  332. package/dist/render-scan.js.map +1 -0
  333. package/dist/theme/cd-tokens.d.ts +66 -0
  334. package/dist/theme/cd-tokens.d.ts.map +1 -0
  335. package/dist/theme/cd-tokens.js +64 -0
  336. package/dist/theme/cd-tokens.js.map +1 -0
  337. package/dist/theme/create-theme.d.ts +214 -39
  338. package/dist/theme/create-theme.d.ts.map +1 -1
  339. package/dist/theme/create-theme.js +204 -13
  340. package/dist/theme/create-theme.js.map +1 -1
  341. package/dist/theme/fonts.d.ts +38 -0
  342. package/dist/theme/fonts.d.ts.map +1 -0
  343. package/dist/theme/fonts.js +56 -0
  344. package/dist/theme/fonts.js.map +1 -0
  345. package/dist/theme/host-fonts.d.ts +39 -0
  346. package/dist/theme/host-fonts.d.ts.map +1 -0
  347. package/dist/theme/host-fonts.js +44 -0
  348. package/dist/theme/host-fonts.js.map +1 -0
  349. package/dist/theme/index.d.ts +8 -1
  350. package/dist/theme/index.d.ts.map +1 -1
  351. package/dist/theme/index.js +5 -1
  352. package/dist/theme/index.js.map +1 -1
  353. package/dist/theme/portal-container.d.ts +16 -0
  354. package/dist/theme/portal-container.d.ts.map +1 -0
  355. package/dist/theme/portal-container.js +19 -0
  356. package/dist/theme/portal-container.js.map +1 -0
  357. package/dist/theme/presets.d.ts +24 -9
  358. package/dist/theme/presets.d.ts.map +1 -1
  359. package/dist/theme/presets.js +44 -11
  360. package/dist/theme/presets.js.map +1 -1
  361. package/dist/theme/theme-provider.d.ts +27 -9
  362. package/dist/theme/theme-provider.d.ts.map +1 -1
  363. package/dist/theme/theme-provider.js +42 -20
  364. package/dist/theme/theme-provider.js.map +1 -1
  365. package/package.json +11 -5
  366. package/src/app/adapt-data-widget.tsx +2 -42
  367. package/src/app/builder/BuilderBanners.tsx +26 -13
  368. package/src/app/builder/CanvasRow.tsx +79 -32
  369. package/src/app/builder/CatalogueSheet.tsx +58 -35
  370. package/src/app/builder/CellPropsSheet.tsx +17 -2
  371. package/src/app/builder/DiscardDialog.tsx +59 -0
  372. package/src/app/builder/EmptyHint.tsx +16 -11
  373. package/src/app/builder/LayoutSurface.tsx +207 -67
  374. package/src/app/builder/PipelineStrip.tsx +265 -156
  375. package/src/app/builder/SaveDialog.tsx +25 -14
  376. package/src/app/builder/Toolbar.tsx +154 -57
  377. package/src/app/builder/Workspace.tsx +226 -56
  378. package/src/app/builder/builder-feedback.test.tsx +75 -0
  379. package/src/app/builder/builder-model.test.ts +5 -0
  380. package/src/app/builder/builder-model.ts +346 -6
  381. package/src/app/builder/builder-model.ux.test.ts +456 -0
  382. package/src/app/builder/builder-ux.test.tsx +844 -0
  383. package/src/app/builder/labels.ts +15 -121
  384. package/src/app/builder/reveal.ts +10 -0
  385. package/src/app/builder/status-chip.tsx +8 -4
  386. package/src/app/builder/use-builder-catalogue.ts +62 -19
  387. package/src/app/builder/use-builder-session.ts +68 -0
  388. package/src/app/builder/use-display-resolver.ts +24 -0
  389. package/src/app/builder/use-save-dashboard.ts +5 -0
  390. package/src/app/index.ts +20 -3
  391. package/src/app/layout-builder.tsx +193 -186
  392. package/src/app/mcp-app-view-chrome.test.tsx +487 -14
  393. package/src/app/mcp-app-view-chrome.tsx +328 -100
  394. package/src/app/mcp-app-view-states.tsx +140 -0
  395. package/src/app/mcp-app-view.effects.test.tsx +292 -0
  396. package/src/app/mcp-app-view.test.tsx +478 -0
  397. package/src/app/mcp-app-view.tsx +447 -190
  398. package/src/app/mcp-toolkit-app.test.tsx +40 -0
  399. package/src/app/mcp-toolkit-app.tsx +39 -8
  400. package/src/app/mcp-use-host-bridge.test.tsx +180 -0
  401. package/src/app/mcp-use-host-bridge.tsx +67 -24
  402. package/src/app/model-context.test.tsx +116 -0
  403. package/src/app/model-context.tsx +125 -0
  404. package/src/app/use-build-mode-focus.ts +62 -0
  405. package/src/app/use-view-actions.ts +89 -0
  406. package/src/app/use-view-lifecycle.ts +122 -0
  407. package/src/app/view-lifecycle.test.ts +443 -0
  408. package/src/app/view-lifecycle.ts +334 -0
  409. package/src/app/widget-fixture.effects.test.tsx +51 -0
  410. package/src/app/widget-fixture.render.test.tsx +81 -0
  411. package/src/app/widget-fixture.tsx +34 -11
  412. package/src/app/widget-props-sheet.tsx +67 -39
  413. package/src/app/widget-renderer.render.test.tsx +131 -0
  414. package/src/app/widget-renderer.tsx +136 -38
  415. package/src/components/DrillButton.tsx +24 -10
  416. package/src/components/EmptyState.tsx +83 -0
  417. package/src/components/ErrorState.tsx +204 -0
  418. package/src/components/FilterBar.test.tsx +82 -0
  419. package/src/components/FilterBar.tsx +56 -27
  420. package/src/components/GroupCard.tsx +3 -2
  421. package/src/components/KpiGrid.test.tsx +279 -0
  422. package/src/components/KpiGrid.tsx +329 -91
  423. package/src/components/ListFooter.test.tsx +116 -0
  424. package/src/components/ListFooter.tsx +110 -12
  425. package/src/components/PagedListFooter.test.tsx +140 -0
  426. package/src/components/PagedListFooter.tsx +107 -0
  427. package/src/components/Pills.test.tsx +77 -0
  428. package/src/components/Pills.tsx +78 -16
  429. package/src/components/SectionHeading.tsx +9 -3
  430. package/src/components/Toast.tsx +206 -0
  431. package/src/components/WidgetHeader.test.tsx +78 -0
  432. package/src/components/WidgetHeader.tsx +63 -16
  433. package/src/components/feedback-states.test.tsx +314 -0
  434. package/src/components/tone-figures.test.tsx +58 -0
  435. package/src/fonts.css +19 -0
  436. package/src/globals.css +9 -165
  437. package/src/hooks/index.ts +6 -3
  438. package/src/hooks/tool-result-recovery-model.test.ts +744 -0
  439. package/src/hooks/tool-result-recovery-model.ts +418 -0
  440. package/src/hooks/use-tool-query.effects.test.tsx +205 -0
  441. package/src/hooks/use-tool-query.ts +156 -33
  442. package/src/hooks/use-tool-result-recovery.effects.test.tsx +141 -0
  443. package/src/hooks/use-tool-result-recovery.test.ts +28 -5
  444. package/src/hooks/use-tool-result-recovery.ts +101 -131
  445. package/src/hooks/use-view-data.test.tsx +169 -0
  446. package/src/hooks/use-view-data.ts +131 -10
  447. package/src/hooks/use-view-tool-query.ts +25 -19
  448. package/src/i18n-coverage.test.tsx +425 -0
  449. package/src/index.ts +102 -13
  450. package/src/lib/focus-return.test.ts +288 -0
  451. package/src/lib/focus-return.ts +96 -0
  452. package/src/lib/focus-ring.ts +20 -0
  453. package/src/lib/icon-stroke.test.ts +59 -0
  454. package/src/lib/icon-stroke.ts +13 -0
  455. package/src/lib/parse-tool-result.test.ts +46 -1
  456. package/src/lib/parse-tool-result.ts +40 -2
  457. package/src/lib/toast-model.test.ts +233 -0
  458. package/src/lib/toast-model.ts +165 -0
  459. package/src/lib/tone-utils.test.ts +125 -16
  460. package/src/lib/tone-utils.ts +144 -15
  461. package/src/lib/tool-query.test.ts +569 -0
  462. package/src/lib/tool-query.ts +253 -0
  463. package/src/lib/tool-result-observer.test.ts +296 -0
  464. package/src/lib/tool-result-observer.ts +211 -0
  465. package/src/lib/typography.test.ts +45 -0
  466. package/src/lib/typography.ts +47 -0
  467. package/src/lib/utils.ts +12 -1
  468. package/src/locale/index.ts +14 -1
  469. package/src/locale/locale-provider.test.tsx +87 -0
  470. package/src/locale/locale-provider.tsx +90 -3
  471. package/src/locale/message-types.ts +337 -0
  472. package/src/locale/messages-de.ts +198 -0
  473. package/src/locale/messages-en.ts +196 -0
  474. package/src/locale/toolkit-messages.test.ts +349 -0
  475. package/src/locale/toolkit-messages.ts +143 -0
  476. package/src/no-hardcoded-copy.test.ts +432 -0
  477. package/src/primitives/alert.tsx +7 -2
  478. package/src/primitives/badge.tsx +19 -7
  479. package/src/primitives/button.tsx +13 -8
  480. package/src/primitives/card.tsx +4 -4
  481. package/src/primitives/dialog.tsx +42 -8
  482. package/src/primitives/input.tsx +5 -3
  483. package/src/primitives/scroll-area.tsx +1 -1
  484. package/src/primitives/select.tsx +7 -4
  485. package/src/primitives/sheet.tsx +33 -6
  486. package/src/primitives/skeleton.tsx +1 -1
  487. package/src/primitives/switch.tsx +4 -2
  488. package/src/primitives/tabs.tsx +9 -6
  489. package/src/providers/query-provider.test.tsx +154 -0
  490. package/src/providers/query-provider.tsx +66 -5
  491. package/src/render-cases-primitives.tsx +1 -1
  492. package/src/render-cases.tsx +82 -6
  493. package/src/render-effects.tsx +96 -0
  494. package/src/render-scan.ts +75 -0
  495. package/src/theme/cd-tokens.generated.css +48 -0
  496. package/src/theme/cd-tokens.test.ts +109 -0
  497. package/src/theme/cd-tokens.ts +67 -0
  498. package/src/theme/create-theme.test.ts +253 -5
  499. package/src/theme/create-theme.ts +341 -41
  500. package/src/theme/fonts.test.ts +85 -0
  501. package/src/theme/fonts.ts +72 -0
  502. package/src/theme/host-fonts.test.ts +135 -0
  503. package/src/theme/host-fonts.ts +52 -0
  504. package/src/theme/index.ts +15 -1
  505. package/src/theme/portal-container.ts +20 -0
  506. package/src/theme/presets.ts +44 -11
  507. package/src/theme/theme-css.test.ts +619 -0
  508. package/src/theme/theme-provider.test.tsx +82 -0
  509. package/src/theme/theme-provider.tsx +60 -24
  510. package/src/theme.css +344 -0
  511. package/ui-catalog.json +783 -49
@@ -0,0 +1,569 @@
1
+ import {
2
+ environmentManager,
3
+ hashKey,
4
+ keepPreviousData,
5
+ MutationObserver,
6
+ QueryClient,
7
+ QueryObserver,
8
+ type QueryObserverOptions,
9
+ } from "@tanstack/react-query"
10
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
11
+ import { isToolError, ToolResultError } from "./parse-tool-result.js"
12
+ import {
13
+ claimToolQueryKey,
14
+ invalidateToolQueries,
15
+ NoToolTransportError,
16
+ seedToolQuery,
17
+ settleEachCall,
18
+ shouldRetryToolCall,
19
+ TOOL_QUERY_RETRIES,
20
+ toolKeyCollisionMessage,
21
+ toolQueryKey,
22
+ toolQueryOptions,
23
+ type ToolCaller,
24
+ } from "./tool-query.js"
25
+
26
+ /**
27
+ * The query layer of the toolkit's data hooks (toolkit#176 K18), against a
28
+ * real TanStack `QueryClient` and its observers — what `useToolQuery`,
29
+ * `useViewToolQuery` and `useViewData` run, without a renderer.
30
+ */
31
+
32
+ const ok = (structuredContent: unknown) => ({ content: [], structuredContent })
33
+ const isErrorResult = (text: string) => ({ isError: true, content: [{ type: "text", text }] })
34
+ /** How mcp-use's `useDynamicTool` rejects an `isError` answer inside a view. */
35
+ class ToolError extends Error {
36
+ readonly result: unknown
37
+ constructor(result: unknown) {
38
+ super("tool failed")
39
+ this.name = "ToolError"
40
+ this.result = result
41
+ }
42
+ }
43
+
44
+ let client: QueryClient
45
+
46
+ beforeEach(() => {
47
+ vi.useFakeTimers()
48
+ client = new QueryClient()
49
+ })
50
+
51
+ afterEach(() => {
52
+ client.clear()
53
+ vi.useRealTimers()
54
+ vi.restoreAllMocks()
55
+ })
56
+
57
+ /** Subscribe an observer for `options`; returns it and its latest result. */
58
+ function observe<T>(options: QueryObserverOptions<T, Error, T>) {
59
+ const observer = new QueryObserver<T, Error, T>(client, options)
60
+ const unsubscribe = observer.subscribe(() => {})
61
+ return { observer, unsubscribe }
62
+ }
63
+
64
+ describe("toolQueryKey", () => {
65
+ it("is the caller's key, then the args — the 2.x key, unchanged", () => {
66
+ expect(toolQueryKey(["orders", 7], "orders_list", { page: 1 })).toEqual([
67
+ "orders",
68
+ 7,
69
+ { page: 1 },
70
+ ])
71
+ expect(toolQueryOptions(["orders"], "orders_list", { page: 1 }, undefined).queryKey).toEqual(
72
+ toolQueryKey(["orders"], "orders_list", { page: 1 }),
73
+ )
74
+ })
75
+
76
+ it("invalidating by the caller's key or by [...key, args] still matches", async () => {
77
+ const callTool = vi.fn<ToolCaller>(() => Promise.resolve(ok({ n: 1 })))
78
+ observe(toolQueryOptions(["orders"], "orders_list", { page: 1 }, callTool))
79
+ observe(toolQueryOptions(["orders"], "orders_list", { page: 2 }, callTool))
80
+ await vi.advanceTimersByTimeAsync(0)
81
+ expect(client.getQueryCache().findAll({ queryKey: ["orders"] })).toHaveLength(2)
82
+ expect(client.getQueryCache().findAll({ queryKey: ["orders", { page: 1 }] })).toHaveLength(1)
83
+ })
84
+ })
85
+
86
+ describe("toolQueryKey with the tool name (toolNameInQueryKey, K18)", () => {
87
+ it("is the caller's key, the tool name, then the args", () => {
88
+ expect(toolQueryKey(["orders", 7], "orders_list", { page: 1 }, true)).toEqual([
89
+ "orders",
90
+ 7,
91
+ "orders_list",
92
+ { page: 1 },
93
+ ])
94
+ expect(toolQueryKey(["orders"], "orders_list", { page: 1 }, false)).toEqual([
95
+ "orders",
96
+ { page: 1 },
97
+ ])
98
+ expect(
99
+ toolQueryOptions(["orders"], "orders_list", { page: 1 }, undefined, undefined, {
100
+ toolNameInKey: true,
101
+ }).queryKey,
102
+ ).toEqual(["orders", "orders_list", { page: 1 }])
103
+ })
104
+
105
+ it("two tools behind one key and args get an entry each; the caller's key still invalidates both", async () => {
106
+ const callTool = vi.fn<ToolCaller>((name) => Promise.resolve(ok({ from: name })))
107
+ const build = { toolNameInKey: true }
108
+ const a = observe(toolQueryOptions(["feed"], "a_data", { id: 1 }, callTool, undefined, build))
109
+ const b = observe(toolQueryOptions(["feed"], "b_data", { id: 1 }, callTool, undefined, build))
110
+ await vi.advanceTimersByTimeAsync(0)
111
+ expect(a.observer.getCurrentResult().data).toEqual({ from: "a_data" })
112
+ expect(b.observer.getCurrentResult().data).toEqual({ from: "b_data" })
113
+ expect(client.getQueryCache().findAll({ queryKey: ["feed"] })).toHaveLength(2)
114
+ expect(client.getQueryCache().findAll({ queryKey: ["feed", "a_data"] })).toHaveLength(1)
115
+ expect(hashKey(toolQueryKey(["feed"], "a_data", { id: 1 }, true))).not.toBe(
116
+ hashKey(toolQueryKey(["feed"], "b_data", { id: 1 }, true)),
117
+ )
118
+ callTool.mockClear()
119
+ await client.invalidateQueries({ queryKey: ["feed"] })
120
+ expect(callTool.mock.calls.map(([name]) => name).sort()).toEqual(["a_data", "b_data"])
121
+ })
122
+
123
+ it("with the 2.x key the same two tools share one entry: the collision the guard reports", async () => {
124
+ const callTool = vi.fn<ToolCaller>((name) => Promise.resolve(ok({ from: name })))
125
+ observe(toolQueryOptions(["feed"], "a_data", { id: 1 }, callTool))
126
+ observe(toolQueryOptions(["feed"], "b_data", { id: 1 }, callTool))
127
+ await vi.advanceTimersByTimeAsync(0)
128
+ expect(client.getQueryCache().findAll({ queryKey: ["feed"] })).toHaveLength(1)
129
+ })
130
+ })
131
+
132
+ describe("claimToolQueryKey (the guard of a key two tools share)", () => {
133
+ const keyId = hashKey(toolQueryKey(["feed"], "a_data", { id: 1 }))
134
+
135
+ it("reports the other tool when a second one claims the same key, once per switch", () => {
136
+ expect(claimToolQueryKey(client, keyId, "a_data")).toBeNull()
137
+ expect(claimToolQueryKey(client, keyId, "a_data"), "the same tool again").toBeNull()
138
+ expect(claimToolQueryKey(client, keyId, "b_data")).toBe("a_data")
139
+ expect(claimToolQueryKey(client, keyId, "b_data")).toBeNull()
140
+ expect(claimToolQueryKey(client, keyId, "a_data")).toBe("b_data")
141
+ })
142
+
143
+ it("keeps the claims per client and per key", () => {
144
+ claimToolQueryKey(client, keyId, "a_data")
145
+ expect(claimToolQueryKey(new QueryClient(), keyId, "b_data")).toBeNull()
146
+ expect(claimToolQueryKey(client, hashKey(["other"]), "b_data")).toBeNull()
147
+ })
148
+
149
+ it("names the key and both tools in its warning", () => {
150
+ expect(toolKeyCollisionMessage('["feed",{"id":1}]', "b_data", "a_data")).toBe(
151
+ '[mcp-toolkit] the query key ["feed",{"id":1}] is used by "a_data" and "b_data": both ' +
152
+ "share one cache entry and overwrite each other's answer. Give each tool its own queryKey.",
153
+ )
154
+ })
155
+ })
156
+
157
+ describe("toolQueryOptions", () => {
158
+ it("passes TanStack's query options through", () => {
159
+ const select = (d: number) => d * 2
160
+ const retryDelay = () => 5
161
+ const options = toolQueryOptions<number>(["k"], "t", {}, vi.fn<ToolCaller>(), {
162
+ retry: 1,
163
+ retryDelay,
164
+ staleTime: 10_000,
165
+ placeholderData: keepPreviousData,
166
+ initialData: 3,
167
+ initialDataUpdatedAt: 123,
168
+ enabled: false,
169
+ select,
170
+ gcTime: 50,
171
+ })
172
+ expect(options).toMatchObject({
173
+ queryKey: ["k", {}],
174
+ retry: 1,
175
+ retryDelay,
176
+ staleTime: 10_000,
177
+ placeholderData: keepPreviousData,
178
+ initialData: 3,
179
+ initialDataUpdatedAt: 123,
180
+ enabled: false,
181
+ select,
182
+ gcTime: 50,
183
+ })
184
+ })
185
+
186
+ it("leaves retry to the caller, else to the client's default (an omitted key stays omitted)", () => {
187
+ const callTool = vi.fn<ToolCaller>()
188
+ expect("retry" in toolQueryOptions(["k"], "t", {}, callTool)).toBe(false)
189
+ expect(toolQueryOptions(["k"], "t", {}, callTool, { retry: 1 }).retry).toBe(1)
190
+ expect(toolQueryOptions(["k"], "t", {}, callTool, { retry: shouldRetryToolCall }).retry).toBe(
191
+ shouldRetryToolCall,
192
+ )
193
+ })
194
+
195
+ it("never retries a missing transport: nothing changes until the mount does", () => {
196
+ expect(toolQueryOptions(["k"], "t", {}, undefined).retry).toBe(false)
197
+ expect(toolQueryOptions(["k"], "t", {}, undefined, { retry: 3 }).retry).toBe(false)
198
+ })
199
+
200
+ it("calls the tool with its args and decodes the result (structured-first, or a custom decoder)", async () => {
201
+ const callTool = vi.fn<ToolCaller>(() => Promise.resolve(ok({ n: 1 })))
202
+ const { queryFn } = toolQueryOptions(["k"], "orders_list", { page: 2 }, callTool)
203
+ await expect(Promise.resolve((queryFn as () => Promise<unknown>)())).resolves.toEqual({ n: 1 })
204
+ expect(callTool).toHaveBeenCalledWith("orders_list", { page: 2 })
205
+ const custom = toolQueryOptions(["k"], "t", {}, callTool, undefined, {
206
+ decode: () => "decoded",
207
+ })
208
+ await expect(Promise.resolve((custom.queryFn as () => Promise<unknown>)())).resolves.toBe(
209
+ "decoded",
210
+ )
211
+ })
212
+
213
+ it("without a transport the query fails loudly instead of idling (N25)", async () => {
214
+ const { queryFn } = toolQueryOptions(["k"], "orders_list", {}, undefined)
215
+ await expect(Promise.resolve((queryFn as () => Promise<unknown>)())).rejects.toBeInstanceOf(
216
+ NoToolTransportError,
217
+ )
218
+ const { observer } = observe(toolQueryOptions(["k"], "orders_list", {}, undefined))
219
+ await vi.advanceTimersByTimeAsync(0)
220
+ expect(observer.getCurrentResult()).toMatchObject({ status: "error", failureCount: 1 })
221
+ expect(observer.getCurrentResult().error).toMatchObject({
222
+ name: "NoToolTransportError",
223
+ message:
224
+ '[mcp-toolkit] cannot call "orders_list": no tool transport. Mount the widget under ' +
225
+ "AppQueryProvider with callTool, or a HostBridgeProvider.",
226
+ })
227
+ })
228
+ })
229
+
230
+ describe("shouldRetryToolCall (the opt-in retry policy for tool errors)", () => {
231
+ it("never retries a tool's own isError answer or a missing transport", () => {
232
+ expect(
233
+ shouldRetryToolCall(0, new ToolResultError("[404] gone", isErrorResult("[404] gone"))),
234
+ ).toBe(false)
235
+ expect(shouldRetryToolCall(0, new ToolError(isErrorResult("x")))).toBe(false)
236
+ expect(shouldRetryToolCall(0, new NoToolTransportError("t"))).toBe(false)
237
+ })
238
+
239
+ it("retries a failed transport up to TanStack's default count", () => {
240
+ const lost = new Error("connection lost")
241
+ expect(TOOL_QUERY_RETRIES).toBe(3)
242
+ expect(shouldRetryToolCall(0, lost)).toBe(true)
243
+ expect(shouldRetryToolCall(2, lost)).toBe(true)
244
+ expect(shouldRetryToolCall(3, lost)).toBe(false)
245
+ expect(shouldRetryToolCall(0, "not an error")).toBe(true)
246
+ })
247
+ })
248
+
249
+ describe("a tool query against a real client", () => {
250
+ it("by default an isError answer is retried like any failure (TanStack's 3, the 2.x behaviour)", async () => {
251
+ // TanStack retries 3 times in a browser, never on a server: test the browser.
252
+ const wasServer = environmentManager.isServer()
253
+ environmentManager.setIsServer(() => false)
254
+ try {
255
+ const callTool = vi.fn<ToolCaller>(() => Promise.resolve(isErrorResult("[503] busy")))
256
+ const { observer } = observe(toolQueryOptions(["incident"], "incident_data", {}, callTool))
257
+ await vi.advanceTimersByTimeAsync(0)
258
+ expect(observer.getCurrentResult()).toMatchObject({ status: "pending", failureCount: 1 })
259
+ await vi.advanceTimersByTimeAsync(10_000)
260
+ expect(callTool).toHaveBeenCalledTimes(4)
261
+ expect(observer.getCurrentResult().error).toBeInstanceOf(ToolResultError)
262
+ } finally {
263
+ environmentManager.setIsServer(() => wasServer)
264
+ }
265
+ })
266
+
267
+ it("a client's default retry applies to the tool queries", async () => {
268
+ client.setDefaultOptions({ queries: { retry: false } })
269
+ const callTool = vi.fn<ToolCaller>(() => Promise.reject(new Error("connection lost")))
270
+ const { observer } = observe(toolQueryOptions(["feed"], "feed_data", {}, callTool))
271
+ await vi.advanceTimersByTimeAsync(0)
272
+ expect(callTool).toHaveBeenCalledOnce()
273
+ expect(observer.getCurrentResult().status).toBe("error")
274
+ })
275
+
276
+ it("with shouldRetryToolCall an isError answer fails at once: one call, no ~7 s backoff", async () => {
277
+ const callTool = vi.fn<ToolCaller>(() =>
278
+ Promise.resolve(isErrorResult("[404] no such incident")),
279
+ )
280
+ const { observer } = observe(
281
+ toolQueryOptions(["incident"], "incident_data", { id: 9 }, callTool, {
282
+ retry: shouldRetryToolCall,
283
+ }),
284
+ )
285
+ await vi.advanceTimersByTimeAsync(0)
286
+ const result = observer.getCurrentResult()
287
+ expect(callTool).toHaveBeenCalledOnce()
288
+ expect(result.status).toBe("error")
289
+ expect(result.error).toBeInstanceOf(ToolResultError)
290
+ expect(result.failureCount).toBe(1)
291
+ expect(result.failureReason).toBe(result.error)
292
+ })
293
+
294
+ it("with shouldRetryToolCall a failed transport is retried, exposing failureCount/failureReason meanwhile", async () => {
295
+ const callTool = vi.fn<ToolCaller>(() => Promise.reject(new Error("connection lost")))
296
+ const { observer } = observe(
297
+ toolQueryOptions(["feed"], "feed_data", {}, callTool, { retry: shouldRetryToolCall }),
298
+ )
299
+ await vi.advanceTimersByTimeAsync(0)
300
+ expect(observer.getCurrentResult()).toMatchObject({ status: "pending", failureCount: 1 })
301
+ expect(observer.getCurrentResult().failureReason?.message).toBe("connection lost")
302
+ await vi.advanceTimersByTimeAsync(10_000)
303
+ expect(callTool).toHaveBeenCalledTimes(1 + TOOL_QUERY_RETRIES)
304
+ expect(observer.getCurrentResult()).toMatchObject({ status: "error", failureCount: 4 })
305
+ })
306
+
307
+ it("retry and retryDelay are the caller's to set — a ToolError can be retried on request", async () => {
308
+ const callTool = vi.fn<ToolCaller>(() => Promise.reject(new ToolError(isErrorResult("busy"))))
309
+ observe(
310
+ toolQueryOptions(["feed"], "feed_data", {}, callTool, {
311
+ retry: (count, error) => isToolError(error) && count < 2,
312
+ retryDelay: 100,
313
+ }),
314
+ )
315
+ await vi.advanceTimersByTimeAsync(99)
316
+ expect(callTool).toHaveBeenCalledTimes(1)
317
+ await vi.advanceTimersByTimeAsync(1_000)
318
+ expect(callTool).toHaveBeenCalledTimes(3)
319
+ })
320
+
321
+ it("enabled: false keeps the tool uncalled", async () => {
322
+ const callTool = vi.fn<ToolCaller>(() => Promise.resolve(ok({})))
323
+ observe(toolQueryOptions(["feed"], "feed_data", {}, callTool, { enabled: false }))
324
+ await vi.advanceTimersByTimeAsync(1_000)
325
+ expect(callTool).not.toHaveBeenCalled()
326
+ })
327
+
328
+ it("initialData + initialDataUpdatedAt within staleTime is not refetched on mount", async () => {
329
+ const callTool = vi.fn<ToolCaller>(() => Promise.resolve(ok({ n: 2 })))
330
+ const { observer } = observe(
331
+ toolQueryOptions<{ n: number }>(["feed"], "feed_data", {}, callTool, {
332
+ initialData: { n: 1 },
333
+ initialDataUpdatedAt: Date.now(),
334
+ staleTime: 30_000,
335
+ }),
336
+ )
337
+ await vi.advanceTimersByTimeAsync(0)
338
+ expect(callTool).not.toHaveBeenCalled()
339
+ expect(observer.getCurrentResult().data).toEqual({ n: 1 })
340
+ })
341
+
342
+ it("an old initialDataUpdatedAt is stale and refetched", async () => {
343
+ const callTool = vi.fn<ToolCaller>(() => Promise.resolve(ok({ n: 2 })))
344
+ const { observer } = observe(
345
+ toolQueryOptions<{ n: number }>(["feed"], "feed_data", {}, callTool, {
346
+ initialData: { n: 1 },
347
+ initialDataUpdatedAt: Date.now() - 60_000,
348
+ staleTime: 30_000,
349
+ }),
350
+ )
351
+ await vi.advanceTimersByTimeAsync(0)
352
+ expect(callTool).toHaveBeenCalledOnce()
353
+ expect(observer.getCurrentResult().data).toEqual({ n: 2 })
354
+ })
355
+
356
+ it("placeholderData: keepPreviousData keeps the previous args' answer while the next loads", async () => {
357
+ let resolveNext: (v: unknown) => void = () => {}
358
+ const callTool = vi.fn<ToolCaller>((_name, args) =>
359
+ (args as { q: string }).q === "a"
360
+ ? Promise.resolve(ok({ rows: ["a"] }))
361
+ : new Promise((resolve) => (resolveNext = resolve)),
362
+ )
363
+ const options = (q: string) =>
364
+ toolQueryOptions<{ rows: string[] }>(["search"], "search_data", { q }, callTool, {
365
+ placeholderData: keepPreviousData,
366
+ })
367
+ const { observer } = observe(options("a"))
368
+ await vi.advanceTimersByTimeAsync(0)
369
+ observer.setOptions(options("b"))
370
+ await vi.advanceTimersByTimeAsync(0)
371
+ expect(observer.getCurrentResult()).toMatchObject({
372
+ data: { rows: ["a"] },
373
+ isPlaceholderData: true,
374
+ isFetching: true,
375
+ })
376
+ resolveNext(ok({ rows: ["b"] }))
377
+ await vi.advanceTimersByTimeAsync(0)
378
+ expect(observer.getCurrentResult()).toMatchObject({
379
+ data: { rows: ["b"] },
380
+ isPlaceholderData: false,
381
+ })
382
+ })
383
+
384
+ it("refetch reads the tool again", async () => {
385
+ const callTool = vi.fn<ToolCaller>(() => Promise.resolve(ok({ n: 1 })))
386
+ const { observer } = observe(toolQueryOptions(["feed"], "feed_data", {}, callTool))
387
+ await vi.advanceTimersByTimeAsync(0)
388
+ await observer.refetch()
389
+ expect(callTool).toHaveBeenCalledTimes(2)
390
+ })
391
+ })
392
+
393
+ describe("seedToolQuery (useViewData's seed mode)", () => {
394
+ const key = toolQueryKey(["incident"], "incident_data", { id: 1 })
395
+
396
+ it("writes an empty entry, with the given updatedAt", () => {
397
+ expect(seedToolQuery(client, key, { n: 1 }, 1234)).toBe(true)
398
+ expect(client.getQueryData(key)).toEqual({ n: 1 })
399
+ expect(client.getQueryState(key)?.dataUpdatedAt).toBe(1234)
400
+ })
401
+
402
+ it("never rolls a refetched answer back with the OLD payload (a remount or re-render)", () => {
403
+ seedToolQuery(client, key, { n: 1 })
404
+ client.setQueryData(key, { n: 2 })
405
+ expect(seedToolQuery(client, key, { n: 1 })).toBe(false)
406
+ expect(client.getQueryData(key)).toEqual({ n: 2 })
407
+ })
408
+
409
+ it("a payload not seeded before is a new delivery and wins", () => {
410
+ seedToolQuery(client, key, { n: 1 })
411
+ client.setQueryData(key, { n: 2 })
412
+ expect(seedToolQuery(client, key, { n: 3 })).toBe(true)
413
+ expect(client.getQueryData(key)).toEqual({ n: 3 })
414
+ })
415
+
416
+ it("re-seeds an entry that was dropped, and keeps seeds per client and key", () => {
417
+ seedToolQuery(client, key, { n: 1 })
418
+ client.removeQueries({ queryKey: key })
419
+ expect(seedToolQuery(client, key, { n: 1 })).toBe(true)
420
+ const other = new QueryClient()
421
+ expect(seedToolQuery(other, key, { n: 1 })).toBe(true)
422
+ const otherKey = toolQueryKey(["incident"], "incident_data", { id: 2 })
423
+ expect(seedToolQuery(client, otherKey, { n: 1 })).toBe(true)
424
+ })
425
+
426
+ it("a payload that cannot be serialised is always a new delivery", () => {
427
+ const circular: Record<string, unknown> = {}
428
+ circular.self = circular
429
+ expect(seedToolQuery(client, key, circular)).toBe(true)
430
+ client.setQueryData(key, { n: 2 })
431
+ expect(seedToolQuery(client, key, circular)).toBe(true)
432
+ })
433
+ })
434
+
435
+ describe("settleEachCall (useToolMutation's per-call callbacks)", () => {
436
+ const ctx = () => ({ client, meta: undefined, mutationKey: undefined })
437
+
438
+ it("fires the callbacks of EVERY call, where TanStack's own mutate keeps only the latest", async () => {
439
+ const mutationFn = vi.fn((v: { id: number }) => Promise.resolve(`retried ${String(v.id)}`))
440
+ const observer = new MutationObserver(client, { mutationFn })
441
+ // Mounted, as `useMutation` keeps it: TanStack fires per-call callbacks only then.
442
+ observer.subscribe(() => {})
443
+ const tanstack = [vi.fn(), vi.fn()]
444
+ void observer.mutate({ id: 1 }, { onSuccess: tanstack[0] })
445
+ void observer.mutate({ id: 2 }, { onSuccess: tanstack[1] })
446
+ await vi.advanceTimersByTimeAsync(0)
447
+ expect(tanstack[0]).not.toHaveBeenCalled()
448
+ expect(tanstack[1]).toHaveBeenCalledOnce()
449
+
450
+ const ours = [vi.fn(), vi.fn()]
451
+ const settled = [vi.fn(), vi.fn()]
452
+ settleEachCall(
453
+ observer.mutate({ id: 1 }),
454
+ { id: 1 },
455
+ { onSuccess: ours[0], onSettled: settled[0] },
456
+ ctx,
457
+ )
458
+ settleEachCall(
459
+ observer.mutate({ id: 2 }),
460
+ { id: 2 },
461
+ { onSuccess: ours[1], onSettled: settled[1] },
462
+ ctx,
463
+ )
464
+ await vi.advanceTimersByTimeAsync(0)
465
+ expect(ours[0]).toHaveBeenCalledWith("retried 1", { id: 1 }, undefined, ctx())
466
+ expect(ours[1]).toHaveBeenCalledWith("retried 2", { id: 2 }, undefined, ctx())
467
+ expect(settled[0]).toHaveBeenCalledWith("retried 1", null, { id: 1 }, undefined, ctx())
468
+ expect(settled[1]).toHaveBeenCalledOnce()
469
+ })
470
+
471
+ it("a failed call fires onError and onSettled with the error, never an unhandled rejection", async () => {
472
+ const error = new Error("[409] already resolved")
473
+ const onError = vi.fn()
474
+ const onSettled = vi.fn()
475
+ const onSuccess = vi.fn()
476
+ settleEachCall(Promise.reject(error), { id: 1 }, { onError, onSettled, onSuccess }, ctx)
477
+ settleEachCall(Promise.reject(error), { id: 2 }, undefined, ctx)
478
+ await vi.advanceTimersByTimeAsync(0)
479
+ expect(onError).toHaveBeenCalledWith(error, { id: 1 }, undefined, ctx())
480
+ expect(onSettled).toHaveBeenCalledWith(undefined, error, { id: 1 }, undefined, ctx())
481
+ expect(onSuccess).not.toHaveBeenCalled()
482
+ })
483
+
484
+ it("skips the callbacks once the component unmounted (context null)", async () => {
485
+ const onSuccess = vi.fn()
486
+ const onError = vi.fn()
487
+ settleEachCall(Promise.resolve(1), {}, { onSuccess }, () => null)
488
+ settleEachCall(Promise.reject(new Error("x")), {}, { onError }, () => null)
489
+ await vi.advanceTimersByTimeAsync(0)
490
+ expect(onSuccess).not.toHaveBeenCalled()
491
+ expect(onError).not.toHaveBeenCalled()
492
+ })
493
+
494
+ it("any subset of callbacks is fine: a missing one is skipped, nothing surfaces", async () => {
495
+ const failedX = Promise.reject(new Error("x"))
496
+ const failedY = Promise.reject(new Error("y"))
497
+ const surfaced = vi.spyOn(Promise, "reject")
498
+ const onSettled = vi.fn()
499
+ const onSuccess = vi.fn()
500
+ settleEachCall(Promise.resolve(1), {}, { onSettled }, ctx)
501
+ settleEachCall(failedX, {}, { onSettled }, ctx)
502
+ settleEachCall(failedY, {}, { onSuccess }, ctx)
503
+ settleEachCall(Promise.resolve(2), {}, {}, ctx)
504
+ await vi.advanceTimersByTimeAsync(0)
505
+ expect(onSettled).toHaveBeenCalledTimes(2)
506
+ expect(onSuccess).not.toHaveBeenCalled()
507
+ expect(surfaced, "no callback threw").not.toHaveBeenCalled()
508
+ })
509
+
510
+ it("a throwing callback surfaces as a rejection without skipping the ones after it", async () => {
511
+ const surfaced = vi.spyOn(Promise, "reject").mockImplementation(() => Promise.resolve())
512
+ const onSettled = vi.fn()
513
+ const onSettledAfterError = vi.fn()
514
+ settleEachCall(
515
+ Promise.resolve(1),
516
+ {},
517
+ {
518
+ onSuccess: () => {
519
+ throw new Error("boom")
520
+ },
521
+ onSettled,
522
+ },
523
+ ctx,
524
+ )
525
+ settleEachCall(
526
+ new Promise((_, reject) => reject(new Error("call failed"))),
527
+ {},
528
+ {
529
+ onError: () => {
530
+ // eslint-disable-next-line @typescript-eslint/only-throw-error -- a callback may throw a non-Error; settle() must still surface it as an Error
531
+ throw "not an error"
532
+ },
533
+ onSettled: onSettledAfterError,
534
+ },
535
+ ctx,
536
+ )
537
+ await vi.advanceTimersByTimeAsync(0)
538
+ expect(onSettled).toHaveBeenCalledOnce()
539
+ expect(onSettledAfterError).toHaveBeenCalledOnce()
540
+ const reasons = surfaced.mock.calls.map(([reason]) => (reason as Error).message)
541
+ expect(reasons).toEqual(["boom", "not an error"])
542
+ })
543
+ })
544
+
545
+ describe("invalidateToolQueries (useToolMutation's invalidation)", () => {
546
+ it("refetches the active queries under a key, only marks the inactive ones stale", async () => {
547
+ const callTool = vi.fn<ToolCaller>(() => Promise.resolve(ok({ n: 1 })))
548
+ const active = observe(toolQueryOptions(["incidents"], "incidents_data", { page: 1 }, callTool))
549
+ const inactive = observe(
550
+ toolQueryOptions(["incidents"], "incidents_data", { page: 2 }, callTool),
551
+ )
552
+ observe(toolQueryOptions(["tasks"], "tasks_data", {}, callTool))
553
+ await vi.advanceTimersByTimeAsync(0)
554
+ inactive.unsubscribe()
555
+ callTool.mockClear()
556
+ await invalidateToolQueries(client, [["incidents"]])
557
+ expect(callTool).toHaveBeenCalledExactlyOnceWith("incidents_data", { page: 1 })
558
+ const page2 = toolQueryKey(["incidents"], "incidents_data", { page: 2 })
559
+ expect(client.getQueryState(page2)?.isInvalidated).toBe(true)
560
+ expect(active.observer.getCurrentResult().isFetching).toBe(false)
561
+ })
562
+
563
+ it("does nothing without keys", async () => {
564
+ const spy = vi.spyOn(client, "invalidateQueries")
565
+ await invalidateToolQueries(client, undefined)
566
+ await invalidateToolQueries(client, [])
567
+ expect(spy).not.toHaveBeenCalled()
568
+ })
569
+ })