@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,36 +1,44 @@
1
1
  /**
2
- * Presentational chrome for `McpAppView` — the pre-payload placeholder, the
3
- * toolbar, the pipeline error list, and the build/render body — plus the
4
- * view-envelope types they share.
5
- * Split out of `mcp-app-view.tsx` so the shell component keeps only the
6
- * host-sync and state logic while this file stays hook-free.
2
+ * Presentational chrome for `McpAppView` — the toolbar, the failed-steps
3
+ * alert, and the build/render body — plus the view-envelope types they share.
4
+ * The pre-payload states (waiting / error / cancelled) live in
5
+ * `mcp-app-view-states.tsx`. Split out of `mcp-app-view.tsx` so the shell
6
+ * component keeps only the host-sync and state logic while these files stay
7
+ * hook-light.
7
8
  */
8
- import { Maximize2, Minimize2, Pencil, RefreshCw } from "lucide-react"
9
+ import { createElement, type ReactNode } from "react"
10
+ import { LucideProvider, Maximize2, Minimize2, Pencil, RefreshCw } from "lucide-react"
9
11
  import type { LayoutConfig, PipelineStepRef } from "@miragon/mcp-toolkit-core"
10
- import { Skeleton } from "../primitives/skeleton.js"
11
- import { LayoutBuilder, type LayoutBuilderProps } from "./layout-builder.js"
12
+ import { ErrorState } from "../components/ErrorState.js"
13
+ import { DENSE_ICON_STROKE } from "../lib/icon-stroke.js"
14
+ import { TOOLKIT_MESSAGES, type ViewMessages } from "../locale/toolkit-messages.js"
15
+ import { Button } from "../primitives/button.js"
16
+ import {
17
+ LayoutBuilder,
18
+ type LayoutBuilderLabels,
19
+ type LayoutBuilderProps,
20
+ } from "./layout-builder.js"
12
21
  import { WidgetRenderer, type WidgetComponent } from "./widget-renderer.js"
13
22
 
14
- export interface McpAppViewLabels {
15
- loading?: string
16
- /** Shown instead of the loading skeleton when the host cancelled the rendering tool call. */
17
- cancelled?: string
18
- refresh?: string
19
- refreshing?: string
20
- enterFullscreen?: string
21
- exitFullscreen?: string
22
- build?: string
23
- }
23
+ /**
24
+ * Override strings of the view chrome (placeholder, error state, toolbar,
25
+ * step errors). Every field defaults to the active locale's toolkit catalog
26
+ * (`en`/`de`, see `useToolkitMessages`).
27
+ */
28
+ export type McpAppViewLabels = Partial<ViewMessages>
24
29
 
25
- export const DEFAULT_LABELS: Required<McpAppViewLabels> = {
26
- loading: "Waiting for pipeline result...",
27
- cancelled: "Tool call was cancelled.",
28
- refresh: "Refresh",
29
- refreshing: "Loading...",
30
- enterFullscreen: "Fullscreen",
31
- exitFullscreen: "Collapse",
32
- build: "Build",
33
- }
30
+ /**
31
+ * English chrome strings.
32
+ * @deprecated Read the active locale's strings with `useToolkitMessages().view`
33
+ * (or `TOOLKIT_MESSAGES.en.view`); this alias stays for existing imports.
34
+ */
35
+ export const DEFAULT_LABELS: Required<McpAppViewLabels> = TOOLKIT_MESSAGES.en.view
36
+
37
+ /** How the toolbar shows the view title — so a view has exactly ONE title. */
38
+ export type ViewTitleMode = "show" | "hide" | "slot"
39
+
40
+ /** Heading level of the toolbar title. */
41
+ export type ViewTitleLevel = 1 | 2 | 3 | 4 | 5 | 6
34
42
 
