@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
@@ -1,23 +1,56 @@
1
- import { useState, useCallback } from "react"
2
- import { ToolCancelledError, useDisplayMode, useHostContext, useToolContext } from "mcp-use/react"
3
- import type { LayoutConfig, PipelineStepRef } from "@miragon/mcp-toolkit-core"
4
- import { AppQueryProvider, queryClient } from "../providers/query-provider.js"
5
- import { useToolResultRecovery } from "../hooks/use-tool-result-recovery.js"
6
- import { parseToolResult } from "../lib/parse-tool-result.js"
1
+ import { useState, useCallback, useEffect, useMemo, type ReactNode } from "react"
2
+ import { LucideProvider } from "lucide-react"
3
+ import { ToolCancelledError, useHostContext, useToolContext } from "mcp-use/react"
4
+ import { ToastProvider } from "../components/Toast.js"
5
+ import {
6
+ ToolkitLocaleFallback,
7
+ useToolkitLocale,
8
+ useToolkitMessages,
9
+ } from "../locale/locale-provider.js"
10
+ import { formatClockTime, resolveFormatLocale } from "../locale/toolkit-messages.js"
11
+ import { AppQueryProvider, useAppQueryClient } from "../providers/query-provider.js"
12
+ import { resolveToolSafety } from "../hooks/tool-result-recovery-model.js"
13
+ import {
14
+ DEFAULT_ASSUME_STRIPPED_AFTER_MS,
15
+ resolveRecoveryToolName,
16
+ useToolResultRecovery,
17
+ type RecoveryPolicy,
18
+ } from "../hooks/use-tool-result-recovery.js"
19
+ import { ICON_STROKE } from "../lib/icon-stroke.js"
7
20
  import { useHostBridge } from "./host-bridge.js"
21
+ import { useViewResultSink } from "./mcp-use-host-bridge.js"
22
+ import { HostModelContext } from "./model-context.js"
8
23
  import {
9
- DEFAULT_LABELS,
24
+ applyBuilderCommit,
10
25
  isCompleteViewData,
11
26
  StepErrors,
12
27
  ViewBody,
13
- ViewPlaceholder,
14
28
  ViewToolbar,
29
+ type BuilderCommit,
30
+ type DescribeStep,
15
31
  type McpAppViewLabels,
16
32
  type ViewData,
33
+ type ViewTitleLevel,
34
+ type ViewTitleMode,
17
35
  } from "./mcp-app-view-chrome.js"
36
+ import { ViewPlaceholder, type DescribeViewError } from "./mcp-app-view-states.js"
37
+ import type { LayoutBuilderLabels } from "./layout-builder.js"
38
+ import { useBuildModeFocus } from "./use-build-mode-focus.js"
39
+ import { useFullscreenToggle, useViewRefresh } from "./use-view-actions.js"
40
+ import { useRetryFocus, useViewLifecycle } from "./use-view-lifecycle.js"
41
+ import {
42
+ DEFAULT_SLOW_HINT_MS,
43
+ describeViewForModel,
44
+ reloadedAtOf,
45
+ type ViewLifecycle,
46
+ type ViewSource,
47
+ } from "./view-lifecycle.js"
18
48
  import type { WidgetComponent } from "./widget-renderer.js"
19
49
 
20
- export type { McpAppViewLabels } from "./mcp-app-view-chrome.js"
50
+ export type { McpAppViewLabels, ViewTitleLevel, ViewTitleMode } from "./mcp-app-view-chrome.js"
51
+ export type { DescribeStep } from "./mcp-app-view-chrome.js"
52
+ export type { DescribeViewError, ViewErrorKind } from "./mcp-app-view-states.js"
53
+ export type { RecoveryPolicy } from "../hooks/use-tool-result-recovery.js"
21
54
 
