@miragon/mcp-toolkit-ui 2.7.0 → 2.8.1

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 (388) hide show
  1. package/README.md +7 -4
  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.js +1 -1
  7. package/dist/app/builder/BuilderBanners.js.map +1 -1
  8. package/dist/app/builder/CanvasRow.d.ts +4 -2
  9. package/dist/app/builder/CanvasRow.d.ts.map +1 -1
  10. package/dist/app/builder/CanvasRow.js +21 -7
  11. package/dist/app/builder/CanvasRow.js.map +1 -1
  12. package/dist/app/builder/CatalogueSheet.d.ts.map +1 -1
  13. package/dist/app/builder/CatalogueSheet.js +11 -4
  14. package/dist/app/builder/CatalogueSheet.js.map +1 -1
  15. package/dist/app/builder/CellPropsSheet.d.ts +7 -3
  16. package/dist/app/builder/CellPropsSheet.d.ts.map +1 -1
  17. package/dist/app/builder/CellPropsSheet.js +10 -3
  18. package/dist/app/builder/CellPropsSheet.js.map +1 -1
  19. package/dist/app/builder/DiscardDialog.d.ts +18 -0
  20. package/dist/app/builder/DiscardDialog.d.ts.map +1 -0
  21. package/dist/app/builder/DiscardDialog.js +18 -0
  22. package/dist/app/builder/DiscardDialog.js.map +1 -0
  23. package/dist/app/builder/LayoutSurface.d.ts +33 -9
  24. package/dist/app/builder/LayoutSurface.d.ts.map +1 -1
  25. package/dist/app/builder/LayoutSurface.js +74 -23
  26. package/dist/app/builder/LayoutSurface.js.map +1 -1
  27. package/dist/app/builder/PipelineStrip.d.ts +2 -1
  28. package/dist/app/builder/PipelineStrip.d.ts.map +1 -1
  29. package/dist/app/builder/PipelineStrip.js +26 -13
  30. package/dist/app/builder/PipelineStrip.js.map +1 -1
  31. package/dist/app/builder/SaveDialog.d.ts +9 -1
  32. package/dist/app/builder/SaveDialog.d.ts.map +1 -1
  33. package/dist/app/builder/SaveDialog.js +8 -2
  34. package/dist/app/builder/SaveDialog.js.map +1 -1
  35. package/dist/app/builder/Toolbar.d.ts +12 -4
  36. package/dist/app/builder/Toolbar.d.ts.map +1 -1
  37. package/dist/app/builder/Toolbar.js +18 -7
  38. package/dist/app/builder/Toolbar.js.map +1 -1
  39. package/dist/app/builder/Workspace.d.ts +23 -3
  40. package/dist/app/builder/Workspace.d.ts.map +1 -1
  41. package/dist/app/builder/Workspace.js +58 -8
  42. package/dist/app/builder/Workspace.js.map +1 -1
  43. package/dist/app/builder/builder-model.d.ts +143 -1
  44. package/dist/app/builder/builder-model.d.ts.map +1 -1
  45. package/dist/app/builder/builder-model.js +191 -1
  46. package/dist/app/builder/builder-model.js.map +1 -1
  47. package/dist/app/builder/reveal.d.ts +10 -0
  48. package/dist/app/builder/reveal.d.ts.map +1 -0
  49. package/dist/app/builder/reveal.js +10 -0
  50. package/dist/app/builder/reveal.js.map +1 -0
  51. package/dist/app/builder/use-builder-catalogue.d.ts +4 -1
  52. package/dist/app/builder/use-builder-catalogue.d.ts.map +1 -1
  53. package/dist/app/builder/use-builder-catalogue.js +44 -17
  54. package/dist/app/builder/use-builder-catalogue.js.map +1 -1
  55. package/dist/app/builder/use-builder-session.d.ts +30 -0
  56. package/dist/app/builder/use-builder-session.d.ts.map +1 -0
  57. package/dist/app/builder/use-builder-session.js +58 -0
  58. package/dist/app/builder/use-builder-session.js.map +1 -0
  59. package/dist/app/builder/use-display-resolver.d.ts +17 -0
  60. package/dist/app/builder/use-display-resolver.d.ts.map +1 -0
  61. package/dist/app/builder/use-display-resolver.js +16 -0
  62. package/dist/app/builder/use-display-resolver.js.map +1 -0
  63. package/dist/app/index.d.ts +8 -3
  64. package/dist/app/index.d.ts.map +1 -1
  65. package/dist/app/index.js +8 -2
  66. package/dist/app/index.js.map +1 -1
  67. package/dist/app/layout-builder.d.ts +13 -25
  68. package/dist/app/layout-builder.d.ts.map +1 -1
  69. package/dist/app/layout-builder.js +75 -81
  70. package/dist/app/layout-builder.js.map +1 -1
  71. package/dist/app/mcp-app-view-chrome.d.ts +12 -3
  72. package/dist/app/mcp-app-view-chrome.d.ts.map +1 -1
  73. package/dist/app/mcp-app-view-chrome.js +21 -5
  74. package/dist/app/mcp-app-view-chrome.js.map +1 -1
  75. package/dist/app/mcp-app-view-states.d.ts +11 -3
  76. package/dist/app/mcp-app-view-states.d.ts.map +1 -1
  77. package/dist/app/mcp-app-view-states.js +29 -8
  78. package/dist/app/mcp-app-view-states.js.map +1 -1
  79. package/dist/app/mcp-app-view.d.ts +33 -1
  80. package/dist/app/mcp-app-view.d.ts.map +1 -1
  81. package/dist/app/mcp-app-view.js +181 -127
  82. package/dist/app/mcp-app-view.js.map +1 -1
  83. package/dist/app/mcp-toolkit-app.d.ts +6 -1
  84. package/dist/app/mcp-toolkit-app.d.ts.map +1 -1
  85. package/dist/app/mcp-toolkit-app.js +8 -1
  86. package/dist/app/mcp-toolkit-app.js.map +1 -1
  87. package/dist/app/mcp-use-host-bridge.d.ts +15 -6
  88. package/dist/app/mcp-use-host-bridge.d.ts.map +1 -1
  89. package/dist/app/mcp-use-host-bridge.js +51 -22
  90. package/dist/app/mcp-use-host-bridge.js.map +1 -1
  91. package/dist/app/model-context.d.ts +71 -0
  92. package/dist/app/model-context.d.ts.map +1 -0
  93. package/dist/app/model-context.js +101 -0
  94. package/dist/app/model-context.js.map +1 -0
  95. package/dist/app/use-build-mode-focus.d.ts +33 -0
  96. package/dist/app/use-build-mode-focus.d.ts.map +1 -0
  97. package/dist/app/use-build-mode-focus.js +59 -0
  98. package/dist/app/use-build-mode-focus.js.map +1 -0
  99. package/dist/app/use-view-actions.d.ts +31 -0
  100. package/dist/app/use-view-actions.d.ts.map +1 -0
  101. package/dist/app/use-view-actions.js +81 -0
  102. package/dist/app/use-view-actions.js.map +1 -0
  103. package/dist/app/use-view-lifecycle.d.ts +4 -0
  104. package/dist/app/use-view-lifecycle.d.ts.map +1 -1
  105. package/dist/app/use-view-lifecycle.js +2 -0
  106. package/dist/app/use-view-lifecycle.js.map +1 -1
  107. package/dist/app/view-lifecycle.d.ts +80 -4
  108. package/dist/app/view-lifecycle.d.ts.map +1 -1
  109. package/dist/app/view-lifecycle.js +90 -3
  110. package/dist/app/view-lifecycle.js.map +1 -1
  111. package/dist/app/widget-fixture.d.ts +10 -3
  112. package/dist/app/widget-fixture.d.ts.map +1 -1
  113. package/dist/app/widget-fixture.js +21 -8
  114. package/dist/app/widget-fixture.js.map +1 -1
  115. package/dist/app/widget-props-sheet.d.ts +3 -1
  116. package/dist/app/widget-props-sheet.d.ts.map +1 -1
  117. package/dist/app/widget-props-sheet.js +22 -10
  118. package/dist/app/widget-props-sheet.js.map +1 -1
  119. package/dist/app/widget-renderer.js +1 -1
  120. package/dist/app/widget-renderer.js.map +1 -1
  121. package/dist/components/DrillButton.d.ts +14 -6
  122. package/dist/components/DrillButton.d.ts.map +1 -1
  123. package/dist/components/DrillButton.js +14 -7
  124. package/dist/components/DrillButton.js.map +1 -1
  125. package/dist/components/EmptyState.d.ts.map +1 -1
  126. package/dist/components/EmptyState.js +4 -2
  127. package/dist/components/EmptyState.js.map +1 -1
  128. package/dist/components/ErrorState.d.ts +5 -2
  129. package/dist/components/ErrorState.d.ts.map +1 -1
  130. package/dist/components/ErrorState.js +25 -8
  131. package/dist/components/ErrorState.js.map +1 -1
  132. package/dist/components/FilterBar.d.ts +15 -4
  133. package/dist/components/FilterBar.d.ts.map +1 -1
  134. package/dist/components/FilterBar.js +11 -6
  135. package/dist/components/FilterBar.js.map +1 -1
  136. package/dist/components/KpiGrid.d.ts +66 -21
  137. package/dist/components/KpiGrid.d.ts.map +1 -1
  138. package/dist/components/KpiGrid.js +101 -34
  139. package/dist/components/KpiGrid.js.map +1 -1
  140. package/dist/components/ListFooter.d.ts +31 -4
  141. package/dist/components/ListFooter.d.ts.map +1 -1
  142. package/dist/components/ListFooter.js +30 -11
  143. package/dist/components/ListFooter.js.map +1 -1
  144. package/dist/components/PagedListFooter.d.ts +57 -0
  145. package/dist/components/PagedListFooter.d.ts.map +1 -0
  146. package/dist/components/PagedListFooter.js +27 -0
  147. package/dist/components/PagedListFooter.js.map +1 -0
  148. package/dist/components/Pills.d.ts +27 -7
  149. package/dist/components/Pills.d.ts.map +1 -1
  150. package/dist/components/Pills.js +21 -6
  151. package/dist/components/Pills.js.map +1 -1
  152. package/dist/components/SectionHeading.d.ts +3 -1
  153. package/dist/components/SectionHeading.d.ts.map +1 -1
  154. package/dist/components/SectionHeading.js +6 -2
  155. package/dist/components/SectionHeading.js.map +1 -1
  156. package/dist/components/Toast.d.ts +9 -3
  157. package/dist/components/Toast.d.ts.map +1 -1
  158. package/dist/components/Toast.js +31 -16
  159. package/dist/components/Toast.js.map +1 -1
  160. package/dist/components/WidgetHeader.d.ts +34 -10
  161. package/dist/components/WidgetHeader.d.ts.map +1 -1
  162. package/dist/components/WidgetHeader.js +11 -6
  163. package/dist/components/WidgetHeader.js.map +1 -1
  164. package/dist/hooks/index.d.ts +4 -4
  165. package/dist/hooks/index.d.ts.map +1 -1
  166. package/dist/hooks/index.js +1 -1
  167. package/dist/hooks/index.js.map +1 -1
  168. package/dist/hooks/tool-result-recovery-model.d.ts +155 -0
  169. package/dist/hooks/tool-result-recovery-model.d.ts.map +1 -0
  170. package/dist/hooks/tool-result-recovery-model.js +273 -0
  171. package/dist/hooks/tool-result-recovery-model.js.map +1 -0
  172. package/dist/hooks/use-tool-query.d.ts +56 -14
  173. package/dist/hooks/use-tool-query.d.ts.map +1 -1
  174. package/dist/hooks/use-tool-query.js +102 -25
  175. package/dist/hooks/use-tool-query.js.map +1 -1
  176. package/dist/hooks/use-tool-result-recovery.d.ts +47 -52
  177. package/dist/hooks/use-tool-result-recovery.d.ts.map +1 -1
  178. package/dist/hooks/use-tool-result-recovery.js +58 -120
  179. package/dist/hooks/use-tool-result-recovery.js.map +1 -1
  180. package/dist/hooks/use-view-data.d.ts +52 -10
  181. package/dist/hooks/use-view-data.d.ts.map +1 -1
  182. package/dist/hooks/use-view-data.js +81 -16
  183. package/dist/hooks/use-view-data.js.map +1 -1
  184. package/dist/hooks/use-view-tool-query.d.ts +12 -7
  185. package/dist/hooks/use-view-tool-query.d.ts.map +1 -1
  186. package/dist/hooks/use-view-tool-query.js +15 -14
  187. package/dist/hooks/use-view-tool-query.js.map +1 -1
  188. package/dist/index.d.ts +17 -10
  189. package/dist/index.d.ts.map +1 -1
  190. package/dist/index.js +18 -6
  191. package/dist/index.js.map +1 -1
  192. package/dist/lib/focus-return.d.ts +61 -0
  193. package/dist/lib/focus-return.d.ts.map +1 -0
  194. package/dist/lib/focus-return.js +46 -0
  195. package/dist/lib/focus-return.js.map +1 -0
  196. package/dist/lib/parse-tool-result.d.ts +23 -1
  197. package/dist/lib/parse-tool-result.d.ts.map +1 -1
  198. package/dist/lib/parse-tool-result.js +38 -2
  199. package/dist/lib/parse-tool-result.js.map +1 -1
  200. package/dist/lib/toast-model.d.ts +41 -1
  201. package/dist/lib/toast-model.d.ts.map +1 -1
  202. package/dist/lib/toast-model.js +67 -1
  203. package/dist/lib/toast-model.js.map +1 -1
  204. package/dist/lib/tone-utils.d.ts +58 -6
  205. package/dist/lib/tone-utils.d.ts.map +1 -1
  206. package/dist/lib/tone-utils.js +66 -4
  207. package/dist/lib/tone-utils.js.map +1 -1
  208. package/dist/lib/tool-query.d.ts +107 -0
  209. package/dist/lib/tool-query.d.ts.map +1 -0
  210. package/dist/lib/tool-query.js +182 -0
  211. package/dist/lib/tool-query.js.map +1 -0
  212. package/dist/lib/tool-result-observer.d.ts +108 -0
  213. package/dist/lib/tool-result-observer.d.ts.map +1 -0
  214. package/dist/lib/tool-result-observer.js +158 -0
  215. package/dist/lib/tool-result-observer.js.map +1 -0
  216. package/dist/lib/typography.d.ts +37 -0
  217. package/dist/lib/typography.d.ts.map +1 -0
  218. package/dist/lib/typography.js +41 -0
  219. package/dist/lib/typography.js.map +1 -0
  220. package/dist/lib/utils.d.ts.map +1 -1
  221. package/dist/lib/utils.js +11 -1
  222. package/dist/lib/utils.js.map +1 -1
  223. package/dist/locale/index.d.ts +1 -1
  224. package/dist/locale/index.d.ts.map +1 -1
  225. package/dist/locale/message-types.d.ts +117 -4
  226. package/dist/locale/message-types.d.ts.map +1 -1
  227. package/dist/locale/messages-de.d.ts.map +1 -1
  228. package/dist/locale/messages-de.js +44 -10
  229. package/dist/locale/messages-de.js.map +1 -1
  230. package/dist/locale/messages-en.d.ts.map +1 -1
  231. package/dist/locale/messages-en.js +44 -10
  232. package/dist/locale/messages-en.js.map +1 -1
  233. package/dist/locale/toolkit-messages.d.ts +19 -1
  234. package/dist/locale/toolkit-messages.d.ts.map +1 -1
  235. package/dist/locale/toolkit-messages.js +54 -1
  236. package/dist/locale/toolkit-messages.js.map +1 -1
  237. package/dist/primitives/alert.d.ts +1 -1
  238. package/dist/primitives/alert.d.ts.map +1 -1
  239. package/dist/primitives/alert.js +2 -0
  240. package/dist/primitives/alert.js.map +1 -1
  241. package/dist/primitives/badge.d.ts +1 -1
  242. package/dist/primitives/badge.d.ts.map +1 -1
  243. package/dist/primitives/badge.js +9 -4
  244. package/dist/primitives/badge.js.map +1 -1
  245. package/dist/primitives/button.d.ts +1 -1
  246. package/dist/primitives/button.js +1 -1
  247. package/dist/primitives/button.js.map +1 -1
  248. package/dist/primitives/dialog.d.ts +1 -1
  249. package/dist/primitives/dialog.d.ts.map +1 -1
  250. package/dist/primitives/dialog.js +10 -3
  251. package/dist/primitives/dialog.js.map +1 -1
  252. package/dist/primitives/select.js +1 -1
  253. package/dist/primitives/select.js.map +1 -1
  254. package/dist/primitives/sheet.d.ts +1 -1
  255. package/dist/primitives/sheet.d.ts.map +1 -1
  256. package/dist/primitives/sheet.js +6 -2
  257. package/dist/primitives/sheet.js.map +1 -1
  258. package/dist/providers/query-provider.d.ts +32 -1
  259. package/dist/providers/query-provider.d.ts.map +1 -1
  260. package/dist/providers/query-provider.js +53 -4
  261. package/dist/providers/query-provider.js.map +1 -1
  262. package/dist/render-cases-primitives.js +1 -1
  263. package/dist/render-cases-primitives.js.map +1 -1
  264. package/dist/render-cases.d.ts.map +1 -1
  265. package/dist/render-cases.js +30 -6
  266. package/dist/render-cases.js.map +1 -1
  267. package/dist/render-effects.d.ts +42 -0
  268. package/dist/render-effects.d.ts.map +1 -0
  269. package/dist/render-effects.js +81 -0
  270. package/dist/render-effects.js.map +1 -0
  271. package/dist/theme/create-theme.d.ts +19 -1
  272. package/dist/theme/create-theme.d.ts.map +1 -1
  273. package/dist/theme/create-theme.js +15 -0
  274. package/dist/theme/create-theme.js.map +1 -1
  275. package/package.json +5 -4
  276. package/src/app/adapt-data-widget.tsx +2 -42
  277. package/src/app/builder/BuilderBanners.tsx +1 -1
  278. package/src/app/builder/CanvasRow.tsx +63 -17
  279. package/src/app/builder/CatalogueSheet.tsx +50 -29
  280. package/src/app/builder/CellPropsSheet.tsx +17 -2
  281. package/src/app/builder/DiscardDialog.tsx +59 -0
  282. package/src/app/builder/LayoutSurface.tsx +206 -66
  283. package/src/app/builder/PipelineStrip.tsx +253 -153
  284. package/src/app/builder/SaveDialog.tsx +14 -2
  285. package/src/app/builder/Toolbar.tsx +144 -59
  286. package/src/app/builder/Workspace.tsx +226 -58
  287. package/src/app/builder/builder-model.ts +341 -2
  288. package/src/app/builder/builder-model.ux.test.ts +456 -0
  289. package/src/app/builder/builder-ux.test.tsx +844 -0
  290. package/src/app/builder/reveal.ts +10 -0
  291. package/src/app/builder/use-builder-catalogue.ts +56 -19
  292. package/src/app/builder/use-builder-session.ts +68 -0
  293. package/src/app/builder/use-display-resolver.ts +24 -0
  294. package/src/app/index.ts +12 -2
  295. package/src/app/layout-builder.tsx +178 -191
  296. package/src/app/mcp-app-view-chrome.test.tsx +141 -1
  297. package/src/app/mcp-app-view-chrome.tsx +56 -6
  298. package/src/app/mcp-app-view-states.tsx +51 -11
  299. package/src/app/mcp-app-view.effects.test.tsx +292 -0
  300. package/src/app/mcp-app-view.test.tsx +222 -2
  301. package/src/app/mcp-app-view.tsx +267 -117
  302. package/src/app/mcp-toolkit-app.test.tsx +40 -0
  303. package/src/app/mcp-toolkit-app.tsx +8 -1
  304. package/src/app/mcp-use-host-bridge.test.tsx +180 -0
  305. package/src/app/mcp-use-host-bridge.tsx +67 -24
  306. package/src/app/model-context.test.tsx +116 -0
  307. package/src/app/model-context.tsx +125 -0
  308. package/src/app/use-build-mode-focus.ts +62 -0
  309. package/src/app/use-view-actions.ts +89 -0
  310. package/src/app/use-view-lifecycle.ts +6 -0
  311. package/src/app/view-lifecycle.test.ts +216 -0
  312. package/src/app/view-lifecycle.ts +165 -5
  313. package/src/app/widget-fixture.effects.test.tsx +51 -0
  314. package/src/app/widget-fixture.render.test.tsx +81 -0
  315. package/src/app/widget-fixture.tsx +34 -11
  316. package/src/app/widget-props-sheet.tsx +57 -16
  317. package/src/app/widget-renderer.tsx +1 -1
  318. package/src/components/DrillButton.test.tsx +48 -0
  319. package/src/components/DrillButton.tsx +26 -11
  320. package/src/components/EmptyState.tsx +6 -4
  321. package/src/components/ErrorState.tsx +31 -9
  322. package/src/components/FilterBar.test.tsx +82 -0
  323. package/src/components/FilterBar.tsx +52 -26
  324. package/src/components/KpiGrid.test.tsx +279 -0
  325. package/src/components/KpiGrid.tsx +297 -90
  326. package/src/components/ListFooter.test.tsx +116 -0
  327. package/src/components/ListFooter.tsx +102 -15
  328. package/src/components/PagedListFooter.test.tsx +140 -0
  329. package/src/components/PagedListFooter.tsx +107 -0
  330. package/src/components/Pills.test.tsx +77 -0
  331. package/src/components/Pills.tsx +63 -10
  332. package/src/components/SectionHeading.tsx +9 -3
  333. package/src/components/Toast.tsx +37 -19
  334. package/src/components/WidgetHeader.test.tsx +78 -0
  335. package/src/components/WidgetHeader.tsx +63 -16
  336. package/src/components/feedback-states.test.tsx +98 -9
  337. package/src/components/tone-figures.test.tsx +8 -4
  338. package/src/hooks/index.ts +6 -3
  339. package/src/hooks/tool-result-recovery-model.test.ts +744 -0
  340. package/src/hooks/tool-result-recovery-model.ts +418 -0
  341. package/src/hooks/use-tool-query.effects.test.tsx +205 -0
  342. package/src/hooks/use-tool-query.ts +156 -33
  343. package/src/hooks/use-tool-result-recovery.effects.test.tsx +141 -0
  344. package/src/hooks/use-tool-result-recovery.test.ts +3 -4
  345. package/src/hooks/use-tool-result-recovery.ts +84 -142
  346. package/src/hooks/use-view-data.test.tsx +118 -9
  347. package/src/hooks/use-view-data.ts +128 -18
  348. package/src/hooks/use-view-tool-query.ts +25 -19
  349. package/src/i18n-coverage.test.tsx +104 -2
  350. package/src/index.ts +48 -11
  351. package/src/lib/focus-return.test.ts +288 -0
  352. package/src/lib/focus-return.ts +96 -0
  353. package/src/lib/parse-tool-result.test.ts +46 -1
  354. package/src/lib/parse-tool-result.ts +40 -2
  355. package/src/lib/toast-model.test.ts +166 -1
  356. package/src/lib/toast-model.ts +104 -2
  357. package/src/lib/tone-utils.test.ts +86 -10
  358. package/src/lib/tone-utils.ts +89 -8
  359. package/src/lib/tool-query.test.ts +569 -0
  360. package/src/lib/tool-query.ts +253 -0
  361. package/src/lib/tool-result-observer.test.ts +296 -0
  362. package/src/lib/tool-result-observer.ts +211 -0
  363. package/src/lib/typography.test.ts +45 -0
  364. package/src/lib/typography.ts +47 -0
  365. package/src/lib/utils.ts +12 -1
  366. package/src/locale/index.ts +1 -0
  367. package/src/locale/message-types.ts +118 -2
  368. package/src/locale/messages-de.ts +49 -10
  369. package/src/locale/messages-en.ts +49 -10
  370. package/src/locale/toolkit-messages.test.ts +133 -0
  371. package/src/locale/toolkit-messages.ts +63 -2
  372. package/src/primitives/alert.tsx +3 -0
  373. package/src/primitives/badge.tsx +9 -4
  374. package/src/primitives/button.tsx +1 -1
  375. package/src/primitives/dialog.tsx +10 -1
  376. package/src/primitives/select.tsx +1 -1
  377. package/src/primitives/sheet.tsx +7 -0
  378. package/src/providers/query-provider.test.tsx +154 -0
  379. package/src/providers/query-provider.tsx +66 -5
  380. package/src/render-cases-primitives.tsx +1 -1
  381. package/src/render-cases.tsx +38 -6
  382. package/src/render-effects.tsx +96 -0
  383. package/src/theme/cd-tokens.test.ts +3 -1
  384. package/src/theme/create-theme.test.ts +10 -0
  385. package/src/theme/create-theme.ts +28 -0
  386. package/src/theme/theme-css.test.ts +51 -0
  387. package/src/theme.css +31 -1
  388. package/ui-catalog.json +448 -41
