@seed-ship/mcp-ui-solid 6.19.1 → 6.21.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 (282) hide show
  1. package/CHANGELOG.md +663 -0
  2. package/README.md +911 -4
  3. package/dist/adapters/connector.cjs +15 -4
  4. package/dist/adapters/connector.cjs.map +1 -1
  5. package/dist/adapters/connector.d.ts +38 -0
  6. package/dist/adapters/connector.d.ts.map +1 -1
  7. package/dist/adapters/connector.js +15 -4
  8. package/dist/adapters/connector.js.map +1 -1
  9. package/dist/adapters/index.d.ts +4 -3
  10. package/dist/adapters/index.d.ts.map +1 -1
  11. package/dist/adapters/macro-run.cjs +25 -13
  12. package/dist/adapters/macro-run.cjs.map +1 -1
  13. package/dist/adapters/macro-run.d.ts +37 -2
  14. package/dist/adapters/macro-run.d.ts.map +1 -1
  15. package/dist/adapters/macro-run.js +25 -13
  16. package/dist/adapters/macro-run.js.map +1 -1
  17. package/dist/adapters.cjs +2 -0
  18. package/dist/adapters.cjs.map +1 -1
  19. package/dist/adapters.js +4 -2
  20. package/dist/components/ActionGroupRenderer.cjs +3 -1
  21. package/dist/components/ActionGroupRenderer.cjs.map +1 -1
  22. package/dist/components/ActionGroupRenderer.d.ts.map +1 -1
  23. package/dist/components/ActionGroupRenderer.js +3 -1
  24. package/dist/components/ActionGroupRenderer.js.map +1 -1
  25. package/dist/components/AgentCard.cjs +11 -8
  26. package/dist/components/AgentCard.cjs.map +1 -1
  27. package/dist/components/AgentCard.d.ts.map +1 -1
  28. package/dist/components/AgentCard.js +11 -8
  29. package/dist/components/AgentCard.js.map +1 -1
  30. package/dist/components/AgentHandoff.cjs +6 -1
  31. package/dist/components/AgentHandoff.cjs.map +1 -1
  32. package/dist/components/AgentHandoff.d.ts.map +1 -1
  33. package/dist/components/AgentHandoff.js +6 -1
  34. package/dist/components/AgentHandoff.js.map +1 -1
  35. package/dist/components/ArtifactRenderer.cjs +25 -6
  36. package/dist/components/ArtifactRenderer.cjs.map +1 -1
  37. package/dist/components/ArtifactRenderer.d.ts.map +1 -1
  38. package/dist/components/ArtifactRenderer.js +25 -6
  39. package/dist/components/ArtifactRenderer.js.map +1 -1
  40. package/dist/components/AutocompleteDropdown.cjs +30 -24
  41. package/dist/components/AutocompleteDropdown.cjs.map +1 -1
  42. package/dist/components/AutocompleteDropdown.d.ts.map +1 -1
  43. package/dist/components/AutocompleteDropdown.js +30 -24
  44. package/dist/components/AutocompleteDropdown.js.map +1 -1
  45. package/dist/components/AutocompleteFormField.cjs +3 -1
  46. package/dist/components/AutocompleteFormField.cjs.map +1 -1
  47. package/dist/components/AutocompleteFormField.d.ts.map +1 -1
  48. package/dist/components/AutocompleteFormField.js +4 -2
  49. package/dist/components/AutocompleteFormField.js.map +1 -1
  50. package/dist/components/BriefingDiff.cjs +45 -39
  51. package/dist/components/BriefingDiff.cjs.map +1 -1
  52. package/dist/components/BriefingDiff.d.ts.map +1 -1
  53. package/dist/components/BriefingDiff.js +45 -39
  54. package/dist/components/BriefingDiff.js.map +1 -1
  55. package/dist/components/CarouselRenderer.cjs +8 -2
  56. package/dist/components/CarouselRenderer.cjs.map +1 -1
  57. package/dist/components/CarouselRenderer.d.ts.map +1 -1
  58. package/dist/components/CarouselRenderer.js +8 -2
  59. package/dist/components/CarouselRenderer.js.map +1 -1
  60. package/dist/components/ChartJSRenderer.cjs +60 -36
  61. package/dist/components/ChartJSRenderer.cjs.map +1 -1
  62. package/dist/components/ChartJSRenderer.d.ts.map +1 -1
  63. package/dist/components/ChartJSRenderer.js +61 -37
  64. package/dist/components/ChartJSRenderer.js.map +1 -1
  65. package/dist/components/ChatPrompt.cjs +15 -9
  66. package/dist/components/ChatPrompt.cjs.map +1 -1
  67. package/dist/components/ChatPrompt.d.ts.map +1 -1
  68. package/dist/components/ChatPrompt.js +15 -9
  69. package/dist/components/ChatPrompt.js.map +1 -1
  70. package/dist/components/CodeBlockRenderer.cjs +34 -16
  71. package/dist/components/CodeBlockRenderer.cjs.map +1 -1
  72. package/dist/components/CodeBlockRenderer.d.ts.map +1 -1
  73. package/dist/components/CodeBlockRenderer.js +34 -16
  74. package/dist/components/CodeBlockRenderer.js.map +1 -1
  75. package/dist/components/DataPreviewSection.cjs +91 -44
  76. package/dist/components/DataPreviewSection.cjs.map +1 -1
  77. package/dist/components/DataPreviewSection.d.ts.map +1 -1
  78. package/dist/components/DataPreviewSection.js +92 -45
  79. package/dist/components/DataPreviewSection.js.map +1 -1
  80. package/dist/components/DegradedFallback.cjs +20 -16
  81. package/dist/components/DegradedFallback.cjs.map +1 -1
  82. package/dist/components/DegradedFallback.d.ts.map +1 -1
  83. package/dist/components/DegradedFallback.js +20 -16
  84. package/dist/components/DegradedFallback.js.map +1 -1
  85. package/dist/components/FooterRenderer.cjs +21 -17
  86. package/dist/components/FooterRenderer.cjs.map +1 -1
  87. package/dist/components/FooterRenderer.d.ts.map +1 -1
  88. package/dist/components/FooterRenderer.js +21 -17
  89. package/dist/components/FooterRenderer.js.map +1 -1
  90. package/dist/components/FormFieldRenderer.cjs +116 -93
  91. package/dist/components/FormFieldRenderer.cjs.map +1 -1
  92. package/dist/components/FormFieldRenderer.d.ts.map +1 -1
  93. package/dist/components/FormFieldRenderer.js +116 -93
  94. package/dist/components/FormFieldRenderer.js.map +1 -1
  95. package/dist/components/FormRenderer.cjs +55 -31
  96. package/dist/components/FormRenderer.cjs.map +1 -1
  97. package/dist/components/FormRenderer.d.ts.map +1 -1
  98. package/dist/components/FormRenderer.js +55 -31
  99. package/dist/components/FormRenderer.js.map +1 -1
  100. package/dist/components/GenerativeUIErrorBoundary.cjs +20 -15
  101. package/dist/components/GenerativeUIErrorBoundary.cjs.map +1 -1
  102. package/dist/components/GenerativeUIErrorBoundary.d.ts.map +1 -1
  103. package/dist/components/GenerativeUIErrorBoundary.js +20 -15
  104. package/dist/components/GenerativeUIErrorBoundary.js.map +1 -1
  105. package/dist/components/GraphRenderer.cjs +56 -33
  106. package/dist/components/GraphRenderer.cjs.map +1 -1
  107. package/dist/components/GraphRenderer.d.ts.map +1 -1
  108. package/dist/components/GraphRenderer.js +56 -33
  109. package/dist/components/GraphRenderer.js.map +1 -1
  110. package/dist/components/IframeFallbackLink.cjs +37 -0
  111. package/dist/components/IframeFallbackLink.cjs.map +1 -0
  112. package/dist/components/IframeFallbackLink.d.ts +23 -0
  113. package/dist/components/IframeFallbackLink.d.ts.map +1 -0
  114. package/dist/components/IframeFallbackLink.js +37 -0
  115. package/dist/components/IframeFallbackLink.js.map +1 -0
  116. package/dist/components/ImageGalleryRenderer.cjs +13 -4
  117. package/dist/components/ImageGalleryRenderer.cjs.map +1 -1
  118. package/dist/components/ImageGalleryRenderer.d.ts.map +1 -1
  119. package/dist/components/ImageGalleryRenderer.js +13 -4
  120. package/dist/components/ImageGalleryRenderer.js.map +1 -1
  121. package/dist/components/LightboxOverlay.cjs +16 -8
  122. package/dist/components/LightboxOverlay.cjs.map +1 -1
  123. package/dist/components/LightboxOverlay.d.ts.map +1 -1
  124. package/dist/components/LightboxOverlay.js +17 -9
  125. package/dist/components/LightboxOverlay.js.map +1 -1
  126. package/dist/components/MapRenderer.cjs +59 -29
  127. package/dist/components/MapRenderer.cjs.map +1 -1
  128. package/dist/components/MapRenderer.d.ts +10 -2
  129. package/dist/components/MapRenderer.d.ts.map +1 -1
  130. package/dist/components/MapRenderer.js +60 -30
  131. package/dist/components/MapRenderer.js.map +1 -1
  132. package/dist/components/ModalRenderer.cjs +4 -1
  133. package/dist/components/ModalRenderer.cjs.map +1 -1
  134. package/dist/components/ModalRenderer.d.ts.map +1 -1
  135. package/dist/components/ModalRenderer.js +5 -2
  136. package/dist/components/ModalRenderer.js.map +1 -1
  137. package/dist/components/ScratchpadPanel.cjs +578 -535
  138. package/dist/components/ScratchpadPanel.cjs.map +1 -1
  139. package/dist/components/ScratchpadPanel.d.ts.map +1 -1
  140. package/dist/components/ScratchpadPanel.js +578 -535
  141. package/dist/components/ScratchpadPanel.js.map +1 -1
  142. package/dist/components/StreamingUIRenderer.cjs +92 -28
  143. package/dist/components/StreamingUIRenderer.cjs.map +1 -1
  144. package/dist/components/StreamingUIRenderer.d.ts.map +1 -1
  145. package/dist/components/StreamingUIRenderer.js +92 -28
  146. package/dist/components/StreamingUIRenderer.js.map +1 -1
  147. package/dist/components/UIResourceRenderer.cjs +481 -365
  148. package/dist/components/UIResourceRenderer.cjs.map +1 -1
  149. package/dist/components/UIResourceRenderer.d.ts +21 -0
  150. package/dist/components/UIResourceRenderer.d.ts.map +1 -1
  151. package/dist/components/UIResourceRenderer.js +484 -368
  152. package/dist/components/UIResourceRenderer.js.map +1 -1
  153. package/dist/components/VerifiedText.cjs +39 -20
  154. package/dist/components/VerifiedText.cjs.map +1 -1
  155. package/dist/components/VerifiedText.d.ts +2 -1
  156. package/dist/components/VerifiedText.d.ts.map +1 -1
  157. package/dist/components/VerifiedText.js +39 -20
  158. package/dist/components/VerifiedText.js.map +1 -1
  159. package/dist/components/VideoRenderer.cjs +51 -20
  160. package/dist/components/VideoRenderer.cjs.map +1 -1
  161. package/dist/components/VideoRenderer.d.ts.map +1 -1
  162. package/dist/components/VideoRenderer.js +51 -20
  163. package/dist/components/VideoRenderer.js.map +1 -1
  164. package/dist/components/chart-data-table.cjs +38 -17
  165. package/dist/components/chart-data-table.cjs.map +1 -1
  166. package/dist/components/chart-data-table.d.ts +30 -1
  167. package/dist/components/chart-data-table.d.ts.map +1 -1
  168. package/dist/components/chart-data-table.js +38 -17
  169. package/dist/components/chart-data-table.js.map +1 -1
  170. package/dist/context/MCPActionContext.cjs +3 -1
  171. package/dist/context/MCPActionContext.cjs.map +1 -1
  172. package/dist/context/MCPActionContext.d.ts.map +1 -1
  173. package/dist/context/MCPActionContext.js +3 -1
  174. package/dist/context/MCPActionContext.js.map +1 -1
  175. package/dist/context/MCPUIConfigContext.cjs +29 -0
  176. package/dist/context/MCPUIConfigContext.cjs.map +1 -0
  177. package/dist/context/MCPUIConfigContext.d.ts +124 -0
  178. package/dist/context/MCPUIConfigContext.d.ts.map +1 -0
  179. package/dist/context/MCPUIConfigContext.js +29 -0
  180. package/dist/context/MCPUIConfigContext.js.map +1 -0
  181. package/dist/context/MCPUIStringsContext.cjs +287 -2
  182. package/dist/context/MCPUIStringsContext.cjs.map +1 -1
  183. package/dist/context/MCPUIStringsContext.d.ts +607 -3
  184. package/dist/context/MCPUIStringsContext.d.ts.map +1 -1
  185. package/dist/context/MCPUIStringsContext.js +287 -2
  186. package/dist/context/MCPUIStringsContext.js.map +1 -1
  187. package/dist/hooks/index.d.ts +2 -2
  188. package/dist/hooks/index.d.ts.map +1 -1
  189. package/dist/hooks/useStreamingUI.cjs +28 -10
  190. package/dist/hooks/useStreamingUI.cjs.map +1 -1
  191. package/dist/hooks/useStreamingUI.d.ts +37 -0
  192. package/dist/hooks/useStreamingUI.d.ts.map +1 -1
  193. package/dist/hooks/useStreamingUI.js +28 -10
  194. package/dist/hooks/useStreamingUI.js.map +1 -1
  195. package/dist/hooks.cjs +1 -0
  196. package/dist/hooks.cjs.map +1 -1
  197. package/dist/hooks.js +2 -1
  198. package/dist/index.cjs +29 -8
  199. package/dist/index.cjs.map +1 -1
  200. package/dist/index.d.cts +12 -4
  201. package/dist/index.d.ts +12 -4
  202. package/dist/index.d.ts.map +1 -1
  203. package/dist/index.js +24 -3
  204. package/dist/index.js.map +1 -1
  205. package/dist/services/index.d.ts +1 -1
  206. package/dist/services/index.d.ts.map +1 -1
  207. package/dist/services/validation.cjs +52 -26
  208. package/dist/services/validation.cjs.map +1 -1
  209. package/dist/services/validation.d.ts +67 -2
  210. package/dist/services/validation.d.ts.map +1 -1
  211. package/dist/services/validation.js +52 -26
  212. package/dist/services/validation.js.map +1 -1
  213. package/dist/utils/degraded-projections.cjs +37 -12
  214. package/dist/utils/degraded-projections.cjs.map +1 -1
  215. package/dist/utils/degraded-projections.d.ts +39 -3
  216. package/dist/utils/degraded-projections.d.ts.map +1 -1
  217. package/dist/utils/degraded-projections.js +37 -12
  218. package/dist/utils/degraded-projections.js.map +1 -1
  219. package/dist/utils/format-string.cjs +10 -0
  220. package/dist/utils/format-string.cjs.map +1 -0
  221. package/dist/utils/format-string.d.ts +26 -0
  222. package/dist/utils/format-string.d.ts.map +1 -0
  223. package/dist/utils/format-string.js +10 -0
  224. package/dist/utils/format-string.js.map +1 -0
  225. package/dist/utils/iframe-coep.cjs +30 -0
  226. package/dist/utils/iframe-coep.cjs.map +1 -0
  227. package/dist/utils/iframe-coep.d.ts +46 -0
  228. package/dist/utils/iframe-coep.d.ts.map +1 -0
  229. package/dist/utils/iframe-coep.js +30 -0
  230. package/dist/utils/iframe-coep.js.map +1 -0
  231. package/dist/validation.cjs +5 -0
  232. package/dist/validation.cjs.map +1 -1
  233. package/dist/validation.d.cts +2 -1
  234. package/dist/validation.d.ts +2 -1
  235. package/dist/validation.d.ts.map +1 -1
  236. package/dist/validation.js +6 -1
  237. package/package.json +1 -1
  238. package/src/adapters/connector.ts +58 -4
  239. package/src/adapters/index.ts +12 -2
  240. package/src/adapters/macro-run.ts +71 -11
  241. package/src/components/ActionGroupRenderer.tsx +3 -1
  242. package/src/components/AgentCard.tsx +27 -9
  243. package/src/components/AgentHandoff.tsx +4 -1
  244. package/src/components/ArtifactRenderer.tsx +17 -5
  245. package/src/components/AutocompleteDropdown.tsx +8 -6
  246. package/src/components/AutocompleteFormField.tsx +5 -1
  247. package/src/components/BriefingDiff.tsx +11 -3
  248. package/src/components/CarouselRenderer.tsx +4 -2
  249. package/src/components/ChartJSRenderer.tsx +18 -9
  250. package/src/components/ChatPrompt.tsx +13 -8
  251. package/src/components/CodeBlockRenderer.tsx +12 -10
  252. package/src/components/DataPreviewSection.tsx +92 -26
  253. package/src/components/DegradedFallback.tsx +4 -2
  254. package/src/components/FooterRenderer.tsx +3 -1
  255. package/src/components/FormFieldRenderer.tsx +34 -19
  256. package/src/components/FormRenderer.tsx +38 -10
  257. package/src/components/GenerativeUIErrorBoundary.tsx +8 -3
  258. package/src/components/GraphRenderer.tsx +31 -16
  259. package/src/components/IframeFallbackLink.tsx +57 -0
  260. package/src/components/ImageGalleryRenderer.tsx +8 -4
  261. package/src/components/LightboxOverlay.tsx +6 -4
  262. package/src/components/MapRenderer.tsx +57 -20
  263. package/src/components/ModalRenderer.tsx +3 -1
  264. package/src/components/ScratchpadPanel.tsx +95 -44
  265. package/src/components/StreamingUIRenderer.tsx +41 -11
  266. package/src/components/UIResourceRenderer.tsx +176 -67
  267. package/src/components/VerifiedText.tsx +19 -9
  268. package/src/components/VideoRenderer.tsx +26 -4
  269. package/src/components/chart-data-table.ts +68 -14
  270. package/src/context/MCPActionContext.tsx +4 -1
  271. package/src/context/MCPUIConfigContext.tsx +151 -0
  272. package/src/context/MCPUIStringsContext.tsx +1014 -1
  273. package/src/hooks/index.ts +2 -1
  274. package/src/hooks/useStreamingUI.ts +64 -10
  275. package/src/index.ts +38 -0
  276. package/src/services/index.ts +1 -0
  277. package/src/services/validation.ts +131 -32
  278. package/src/solid-jsx-attrs.d.ts +24 -0
  279. package/src/utils/degraded-projections.ts +75 -12
  280. package/src/utils/format-string.ts +33 -0
  281. package/src/utils/iframe-coep.ts +74 -0
  282. package/src/validation.ts +9 -0