35
43
  export interface RefreshParams {
36
44
  keys?: Record<string, unknown>
@@ -71,32 +79,40 @@ export function isCompleteViewData(value: unknown): value is ViewData {
71
79
  return Boolean(v?.context && v?.layout)
72
80
  }
73
81
 
74
- // -------------------------------------------------------------------------- //
75
- // Placeholder: shown until a view payload exists
76
- // -------------------------------------------------------------------------- //
82
+ /** What the LayoutBuilder hands back on Apply. */
83
+ export interface BuilderCommit {
84
+ layout: LayoutConfig
85
+ keys?: Record<string, unknown>
86
+ steps?: PipelineStepRef[]
87
+ title?: string
88
+ context?: ViewData["context"]
89
+ }
77
90
 
78
- export function ViewPlaceholder({
79
- cancelled,
80
- labels,
81
- }: {
82
- /** The host cancelled the rendering call — no payload will arrive. */
83
- cancelled: boolean
84
- labels: Required<McpAppViewLabels>
85
- }) {
86
- if (cancelled) {
87
- return (
88
- <div className="p-4">
89
- <p className="text-muted-foreground text-sm">{labels.cancelled}</p>
90
- </div>
91
- )
91
+ /**
92
+ * Commit the builder's draft into the view payload so the renderer shows the
93
+ * just-built layout and Refresh re-issues it. Exported for tests.
94
+ */
95
+ export function applyBuilderCommit(prev: ViewData, commit: BuilderCommit): ViewData {
96
+ const title = commit.title ?? prev.title
97
+ return {
98
+ ...prev,
99
+ layout: commit.layout,
100
+ title,
101
+ context: commit.context ?? prev.context,
102
+ _refreshParams: { keys: commit.keys, steps: commit.steps, layout: commit.layout, title },
92
103
  }
93
- return (
94
- <div className="flex flex-col gap-4 p-4">
95
- <Skeleton className="h-8 w-48" />
96
- <Skeleton className="h-32 w-full" />
97
- <p className="text-muted-foreground text-sm">{labels.loading}</p>
98
- </div>
99
- )
104
+ }
105
+
106
+ /**
107
+ * Whether the Fullscreen toggle may render: the host offers fullscreen
108
+ * (mcp-use's negotiated `availableDisplayModes`, toolkit#178 K22), or the view
109
+ * is already there (its exit toggle must stay).
110
+ */
111
+ export function fullscreenAvailable(
112
+ displayMode: string,
113
+ availableDisplayModes: readonly string[],
114
+ ): boolean {
115
+ return displayMode === "fullscreen" || availableDisplayModes.includes("fullscreen")
100
116
  }
101
117
 
102
118
  // -------------------------------------------------------------------------- //
@@ -109,91 +125,294 @@ export interface ViewToolbarProps {
109
125
  viewData: ViewData
110
126
  builderAvailable: boolean
111
127
  isFullscreen: boolean
128
+ /** The host offers fullscreen (see {@link fullscreenAvailable}). */
129
+ canFullscreen: boolean
112
130
  isRefreshing: boolean
113
131
  labels: Required<McpAppViewLabels>
132
+ titleMode: ViewTitleMode
133
+ titleLevel: ViewTitleLevel
134
+ renderTitle?: (title: string | undefined) => ReactNode
114
135
  onRefresh: () => Promise<void>
115
136
  onEnterBuildMode: () => void
116
137
  onToggleFullscreen: () => Promise<void>
138
+ /**
139
+ * The pre-formatted time of a view that loaded its data again on its own
140
+ * (toolkit#176 N23): the toolbar stamps it in a quiet line under the title,
141
+ * so the figures read as live data of that time, not the result the chat
142
+ * answered from.
143
+ */
144
+ dataAsOf?: string
117
145
  }
118
146
 
147
+ /** The toolbar title per {@link ViewTitleMode}: a heading, the consumer's slot, or nothing. */
148
+ function ToolbarTitle({
149
+ title,
150
+ mode,
151
+ level,
152
+ renderTitle,
153
+ }: {
154
+ title: string | undefined
155
+ mode: ViewTitleMode
156
+ level: ViewTitleLevel
157
+ renderTitle?: (title: string | undefined) => ReactNode
158
+ }) {
159
+ if (mode === "slot") return <div className="min-w-0 flex-1">{renderTitle?.(title)}</div>
160
+ if (mode === "hide" || !title) return null
161
+ return createElement(
162
+ `h${level}`,
163
+ { className: "min-w-0 flex-1 truncate text-lg font-semibold tracking-tight" },
164
+ title,
165
+ )
166
+ }
167
+
168
+ /**
169
+ * The title, and under it the stamp of data the view re-loaded itself. The
170
+ * stamp is a second line of the title column, never an item beside the
171
+ * controls: the title keeps the row's width (it is the user's orientation),
172
+ * and the stamp truncates only against its own column. Its words carry its
173
+ * meaning, so it needs no tooltip that touch and keyboard users never reach.
174
+ * Without a stamp the title renders as before, straight in the row.
175
+ */
176
+ function ToolbarHeading({
177
+ title,
178
+ mode,
179
+ level,
180
+ renderTitle,
181
+ stamp,
182
+ }: {
183
+ title: string | undefined
184
+ mode: ViewTitleMode
185
+ level: ViewTitleLevel
186
+ renderTitle?: (title: string | undefined) => ReactNode
187
+ stamp: string | undefined
188
+ }) {
189
+ const heading = <ToolbarTitle title={title} mode={mode} level={level} renderTitle={renderTitle} />
190
+ if (!stamp) return heading
191
+ return (
192
+ <div data-slot="view-heading" className="min-w-0 flex-1">
193
+ {heading}
194
+ <p data-slot="view-data-as-of" className="text-muted-foreground truncate text-sm">
195
+ {stamp}
196
+ </p>
197
+ </div>
198
+ )
199
+ }
200
+
201
+ /** Which toolbar controls render — pure, so the gates are unit-testable. */
202
+ export function toolbarControls(input: {
203
+ viewData: Pick<ViewData, "_refreshParams" | "layout" | "title">
204
+ builderAvailable: boolean
205
+ canFullscreen: boolean
206
+ titleMode: ViewTitleMode
207
+ }): { title: boolean; refresh: boolean; edit: boolean; fullscreen: boolean; any: boolean } {
208
+ const refreshable = Boolean(input.viewData._refreshParams && input.viewData.layout)
209
+ const edit = input.builderAvailable && Boolean(input.viewData._refreshParams)
210
+ const title =
211
+ input.titleMode === "slot" || (input.titleMode === "show" && Boolean(input.viewData.title))
212
+ return {
213
+ title,
214
+ refresh: refreshable,
215
+ edit,
216
+ fullscreen: input.canFullscreen,
217
+ any: title || refreshable || edit || input.canFullscreen,
218
+ }
219
+ }
220
+
221
+ /**
222
+ * The CI §8 chrome button (Icon-Button, Chrome): surface, `--cd-linie` border,
223
+ * shadow-1, blue on hover. The `outline` variant brings the surface, the
224
+ * border and the blue hover (it is also CI §8's secondary button, which has no
225
+ * shadow), so the chrome adds `shadow-1` on top instead of changing it.
226
+ */
227
+ const CHROME_BUTTON = { variant: "outline", className: "shadow-1" } as const
228
+
229
+ function RefreshButton({
230
+ isRefreshing,
231
+ labels,
232
+ onRefresh,
233
+ }: {
234
+ isRefreshing: boolean
235
+ labels: Required<McpAppViewLabels>
236
+ onRefresh: () => Promise<void>
237
+ }) {
238
+ const label = isRefreshing ? labels.refreshing : labels.refresh
239
+ return (
240
+ <Button
241
+ type="button"
242
+ {...CHROME_BUTTON}
243
+ size="icon-sm"
244
+ aria-label={label}
245
+ title={label}
246
+ disabled={isRefreshing}
247
+ onClick={() => {
248
+ void onRefresh()
249
+ }}
250
+ >
251
+ <RefreshCw
252
+ aria-hidden="true"
253
+ className={isRefreshing ? "motion-safe:animate-spin" : undefined}
254
+ />
255
+ </Button>
256
+ )
257
+ }
258
+
259
+ function FullscreenButton({
260
+ isFullscreen,
261
+ labels,
262
+ onToggle,
263
+ }: {
264
+ isFullscreen: boolean
265
+ labels: Required<McpAppViewLabels>
266
+ onToggle: () => Promise<void>
267
+ }) {
268
+ const label = isFullscreen ? labels.exitFullscreen : labels.enterFullscreen
269
+ const Icon = isFullscreen ? Minimize2 : Maximize2
270
+ return (
271
+ <Button
272
+ type="button"
273
+ {...CHROME_BUTTON}
274
+ size="icon-sm"
275
+ aria-label={label}
276
+ title={label}
277
+ onClick={() => {
278
+ void onToggle()
279
+ }}
280
+ >
281
+ <Icon aria-hidden="true" />
282
+ </Button>
283
+ )
284
+ }
285
+
286
+ /**
287
+ * The view toolbar: the title (per {@link ViewTitleMode}, so a widget that
288
+ * renders its own heading is not titled twice), then Edit, Refresh and
289
+ * Fullscreen. Refresh and Fullscreen are 32 px icon buttons named by
290
+ * `aria-label`; Fullscreen renders only when the host offers it. All three are
291
+ * chrome buttons in the CI pattern (CI §8: surface, border, shadow-1, blue
292
+ * hover; {@link CHROME_BUTTON}). A 16 px inset keeps the bar off the frame
293
+ * edge, also in fullscreen. The controls never shrink; the stamp of re-loaded
294
+ * data sits under the title ({@link ToolbarHeading}), so neither the title
295
+ * nor the stamp competes with the controls for width.
296
+ */
119
297
  export function ViewToolbar({
120
298
  hidden,
121
299
  viewData,
122
300
  builderAvailable,
123
301
  isFullscreen,
302
+ canFullscreen,
124
303
  isRefreshing,
125
304
  labels,
305
+ titleMode,
306
+ titleLevel,
307
+ renderTitle,
126
308
  onRefresh,
127
309
  onEnterBuildMode,
128
310
  onToggleFullscreen,
311
+ dataAsOf,
129
312
  }: ViewToolbarProps) {
130
- if (hidden) return null
313
+ const show = toolbarControls({ viewData, builderAvailable, canFullscreen, titleMode })
314
+ if (hidden || (!show.any && !dataAsOf)) return null
131
315
  return (
132
- <div className="mb-4 flex items-center justify-between">
133
- {viewData.title && <h2 className="text-xl font-bold">{viewData.title}</h2>}
134
- <div className="ml-auto flex items-center gap-2">
135
- {viewData._refreshParams && viewData.layout && (
136
- <button
137
- onClick={() => {
138
- void onRefresh()
139
- }}
140
- disabled={isRefreshing}
141
- className="hover:bg-accent hover:text-accent-foreground inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm font-medium transition-colors disabled:opacity-50"
142
- >
143
- <RefreshCw
144
- aria-hidden="true"
145
- className={`h-4 w-4 ${isRefreshing ? "animate-spin" : ""}`}
316
+ <LucideProvider strokeWidth={DENSE_ICON_STROKE}>
317
+ <div data-slot="view-toolbar" className="flex min-h-12 items-center gap-2 px-4 py-2">
318
+ <ToolbarHeading
319
+ title={viewData.title}
320
+ mode={titleMode}
321
+ level={titleLevel}
322
+ renderTitle={renderTitle}
323
+ stamp={dataAsOf ? labels.dataAsOf(dataAsOf) : undefined}
324
+ />
325
+ <div className="ml-auto flex shrink-0 items-center gap-1">
326
+ {show.edit && (
327
+ <Button
328
+ type="button"
329
+ {...CHROME_BUTTON}
330
+ size="sm"
331
+ data-slot="view-edit"
332
+ onClick={onEnterBuildMode}
333
+ >
334
+ <Pencil aria-hidden="true" />
335
+ {labels.build}
336
+ </Button>
337
+ )}
338
+ {show.refresh && (
339
+ <RefreshButton isRefreshing={isRefreshing} labels={labels} onRefresh={onRefresh} />
340
+ )}
341
+ {show.fullscreen && (
342
+ <FullscreenButton
343
+ isFullscreen={isFullscreen}
344
+ labels={labels}
345
+ onToggle={onToggleFullscreen}
146
346
  />
147
- {isRefreshing ? labels.refreshing : labels.refresh}
148
- </button>
149
- )}
150
- {builderAvailable && viewData._refreshParams && (
151
- <button
152
- onClick={onEnterBuildMode}
153
- className="hover:bg-accent hover:text-accent-foreground inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm font-medium transition-colors"
154
- >
155
- <Pencil className="h-4 w-4" />
156
- {labels.build}
157
- </button>
158
- )}
159
- <button
160
- onClick={() => {
161
- void onToggleFullscreen()
162
- }}
163
- className="hover:bg-accent hover:text-accent-foreground inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm font-medium transition-colors"
164
- >
165
- {isFullscreen ? (
166
- <Minimize2 aria-hidden="true" className="h-4 w-4" />
167
- ) : (
168
- <Maximize2 aria-hidden="true" className="h-4 w-4" />
169
347
  )}
170
- {isFullscreen ? labels.exitFullscreen : labels.enterFullscreen}
171
- </button>
348
+ </div>
172
349
  </div>
173
- </div>
350
+ </LucideProvider>
174
351
  )
175
352
  }
176
353
 
177
354
  // -------------------------------------------------------------------------- //
178
- // Pipeline error list
355
+ // Failed data steps
179
356
  // -------------------------------------------------------------------------- //
180
357
 
358
+ /** Resolves a human title for a pipeline step (`{ id, step }` from the view's steps). */
359
+ export type DescribeStep = (step: { id: string; step?: string }) => string | undefined
360
+
361
+ /**
362
+ * The failed data steps as ONE inline alert: what happened (the title), which
363
+ * data is affected (each step by its human title, else its step id), what you
364
+ * can do (retry, or ask in the chat when the view cannot refresh). The raw
365
+ * step reasons stay under "Details", out of the user copy.
366
+ */
181
367
  export function StepErrors({
182
368
  hidden,
183
369
  errors,
370
+ steps,
371
+ labels,
372
+ describeStep,
373
+ onRetry,
184
374
  }: {
185
- /** Build mode surfaces its own diagnostics; the shell list hides. */
375
+ /** Build mode surfaces its own diagnostics; the shell alert hides. */
186
376
  hidden: boolean
187
377
  errors: ViewData["context"]["errors"]
378
+ /** The view's step refs (`_refreshParams.steps`), to name a failed step by its definition. */
379
+ steps?: PipelineStepRef[]
380
+ labels: Required<McpAppViewLabels>
381
+ describeStep?: DescribeStep
382
+ /** Re-runs the view (a refresh); omitted when the view cannot be refreshed. */
383
+ onRetry?: () => void
188
384
  }) {
189
385
  if (hidden || errors.length === 0) return null
386
+ const failed = errors.map((err) => {
387
+ const ref = { id: err.stepId, step: steps?.find((s) => s.id === err.stepId)?.step }
388
+ return { ref, human: describeStep?.(ref), reason: err.reason }
389
+ })
190
390
  return (
191
- <div className="mb-4 flex flex-col gap-2">
192
- {errors.map((err) => (
193
- <div key={err.stepId} className="bg-destructive/10 text-destructive rounded-lg p-3 text-sm">
194
- <strong>{err.stepId}:</strong> {err.reason}
195
- </div>
196
- ))}
391
+ <div className="px-4 pb-4">
392
+ <ErrorState
393
+ variant="inline"
394
+ title={labels.stepErrorsTitle}
395
+ retryLabel={labels.retry}
396
+ onRetry={onRetry}
397
+ detailsLabel={labels.errorDetails}
398
+ details={failed.map(({ ref, reason }) => `${ref.step ?? ref.id}: ${reason}`).join("\n")}
399
+ description={
400
+ <>
401
+ <ul className="flex flex-col gap-1">
402
+ {failed.map(({ ref, human }) => (
403
+ <li key={ref.id} className="break-words">
404
+ {human ? (
405
+ <span className="font-medium">{human}</span>
406
+ ) : (
407
+ <span className="text-small font-mono">{ref.step ?? ref.id}</span>
408
+ )}
409
+ </li>
410
+ ))}
411
+ </ul>
412
+ <p className="mt-1">{onRetry ? labels.stepErrorsHint : labels.errorHintNoRetry}</p>
413
+ </>
414
+ }
415
+ />
197
416
  </div>
198
417
  )
199
418
  }
@@ -208,9 +427,17 @@ export interface ViewBodyProps {
208
427
  widgets: Record<string, WidgetComponent>
209
428
  callTool: (name: string, args: object) => Promise<unknown>
210
429
  onExit: LayoutBuilderProps["onExit"]
430
+ builderLabels?: LayoutBuilderLabels
211
431
  }
212
432
 
213
- export function ViewBody({ buildMode, viewData, widgets, callTool, onExit }: ViewBodyProps) {
433
+ export function ViewBody({
434
+ buildMode,
435
+ viewData,
436
+ widgets,
437
+ callTool,
438
+ onExit,
439
+ builderLabels,
440
+ }: ViewBodyProps) {
214
441
  if (buildMode) {
215
442
  return (
216
443
  <LayoutBuilder
@@ -220,6 +447,7 @@ export function ViewBody({ buildMode, viewData, widgets, callTool, onExit }: Vie
220
447
  initialSteps={viewData._refreshParams?.steps}
221
448
  widgets={widgets}
222
449
  callTool={callTool}
450
+ labels={builderLabels}
223
451
  onExit={onExit}
224
452
  />
225
453
  )
@@ -0,0 +1,140 @@
1
+ /**
2
+ * The pre-payload states of `McpAppView`, one per lifecycle phase
3
+ * (`view-lifecycle.ts`): waiting (skeleton + status line + slow hint),
4
+ * cancelled, and error (icon, title, what to do, raw reason under "Details",
5
+ * retry when the originating tool is known). A `blocked` error is a notice in
6
+ * the info tone, not a failure: the tool ran, but the chat did not pass its
7
+ * result to the view. Hook-free and SSR-renderable, so each phase is pinned
8
+ * by a test.
9
+ */
10
+ import { ErrorState, type ErrorStateTone } from "../components/ErrorState.js"
11
+ import { Skeleton } from "../primitives/skeleton.js"
12
+ import type { McpAppViewLabels } from "./mcp-app-view-chrome.js"
13
+ import type { ViewErrorKind, ViewLifecycle } from "./view-lifecycle.js"
14
+
15
+ export type { ViewErrorKind } from "./view-lifecycle.js"
16
+
17
+ /**
18
+ * Human text for a raw error reason (e.g. map "503" to "the engine is down").
19
+ * `info.kind` tells a failure (`"failed"`) from a result the chat did not pass
20
+ * to the view (`"blocked"`, no reason); return `undefined` to keep the
21
+ * toolkit's words for that state.
22
+ */
23
+ export type DescribeViewError = (
24
+ reason: string | undefined,
25
+ info?: { kind: ViewErrorKind },
26
+ ) => { title?: string; description?: string } | undefined
27
+
28
+ export function ViewPlaceholder({
29
+ lifecycle,
30
+ labels,
31
+ onRetry,
32
+ describeError,
33
+ }: {
34
+ /** The current phase; `result` renders nothing (the view takes over). */
35
+ lifecycle: ViewLifecycle
36
+ labels: Required<McpAppViewLabels>
37
+ onRetry: () => void
38
+ describeError?: DescribeViewError
39
+ }) {
40
+ switch (lifecycle.phase) {
41
+ case "result":
42
+ return null
43
+ case "cancelled":
44
+ return (
45
+ <div className="px-4 py-4">
46
+ <p role="status" className="text-muted-foreground text-sm">
47
+ {labels.cancelled}
48
+ </p>
49
+ </div>
50
+ )
51
+ case "error":
52
+ return (
53
+ <ViewErrorState
54
+ kind={lifecycle.kind}
55
+ reason={lifecycle.reason}
56
+ canRetry={lifecycle.canRetry}
57
+ labels={labels}
58
+ onRetry={onRetry}
59
+ describeError={describeError}
60
+ />
61
+ )
62
+ case "waiting":
63
+ return <ViewWaiting slow={lifecycle.slow} labels={labels} />
64
+ }
65
+ }
66
+
67
+ function ViewWaiting({ slow, labels }: { slow: boolean; labels: Required<McpAppViewLabels> }) {
68
+ // No aria-busy on an ancestor of the live region: assistive technology may
69
+ // hold back changes inside a busy subtree, and the slow hint is one.
70
+ return (
71
+ <div className="flex flex-col gap-4 px-4 py-4">
72
+ <Skeleton aria-hidden="true" className="h-8 w-48" />
73
+ <Skeleton aria-hidden="true" className="h-32 w-full" />
74
+ {/* One live region: the status line, then the slow hint when it appears. */}
75
+ <div role="status" className="text-muted-foreground flex flex-col gap-1 text-sm">
76
+ <p>{labels.loading}</p>
77
+ {slow && <p data-slot="slow-hint">{labels.slowHint}</p>}
78
+ </div>
79
+ </div>
80
+ )
81
+ }
82
+
83
+ /** The words and tone of an error phase; the hint never promises a retry the state does not offer. */
84
+ function errorStateCopy(
85
+ kind: ViewErrorKind,
86
+ canRetry: boolean,
87
+ labels: Required<McpAppViewLabels>,
88
+ ): { tone: ErrorStateTone; role: "alert" | "status"; title: string; hint: string; retry: string } {
89
+ if (kind === "blocked") {
90
+ // Nothing failed: a quiet notice that says what happened and what to do.
91
+ return {
92
+ tone: "info",
93
+ role: "status",
94
+ title: labels.blockedTitle,
95
+ hint: canRetry ? labels.blockedHint : labels.blockedHintNoRetry,
96
+ retry: labels.blockedRetry,
97
+ }
98
+ }
99
+ return {
100
+ tone: "danger",
101
+ role: "alert",
102
+ title: labels.errorTitle,
103
+ hint: canRetry ? labels.errorHint : labels.errorHintNoRetry,
104
+ retry: labels.retry,
105
+ }
106
+ }
107
+
108
+ function ViewErrorState({
109
+ kind = "failed",
110
+ reason,
111
+ canRetry,
112
+ labels,
113
+ onRetry,
114
+ describeError,
115
+ }: {
116
+ /** Absent for a failure (see `ViewLifecycle`). */
117
+ kind: ViewErrorKind | undefined
118
+ reason: string | undefined
119
+ canRetry: boolean
120
+ labels: Required<McpAppViewLabels>
121
+ onRetry: () => void
122
+ describeError?: DescribeViewError
123
+ }) {
124
+ const human = describeError?.(reason, { kind })
125
+ const copy = errorStateCopy(kind, canRetry, labels)
126
+ return (
127
+ <div className="px-4 py-4">
128
+ <ErrorState
129
+ tone={copy.tone}
130
+ role={copy.role}
131
+ title={human?.title ?? copy.title}
132
+ description={human?.description ?? copy.hint}
133
+ details={reason}
134
+ detailsLabel={labels.errorDetails}
135
+ retryLabel={copy.retry}
136
+ onRetry={canRetry ? onRetry : undefined}
137
+ />
138
+ </div>
139
+ )
140
+ }