@miragon/mcp-toolkit-ui 2.6.0 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (511) hide show
  1. package/README.md +14 -5
  2. package/dist/app/adapt-data-widget.d.ts +1 -22
  3. package/dist/app/adapt-data-widget.d.ts.map +1 -1
  4. package/dist/app/adapt-data-widget.js +1 -35
  5. package/dist/app/adapt-data-widget.js.map +1 -1
  6. package/dist/app/builder/BuilderBanners.d.ts +7 -2
  7. package/dist/app/builder/BuilderBanners.d.ts.map +1 -1
  8. package/dist/app/builder/BuilderBanners.js +8 -3
  9. package/dist/app/builder/BuilderBanners.js.map +1 -1
  10. package/dist/app/builder/CanvasRow.d.ts +4 -2
  11. package/dist/app/builder/CanvasRow.d.ts.map +1 -1
  12. package/dist/app/builder/CanvasRow.js +29 -17
  13. package/dist/app/builder/CanvasRow.js.map +1 -1
  14. package/dist/app/builder/CatalogueSheet.d.ts.map +1 -1
  15. package/dist/app/builder/CatalogueSheet.js +14 -6
  16. package/dist/app/builder/CatalogueSheet.js.map +1 -1
  17. package/dist/app/builder/CellPropsSheet.d.ts +7 -3
  18. package/dist/app/builder/CellPropsSheet.d.ts.map +1 -1
  19. package/dist/app/builder/CellPropsSheet.js +10 -3
  20. package/dist/app/builder/CellPropsSheet.js.map +1 -1
  21. package/dist/app/builder/DiscardDialog.d.ts +18 -0
  22. package/dist/app/builder/DiscardDialog.d.ts.map +1 -0
  23. package/dist/app/builder/DiscardDialog.js +18 -0
  24. package/dist/app/builder/DiscardDialog.js.map +1 -0
  25. package/dist/app/builder/EmptyHint.d.ts +4 -0
  26. package/dist/app/builder/EmptyHint.d.ts.map +1 -1
  27. package/dist/app/builder/EmptyHint.js +6 -1
  28. package/dist/app/builder/EmptyHint.js.map +1 -1
  29. package/dist/app/builder/LayoutSurface.d.ts +33 -9
  30. package/dist/app/builder/LayoutSurface.d.ts.map +1 -1
  31. package/dist/app/builder/LayoutSurface.js +74 -23
  32. package/dist/app/builder/LayoutSurface.js.map +1 -1
  33. package/dist/app/builder/PipelineStrip.d.ts +5 -2
  34. package/dist/app/builder/PipelineStrip.d.ts.map +1 -1
  35. package/dist/app/builder/PipelineStrip.js +33 -15
  36. package/dist/app/builder/PipelineStrip.js.map +1 -1
  37. package/dist/app/builder/SaveDialog.d.ts +9 -1
  38. package/dist/app/builder/SaveDialog.d.ts.map +1 -1
  39. package/dist/app/builder/SaveDialog.js +9 -3
  40. package/dist/app/builder/SaveDialog.js.map +1 -1
  41. package/dist/app/builder/Toolbar.d.ts +14 -4
  42. package/dist/app/builder/Toolbar.d.ts.map +1 -1
  43. package/dist/app/builder/Toolbar.js +24 -7
  44. package/dist/app/builder/Toolbar.js.map +1 -1
  45. package/dist/app/builder/Workspace.d.ts +23 -3
  46. package/dist/app/builder/Workspace.d.ts.map +1 -1
  47. package/dist/app/builder/Workspace.js +58 -8
  48. package/dist/app/builder/Workspace.js.map +1 -1
  49. package/dist/app/builder/builder-model.d.ts +146 -3
  50. package/dist/app/builder/builder-model.d.ts.map +1 -1
  51. package/dist/app/builder/builder-model.js +196 -5
  52. package/dist/app/builder/builder-model.js.map +1 -1
  53. package/dist/app/builder/labels.d.ts +13 -62
  54. package/dist/app/builder/labels.d.ts.map +1 -1
  55. package/dist/app/builder/labels.js +9 -58
  56. package/dist/app/builder/labels.js.map +1 -1
  57. package/dist/app/builder/reveal.d.ts +10 -0
  58. package/dist/app/builder/reveal.d.ts.map +1 -0
  59. package/dist/app/builder/reveal.js +10 -0
  60. package/dist/app/builder/reveal.js.map +1 -0
  61. package/dist/app/builder/status-chip.d.ts.map +1 -1
  62. package/dist/app/builder/status-chip.js +3 -2
  63. package/dist/app/builder/status-chip.js.map +1 -1
  64. package/dist/app/builder/use-builder-catalogue.d.ts +5 -1
  65. package/dist/app/builder/use-builder-catalogue.d.ts.map +1 -1
  66. package/dist/app/builder/use-builder-catalogue.js +50 -17
  67. package/dist/app/builder/use-builder-catalogue.js.map +1 -1
  68. package/dist/app/builder/use-builder-session.d.ts +30 -0
  69. package/dist/app/builder/use-builder-session.d.ts.map +1 -0
  70. package/dist/app/builder/use-builder-session.js +58 -0
  71. package/dist/app/builder/use-builder-session.js.map +1 -0
  72. package/dist/app/builder/use-display-resolver.d.ts +17 -0
  73. package/dist/app/builder/use-display-resolver.d.ts.map +1 -0
  74. package/dist/app/builder/use-display-resolver.js +16 -0
  75. package/dist/app/builder/use-display-resolver.js.map +1 -0
  76. package/dist/app/builder/use-save-dashboard.d.ts +3 -1
  77. package/dist/app/builder/use-save-dashboard.d.ts.map +1 -1
  78. package/dist/app/builder/use-save-dashboard.js +3 -1
  79. package/dist/app/builder/use-save-dashboard.js.map +1 -1
  80. package/dist/app/index.d.ts +8 -3
  81. package/dist/app/index.d.ts.map +1 -1
  82. package/dist/app/index.js +8 -2
  83. package/dist/app/index.js.map +1 -1
  84. package/dist/app/layout-builder.d.ts +18 -25
  85. package/dist/app/layout-builder.d.ts.map +1 -1
  86. package/dist/app/layout-builder.js +84 -78
  87. package/dist/app/layout-builder.js.map +1 -1
  88. package/dist/app/mcp-app-view-chrome.d.ts +103 -20
  89. package/dist/app/mcp-app-view-chrome.d.ts.map +1 -1
  90. package/dist/app/mcp-app-view-chrome.js +125 -40
  91. package/dist/app/mcp-app-view-chrome.js.map +1 -1
  92. package/dist/app/mcp-app-view-states.d.ts +23 -0
  93. package/dist/app/mcp-app-view-states.d.ts.map +1 -0
  94. package/dist/app/mcp-app-view-states.js +55 -0
  95. package/dist/app/mcp-app-view-states.js.map +1 -0
  96. package/dist/app/mcp-app-view.d.ts +72 -5
  97. package/dist/app/mcp-app-view.d.ts.map +1 -1
  98. package/dist/app/mcp-app-view.js +241 -141
  99. package/dist/app/mcp-app-view.js.map +1 -1
  100. package/dist/app/mcp-toolkit-app.d.ts +8 -2
  101. package/dist/app/mcp-toolkit-app.d.ts.map +1 -1
  102. package/dist/app/mcp-toolkit-app.js +33 -4
  103. package/dist/app/mcp-toolkit-app.js.map +1 -1
  104. package/dist/app/mcp-use-host-bridge.d.ts +15 -6
  105. package/dist/app/mcp-use-host-bridge.d.ts.map +1 -1
  106. package/dist/app/mcp-use-host-bridge.js +51 -22
  107. package/dist/app/mcp-use-host-bridge.js.map +1 -1
  108. package/dist/app/model-context.d.ts +71 -0
  109. package/dist/app/model-context.d.ts.map +1 -0
  110. package/dist/app/model-context.js +101 -0
  111. package/dist/app/model-context.js.map +1 -0
  112. package/dist/app/use-build-mode-focus.d.ts +33 -0
  113. package/dist/app/use-build-mode-focus.d.ts.map +1 -0
  114. package/dist/app/use-build-mode-focus.js +59 -0
  115. package/dist/app/use-build-mode-focus.js.map +1 -0
  116. package/dist/app/use-view-actions.d.ts +31 -0
  117. package/dist/app/use-view-actions.d.ts.map +1 -0
  118. package/dist/app/use-view-actions.js +81 -0
  119. package/dist/app/use-view-actions.js.map +1 -0
  120. package/dist/app/use-view-lifecycle.d.ts +42 -0
  121. package/dist/app/use-view-lifecycle.d.ts.map +1 -0
  122. package/dist/app/use-view-lifecycle.js +81 -0
  123. package/dist/app/use-view-lifecycle.js.map +1 -0
  124. package/dist/app/view-lifecycle.d.ts +185 -0
  125. package/dist/app/view-lifecycle.d.ts.map +1 -0
  126. package/dist/app/view-lifecycle.js +207 -0
  127. package/dist/app/view-lifecycle.js.map +1 -0
  128. package/dist/app/widget-fixture.d.ts +10 -3
  129. package/dist/app/widget-fixture.d.ts.map +1 -1
  130. package/dist/app/widget-fixture.js +21 -8
  131. package/dist/app/widget-fixture.js.map +1 -1
  132. package/dist/app/widget-props-sheet.d.ts +6 -10
  133. package/dist/app/widget-props-sheet.d.ts.map +1 -1
  134. package/dist/app/widget-props-sheet.js +25 -20
  135. package/dist/app/widget-props-sheet.js.map +1 -1
  136. package/dist/app/widget-renderer.d.ts +41 -1
  137. package/dist/app/widget-renderer.d.ts.map +1 -1
  138. package/dist/app/widget-renderer.js +82 -31
  139. package/dist/app/widget-renderer.js.map +1 -1
  140. package/dist/components/DrillButton.d.ts +9 -6
  141. package/dist/components/DrillButton.d.ts.map +1 -1
  142. package/dist/components/DrillButton.js +9 -6
  143. package/dist/components/DrillButton.js.map +1 -1
  144. package/dist/components/EmptyState.d.ts +33 -0
  145. package/dist/components/EmptyState.d.ts.map +1 -0
  146. package/dist/components/EmptyState.js +19 -0
  147. package/dist/components/EmptyState.js.map +1 -0
  148. package/dist/components/ErrorState.d.ts +53 -0
  149. package/dist/components/ErrorState.d.ts.map +1 -0
  150. package/dist/components/ErrorState.js +77 -0
  151. package/dist/components/ErrorState.js.map +1 -0
  152. package/dist/components/FilterBar.d.ts +16 -5
  153. package/dist/components/FilterBar.d.ts.map +1 -1
  154. package/dist/components/FilterBar.js +13 -5
  155. package/dist/components/FilterBar.js.map +1 -1
  156. package/dist/components/GroupCard.d.ts.map +1 -1
  157. package/dist/components/GroupCard.js +2 -1
  158. package/dist/components/GroupCard.js.map +1 -1
  159. package/dist/components/KpiGrid.d.ts +70 -18
  160. package/dist/components/KpiGrid.d.ts.map +1 -1
  161. package/dist/components/KpiGrid.js +112 -23
  162. package/dist/components/KpiGrid.js.map +1 -1
  163. package/dist/components/ListFooter.d.ts +32 -4
  164. package/dist/components/ListFooter.d.ts.map +1 -1
  165. package/dist/components/ListFooter.js +35 -6
  166. package/dist/components/ListFooter.js.map +1 -1
  167. package/dist/components/PagedListFooter.d.ts +57 -0
  168. package/dist/components/PagedListFooter.d.ts.map +1 -0
  169. package/dist/components/PagedListFooter.js +27 -0
  170. package/dist/components/PagedListFooter.js.map +1 -0
  171. package/dist/components/Pills.d.ts +36 -12
  172. package/dist/components/Pills.d.ts.map +1 -1
  173. package/dist/components/Pills.js +29 -8
  174. package/dist/components/Pills.js.map +1 -1
  175. package/dist/components/SectionHeading.d.ts +3 -1
  176. package/dist/components/SectionHeading.d.ts.map +1 -1
  177. package/dist/components/SectionHeading.js +6 -2
  178. package/dist/components/SectionHeading.js.map +1 -1
  179. package/dist/components/Toast.d.ts +43 -0
  180. package/dist/components/Toast.d.ts.map +1 -0
  181. package/dist/components/Toast.js +106 -0
  182. package/dist/components/Toast.js.map +1 -0
  183. package/dist/components/WidgetHeader.d.ts +34 -10
  184. package/dist/components/WidgetHeader.d.ts.map +1 -1
  185. package/dist/components/WidgetHeader.js +11 -6
  186. package/dist/components/WidgetHeader.js.map +1 -1
  187. package/dist/hooks/index.d.ts +4 -4
  188. package/dist/hooks/index.d.ts.map +1 -1
  189. package/dist/hooks/index.js +1 -1
  190. package/dist/hooks/index.js.map +1 -1
  191. package/dist/hooks/tool-result-recovery-model.d.ts +155 -0
  192. package/dist/hooks/tool-result-recovery-model.d.ts.map +1 -0
  193. package/dist/hooks/tool-result-recovery-model.js +273 -0
  194. package/dist/hooks/tool-result-recovery-model.js.map +1 -0
  195. package/dist/hooks/use-tool-query.d.ts +56 -14
  196. package/dist/hooks/use-tool-query.d.ts.map +1 -1
  197. package/dist/hooks/use-tool-query.js +102 -25
  198. package/dist/hooks/use-tool-query.js.map +1 -1
  199. package/dist/hooks/use-tool-result-recovery.d.ts +61 -44
  200. package/dist/hooks/use-tool-result-recovery.d.ts.map +1 -1
  201. package/dist/hooks/use-tool-result-recovery.js +62 -110
  202. package/dist/hooks/use-tool-result-recovery.js.map +1 -1
  203. package/dist/hooks/use-view-data.d.ts +55 -7
  204. package/dist/hooks/use-view-data.d.ts.map +1 -1
  205. package/dist/hooks/use-view-data.js +81 -11
  206. package/dist/hooks/use-view-data.js.map +1 -1
  207. package/dist/hooks/use-view-tool-query.d.ts +12 -7
  208. package/dist/hooks/use-view-tool-query.d.ts.map +1 -1
  209. package/dist/hooks/use-view-tool-query.js +15 -14
  210. package/dist/hooks/use-view-tool-query.js.map +1 -1
  211. package/dist/index.d.ts +27 -13
  212. package/dist/index.d.ts.map +1 -1
  213. package/dist/index.js +29 -7
  214. package/dist/index.js.map +1 -1
  215. package/dist/lib/focus-return.d.ts +61 -0
  216. package/dist/lib/focus-return.d.ts.map +1 -0
  217. package/dist/lib/focus-return.js +46 -0
  218. package/dist/lib/focus-return.js.map +1 -0
  219. package/dist/lib/focus-ring.d.ts +18 -0
  220. package/dist/lib/focus-ring.d.ts.map +1 -0
  221. package/dist/lib/focus-ring.js +18 -0
  222. package/dist/lib/focus-ring.js.map +1 -0
  223. package/dist/lib/icon-stroke.d.ts +12 -0
  224. package/dist/lib/icon-stroke.d.ts.map +1 -0
  225. package/dist/lib/icon-stroke.js +12 -0
  226. package/dist/lib/icon-stroke.js.map +1 -0
  227. package/dist/lib/parse-tool-result.d.ts +23 -1
  228. package/dist/lib/parse-tool-result.d.ts.map +1 -1
  229. package/dist/lib/parse-tool-result.js +38 -2
  230. package/dist/lib/parse-tool-result.js.map +1 -1
  231. package/dist/lib/toast-model.d.ts +86 -0
  232. package/dist/lib/toast-model.d.ts.map +1 -0
  233. package/dist/lib/toast-model.js +102 -0
  234. package/dist/lib/toast-model.js.map +1 -0
  235. package/dist/lib/tone-utils.d.ts +95 -9
  236. package/dist/lib/tone-utils.d.ts.map +1 -1
  237. package/dist/lib/tone-utils.js +100 -9
  238. package/dist/lib/tone-utils.js.map +1 -1
  239. package/dist/lib/tool-query.d.ts +107 -0
  240. package/dist/lib/tool-query.d.ts.map +1 -0
  241. package/dist/lib/tool-query.js +182 -0
  242. package/dist/lib/tool-query.js.map +1 -0
  243. package/dist/lib/tool-result-observer.d.ts +108 -0
  244. package/dist/lib/tool-result-observer.d.ts.map +1 -0
  245. package/dist/lib/tool-result-observer.js +158 -0
  246. package/dist/lib/tool-result-observer.js.map +1 -0
  247. package/dist/lib/typography.d.ts +37 -0
  248. package/dist/lib/typography.d.ts.map +1 -0
  249. package/dist/lib/typography.js +41 -0
  250. package/dist/lib/typography.js.map +1 -0
  251. package/dist/lib/utils.d.ts.map +1 -1
  252. package/dist/lib/utils.js +11 -1
  253. package/dist/lib/utils.js.map +1 -1
  254. package/dist/locale/index.d.ts +3 -1
  255. package/dist/locale/index.d.ts.map +1 -1
  256. package/dist/locale/index.js +2 -1
  257. package/dist/locale/index.js.map +1 -1
  258. package/dist/locale/locale-provider.d.ts +42 -1
  259. package/dist/locale/locale-provider.d.ts.map +1 -1
  260. package/dist/locale/locale-provider.js +44 -3
  261. package/dist/locale/locale-provider.js.map +1 -1
  262. package/dist/locale/message-types.d.ts +336 -0
  263. package/dist/locale/message-types.d.ts.map +1 -0
  264. package/dist/locale/message-types.js +7 -0
  265. package/dist/locale/message-types.js.map +1 -0
  266. package/dist/locale/messages-de.d.ts +9 -0
  267. package/dist/locale/messages-de.d.ts.map +1 -0
  268. package/dist/locale/messages-de.js +185 -0
  269. package/dist/locale/messages-de.js.map +1 -0
  270. package/dist/locale/messages-en.d.ts +8 -0
  271. package/dist/locale/messages-en.d.ts.map +1 -0
  272. package/dist/locale/messages-en.js +184 -0
  273. package/dist/locale/messages-en.js.map +1 -0
  274. package/dist/locale/toolkit-messages.d.ts +57 -0
  275. package/dist/locale/toolkit-messages.d.ts.map +1 -0
  276. package/dist/locale/toolkit-messages.js +103 -0
  277. package/dist/locale/toolkit-messages.js.map +1 -0
  278. package/dist/primitives/alert.d.ts +1 -1
  279. package/dist/primitives/alert.d.ts.map +1 -1
  280. package/dist/primitives/alert.js +6 -2
  281. package/dist/primitives/alert.js.map +1 -1
  282. package/dist/primitives/badge.d.ts +1 -1
  283. package/dist/primitives/badge.d.ts.map +1 -1
  284. package/dist/primitives/badge.js +19 -6
  285. package/dist/primitives/badge.js.map +1 -1
  286. package/dist/primitives/button.d.ts +1 -1
  287. package/dist/primitives/button.d.ts.map +1 -1
  288. package/dist/primitives/button.js +12 -8
  289. package/dist/primitives/button.js.map +1 -1
  290. package/dist/primitives/card.js +4 -4
  291. package/dist/primitives/card.js.map +1 -1
  292. package/dist/primitives/dialog.d.ts +7 -3
  293. package/dist/primitives/dialog.d.ts.map +1 -1
  294. package/dist/primitives/dialog.js +22 -8
  295. package/dist/primitives/dialog.js.map +1 -1
  296. package/dist/primitives/input.d.ts.map +1 -1
  297. package/dist/primitives/input.js +4 -1
  298. package/dist/primitives/input.js.map +1 -1
  299. package/dist/primitives/scroll-area.js +1 -1
  300. package/dist/primitives/scroll-area.js.map +1 -1
  301. package/dist/primitives/select.d.ts.map +1 -1
  302. package/dist/primitives/select.js +6 -3
  303. package/dist/primitives/select.js.map +1 -1
  304. package/dist/primitives/sheet.d.ts +3 -1
  305. package/dist/primitives/sheet.d.ts.map +1 -1
  306. package/dist/primitives/sheet.js +17 -7
  307. package/dist/primitives/sheet.js.map +1 -1
  308. package/dist/primitives/skeleton.js +1 -1
  309. package/dist/primitives/skeleton.js.map +1 -1
  310. package/dist/primitives/switch.d.ts.map +1 -1
  311. package/dist/primitives/switch.js +2 -1
  312. package/dist/primitives/switch.js.map +1 -1
  313. package/dist/primitives/tabs.d.ts.map +1 -1
  314. package/dist/primitives/tabs.js +5 -3
  315. package/dist/primitives/tabs.js.map +1 -1
  316. package/dist/providers/query-provider.d.ts +32 -1
  317. package/dist/providers/query-provider.d.ts.map +1 -1
  318. package/dist/providers/query-provider.js +53 -4
  319. package/dist/providers/query-provider.js.map +1 -1
  320. package/dist/render-cases-primitives.js +1 -1
  321. package/dist/render-cases-primitives.js.map +1 -1
  322. package/dist/render-cases.d.ts.map +1 -1
  323. package/dist/render-cases.js +48 -6
  324. package/dist/render-cases.js.map +1 -1
  325. package/dist/render-effects.d.ts +42 -0
  326. package/dist/render-effects.d.ts.map +1 -0
  327. package/dist/render-effects.js +81 -0
  328. package/dist/render-effects.js.map +1 -0
  329. package/dist/render-scan.d.ts +25 -0
  330. package/dist/render-scan.d.ts.map +1 -0
  331. package/dist/render-scan.js +66 -0
  332. package/dist/render-scan.js.map +1 -0
  333. package/dist/theme/cd-tokens.d.ts +66 -0
  334. package/dist/theme/cd-tokens.d.ts.map +1 -0
  335. package/dist/theme/cd-tokens.js +64 -0
  336. package/dist/theme/cd-tokens.js.map +1 -0
  337. package/dist/theme/create-theme.d.ts +214 -39
  338. package/dist/theme/create-theme.d.ts.map +1 -1
  339. package/dist/theme/create-theme.js +204 -13
  340. package/dist/theme/create-theme.js.map +1 -1
  341. package/dist/theme/fonts.d.ts +38 -0
  342. package/dist/theme/fonts.d.ts.map +1 -0
  343. package/dist/theme/fonts.js +56 -0
  344. package/dist/theme/fonts.js.map +1 -0
  345. package/dist/theme/host-fonts.d.ts +39 -0
  346. package/dist/theme/host-fonts.d.ts.map +1 -0
  347. package/dist/theme/host-fonts.js +44 -0
  348. package/dist/theme/host-fonts.js.map +1 -0
  349. package/dist/theme/index.d.ts +8 -1
  350. package/dist/theme/index.d.ts.map +1 -1
  351. package/dist/theme/index.js +5 -1
  352. package/dist/theme/index.js.map +1 -1
  353. package/dist/theme/portal-container.d.ts +16 -0
  354. package/dist/theme/portal-container.d.ts.map +1 -0
  355. package/dist/theme/portal-container.js +19 -0
  356. package/dist/theme/portal-container.js.map +1 -0
  357. package/dist/theme/presets.d.ts +24 -9
  358. package/dist/theme/presets.d.ts.map +1 -1
  359. package/dist/theme/presets.js +44 -11
  360. package/dist/theme/presets.js.map +1 -1
  361. package/dist/theme/theme-provider.d.ts +27 -9
  362. package/dist/theme/theme-provider.d.ts.map +1 -1
  363. package/dist/theme/theme-provider.js +42 -20
  364. package/dist/theme/theme-provider.js.map +1 -1
  365. package/package.json +11 -5
  366. package/src/app/adapt-data-widget.tsx +2 -42
  367. package/src/app/builder/BuilderBanners.tsx +26 -13
  368. package/src/app/builder/CanvasRow.tsx +79 -32
  369. package/src/app/builder/CatalogueSheet.tsx +58 -35
  370. package/src/app/builder/CellPropsSheet.tsx +17 -2
  371. package/src/app/builder/DiscardDialog.tsx +59 -0
  372. package/src/app/builder/EmptyHint.tsx +16 -11
  373. package/src/app/builder/LayoutSurface.tsx +207 -67
  374. package/src/app/builder/PipelineStrip.tsx +265 -156
  375. package/src/app/builder/SaveDialog.tsx +25 -14
  376. package/src/app/builder/Toolbar.tsx +154 -57
  377. package/src/app/builder/Workspace.tsx +226 -56
  378. package/src/app/builder/builder-feedback.test.tsx +75 -0
  379. package/src/app/builder/builder-model.test.ts +5 -0
  380. package/src/app/builder/builder-model.ts +346 -6
  381. package/src/app/builder/builder-model.ux.test.ts +456 -0
  382. package/src/app/builder/builder-ux.test.tsx +844 -0
  383. package/src/app/builder/labels.ts +15 -121
  384. package/src/app/builder/reveal.ts +10 -0
  385. package/src/app/builder/status-chip.tsx +8 -4
  386. package/src/app/builder/use-builder-catalogue.ts +62 -19
  387. package/src/app/builder/use-builder-session.ts +68 -0
  388. package/src/app/builder/use-display-resolver.ts +24 -0
  389. package/src/app/builder/use-save-dashboard.ts +5 -0
  390. package/src/app/index.ts +20 -3
  391. package/src/app/layout-builder.tsx +193 -186
  392. package/src/app/mcp-app-view-chrome.test.tsx +487 -14
  393. package/src/app/mcp-app-view-chrome.tsx +328 -100
  394. package/src/app/mcp-app-view-states.tsx +140 -0
  395. package/src/app/mcp-app-view.effects.test.tsx +292 -0
  396. package/src/app/mcp-app-view.test.tsx +478 -0
  397. package/src/app/mcp-app-view.tsx +447 -190
  398. package/src/app/mcp-toolkit-app.test.tsx +40 -0
  399. package/src/app/mcp-toolkit-app.tsx +39 -8
  400. package/src/app/mcp-use-host-bridge.test.tsx +180 -0
  401. package/src/app/mcp-use-host-bridge.tsx +67 -24
  402. package/src/app/model-context.test.tsx +116 -0
  403. package/src/app/model-context.tsx +125 -0
  404. package/src/app/use-build-mode-focus.ts +62 -0
  405. package/src/app/use-view-actions.ts +89 -0
  406. package/src/app/use-view-lifecycle.ts +122 -0
  407. package/src/app/view-lifecycle.test.ts +443 -0
  408. package/src/app/view-lifecycle.ts +334 -0
  409. package/src/app/widget-fixture.effects.test.tsx +51 -0
  410. package/src/app/widget-fixture.render.test.tsx +81 -0
  411. package/src/app/widget-fixture.tsx +34 -11
  412. package/src/app/widget-props-sheet.tsx +67 -39
  413. package/src/app/widget-renderer.render.test.tsx +131 -0
  414. package/src/app/widget-renderer.tsx +136 -38
  415. package/src/components/DrillButton.tsx +24 -10
  416. package/src/components/EmptyState.tsx +83 -0
  417. package/src/components/ErrorState.tsx +204 -0
  418. package/src/components/FilterBar.test.tsx +82 -0
  419. package/src/components/FilterBar.tsx +56 -27
  420. package/src/components/GroupCard.tsx +3 -2
  421. package/src/components/KpiGrid.test.tsx +279 -0
  422. package/src/components/KpiGrid.tsx +329 -91
  423. package/src/components/ListFooter.test.tsx +116 -0
  424. package/src/components/ListFooter.tsx +110 -12
  425. package/src/components/PagedListFooter.test.tsx +140 -0
  426. package/src/components/PagedListFooter.tsx +107 -0
  427. package/src/components/Pills.test.tsx +77 -0
  428. package/src/components/Pills.tsx +78 -16
  429. package/src/components/SectionHeading.tsx +9 -3
  430. package/src/components/Toast.tsx +206 -0
  431. package/src/components/WidgetHeader.test.tsx +78 -0
  432. package/src/components/WidgetHeader.tsx +63 -16
  433. package/src/components/feedback-states.test.tsx +314 -0
  434. package/src/components/tone-figures.test.tsx +58 -0
  435. package/src/fonts.css +19 -0
  436. package/src/globals.css +9 -165
  437. package/src/hooks/index.ts +6 -3
  438. package/src/hooks/tool-result-recovery-model.test.ts +744 -0
  439. package/src/hooks/tool-result-recovery-model.ts +418 -0
  440. package/src/hooks/use-tool-query.effects.test.tsx +205 -0
  441. package/src/hooks/use-tool-query.ts +156 -33
  442. package/src/hooks/use-tool-result-recovery.effects.test.tsx +141 -0
  443. package/src/hooks/use-tool-result-recovery.test.ts +28 -5
  444. package/src/hooks/use-tool-result-recovery.ts +101 -131
  445. package/src/hooks/use-view-data.test.tsx +169 -0
  446. package/src/hooks/use-view-data.ts +131 -10
  447. package/src/hooks/use-view-tool-query.ts +25 -19
  448. package/src/i18n-coverage.test.tsx +425 -0
  449. package/src/index.ts +102 -13
  450. package/src/lib/focus-return.test.ts +288 -0
  451. package/src/lib/focus-return.ts +96 -0
  452. package/src/lib/focus-ring.ts +20 -0
  453. package/src/lib/icon-stroke.test.ts +59 -0
  454. package/src/lib/icon-stroke.ts +13 -0
  455. package/src/lib/parse-tool-result.test.ts +46 -1
  456. package/src/lib/parse-tool-result.ts +40 -2
  457. package/src/lib/toast-model.test.ts +233 -0
  458. package/src/lib/toast-model.ts +165 -0
  459. package/src/lib/tone-utils.test.ts +125 -16
  460. package/src/lib/tone-utils.ts +144 -15
  461. package/src/lib/tool-query.test.ts +569 -0
  462. package/src/lib/tool-query.ts +253 -0
  463. package/src/lib/tool-result-observer.test.ts +296 -0
  464. package/src/lib/tool-result-observer.ts +211 -0
  465. package/src/lib/typography.test.ts +45 -0
  466. package/src/lib/typography.ts +47 -0
  467. package/src/lib/utils.ts +12 -1
  468. package/src/locale/index.ts +14 -1
  469. package/src/locale/locale-provider.test.tsx +87 -0
  470. package/src/locale/locale-provider.tsx +90 -3
  471. package/src/locale/message-types.ts +337 -0
  472. package/src/locale/messages-de.ts +198 -0
  473. package/src/locale/messages-en.ts +196 -0
  474. package/src/locale/toolkit-messages.test.ts +349 -0
  475. package/src/locale/toolkit-messages.ts +143 -0
  476. package/src/no-hardcoded-copy.test.ts +432 -0
  477. package/src/primitives/alert.tsx +7 -2
  478. package/src/primitives/badge.tsx +19 -7
  479. package/src/primitives/button.tsx +13 -8
  480. package/src/primitives/card.tsx +4 -4
  481. package/src/primitives/dialog.tsx +42 -8
  482. package/src/primitives/input.tsx +5 -3
  483. package/src/primitives/scroll-area.tsx +1 -1
  484. package/src/primitives/select.tsx +7 -4
  485. package/src/primitives/sheet.tsx +33 -6
  486. package/src/primitives/skeleton.tsx +1 -1
  487. package/src/primitives/switch.tsx +4 -2
  488. package/src/primitives/tabs.tsx +9 -6
  489. package/src/providers/query-provider.test.tsx +154 -0
  490. package/src/providers/query-provider.tsx +66 -5
  491. package/src/render-cases-primitives.tsx +1 -1
  492. package/src/render-cases.tsx +82 -6
  493. package/src/render-effects.tsx +96 -0
  494. package/src/render-scan.ts +75 -0
  495. package/src/theme/cd-tokens.generated.css +48 -0
  496. package/src/theme/cd-tokens.test.ts +109 -0
  497. package/src/theme/cd-tokens.ts +67 -0
  498. package/src/theme/create-theme.test.ts +253 -5
  499. package/src/theme/create-theme.ts +341 -41
  500. package/src/theme/fonts.test.ts +85 -0
  501. package/src/theme/fonts.ts +72 -0
  502. package/src/theme/host-fonts.test.ts +135 -0
  503. package/src/theme/host-fonts.ts +52 -0
  504. package/src/theme/index.ts +15 -1
  505. package/src/theme/portal-container.ts +20 -0
  506. package/src/theme/presets.ts +44 -11
  507. package/src/theme/theme-css.test.ts +619 -0
  508. package/src/theme/theme-provider.test.tsx +82 -0
  509. package/src/theme/theme-provider.tsx +60 -24
  510. package/src/theme.css +344 -0
  511. package/ui-catalog.json +783 -49
