@miragon/mcp-toolkit-ui 2.5.0 → 2.7.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 (348) hide show
  1. package/README.md +8 -2
  2. package/dist/app/builder/BuilderBanners.d.ts +7 -2
  3. package/dist/app/builder/BuilderBanners.d.ts.map +1 -1
  4. package/dist/app/builder/BuilderBanners.js +8 -3
  5. package/dist/app/builder/BuilderBanners.js.map +1 -1
  6. package/dist/app/builder/CanvasRow.d.ts.map +1 -1
  7. package/dist/app/builder/CanvasRow.js +12 -14
  8. package/dist/app/builder/CanvasRow.js.map +1 -1
  9. package/dist/app/builder/CatalogueSheet.d.ts.map +1 -1
  10. package/dist/app/builder/CatalogueSheet.js +6 -5
  11. package/dist/app/builder/CatalogueSheet.js.map +1 -1
  12. package/dist/app/builder/EmptyHint.d.ts +4 -0
  13. package/dist/app/builder/EmptyHint.d.ts.map +1 -1
  14. package/dist/app/builder/EmptyHint.js +6 -1
  15. package/dist/app/builder/EmptyHint.js.map +1 -1
  16. package/dist/app/builder/LayoutSurface.js +3 -3
  17. package/dist/app/builder/LayoutSurface.js.map +1 -1
  18. package/dist/app/builder/PipelineStrip.d.ts +3 -1
  19. package/dist/app/builder/PipelineStrip.d.ts.map +1 -1
  20. package/dist/app/builder/PipelineStrip.js +11 -6
  21. package/dist/app/builder/PipelineStrip.js.map +1 -1
  22. package/dist/app/builder/SaveDialog.d.ts.map +1 -1
  23. package/dist/app/builder/SaveDialog.js +2 -2
  24. package/dist/app/builder/SaveDialog.js.map +1 -1
  25. package/dist/app/builder/Toolbar.d.ts +3 -1
  26. package/dist/app/builder/Toolbar.d.ts.map +1 -1
  27. package/dist/app/builder/Toolbar.js +9 -3
  28. package/dist/app/builder/Toolbar.js.map +1 -1
  29. package/dist/app/builder/Workspace.js +2 -2
  30. package/dist/app/builder/Workspace.js.map +1 -1
  31. package/dist/app/builder/builder-model.d.ts +3 -2
  32. package/dist/app/builder/builder-model.d.ts.map +1 -1
  33. package/dist/app/builder/builder-model.js +5 -4
  34. package/dist/app/builder/builder-model.js.map +1 -1
  35. package/dist/app/builder/labels.d.ts +13 -62
  36. package/dist/app/builder/labels.d.ts.map +1 -1
  37. package/dist/app/builder/labels.js +9 -58
  38. package/dist/app/builder/labels.js.map +1 -1
  39. package/dist/app/builder/status-chip.d.ts.map +1 -1
  40. package/dist/app/builder/status-chip.js +3 -2
  41. package/dist/app/builder/status-chip.js.map +1 -1
  42. package/dist/app/builder/use-builder-catalogue.d.ts +1 -0
  43. package/dist/app/builder/use-builder-catalogue.d.ts.map +1 -1
  44. package/dist/app/builder/use-builder-catalogue.js +6 -0
  45. package/dist/app/builder/use-builder-catalogue.js.map +1 -1
  46. package/dist/app/builder/use-save-dashboard.d.ts +3 -1
  47. package/dist/app/builder/use-save-dashboard.d.ts.map +1 -1
  48. package/dist/app/builder/use-save-dashboard.js +3 -1
  49. package/dist/app/builder/use-save-dashboard.js.map +1 -1
  50. package/dist/app/index.d.ts +1 -1
  51. package/dist/app/index.d.ts.map +1 -1
  52. package/dist/app/index.js.map +1 -1
  53. package/dist/app/layout-builder.d.ts +7 -2
  54. package/dist/app/layout-builder.d.ts.map +1 -1
  55. package/dist/app/layout-builder.js +17 -5
  56. package/dist/app/layout-builder.js.map +1 -1
  57. package/dist/app/mcp-app-view-chrome.d.ts +94 -20
  58. package/dist/app/mcp-app-view-chrome.d.ts.map +1 -1
  59. package/dist/app/mcp-app-view-chrome.js +109 -40
  60. package/dist/app/mcp-app-view-chrome.js.map +1 -1
  61. package/dist/app/mcp-app-view-states.d.ts +15 -0
  62. package/dist/app/mcp-app-view-states.d.ts.map +1 -0
  63. package/dist/app/mcp-app-view-states.js +34 -0
  64. package/dist/app/mcp-app-view-states.js.map +1 -0
  65. package/dist/app/mcp-app-view.d.ts +40 -5
  66. package/dist/app/mcp-app-view.d.ts.map +1 -1
  67. package/dist/app/mcp-app-view.js +174 -128
  68. package/dist/app/mcp-app-view.js.map +1 -1
  69. package/dist/app/mcp-toolkit-app.d.ts +2 -1
  70. package/dist/app/mcp-toolkit-app.d.ts.map +1 -1
  71. package/dist/app/mcp-toolkit-app.js +25 -3
  72. package/dist/app/mcp-toolkit-app.js.map +1 -1
  73. package/dist/app/use-view-lifecycle.d.ts +38 -0
  74. package/dist/app/use-view-lifecycle.d.ts.map +1 -0
  75. package/dist/app/use-view-lifecycle.js +79 -0
  76. package/dist/app/use-view-lifecycle.js.map +1 -0
  77. package/dist/app/view-lifecycle.d.ts +109 -0
  78. package/dist/app/view-lifecycle.d.ts.map +1 -0
  79. package/dist/app/view-lifecycle.js +120 -0
  80. package/dist/app/view-lifecycle.js.map +1 -0
  81. package/dist/app/widget-props-sheet.d.ts +3 -9
  82. package/dist/app/widget-props-sheet.d.ts.map +1 -1
  83. package/dist/app/widget-props-sheet.js +6 -13
  84. package/dist/app/widget-props-sheet.js.map +1 -1
  85. package/dist/app/widget-renderer.d.ts +41 -1
  86. package/dist/app/widget-renderer.d.ts.map +1 -1
  87. package/dist/app/widget-renderer.js +82 -31
  88. package/dist/app/widget-renderer.js.map +1 -1
  89. package/dist/components/DrillButton.d.ts.map +1 -1
  90. package/dist/components/DrillButton.js +2 -1
  91. package/dist/components/DrillButton.js.map +1 -1
  92. package/dist/components/EmptyState.d.ts +33 -0
  93. package/dist/components/EmptyState.d.ts.map +1 -0
  94. package/dist/components/EmptyState.js +17 -0
  95. package/dist/components/EmptyState.js.map +1 -0
  96. package/dist/components/ErrorState.d.ts +50 -0
  97. package/dist/components/ErrorState.d.ts.map +1 -0
  98. package/dist/components/ErrorState.js +60 -0
  99. package/dist/components/ErrorState.js.map +1 -0
  100. package/dist/components/FilterBar.d.ts +1 -1
  101. package/dist/components/FilterBar.d.ts.map +1 -1
  102. package/dist/components/FilterBar.js +7 -4
  103. package/dist/components/FilterBar.js.map +1 -1
  104. package/dist/components/GroupCard.d.ts.map +1 -1
  105. package/dist/components/GroupCard.js +2 -1
  106. package/dist/components/GroupCard.js.map +1 -1
  107. package/dist/components/KpiGrid.d.ts +10 -3
  108. package/dist/components/KpiGrid.d.ts.map +1 -1
  109. package/dist/components/KpiGrid.js +31 -9
  110. package/dist/components/KpiGrid.js.map +1 -1
  111. package/dist/components/ListFooter.d.ts +3 -2
  112. package/dist/components/ListFooter.d.ts.map +1 -1
  113. package/dist/components/ListFooter.js +14 -4
  114. package/dist/components/ListFooter.js.map +1 -1
  115. package/dist/components/Pills.d.ts +10 -6
  116. package/dist/components/Pills.d.ts.map +1 -1
  117. package/dist/components/Pills.js +12 -6
  118. package/dist/components/Pills.js.map +1 -1
  119. package/dist/components/Toast.d.ts +37 -0
  120. package/dist/components/Toast.d.ts.map +1 -0
  121. package/dist/components/Toast.js +91 -0
  122. package/dist/components/Toast.js.map +1 -0
  123. package/dist/components/WidgetHeader.js +1 -1
  124. package/dist/hooks/use-tool-result-recovery.d.ts +23 -1
  125. package/dist/hooks/use-tool-result-recovery.d.ts.map +1 -1
  126. package/dist/hooks/use-tool-result-recovery.js +21 -7
  127. package/dist/hooks/use-tool-result-recovery.js.map +1 -1
  128. package/dist/hooks/use-view-data.d.ts +6 -0
  129. package/dist/hooks/use-view-data.d.ts.map +1 -1
  130. package/dist/hooks/use-view-data.js +6 -1
  131. package/dist/hooks/use-view-data.js.map +1 -1
  132. package/dist/index.d.ts +12 -5
  133. package/dist/index.d.ts.map +1 -1
  134. package/dist/index.js +14 -4
  135. package/dist/index.js.map +1 -1
  136. package/dist/lib/focus-ring.d.ts +18 -0
  137. package/dist/lib/focus-ring.d.ts.map +1 -0
  138. package/dist/lib/focus-ring.js +18 -0
  139. package/dist/lib/focus-ring.js.map +1 -0
  140. package/dist/lib/icon-stroke.d.ts +12 -0
  141. package/dist/lib/icon-stroke.d.ts.map +1 -0
  142. package/dist/lib/icon-stroke.js +12 -0
  143. package/dist/lib/icon-stroke.js.map +1 -0
  144. package/dist/lib/toast-model.d.ts +46 -0
  145. package/dist/lib/toast-model.d.ts.map +1 -0
  146. package/dist/lib/toast-model.js +36 -0
  147. package/dist/lib/toast-model.js.map +1 -0
  148. package/dist/lib/tone-utils.d.ts +43 -9
  149. package/dist/lib/tone-utils.d.ts.map +1 -1
  150. package/dist/lib/tone-utils.js +36 -7
  151. package/dist/lib/tone-utils.js.map +1 -1
  152. package/dist/locale/index.d.ts +3 -1
  153. package/dist/locale/index.d.ts.map +1 -1
  154. package/dist/locale/index.js +2 -1
  155. package/dist/locale/index.js.map +1 -1
  156. package/dist/locale/locale-provider.d.ts +42 -1
  157. package/dist/locale/locale-provider.d.ts.map +1 -1
  158. package/dist/locale/locale-provider.js +44 -3
  159. package/dist/locale/locale-provider.js.map +1 -1
  160. package/dist/locale/message-types.d.ts +223 -0
  161. package/dist/locale/message-types.d.ts.map +1 -0
  162. package/dist/locale/message-types.js +7 -0
  163. package/dist/locale/message-types.js.map +1 -0
  164. package/dist/locale/messages-de.d.ts +9 -0
  165. package/dist/locale/messages-de.d.ts.map +1 -0
  166. package/dist/locale/messages-de.js +151 -0
  167. package/dist/locale/messages-de.js.map +1 -0
  168. package/dist/locale/messages-en.d.ts +8 -0
  169. package/dist/locale/messages-en.d.ts.map +1 -0
  170. package/dist/locale/messages-en.js +150 -0
  171. package/dist/locale/messages-en.js.map +1 -0
  172. package/dist/locale/toolkit-messages.d.ts +39 -0
  173. package/dist/locale/toolkit-messages.d.ts.map +1 -0
  174. package/dist/locale/toolkit-messages.js +50 -0
  175. package/dist/locale/toolkit-messages.js.map +1 -0
  176. package/dist/primitives/alert.d.ts.map +1 -1
  177. package/dist/primitives/alert.js +4 -2
  178. package/dist/primitives/alert.js.map +1 -1
  179. package/dist/primitives/badge.d.ts +1 -1
  180. package/dist/primitives/badge.d.ts.map +1 -1
  181. package/dist/primitives/badge.js +14 -6
  182. package/dist/primitives/badge.js.map +1 -1
  183. package/dist/primitives/button.d.ts.map +1 -1
  184. package/dist/primitives/button.js +12 -8
  185. package/dist/primitives/button.js.map +1 -1
  186. package/dist/primitives/card.js +4 -4
  187. package/dist/primitives/card.js.map +1 -1
  188. package/dist/primitives/dialog.d.ts +7 -3
  189. package/dist/primitives/dialog.d.ts.map +1 -1
  190. package/dist/primitives/dialog.js +14 -7
  191. package/dist/primitives/dialog.js.map +1 -1
  192. package/dist/primitives/input.d.ts.map +1 -1
  193. package/dist/primitives/input.js +4 -1
  194. package/dist/primitives/input.js.map +1 -1
  195. package/dist/primitives/scroll-area.js +1 -1
  196. package/dist/primitives/scroll-area.js.map +1 -1
  197. package/dist/primitives/select.d.ts.map +1 -1
  198. package/dist/primitives/select.js +5 -2
  199. package/dist/primitives/select.js.map +1 -1
  200. package/dist/primitives/sheet.d.ts +3 -1
  201. package/dist/primitives/sheet.d.ts.map +1 -1
  202. package/dist/primitives/sheet.js +12 -6
  203. package/dist/primitives/sheet.js.map +1 -1
  204. package/dist/primitives/skeleton.js +1 -1
  205. package/dist/primitives/skeleton.js.map +1 -1
  206. package/dist/primitives/switch.d.ts.map +1 -1
  207. package/dist/primitives/switch.js +2 -1
  208. package/dist/primitives/switch.js.map +1 -1
  209. package/dist/primitives/tabs.d.ts.map +1 -1
  210. package/dist/primitives/tabs.js +5 -3
  211. package/dist/primitives/tabs.js.map +1 -1
  212. package/dist/render-cases.d.ts.map +1 -1
  213. package/dist/render-cases.js +20 -2
  214. package/dist/render-cases.js.map +1 -1
  215. package/dist/render-scan.d.ts +25 -0
  216. package/dist/render-scan.d.ts.map +1 -0
  217. package/dist/render-scan.js +66 -0
  218. package/dist/render-scan.js.map +1 -0
  219. package/dist/theme/cd-tokens.d.ts +66 -0
  220. package/dist/theme/cd-tokens.d.ts.map +1 -0
  221. package/dist/theme/cd-tokens.js +64 -0
  222. package/dist/theme/cd-tokens.js.map +1 -0
  223. package/dist/theme/create-theme.d.ts +196 -39
  224. package/dist/theme/create-theme.d.ts.map +1 -1
  225. package/dist/theme/create-theme.js +189 -13
  226. package/dist/theme/create-theme.js.map +1 -1
  227. package/dist/theme/fonts.d.ts +38 -0
  228. package/dist/theme/fonts.d.ts.map +1 -0
  229. package/dist/theme/fonts.js +56 -0
  230. package/dist/theme/fonts.js.map +1 -0
  231. package/dist/theme/host-fonts.d.ts +39 -0
  232. package/dist/theme/host-fonts.d.ts.map +1 -0
  233. package/dist/theme/host-fonts.js +44 -0
  234. package/dist/theme/host-fonts.js.map +1 -0
  235. package/dist/theme/index.d.ts +8 -1
  236. package/dist/theme/index.d.ts.map +1 -1
  237. package/dist/theme/index.js +5 -1
  238. package/dist/theme/index.js.map +1 -1
  239. package/dist/theme/portal-container.d.ts +16 -0
  240. package/dist/theme/portal-container.d.ts.map +1 -0
  241. package/dist/theme/portal-container.js +19 -0
  242. package/dist/theme/portal-container.js.map +1 -0
  243. package/dist/theme/presets.d.ts +24 -9
  244. package/dist/theme/presets.d.ts.map +1 -1
  245. package/dist/theme/presets.js +44 -11
  246. package/dist/theme/presets.js.map +1 -1
  247. package/dist/theme/theme-provider.d.ts +27 -9
  248. package/dist/theme/theme-provider.d.ts.map +1 -1
  249. package/dist/theme/theme-provider.js +42 -20
  250. package/dist/theme/theme-provider.js.map +1 -1
  251. package/package.json +13 -8
  252. package/src/app/builder/BuilderBanners.tsx +26 -13
  253. package/src/app/builder/CanvasRow.tsx +22 -21
  254. package/src/app/builder/CatalogueSheet.tsx +16 -14
  255. package/src/app/builder/EmptyHint.tsx +16 -11
  256. package/src/app/builder/LayoutSurface.tsx +7 -7
  257. package/src/app/builder/PipelineStrip.tsx +26 -17
  258. package/src/app/builder/SaveDialog.tsx +11 -12
  259. package/src/app/builder/Toolbar.tsx +19 -7
  260. package/src/app/builder/Workspace.tsx +5 -3
  261. package/src/app/builder/builder-feedback.test.tsx +75 -0
  262. package/src/app/builder/builder-model.test.ts +5 -0
  263. package/src/app/builder/builder-model.ts +5 -4
  264. package/src/app/builder/labels.ts +15 -121
  265. package/src/app/builder/status-chip.tsx +8 -4
  266. package/src/app/builder/use-builder-catalogue.ts +6 -0
  267. package/src/app/builder/use-save-dashboard.ts +5 -0
  268. package/src/app/index.ts +8 -1
  269. package/src/app/layout-builder.tsx +23 -3
  270. package/src/app/mcp-app-view-chrome.test.tsx +347 -14
  271. package/src/app/mcp-app-view-chrome.tsx +278 -100
  272. package/src/app/mcp-app-view-states.tsx +100 -0
  273. package/src/app/mcp-app-view.test.tsx +258 -0
  274. package/src/app/mcp-app-view.tsx +292 -185
  275. package/src/app/mcp-toolkit-app.tsx +31 -7
  276. package/src/app/use-view-lifecycle.ts +116 -0
  277. package/src/app/view-lifecycle.test.ts +227 -0
  278. package/src/app/view-lifecycle.ts +174 -0
  279. package/src/app/widget-props-sheet.tsx +13 -26
  280. package/src/app/widget-renderer.render.test.tsx +131 -0
  281. package/src/app/widget-renderer.tsx +136 -38
  282. package/src/components/DrillButton.tsx +2 -1
  283. package/src/components/EmptyState.tsx +81 -0
  284. package/src/components/ErrorState.tsx +182 -0
  285. package/src/components/FilterBar.tsx +9 -6
  286. package/src/components/GroupCard.tsx +3 -2
  287. package/src/components/KpiGrid.tsx +75 -44
  288. package/src/components/ListFooter.tsx +17 -6
  289. package/src/components/Pills.tsx +20 -11
  290. package/src/components/Toast.tsx +188 -0
  291. package/src/components/WidgetHeader.tsx +1 -1
  292. package/src/components/feedback-states.test.tsx +225 -0
  293. package/src/components/tone-figures.test.tsx +54 -0
  294. package/src/fonts.css +19 -0
  295. package/src/globals.css +9 -165
  296. package/src/hooks/use-tool-result-recovery.test.ts +25 -1
  297. package/src/hooks/use-tool-result-recovery.ts +34 -6
  298. package/src/hooks/use-view-data.test.tsx +60 -0
  299. package/src/hooks/use-view-data.ts +12 -1
  300. package/src/i18n-coverage.test.tsx +323 -0
  301. package/src/index.ts +57 -5
  302. package/src/lib/focus-ring.ts +20 -0
  303. package/src/lib/icon-stroke.test.ts +59 -0
  304. package/src/lib/icon-stroke.ts +13 -0
  305. package/src/lib/toast-model.test.ts +68 -0
  306. package/src/lib/toast-model.ts +63 -0
  307. package/src/lib/tone-utils.test.ts +47 -14
  308. package/src/lib/tone-utils.ts +61 -13
  309. package/src/locale/index.ts +13 -1
  310. package/src/locale/locale-provider.test.tsx +87 -0
  311. package/src/locale/locale-provider.tsx +90 -3
  312. package/src/locale/message-types.ts +221 -0
  313. package/src/locale/messages-de.ts +159 -0
  314. package/src/locale/messages-en.ts +157 -0
  315. package/src/locale/toolkit-messages.test.ts +216 -0
  316. package/src/locale/toolkit-messages.ts +82 -0
  317. package/src/no-hardcoded-copy.test.ts +432 -0
  318. package/src/primitives/alert.tsx +4 -2
  319. package/src/primitives/badge.tsx +14 -7
  320. package/src/primitives/button.tsx +13 -8
  321. package/src/primitives/card.tsx +4 -4
  322. package/src/primitives/dialog.tsx +32 -7
  323. package/src/primitives/input.tsx +5 -3
  324. package/src/primitives/scroll-area.tsx +1 -1
  325. package/src/primitives/select.tsx +6 -3
  326. package/src/primitives/sheet.tsx +26 -6
  327. package/src/primitives/skeleton.tsx +1 -1
  328. package/src/primitives/switch.tsx +4 -2
  329. package/src/primitives/tabs.tsx +9 -6
  330. package/src/render-cases.tsx +45 -1
  331. package/src/render-scan.ts +75 -0
  332. package/src/theme/cd-tokens.generated.css +48 -0
  333. package/src/theme/cd-tokens.test.ts +107 -0
  334. package/src/theme/cd-tokens.ts +67 -0
  335. package/src/theme/create-theme.test.ts +243 -5
  336. package/src/theme/create-theme.ts +313 -41
  337. package/src/theme/fonts.test.ts +85 -0
  338. package/src/theme/fonts.ts +72 -0
  339. package/src/theme/host-fonts.test.ts +135 -0
  340. package/src/theme/host-fonts.ts +52 -0
  341. package/src/theme/index.ts +15 -1
  342. package/src/theme/portal-container.ts +20 -0
  343. package/src/theme/presets.ts +44 -11
  344. package/src/theme/theme-css.test.ts +568 -0
  345. package/src/theme/theme-provider.test.tsx +82 -0
  346. package/src/theme/theme-provider.tsx +60 -24
  347. package/src/theme.css +314 -0
  348. package/ui-catalog.json +344 -17
