@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
@@ -0,0 +1,258 @@
1
+ import type { ReactNode } from "react"
2
+ import { renderToStaticMarkup } from "react-dom/server"
3
+ import { ToolCancelledError, ToolError } from "mcp-use/react"
4
+ import { beforeEach, describe, expect, it, vi } from "vitest"
5
+ import { LocaleProvider, useLocale } from "../locale/locale-provider.js"
6
+ import { TOOLKIT_MESSAGES, type ToolkitMessages } from "../locale/toolkit-messages.js"
7
+ import { attribute, decodeEntities, openingTags, userFacingTexts } from "../render-scan.js"
8
+ import { createStandaloneHostBridge, HostBridgeProvider } from "./host-bridge.js"
9
+ import { McpAppView } from "./mcp-app-view.js"
10
+ import type { ViewData } from "./mcp-app-view-chrome.js"
11
+
12
+ /**
13
+ * McpAppView's wiring, rendered through its real hooks (SSR, the house path):
14
+ * `useToolContext().status` → `useViewLifecycle` → the placeholder, the
15
+ * locale fallback, and the provider order behind the Lucide stroke. The
16
+ * mcp-use view hooks are the only mock; every phase here resolves on the
17
+ * first render, so no effect has to run. Guards toolkit#176 K14: an isError
18
+ * result must never fall back to the endless skeleton.
19
+ */
20
+
21
+ /** What the mocked mcp-use view hooks return on the next render. */
22
+ interface HostState {
23
+ tool: Record<string, unknown>
24
+ locale: string
25
+ hostContext: Record<string, unknown> | undefined
26
+ }
27
+
28
+ const host: HostState = vi.hoisted(() => ({ tool: {}, locale: "en-US", hostContext: undefined }))
29
+
30
+ // Only the fields McpAppView reads; the casts narrow the fixtures to the hook types.
31
+ vi.mock(import("mcp-use/react"), async (importOriginal) => {
32
+ const actual = await importOriginal()
33
+ const useToolContext = () => host.tool
34
+ const useHostContext = () => ({
35
+ locale: host.locale,
36
+ safeArea: { top: 0, right: 0, bottom: 0, left: 0 },
37
+ hostContext: host.hostContext,
38
+ })
39
+ return {
40
+ ...actual,
41
+ useToolContext: useToolContext as unknown as typeof actual.useToolContext,
42
+ useHostContext: useHostContext as unknown as typeof actual.useHostContext,
43
+ useDisplayMode: () => ({
44
+ displayMode: "inline",
45
+ availableDisplayModes: ["inline", "fullscreen"],
46
+ requestDisplayMode: () => Promise.resolve(),
47
+ }),
48
+ }
49
+ })
50
+
51
+ // No toast can be raised during SSR, so the provider renders one fixture
52
+ // toast where McpAppView mounts it: the stroke its icons inherit is the
53
+ // stroke a real toast stack gets.
54
+ vi.mock(import("../components/Toast.js"), async (importOriginal) => {
55
+ const actual = await importOriginal()
56
+ const { createElement, Fragment } = await import("react")
57
+ const { createToast } = await import("../lib/toast-model.js")
58
+ const fixture = [createToast(1, { tone: "danger", title: "Fixture toast" })]
59
+ return {
60
+ ...actual,
61
+ ToastProvider: ({ children }: { children: ReactNode }) =>
62
+ createElement(
63
+ Fragment,
64
+ null,
65
+ children,
66
+ createElement(actual.ToastViewport, { toasts: fixture, onDismiss: () => {} }),
67
+ ),
68
+ }
69
+ })
70
+
71
+ const en = TOOLKIT_MESSAGES.en.view
72
+ const de = TOOLKIT_MESSAGES.de.view
73
+ const KNOWN_TOOL = { toolInfo: { tool: { name: "render-view" } } }
74
+ const REASON = "[503 ProcessEngineException] Engine is shutting down"
75
+
76
+ const bridge = createStandaloneHostBridge({ callTool: () => Promise.resolve({}) })
77
+
78
+ /** A consumer widget that prints the locale it reads. */
79
+ function LocaleProbe() {
80
+ return <span data-testid="probe">widget-locale:{useLocale()}</span>
81
+ }
82
+
83
+ const VIEW: ViewData = {
84
+ title: "Process definitions",
85
+ context: {
86
+ keys: {},
87
+ stepIds: ["kpi"],
88
+ stepData: {},
89
+ errors: [{ stepId: "kpi", reason: "timeout" }],
90
+ },
91
+ layout: { rows: [{ row: [{ widget: "demo:probe" }] }] },
92
+ _refreshParams: { layout: { rows: [] }, steps: [{ id: "kpi", step: "demo:kpis" }] },
93
+ }
94
+
95
+ /** Render McpAppView; `view` is the shell, `toasts` the toast stack after it. */
96
+ function render(wrap: (node: ReactNode) => ReactNode = (node) => node) {
97
+ const html = renderToStaticMarkup(
98
+ <HostBridgeProvider bridge={bridge}>
99
+ {wrap(<McpAppView widgets={{ "demo:probe": LocaleProbe }} />)}
100
+ </HostBridgeProvider>,
101
+ )
102
+ const at = html.indexOf('<div data-slot="toast-viewport"')
103
+ expect(at, "the fixture toast stack renders").toBeGreaterThan(0)
104
+ return { html, view: html.slice(0, at), toasts: html.slice(at) }
105
+ }
106
+
107
+ const strokes = (html: string) => openingTags(html, "svg").map((t) => attribute(t, "stroke-width"))
108
+
109
+ const toolError = (text: string) =>
110
+ new ToolError({ isError: true, content: [{ type: "text", text }] })
111
+
112
+ beforeEach(() => {
113
+ host.tool = { status: "pending", toolInput: undefined, toolOutput: undefined }
114
+ host.locale = "en-US"
115
+ host.hostContext = KNOWN_TOOL
116
+ })
117
+
118
+ describe("McpAppView lifecycle wiring (toolkit#176)", () => {
119
+ it("an isError rendering result shows the error state with a retry, never the skeleton (K14)", () => {
120
+ host.tool = { status: "error", error: toolError(REASON), toolInput: {} }
121
+ const { view } = render()
122
+ expect(view).not.toContain('data-slot="skeleton"')
123
+ expect(view).not.toContain(en.loading)
124
+ expect(view).toContain('role="alert"')
125
+ expect(decodeEntities(view)).toContain(en.errorTitle)
126
+ expect(view).toContain(en.errorHint)
127
+ expect(view).toContain(`${en.retry}</button>`)
128
+ // The raw reason informs under Details; it is not the message.
129
+ expect(view).toMatch(/<details[^>]*><summary[^>]*>Details<\/summary><pre[^>]*>\[503/)
130
+ })
131
+
132
+ it("without the originating tool the error offers no retry and promises none", () => {
133
+ host.tool = { status: "error", error: toolError(REASON), toolInput: {} }
134
+ host.hostContext = undefined
135
+ const { view } = render()
136
+ expect(view).toContain('role="alert"')
137
+ expect(view).not.toContain("<button")
138
+ expect(view).toContain(en.errorHintNoRetry)
139
+ expect(view).not.toContain(en.errorHint)
140
+ })
141
+
142
+ it("a latched result without a view on such a host is an error, not an endless wait", () => {
143
+ host.tool = { status: "ready", toolOutput: { notAView: true }, toolInput: {} }
144
+ host.hostContext = undefined
145
+ const { view } = render()
146
+ expect(view).not.toContain('data-slot="skeleton"')
147
+ expect(view).toContain('role="alert"')
148
+ expect(view).toContain(en.errorHintNoRetry)
149
+ })
150
+
151
+ it("a host cancellation shows the cancelled notice, no skeleton and no retry", () => {
152
+ host.tool = { status: "error", error: new ToolCancelledError("user"), toolInput: {} }
153
+ const { view } = render()
154
+ expect(view).toContain(en.cancelled)
155
+ expect(view).not.toContain('role="alert"')
156
+ expect(view).not.toContain('data-slot="skeleton"')
157
+ expect(view).not.toContain("<button")
158
+ })
159
+
160
+ it("an open call waits: skeleton and status line in a stable, focusable frame", () => {
161
+ const { view } = render()
162
+ expect(view).toContain('data-slot="skeleton"')
163
+ expect(view).toMatch(/role="status"[^>]*>.*Loading view…/)
164
+ const frame = openingTags(view, "div")[0] ?? ""
165
+ expect(attribute(frame, "data-slot")).toBe("mcp-app-view")
166
+ expect(attribute(frame, "tabindex")).toBe("-1")
167
+ })
168
+
169
+ it("a complete payload renders the view: toolbar, step alert, widgets", () => {
170
+ host.tool = { status: "ready", toolOutput: VIEW, toolInput: {} }
171
+ const { view } = render()
172
+ expect(view).not.toContain('data-slot="skeleton"')
173
+ expect(view).toContain('data-slot="view-toolbar"')
174
+ expect(view).toContain(`aria-label="${en.refresh}"`)
175
+ expect(decodeEntities(view)).toContain(en.stepErrorsTitle)
176
+ expect(view).toContain("widget-locale:")
177
+ // The frame stays the same element across placeholder and view.
178
+ expect(attribute(openingTags(view, "div")[0] ?? "", "data-slot")).toBe("mcp-app-view")
179
+ })
180
+ })
181
+
182
+ describe("McpAppView locale", () => {
183
+ it("speaks the host's language in its own chrome", () => {
184
+ host.locale = "de-DE"
185
+ host.tool = { status: "error", error: toolError(REASON), toolInput: {} }
186
+ const { view, toasts } = render()
187
+ expect(decodeEntities(view)).toContain(de.errorTitle)
188
+ expect(view).toContain(`${de.retry}</button>`)
189
+ expect(toasts).toContain(`aria-label="${TOOLKIT_MESSAGES.de.common.notifications}"`)
190
+ })
191
+
192
+ it("leaves useLocale() of consumer widgets alone (no LocaleProvider of its own)", () => {
193
+ host.locale = "de-DE"
194
+ host.tool = { status: "ready", toolOutput: VIEW, toolInput: {} }
195
+ const { view } = render()
196
+ expect(view).toContain(`aria-label="${de.refresh}"`)
197
+ expect(view).toContain("widget-locale:en<")
198
+ })
199
+
200
+ it("follows a consumer LocaleProvider over the host's locale", () => {
201
+ host.locale = "de-DE"
202
+ host.tool = { status: "ready", toolOutput: VIEW, toolInput: {} }
203
+ const { view } = render((node) => <LocaleProvider locale="en">{node}</LocaleProvider>)
204
+ expect(view).toContain(`aria-label="${en.refresh}"`)
205
+ expect(view).toContain("widget-locale:en<")
206
+ })
207
+
208
+ it("renders no English and no raw reason as message text under a German host", () => {
209
+ host.locale = "de-DE"
210
+ host.tool = { status: "ready", toolOutput: VIEW, toolInput: {} }
211
+ const catalog = catalogStrings(TOOLKIT_MESSAGES.de)
212
+ const fixtures = [
213
+ VIEW.title ?? "",
214
+ "demo:kpis",
215
+ "kpi",
216
+ "timeout",
217
+ "Fixture toast",
218
+ "widget-locale:en",
219
+ ]
220
+ const { html } = render()
221
+ const leaks = userFacingTexts(html)
222
+ .map(({ source, value }) => {
223
+ let rest = value
224
+ for (const known of [...catalog, ...fixtures]) rest = rest.split(known).join(" ")
225
+ return { source, value, rest: rest.trim() }
226
+ })
227
+ .filter(({ rest }) => /\p{L}/u.test(rest))
228
+ expect(leaks).toEqual([])
229
+ })
230
+ })
231
+
232
+ describe("McpAppView icon stroke (CI §11)", () => {
233
+ it("draws the view's icons and its toast stack at 2.5", () => {
234
+ host.tool = { status: "error", error: toolError(REASON), toolInput: {} }
235
+ const { view, toasts } = render()
236
+ expect(strokes(view).length).toBeGreaterThan(0)
237
+ expect(new Set(strokes(view))).toEqual(new Set(["2.5"]))
238
+ expect(strokes(toasts).length).toBeGreaterThan(0)
239
+ expect(new Set(strokes(toasts))).toEqual(new Set(["2.5"]))
240
+ })
241
+
242
+ it("narrows the dense toolbar to 2", () => {
243
+ host.tool = { status: "ready", toolOutput: VIEW, toolInput: {} }
244
+ const { view } = render()
245
+ const [toolbar] = view.split('data-slot="view-toolbar"').slice(1)
246
+ const bar = (toolbar ?? "").split("</div></div>")[0] ?? ""
247
+ expect(strokes(bar).length).toBeGreaterThan(0)
248
+ expect(new Set(strokes(bar))).toEqual(new Set(["2"]))
249
+ })
250
+ })
251
+
252
+ /** Every string value of a catalog (parametrised messages are skipped), longest first. */
253
+ function catalogStrings(messages: ToolkitMessages): string[] {
254
+ return Object.values(messages)
255
+ .flatMap((section) => Object.values(section as Record<string, unknown>))
256
+ .filter((v): v is string => typeof v === "string")
257
+ .sort((a, b) => b.length - a.length)
258
+ }