@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
@@ -12,6 +12,8 @@ export const en: ToolkitMessages = {
12
12
  details: "Details",
13
13
  dismiss: "Close notification",
14
14
  notifications: "Notifications",
15
+ loadFailed: "This content couldn't be loaded",
16
+ loadFailedHint: "Try again. If it fails again, ask in the chat.",
15
17
  },
16
18
  view: {
17
19
  loading: "Loading view…",
@@ -26,6 +28,11 @@ export const en: ToolkitMessages = {
26
28
  errorHint: "Try again. If it fails again, ask in the chat.",
27
29
  errorHintNoRetry: "Ask in the chat to open this view again.",
28
30
  retry: "Try again",
31
+ blockedTitle: "The chat didn't pass the result to this view",
32
+ blockedHint: "Load the view yourself, or ask in the chat for the current state.",
33
+ blockedHintNoRetry:
34
+ "The action already ran, so the view doesn't start it again. Ask in the chat for the current state.",
35
+ blockedRetry: "Load view",
29
36
  errorDetails: "Details",
30
37
  stepErrorsTitle: "Some data couldn't be loaded",
31
38
  stepErrorsHint: "Try again to load the missing data.",
@@ -33,14 +40,21 @@ export const en: ToolkitMessages = {
33
40
  refreshFailedHint: "You're seeing the last loaded data. Try again in a moment.",
34
41
  fullscreenFailed: "Couldn't switch the display mode.",
35
42
  fullscreenFailedHint: "The view stays as it is. Try again in a moment.",
43
+ dataAsOf: (time) => `Reloaded at ${time}`,
36
44
  },
37
45
  listFooter: {
38
46
  showing: ({ shown, total, noun }) => `Showing ${shown} of ${total} ${noun}`.trimEnd(),
39
47
  defaultNoun: "rows",
40
48
  loadMore: "Load more",
41
49
  loadingMore: "Loading…",
50
+ updating: "Updating…",
51
+ noResults: "No entries",
52
+ refreshFailed: "Couldn't update the list",
53
+ refreshFailedHint: "You're seeing the previous result. Try again in a moment.",
54
+ loadMoreFailed: "Couldn't load more entries",
55
+ loadMoreFailedHint: "The entries above are current. Try again in a moment.",
42
56
  },
43
- filterBar: { searchPlaceholder: "Filter…" },
57
+ filterBar: { searchPlaceholder: "Filter…", searchLabel: "Filter", chipsLabel: "Filters" },
44
58
  pills: { live: "Live" },
45
59
  renderer: {
46
60
  widgetUnavailable: "This element isn't available in this version.",
@@ -52,13 +66,37 @@ export const en: ToolkitMessages = {
52
66
  builderBadge: "Editing",
53
67
  previewBadge: "Preview",
54
68
  catalogue: "Catalogue",
55
- save: "Save",
69
+ save: "Save as dashboard",
56
70
  done: "Done",
71
+ apply: "Apply",
72
+ applyHint: "Shows your changes in this view and saves nothing.",
73
+ discard: "Discard",
74
+ discardHint: "Leaves the editor. The view stays as it was.",
75
+ discardHintStandalone: "Resets your changes.",
76
+ saveHint: "Saves the layout as a dashboard you can open again later.",
77
+ unsavedChanges: "Unsaved changes",
78
+ discardDialogTitle: "Discard your changes?",
79
+ discardDialogDescription:
80
+ "You lose your changes to this view. The view stays as it was before you edited it.",
81
+ discardDialogDescriptionSaved:
82
+ "The view goes back to how it was before you edited it. Your saved dashboard keeps your changes.",
83
+ keepEditing: "Keep editing",
84
+ discardConfirm: "Discard changes",
85
+ advanced: "Advanced",
86
+ advancedHint: "Shows the data sources, the catalogue and the technical IDs.",
87
+ renameTabNamed: (label) => `Rename tab “${label}”`,
88
+ removeTabNamed: (label) => `Remove tab “${label}”`,
89
+ rowTarget: "New elements go here",
90
+ stepProvides: "provides",
91
+ keyValueFor: (key) => (key ? `Value of ${key}` : "Value"),
57
92
  viewLayoutTab: "Layout",
58
93
  viewPipelineTab: "Data sources",
59
94
  viewPreviewTab: "Preview",
60
- saveDialogTitle: "Save dashboard",
61
- saveDialogDescription: "Saves the layout together with the data sources that fill it.",
95
+ saveDialogTitle: "Save as dashboard",
96
+ saveDialogDescription:
97
+ "Saves the layout and its data sources as a dashboard you can open again later. To change this view, use Apply.",
98
+ saveDialogDescriptionStandalone:
99
+ "Saves the layout and its data sources as a dashboard you can open again later.",
62
100
  saveNameLabel: "Name",
63
101
  saveDescriptionLabel: "Description",
64
102
  saveConfirm: "Save dashboard",
@@ -75,10 +113,11 @@ export const en: ToolkitMessages = {
75
113
  emptyKeys: "No keys yet.",
76
114
  emptySteps: "No steps yet.",
77
115
  statusUpToDate: "Up to date",
78
- statusPending: "Applying soon…",
79
- statusRefreshing: "Applying…",
116
+ statusPending: "Updating soon…",
117
+ statusRefreshing: "Updating…",
80
118
  paletteHeader: "Elements",
81
- paletteEmpty: "No elements available yet. Add a key or a step.",
119
+ paletteLoading: "Loading elements…",
120
+ paletteEmpty: "No elements available yet. Elements appear as soon as their data is there.",
82
121
  canvasEmpty: "Drag an element here or click one in the list.",
83
122
  canvasDropHint: "Drop to add",
84
123
  addRow: "Add row",
@@ -117,9 +156,9 @@ export const en: ToolkitMessages = {
117
156
  saveNamePlaceholder: "Name of the dashboard",
118
157
  saveDescriptionPlaceholder: "Optional",
119
158
  saved: (name) => `Dashboard “${name}” saved.`,
120
- renameHint: "Double-click a tab to rename it.",
121
- renameTitle: "Double-click to rename",
122
- autoApplies: "Changes apply right away",
159
+ renameHint: "Rename a tab with F2, a double-click or its pencil button.",
160
+ renameTitle: "F2 or double-click to rename",
161
+ autoApplies: "Changes show in the preview right away",
123
162
  noMoreKeys: "No more keys available.",
124
163
  parsedType: "Detected type",
125
164
  valueType: (code) =>
@@ -1,6 +1,8 @@
1
1
  import { describe, expect, it } from "vitest"
2
2
  import {
3
+ formatClockTime,
3
4
  formatCount,
5
+ resolveFormatLocale,
4
6
  resolveToolkitLocale,
5
7
  resolveToolkitMessages,
6
8
  TOOLKIT_LOCALES,
@@ -24,6 +26,9 @@ const SAMPLES: Record<string, unknown[]> = {
24
26
  configureElementSet: [2],
25
27
  unsupportedType: ["array"],
26
28
  valueType: ["num"],
29
+ renameTabNamed: ["Übersicht"],
30
+ removeTabNamed: ["Übersicht"],
31
+ keyValueFor: ["demo:id"],
27
32
  }
28
33
 
29
34
  /** Every message of a catalog as `[path, rendered text]`. */
@@ -155,6 +160,40 @@ describe("toolkit catalogs", () => {
155
160
  expect(de.propsSheet.unsupportedType("array")).toBe(
156
161
  "Diesen Feldtyp (array) kannst du hier nicht bearbeiten.",
157
162
  )
163
+ expect(en.builder.renameTabNamed("Ops")).toBe("Rename tab “Ops”")
164
+ expect(de.builder.renameTabNamed("Ops")).toBe("Tab „Ops“ umbenennen")
165
+ expect(en.builder.removeTabNamed("Ops")).toBe("Remove tab “Ops”")
166
+ expect(de.builder.removeTabNamed("Ops")).toBe("Tab „Ops“ entfernen")
167
+ expect(en.builder.keyValueFor("demo:id")).toBe("Value of demo:id")
168
+ expect(de.builder.keyValueFor("demo:id")).toBe("Wert von demo:id")
169
+ // A key row without a name yet still has an accessible name.
170
+ expect(en.builder.keyValueFor("")).toBe("Value")
171
+ expect(de.builder.keyValueFor("")).toBe("Wert")
172
+ })
173
+
174
+ it("builder: names Apply, Discard and Save apart in user words (U18)", () => {
175
+ const { en, de } = TOOLKIT_MESSAGES
176
+ expect([en.builder.apply, en.builder.discard, en.builder.save]).toEqual([
177
+ "Apply",
178
+ "Discard",
179
+ "Save as dashboard",
180
+ ])
181
+ expect([de.builder.apply, de.builder.discard, de.builder.save]).toEqual([
182
+ "Übernehmen",
183
+ "Verwerfen",
184
+ "Als Dashboard speichern",
185
+ ])
186
+ expect(de.builder.advanced).toBe("Erweitert")
187
+ // The refresh state no longer reuses the Apply verb.
188
+ for (const m of [en.builder, de.builder]) {
189
+ expect(m.statusPending).not.toMatch(/apply|übernomm/i)
190
+ expect(m.statusRefreshing).not.toMatch(/apply|übernomm/i)
191
+ }
192
+ // Rename is announced with its keyboard path, not double-click alone.
193
+ expect(en.builder.renameHint).toContain("F2")
194
+ expect(de.builder.renameHint).toContain("F2")
195
+ expect(en.builder.renameTitle).toContain("F2")
196
+ expect(de.builder.renameTitle).toContain("F2")
158
197
  })
159
198
  })
160
199
 
@@ -203,6 +242,31 @@ describe("resolveToolkitMessages", () => {
203
242
  })
204
243
  })
205
244
 
245
+ describe("resolveToolkitMessages: the deprecated builder `done`", () => {
246
+ it("labels Apply with an override of `done`", () => {
247
+ const merged = resolveToolkitMessages("de", { builder: { done: "Los" } })
248
+ expect(merged.builder.apply).toBe("Los")
249
+ expect(merged.builder.done).toBe("Los")
250
+ })
251
+
252
+ it("keeps an `apply` override over `done`", () => {
253
+ const merged = resolveToolkitMessages("en", { builder: { done: "Finish", apply: "Show" } })
254
+ expect(merged.builder.apply).toBe("Show")
255
+ })
256
+
257
+ it("leaves Apply alone without a `done` override", () => {
258
+ expect(resolveToolkitMessages("en", { builder: { save: "Keep" } }).builder.apply).toBe(
259
+ TOOLKIT_MESSAGES.en.builder.apply,
260
+ )
261
+ expect(resolveToolkitMessages("de", { builder: { apply: "Zeigen" } }).builder.apply).toBe(
262
+ "Zeigen",
263
+ )
264
+ expect(resolveToolkitMessages("de", { view: { refresh: "Neu" } }).builder).toEqual(
265
+ TOOLKIT_MESSAGES.de.builder,
266
+ )
267
+ })
268
+ })
269
+
206
270
  describe("formatCount", () => {
207
271
  it("groups digits per locale", () => {
208
272
  expect(formatCount(1234567, "en")).toBe("1,234,567")
@@ -214,3 +278,72 @@ describe("formatCount", () => {
214
278
  expect(formatCount(1234, "not a locale!")).toBe("1,234")
215
279
  })
216
280
  })
281
+
282
+ describe("formatClockTime (the view's data-as-of stamp)", () => {
283
+ const at = Date.UTC(2026, 9, 11, 14, 32)
284
+
285
+ it("formats hours and minutes per locale in the host's time zone", () => {
286
+ expect(formatClockTime(at, "de-DE", "UTC")).toBe("14:32")
287
+ expect(formatClockTime(at, "de-DE", "Europe/Berlin")).toBe("16:32")
288
+ expect(formatClockTime(at, "en-GB", "UTC")).toBe("14:32")
289
+ })
290
+
291
+ it("never pads the hour of a 12-hour clock: 2:32 PM, not 02:32 PM", () => {
292
+ expect(formatClockTime(at, "en-US", "UTC")).toBe("2:32 PM")
293
+ expect(formatClockTime(Date.UTC(2026, 9, 11, 9, 5), "en-US", "UTC")).toBe("9:05 AM")
294
+ // Minutes keep two digits.
295
+ expect(formatClockTime(Date.UTC(2026, 9, 11, 14, 5), "de-DE", "UTC")).toBe("14:05")
296
+ })
297
+
298
+ it("a zone the runtime rejects drops the zone, never the locale", () => {
299
+ // Windows ids reach a view from some hosts; German stays a 24-hour clock.
300
+ expect(formatClockTime(at, "de", "W. Europe Standard Time")).toMatch(/^\d{2}:\d{2}$/)
301
+ expect(formatClockTime(at, "de-DE", "Not/AZone")).toMatch(/^\d{2}:\d{2}$/)
302
+ expect(formatClockTime(at, "en-US", "Not/AZone")).toMatch(/^[1-9]\d?:\d\d [AP]M$/)
303
+ })
304
+
305
+ it("an invalid tag falls back to English, in the host's zone when it is valid", () => {
306
+ expect(() => formatClockTime(at, "not a locale!", "UTC")).not.toThrow()
307
+ expect(formatClockTime(at, "not a locale!", "UTC")).toBe("2:32 PM")
308
+ expect(formatClockTime(at, "not a locale!", "Not/AZone")).toMatch(/^[1-9]\d?:\d\d [AP]M$/)
309
+ })
310
+
311
+ it("the catalogs stamp it in words that carry its meaning", () => {
312
+ expect(TOOLKIT_MESSAGES.en.view.dataAsOf("2:32 PM")).toBe("Reloaded at 2:32 PM")
313
+ expect(TOOLKIT_MESSAGES.de.view.dataAsOf("14:32")).toBe("Neu geladen um 14:32 Uhr")
314
+ })
315
+ })
316
+
317
+ describe("resolveFormatLocale (a time in the language of its sentence)", () => {
318
+ it("formats in the language the words come from, never the host's other language", () => {
319
+ // A German profile on an en-US host: German words, a German clock.
320
+ expect(resolveFormatLocale("de", "en-US")).toBe("de")
321
+ expect(resolveFormatLocale("en", "de-DE")).toBe("en")
322
+ expect(
323
+ formatClockTime(Date.UTC(2026, 9, 11, 14, 32), resolveFormatLocale("de", "en-US"), "UTC"),
324
+ ).toBe("14:32")
325
+ })
326
+
327
+ it("keeps the regional tag of the same language: en-GB keeps its 24-hour clock", () => {
328
+ expect(resolveFormatLocale("en", "en-GB")).toBe("en-GB")
329
+ expect(resolveFormatLocale("en-GB", "en-GB")).toBe("en-GB")
330
+ expect(resolveFormatLocale("de", "DE_AT")).toBe("DE-AT")
331
+ expect(resolveFormatLocale(" de ", "de-CH ")).toBe("de-CH")
332
+ })
333
+
334
+ it("prefers the text locale's own region over the host's", () => {
335
+ expect(resolveFormatLocale("en-GB", "en-US")).toBe("en-GB")
336
+ expect(resolveFormatLocale("de-AT", "de-DE")).toBe("de-AT")
337
+ })
338
+
339
+ it("maps a language without a catalog onto the catalog's, like the words", () => {
340
+ // French words fall back to English, so does their clock.
341
+ expect(resolveFormatLocale("fr-FR", "fr-FR")).toBe("en")
342
+ expect(resolveFormatLocale(undefined, "de-DE")).toBe("en")
343
+ expect(resolveFormatLocale(undefined, "en-AU")).toBe("en-AU")
344
+ expect(resolveFormatLocale("de")).toBe("de")
345
+ // A bare tag or a lookalike prefix is no region of the language.
346
+ expect(resolveFormatLocale("en", "en")).toBe("en")
347
+ expect(resolveFormatLocale("de", "dex-DE")).toBe("de")
348
+ })
349
+ })
@@ -8,13 +8,14 @@
8
8
  * Pure data + functions (no React, no `mcp-use/react`): browser-safe and
9
9
  * shared by the root barrel and the `./app` shell alike.
10
10
  */
11
- import type { ToolkitMessages } from "./message-types.js"
11
+ import type { BuilderMessages, ToolkitMessages } from "./message-types.js"
12
12
  import { de } from "./messages-de.js"
13
13
  import { en } from "./messages-en.js"
14
14
 
15
15
  export type {
16
16
  BuilderMessages,
17
17
  CommonMessages,
18
+ FilterBarMessages,
18
19
  ListFooterMessages,
19
20
  PropsSheetMessages,
20
21
  RendererMessages,
@@ -63,11 +64,24 @@ export function resolveToolkitMessages(
63
64
  filterBar: { ...base.filterBar, ...override.filterBar },
64
65
  pills: { ...base.pills, ...override.pills },
65
66
  renderer: { ...base.renderer, ...override.renderer },
66
- builder: { ...base.builder, ...override.builder },
67
+ builder: mergeBuilder(base.builder, override.builder),
67
68
  propsSheet: { ...base.propsSheet, ...override.propsSheet },
68
69
  }
69
70
  }
70
71
 
72
+ /**
73
+ * The builder section with its override. An override of the deprecated
74
+ * `done` still labels the commit button, Apply, unless it sets `apply` too.
75
+ */
76
+ function mergeBuilder(
77
+ base: BuilderMessages,
78
+ override: Partial<BuilderMessages> | undefined,
79
+ ): BuilderMessages {
80
+ const merged = { ...base, ...override }
81
+ if (override?.done !== undefined && override.apply === undefined) merged.apply = override.done
82
+ return merged
83
+ }
84
+
71
85
  /**
72
86
  * Format a count for display in `locale` (grouping separators per locale:
73
87
  * `1,234` / `1.234`). An invalid tag falls back to English instead of
@@ -80,3 +94,50 @@ export function formatCount(value: number, locale: string | undefined): string {
80
94
  return new Intl.NumberFormat("en").format(value)
81
95
  }
82
96
  }
97
+
98
+ /**
99
+ * The tag a toolkit sentence formats its numbers and times in: the language
100
+ * its words come from (`textLocale`, mapped onto a shipped catalog the way
101
+ * {@link resolveToolkitMessages} maps it), with the regional tag of that
102
+ * language from `textLocale` or else `hostLocale`, so `en-GB` keeps its
103
+ * 24-hour clock. A tag of another language never mixes in: German words
104
+ * never carry an English clock ("Neu geladen um 4:32 PM Uhr").
105
+ */
106
+ export function resolveFormatLocale(textLocale: string | undefined, hostLocale?: string): string {
107
+ const language = resolveToolkitLocale(textLocale)
108
+ const regionOfLanguage = (tag: string | undefined): string | undefined => {
109
+ const normal = tag?.trim().replace(/_/g, "-")
110
+ if (!normal?.includes("-")) return undefined
111
+ return normal.toLowerCase().split("-")[0] === language ? normal : undefined
112
+ }
113
+ return regionOfLanguage(textLocale) ?? regionOfLanguage(hostLocale) ?? language
114
+ }
115
+
116
+ /**
117
+ * Format the clock time (hours and minutes) of `epochMs` in `locale`, in the
118
+ * host's `timeZone` when it reports one: `14:32` / `2:32 PM`. The hour is
119
+ * `numeric`, so each locale pads it its own way (a 12-hour clock never shows
120
+ * `02:32 PM`). A zone the runtime rejects (a Windows id, say) drops the zone,
121
+ * never the locale; an invalid tag falls back to English. It never throws
122
+ * inside a render.
123
+ */
124
+ export function formatClockTime(
125
+ epochMs: number,
126
+ locale: string | undefined,
127
+ timeZone?: string,
128
+ ): string {
129
+ const clock: Intl.DateTimeFormatOptions = { hour: "numeric", minute: "2-digit" }
130
+ const attempts: readonly (readonly [string | undefined, string | undefined])[] = [
131
+ [locale, timeZone],
132
+ [locale, undefined],
133
+ ["en", timeZone],
134
+ ]
135
+ for (const [tag, zone] of attempts) {
136
+ try {
137
+ return new Intl.DateTimeFormat(tag, { ...clock, timeZone: zone }).format(epochMs)
138
+ } catch {
139
+ // The next attempt drops what the runtime rejected.
140
+ }
141
+ }
142
+ return new Intl.DateTimeFormat("en", clock).format(epochMs)
143
+ }
@@ -11,6 +11,9 @@ const alertVariants = cva(
11
11
  default: "bg-card text-card-foreground",
12
12
  // Tone model: tint, a border and icon in the danger colour; title and
13
13
  // description in the foreground (CI §3.3: text on a tint is black).
14
+ danger:
15
+ "border-danger bg-danger-soft text-foreground *:data-[slot=alert-title]:text-foreground *:data-[slot=alert-description]:text-foreground [&>svg]:text-danger",
16
+ /** @deprecated Use `danger`: one name per tone. */
14
17
  destructive:
15
18
  "border-danger bg-danger-soft text-foreground *:data-[slot=alert-title]:text-foreground *:data-[slot=alert-description]:text-foreground [&>svg]:text-danger",
16
19
  },
@@ -6,16 +6,21 @@ import { FOCUS_RING } from "../lib/focus-ring.js"
6
6
  import { TONE_BORDER, TONE_SOFT } from "../lib/tone-utils.js"
7
7
  import { cn } from "../lib/utils.js"
8
8
 
9
- // Badges and chips take radius sm (CI §6). The status variants follow the tone
10
- // model: tint + foreground text, rim in the tone (TONE_SOFT + TONE_BORDER;
11
- // CI §3.3 "-soft: Text darauf --cd-schwarz"). The ink is never text on a tint.
9
+ // Badges and chips take radius sm (CI §6) and the 13 px floor of the type
10
+ // scale (`text-small`; `leading-4` keeps the one line inside the 20 px badge).
11
+ // The status variants follow the tone model and its ONE vocabulary: tint +
12
+ // foreground text, rim in the tone (TONE_SOFT + TONE_BORDER; CI §3.3 "-soft:
13
+ // Text darauf --cd-schwarz"). The ink is never text on a tint. `danger` is the
14
+ // status red; `destructive` is its deprecated name, kept for existing calls.
12
15
  const badgeVariants = cva(
13
- `group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-sm border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all ${FOCUS_RING} has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-danger [&>svg]:pointer-events-none [&>svg]:size-3!`,
16
+ `group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-sm border border-transparent px-2 py-0.5 text-small leading-4 font-medium whitespace-nowrap transition-all ${FOCUS_RING} has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-danger [&>svg]:pointer-events-none [&>svg]:size-3!`,
14
17
  {
15
18
  variants: {
16
19
  variant: {
17
20
  default: "bg-primary text-primary-foreground [a]:hover:bg-primary-hover",
18
21
  secondary: "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
22
+ danger: `${TONE_SOFT.danger} ${TONE_BORDER.danger}`,
23
+ /** @deprecated Use `danger`: one name per tone. */
19
24
  destructive: `${TONE_SOFT.danger} ${TONE_BORDER.danger}`,
20
25
  success: `${TONE_SOFT.success} ${TONE_BORDER.success}`,
21
26
  warning: `${TONE_SOFT.warning} ${TONE_BORDER.warning}`,
@@ -25,7 +25,7 @@ const buttonVariants = cva(
25
25
  },
26
26
  size: {
27
27
  default: "h-9 px-4 py-2 has-[>svg]:px-3",
28
- xs: "h-6 gap-1 rounded-sm px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
28
+ xs: "h-6 gap-1 rounded-sm px-2 text-small has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
29
29
  sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
30
30
  lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
31
31
  icon: "size-9",
@@ -5,6 +5,7 @@ import { XIcon } from "lucide-react"
5
5
  import { Dialog as DialogPrimitive } from "radix-ui"
6
6
 
7
7
  import { FOCUS_RING } from "../lib/focus-ring.js"
8
+ import { useFocusReturn } from "../lib/focus-return.js"
8
9
  import { useToolkitMessages } from "../locale/locale-provider.js"
9
10
  import { cn } from "../lib/utils.js"
10
11
  import { usePortalContainer } from "../theme/portal-container.js"
@@ -58,6 +59,8 @@ function DialogContent({
58
59
  children,
59
60
  showCloseButton = true,
60
61
  closeLabel,
62
+ onOpenAutoFocus,
63
+ onCloseAutoFocus,
61
64
  ...props
62
65
  }: React.ComponentProps<typeof DialogPrimitive.Content> & {
63
66
  showCloseButton?: boolean
@@ -65,6 +68,9 @@ function DialogContent({
65
68
  closeLabel?: string
66
69
  }) {
67
70
  const messages = useToolkitMessages()
71
+ // Without a DialogTrigger, Radix returns the focus nowhere: hand it back to
72
+ // the element that opened it (lib/focus-return.ts).
73
+ const focusReturn = useFocusReturn({ onOpenAutoFocus, onCloseAutoFocus })
68
74
  return (
69
75
  <DialogPortal data-slot="dialog-portal">
70
76
  <DialogOverlay />
@@ -75,6 +81,7 @@ function DialogContent({
75
81
  className,
76
82
  )}
77
83
  {...props}
84
+ {...focusReturn}
78
85
  >
79
86
  {children}
80
87
  {showCloseButton && (
@@ -136,7 +143,9 @@ function DialogTitle({ className, ...props }: React.ComponentProps<typeof Dialog
136
143
  return (
137
144
  <DialogPrimitive.Title
138
145
  data-slot="dialog-title"
139
- className={cn("text-lg leading-none font-semibold", className)}
146
+ // The dialog's heading row (CI §8: weight 600) on the type scale's
147
+ // section step (21 px / 600, the CI h3).
148
+ className={cn("text-section", className)}
140
149
  {...props}
141
150
  />
142
151
  )
@@ -86,7 +86,7 @@ function SelectLabel({ className, ...props }: React.ComponentProps<typeof Select
86
86
  return (
87
87
  <SelectPrimitive.Label
88
88
  data-slot="select-label"
89
- className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
89
+ className={cn("text-muted-foreground text-small px-2 py-1.5", className)}
90
90
  {...props}
91
91
  />
92
92
  )
@@ -5,6 +5,7 @@ import { XIcon } from "lucide-react"
5
5
  import { Dialog as SheetPrimitive } from "radix-ui"
6
6
 
7
7
  import { FOCUS_RING } from "../lib/focus-ring.js"
8
+ import { useFocusReturn } from "../lib/focus-return.js"
8
9
  import { useToolkitMessages } from "../locale/locale-provider.js"
9
10
  import { cn } from "../lib/utils.js"
10
11
  import { usePortalContainer } from "../theme/portal-container.js"
@@ -55,6 +56,8 @@ function SheetContent({
55
56
  side = "right",
56
57
  showCloseButton = true,
57
58
  closeLabel,
59
+ onOpenAutoFocus,
60
+ onCloseAutoFocus,
58
61
  ...props
59
62
  }: React.ComponentProps<typeof SheetPrimitive.Content> & {
60
63
  side?: "top" | "right" | "bottom" | "left"
@@ -63,6 +66,9 @@ function SheetContent({
63
66
  closeLabel?: string
64
67
  }) {
65
68
  const messages = useToolkitMessages()
69
+ // Without a SheetTrigger, Radix returns the focus nowhere: hand it back to
70
+ // the element that opened it (lib/focus-return.ts).
71
+ const focusReturn = useFocusReturn({ onOpenAutoFocus, onCloseAutoFocus })
66
72
  return (
67
73
  <SheetPortal>
68
74
  <SheetOverlay />
@@ -81,6 +87,7 @@ function SheetContent({
81
87
  className,
82
88
  )}
83
89
  {...props}
90
+ {...focusReturn}
84
91
  >
85
92
  {children}
86
93
  {showCloseButton && (
@@ -0,0 +1,154 @@
1
+ import { QueryClient, QueryClientProvider, useQueryClient } from "@tanstack/react-query"
2
+ import { renderToStaticMarkup } from "react-dom/server"
3
+ import { describe, expect, it, vi } from "vitest"
4
+ import { createStandaloneHostBridge, HostBridgeProvider } from "../app/host-bridge.js"
5
+ import { useToolQueryKey } from "../hooks/use-tool-query.js"
6
+ import {
7
+ AppQueryProvider,
8
+ queryClient,
9
+ useCallTool,
10
+ useToolNameInQueryKey,
11
+ } from "./query-provider.js"
12
+
13
+ /**
14
+ * The tool transport and client of the data hooks (toolkit#176 N25, K18):
15
+ * `AppQueryProvider`'s `callTool`, else the nearest HostBridge — so the
16
+ * documented HostBridgeProvider mount fetches instead of idling.
17
+ */
18
+
19
+ type Caller = ReturnType<typeof useCallTool>
20
+
21
+ function probe(tree: (node: React.ReactNode) => React.ReactNode): {
22
+ callTool: Caller
23
+ client: QueryClient
24
+ } {
25
+ let callTool: Caller
26
+ let client: QueryClient | undefined
27
+ function Probe() {
28
+ callTool = useCallTool()
29
+ client = useQueryClient()
30
+ return null
31
+ }
32
+ renderToStaticMarkup(tree(<Probe />))
33
+ if (!client) throw new Error("probe did not render")
34
+ return { callTool, client }
35
+ }
36
+
37
+ describe("useCallTool", () => {
38
+ it("AppQueryProvider's callTool wins over a bridge", async () => {
39
+ const explicit = vi.fn(() => Promise.resolve("explicit"))
40
+ const bridgeCall = vi.fn(() => Promise.resolve("bridge"))
41
+ const bridge = createStandaloneHostBridge({ callTool: bridgeCall })
42
+ const { callTool } = probe((node) => (
43
+ <HostBridgeProvider bridge={bridge}>
44
+ <AppQueryProvider callTool={explicit}>{node}</AppQueryProvider>
45
+ </HostBridgeProvider>
46
+ ))
47
+ await expect(callTool?.("t", { a: 1 })).resolves.toBe("explicit")
48
+ expect(explicit).toHaveBeenCalledWith("t", { a: 1 })
49
+ expect(bridgeCall).not.toHaveBeenCalled()
50
+ })
51
+
52
+ it("falls back to the nearest HostBridgeProvider's callTool (N25)", async () => {
53
+ const bridgeCall = vi.fn(() => Promise.resolve("bridge"))
54
+ const bridge = createStandaloneHostBridge({ callTool: bridgeCall })
55
+ const { callTool } = probe((node) => (
56
+ <AppQueryProvider>
57
+ <HostBridgeProvider bridge={bridge}>{node}</HostBridgeProvider>
58
+ </AppQueryProvider>
59
+ ))
60
+ await expect(callTool?.("orders_get", { id: 7 })).resolves.toBe("bridge")
61
+ expect(bridgeCall).toHaveBeenCalledWith("orders_get", { id: 7 })
62
+ })
63
+
64
+ it("is undefined with neither (the query then fails with NoToolTransportError)", () => {
65
+ const { callTool } = probe((node) => <AppQueryProvider>{node}</AppQueryProvider>)
66
+ expect(callTool).toBeUndefined()
67
+ })
68
+ })
69
+
70
+ describe("AppQueryProvider", () => {
71
+ it("shares the toolkit's client by default", () => {
72
+ expect(probe((node) => <AppQueryProvider>{node}</AppQueryProvider>).client).toBe(queryClient)
73
+ })
74
+
75
+ it("takes a client of its own, e.g. one per mount", () => {
76
+ const own = new QueryClient()
77
+ expect(probe((node) => <AppQueryProvider client={own}>{node}</AppQueryProvider>).client).toBe(
78
+ own,
79
+ )
80
+ })
81
+
82
+ it("a nested provider without client keeps its parent's, like toolNameInQueryKey", () => {
83
+ const own = new QueryClient()
84
+ const other = new QueryClient()
85
+ const nested = probe((node) => (
86
+ <AppQueryProvider client={own}>
87
+ <AppQueryProvider callTool={vi.fn()}>{node}</AppQueryProvider>
88
+ </AppQueryProvider>
89
+ ))
90
+ expect(nested.client).toBe(own)
91
+ const replaced = probe((node) => (
92
+ <AppQueryProvider client={own}>
93
+ <AppQueryProvider client={other}>{node}</AppQueryProvider>
94
+ </AppQueryProvider>
95
+ ))
96
+ expect(replaced.client).toBe(other)
97
+ })
98
+
99
+ it("a plain TanStack QueryClientProvider is not inherited: the shared client stays", () => {
100
+ const foreign = new QueryClient()
101
+ const { client } = probe((node) => (
102
+ <QueryClientProvider client={foreign}>
103
+ <AppQueryProvider>{node}</AppQueryProvider>
104
+ </QueryClientProvider>
105
+ ))
106
+ expect(client).toBe(queryClient)
107
+ })
108
+ })
109
+
110
+ describe("AppQueryProvider toolNameInQueryKey (K18)", () => {
111
+ /** The setting and the key `useToolQueryKey` builds under `tree`. */
112
+ function keyUnder(tree: (node: React.ReactNode) => React.ReactNode) {
113
+ let setting: boolean | undefined
114
+ let key: unknown[] | undefined
115
+ function Probe() {
116
+ setting = useToolNameInQueryKey()
117
+ key = useToolQueryKey(["feed"], "feed_data", { id: 1 })
118
+ return null
119
+ }
120
+ renderToStaticMarkup(tree(<Probe />))
121
+ return { setting, key }
122
+ }
123
+
124
+ it("is off by default: the 2.x key, outside a provider too", () => {
125
+ expect(keyUnder((node) => node)).toEqual({ setting: false, key: ["feed", { id: 1 }] })
126
+ expect(keyUnder((node) => <AppQueryProvider>{node}</AppQueryProvider>)).toEqual({
127
+ setting: false,
128
+ key: ["feed", { id: 1 }],
129
+ })
130
+ })
131
+
132
+ it("on, the hooks' key carries the tool name", () => {
133
+ expect(
134
+ keyUnder((node) => <AppQueryProvider toolNameInQueryKey>{node}</AppQueryProvider>),
135
+ ).toEqual({ setting: true, key: ["feed", "feed_data", { id: 1 }] })
136
+ })
137
+
138
+ it("a nested provider keeps its parent's setting unless it sets its own", () => {
139
+ expect(
140
+ keyUnder((node) => (
141
+ <AppQueryProvider toolNameInQueryKey>
142
+ <AppQueryProvider callTool={vi.fn()}>{node}</AppQueryProvider>
143
+ </AppQueryProvider>
144
+ )).setting,
145
+ ).toBe(true)
146
+ expect(
147
+ keyUnder((node) => (
148
+ <AppQueryProvider toolNameInQueryKey>
149
+ <AppQueryProvider toolNameInQueryKey={false}>{node}</AppQueryProvider>
150
+ </AppQueryProvider>
151
+ )).setting,
152
+ ).toBe(false)
153
+ })
154
+ })