@@ -0,0 +1,334 @@
1
+ /**
2
+ * The `McpAppView` lifecycle as an explicit state machine (toolkit#176).
3
+ *
4
+ * Before the view payload exists the shell is in exactly one phase:
5
+ *
6
+ * waiting ──result──▶ result
7
+ * │ ╲
8
+ * │ ╲──tool error / failed recovery / unrecoverable result──▶ error ──retry──▶ waiting
9
+ * │ ╲
10
+ * │ ╲──stripped result the policy must not re-load──▶ error (blocked)
11
+ * │
12
+ * └──host cancelled──▶ cancelled (terminal: the user stopped it)
13
+ *
14
+ * `waiting` carries a `slow` flag once loading outlasts the slow-hint delay.
15
+ * An `isError` result (or a failed call) is an `error`, never an endless
16
+ * skeleton, and a retry re-runs the originating tool once on the user's click.
17
+ * A latched result that is no view on a host that names no originating tool
18
+ * is an `error` too: nothing could recover it, so waiting would never end.
19
+ * A `blocked` error is no failure: the tool ran, the host kept its result
20
+ * from the view, and the recovery policy forbids re-running it. It offers to
21
+ * load the view only for a tool the host does not report as writing, so one
22
+ * click never runs a write a second time (CI §9).
23
+ * Pure functions only: the hook in `use-view-lifecycle.ts` feeds them.
24
+ */
25
+ import { parseToolResult } from "../lib/parse-tool-result.js"
26
+
27
+ /** `useToolContext().status` of the rendering call. */
28
+ export type RenderCallStatus = "pending" | "ready" | "error"
29
+
30
+ /** The user-triggered retry of the rendering call. */
31
+ export type RetryState =
32
+ | { status: "idle"; attempt: number }
33
+ | { status: "running"; attempt: number }
34
+ | { status: "failed"; attempt: number; reason: string | undefined }
35
+
36
+ export const RETRY_IDLE: RetryState = { status: "idle", attempt: 0 }
37
+
38
+ export type RetryAction =
39
+ { type: "start" } | { type: "failed"; reason: string | undefined } | { type: "succeeded" }
40
+
41
+ /** start → running (next attempt); failed → failed with reason; succeeded → idle. */
42
+ export function retryReducer(state: RetryState, action: RetryAction): RetryState {
43
+ switch (action.type) {
44
+ case "start":
45
+ return { status: "running", attempt: state.attempt + 1 }
46
+ case "failed":
47
+ return { status: "failed", attempt: state.attempt, reason: action.reason }
48
+ case "succeeded":
49
+ return { status: "idle", attempt: state.attempt }
50
+ }
51
+ }
52
+
53
+ export interface ViewLifecycleInput {
54
+ /** A renderable payload is in hand (host-delivered, recovered or retried). */
55
+ hasViewData: boolean
56
+ /** Status of the original rendering call. */
57
+ callStatus: RenderCallStatus
58
+ /** The host cancelled the original rendering call. */
59
+ cancelled: boolean
60
+ /** Message of the original call's error, when it failed. */
61
+ callErrorReason: string | undefined
62
+ /**
63
+ * The automatic stripped-result recovery ran and failed, or was needed but
64
+ * could not run because the host names no tool.
65
+ */
66
+ recoveryFailed: boolean
67
+ /**
68
+ * The host did not pass the result to the view, and the recovery policy
69
+ * forbids re-running the tool: it ran, but the view never got its result.
70
+ * Default `false`.
71
+ */
72
+ recoveryBlocked?: boolean
73
+ /**
74
+ * The host reports the originating tool as writing (`readOnlyHint: false`
75
+ * or `destructiveHint: true`). A blocked state then offers no retry: one
76
+ * click would run the write a second time. Default `false`.
77
+ */
78
+ toolWrites?: boolean
79
+ /** The user-triggered retry. */
80
+ retryState: RetryState
81
+ /** Loading has outlasted the slow-hint delay. */
82
+ slow: boolean
83
+ /** The originating tool is known, so a retry can re-run it. */
84
+ canRetry: boolean
85
+ }
86
+
87
+ /**
88
+ * What an `error` phase stands for: `"failed"` (the call, the recovery or the
89
+ * retry failed) or `"blocked"` (the tool ran, but the host did not pass its
90
+ * result to the view, and the recovery policy keeps the view from re-running it).
91
+ */
92
+ export type ViewErrorKind = "failed" | "blocked"
93
+
94
+ export type ViewLifecycle =
95
+ | { phase: "result" }
96
+ | { phase: "waiting"; slow: boolean }
97
+ | { phase: "cancelled" }
98
+ | {
99
+ phase: "error"
100
+ reason: string | undefined
101
+ canRetry: boolean
102
+ /** Absent for a failure; `"blocked"` for a result the view must not re-load. */
103
+ kind?: ViewErrorKind
104
+ }
105
+
106
+ /**
107
+ * Resolve the single phase the view is in. Precedence: a payload always wins;
108
+ * a running retry is waiting; a failed retry is an error with ITS reason; a
109
+ * cancellation is terminal; a failed call is an error; a blocked recovery is
110
+ * a `blocked` error, retryable only for a tool not reported as writing; a
111
+ * failed recovery or a latched non-view result nothing can recover is an
112
+ * error; everything else is still waiting.
113
+ */
114
+ export function resolveViewLifecycle(input: ViewLifecycleInput): ViewLifecycle {
115
+ if (input.hasViewData) return { phase: "result" }
116
+ const { retryState } = input
117
+ if (retryState.status === "running") return { phase: "waiting", slow: input.slow }
118
+ if (retryState.status === "failed") {
119
+ return { phase: "error", reason: retryState.reason, canRetry: input.canRetry }
120
+ }
121
+ if (input.cancelled) return { phase: "cancelled" }
122
+ if (input.callStatus === "error") {
123
+ return { phase: "error", reason: input.callErrorReason, canRetry: input.canRetry }
124
+ }
125
+ if (input.recoveryBlocked) {
126
+ const canRetry = input.canRetry && !input.toolWrites
127
+ return { phase: "error", reason: undefined, canRetry, kind: "blocked" }
128
+ }
129
+ if (input.recoveryFailed) return { phase: "error", reason: undefined, canRetry: input.canRetry }
130
+ // A latched result without a renderable view: the recovery re-runs the
131
+ // originating tool, so it can only help when the host names that tool.
132
+ if (input.callStatus === "ready" && !input.canRetry) {
133
+ return { phase: "error", reason: undefined, canRetry: false }
134
+ }
135
+ return { phase: "waiting", slow: input.slow }
136
+ }
137
+
138
+ /**
139
+ * The phase with its slow flag: a wait turns slow only while the wait that
140
+ * outlasted the delay (`slowWait`, a {@link waitKey}) is still the current one,
141
+ * so a new attempt starts without the hint.
142
+ */
143
+ export function withSlowHint(
144
+ lifecycle: ViewLifecycle,
145
+ slowWait: string | null,
146
+ currentWait: string | null,
147
+ ): ViewLifecycle {
148
+ return lifecycle.phase === "waiting" && slowWait !== null && slowWait === currentWait
149
+ ? { phase: "waiting", slow: true }
150
+ : lifecycle
151
+ }
152
+
153
+ /**
154
+ * Where keyboard focus goes after the user pressed retry (the button unmounts
155
+ * as the view starts waiting): the view frame while waiting, the new retry
156
+ * button when the retry failed, the frame again once the view or a
157
+ * cancellation replaced the state. `settled` ends the hand-off.
158
+ */
159
+ export function retryFocusTarget(phase: ViewLifecycle["phase"]): {
160
+ target: "frame" | "retry"
161
+ settled: boolean
162
+ } {
163
+ if (phase === "waiting") return { target: "frame", settled: false }
164
+ if (phase === "error") return { target: "retry", settled: true }
165
+ return { target: "frame", settled: true }
166
+ }
167
+
168
+ /**
169
+ * Identity of the current wait, so the slow-hint timer restarts for every new
170
+ * wait (the first load, then each retry attempt) and never for a finished one.
171
+ * `null` when the view is not waiting.
172
+ */
173
+ export function waitKey(lifecycle: ViewLifecycle, retryState: RetryState): string | null {
174
+ return lifecycle.phase === "waiting" ? `${retryState.status}:${retryState.attempt}` : null
175
+ }
176
+
177
+ /** Default delay before the waiting placeholder adds its slow hint. */
178
+ export const DEFAULT_SLOW_HINT_MS = 8000
179
+
180
+ /**
181
+ * A decoded view call (a retry or a refresh), typed so a failure never turns
182
+ * into UI text: `isError` tells a tool error (with its raw `reason`, for the
183
+ * Details disclosure or the console) from a payload that is no view.
184
+ */
185
+ export type ViewResultOutcome<T> =
186
+ { ok: true; data: T } | { ok: false; isError: boolean; reason: string | undefined }
187
+
188
+ /**
189
+ * Decode a view call's result: the payload when it is a complete view,
190
+ * otherwise a failure — a tool error with its `isError` text, or a payload
191
+ * the shell cannot render (no reason).
192
+ */
193
+ export function decodeViewResult<T>(
194
+ result: unknown,
195
+ isValid: (value: unknown) => value is T,
196
+ ): ViewResultOutcome<T> {
197
+ let decoded: unknown
198
+ try {
199
+ decoded = parseToolResult<unknown>(result)
200
+ } catch (err) {
201
+ return { ok: false, isError: true, reason: errorMessage(err) }
202
+ }
203
+ return isValid(decoded)
204
+ ? { ok: true, data: decoded }
205
+ : { ok: false, isError: false, reason: undefined }
206
+ }
207
+
208
+ /** What {@link describeViewForModel} reports on. */
209
+ export interface ViewModelContextInput {
210
+ lifecycle: ViewLifecycle
211
+ /** The originating tool, when the host names it. */
212
+ toolName: string | undefined
213
+ /** The shown payload's failed data steps (result phase). */
214
+ stepErrors?: readonly { stepId: string; reason: string }[]
215
+ /** The step-error alert offers a retry (the view can refresh). */
216
+ stepsRetryable?: boolean
217
+ /**
218
+ * When the view re-loaded the shown payload itself (epoch ms, see
219
+ * {@link reloadedAtOf}); `null` for a payload the host delivered.
220
+ */
221
+ reloadedAt?: number | null
222
+ /**
223
+ * How it re-loaded: it re-ran the tool because the host did not pass the
224
+ * result along (`"recovery"`, the default: its own recovery or the user's
225
+ * retry), the user refreshed the view (`"refresh"`), or the user applied an
226
+ * edit from the view builder, which ran the data steps again (`"builder"`).
227
+ */
228
+ reloadedBy?: "recovery" | "refresh" | "builder"
229
+ }
230
+
231
+ /**
232
+ * Where the shown payload came from: the host's tool result, the view's own
233
+ * re-execution when the host stripped it, the user's retry, a refresh, or the
234
+ * user's Apply in the view builder (its figures are the builder's own run of
235
+ * the data steps).
236
+ */
237
+ export type ViewSource = "host" | "recovered" | "retry" | "refresh" | "builder"
238
+
239
+ /**
240
+ * When a payload counts as re-loaded by the view itself (N23): `at` when its
241
+ * figures can differ from the result the chat answered from, else `null`. A
242
+ * recovery, a refresh and a builder Apply always; a retry only after a call
243
+ * whose result reached the chat (a stripped or unusable one), never after a
244
+ * failed or cancelled call, of which the model never had figures. The host's
245
+ * own payload never.
246
+ */
247
+ export function reloadedAtOf(source: ViewSource, at: number, callFailed: boolean): number | null {
248
+ if (source === "host") return null
249
+ if (source === "retry" && callFailed) return null
250
+ return at
251
+ }
252
+
253
+ /** How the view says, to the model, that it loaded the shown figures itself. */
254
+ const RELOADED_BY: Record<
255
+ NonNullable<ViewModelContextInput["reloadedBy"]>,
256
+ (at: string) => string
257
+ > = {
258
+ recovery: (at) =>
259
+ `re-ran the tool at ${at} because the host did not pass the original result to the view`,
260
+ refresh: (at) => `was refreshed by the user at ${at}`,
261
+ builder: (at) =>
262
+ `was edited by the user in the view builder, which loaded its data again at ${at}`,
263
+ }
264
+
265
+ /**
266
+ * The view's own line of model context (toolkit#176 K20): what failed and
267
+ * that the user sees an error instead of the view; a blocked recovery (the
268
+ * tool ran, its result never reached the view, and the model must not re-run
269
+ * it just to show the view); failed data steps; a payload the view re-loaded
270
+ * itself (a recovery, a retry, a refresh, a builder Apply), whose figures can
271
+ * differ from the tool result the model answered from (N23). `null` while
272
+ * there is nothing to report (waiting, or a plain host-delivered view).
273
+ * English: it is read by the model, never shown.
274
+ */
275
+ export function describeViewForModel(input: ViewModelContextInput): string | null {
276
+ const subject = input.toolName ? `The view of ${input.toolName}` : "The view"
277
+ const { lifecycle } = input
278
+ if (lifecycle.phase === "error") {
279
+ if (lifecycle.kind === "blocked") {
280
+ return describeBlockedForModel(subject, input.toolName, lifecycle.canRetry)
281
+ }
282
+ const reason = lifecycle.reason ? ` (reason: ${lifecycle.reason})` : ""
283
+ const offer = lifecycle.canRetry ? "an error message with a retry button" : "an error message"
284
+ return `${subject} could not be shown${reason}. The user sees ${offer} instead of the view.`
285
+ }
286
+ if (lifecycle.phase === "cancelled") {
287
+ return `${subject} was cancelled before it rendered. The user sees a note that the call was cancelled.`
288
+ }
289
+ if (lifecycle.phase === "waiting") return null
290
+ const lines: string[] = []
291
+ if (input.reloadedAt != null) {
292
+ const at = new Date(input.reloadedAt).toISOString()
293
+ const how = RELOADED_BY[input.reloadedBy ?? "recovery"](at)
294
+ lines.push(
295
+ `${subject} ${how}; its figures are live as of then and can differ from the original result.`,
296
+ )
297
+ }
298
+ const failed = input.stepErrors ?? []
299
+ if (failed.length > 0) {
300
+ const steps = failed.map((e) => `${e.stepId}: ${e.reason}`).join("; ")
301
+ const lead = lines.length > 0 ? "Some" : `${subject} is shown, but some`
302
+ const offer = input.stepsRetryable ? "an alert with a retry button" : "an alert"
303
+ lines.push(`${lead} of its data could not be loaded (${steps}). The user sees ${offer}.`)
304
+ }
305
+ return lines.length > 0 ? lines.join(" ") : null
306
+ }
307
+
308
+ /**
309
+ * The model's line for a blocked recovery: the tool ran (nothing failed), the
310
+ * host kept its result from the view, the view did not re-run it, and the
311
+ * model must not re-run it just to show the view, a write least of all.
312
+ */
313
+ function describeBlockedForModel(
314
+ subject: string,
315
+ toolName: string | undefined,
316
+ canRetry: boolean,
317
+ ): string {
318
+ const tool = toolName ?? "the tool"
319
+ const offer = canRetry
320
+ ? "a note with a button that loads the view"
321
+ : "a note to ask in the chat for the current state"
322
+ return (
323
+ `${subject} is not shown: ${tool} ran, but the host did not pass its result to the view, ` +
324
+ `and the view did not re-run it. The user sees ${offer} instead of the view. ` +
325
+ `Do not re-run ${tool} just to show the view.`
326
+ )
327
+ }
328
+
329
+ /** A displayable message for anything thrown. */
330
+ export function errorMessage(err: unknown): string | undefined {
331
+ if (err instanceof Error) return err.message || undefined
332
+ if (typeof err === "string") return err || undefined
333
+ return undefined
334
+ }
@@ -0,0 +1,51 @@
1
+ // @vitest-environment happy-dom
2
+ import { useEffect } from "react"
3
+ import { afterEach, describe, expect, it } from "vitest"
4
+ import { useToast } from "../components/Toast.js"
5
+ import { TOOLKIT_MESSAGES } from "../locale/toolkit-messages.js"
6
+ import { renderWithEffects, type EffectRender } from "../render-effects.js"
7
+ import { WidgetFixtureHost } from "./widget-fixture.js"
8
+
9
+ /**
10
+ * The harness's toast stack inside the host's locale scope, with its effects
11
+ * running: a toast exists only after a widget's effect shows it, so the SSR
12
+ * suite (`widget-fixture.render.test.tsx`) never sees its words. Its one
13
+ * localized word is the dismiss button's name; it follows `globals.locale`
14
+ * like `McpAppView`'s stack does, which holds only while the `ToastProvider`
15
+ * sits inside the `ToolkitLocaleFallback`.
16
+ */
17
+
18
+ /** A widget that reports a write's outcome as a toast once it mounts. */
19
+ function SavesOnMount() {
20
+ const toast = useToast()
21
+ useEffect(() => {
22
+ toast.show({ tone: "success", title: "Saved" })
23
+ }, [toast])
24
+ return <p>widget</p>
25
+ }
26
+
27
+ let mounted: EffectRender | null = null
28
+
29
+ afterEach(async () => {
30
+ await mounted?.unmount()
31
+ mounted = null
32
+ })
33
+
34
+ /** The dismiss button's accessible name of the toast the widget raised. */
35
+ async function dismissLabel(globals?: Record<string, unknown>): Promise<string | null> {
36
+ mounted = await renderWithEffects(<WidgetFixtureHost widget={SavesOnMount} globals={globals} />)
37
+ const toast = mounted.container.querySelector('[data-slot="toast"]')
38
+ expect(toast?.textContent, "the widget's toast reached the stack").toContain("Saved")
39
+ return toast?.querySelector("button")?.getAttribute("aria-label") ?? null
40
+ }
41
+
42
+ describe("WidgetFixtureHost toast stack (effects)", () => {
43
+ it("names a toast's dismiss button in globals.locale", async () => {
44
+ expect(await dismissLabel({ locale: "de" })).toBe(TOOLKIT_MESSAGES.de.common.dismiss)
45
+ })
46
+
47
+ it("names it in English without a host locale", async () => {
48
+ expect(await dismissLabel()).toBe(TOOLKIT_MESSAGES.en.common.dismiss)
49
+ expect(TOOLKIT_MESSAGES.en.common.dismiss).not.toBe(TOOLKIT_MESSAGES.de.common.dismiss)
50
+ })
51
+ })
@@ -0,0 +1,81 @@
1
+ import { renderToStaticMarkup } from "react-dom/server"
2
+ import { describe, expect, it } from "vitest"
3
+ import { Check } from "lucide-react"
4
+ import { ListFooter } from "../components/ListFooter.js"
5
+ import { ICON_STROKE } from "../lib/icon-stroke.js"
6
+ import { LocaleProvider, useLocale } from "../locale/locale-provider.js"
7
+ import { useHostBridge } from "./host-bridge.js"
8
+ import { WidgetFixtureHost } from "./widget-fixture.js"
9
+
10
+ /**
11
+ * The harness gives a widget what `McpAppView` gives it in a host, so a
12
+ * preview in the widget playground shows what a user sees: the toolkit's
13
+ * strings in the host locale (`globals.locale`), the host theme on the
14
+ * bridge (`globals.theme`), the CI icon stroke and a toast stack.
15
+ */
16
+
17
+ /** A widget made of toolkit chrome: the list footer's text follows the toolkit locale. */
18
+ function Footer() {
19
+ return <ListFooter shown={8} total={1234} hasMore onLoadMore={() => {}} noun="Aufgaben" />
20
+ }
21
+
22
+ const render = (globals?: Record<string, unknown>, widget = Footer) =>
23
+ renderToStaticMarkup(<WidgetFixtureHost widget={widget} globals={globals} />)
24
+
25
+ describe("WidgetFixtureHost host scope", () => {
26
+ it("renders the toolkit's strings and number format in globals.locale", () => {
27
+ const html = render({ locale: "de-DE" })
28
+ expect(html).toContain("Zeigt 8 von 1.234 Aufgaben")
29
+ expect(html).toContain("Mehr laden")
30
+ })
31
+
32
+ it("defaults to English and falls back to it for a blank locale", () => {
33
+ for (const globals of [undefined, { locale: "" }, { locale: 42 }]) {
34
+ const html = render(globals)
35
+ expect(html).toContain("Showing 8 of 1,234 Aufgaben")
36
+ expect(html).toContain("Load more")
37
+ }
38
+ })
39
+
40
+ it("lets a LocaleProvider around the harness win over globals.locale", () => {
41
+ const html = renderToStaticMarkup(
42
+ <LocaleProvider locale="de">
43
+ <WidgetFixtureHost widget={Footer} globals={{ locale: "en-US" }} />
44
+ </LocaleProvider>,
45
+ )
46
+ expect(html).toContain("Mehr laden")
47
+ })
48
+
49
+ it("leaves useLocale() of the widget alone, like McpAppView does", () => {
50
+ function ShowsLocale() {
51
+ return <p>{useLocale()}</p>
52
+ }
53
+ expect(render({ locale: "de-DE" }, ShowsLocale)).toContain("<p>en</p>")
54
+ })
55
+
56
+ it("reports globals.theme on the bridge", () => {
57
+ function ShowsTheme() {
58
+ return <p>{useHostBridge().theme}</p>
59
+ }
60
+ expect(render({ theme: "dark" }, ShowsTheme)).toContain("<p>dark</p>")
61
+ expect(render(undefined, ShowsTheme)).toContain("<p>light</p>")
62
+ })
63
+
64
+ it("draws Lucide icons at the view's stroke", () => {
65
+ expect(render(undefined, () => <Check />)).toContain(`stroke-width="${ICON_STROKE}"`)
66
+ })
67
+
68
+ it("mounts the toast stack as the view's one polite live region", () => {
69
+ // Like McpAppView's: fixed attributes, no role and no name, so the first
70
+ // toast lands in a region the browser already tracks. Its words (the
71
+ // dismiss label in the host locale) need a toast, so effects:
72
+ // widget-fixture.effects.test.tsx.
73
+ const html = render()
74
+ const viewport = /<div data-slot="toast-viewport"[^>]*>/.exec(html)?.[0]
75
+ expect(viewport).toBeDefined()
76
+ expect(viewport).toContain('aria-live="polite"')
77
+ expect(viewport).toContain('aria-atomic="false"')
78
+ expect(viewport).not.toContain("aria-label")
79
+ expect(viewport).not.toContain("role=")
80
+ })
81
+ })
@@ -1,5 +1,9 @@
1
1
  import { createContext, useContext, useMemo, type ComponentType, type ReactNode } from "react"