22
55
  export interface McpAppViewProps {
23
56
  /**
@@ -46,222 +79,446 @@ export interface McpAppViewProps {
46
79
  */
47
80
  builderEnabled?: boolean
48
81
  /**
49
- * Override UI strings (loading, refresh, fullscreen, build).
50
- * Defaults to English.
82
+ * Override chrome strings (loading, error, refresh, fullscreen, edit, …).
83
+ * Defaults to the active locale's toolkit catalog: the nearest
84
+ * `LocaleProvider`, else the host's locale (`en`/`de`, English otherwise).
51
85
  */
52
86
  labels?: McpAppViewLabels
87
+ /** Override strings of the embedded view builder (defaults: the locale's catalog). */
88
+ builderLabels?: LayoutBuilderLabels
89
+ /**
90
+ * How the toolbar shows the view title, so a view carries ONE title:
91
+ * `"show"` (default) renders it as a heading, `"hide"` leaves the title to
92
+ * the widget's own header, `"slot"` renders {@link McpAppViewProps.renderTitle}.
93
+ */
94
+ titleMode?: ViewTitleMode
95
+ /** Heading level of the toolbar title in `"show"` mode. Default `2`. */
96
+ titleLevel?: ViewTitleLevel
97
+ /** Renders the toolbar title in `"slot"` mode; receives the view's title. */
98
+ renderTitle?: (title: string | undefined) => ReactNode
99
+ /** Delay before the loading placeholder adds its "taking longer" hint. Default 8000 ms. */
100
+ slowHintAfterMs?: number
101
+ /**
102
+ * Map a raw rendering error (the tool's `isError` text) onto human text.
103
+ * The raw reason always stays available under "Details".
104
+ */
105
+ describeError?: DescribeViewError
106
+ /** Human title of a pipeline step in the failed-steps alert (default: the step id). */
107
+ describeStep?: DescribeStep
108
+ /**
109
+ * Which originating tools the view may re-execute on its own when the host
110
+ * strips the result's `structuredContent` (claude.ai, Claude Desktop).
111
+ * `"auto"` (default): any tool the host does not report as writing
112
+ * (`readOnlyHint: false` or `destructiveHint: true`); `"read-only"`: only
113
+ * tools reported with `readOnlyHint: true`; `"never"`: none. A recovery
114
+ * the policy forbids shows a note that the chat did not pass the result to
115
+ * the view. It offers to load the view only for a tool the host does not
116
+ * report as writing; for a writing tool it asks for the current state in
117
+ * the chat, so one click never runs the write a second time.
118
+ */
119
+ recoverStrippedResult?: RecoveryPolicy
120
+ /**
121
+ * Grace period (ms) after which a silent call counts as a stripped result.
122
+ * Only used where the view could not watch the host's notifications from
123
+ * the start (e.g. a custom shell mounted under its own `bootstrapView`
124
+ * without `installToolResultObserver`); a view mounted by
125
+ * `mountMcpToolkitApp` sees the stripped result itself and never guesses,
126
+ * so a slow tool is never re-run. Default 2500 ms.
127
+ */
128
+ strippedResultGraceMs?: number
129
+ /**
130
+ * Put the tool name into the cache keys of the data hooks inside the view
131
+ * (`[...queryKey, toolName, args]`, see `toolQueryKey`), so two tools never
132
+ * share an entry. Omitted: an enclosing `AppQueryProvider`'s setting, else
133
+ * the 2.x key `[...queryKey, args]`. The next major makes it the default.
134
+ */
135
+ toolNameInQueryKey?: boolean
53
136
  }
54
137
 