@@ -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 Done. */
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,244 @@ 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>
117
138
  }
118
139
 
140
+ /** The toolbar title per {@link ViewTitleMode}: a heading, the consumer's slot, or nothing. */
141
+ function ToolbarTitle({
142
+ title,
143
+ mode,
144
+ level,
145
+ renderTitle,
146
+ }: {
147
+ title: string | undefined
148
+ mode: ViewTitleMode
149
+ level: ViewTitleLevel
150
+ renderTitle?: (title: string | undefined) => ReactNode
151
+ }) {
152
+ if (mode === "slot") return <div className="min-w-0 flex-1">{renderTitle?.(title)}</div>
153
+ if (mode === "hide" || !title) return null
154
+ return createElement(
155
+ `h${level}`,
156
+ { className: "min-w-0 flex-1 truncate text-lg font-semibold tracking-tight" },
157
+ title,
158
+ )
159
+ }
160
+
161
+ /** Which toolbar controls render — pure, so the gates are unit-testable. */
162
+ export function toolbarControls(input: {
163
+ viewData: Pick<ViewData, "_refreshParams" | "layout" | "title">
164
+ builderAvailable: boolean
165
+ canFullscreen: boolean
166
+ titleMode: ViewTitleMode
167
+ }): { title: boolean; refresh: boolean; edit: boolean; fullscreen: boolean; any: boolean } {
168
+ const refreshable = Boolean(input.viewData._refreshParams && input.viewData.layout)
169
+ const edit = input.builderAvailable && Boolean(input.viewData._refreshParams)
170
+ const title =
171
+ input.titleMode === "slot" || (input.titleMode === "show" && Boolean(input.viewData.title))
172
+ return {
173
+ title,
174
+ refresh: refreshable,
175
+ edit,
176
+ fullscreen: input.canFullscreen,
177
+ any: title || refreshable || edit || input.canFullscreen,
178
+ }
179
+ }
180
+
181
+ /**
182
+ * The CI §8 chrome button (Icon-Button, Chrome): surface, `--cd-linie` border,
183
+ * shadow-1, blue on hover. The `outline` variant brings the surface, the
184
+ * border and the blue hover (it is also CI §8's secondary button, which has no
185
+ * shadow), so the chrome adds `shadow-1` on top instead of changing it.
186
+ */
187
+ const CHROME_BUTTON = { variant: "outline", className: "shadow-1" } as const
188
+
189
+ function RefreshButton({
190
+ isRefreshing,
191
+ labels,
192
+ onRefresh,
193
+ }: {
194
+ isRefreshing: boolean
195
+ labels: Required<McpAppViewLabels>
196
+ onRefresh: () => Promise<void>
197
+ }) {
198
+ const label = isRefreshing ? labels.refreshing : labels.refresh
199
+ return (
200
+ <Button
201
+ type="button"
202
+ {...CHROME_BUTTON}
203
+ size="icon-sm"
204
+ aria-label={label}
205
+ title={label}
206
+ disabled={isRefreshing}
207
+ onClick={() => {
208
+ void onRefresh()
209
+ }}
210
+ >
211
+ <RefreshCw
212
+ aria-hidden="true"
213
+ className={isRefreshing ? "motion-safe:animate-spin" : undefined}
214
+ />
215
+ </Button>
216
+ )
217
+ }
218
+
219
+ function FullscreenButton({
220
+ isFullscreen,
221
+ labels,
222
+ onToggle,
223
+ }: {
224
+ isFullscreen: boolean
225
+ labels: Required<McpAppViewLabels>
226
+ onToggle: () => Promise<void>
227
+ }) {
228
+ const label = isFullscreen ? labels.exitFullscreen : labels.enterFullscreen
229
+ const Icon = isFullscreen ? Minimize2 : Maximize2
230
+ return (
231
+ <Button
232
+ type="button"
233
+ {...CHROME_BUTTON}
234
+ size="icon-sm"
235
+ aria-label={label}
236
+ title={label}
237
+ onClick={() => {
238
+ void onToggle()
239
+ }}
240
+ >
241
+ <Icon aria-hidden="true" />
242
+ </Button>
243
+ )
244
+ }
245
+
246
+ /**
247
+ * The view toolbar: the title (per {@link ViewTitleMode}, so a widget that
248
+ * renders its own heading is not titled twice), then Edit, Refresh and
249
+ * Fullscreen. Refresh and Fullscreen are 32 px icon buttons named by
250
+ * `aria-label`; Fullscreen renders only when the host offers it. All three are
251
+ * chrome buttons in the CI pattern (CI §8: surface, border, shadow-1, blue
252
+ * hover; {@link CHROME_BUTTON}). A 16 px inset keeps the bar off the frame
253
+ * edge, also in fullscreen.
254
+ */
119
255
  export function ViewToolbar({
120
256
  hidden,
121
257
  viewData,
122
258
  builderAvailable,
123
259
  isFullscreen,
260
+ canFullscreen,
124
261
  isRefreshing,
125
262
  labels,
263
+ titleMode,
264
+ titleLevel,
265
+ renderTitle,
126
266
  onRefresh,
127
267
  onEnterBuildMode,
128
268
  onToggleFullscreen,
129
269
  }: ViewToolbarProps) {
130
- if (hidden) return null
270
+ const show = toolbarControls({ viewData, builderAvailable, canFullscreen, titleMode })
271
+ if (hidden || !show.any) return null
131
272
  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" : ""}`}
273
+ <LucideProvider strokeWidth={DENSE_ICON_STROKE}>
274
+ <div data-slot="view-toolbar" className="flex min-h-12 items-center gap-2 px-4 py-2">
275
+ <ToolbarTitle
276
+ title={viewData.title}
277
+ mode={titleMode}
278
+ level={titleLevel}
279
+ renderTitle={renderTitle}
280
+ />
281
+ <div className="ml-auto flex shrink-0 items-center gap-1">
282
+ {show.edit && (
283
+ <Button type="button" {...CHROME_BUTTON} size="sm" onClick={onEnterBuildMode}>
284
+ <Pencil aria-hidden="true" />
285
+ {labels.build}
286
+ </Button>
287
+ )}
288
+ {show.refresh && (
289
+ <RefreshButton isRefreshing={isRefreshing} labels={labels} onRefresh={onRefresh} />
290
+ )}
291
+ {show.fullscreen && (
292
+ <FullscreenButton
293
+ isFullscreen={isFullscreen}
294
+ labels={labels}
295
+ onToggle={onToggleFullscreen}
146
296
  />
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
297
  )}
170
- {isFullscreen ? labels.exitFullscreen : labels.enterFullscreen}
171
- </button>
298
+ </div>
172
299
  </div>
173
- </div>
300
+ </LucideProvider>
174
301
  )