2
+ import { LucideProvider } from "lucide-react"
2
3
  import type { PipelineContext, StepResult, WidgetProps } from "@miragon/mcp-toolkit-core"
4
+ import { ToastProvider } from "../components/Toast.js"
5
+ import { ICON_STROKE } from "../lib/icon-stroke.js"
6
+ import { ToolkitLocaleFallback } from "../locale/locale-provider.js"
3
7
  import { AppQueryProvider } from "../providers/query-provider.js"
4
8
  import { HostBridgeProvider, createStandaloneHostBridge } from "./host-bridge.js"
5
9
 
@@ -143,10 +147,10 @@ export function useFixtureHost(): FixtureHostValue | null {
143
147
  }
144
148
 
145
149
  /**
146
- * Default host-context values the harness reports. Only `theme` is read since
147
- * the mcp-use 2.x move (the other host globals were consumed by the 1.x
148
- * `window.openai` shim); the object keeps its shape so existing playground
149
- * setups that spread overrides keep compiling.
150
+ * Default host-context values the harness reports. `theme` and `locale` are
151
+ * read since the mcp-use 2.x move (the other host globals were consumed by
152
+ * the 1.x `window.openai` shim); the object keeps its shape so existing
153
+ * playground setups that spread overrides keep compiling.
150
154
  */