@@ -6,7 +6,7 @@ import {
6
6
  useMemo,
7
7
  useReducer,
8
8
  useRef,
9
- useState,
9
+ type FocusEvent,
10
10
  type ReactNode,
11
11
  } from "react"
12
12
  import { CircleAlert, CircleCheck, Info, TriangleAlert, X, type LucideIcon } from "lucide-react"
@@ -16,9 +16,11 @@ import { TONE_ICON } from "../lib/tone-utils.js"
16
16
  import { cn } from "../lib/utils.js"
17
17
  import {
18
18
  createToast,
19
+ createToastTimer,
19
20
  toastReducer,
20
21
  type ToastItem,
21
22
  type ToastOptions,
23
+ type ToastTimer,
22
24
  type ToastTone,
23
25
  } from "../lib/toast-model.js"
24
26
 
@@ -73,9 +75,15 @@ const TOAST_ICON: Record<ToastTone, LucideIcon> = {
73
75
 
74
76
  /**
75
77
  * The stack of toasts — a presentational view over a list (the provider owns
76
- * the state). The container is ONE persistent live region (`role="status"`,
77
- * `aria-live="polite"`), so a newly added toast is announced without stealing
78
- * focus. Bottom centre, above dialogs (z 60), per CI §5/§8.
78
+ * the state). The container is ONE persistent live region
79
+ * (`aria-live="polite"`, mounted before the first toast so that toast is
80
+ * announced, each new toast on its own: `aria-atomic="false"`), without
81
+ * stealing focus. Its attributes never change: a role or name that switched
82
+ * on with the first toast would make the browser rebuild the region in the
83
+ * same commit that inserts the toast, and a region created with its content
84
+ * already in it is not announced. So it carries no role (a generic element
85
+ * takes no name either): an idle stack is no second "status" next to a view's
86
+ * own loading status. Bottom centre, above dialogs (z 60), per CI §5/§8.
79
87
  */
80
88
  export function ToastViewport({
81
89
  toasts,
@@ -88,9 +96,8 @@ export function ToastViewport({
88
96
  return (
89
97
  <div
90
98
  data-slot="toast-viewport"
91
- role="status"
92
99
  aria-live="polite"
93
- aria-label={messages.common.notifications}
100
+ aria-atomic="false"
94
101
  className="pointer-events-none fixed inset-x-0 bottom-4 z-60 flex flex-col items-center gap-2 px-4"
95
102
  >
96
103
  {toasts.map((toast) => (
@@ -114,31 +121,42 @@ function ToastCard({
114
121
  dismissLabel: string
115
122
  onDismiss: (id: number) => void
116
123
  }) {
117
- // Hover or keyboard focus holds the toast: the timer restarts on leave.
118
- const [held, setHeld] = useState(false)
124
+ // Hover or keyboard focus holds the toast; on leave it resumes with the
125
+ // time that was LEFT (createToastTimer), never a fresh full duration.
126
+ const timer = useRef<ToastTimer | null>(null)
119
127
  useEffect(() => {
120
- if (held) return
121
- const timer = setTimeout(() => onDismiss(toast.id), toast.duration)
122
- return () => clearTimeout(timer)
123
- }, [held, onDismiss, toast.id, toast.duration])
128
+ const countdown = createToastTimer(toast.duration, () => onDismiss(toast.id))
129
+ timer.current = countdown
130
+ return () => {
131
+ countdown.cancel()
132
+ timer.current = null
133
+ }
134
+ }, [onDismiss, toast.id, toast.duration])
135
+ const releaseFocus = (event: FocusEvent<HTMLDivElement>) => {
136
+ // Focus moving inside the toast (card → close button) keeps the hold.
137
+ if (!event.currentTarget.contains(event.relatedTarget)) {
138
+ timer.current?.release("focus")
139
+ }
140
+ }
124
141
  const Icon = TOAST_ICON[toast.tone]
125
142
  return (
126
143
  <div
127
144
  data-slot="toast"
128
145
  data-tone={toast.tone}
129
- onMouseEnter={() => setHeld(true)}
130
- onMouseLeave={() => setHeld(false)}
131
- onFocus={() => setHeld(true)}
132
- onBlur={() => setHeld(false)}
146
+ onMouseEnter={() => timer.current?.hold("pointer")}
147
+ onMouseLeave={() => timer.current?.release("pointer")}
148
+ onFocus={() => timer.current?.hold("focus")}
149
+ onBlur={releaseFocus}
133
150
  className={cn(
134
151
  // An overlay like Dialog/Sheet: shadow-2 on light, the 3:1 input rim on
135
- // dark (shadows vanish on the ground).
136
- "bg-popover text-popover-foreground dark:border-input shadow-2 pointer-events-auto flex w-full max-w-sm items-start gap-3 rounded-md border border-l-4 px-4 py-3 text-sm",
152
+ // dark (shadows vanish on the ground). Its words are running text (16 px).
153
+ "bg-popover text-popover-foreground dark:border-input shadow-2 text-body pointer-events-auto flex w-full max-w-sm items-start gap-3 rounded-md border border-l-4 px-4 py-3",
137
154
  "motion-safe:animate-in motion-safe:fade-in-0 motion-safe:slide-in-from-bottom-2 motion-safe:duration-(--motion-base) motion-safe:ease-(--ease)",
138
155
  TONE_EDGE[toast.tone],
139
156
  )}
140
157
  >
141
- <Icon aria-hidden="true" className={cn("mt-0.5 size-4 shrink-0", TONE_ICON[toast.tone])} />
158
+ {/* mt-1 centres the 16 px icon on the first 16/1.6 line. */}
159
+ <Icon aria-hidden="true" className={cn("mt-1 size-4 shrink-0", TONE_ICON[toast.tone])} />
142
160
  <div className="min-w-0 flex-1">
143
161
  <p className="font-medium break-words">{toast.title}</p>
144
162
  {toast.description && (
@@ -0,0 +1,78 @@
1
+ import { renderToStaticMarkup } from "react-dom/server"
2
+ import { describe, expect, it } from "vitest"
3
+ import { MICRO_LABEL } from "../lib/typography.js"
4
+ import { attribute, openingTags } from "../render-scan.js"
5
+ import { StatusBadge } from "./Pills.js"
6
+ import { SectionHeading } from "./SectionHeading.js"
7
+ import { WidgetHeader, type WidgetHeadingLevel } from "./WidgetHeader.js"
8
+
9
+ const classOf = (tag: string | undefined) => attribute(tag ?? "", "class") ?? ""
10
+
11
+ describe("WidgetHeader: one title at the level of its surface", () => {
12
+ it("is an h1 at the page-title size by default", () => {
13
+ const html = renderToStaticMarkup(<WidgetHeader title="Incidents" />)
14
+ expect(html).toMatch(/<h1 class="[^"]*text-title[^"]*">Incidents<\/h1>/)
15
+ expect(html).not.toMatch(/text-3xl|text-xs/)
16
+ })
17
+
18
+ it.each(["h2", "h3", "h4", "h5", "h6"] satisfies WidgetHeadingLevel[])(
19
+ "takes the heading level %s from `as`, keeping the size",
20
+ (level) => {
21
+ const html = renderToStaticMarkup(<WidgetHeader as={level} title="Incidents" />)
22
+ expect(html).toMatch(
23
+ new RegExp(`<${level} class="[^"]*text-title[^"]*">Incidents</${level}>`),
24
+ )
25
+ expect(html).not.toContain("<h1")
26
+ },
27
+ )
28
+
29
+ it("renders the compact detail size at the section step", () => {
30
+ const html = renderToStaticMarkup(<WidgetHeader as="h2" size="detail" title="Instance" />)
31
+ expect(classOf(openingTags(html, "h2")[0])).toContain("text-section")
32
+ expect(classOf(openingTags(html, "h2")[0])).not.toContain("text-title")
33
+ })
34
+
35
+ it("puts a state badge above the title and a suffix inside the heading", () => {
36
+ const html = renderToStaticMarkup(
37
+ <WidgetHeader
38
+ title="Invoice"
39
+ titleSuffix={<span>v3</span>}
40
+ badge={<StatusBadge tone="warning">Suspended</StatusBadge>}
41
+ sub="Updated 14:32"
42
+ actions={<button type="button">Refresh</button>}
43
+ />,
44
+ )
45
+ expect(html.indexOf("Suspended")).toBeLessThan(html.indexOf("<h1"))
46
+ expect(html).toMatch(/<h1[^>]*>Invoice<span>v3<\/span><\/h1>/)
47
+ expect(html).toContain("Updated 14:32")
48
+ expect(html).toContain("Refresh</button>")
49
+ expect(html).not.toContain("size-11")
50
+ // The meta row is a caption at the 13 px step, not running text in the
51
+ // dense-table size.
52
+ const meta = openingTags(html, "div").find((t) => /flex-wrap items-center gap-2/.test(t))
53
+ expect(classOf(meta)).toMatch(/(^| )text-small( |$)/)
54
+ expect(html).not.toMatch(/\btext-dense\b/)
55
+ })
56
+
57
+ it("still renders the deprecated icon tile, hidden from assistive tech", () => {
58
+ const html = renderToStaticMarkup(<WidgetHeader icon={<svg />} iconTone="info" title="Tasks" />)
59
+ const tile = openingTags(html, "div").find((t) => /size-11/.test(t))
60
+ expect(attribute(tile ?? "", "aria-hidden")).toBe("true")
61
+ expect(classOf(tile)).toContain("bg-info-soft")
62
+ })
63
+
64
+ it("merges a className onto the header", () => {
65
+ const html = renderToStaticMarkup(<WidgetHeader title="x" className="mb-6" />)
66
+ expect(classOf(openingTags(html, "header")[0])).toContain("mb-6")
67
+ })
68
+ })
69
+
70
+ describe("SectionHeading: a block label inside a widget body", () => {
71
+ it("is a 13 px caption (MICRO_LABEL), never in the dense-table size", () => {
72
+ const html = renderToStaticMarkup(<SectionHeading title="Recent" hint="3 new" />)
73
+ const row = classOf(openingTags(html, "div")[0])
74
+ expect(row).toContain(MICRO_LABEL)
75
+ expect(row).toContain("text-muted-foreground")
76
+ expect(html).not.toMatch(/\btext-dense\b/)
77
+ })
78
+ })
@@ -1,46 +1,93 @@
1
1
  import type { ReactNode } from "react"
2
- import { TONE_SOFT, type ToneVariant } from "../lib/tone-utils.js"
2
+ import { resolveTone, TONE_SOFT, type ToneName } from "../lib/tone-utils.js"
3
+ import { cn } from "../lib/utils.js"
4
+
5
+ /** Heading element of a {@link WidgetHeader} title. */
6
+ export type WidgetHeadingLevel = "h1" | "h2" | "h3" | "h4" | "h5" | "h6"
3
7
 
4
8
  export interface WidgetHeaderProps {
5
- /** Optional icon/glyph rendered in a tinted tile above the title. */
6
- icon?: ReactNode
7
- /** Tone for the icon tile. Default `"neutral"`. */
8
- iconTone?: ToneVariant
9
9
  /** The widget title. */
10
10
  title: ReactNode
11
- /** Subtitle/meta row under the title (e.g. a live pill + counts). */
11
+ /**
12
+ * Heading element of the title. Default `"h1"`: the widget is the page.
13
+ * Pick the level that keeps ONE outline per view — below a toolbar title
14
+ * (`McpAppView` `titleMode="show"`, an h2 by default) use `"h3"`, inside a
15
+ * composed view under its own heading one level below it. The size follows
16
+ * `size`, not the level.
17
+ */
18
+ as?: WidgetHeadingLevel
19
+ /**
20
+ * `"default"`: the page title (28 px / 700, the CI h2 style). `"detail"`:
21
+ * the compact detail-page title (21 px / 600, the CI h3 style).
22
+ */
23
+ size?: "default" | "detail"
24
+ /** Inline after the title inside the heading (e.g. a version chip). */
25
+ titleSuffix?: ReactNode
26
+ /** Above the title — a `StatusBadge` that says the state in words. */
27
+ badge?: ReactNode
28
+ /** Subtitle/meta row under the title (e.g. a live pill + counts), a 13 px caption row. */
12
29
  sub?: ReactNode
13
30
  /** Right-aligned actions slot (buttons, drill controls). */
14
31
  actions?: ReactNode
32
+ /**
33
+ * @deprecated A glyph in a tinted tile above every title is the icon-tile
34
+ * pattern the CI rules out (anti-slop U2). Say a state in words with a
35
+ * `StatusBadge` in {@link WidgetHeaderProps.badge}. Still rendered when set.
36
+ */
37
+ icon?: ReactNode
38
+ /** @deprecated Tone of the deprecated {@link WidgetHeaderProps.icon} tile. */
39
+ iconTone?: ToneName
40
+ className?: string
41
+ }
42
+
43
+ const TITLE_SIZE: Record<NonNullable<WidgetHeaderProps["size"]>, string> = {
44
+ default: "text-title",
45
+ detail: "text-section",
15
46
  }
16
47
 
17
48
  /**
18
- * Page-level header for the top of a dashboard widget: an optional tinted icon
19
- * tile, a large title, a subtitle/meta row, and a right-aligned actions slot.
20
- * Use once per widget surface to anchor it.
49
+ * Page-level header at the top of a widget: an optional state badge, the
50
+ * title (heading level per `as`, size per `size`), a subtitle/meta row and a
51
+ * right-aligned actions slot. Use once per widget surface to anchor it.
21
52
  */
22
53
  export function WidgetHeader({
23
- icon,
24
- iconTone = "neutral",
25
54
  title,
55
+ as: Heading = "h1",
56
+ size = "default",
57
+ titleSuffix,
58
+ badge,
26
59
  sub,
27
60
  actions,
61
+ icon,
62
+ iconTone = "neutral",
63
+ className,
28
64
  }: WidgetHeaderProps) {
29
65
  return (
30
- <header className="flex flex-wrap items-start justify-between gap-4">
66
+ <header
67
+ data-slot="widget-header"
68
+ className={cn("flex flex-wrap items-start justify-between gap-4", className)}
69
+ >
31
70
  <div className="min-w-0">
32
71
  {icon && (
33
72
  <div
34
- className={`mb-3.5 grid size-11 place-items-center rounded-md text-xl ${TONE_SOFT[iconTone]}`}
73
+ aria-hidden="true"
74
+ className={cn(
75
+ "text-section mb-3.5 grid size-11 place-items-center rounded-md",
76
+ TONE_SOFT[resolveTone(iconTone)],
77
+ )}
35
78
  >
36
79
  {icon}
37
80
  </div>
38
81
  )}
39
- <h1 className="text-foreground mb-1.5 text-3xl leading-tight font-bold tracking-tight">
82
+ {badge && <div className="mb-3">{badge}</div>}
83
+ <Heading
84
+ className={cn("text-foreground mb-1.5 tracking-tight break-words", TITLE_SIZE[size])}
85
+ >
40
86
  {title}
41
- </h1>
87
+ {titleSuffix}
88
+ </Heading>
42
89
  {sub && (
43
- <div className="text-muted-foreground flex flex-wrap items-center gap-2 text-sm">
90
+ <div className="text-muted-foreground text-small flex flex-wrap items-center gap-2">
44
91
  {sub}
45
92
  </div>
46
93
  )}
@@ -8,8 +8,8 @@ import { FOCUS_RING } from "../lib/focus-ring.js"
8
8
  import { createToast } from "../lib/toast-model.js"
9
9
  import { TONE_BORDER, TONE_ICON, TONE_SOFT } from "../lib/tone-utils.js"
10
10
  import { attribute, openingTags } from "../render-scan.js"
11
- import { EmptyState } from "./EmptyState.js"
12
- import { ErrorState } from "./ErrorState.js"
11
+ import { EmptyState, type EmptyStateProps } from "./EmptyState.js"
12
+ import { ErrorState, type ErrorStateTone } from "./ErrorState.js"
13
13
  import { ToastProvider, ToastViewport, useToast, type ToastApi } from "./Toast.js"
14
14
 
15
15
  /**
@@ -38,16 +38,41 @@ describe("Toast", () => {
38
38
  createToast(2, { tone: "danger", title: "Retry failed.", description: "Engine down (503)" }),
39
39
  ]
40
40
 
41
- it("renders ONE persistent live region: role=status, aria-live=polite, a localized name", () => {
41
+ /** The stack's region, found by its slot (it has no role to query by). */
42
+ const regionOf = (html: string) =>
43
+ openingTags(html, "div").find((t) => attribute(t, "data-slot") === "toast-viewport") ?? ""
44
+
45
+ it("renders ONE persistent live region: aria-live=polite, each toast on its own", () => {
42
46
  const html = inGerman(<ToastViewport toasts={toasts} onDismiss={noop} />)
43
- const [region, ...others] = openingTags(html, "div").filter((t) => /role="status"/.test(t))
47
+ const [region, ...others] = openingTags(html, "div").filter((t) => /aria-live=/.test(t))
44
48
  expect(others).toHaveLength(0)
49
+ expect(attribute(region ?? "", "data-slot")).toBe("toast-viewport")
45
50
  expect(attribute(region ?? "", "aria-live")).toBe("polite")
46
- expect(attribute(region ?? "", "aria-label")).toBe(de.common.notifications)
47
- // An empty stack keeps the region mounted, so the first toast is announced.
48
- const empty = inGerman(<ToastViewport toasts={[]} onDismiss={noop} />)
49
- expect(empty).toContain('role="status"')
50
- expect(empty).toContain('aria-live="polite"')
51
+ // Each new toast is announced on its own, not the whole stack again.
52
+ expect(attribute(region ?? "", "aria-atomic")).toBe("false")
53
+ expect(html).not.toContain('role="status"')
54
+ })
55
+
56
+ it("keeps the region's attributes fixed from idle to the first toast", () => {
57
+ // An empty stack keeps the live region, so the first toast is announced.
58
+ // A role or name that switched on with that toast would make the browser
59
+ // rebuild the region in the same commit, with the toast already inside:
60
+ // not announced. So idle and busy carry exactly the same attributes…
61
+ const idle = regionOf(inGerman(<ToastViewport toasts={[]} onDismiss={noop} />))
62
+ const busy = regionOf(inGerman(<ToastViewport toasts={toasts} onDismiss={noop} />))
63
+ expect(idle).not.toBe("")
64
+ expect(busy).toBe(idle)
65
+ // …and no role or name at all: an idle stack is no second "status" beside
66
+ // a view's own loading status (a generic element takes no aria-label).
67
+ expect(attribute(idle, "role")).toBeUndefined()
68
+ expect(attribute(idle, "aria-label")).toBeUndefined()
69
+ const loading = inGerman(
70
+ <ToastProvider>
71
+ <p role="status">Ansicht wird geladen…</p>
72
+ </ToastProvider>,
73
+ )
74
+ expect(loading.match(/role="status"/g)).toHaveLength(1)
75
+ expect(loading.match(/aria-live="polite"/g)).toHaveLength(1)
51
76
  })
52
77
 
53
78
  it("shows the status as edge + icon next to plain text, never as coloured text", () => {
@@ -69,6 +94,14 @@ describe("Toast", () => {
69
94
  expect(html).toContain("Engine down (503)")
70
95
  })
71
96
 
97
+ it("sets its words as running text (16 px), not in the dense-table size", () => {
98
+ const html = inGerman(<ToastViewport toasts={toasts} onDismiss={noop} />)
99
+ for (const card of openingTags(html, "div").filter((t) => /data-slot="toast"/.test(t))) {
100
+ expect(attribute(card, "class")).toMatch(/(^| )text-body( |$)/)
101
+ expect(attribute(card, "class")).not.toMatch(/\btext-dense\b/)
102
+ }
103
+ })
104
+
72
105
  it("gives every toast a native close button named in the locale, and steals no focus", () => {
73
106
  const html = inGerman(<ToastViewport toasts={toasts} onDismiss={noop} />)
74
107
  const buttons = openingTags(html, "button")
@@ -150,6 +183,29 @@ describe("EmptyState", () => {
150
183
  expect(openingTags(html, "button")).toHaveLength(2)
151
184
  expect(renderToStaticMarkup(<EmptyState title="Bare" />)).not.toContain("<button")
152
185
  })
186
+
187
+ it("sets its help line as running text (16 px), not in the dense-table size", () => {
188
+ const html = renderToStaticMarkup(<EmptyState title="Nothing" description="Add one." />)
189
+ const line = openingTags(html, "p").find((t) => /max-w-prose/.test(t)) ?? ""
190
+ expect(attribute(line, "class")).toMatch(/(^| )text-body( |$)/)
191
+ expect(html).not.toMatch(/\btext-dense\b/)
192
+ })
193
+
194
+ it("resolves an untyped tone: a retired success name colours the icon, anything else stays quiet", () => {
195
+ const iconClass = (tone: string) => {
196
+ const html = renderToStaticMarkup(
197
+ <EmptyState
198
+ icon={<CircleCheck />}
199
+ tone={tone as unknown as EmptyStateProps["tone"]}
200
+ title="x"
201
+ />,
202
+ )
203
+ return openingTags(html, "div").find((t) => /aria-hidden="true"/.test(t)) ?? ""
204
+ }
205
+ expect(iconClass("ok")).toContain(TONE_ICON.success)
206
+ expect(iconClass("danger")).toContain(TONE_ICON.neutral)
207
+ expect(iconClass("purple")).toContain(TONE_ICON.neutral)
208
+ })
153
209
  })
154
210
 
155
211
  describe("ErrorState", () => {
@@ -222,4 +278,37 @@ describe("ErrorState", () => {
222
278
  expect(html).toContain("Reload</button>")
223
279
  expect(html).toContain('<a href="#help">Help</a>')
224
280
  })
281
+
282
+ it("sets the block's description as running text (16 px); the inline alert box stays dense", () => {
283
+ const descriptionClass = (html: string) =>
284
+ /<div class="([^"]*)">what to do<\/div>/.exec(html)?.[1] ?? ""
285
+ const block = renderToStaticMarkup(<ErrorState title="x" description="what to do" />)
286
+ expect(descriptionClass(block)).toMatch(/(^| )text-body( |$)/)
287
+ const inline = renderToStaticMarkup(
288
+ <ErrorState variant="inline" title="x" description="what to do" />,
289
+ )
290
+ expect(descriptionClass(inline)).toMatch(/(^| )text-dense( |$)/)
291
+ })
292
+
293
+ it.each([
294
+ ["error", "danger"],
295
+ ["critical", "danger"],
296
+ ["warn", "warning"],
297
+ ["notice", "info"],
298
+ ["success", "danger"],
299
+ ["purple", "danger"],
300
+ ])(
301
+ "resolves an untyped tone %j to its %s state instead of crashing on a missing icon",
302
+ (name, tone) => {
303
+ const html = renderToStaticMarkup(
304
+ <ErrorState title="x" tone={name as unknown as ErrorStateTone} />,
305
+ )
306
+ const root = openingTags(html, "div")[0] ?? ""
307
+ expect(attribute(root, "data-tone")).toBe(tone)
308
+ expect(attribute(root, "class")).toContain(TONE_BORDER[tone as ErrorStateTone])
309
+ expect(attribute(openingTags(html, "svg")[0] ?? "", "class")).toContain(
310
+ TONE_ICON[tone as ErrorStateTone],
311
+ )
312
+ },
313
+ )
225
314
  })
@@ -28,13 +28,17 @@ describe("figures never take a tone colour", () => {
28
28
  ]}
29
29
  />,
30
30
  )
31
- expect(html).not.toMatch(TONE_AS_TEXT)
32
- // The tone is still there — as a dot.
31
+ // Icons draw in the tone (SVG currentColor, TONE_ICON); no WORD or digit does.
32
+ const words = html.replace(/<svg[\s\S]*?<\/svg>/g, "")
33
+ expect(words).not.toMatch(TONE_AS_TEXT)
34
+ // The tone is still there — as a dot next to a label, an icon next to a trend.
33
35
  expect(html).toMatch(/bg-danger\b/)
34
36
  expect(html).toMatch(/bg-success\b/)
37
+ expect(html).toMatch(/<svg[^>]*class="lucide lucide-trending-up[^"]*text-danger"/)
38
+ expect(html).toMatch(/<svg[^>]*class="lucide lucide-trending-down[^"]*text-success"/)
35
39
  // A toned trend reads in the foreground, an untoned one stays quiet.
36
- expect(html).toMatch(/text-foreground[^"]*"><span aria-hidden="true"[^>]*bg-danger/)
37
- expect(html).toMatch(/text-muted-foreground">±0/)
40
+ expect(html).toMatch(/<span class="text-foreground">\+12% vs last week<\/span>/)
41
+ expect(html).toMatch(/<span class="text-muted-foreground">±0<\/span>/)
38
42
  })
39
43
 
40
44
  it.each(TONES)(
@@ -1,17 +1,20 @@
1
1
  export { useIsMobile } from "./use-mobile.js"
2
2
  export { useDebouncedValue } from "./use-debounced-value.js"
3
- export { useToolQuery, useToolMutation } from "./use-tool-query.js"
4
- export type { UseToolQueryOptions } from "./use-tool-query.js"
3
+ export { useToolQuery, useToolQueryKey, useToolMutation } from "./use-tool-query.js"
4
+ export type { UseToolQueryOptions, UseToolMutationOptions } from "./use-tool-query.js"
5
5
  export { useViewToolQuery } from "./use-view-tool-query.js"
6
6
  export type { UseViewToolQueryOptions } from "./use-view-tool-query.js"
7
7
  export { useViewData } from "./use-view-data.js"
8
- export type { ViewDataResult } from "./use-view-data.js"
8
+ export type { ViewDataResult, UseViewDataOptions, ViewDataMode } from "./use-view-data.js"
9
9
  export {
10
10
  useToolResultRecovery,
11
11
  resolveRecoveryToolName,
12
12
  decodeRecoveredResult,
13
13
  } from "./use-tool-result-recovery.js"
14
14
  export type {
15
+ RecoveryBlockReason,
16
+ RecoveryPolicy,
15
17
  ToolResultRecoveryOptions,
16
18
  ToolResultRecoveryState,
19
+ ToolSafety,
17
20
  } from "./use-tool-result-recovery.js"