175
302
  }
176
303
 
177
304
  // -------------------------------------------------------------------------- //
178
- // Pipeline error list
305
+ // Failed data steps
179
306
  // -------------------------------------------------------------------------- //
180
307
 
308
+ /** Resolves a human title for a pipeline step (`{ id, step }` from the view's steps). */
309
+ export type DescribeStep = (step: { id: string; step?: string }) => string | undefined
310
+
311
+ /**
312
+ * The failed data steps as ONE inline alert: what happened (the title), which
313
+ * data is affected (each step by its human title, else its step id), what you
314
+ * can do (retry, or ask in the chat when the view cannot refresh). The raw
315
+ * step reasons stay under "Details", out of the user copy.
316
+ */
181
317
  export function StepErrors({
182
318
  hidden,
183
319
  errors,
320
+ steps,
321
+ labels,
322
+ describeStep,
323
+ onRetry,
184
324
  }: {
185
- /** Build mode surfaces its own diagnostics; the shell list hides. */
325
+ /** Build mode surfaces its own diagnostics; the shell alert hides. */
186
326
  hidden: boolean
187
327
  errors: ViewData["context"]["errors"]
328
+ /** The view's step refs (`_refreshParams.steps`), to name a failed step by its definition. */
329
+ steps?: PipelineStepRef[]
330
+ labels: Required<McpAppViewLabels>
331
+ describeStep?: DescribeStep
332
+ /** Re-runs the view (a refresh); omitted when the view cannot be refreshed. */
333
+ onRetry?: () => void
188
334
  }) {
189
335
  if (hidden || errors.length === 0) return null
336
+ const failed = errors.map((err) => {
337
+ const ref = { id: err.stepId, step: steps?.find((s) => s.id === err.stepId)?.step }
338
+ return { ref, human: describeStep?.(ref), reason: err.reason }
339
+ })
190
340
  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
- ))}
341
+ <div className="px-4 pb-4">
342
+ <ErrorState
343
+ variant="inline"
344
+ title={labels.stepErrorsTitle}
345
+ retryLabel={labels.retry}
346
+ onRetry={onRetry}
347
+ detailsLabel={labels.errorDetails}
348
+ details={failed.map(({ ref, reason }) => `${ref.step ?? ref.id}: ${reason}`).join("\n")}
349
+ description={
350
+ <>
351
+ <ul className="flex flex-col gap-1">
352
+ {failed.map(({ ref, human }) => (
353
+ <li key={ref.id} className="break-words">
354
+ {human ? (
355
+ <span className="font-medium">{human}</span>
356
+ ) : (
357
+ <span className="font-mono text-xs">{ref.step ?? ref.id}</span>
358
+ )}
359
+ </li>
360
+ ))}
361
+ </ul>
362
+ <p className="mt-1">{onRetry ? labels.stepErrorsHint : labels.errorHintNoRetry}</p>
363
+ </>
364
+ }
365
+ />
197
366
  </div>