55
- export function McpAppView({
56
- widgets,
57
- refreshToolName = "refresh-view",
58
- builderEnabled,
59
- labels,
60
- }: McpAppViewProps) {
61
- const effectiveLabels = { ...DEFAULT_LABELS, ...labels }
62
- // Since mcp-use 2.x the shell reads the view channels through the dedicated
63
- // hooks (all require the surrounding `bootstrapView` mount that
64
- // `mountMcpToolkitApp` provides) and calls tools through the HostBridge that
65
- // `McpUseHostBridgeProvider` installs.
66
- const tool = useToolContext()
67
- const { displayMode, requestDisplayMode } = useDisplayMode()
68
- const { safeArea, hostContext } = useHostContext()
69
- const bridge = useHostBridge()
70
- const callTool = useCallback(
71
- (name: string, args: Record<string, unknown>) => bridge.callTool(name, args),
72
- [bridge],
138
+ /**
139
+ * The toolkit's MCP App view: lifecycle placeholder (waiting / error /
140
+ * cancelled), toolbar, failed-steps alert and the rendered layout (or the
141
+ * builder). Mounts a `ToastProvider` and binds the toolkit's own strings to
142
+ * the host's locale unless a `LocaleProvider` sets one, so the chrome follows
143
+ * the host language; `useLocale()` in consumer widgets is unaffected. Lucide
144
+ * icons draw at the CI stroke (2.5) throughout, the toast stack included.
145
+ * The widgets' data hooks fetch through an enclosing `AppQueryProvider`'s
146
+ * client, else the toolkit's shared `queryClient`.
147
+ */
148
+ export function McpAppView(props: McpAppViewProps) {
149
+ const { locale } = useHostContext()
150
+ return (
151
+ <ToolkitLocaleFallback locale={locale}>
152
+ {/* Wraps the ToastProvider, whose toast stack renders beside the shell. */}
153
+ <LucideProvider strokeWidth={ICON_STROKE}>
154
+ <ToastProvider>
155
+ <McpAppViewShell {...props} />
156
+ </ToastProvider>
157
+ </LucideProvider>
158
+ </ToolkitLocaleFallback>
73
159
  )
160
+ }
74
161
 
75
- const isPending = tool.status === "pending"
76
- // Since mcp-use 2.7.1 a host cancellation of the rendering call latches as a
77
- // terminal `status: "error"` — show it instead of the skeleton, and never
78
- // let the recovery below re-run the call the user stopped.
79
- const isCancelled = tool.status === "error" && tool.error instanceof ToolCancelledError
80
- const initialViewData = tool.status === "ready" ? (tool.toolOutput as ViewData) : undefined
81
- const toolInput = tool.toolInput
82
-
83
- const [viewData, setViewData] = useState<ViewData | null>(null)
84
- const [isRefreshing, setIsRefreshing] = useState(false)
162
+ /** The payload the view shows, where it came from, and when the view loaded it itself. */
163
+ interface ShownView {
164
+ data: ViewData | null
165
+ source: ViewSource | null
166
+ /**
167
+ * When the view re-loaded the payload itself (epoch ms), so its figures can
168
+ * differ from the result the chat answered from (`reloadedAtOf`); `null`
169
+ * otherwise.
170
+ */
171
+ reloadedAt: number | null
172
+ }
85
173
 
86
- // In-iframe build mode is purely a UI state — the LLM never sees the
87
- // catalogue, the user toggles into build by clicking the toolbar button.
88
- const [buildMode, setBuildMode] = useState(false)
174
+ const NO_VIEW: ShownView = { data: null, source: null, reloadedAt: null }
89
175
 
90
- // Sync host-provided initialViewData → local viewData via render-phase
91
- // setState (rather than an effect) so the React Compiler doesn't flag a
92
- // cascading render. The guard against transient empty toolOutput stays:
93
- // widgetProps may briefly drop `context`/`layout` when displayMode toggles,
94
- // and we don't want that to clobber a valid viewData.
95
- //
176
+ /**
177
+ * Host-delivered payload → the shown view, recovered payloads included, each
178
+ * tagged with its source. Render-phase setState (not an effect) so the React
179
+ * Compiler doesn't flag a cascading render.
180
+ */
181
+ function useHostViewData(
182
+ initialViewData: ViewData | undefined,
183
+ isPending: boolean,
184
+ recovered: ViewData | null,
185
+ recoveredAt: number | null,
186
+ ) {
187
+ const [view, setView] = useState<ShownView>(NO_VIEW)
96
188
  // `prev` MUST start as `null`, not `initialViewData`: hosts that expose the
97
189
  // complete tool output before the app's first render (e.g. window.openai
98
190
  // globals injected ahead of script execution) would otherwise never trip the
99
- // identity check — initialViewData never changes again, the sync never runs,
100
- // and the app idles on the loading skeleton forever.
101
- const [prevInitialViewData, setPrevInitialViewData] = useState<typeof initialViewData | null>(
102
- null,
103
- )
104
- if (initialViewData !== prevInitialViewData) {
105
- setPrevInitialViewData(initialViewData)
191
+ // identity check — the sync never runs and the app idles on the skeleton.
192
+ // The completeness guard stays: widgetProps may briefly drop
193
+ // `context`/`layout` when displayMode toggles.
194
+ const [prevInitial, setPrevInitial] = useState<ViewData | undefined | null>(null)
195
+ if (initialViewData !== prevInitial) {
196
+ setPrevInitial(initialViewData)
106
197
  if (!isPending && isCompleteViewData(initialViewData)) {
107
- setViewData(initialViewData)
198
+ setView({ data: initialViewData, source: "host", reloadedAt: null })
108
199
  }
109
200
  }
201
+ const [prevRecovered, setPrevRecovered] = useState<ViewData | null>(null)
202
+ if (recovered !== prevRecovered) {
203
+ setPrevRecovered(recovered)
204
+ // The host-delivered payload always wins; a recovery fills the gap, and a
205
+ // recovery for newer arguments replaces an older recovered payload (N21).
206
+ if (recovered) {
207
+ setView((prev) =>
208
+ prev.data === null || prev.source === "recovered"
209
+ ? { data: recovered, source: "recovered", reloadedAt: recoveredAt }
210
+ : prev,
211
+ )
212
+ }
213
+ }
214
+ return [view, setView] as const
215
+ }
216
+
217
+ /**
218
+ * Hands the payload the view shows (the host's, its own recovery's, the
219
+ * user's retry or refresh) to the host bridge, so `HostBridge.getWidgetData()`
220
+ * returns what the view shows (toolkit#176 K15), also after a latch the view
221
+ * could not render.
222
+ */
223
+ function useReportShownPayload(view: ShownView) {
224
+ const report = useViewResultSink()
225
+ const payload = view.data
226
+ useEffect(() => {
227
+ if (payload) report?.(payload)
228
+ }, [payload, report])
229
+ }
110
230
 
111
- // Hosts that strip `structuredContent` from the tool-result notification
112
- // (claude.ai / Claude Desktop) never latch a result under mcp-use 2.x — the
113
- // `useToolContext` latch ignores content-only results, so `isPending` stays
114
- // true forever. The recovery therefore also arms itself on a grace timer
115
- // once the originating tool is known, re-executes it via `callTool`
116
- // (responses carry `structuredContent` intact), and fills the gap.
117
- // Conforming hosts deliver a valid payload up front, so it never fires
118
- // there.
231
+ /** The rendering call as mcp-use 2.x latches it, plus the tool the host names. */
232
+ function useRenderCall() {
233
+ const tool = useToolContext()
234
+ const { hostContext } = useHostContext()
235
+ // Since mcp-use 2.7.1 a host cancellation latches as `status: "error"`.
236
+ const isCancelled = tool.status === "error" && tool.error instanceof ToolCancelledError
237
+ const isCallError = tool.status === "error" && !isCancelled
238
+ return {
239
+ tool,
240
+ hostContext,
241
+ isPending: tool.status === "pending",
242
+ isCancelled,
243
+ isCallError,
244
+ callError: isCallError ? tool.error : undefined,
245
+ initialViewData: tool.status === "ready" ? (tool.toolOutput as ViewData) : undefined,
246
+ toolName: resolveRecoveryToolName(hostContext),
247
+ /** The host reports the tool as writing: the view never offers to re-run it after a block. */
248
+ toolWrites: resolveToolSafety(hostContext) === "writes",
249
+ }
250
+ }
251
+
252
+ /**
253
+ * The payload the view shows: the host's, else the stripped-result recovery's.
254
+ * Hosts that strip `structuredContent` (claude.ai) never latch a result under
255
+ * mcp-use 2.x; the recovery re-executes the call once when the stripped
256
+ * result arrives. It never fires on a cancelled or failed call, nor while a
257
+ * slow tool is still running (#176): those render their state.
258
+ */
259
+ function useShownView(
260
+ call: ReturnType<typeof useRenderCall>,
261
+ callTool: (name: string, args: Record<string, unknown>) => Promise<unknown>,
262
+ policy: RecoveryPolicy = "auto",
263
+ graceMs: number = DEFAULT_ASSUME_STRIPPED_AFTER_MS,
264
+ ) {
119
265
  const recovery = useToolResultRecovery<ViewData>({
120
- resultReady: !isPending,
121
- cancelled: isCancelled,
122
- props: initialViewData,
266
+ resultReady: !call.isPending,
267
+ cancelled: call.isCancelled,
268
+ errored: call.isCallError,
269
+ props: call.initialViewData,
123
270
  isValid: isCompleteViewData,
124
- toolInput,
125
- hostContext,
271
+ toolInput: call.tool.toolInput,
272
+ hostContext: call.hostContext,
126
273
  callTool,
274
+ policy,
275
+ assumeStrippedAfterMs: graceMs,
127
276
  })
128
- const [prevRecovered, setPrevRecovered] = useState<ViewData | null>(null)
129
- if (recovery.data !== prevRecovered) {
130
- setPrevRecovered(recovery.data)
131
- // The host-delivered payload always wins; recovery only fills the gap.
132
- if (recovery.data && !viewData) setViewData(recovery.data)
277
+ const [view, setView] = useHostViewData(
278
+ call.initialViewData,
279
+ call.isPending,
280
+ recovery.data,
281
+ recovery.recoveredAt,
282
+ )
283
+ useReportShownPayload(view)
284
+ // A re-run's figures can differ from the answer in the chat (N23): stamp
285
+ // it, unless the original call failed and the chat never had figures.
286
+ const callFailed = call.isCallError || call.isCancelled
287
+ const showRetried = useCallback(
288
+ (data: ViewData) =>
289
+ setView({ data, source: "retry", reloadedAt: reloadedAtOf("retry", Date.now(), callFailed) }),
290
+ [setView, callFailed],
291
+ )
292
+ const showRefreshed = useCallback(
293
+ (data: ViewData) =>
294
+ setView({
295
+ data,
296
+ source: "refresh",
297
+ reloadedAt: reloadedAtOf("refresh", Date.now(), callFailed),
298
+ }),
299
+ [setView, callFailed],
300
+ )
301
+ return {
302
+ view,
303
+ setView,
304
+ showRetried,
305
+ showRefreshed,
306
+ // Without a named tool nothing can re-run it: a plain failure. A policy
307
+ // block is its own state: the tool ran, and the view must not repeat it.
308
+ recoveryFailed: recovery.failed || recovery.blocked === "no-tool",
309
+ recoveryBlocked: recovery.blocked === "policy",
133
310
  }
311
+ }
134
312
 
135
- const toggleFullscreen = useCallback(async () => {
136
- const newMode = displayMode === "fullscreen" ? "inline" : "fullscreen"
137
- try {
138
- // Advisory since 2.x: the promise only confirms the host processed the
139
- // request. `displayMode` from `useDisplayMode` updates reactively when
140
- // (and only when) the host actually applies the change.
141
- await requestDisplayMode({ mode: newMode })
142
- } catch (e) {
143
- console.error("Failed to toggle display mode:", e)
144
- }
145
- }, [displayMode, requestDisplayMode])
313
+ /**
314
+ * The view's own line of model context (toolkit#176 K20, N23): an error the
315
+ * user sees instead of the view, failed data steps, or data the view
316
+ * re-loaded itself (a recovery, a retry, a refresh). Renders nothing while
317
+ * there is nothing to report.
318
+ */
319
+ function ViewModelContext({
320
+ lifecycle,
321
+ toolName,
322
+ view,
323
+ }: {
324
+ lifecycle: ViewLifecycle
325
+ toolName: string | undefined
326
+ view: ShownView
327
+ }) {
328
+ const content = describeViewForModel({
329
+ lifecycle,
330
+ toolName,
331
+ stepErrors: view.data?.context.errors,
332
+ stepsRetryable: Boolean(view.data?._refreshParams),
333
+ reloadedAt: view.reloadedAt,
334
+ reloadedBy: reloadedByOf(view.source),
335
+ })
336
+ return content ? <HostModelContext content={content} /> : null
337
+ }
146
338
 
147
- // Stable callTool callback (avoids a new object reference on every render)
339
+ function McpAppViewShell({
340
+ widgets,
341
+ refreshToolName = "refresh-view",
342
+ builderEnabled,
343
+ labels,
344
+ builderLabels,
345
+ titleMode = "show",
346
+ titleLevel = 2,
347
+ renderTitle,
348
+ slowHintAfterMs = DEFAULT_SLOW_HINT_MS,
349
+ describeError,
350
+ describeStep,
351
+ recoverStrippedResult,
352
+ strippedResultGraceMs,
353
+ toolNameInQueryKey,
354
+ }: McpAppViewProps) {
355
+ const messages = useToolkitMessages()
356
+ const L = useMemo(() => ({ ...messages.view, ...labels }), [messages.view, labels])
357
+ // The mcp-use 2.x view channels (all need the `bootstrapView` mount
358
+ // `mountMcpToolkitApp` provides); tools go through the HostBridge.
359
+ const call = useRenderCall()
360
+ const { safeArea, locale, timeZone } = useHostContext()
361
+ // A time in the language of the sentence around it, in the host's zone.
362
+ const formatLocale = resolveFormatLocale(useToolkitLocale(), locale)
363
+ // The client the view's widgets fetch through (an enclosing
364
+ // AppQueryProvider's, else the shared one): a refresh invalidates it.
365
+ const viewClient = useAppQueryClient()
366
+ const bridge = useHostBridge()
367
+ const callTool = useCallback(
368
+ (name: string, args: Record<string, unknown>) => bridge.callTool(name, args),
369
+ [bridge],
370
+ )
371
+ // The widget-facing shape (`args: object`, so generated input interfaces
372
+ // without an index signature pass straight through).
148
373
  const callToolFn = useCallback(
149
- async (name: string, args: object) => {
150
- return callTool(name, args as Record<string, unknown>)
151
- },
152
- [callTool],
374
+ (name: string, args: object) => bridge.callTool(name, args as Record<string, unknown>),
375
+ [bridge],
153
376
  )
154
-
155
- const refreshView = useCallback(async () => {
156
- if (!viewData?._refreshParams) return
157
- setIsRefreshing(true)
158
- try {
159
- const result = await callTool(
160
- refreshToolName,
161
- viewData._refreshParams as unknown as Record<string, unknown>,
162
- )
163
- // Decode through the shared structured-first parser (one convention with
164
- // `useToolQuery`); a refresh returns the view envelope as `structuredContent`.
165
- const refreshed = parseToolResult<ViewData | null>(result)
166
- if (refreshed?.context && refreshed?.layout) {
167
- setViewData(refreshed)
168
- }
169
- void queryClient.invalidateQueries()
170
- } catch (e) {
171
- console.error("Failed to refresh view:", e)
172
- } finally {
173
- setIsRefreshing(false)
174
- }
175
- }, [callTool, refreshToolName, viewData])
176
-
177
- // Called by LayoutBuilder when the user clicks Done. Commits the draft
178
- // layout (and any keys/steps edits) into the parent viewData so the
179
- // WidgetRenderer takes over showing the just-built layout.
377
+ const shown = useShownView(call, callTool, recoverStrippedResult, strippedResultGraceMs)
378
+ const { view, setView } = shown
379
+ const viewData = view.data
380
+ const { lifecycle, retry: retryCall } = useViewLifecycle({
381
+ hasViewData: viewData !== null,
382
+ callStatus: call.tool.status,
383
+ cancelled: call.isCancelled,
384
+ callError: call.callError,
385
+ recoveryFailed: shown.recoveryFailed,
386
+ recoveryBlocked: shown.recoveryBlocked,
387
+ toolWrites: call.toolWrites,
388
+ toolName: call.toolName,
389
+ toolInput: call.tool.toolInput,
390
+ callTool,
391
+ isValid: isCompleteViewData,
392
+ onResult: shown.showRetried,
393
+ slowHintAfterMs,
394
+ })
395
+ const { frameRef, retry } = useRetryFocus(lifecycle.phase, retryCall)
396
+ const { isRefreshing, refreshView } = useViewRefresh({
397
+ viewData,
398
+ setViewData: shown.showRefreshed,
399
+ callTool,
400
+ refreshToolName,
401
+ labels: L,
402
+ client: viewClient,
403
+ })
404
+ const fullscreen = useFullscreenToggle(L)
405
+ // In-iframe build mode is purely a UI state — the LLM never sees the catalogue.
406
+ const [buildMode, setBuildMode] = useState(false)
407
+ useBuildModeFocus(buildMode, frameRef)
180
408
  const exitBuildMode = useCallback(
181
- (commit?: {
182
- layout: LayoutConfig
183
- keys?: Record<string, unknown>
184
- steps?: PipelineStepRef[]
185
- title?: string
186
- context?: ViewData["context"]
187
- }) => {
188
- if (commit) {
189
- setViewData((prev) =>
190
- prev
191
- ? {
192
- ...prev,
193
- layout: commit.layout,
194
- title: commit.title ?? prev.title,
195
- context: commit.context ?? prev.context,
196
- _refreshParams: {
197
- keys: commit.keys,
198
- steps: commit.steps,
199
- layout: commit.layout,
200
- title: commit.title ?? prev.title,
201
- },
202
- }
203
- : prev,
204
- )
205
- }
409
+ (commit?: BuilderCommit) => {
410
+ if (commit) setView((prev) => withBuilderCommit(prev, commit))
206
411
  setBuildMode(false)
207
412
  },
208
- [],
413
+ [setView],
414
+ )
415
+ const viewContext = (
416
+ <ViewModelContext lifecycle={lifecycle} toolName={call.toolName} view={view} />
209
417
  )
210
418
 
211
- // `isPending` must not gate the render: on hosts that strip
212
- // `structuredContent` the 2.x latch stays "pending" forever, and recovered
213
- // viewData still has to replace the skeleton.
419
+ // `isPending` must not gate the render: on stripping hosts the latch stays
420
+ // "pending" forever, and recovered viewData still has to replace it. The
421
+ // frame is the stable focus target of a retry (`useRetryFocus`).
214
422
  if (!viewData) {
215
- return <ViewPlaceholder cancelled={isCancelled} labels={effectiveLabels} />
423
+ return (
424
+ <div ref={frameRef} tabIndex={-1} data-slot="mcp-app-view" className="outline-hidden">
425
+ {viewContext}
426
+ <ViewPlaceholder
427
+ lifecycle={lifecycle}
428
+ labels={L}
429
+ onRetry={retry}
430
+ describeError={describeError}
431
+ />
432
+ </div>
433
+ )
216
434
  }
217
435
 
218
- const isFullscreen = displayMode === "fullscreen"
219
-
220
- // Follow the server's own signal by default: render-view reports
221
- // `builderAvailable` (derived from `app.builder`), so the Build button only
222
- // shows when the catalogue/dashboard tools are actually registered. An
223
- // explicit `builderEnabled` prop forces the affordance on or off.
224
- const builderAvailable = builderEnabled ?? viewData.builderAvailable ?? false
225
-
226
436
  return (
227
- <main
228
- style={{
229
- // Fullscreen: the widget owns the viewport, so we take the full window
230
- // and handle our own scrolling. Inline: the host (Claude, ChatGPT) owns
231
- // sizing + scrolling and observes our intrinsic height via
232
- // `bootstrapView`'s auto-resize, so we let content dictate height and
233
- // avoid setting overflowY (which would clip our own content inside a
234
- // host that's already auto-sized to us).
235
- ...(isFullscreen ? { minHeight: "100vh", overflowY: "auto" as const } : null),
236
- paddingTop: safeArea.top,
237
- paddingRight: safeArea.right,
238
- paddingBottom: safeArea.bottom,
239
- paddingLeft: safeArea.left,
240
- }}
241
- >
242
- <ViewToolbar
243
- hidden={buildMode}
244
- viewData={viewData}
245
- builderAvailable={builderAvailable}
246
- isFullscreen={isFullscreen}
247
- isRefreshing={isRefreshing}
248
- labels={effectiveLabels}
249
- onRefresh={refreshView}
250
- onEnterBuildMode={() => setBuildMode(true)}
251
- onToggleFullscreen={toggleFullscreen}
252
- />
437
+ <div ref={frameRef} tabIndex={-1} data-slot="mcp-app-view" className="outline-hidden">
438
+ {viewContext}
439
+ <main
440
+ style={{
441
+ // Fullscreen: the view owns the viewport and scrolls itself. Inline:
442
+ // the host sizes us from our intrinsic height (bootstrapView's
443
+ // auto-resize), so no overflow clipping there.
444
+ ...(fullscreen.isFullscreen ? { minHeight: "100vh", overflowY: "auto" as const } : null),
445
+ paddingTop: safeArea.top,
446
+ paddingRight: safeArea.right,
447
+ paddingBottom: safeArea.bottom,
448
+ paddingLeft: safeArea.left,
449
+ }}
450
+ >
451
+ <ViewToolbar
452
+ hidden={buildMode}
453
+ viewData={viewData}
454
+ builderAvailable={builderEnabled ?? viewData.builderAvailable ?? false}
455
+ isFullscreen={fullscreen.isFullscreen}
456
+ canFullscreen={fullscreen.canFullscreen}
457
+ isRefreshing={isRefreshing}
458
+ labels={L}
459
+ titleMode={titleMode}
460
+ titleLevel={titleLevel}
461
+ renderTitle={renderTitle}
462
+ onRefresh={refreshView}
463
+ onEnterBuildMode={() => setBuildMode(true)}
464
+ onToggleFullscreen={fullscreen.toggle}
465
+ dataAsOf={formatDataAsOf(view.reloadedAt, formatLocale, timeZone)}
466
+ />
253
467
 
254
- <StepErrors hidden={buildMode} errors={viewData.context.errors} />
468
+ <StepErrors
469
+ hidden={buildMode}
470
+ errors={viewData.context.errors}
471
+ steps={viewData._refreshParams?.steps}
472
+ labels={L}
473
+ describeStep={describeStep}
474
+ onRetry={viewData._refreshParams ? () => void refreshView() : undefined}
475
+ />
255
476
 
256
- <AppQueryProvider callTool={callToolFn}>
257
- <ViewBody
258
- buildMode={buildMode}
259
- viewData={viewData}
260
- widgets={widgets}
477
+ <AppQueryProvider
261
478
  callTool={callToolFn}
262
- onExit={exitBuildMode}
263
- />
264
- </AppQueryProvider>
265
- </main>
479
+ client={viewClient}
480
+ toolNameInQueryKey={toolNameInQueryKey}
481
+ >
482
+ <ViewBody
483
+ buildMode={buildMode}
484
+ viewData={viewData}
485
+ widgets={widgets}
486
+ callTool={callToolFn}
487
+ builderLabels={builderLabels}
488
+ onExit={exitBuildMode}
489
+ />
490
+ </AppQueryProvider>
491
+ </main>
492
+ </div>
266
493
  )
267
494
  }
495
+
496
+ /** How the model line names the view's own re-load of the shown payload. */
497
+ function reloadedByOf(source: ViewSource | null): "recovery" | "refresh" | "builder" {
498
+ if (source === "refresh" || source === "builder") return source
499
+ return "recovery"
500
+ }
501
+
502
+ /**
503
+ * The shown view with a builder commit applied (nothing to apply without
504
+ * data). A commit that carries the builder's live context shows figures the
505
+ * builder loaded itself (its catalogue call ran the steps again), so it is
506
+ * stamped like a refresh: the toolbar says when, and the model learns that
507
+ * they can differ from the result the chat answered from. A commit without a
508
+ * context keeps the shown figures, and with them their source and stamp.
509
+ */
510
+ function withBuilderCommit(view: ShownView, commit: BuilderCommit): ShownView {
511
+ if (!view.data) return view
512
+ const data = applyBuilderCommit(view.data, commit)
513
+ if (!commit.context) return { ...view, data }
514
+ return { data, source: "builder", reloadedAt: reloadedAtOf("builder", Date.now(), false) }
515
+ }
516
+
517
+ /** The toolbar's "as of" time of a re-loaded payload, `undefined` for any other. */
518
+ function formatDataAsOf(
519
+ reloadedAt: number | null,
520
+ locale: string,
521
+ timeZone: string | undefined,
522
+ ): string | undefined {
523
+ return reloadedAt === null ? undefined : formatClockTime(reloadedAt, locale, timeZone)
524
+ }