151
155
  const DEFAULT_GLOBALS = {
152
156
  theme: "light",
@@ -192,9 +196,12 @@ export interface WidgetFixtureHostProps {
192
196
  */
193
197
  tools?: Record<string, FixtureToolEntry>
194
198
  /**
195
- * Host context overrides, merged over {@link DEFAULT_GLOBALS}. Since the
196
- * mcp-use 2.x move only `theme` influences the simulated host; the other
197
- * keys are accepted for compatibility with existing playground setups.
199
+ * Host context overrides, merged over {@link DEFAULT_GLOBALS}. Two keys
200
+ * influence the simulated host: `theme` (`"light"` | `"dark"`, reported by
201
+ * `useHostBridge().theme`) and `locale` (a BCP 47 tag: the toolkit's own
202
+ * strings and number format follow it, exactly as under `McpAppView`; a
203
+ * `LocaleProvider` around or inside the widget still wins). The other keys
204
+ * are accepted for compatibility with existing playground setups.
198
205
  */
199
206
  globals?: Record<string, unknown>
200
207
  /**
@@ -247,6 +254,10 @@ export function buildFixtureWidgetProps(
247
254
  * would (adapted widgets report through this bridge outside a real view).
248
255
  * - **The toolkit `AppQueryProvider`** — so widgets that fetch via `useToolQuery`
249
256
  * (e.g. generated `use*` hooks) resolve against the same registry.
257
+ * - **What `McpAppView` gives a widget** — the toolkit's strings in the host
258
+ * locale (`globals.locale`, as `McpAppView` does with the host's), the
259
+ * toast stack (`useToast()` shows real toasts) and the icon stroke of the
260
+ * view (2.5), so the preview matches what a host renders.
250
261
  *
251
262
  * ### Boundaries
252
263
  * The harness simulates the **host-portable pattern** (`useHostBridge`,
@@ -282,10 +293,14 @@ export function WidgetFixtureHost({
282
293
  // The standalone host bridge is the harness's single simulation source: it
283
294
  // logs every verb through the `HostActionLog` channel for the playground's
284
295
  // activity log. Depends on the callback props directly — a fresh Provider
285
- // value on callback change is fine for a dev harness. Only `globals.theme`
286
- // still influences the simulated host (the remaining globals fed the 1.x
287
- // `window.openai` shim).
296
+ // value on callback change is fine for a dev harness. `globals.theme` and
297
+ // `globals.locale` influence the simulated host (the remaining globals fed
298
+ // the 1.x `window.openai` shim).
288
299
  const bridgeTheme = (globals?.theme ?? DEFAULT_GLOBALS.theme) === "dark" ? "dark" : "light"
300
+ const hostLocale =
301
+ typeof globals?.locale === "string" && globals.locale.trim() !== ""
302
+ ? globals.locale
303
+ : DEFAULT_GLOBALS.locale
289
304
  const bridge = useMemo(
290
305
  () =>
291
306
  createStandaloneHostBridge({
@@ -308,11 +323,19 @@ export function WidgetFixtureHost({
308
323
  // widgets and raw `({ data })` widgets read their fixture from one mount.
309
324
  const RenderWidget = Widget as ComponentType<Record<string, unknown>>
310
325
 
326
+ // The same scope `McpAppView` puts around a widget: the toolkit's strings
327
+ // in the host locale, the CI icon stroke and a toast stack.
311
328
  return (
312
329
  <FixtureHostContext.Provider value={value}>
313
330
  <HostBridgeProvider bridge={bridge}>
314
331
  <AppQueryProvider callTool={callTool}>
315
- <RenderWidget {...widgetProps} data={data} />
332
+ <ToolkitLocaleFallback locale={hostLocale}>
333
+ <LucideProvider strokeWidth={ICON_STROKE}>
334
+ <ToastProvider>
335
+ <RenderWidget {...widgetProps} data={data} />
336
+ </ToastProvider>
337
+ </LucideProvider>
338
+ </ToolkitLocaleFallback>
316
339
  </AppQueryProvider>
317
340
  </HostBridgeProvider>
318
341
  </FixtureHostContext.Provider>