@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,288 @@
1
+ import { createElement } from "react"
2
+ import { renderToStaticMarkup } from "react-dom/server"
3
+ import ts from "typescript"
4
+ import { afterEach, describe, expect, it, vi } from "vitest"
5
+ import { createFocusReturn, useFocusReturn, type AutoFocusEventLike } from "./focus-return.js"
6
+
7
+ /**
8
+ * Focus return of the kit's dialogs and sheets (CI §12 and §8 "Fokus im
9
+ * Dialog halten und danach zurückgeben"): the hand-back itself, and the
10
+ * gates that keep every overlay on it.
11
+ */
12
+
13
+ declare global {
14
+ interface ImportMeta {
15
+ glob(
16
+ pattern: string | string[],
17
+ options: { query: "?raw"; import: "default"; eager: true },
18
+ ): Record<string, string>
19
+ }
20
+ }
21
+
22
+ const element = (isConnected = true) => ({ isConnected, focus: vi.fn<() => void>() })
23
+ const BODY = { body: true }
24
+
25
+ function closeEvent(defaultPrevented = false): AutoFocusEventLike & {
26
+ preventDefault: ReturnType<typeof vi.fn<() => void>>
27
+ } {
28
+ return { defaultPrevented, preventDefault: vi.fn<() => void>() }
29
+ }
30
+
31
+ describe("createFocusReturn", () => {
32
+ it("hands the focus back to the element that had it as the overlay opened", () => {
33
+ const fr = createFocusReturn()
34
+ const opener = element()
35
+ fr.capture(opener, BODY)
36
+ const event = closeEvent()
37
+ fr.restore(event, true)
38
+ expect(opener.focus).toHaveBeenCalledOnce()
39
+ // Radix's own close handler (it focuses only a Trigger) must not run after it.
40
+ expect(event.preventDefault).toHaveBeenCalledOnce()
41
+ })
42
+
43
+ it("leaves Radix's default alone when the body had the focus (nothing to return to)", () => {
44
+ const fr = createFocusReturn()
45
+ fr.capture(null, BODY)
46
+ const nothing = closeEvent()
47
+ fr.restore(nothing, true)
48
+ expect(nothing.preventDefault).not.toHaveBeenCalled()
49
+
50
+ const body = { ...element(), ...BODY }
51
+ fr.capture(body, body)
52
+ const event = closeEvent()
53
+ fr.restore(event, true)
54
+ expect(body.focus).not.toHaveBeenCalled()
55
+ expect(event.preventDefault).not.toHaveBeenCalled()
56
+ })
57
+
58
+ it("does not focus an opener that left the document (Apply or Discard unmounted it)", () => {
59
+ const fr = createFocusReturn()
60
+ const opener = element(false)
61
+ fr.capture(opener, BODY)
62
+ const event = closeEvent()
63
+ fr.restore(event, true)
64
+ expect(opener.focus).not.toHaveBeenCalled()
65
+ expect(event.preventDefault).not.toHaveBeenCalled()
66
+ })
67
+
68
+ it("leaves the focus where it went when it did not get lost with the overlay", () => {
69
+ // A click outside a non-modal overlay, or a hand-off (Apply) that already moved it.
70
+ const fr = createFocusReturn()
71
+ const opener = element()
72
+ fr.capture(opener, BODY)
73
+ const event = closeEvent()
74
+ fr.restore(event, false)
75
+ expect(opener.focus).not.toHaveBeenCalled()
76
+ expect(event.preventDefault).not.toHaveBeenCalled()
77
+ })
78
+
79
+ it("yields to a consumer's close handler that prevented the default", () => {
80
+ const fr = createFocusReturn()
81
+ const opener = element()
82
+ fr.capture(opener, BODY)
83
+ fr.restore(closeEvent(true), true)
84
+ expect(opener.focus).not.toHaveBeenCalled()
85
+ })
86
+
87
+ it("returns the focus once per opening, to the latest opener", () => {
88
+ const fr = createFocusReturn()
89
+ const first = element()
90
+ const second = element()
91
+ fr.capture(first, BODY)
92
+ fr.capture(second, BODY)
93
+ fr.restore(closeEvent(), true)
94
+ fr.restore(closeEvent(), true)
95
+ expect(first.focus).not.toHaveBeenCalled()
96
+ expect(second.focus).toHaveBeenCalledOnce()
97
+ })
98
+ })
99
+
100
+ // -------------------------------------------------------------------------- //
101
+ // useFocusReturn: the order around the consumer's own handlers
102
+ // -------------------------------------------------------------------------- //
103
+
104
+ /** A Radix-like auto-focus event: `preventDefault()` sets `defaultPrevented`. */
105
+ function autoFocusEvent(): AutoFocusEventLike {
106
+ const event = {
107
+ defaultPrevented: false,
108
+ preventDefault: () => {
109
+ event.defaultPrevented = true
110
+ },
111
+ }
112
+ return event
113
+ }
114
+
115
+ type Handlers = ReturnType<typeof useFocusReturn<AutoFocusEventLike>>
116
+
117
+ /** The handler pair the hook hands a Radix content, from one SSR render. */
118
+ function handlersOf(consumer: Partial<Handlers>): Handlers {
119
+ let handlers: Handlers | undefined
120
+ function Probe() {
121
+ handlers = useFocusReturn(consumer)
122
+ return null
123
+ }
124
+ renderToStaticMarkup(createElement(Probe))
125
+ if (!handlers) throw new Error("the probe did not render")
126
+ return handlers
127
+ }
128
+
129
+ describe("useFocusReturn", () => {
130
+ const body = { tag: "body" }
131
+ const doc = { activeElement: null as unknown, body }
132
+ afterEach(() => {
133
+ vi.unstubAllGlobals()
134
+ })
135
+
136
+ it("notes the opener BEFORE the consumer's open handler can move the focus", () => {
137
+ vi.stubGlobal("document", doc)
138
+ const opener = element()
139
+ const elsewhere = element()
140
+ const handlers = handlersOf({
141
+ onOpenAutoFocus: () => {
142
+ doc.activeElement = elsewhere
143
+ },
144
+ })
145
+ doc.activeElement = opener
146
+ handlers.onOpenAutoFocus(autoFocusEvent())
147
+ doc.activeElement = body // the overlay closed with the focus inside it
148
+ const close = autoFocusEvent()
149
+ handlers.onCloseAutoFocus(close)
150
+ expect(opener.focus).toHaveBeenCalledOnce()
151
+ expect(elsewhere.focus).not.toHaveBeenCalled()
152
+ expect(close.defaultPrevented).toBe(true)
153
+ })
154
+
155
+ it("runs the consumer's close handler first: its preventDefault keeps the focus with it", () => {
156
+ vi.stubGlobal("document", doc)
157
+ const opener = element()
158
+ const onCloseAutoFocus = vi.fn((event: AutoFocusEventLike) => event.preventDefault())
159
+ const handlers = handlersOf({ onCloseAutoFocus })
160
+ doc.activeElement = opener
161
+ handlers.onOpenAutoFocus(autoFocusEvent())
162
+ doc.activeElement = body
163
+ handlers.onCloseAutoFocus(autoFocusEvent())
164
+ expect(onCloseAutoFocus).toHaveBeenCalledOnce()
165
+ expect(opener.focus).not.toHaveBeenCalled()
166
+ })
167
+
168
+ it("hands back on a lost focus only: the body, or none at all", () => {
169
+ vi.stubGlobal("document", doc)
170
+ const opener = element()
171
+ const handlers = handlersOf({})
172
+ for (const atClose of [body, null]) {
173
+ doc.activeElement = opener
174
+ handlers.onOpenAutoFocus(autoFocusEvent())
175
+ doc.activeElement = atClose
176
+ handlers.onCloseAutoFocus(autoFocusEvent())
177
+ }
178
+ expect(opener.focus).toHaveBeenCalledTimes(2)
179
+
180
+ // The focus already sits on another element: it stays there.
181
+ const moved = element()
182
+ doc.activeElement = opener
183
+ handlers.onOpenAutoFocus(autoFocusEvent())
184
+ doc.activeElement = moved
185
+ const close = autoFocusEvent()
186
+ handlers.onCloseAutoFocus(close)
187
+ expect(opener.focus).toHaveBeenCalledTimes(2)
188
+ expect(close.defaultPrevented).toBe(false)
189
+ })
190
+
191
+ it("notes no opener that cannot take the focus", () => {
192
+ vi.stubGlobal("document", doc)
193
+ const handlers = handlersOf({})
194
+ doc.activeElement = { isConnected: true } // no focus()
195
+ handlers.onOpenAutoFocus(autoFocusEvent())
196
+ doc.activeElement = body
197
+ const close = autoFocusEvent()
198
+ expect(() => handlers.onCloseAutoFocus(close)).not.toThrow()
199
+ expect(close.defaultPrevented).toBe(false)
200
+ })
201
+ })
202
+
203
+ // -------------------------------------------------------------------------- //
204
+ // Gates: every overlay of the kit goes through the hand-back
205
+ // -------------------------------------------------------------------------- //
206
+
207
+ const SOURCES = import.meta.glob(["../**/*.tsx", "!../**/*.test.tsx"], {
208
+ query: "?raw",
209
+ import: "default",
210
+ eager: true,
211
+ })
212
+
213
+ /** Names of the JSX attributes on every `<tag …>` in a source (spreads as `...expr`). */
214
+ function attributesOf(file: string, source: string, tag: string): string[][] {
215
+ const sf = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX)
216
+ const found: string[][] = []
217
+ const visit = (node: ts.Node) => {
218
+ if (
219
+ (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) &&
220
+ node.tagName.getText(sf) === tag
221
+ ) {
222
+ found.push(
223
+ node.attributes.properties.map((p) =>
224
+ ts.isJsxAttribute(p) ? p.name.getText(sf) : `...${p.expression.getText(sf)}`,
225
+ ),
226
+ )
227
+ }
228
+ ts.forEachChild(node, visit)
229
+ }
230
+ visit(sf)
231
+ return found
232
+ }
233
+
234
+ /** Whether a source puts `autoFocus` on any JSX element. */
235
+ function hasAutoFocus(file: string, source: string): boolean {
236
+ const sf = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX)
237
+ let found = false
238
+ const visit = (node: ts.Node) => {
239
+ if (ts.isJsxAttribute(node) && node.name.getText(sf) === "autoFocus") found = true
240
+ ts.forEachChild(node, visit)
241
+ }
242
+ visit(sf)
243
+ return found
244
+ }
245
+
246
+ describe("overlay focus gates", () => {
247
+ it("scans the kit's components", () => {
248
+ expect(Object.keys(SOURCES)).toContain("../primitives/dialog.tsx")
249
+ expect(Object.keys(SOURCES)).toContain("../app/builder/DiscardDialog.tsx")
250
+ })
251
+
252
+ it("imports Radix's Dialog only in the two primitives that add the hand-back", () => {
253
+ const importers = Object.entries(SOURCES)
254
+ .filter(([, src]) =>
255
+ /import\s*\{[^}]*\b(?:Alert)?Dialog\b[^}]*\}\s*from\s*"radix-ui"/.test(src),
256
+ )
257
+ .map(([file]) => file)
258
+ .sort()
259
+ expect(importers).toEqual(["../primitives/dialog.tsx", "../primitives/sheet.tsx"])
260
+ })
261
+
262
+ it("spreads the hand-back into the Radix content AFTER the consumer's props", () => {
263
+ for (const [file, primitive] of [
264
+ ["../primitives/dialog.tsx", "DialogPrimitive.Content"],
265
+ ["../primitives/sheet.tsx", "SheetPrimitive.Content"],
266
+ ] as const) {
267
+ const contents = attributesOf(file, SOURCES[file] ?? "", primitive)
268
+ expect(contents, file).toHaveLength(1)
269
+ const attrs = contents[0] ?? []
270
+ expect(attrs.indexOf("...focusReturn"), file).toBeGreaterThan(attrs.indexOf("...props"))
271
+ expect(SOURCES[file], file).toMatch(
272
+ /useFocusReturn\(\{ onOpenAutoFocus, onCloseAutoFocus \}\)/,
273
+ )
274
+ }
275
+ })
276
+
277
+ it("lets no overlay content take the focus itself (Radix would skip noting the opener)", () => {
278
+ const overlays = Object.entries(SOURCES).filter(([, src]) =>
279
+ /<(DialogContent|SheetContent)\b/.test(src),
280
+ )
281
+ expect(overlays.length).toBeGreaterThan(3)
282
+ expect(overlays.filter(([file, src]) => hasAutoFocus(file, src)).map(([file]) => file)).toEqual(
283
+ [],
284
+ )
285
+ expect(hasAutoFocus("self.tsx", "const A = () => <input autoFocus />")).toBe(true)
286
+ expect(hasAutoFocus("self.tsx", "const autoFocus = 1")).toBe(false)
287
+ })
288
+ })
@@ -0,0 +1,96 @@
1
+ import { useState } from "react"
2
+
3
+ /**
4
+ * Focus return for the kit's dialogs and sheets (CI §12: focus goes back to
5
+ * where it came from).
6
+ *
7
+ * A Radix dialog returns the focus only to its `Trigger`. The kit's overlays
8
+ * mostly open from React state, without one (the builder's Save, Discard and
9
+ * Catalogue, a cell's props sheet), and Radix then cancels its focus scope's
10
+ * own restore and focuses nothing: closing them by keyboard left the focus on
11
+ * the document body. `DialogContent` and `SheetContent` therefore note the
12
+ * element that has the focus as they open and hand the focus back to it when
13
+ * they close and the focus went with them, while that element is still in the
14
+ * document. A consumer's own `onCloseAutoFocus` that calls `preventDefault()`
15
+ * keeps full control.
16
+ */
17
+
18
+ /** The part of an element the hand-back needs (structural, so it tests without a DOM). */
19
+ export interface FocusReturnTarget {
20
+ readonly isConnected: boolean
21
+ focus: () => void
22
+ }
23
+
24
+ /** The part of Radix's auto-focus events the hand-back reads. */
25
+ export interface AutoFocusEventLike {
26
+ readonly defaultPrevented: boolean
27
+ preventDefault: () => void
28
+ }
29
+
30
+ /** One overlay's hand-back, from {@link createFocusReturn}. */
31
+ export interface FocusReturn {
32
+ /**
33
+ * Notes the element that has the focus as the overlay opens. The document
34
+ * body is no target: a pointer that opened it without focusing anything
35
+ * (Safari does not focus a clicked button) leaves Radix's default in place.
36
+ */
37
+ capture: (active: FocusReturnTarget | null, body: unknown) => void
38
+ /**
39
+ * Hands the focus back to the noted element when the overlay closes and the
40
+ * focus went with it (`focusLost`: it sits on the body). It leaves the focus
41
+ * where it is when a handler before it prevented that, when the focus
42
+ * already sits elsewhere (a click outside a non-modal overlay, a hand-off
43
+ * that moved it) or when the element left the document (an Apply or
44
+ * Discard that unmounted the builder). Forgets the element either way.
45
+ */
46
+ restore: (event: AutoFocusEventLike, focusLost: boolean) => void
47
+ }
48
+
49
+ /** A {@link FocusReturn} with nothing noted yet. */
50
+ export function createFocusReturn(): FocusReturn {
51
+ let opener: FocusReturnTarget | null = null
52
+ return {
53
+ capture: (active, body) => {
54
+ opener = active === body ? null : active
55
+ },
56
+ restore: (event, focusLost) => {
57
+ const target = opener
58
+ opener = null
59
+ if (event.defaultPrevented || !focusLost || target === null || !target.isConnected) return
60
+ event.preventDefault()
61
+ target.focus()
62
+ },
63
+ }
64
+ }
65
+
66
+ /**
67
+ * The `onOpenAutoFocus` / `onCloseAutoFocus` pair of a Radix dialog content
68
+ * with the focus hand-back of {@link createFocusReturn} composed in: it notes
69
+ * the opener BEFORE the consumer's open handler runs (that one may move the
70
+ * focus) and hands it back AFTER the consumer's close handler (whose
71
+ * `preventDefault()` keeps it out). Radix fires the open event only when the
72
+ * focus is outside the content as it mounts, so an element inside it must not
73
+ * take the focus itself (`autoFocus`): Radix focuses the first one anyway.
74
+ */
75
+ export function useFocusReturn<E extends AutoFocusEventLike>(handlers: {
76
+ onOpenAutoFocus?: (event: E) => void
77
+ onCloseAutoFocus?: (event: E) => void
78
+ }): { onOpenAutoFocus: (event: E) => void; onCloseAutoFocus: (event: E) => void } {
79
+ const [focusReturn] = useState(createFocusReturn)
80
+ return {
81
+ onOpenAutoFocus: (event) => {
82
+ focusReturn.capture(activeElementOf(document), document.body)
83
+ handlers.onOpenAutoFocus?.(event)
84
+ },
85
+ onCloseAutoFocus: (event) => {
86
+ handlers.onCloseAutoFocus?.(event)
87
+ const active = document.activeElement
88
+ focusReturn.restore(event, active === null || active === document.body)
89
+ },
90
+ }
91
+ }
92
+
93
+ function activeElementOf(doc: Document): FocusReturnTarget | null {
94
+ const active = doc.activeElement
95
+ return active && "focus" in active ? (active as HTMLElement) : null
96
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The toolkit's focus indicator as Tailwind classes: an opaque 2px ring in the
3
+ * `--focus` role (blau-link on light, blau-hell on dark — 3:1 or more against
4
+ * every surface, see `theme/theme-css.test.ts`), offset from the control so it
5
+ * reads on any fill. Every interactive kit control uses it; spread it into a
6
+ * custom control's `className` so keyboard focus looks the same everywhere.
7
+ * (`theme.css` also gives every element without its own ring an opaque
8
+ * `:focus-visible` outline in the same colour.)
9
+ *
10
+ * The ring is a box-shadow, which forced-colors mode (Windows High Contrast)
11
+ * drops. `outline-hidden` — never `outline-none` — hides the outline only
12
+ * outside that mode and keeps a transparent 2px outline in it, which the
13
+ * browser paints in the system colour, so focus stays visible there too.
14
+ */
15
+ export const FOCUS_RING =
16
+ "outline-hidden focus-visible:ring-2 focus-visible:ring-focus focus-visible:ring-offset-2 focus-visible:ring-offset-background"
17
+
18
+ /** {@link FOCUS_RING} drawn inside the element — for cells and rows flush with a container edge. */
19
+ export const FOCUS_RING_INSET =
20
+ "outline-hidden focus-visible:ring-2 focus-visible:ring-focus focus-visible:ring-inset"
@@ -0,0 +1,59 @@
1
+ import { describe, expect, it } from "vitest"
2
+ import { DENSE_ICON_STROKE, ICON_STROKE } from "./icon-stroke.js"
3
+
4
+ /**
5
+ * ONE mechanism for the Lucide stroke (CI §11): `LucideProvider` with the two
6
+ * constants of `icon-stroke.ts` — 2.5 for the view (`McpAppView`), 2 in dense
7
+ * chrome bars. No CSS rule (`svg.lucide { stroke-width }`) in the theme, and no
8
+ * per-icon `strokeWidth` in kit code, so the two can never disagree.
9
+ */
10
+
11
+ declare global {
12
+ interface ImportMeta {
13
+ glob(
14
+ pattern: string | string[],
15
+ options: { query: "?raw"; import: "default"; eager: true },
16
+ ): Record<string, string>
17
+ }
18
+ }
19
+
20
+ const STYLESHEETS = import.meta.glob(["../*.css", "../theme/*.css"], {
21
+ query: "?raw",
22
+ import: "default",
23
+ eager: true,
24
+ })
25
+
26
+ const SOURCES = import.meta.glob(["../**/*.tsx", "!../**/*.test.tsx", "!../render-cases*.tsx"], {
27
+ query: "?raw",
28
+ import: "default",
29
+ eager: true,
30
+ })
31
+
32
+ describe("Lucide stroke: one mechanism", () => {
33
+ it("is 2.5 for the view and 2 in dense bars (CI §11)", () => {
34
+ expect(ICON_STROKE).toBe(2.5)
35
+ expect(DENSE_ICON_STROKE).toBe(2)
36
+ })
37
+
38
+ it("is never set in CSS (theme.css, globals.css, fonts.css, the vendored tokens)", () => {
39
+ expect(Object.keys(STYLESHEETS)).toEqual(
40
+ expect.arrayContaining(["../theme.css", "../globals.css"]),
41
+ )
42
+ for (const [file, css] of Object.entries(STYLESHEETS)) {
43
+ expect(css, file).not.toMatch(/stroke-width/)
44
+ }
45
+ })
46
+
47
+ it("is set only on a LucideProvider, from the icon-stroke constants", () => {
48
+ const uses: string[] = []
49
+ for (const [file, source] of Object.entries(SOURCES)) {
50
+ for (const m of source.matchAll(/<(\w+)[^<>]*\bstrokeWidth=\{?([^}\s>]+)\}?/g)) {
51
+ uses.push(`${file}: <${m[1]} strokeWidth=${m[2]}>`)
52
+ expect(m[1], file).toBe("LucideProvider")
53
+ expect(["ICON_STROKE", "DENSE_ICON_STROKE"], file).toContain(m[2])
54
+ }
55
+ }
56
+ // The view and the three dense bars (view toolbar, builder toolbar, pipeline strip).
57
+ expect(uses.length).toBeGreaterThanOrEqual(4)
58
+ })
59
+ })
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Lucide stroke widths of the toolkit's chrome (CI §11, on the 24 px grid).
3
+ * Set once through `LucideProvider`: `McpAppView` applies {@link ICON_STROKE}
4
+ * to everything it renders (its toast stack included), dense bars narrow it
5
+ * to {@link DENSE_ICON_STROKE}. Icons of a consumer's own `lucide-react` follow
6
+ * the provider only when they resolve to the same module instance (dedupe it).
7
+ */
8
+
9
+ /** Default stroke of the toolkit's icons. */
10
+ export const ICON_STROKE = 2.5
11
+
12
+ /** Stroke in dense chrome bars (the view toolbar, the builder's bars). */
13
+ export const DENSE_ICON_STROKE = 2
@@ -1,5 +1,10 @@
1
1
  import { describe, expect, it } from "vitest"