198
367
  )
199
368
  }
@@ -208,9 +377,17 @@ export interface ViewBodyProps {
208
377
  widgets: Record<string, WidgetComponent>
209
378
  callTool: (name: string, args: object) => Promise<unknown>
210
379
  onExit: LayoutBuilderProps["onExit"]
380
+ builderLabels?: LayoutBuilderLabels
211
381
  }
212
382
 
213
- export function ViewBody({ buildMode, viewData, widgets, callTool, onExit }: ViewBodyProps) {
383
+ export function ViewBody({
384
+ buildMode,
385
+ viewData,
386
+ widgets,
387
+ callTool,
388
+ onExit,
389
+ builderLabels,
390
+ }: ViewBodyProps) {
214
391
  if (buildMode) {
215
392
  return (
216
393
  <LayoutBuilder
@@ -220,6 +397,7 @@ export function ViewBody({ buildMode, viewData, widgets, callTool, onExit }: Vie
220
397
  initialSteps={viewData._refreshParams?.steps}
221
398
  widgets={widgets}
222
399
  callTool={callTool}
400
+ labels={builderLabels}
223
401
  onExit={onExit}
224
402
  />
225
403
  )
@@ -0,0 +1,100 @@
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). Hook-free and SSR-renderable, so
6
+ * each phase is pinned by a test.
7
+ */
8
+ import { ErrorState } from "../components/ErrorState.js"
9
+ import { Skeleton } from "../primitives/skeleton.js"
10
+ import type { McpAppViewLabels } from "./mcp-app-view-chrome.js"
11
+ import type { ViewLifecycle } from "./view-lifecycle.js"
12
+
13
+ /** Human text for a raw error reason (e.g. map "503" to "the engine is down"). */
14
+ export type DescribeViewError = (
15
+ reason: string | undefined,
16
+ ) => { title?: string; description?: string } | undefined
17
+
18
+ export function ViewPlaceholder({
19
+ lifecycle,
20
+ labels,
21
+ onRetry,
22
+ describeError,
23
+ }: {
24
+ /** The current phase; `result` renders nothing (the view takes over). */
25
+ lifecycle: ViewLifecycle
26
+ labels: Required<McpAppViewLabels>
27
+ onRetry: () => void
28
+ describeError?: DescribeViewError
29
+ }) {
30
+ switch (lifecycle.phase) {
31
+ case "result":
32
+ return null
33
+ case "cancelled":
34
+ return (
35
+ <div className="px-4 py-4">
36
+ <p role="status" className="text-muted-foreground text-sm">
37
+ {labels.cancelled}
38
+ </p>
39
+ </div>
40
+ )
41
+ case "error":
42
+ return (
43
+ <ViewErrorState
44
+ reason={lifecycle.reason}
45
+ canRetry={lifecycle.canRetry}
46
+ labels={labels}
47
+ onRetry={onRetry}
48
+ describeError={describeError}
49
+ />
50
+ )
51
+ case "waiting":
52
+ return <ViewWaiting slow={lifecycle.slow} labels={labels} />
53
+ }
54
+ }
55
+
56
+ function ViewWaiting({ slow, labels }: { slow: boolean; labels: Required<McpAppViewLabels> }) {
57
+ // No aria-busy on an ancestor of the live region: assistive technology may
58
+ // hold back changes inside a busy subtree, and the slow hint is one.
59
+ return (
60
+ <div className="flex flex-col gap-4 px-4 py-4">
61
+ <Skeleton aria-hidden="true" className="h-8 w-48" />
62
+ <Skeleton aria-hidden="true" className="h-32 w-full" />
63
+ {/* One live region: the status line, then the slow hint when it appears. */}
64
+ <div role="status" className="text-muted-foreground flex flex-col gap-1 text-sm">
65
+ <p>{labels.loading}</p>
66
+ {slow && <p data-slot="slow-hint">{labels.slowHint}</p>}
67
+ </div>
68
+ </div>
69
+ )
70
+ }
71
+
72
+ function ViewErrorState({
73
+ reason,
74
+ canRetry,
75
+ labels,
76
+ onRetry,
77
+ describeError,
78
+ }: {
79
+ reason: string | undefined
80
+ canRetry: boolean
81
+ labels: Required<McpAppViewLabels>
82
+ onRetry: () => void
83
+ describeError?: DescribeViewError
84
+ }) {
85
+ const human = describeError?.(reason)
86
+ // The hint never promises a retry the state does not offer.
87
+ const hint = canRetry ? labels.errorHint : labels.errorHintNoRetry
88
+ return (
89
+ <div className="px-4 py-4">
90
+ <ErrorState
91
+ title={human?.title ?? labels.errorTitle}
92
+ description={human?.description ?? hint}
93
+ details={reason}
94
+ detailsLabel={labels.errorDetails}
95
+ retryLabel={labels.retry}
96
+ onRetry={canRetry ? onRetry : undefined}
97
+ />
98
+ </div>
99
+ )
100
+ }