package/CHANGELOG.md CHANGED
@@ -5,6 +5,669 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [6.21.0] - 2026-09-17
9
+
10
+ The library's iframes now work on a host that serves
11
+ `Cross-Origin-Embedder-Policy: credentialless`.
12
+
13
+ ### The problem
14
+
15
+ Under that header a browser refuses **any** cross-origin iframe whose own
16
+ document does not itself send a COEP header. That is YouTube, Vimeo, Google
17
+ Docs, Notion — nearly every entry of `DEFAULT_IFRAME_DOMAINS`. The frame stays
18
+ blank and nothing in the page can detect it: a refused cross-origin document
19
+ fires no `error` event the parent can observe. The `credentialless` *header*
20
+ only relaxes the rule for sub-resources (images, fonts, map tiles — which is
21
+ why the quickchart `<img>` fallback and the Leaflet tiles were never
22
+ affected); nested documents need the boolean HTML attribute
23
+ `<iframe credentialless>` on the tag itself.
24
+
25
+ ### What the library now does
26
+
27
+ Both iframes it renders — `IframeRenderer` (the `iframe` component) and
28
+ `VideoRenderer`'s YouTube / Vimeo embed — can carry `credentialless` when their
29
+ host is **not** in `TRUSTED_IFRAME_DOMAINS`.
30
+
31
+ - **The `iframe` component on an untrusted host gets it by default.** Those
32
+ frames already ran without cookies, since their sandbox has no
33
+ `allow-same-origin`. The attribute costs them nothing and is exactly what
34
+ unblocks them under COEP.
35
+ - **The video embed does NOT get it by default.** It carries no sandbox, so a
36
+ YouTube or Vimeo frame really does hold provider cookies (consent, playback
37
+ state, sign-in). Taking them away on every host — including the majority
38
+ that send no COEP header — would be a silent regression, so `'auto'` leaves
39
+ it alone and a COEP host opts in with `iframeCredentialless: 'always'`.
40
+ - **Trusted hosts deliberately do NOT get it.** `TRUSTED_IFRAME_DOMAINS`
41
+ (Google Docs/Drive, Notion, Airtable, Figma, Linear, Stripe, Polar, HubSpot,
42
+ Calendly, …) exists precisely because those embeds need their own cookies to
43
+ authenticate. Loaded `credentialless` they would render a login screen — a
44
+ visibly broken embed. Under COEP they stay blocked, which is the honest
45
+ outcome; `iframeCredentialless: 'always'` overrides it if you disagree.
46
+
47
+ The attribute is a **boolean** attribute: it is absent from the DOM when it
48
+ does not apply, never `credentialless="false"`.
49
+
50
+ ### Added
51
+
52
+ - **`<MCPUIConfigProvider>` / `useMCPUIConfig()` / `DEFAULT_MCPUI_CONFIG` /
53
+ `MCPUIConfig`** (new `src/context/MCPUIConfigContext.tsx`, exported from the
54
+ root barrel) — host-level rendering policy, the behavioural counterpart of
55
+ `MCPUIStrings`. Three options:
56
+
57
+ | option | default | meaning |
58
+ | --- | --- | --- |
59
+ | `iframeCredentialless` | `'auto'` | `'auto'`: attribute on the `iframe` component when its host is not trusted (those frames already run without cookies); the video embed, which has no sandbox, is left alone. `'always'`: every iframe, video and trusted hosts included. `'never'`: none. |
60
+ | `customTrustedIframeDomains` | `[]` | extra hosts treated as trusted — no `credentialless`, and `allow-same-origin` in the sandbox. Subdomains match. Reclassification only: the allow-list must already accept the host. |
61
+ | `iframePolicy` | `'strict'` | how an `iframe` component's host is validated; `'extend'` also accepts `customIframeDomains`. |
62
+ | `customIframeDomains` | `[]` | extra hosts the allow-list accepts under `iframePolicy: 'extend'`. Without this, an unlisted host never reaches a renderer. |
63
+ | `iframeFallbackLink` | `'auto'` | when the "open in a new tab" link shows under an embed. `'auto'`: only on a cross-origin-isolated page. |
64
+
65
+ ```tsx
66
+ import { MCPUIConfigProvider } from '@seed-ship/mcp-ui-solid'
67
+
68
+ // A host that serves COEP: credentialless and embeds its own tool.
69
+ <MCPUIConfigProvider
70
+ config={{
71
+ iframeCredentialless: 'always',
72
+ iframePolicy: 'extend',
73
+ customIframeDomains: ['embed.acme.com'],
74
+ customTrustedIframeDomains: ['embed.acme.com'],
75
+ iframeFallbackLink: 'auto',
76
+ }}
77
+ >
78
+ <App />
79
+ </MCPUIConfigProvider>
80
+
81
+ // A host with no COEP header that does not want its embeds to lose cookies.
82
+ <MCPUIConfigProvider config={{ iframeCredentialless: 'never' }}>
83
+ <App />
84
+ </MCPUIConfigProvider>
85
+ ```
86
+
87
+ With no provider mounted, `DEFAULT_MCPUI_CONFIG` applies — every renderer
88
+ still works standalone.
89
+
90
+ - **`isTrustedIframeDomain(url, { customTrustedDomains? })`** — the trusted-host
91
+ test `getIframeSandbox` already performed, extracted and exported from the
92
+ root barrel and from the `@seed-ship/mcp-ui-solid/validation` subpath
93
+ (`getIframeSandbox` is now exported from that subpath too). Invalid URL →
94
+ `false`, so an unparsable URL is never trusted and does receive the
95
+ attribute. Matching is unchanged: exact host, or a subdomain of a listed
96
+ host.
97
+
98
+ - **`MCPUIStrings.iframeOpenInNewTab`** — optional, default
99
+ `'Open in a new tab'`. Label of the link rendered under an embed, which is
100
+ the only way out of a silently blocked frame. Visibility follows
101
+ `iframeFallbackLink`; `window.crossOriginIsolated` is read **after mount**,
102
+ never during render, so SSR markup and hydration agree. That flag requires
103
+ COOP `same-origin` on top of COEP — a host that sends COEP alone should set
104
+ `iframeFallbackLink: 'always'`, since its embeds are blocked while the flag
105
+ reads false. The link is skipped
106
+ when `safeUrl()` rejects the URL. `MCPUIStrings` now covers **271 keys**.
107
+
108
+ ### Fixed
109
+
110
+ - `IframeRenderer` called `getIframeSandbox(params.url)` with no options, so a
111
+ host's custom trusted domains never reached the sandbox. It now passes
112
+ `{ customTrustedDomains: config.customTrustedIframeDomains }`, which is what
113
+ makes `customTrustedIframeDomains` grant `allow-same-origin` as documented.
114
+
115
+ ### Browser support
116
+
117
+ | | header `COEP: credentialless` | attribute `<iframe credentialless>` |
118
+ | --- | --- | --- |
119
+ | Chrome / Edge | 96+ | **110+** |
120
+ | Firefox | 119+ | **not supported** |
121
+ | Safari | not supported (value ignored) | not supported |
122
+
123
+ - **Chrome / Edge** — the attribute fixes the problem. This is the majority
124
+ case.
125
+ - **Firefox** — honours the header but ignores the attribute, so third-party
126
+ iframes stay blocked for as long as the host page sends COEP. Nothing in
127
+ this library can change that; it is a host-side trade-off (drop COEP, or
128
+ accept the degradation on Firefox). The fallback link is what the user gets
129
+ there.
130
+ - **Safari** — does not know the `credentialless` value and treats the header
131
+ as absent: no cross-origin isolation, but embeds work.
132
+
133
+ ### Note for hosts
134
+
135
+ The library's allow-list is not a substitute for your CSP. The browser also
136
+ enforces `frame-src`, which the host sets — a domain missing from it stays
137
+ blocked whatever this library emits (see the comment at
138
+ `services/validation.ts` on `DEFAULT_IFRAME_DOMAINS`: "Must match CSP
139
+ frame-src directive").
140
+
141
+ ### Tests
142
+
143
+ `src/components/IframeCredentialless.test.tsx` (25 tests) — the
144
+ `isTrustedIframeDomain` matching table, attribute presence/absence through
145
+ `<UIResourceRenderer>` and `<VideoRenderer>` for each `iframeCredentialless`
146
+ mode, the `customTrustedIframeDomains` → sandbox plumbing, the fallback link
147
+ under each `iframeFallbackLink` mode with `window.crossOriginIsolated`
148
+ stubbed, and the SSR-shape assertions on the pure decision helpers. Plus a new
149
+ `iframeOpenInNewTab` case in `MCPUIStringsContext.sweep.test.tsx` and the
150
+ default pinned in `mcpui-strings-defaults.test.ts`.
151
+
152
+ ## [6.20.0] - 2026-09-17
153
+
154
+ Every user-visible chrome string the library itself renders — button titles,
155
+ `aria-label`s, placeholders, empty states, degraded-fallback captions, form
156
+ validation messages, and more — is now localizable: through `MCPUIStrings`
157
+ for components (`useMCPUIStrings()`), or through an exported `messages` /
158
+ `labels` option with an English default table for the runtime-free adapters,
159
+ services, helpers and hooks that cannot read Solid context. `MCPUIStrings`
160
+ now covers **270 keys** (9 required, carried over from the original v6.6.0
161
+ surface, plus 261 optional — every key added since defaults to English and
162
+ falls back cleanly when omitted). The only strings still hardcoded fall under
163
+ the explicit exclusion policy below (P1–P8). Both are enforced by an
164
+ AST-based guard test over all of `src` and a runtime pseudo-localization
165
+ render test — see **Tests**.
166
+
167
+ ### Added
168
+
169
+ - **251 new optional `MCPUIStrings` keys** (the released 6.19.1 had 19),
170
+ bringing the total to 270. Each defaults to the exact English literal it
171
+ replaced, except the defaults listed under **Changed**. The complete list,
172
+ with defaults and where each key is used, is the key reference table in
173
+ the README (*Internationalization (i18n) — `MCPUIStrings`*). Selected
174
+ groups:
175
+
176
+ - **Generic chrome** — `download`: `"Download"` (`ArtifactRenderer`
177
+ download link), `unknown`: `"unknown"` (`GenerativeUIErrorBoundary`
178
+ metadata fallback)
179
+ - **`UIResourceRenderer` table citations** — `citationUnresolved`:
180
+ `"[ref. {id}]"` (template `{id}`; placeholder for a citation marker no
181
+ `citationMap` resolves, empty map only — see **Changed**)
182
+ - **`UIResourceRenderer` error / fallback chrome** — `invalidComponent`:
183
+ `"Invalid {type}"`, `toolErrorTitle`: `"Tool Error: {tool}"`,
184
+ `errorTypeLabel`: `"Type: {type}"`, `errorSuggestions`: `"Suggestions:"`,
185
+ `chartInvalidData`: `"Invalid chart data: missing data.datasets"`,
186
+ `unsupportedComponentType`: `"Unsupported component type:"`
187
+ - **`UIResourceRenderer` table (search, pagination, tool-error clipboard)**
188
+ — `tableVirtualizedRows`: `"(virtualized: {count} rows)"`,
189
+ `tableSearchResultsOne` / `tableSearchResultsMany`:
190
+ `"{count} result(s) on {total}"` (was one template with a hardcoded
191
+ `s`), `tableServerPageRange`: `"Showing {start} - {end} of {total}"`,
192
+ `errorCopyText`: `"Error in {tool}: {message}"` (text the tool-error
193
+ card's Copy button puts on the clipboard), `errorUnknownTool`:
194
+ `"unknown tool"` (tool name inside `errorCopyText` when the error has
195
+ none)
196
+ - **`DataPreviewSection`** — `previewPrev`: `"Prev"`, `previewNext`:
197
+ `"Next"`, `previewPageIndicator`: `"Page {page} / {total}"`,
198
+ `previewShowingRange`: `"Showing {start}–{end} of {total}"`,
199
+ `previewRowsOne`: `"{count} row"`, `previewRowsMany`: `"{count} rows"`,
200
+ `previewTotalSuffix`: `" ({total} total)"`
201
+ - **`FormFieldRenderer`** — `fieldResolving`: `"Resolving..."`,
202
+ `fieldAddMorePlaceholder`: `"Add more..."`, `fieldUnknownType`:
203
+ `"Unknown field type: {type}"`, `fieldSelectedCount`: `"{count}
204
+ selected"`
205
+ - **`FormRenderer`** — `formPrefilledOne`: `"{count} field pre-filled out
206
+ of {total}"`, `formPrefilledMany`: `"{count} fields pre-filled out of
207
+ {total}"` (shared with `ScratchpadPanel`'s prefill summary — see
208
+ **Changed**), `formSubmitting`: `"Submitting..."`, `formReset`:
209
+ `"Reset"`
210
+ - **`GenerativeUIErrorBoundary`** — `errorBoundaryTitle`: `"Component
211
+ Failed to Render"`, `errorBoundaryMeta`: `"Type: {type} | ID:
212
+ {id}..."` (both fall back to `unknown`), `errorBoundaryRetry`: `"Retry
213
+ Rendering"`
214
+ - **`ScratchpadPanel`** — `scratchpadSearch`: `"Search"`
215
+ (`waiting_human` search button), `scratchpadNextStep`: `"Next"`
216
+ (steps view advance button), `scratchpadPlan`: `"Plan:"`,
217
+ `scratchpadModify`: `"Modify"` (prompt-section edit button),
218
+ `scratchpadDetails`: `"Details"` (error-section toggle),
219
+ `scratchpadItems`: `"items"` (action-section item-count suffix),
220
+ `statusLoading` / `statusActionAvailable` / `statusYourTurn` /
221
+ `statusProcessing` / `statusComplete` — status badge, one key per
222
+ state (the `error` state reuses `scratchpadError`),
223
+ `scratchpadErrorCode`: `"Code: {code}"`, `scratchpadResultCount`:
224
+ `"{count} results"` (data-source card row count)
225
+ - **`VideoRenderer`** — `videoUnsupported`: `"Your browser does not
226
+ support the video tag."` (`<video>` fallback text)
227
+ - **`AgentCard` / `AgentStatusBadge`** — `agentStatusIdle`: `"Idle"`,
228
+ `agentStatusRunning`: `"Running"`, `agentStatusWaiting`: `"Waiting"`,
229
+ `agentStatusDone`: `"Done"`, `agentStatusError`: `"Error"`
230
+ - **`AgentHandoff`** — `handoffItems`: `"{count} items"` (fallback;
231
+ `content.summary` still wins)
232
+ - **Fallback ladder** (`DegradedFallback`, `ChartJSRenderer`,
233
+ `GraphRenderer`, `MapRenderer`) — `degradedMoreRows`: `"+{count} more
234
+ rows not shown."`, `chartDegradedCaption`, `chartRenderFailed`:
235
+ `"Chart rendering failed: {error}"`, `chartRenderError`,
236
+ `chartQuickchartCaption`, `graphDegradedCaption`, `graphRenderFailed`,
237
+ `graphRenderError`, `graphPngUnsupported`, `graphPngExportFailed`,
238
+ `mapLibraryUnavailable`, `mapDegradedCaption`, `mapRenderFailed`,
239
+ `mapRenderError`
240
+ - **Degraded / accessible table headers** — `degradedColType`,
241
+ `degradedColLat`, `degradedColLng`, `degradedColInfo`,
242
+ `degradedColSource`, `degradedColTarget`, `degradedColNode`,
243
+ `degradedColLabel`, `degradedSeries`: `"Series {n}"`, `chartTableSeries`,
244
+ `chartTablePoint`, plus `degradedMarker`: `"marker"` and
245
+ `degradedFeature`: `"feature"` — the degraded map table's `Type` cell
246
+ for a marker row / a GeoJSON feature without a geometry type (mirrored
247
+ in `DEGRADED_PROJECTION_LABELS`, see below)
248
+ - **`AutocompleteDropdown` / `AutocompleteFormField`** —
249
+ `autocompleteHintNavigate`: `" to navigate, "`, `autocompleteHintSelect`:
250
+ `" to select, "`, `autocompleteHintDismiss`: `" to dismiss"`,
251
+ `autocompleteTabToAccept`: `"Tab to accept"` (the `<kbd>` key caps
252
+ themselves stay hardcoded — P1)
253
+ - **Images and chart fallbacks** — `galleryViewImage`: `"View image
254
+ {index}"`, `galleryImageAlt`: `"Image {index}"` (a payload `image.alt`
255
+ still wins for both), `imageAltFallback`: `"image"`,
256
+ `chartVisualizationAlt`: `"Chart visualization"`, `chartWithTitleAlt`:
257
+ `"Chart: {title}"`, `chartLoadFailed`: `"Failed to load chart"`
258
+ - **Table pagination** — `paginationAllRows`: `"All"`
259
+ - **Form validation** — `fieldRequired`, `fieldMustBeChecked`,
260
+ `fieldMinLength`, `fieldMaxLength`, `fieldInvalidPattern`,
261
+ `fieldInvalidEmail`, `fieldInvalidNumber`, `fieldMinValue`,
262
+ `fieldMaxValue`, `fieldMinDate`, `fieldMaxDate`, `fieldInvalidOption`,
263
+ `fieldInvalidFormat`: `"Invalid format (expected: {format})"`
264
+ (`field.valueFormatHint` wins when set) — `FormRenderer` feeds these
265
+ into `validateFieldValue` / `validateFormData` from `useMCPUIStrings()`
266
+ - **`BriefingDiff`** — `briefingAdded`: `"+{count} added"`,
267
+ `briefingRemoved`: `"{count} removed"`, `briefingChanged`: `"{count}
268
+ changed"`
269
+ - **`FooterRenderer`** — `footerSources`: `"{count} sources"`
270
+ - **`StreamingUIRenderer` / `useStreamingUI`** — `streamInitializing`:
271
+ `"Initializing..."`, `streamConnecting`: `"Connecting to server..."`,
272
+ `streamLoadingComponent`: `"Loading {type} component..."`,
273
+ `streamDashboardLoaded`: `"Dashboard loaded"`, `streamErrorProgress`:
274
+ `"Error: {message}"`, `streamConnectionFailed`: `"Stream connection
275
+ failed"`, `streamRequestFailed`: `"Stream request failed"`,
276
+ `streamEmptyResponse`: `"Response body is null"`, `streamServerSide`:
277
+ `"Streaming UI cannot start on server-side"`, `streamUnknownError`:
278
+ `"Unknown error"`
279
+ - **`locale`**: `"en-US"` — see **Changed**
280
+ - **Unavailable and degraded states shown to end users** —
281
+ `streamServerSideTitle`: `"Streaming unavailable"` (see **Changed**),
282
+ `graphUnavailable`: `"Graph rendering unavailable"`,
283
+ `mapPmtilesUnavailable`: `"PMTiles layer unavailable — the optional
284
+ \"protomaps-leaflet\" peer dependency failed to load or render."`,
285
+ `mapBaseMapStillShown`: `"The base map is still shown."`,
286
+ `chartJsUnavailable`: `"Chart.js is not available. Install chart.js peer
287
+ dependency."`, `chartIframeUnavailable`: `"Interactive chart unavailable
288
+ — install the chart.js peer dependency, or set allowQuickchartFallback to
289
+ use the external quickchart.io renderer."`, `previewInvalidContent`:
290
+ `"[DataPreviewSection] Invalid content format"`. Their text names a peer
291
+ or a config flag, but end users see them, so a host can translate them.
292
+ - **Citation chip tooltip** — `citationViewSource`: `"View source -
293
+ {label}"` (template `{label}`: file name and page)
294
+ - **`ArtifactRenderer` file sizes** — `sizeBytes`: `"{size} B"`,
295
+ `sizeKilobytes`: `"{size} KB"`, `sizeMegabytes`: `"{size} MB"` (the number
296
+ is formatted with `locale`)
297
+
298
+ - **`useStreamingUI({ messages })`** — new optional `messages?:
299
+ Partial<StreamingUIMessages>` on `UseStreamingUIOptions`, with exported
300
+ `DEFAULT_STREAMING_UI_MESSAGES` and `StreamingUIMessages` (root barrel and
301
+ `/hooks`). Covers `initializing`, `connecting`, `loadingComponent`,
302
+ `dashboardLoaded`, `errorProgress`, `connectionFailed`, `requestFailed`,
303
+ `emptyResponse`, `serverSide`, `unknownError`. `StreamingUIRenderer` feeds
304
+ it from the `stream*` `MCPUIStrings` keys through getters (a `messages`
305
+ prop passed directly to the renderer still wins over the context). Every
306
+ other hook in the library (`useAction`, `useAutocomplete`, `useModal`, …)
307
+ is unchanged.
308
+
309
+ - **`CitationCtx.unresolvedLabel?: string`** — new optional field on the
310
+ exported `CitationCtx` interface (`UIResourceRenderer.tsx`). It supplies
311
+ the placeholder `transformCellCitations` keeps in a table cell for a
312
+ citation marker that no `citationMap` resolves (empty map only). Optional,
313
+ so every existing `renderCellValue(value, citationCtx?)` call site keeps
314
+ type-checking; the placeholder now defaults to English (`citationUnresolved`)
315
+ instead of hardcoded French. A second optional field,
316
+ **`CitationCtx.viewSourceLabel?: string`**, carries the default citation
317
+ chip's tooltip template (`citationViewSource`); the table renderer fills
318
+ both from `useMCPUIStrings()`.
319
+
320
+ - **New export `formatMCPUIString(template, vars)`** (root barrel) —
321
+ replaces `{name}` placeholders in a template with `vars[name]`; an
322
+ unrecognized `{placeholder}` is left verbatim rather than throwing or
323
+ rendering `undefined`. It lives in the dependency-free
324
+ `src/utils/format-string.ts` (no imports), so the runtime-free adapters
325
+ use it too, and `MCPUIStringsContext.tsx` re-exports it.
326
+
327
+ - **New `messages` / `labels` options on the runtime-free adapters,
328
+ services and helpers** — these modules cannot read
329
+ `MCPUIStringsProvider` (no `solid-js` import), so their English ships as
330
+ a TRAILING OPTIONAL parameter with an exported default table; every
331
+ existing call site keeps type-checking and behaving identically:
332
+
333
+ - **`connectorResultToUILayout(result, options?)`**
334
+ (`src/adapters/connector.ts`) — `options.messages?:
335
+ Partial<ConnectorAdapterMessages>`. Covers the connector adapter's two
336
+ degraded-state paragraphs: `degradedNotice` (unreadable payload,
337
+ template `{versionSuffix}`), `degradedVersionSuffix` (template
338
+ `{version}`), `versionWarning` (unrecognized schema version, template
339
+ `{version}`, `{expected}`). New exports `ConnectorAdapterMessages` and
340
+ `DEFAULT_CONNECTOR_MESSAGES` from `@seed-ship/mcp-ui-solid/adapters`
341
+ (not the root barrel — no other connector-adapter export is there
342
+ either):
343
+
344
+ ```ts
345
+ import { connectorResultToUILayout } from '@seed-ship/mcp-ui-solid/adapters'
346
+
347
+ connectorResultToUILayout(result, {
348
+ messages: {
349
+ versionWarning: '> ⚠ Schéma connecteur non reconnu (`{version}`, attendu `{expected}`).',
350
+ },
351
+ })
352
+ ```
353
+
354
+ - **`macroRunToScratchpadState(run, options?)` /
355
+ `macroInterrogationToChatPromptConfig(q, options?)`**
356
+ (`src/adapters/macro-run.ts`) — `options.messages?:
357
+ Partial<MacroRunAdapterMessages>`. The real fields are
358
+ `agentSectionTitle` (`"Agent"`), `progressSectionTitle` (`"Progress"`),
359
+ `resultSectionTitle` (`"Result"`), `runAborted` (`"Macro run
360
+ aborted."`), `runFailed` (`"Macro run failed."`), `confirmDefault`
361
+ (`"Please confirm to continue."`). New exports
362
+ `MacroRunAdapterMessages`, `MacroRunAdapterOptions` and
363
+ `DEFAULT_MACRO_RUN_MESSAGES` from `@seed-ship/mcp-ui-solid/adapters`:
364
+
365
+ ```ts
366
+ macroRunToScratchpadState(run, { messages: { resultSectionTitle: 'Résultat' } })
367
+ ```
368
+
369
+ - **`validateFieldValue(value, field, messages?)` /
370
+ `validateFormData(data, fields, messages?)`** (`src/services/validation.ts`)
371
+ — `messages?: Partial<FormValidationMessages>` (13 fields: `required`,
372
+ `mustBeChecked`, `minLength`, `maxLength`, `invalidPattern`,
373
+ `invalidEmail`, `invalidNumber`, `minValue`, `maxValue`, `minDate`,
374
+ `maxDate`, `invalidOption`, `invalidFormat`). New exports
375
+ `validateFieldValue`, `validateFormData`, `FormValidationMessages` and
376
+ `DEFAULT_VALIDATION_MESSAGES` from the root barrel and
377
+ `@seed-ship/mcp-ui-solid/validation` (the two validators were not
378
+ exported from any entry point before). `FormRenderer` feeds
379
+ this from `useMCPUIStrings()` (the `field*` keys above) — a host does
380
+ not normally call `validateFieldValue` directly for that path. The
381
+ STRUCTURAL validators (`validateComponent`, `validateLayout`, …) keep
382
+ their English `ValidationError.message` — see the exclusion policy
383
+ below.
384
+
385
+ - **`graphToDegradedTable` / `mapToDegradedTable` /
386
+ `chartToDegradedTable`** (`src/utils/degraded-projections.ts`) —
387
+ `labels?: Partial<DegradedProjectionLabels>` (`source`, `target`,
388
+ `label`, `node`, `type`, `lat`, `lng`, `info`, `marker`, `feature`,
389
+ `series`). **`chartToDataTable(params, labels?)`**
390
+ (`src/components/chart-data-table.ts`) — `labels?:
391
+ Partial<ChartDataTableLabels>` (`series`, `point`, `label`,
392
+ `seriesName`, `x`, `y`, `r` — the last three are the Chart.js point
393
+ property names the cells mirror, still overridable). New root-barrel
394
+ exports `graphToDegradedTable`, `mapToDegradedTable`,
395
+ `chartToDegradedTable`, `chartToDataTable` (not exported from any entry
396
+ point before), `DegradedProjectionLabels`, `DEGRADED_PROJECTION_LABELS`,
397
+ `ChartDataTableLabels`, `CHART_DATA_TABLE_LABELS`. The renderers pass
398
+ the `degradedCol*` / `chartTable*` keys from `useMCPUIStrings()`.
399
+
400
+ ```ts
401
+ graphToDegradedTable(graph, { source: 'Origine', target: 'Destination' })
402
+ ```
403
+
404
+ - **The chrome-strings guard is now a TypeScript-AST scanner over all of
405
+ `src`.** `scripts/chrome-scan.ts` (outside `src`, not published; `node
406
+ scripts/chrome-scan.ts [root] [--json] [--raw]`) parses every non-test
407
+ source and flags: JSX text; literals reaching a visible attribute, a JSX
408
+ child or the clipboard through `||` / `??`, each ternary branch, template
409
+ spans, `formatMCPUIString`, helper returns, `const` variables and signal
410
+ setters; prose in object properties, `*Label`-style helper returns and
411
+ arrays; French text (accented characters or stopwords); and hardcoded
412
+ BCP-47 locale tags or argument-less `toLocaleString()` calls in
413
+ components. `console.*` / logger / telemetry calls, `onError` payloads and
414
+ thrown errors are skipped by AST context (see **P7** below) — the scanner
415
+ never has to allow-list them individually. The repository's exclusions
416
+ (`ALLOW_LIST`, `SANCTIONED_TABLES`, `SCOPED_EXCLUSIONS`) live in
417
+ `scripts/chrome-scan.policy.ts` and are enforced by
418
+ `mcpui-strings-guard.test.ts` — see **Tests**.
419
+
420
+ - **Runtime pseudo-localization test**, `MCPUIStrings.pseudolocale.test.tsx`
421
+ — see **Tests**.
422
+
423
+ ### Changed
424
+
425
+ - **New `locale` key (default `'en-US'`) drives every number, date and
426
+ collation call the library makes on its own behalf.** `strings.locale` now
427
+ feeds:
428
+ - `UIResourceRenderer` — table sort (`localeCompare`, was hardcoded
429
+ `'fr'`) and the client-side pagination range total (`toLocaleString`,
430
+ was hardcoded `'fr-FR'`), and the tool-error card timestamp
431
+ (`toLocaleString`, was called with no argument at all — the runtime's
432
+ implicit locale, which can differ between SSR and hydration);
433
+ - `DataPreviewSection` — number / currency / date cell formatting and
434
+ sort comparison (were `'fr-FR'` / `'fr'`; the `" EUR"` currency suffix
435
+ itself stays — P4), the page-info row counts (were `'fr-FR'`), and
436
+ percent cells (were `toFixed(1)` + `"%"`, now `Intl.NumberFormat` with
437
+ `style: 'percent'` — identical output in `en-US`);
438
+ - `StreamingUIRenderer` — the metadata panel cost (was `"$"` +
439
+ `toFixed(4)`, now `Intl.NumberFormat` currency USD with a narrow symbol
440
+ — identical output in `en-US`);
441
+ - `ArtifactRenderer` — the file-size number (was `toFixed(1)`);
442
+ - `ScratchpadPanel` — the filter-preview row count and the data-source
443
+ card's `row_count` (were argument-less);
444
+ - `MapRenderer` — `buildPopupContent(feature, popup, allowHtml?, locale?)`
445
+ and `addGeoJSONLayer(..., allowHtml?, locale?)` gain a new trailing
446
+ optional `locale` parameter for the numbers in auto-generated popups
447
+ (was hardcoded `'fr-FR'`).
448
+
449
+ Restore the previous French formatting with
450
+ `<MCPUIStringsProvider strings={{ locale: 'fr-FR' }}>`.
451
+
452
+ `useMCPUIStrings()` always returns a valid tag: an empty or invalid
453
+ `locale` (for example `'not_a_locale'`, which makes `Intl` throw a
454
+ `RangeError`) resolves to `'en-US'`, and a valid one is canonicalized
455
+ (`'fr-fr'` → `'fr-FR'`). The same check is exported as
456
+ `resolveMCPUILocale(locale)`. A locale change re-renders the counts and
457
+ citation wording that depend on it; GeoJSON map popups read it when they
458
+ open, so the map is not rebuilt.
459
+
460
+ Time zones are a separate concern from the locale. Date-only
461
+ `DataPreviewSection` cells (`2026-09-17`) are now formatted in UTC, so the
462
+ server and every viewer show the same calendar date. Date-time cells and the
463
+ tool-error card timestamp are formatted only after mount, in the viewer's
464
+ time zone, because the server cannot know it: server markup shows the raw
465
+ date-time value, then the formatted date replaces it.
466
+
467
+ - **Five more chrome defaults move from French to English**, matching
468
+ every other key (a published library ships no hardcoded non-English
469
+ chrome):
470
+ - `tableSearchPlaceholder`: `"Rechercher dans le tableau..."` → `"Search
471
+ the table..."` (`UIResourceRenderer` table search input; a connector's
472
+ own `tableParams.searchPlaceholder` still wins when set);
473
+ - `verifiedStripLabel`: `"[non vérifié]"` → `"[unverified]"`
474
+ (`VerifiedText` `mode="strip"`);
475
+ - `scratchpadEdit`: `"Modifier"` → `"Edit"` (`ScratchpadPanel`'s
476
+ collapsed auto-submit form edit button);
477
+ - `citationUnresolved`: `"[réf. {id}]"` → `"[ref. {id}]"`, now backed by
478
+ the new `CitationCtx.unresolvedLabel`;
479
+ - `formPrefilledOne` / `formPrefilledMany`: `"{count} champ(s)
480
+ pré-rempli(s) sur {total}"` → `"{count} field(s) pre-filled out of
481
+ {total}"` (`FormRenderer` and `ScratchpadPanel`'s prefill summary).
482
+
483
+ Restore the previous French text for these five, and the connector
484
+ adapter's degraded-state text (also changed from French to English —
485
+ `DEFAULT_CONNECTOR_MESSAGES`, below), with:
486
+
487
+ ```tsx
488
+ <MCPUIStringsProvider
489
+ strings={{
490
+ locale: 'fr-FR',
491
+ tableSearchPlaceholder: 'Rechercher dans le tableau...',
492
+ verifiedStripLabel: '[non vérifié]',
493
+ scratchpadEdit: 'Modifier',
494
+ citationUnresolved: '[réf. {id}]',
495
+ formPrefilledOne: '{count} champ pré-rempli sur {total}',
496
+ formPrefilledMany: '{count} champs pré-remplis sur {total}',
497
+ }}
498
+ >
499
+ <App />
500
+ </MCPUIStringsProvider>
501
+ ```
502
+
503
+ ```ts
504
+ connectorResultToUILayout(result, {
505
+ messages: {
506
+ degradedNotice:
507
+ '### Résultat non rendu\n\nLe résultat du connecteur n\'a pas pu être interprété{versionSuffix}. Cet état explicite remplace une disparition silencieuse du rendu.',
508
+ degradedVersionSuffix: ' (schéma : `{version}`)',
509
+ versionWarning:
510
+ '> ⚠ Schéma connecteur non reconnu (`{version}`, attendu `{expected}`). Le rendu ci-dessous est en mode dégradé.',
511
+ },
512
+ })
513
+ ```
514
+
515
+ - **`StreamingUIRenderer` no longer shows the machine code `ssr` as its
516
+ error heading** when streaming is started during server rendering. It
517
+ renders `streamServerSideTitle` (`"Streaming unavailable"`) instead. The
518
+ hook's `StreamError.error` keeps the code `'ssr'` for consumers that test
519
+ it.
520
+
521
+ - **`ScratchpadPanel`'s feedback badge now shows the option's label**, not
522
+ its machine value (`approve` / `reject` / `comment`); nothing is shown
523
+ after the icon for a free-text comment.
524
+
525
+ - **`MCPActionProvider`'s fallback action error** — when the action
526
+ executor throws a non-`Error` value, `ActionResult.error` (which
527
+ `FormRenderer` displays) now reads `errorUnknown` instead of a hardcoded
528
+ string.
529
+
530
+ ### Exclusion policy
531
+
532
+ Every user-visible chrome string now reads from `MCPUIStrings` (components)
533
+ or an exported `messages`/`labels` option (runtime-free adapters, services,
534
+ helpers, hooks) — enforced by `mcpui-strings-guard.test.ts`: anything the
535
+ scanner flags must become a key or an option, or be allow-listed here under
536
+ one of P1–P8.
537
+
538
+ - **P1 — keyboard key caps inside `<kbd>`.** `AutocompleteDropdown`'s
539
+ `<kbd>Enter</kbd>` and `<kbd>Esc</kbd>` — the physical key reads "Enter"
540
+ whatever the UI language. The surrounding hint prose IS localized
541
+ (`autocompleteHintNavigate` / `autocompleteHintSelect` /
542
+ `autocompleteHintDismiss`).
543
+ - **P2 — legal attribution wording.** `MapRenderer`'s `"OpenStreetMap</a>
544
+ contributors"` tile attribution — exact wording required by the ODbL. A
545
+ host overrides the whole notice via `params.attribution` for another
546
+ language.
547
+ - **P3 — developer diagnostics** that name an npm package, a peer
548
+ dependency or a config flag; bracketed `[Component] …` messages;
549
+ debug-only panels behind an explicit debug flag; developer-misuse errors;
550
+ and the structural payload-schema diagnostics of `services/validation.ts`:
551
+ - `GraphRenderer`'s install hint under the localized
552
+ `graphUnavailable` heading: `"Install"` / `"@antv/g6"` / `"peer
553
+ dependency to render"` / `"type: \"graph\""` / `"components."` — it
554
+ names the missing optional `@antv/g6` peer, for whoever installs the
555
+ host.
556
+ - `RenderContext`'s `'Component "'` / `'" cannot be rendered outside of
557
+ UIResourceRenderer'` — a developer-misuse error (a renderer used
558
+ outside `<UIResourceRenderer>`).
559
+ - `ScratchpadPanel`'s debug overlay (`| ev:`, `| sec:`, `| last:`,
560
+ behind the `debugOverlay` prop) and its `FormDebugTrace` panel (behind
561
+ the `debugTrace` prop) — both mirror raw server/event keys verbatim, for
562
+ developers.
563
+ - `services/validation.ts`'s structural validators — `mapZodIssuesToErrors`,
564
+ `validateGridPosition`, `validateChartComponent`, `validateTableComponent`,
565
+ `validatePayloadSize`, `validateIframeDomain`, `validateComponent`,
566
+ `validateLayout` — their `ValidationError.message` quotes a payload
567
+ path (e.g. `params.data.datasets[0]`) and is addressed to whoever
568
+ produced the payload, not to the end user. The validation CARD chrome
569
+ around them (`validationError`, `validationWarning`,
570
+ `validationUnknownError`) IS localized; the end-user form messages of
571
+ `validateFieldValue` / `validateFormData` ARE localized through
572
+ `DEFAULT_VALIDATION_MESSAGES`.
573
+ - **P4 — machine tokens**: file-format acronyms, HTTP verbs, unit symbols,
574
+ ISO currency codes, and chart point property names. `DataPreviewSection`'s
575
+ `"CSV"` / `"JSON"` export-button glyphs (their `title` attributes,
576
+ `exportCsvRows` / `exportJsonRows`, ARE localized) and its `" EUR"`
577
+ currency suffix; `"ms"` after execution times and durations
578
+ (`FooterRenderer`, `StreamingUIRenderer`, `ScratchpadPanel`); and
579
+ `chartToDataTable`'s `x` / `y` / `r` column headers (the Chart.js point
580
+ property names the cells mirror — still overridable through the
581
+ `labels` parameter).
582
+ - **P5 — pure module functions with a documented render override.** No
583
+ occurrence remains: the default citation chip's tooltip used to be the
584
+ only one and is now localized (`citationViewSource`, passed through
585
+ `CitationCtx.viewSourceLabel`). The item stays in the policy for future
586
+ helpers.
587
+ - **P6 — components with their own documented `labels`/`messages` prop.**
588
+ `PresentationFeedback`'s `DEFAULT_PRESENTATION_FEEDBACK_LABELS` — the
589
+ component documents its own `labels` prop
590
+ (`<PresentationFeedback labels={{ prompt: '…' }} />`) and does not read
591
+ `MCPUIStringsProvider`. (`DEFAULT_CONNECTOR_MESSAGES`,
592
+ `DEFAULT_MACRO_RUN_MESSAGES`, `DEFAULT_VALIDATION_MESSAGES`,
593
+ `DEGRADED_PROJECTION_LABELS` and `CHART_DATA_TABLE_LABELS` are the same
594
+ kind of sanctioned default table for a `messages`/`labels` option — see
595
+ **Added**.)
596
+ - **P7 — strings the library never renders itself.** Hook-level errors
597
+ returned to the consumer (`useAction`'s `"Max retries (…) exceeded"` /
598
+ `"Action cancelled by onBefore callback"`, `useAutocomplete`'s `"Unknown
599
+ error"`); `props.onError` payloads (`"Chart rendering failed"`,
600
+ `"Component validation failed"`, `"Video failed to load"`); and
601
+ `console.*` / logger / telemetry-only strings. A consumer that reads them
602
+ chooses how to display them. One edge case: the SSR stub executor's
603
+ `"Actions not available server-side"` is an `ActionResult.error` that
604
+ `FormRenderer` would display, but a form cannot be submitted during
605
+ server rendering, so it never reaches the DOM in practice.
606
+ - **P8 — LLM-facing schema descriptions, example payloads, comments and
607
+ JSDoc.** `services/component-registry.ts` in full, and the `plugins/`
608
+ system prompts — a catalogue of JSON-schema descriptions and example
609
+ payloads shown to the model; translating it would teach the model to
610
+ emit non-English payloads.
611
+
612
+ Also out of scope (not i18n): the auto-footer's `poweredBy: 'Deposium'`
613
+ brand string (pre-existing since v1.2.0). Payload/content strings in
614
+ general (`params.title`, `action.label`, and similar) remain the
615
+ producing MCP server's responsibility — this sweep only touches the
616
+ library's own chrome.
617
+
618
+ ### Tests
619
+
620
+ - **`mcpui-strings-guard.test.ts` — AST-based chrome scanner.** Runs
621
+ `scanChrome()` (`scripts/chrome-scan.ts`) over every non-test source under
622
+ `src` and asserts zero unallowed findings. Separately asserts: every
623
+ `ALLOW_LIST` entry still matches a real finding (a fixed literal that no
624
+ longer exists fails the build instead of silently going stale); every
625
+ `SCOPED_EXCLUSIONS` function still exists in its file; every
626
+ `SANCTIONED_TABLES` entry is still exported and non-empty; every entry
627
+ across all three names the policy item (P1–P8) it invokes; and no
628
+ sanctioned table or scoped-exclusion function lets French text or a
629
+ hardcoded locale tag through. A second `describe` block carries
630
+ non-vacuity fixtures — one small source snippet per finding class the
631
+ scanner understands (JSX text, attribute literal, ternary branch,
632
+ template span, helper return, signal setter, prose array/object
633
+ property, arrays rendered item by item through `<For>` or `.map()`,
634
+ French text including single distinctive words, hardcoded locale) —
635
+ asserting the scanner flags
636
+ each one, and a matching "does not flag" fixture per skipped AST context
637
+ (`console.*`, `onError`, thrown errors).
638
+ - **`MCPUIStrings.pseudolocale.test.tsx` — runtime pseudo-localization.**
639
+ Mounts the renderers (`UIResourceRenderer` layouts — table, chart, map,
640
+ graph, form, gallery, video, code, carousel, action, artifact, modal,
641
+ error and validation states; `DataPreviewSection`; `ScratchpadPanel`
642
+ sections and statuses; `ChatPrompt`; `FormRenderer`; `StreamingUIRenderer`;
643
+ `GenerativeUIErrorBoundary`; `VerifiedText`; the agent components;
644
+ `AutocompleteDropdown`; `GraphRenderer`'s export menu;
645
+ `EditableUIResourceRenderer`) with a `<MCPUIStringsProvider>` whose
646
+ strings are `⟦key {placeholders}⟧` markers and with CJK-only payload
647
+ content, under `locale: 'fr-FR'`. Walks `document.body` including
648
+ portals and accessible attributes (`aria-label`, `title`, `alt`), and
649
+ separately asserts the tool-error card's clipboard text. Fails on any
650
+ Latin chrome text left over. It backs up the scanner's known blind spots:
651
+ prose stored under machine-looking keys such as `name`, and values passed
652
+ to another file's component props.
653
+ - **`mcpui-strings-defaults.test.ts`** pins every `DEFAULT_MCPUI_STRINGS`
654
+ value, so a default cannot drift silently.
655
+
656
+ ### Known follow-ups
657
+
658
+ - The auto-footer's hardcoded `poweredBy: 'Deposium'` brand string is not
659
+ i18n and is out of scope for this release (pre-existing since v1.2.0).
660
+ - `DataPreviewSection` appends `" EUR"` to every `currency` cell whatever
661
+ the data's actual currency (pre-existing; a currency code belongs in the
662
+ payload, not in the library).
663
+ - `macroRunToScratchpadState` passes a whole `UIComponent` as the content of
664
+ `data_preview` / `chart` / `map` sections, while those sections expect the
665
+ component's params (pre-existing since the MacroRun adapters shipped). A
666
+ table result therefore shows `previewInvalidContent` in the panel.
667
+ - Download filenames (`chart.png`, `table-*.csv`, graph export stems) stay
668
+ in English — they are files, not DOM text, so the policy treats them as
669
+ P4 machine tokens.
670
+
8
671
  ## [6.19.1] - 2026-09-16
9
672
 
10
673
  ### Fixed