2
- import { parseToolResult, parseViewToolResult } from "./parse-tool-result.js"
2
+ import {
3
+ isToolError,
4
+ parseToolResult,
5
+ parseViewToolResult,
6
+ ToolResultError,
7
+ } from "./parse-tool-result.js"
3
8
 
4
9
  describe("parseToolResult (structuredContent-first contract)", () => {
5
10
  it("prefers structuredContent over the text channel", () => {
@@ -137,3 +142,43 @@ describe("parseViewToolResult (show-tool envelope unwrap)", () => {
137
142
  expect(() => parseViewToolResult(result)).toThrowError("boom")
138
143
  })
139
144
  })
145
+
146
+ describe("ToolResultError / isToolError (the query layer's no-retry signal)", () => {
147
+ it("an isError result throws a ToolResultError that carries the result", () => {
148
+ const result = { isError: true, content: [{ type: "text", text: "[404] gone" }] }
149
+ let thrown: unknown
150
+ try {
151
+ parseToolResult(result)
152
+ } catch (err) {
153
+ thrown = err
154
+ }
155
+ expect(thrown).toBeInstanceOf(ToolResultError)
156
+ expect(thrown).toBeInstanceOf(Error)
157
+ expect(thrown).toMatchObject({ name: "ToolResultError", message: "[404] gone", result })
158
+ expect(isToolError(thrown)).toBe(true)
159
+ })
160
+
161
+ it("recognises mcp-use's ToolError by its name and its isError result", () => {
162
+ const toolError = Object.assign(new Error("busy"), {
163
+ name: "ToolError",
164
+ result: { isError: true, content: [] },
165
+ })
166
+ expect(isToolError(toolError)).toBe(true)
167
+ })
168
+
169
+ it("anything else is no tool error: a transport failure, a look-alike, a non-error", () => {
170
+ expect(isToolError(new Error("connection lost"))).toBe(false)
171
+ expect(isToolError(Object.assign(new Error("x"), { name: "ToolError" }))).toBe(false)
172
+ expect(
173
+ isToolError(Object.assign(new Error("x"), { name: "ToolError", result: { isError: false } })),
174
+ ).toBe(false)
175
+ expect(isToolError(Object.assign(new Error("x"), { name: "ToolError", result: null }))).toBe(
176
+ false,
177
+ )
178
+ expect(
179
+ isToolError(Object.assign(new Error("x"), { name: "Other", result: { isError: true } })),
180
+ ).toBe(false)
181
+ expect(isToolError({ name: "ToolError", result: { isError: true } })).toBe(false)
182
+ expect(isToolError(undefined)).toBe(false)
183
+ })
184
+ })
@@ -16,10 +16,48 @@ export interface ParseToolResultOptions {
16
16
  prefer?: "structured" | "text"
17
17
  }
18
18
 
19
+ /**
20
+ * A tool answered with an `isError` result: a deterministic failure of the
21
+ * call itself (a validation error, an engine 404), not of the transport.
22
+ * Thrown by {@link parseToolResult}. The query hooks retry it like any
23
+ * failure by default (the client's `retry`); pass `retry: shouldRetryToolCall`
24
+ * to stop that, since repeating the call repeats the answer. Still an `Error`
25
+ * with the tool's first text block as its message.
26
+ */
27
+ export class ToolResultError extends Error {
28
+ /** The `isError` result as the tool returned it. */
29
+ readonly result: unknown
30
+
31
+ constructor(message: string, result: unknown) {
32
+ super(message)
33
+ this.name = "ToolResultError"
34
+ this.result = result
35
+ }
36
+ }
37
+
38
+ function isRecord(value: unknown): value is Record<string, unknown> {
39
+ return typeof value === "object" && value !== null
40
+ }
41
+
42
+ /**
43
+ * Whether `error` is a tool's own `isError` answer: a {@link ToolResultError},
44
+ * or mcp-use's `ToolError` (how `useDynamicTool` rejects an `isError` result
45
+ * inside a view), recognised by its name and result so the root barrel never
46
+ * imports `mcp-use/react`. Transport failures (a lost connection, a JSON-RPC
47
+ * error such as an unknown tool) are not tool errors.
48
+ */
49
+ export function isToolError(error: unknown): boolean {
50
+ if (error instanceof ToolResultError) return true
51
+ if (!(error instanceof Error) || error.name !== "ToolError") return false
52
+ const result: unknown = (error as Error & { result?: unknown }).result
53
+ return isRecord(result) && result.isError === true
54
+ }
55
+
19
56
  /**
20
57
  * Decodes an MCP tool result down to its data payload.
21
58
  *
22
- * Default (`prefer: "structured"`): `isError` → throw the first text block,
59
+ * Default (`prefer: "structured"`): `isError` → throw a
60
+ * {@link ToolResultError} with the first text block,
23
61
  * then `structuredContent` (if non-null), then JSON-decode the first text
24
62
  * block (verbatim string if it isn't JSON), then the raw result.
25
63
  *
@@ -37,7 +75,7 @@ export function parseToolResult<T = unknown>(result: unknown, opts?: ParseToolRe
37
75
  isError?: boolean
38
76
  }
39
77
  | undefined
40
- if (r?.isError) throw new Error(r.content?.[0]?.text ?? "Tool call failed")
78
+ if (r?.isError) throw new ToolResultError(r.content?.[0]?.text ?? "Tool call failed", result)
41
79
 
42
80
  const fromStructured = (): T | undefined =>
43
81
  r?.structuredContent != null ? (r.structuredContent as T) : undefined