@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
@@ -4,9 +4,10 @@
4
4
  * SolidJS hooks for managing UI resource state and streaming
5
5
  */
6
6
 
7
- export { useStreamingUI } from './useStreamingUI'
7
+ export { useStreamingUI, DEFAULT_STREAMING_UI_MESSAGES } from './useStreamingUI'
8
8
  export type {
9
9
  UseStreamingUIOptions,
10
+ StreamingUIMessages,
10
11
  StreamingUIState,
11
12
  StreamProgress,
12
13
  StreamError,
@@ -24,6 +24,7 @@
24
24
  import { createSignal, onCleanup } from 'solid-js'
25
25
  import type { UIComponent } from '../types'
26
26
  import { createLogger } from '../utils/logger'
27
+ import { formatMCPUIString } from '../utils/format-string'
27
28
 
28
29
  const logger = createLogger('useStreamingUI')
29
30
 
@@ -34,6 +35,50 @@ const isServer = typeof window === 'undefined'
34
35
  // Types
35
36
  // ============================================================================
36
37
 
38
+ /**
39
+ * Every string `useStreamingUI` puts into `progress().message` and
40
+ * `error()` — both are rendered by `<StreamingUIRenderer>`.
41
+ *
42
+ * @since v6.20.0 — `StreamingUIRenderer` feeds these from `MCPUIStrings`
43
+ * (`stream*` keys); a host calling the hook directly passes `messages`.
44
+ */
45
+ export interface StreamingUIMessages {
46
+ /** Initial progress message. */
47
+ initializing: string
48
+ /** Progress message while the request is sent. */
49
+ connecting: string
50
+ /** Progress message on `component-start`. Template — `{type}`. */
51
+ loadingComponent: string
52
+ /** Progress message on `complete`. */
53
+ dashboardLoaded: string
54
+ /** Progress message after an error. Template — `{message}`. */
55
+ errorProgress: string
56
+ /** `StreamError.error` when the fetch / read fails. */
57
+ connectionFailed: string
58
+ /** `StreamError.message` when the server answers non-OK without a message. */
59
+ requestFailed: string
60
+ /** `StreamError.message` when the response has no body. */
61
+ emptyResponse: string
62
+ /** `StreamError.message` when streaming starts during SSR. */
63
+ serverSide: string
64
+ /** `StreamError.message` when the failure carries no message. */
65
+ unknownError: string
66
+ }
67
+
68
+ /** English defaults of {@link StreamingUIMessages}. */
69
+ export const DEFAULT_STREAMING_UI_MESSAGES: StreamingUIMessages = {
70
+ initializing: 'Initializing...',
71
+ connecting: 'Connecting to server...',
72
+ loadingComponent: 'Loading {type} component...',
73
+ dashboardLoaded: 'Dashboard loaded',
74
+ errorProgress: 'Error: {message}',
75
+ connectionFailed: 'Stream connection failed',
76
+ requestFailed: 'Stream request failed',
77
+ emptyResponse: 'Response body is null',
78
+ serverSide: 'Streaming UI cannot start on server-side',
79
+ unknownError: 'Unknown error',
80
+ }
81
+
37
82
  export interface UseStreamingUIOptions {
38
83
  query: string
39
84
  spaceIds?: string[]
@@ -47,6 +92,12 @@ export interface UseStreamingUIOptions {
47
92
  onComplete?: (metadata: CompleteMetadata) => void
48
93
  onError?: (error: StreamError) => void
49
94
  onComponentReceived?: (component: UIComponent) => void
95
+ /**
96
+ * Localized progress / error messages (v6.20.0). Each key is read when
97
+ * the message is produced, so an object with getters stays reactive.
98
+ * Omitted keys fall back to {@link DEFAULT_STREAMING_UI_MESSAGES}.
99
+ */
100
+ messages?: Partial<StreamingUIMessages>
50
101
  }
51
102
 
52
103
  export interface StreamingUIState {
@@ -124,6 +175,9 @@ interface ComponentEvent {
124
175
  // ============================================================================
125
176
 
126
177
  export function useStreamingUI(options: UseStreamingUIOptions) {
178
+ const msg = (key: keyof StreamingUIMessages): string =>
179
+ options.messages?.[key] ?? DEFAULT_STREAMING_UI_MESSAGES[key]
180
+
127
181
  // State
128
182
  const [components, setComponents] = createSignal<UIComponent[]>([])
129
183
  const [isLoading, setIsLoading] = createSignal(false)
@@ -132,7 +186,7 @@ export function useStreamingUI(options: UseStreamingUIOptions) {
132
186
  const [progress, setProgress] = createSignal<StreamProgress>({
133
187
  receivedCount: 0,
134
188
  totalCount: null,
135
- message: 'Initializing...',
189
+ message: msg('initializing'),
136
190
  timestamp: new Date().toISOString(),
137
191
  })
138
192
  const [metadata, setMetadata] = createSignal<CompleteMetadata | null>(null)
@@ -194,7 +248,7 @@ export function useStreamingUI(options: UseStreamingUIOptions) {
194
248
 
195
249
  setProgress((prev) => ({
196
250
  ...prev,
197
- message: `Loading ${data.type} component...`,
251
+ message: formatMCPUIString(msg('loadingComponent'), { type: data.type }),
198
252
  timestamp: new Date().toISOString(),
199
253
  }))
200
254
  }
@@ -242,7 +296,7 @@ export function useStreamingUI(options: UseStreamingUIOptions) {
242
296
 
243
297
  setProgress((prev) => ({
244
298
  ...prev,
245
- message: 'Dashboard loaded',
299
+ message: msg('dashboardLoaded'),
246
300
  timestamp: new Date().toISOString(),
247
301
  }))
248
302
 
@@ -264,7 +318,7 @@ export function useStreamingUI(options: UseStreamingUIOptions) {
264
318
 
265
319
  setProgress((prev) => ({
266
320
  ...prev,
267
- message: `Error: ${data.message}`,
321
+ message: formatMCPUIString(msg('errorProgress'), { message: data.message }),
268
322
  timestamp: new Date().toISOString(),
269
323
  }))
270
324
 
@@ -325,7 +379,7 @@ export function useStreamingUI(options: UseStreamingUIOptions) {
325
379
  logger.warn('startStreaming called on server-side - skipping')
326
380
  setError({
327
381
  error: 'ssr',
328
- message: 'Streaming UI cannot start on server-side',
382
+ message: msg('serverSide'),
329
383
  recoverable: false,
330
384
  })
331
385
  setIsLoading(false)
@@ -343,7 +397,7 @@ export function useStreamingUI(options: UseStreamingUIOptions) {
343
397
  setProgress({
344
398
  receivedCount: 0,
345
399
  totalCount: null,
346
- message: 'Connecting to server...',
400
+ message: msg('connecting'),
347
401
  timestamp: new Date().toISOString(),
348
402
  })
349
403
 
@@ -372,11 +426,11 @@ export function useStreamingUI(options: UseStreamingUIOptions) {
372
426
  .then(async (response) => {
373
427
  if (!response.ok) {
374
428
  const errorData = await response.json()
375
- throw new Error(errorData.message || 'Stream request failed')
429
+ throw new Error(errorData.message || msg('requestFailed'))
376
430
  }
377
431
 
378
432
  if (!response.body) {
379
- throw new Error('Response body is null')
433
+ throw new Error(msg('emptyResponse'))
380
434
  }
381
435
 
382
436
  const reader = response.body.getReader()
@@ -423,8 +477,8 @@ export function useStreamingUI(options: UseStreamingUIOptions) {
423
477
  })
424
478
 
425
479
  handleErrorEvent({
426
- error: 'Stream connection failed',
427
- message: err instanceof Error ? err.message : 'Unknown error',
480
+ error: msg('connectionFailed'),
481
+ message: err instanceof Error ? err.message : msg('unknownError'),
428
482
  recoverable: true,
429
483
  })
430
484
  })
package/src/index.ts CHANGED
@@ -134,12 +134,26 @@ export {
134
134
  MCPUIStringsContext,
135
135
  useMCPUIStrings,
136
136
  DEFAULT_MCPUI_STRINGS,
137
+ formatMCPUIString,
138
+ resolveMCPUILocale,
137
139
  } from './context/MCPUIStringsContext'
138
140
  export type {
139
141
  MCPUIStrings,
140
142
  MCPUIStringsProviderProps,
141
143
  } from './context/MCPUIStringsContext'
142
144
 
145
+ // Host rendering policy — iframe `credentialless` / COEP (v6.21.0)
146
+ export {
147
+ MCPUIConfigProvider,
148
+ MCPUIConfigContext,
149
+ useMCPUIConfig,
150
+ DEFAULT_MCPUI_CONFIG,
151
+ } from './context/MCPUIConfigContext'
152
+ export type {
153
+ MCPUIConfig,
154
+ MCPUIConfigProviderProps,
155
+ } from './context/MCPUIConfigContext'
156
+
143
157
  // Telemetry sink (B.5 — v5.6.0)
144
158
  export {
145
159
  MCPUITelemetryProvider,
@@ -177,6 +191,7 @@ export type { AutocompleteFormFieldProps, AutocompleteFormFieldParams } from './
177
191
  // Hooks
178
192
  export {
179
193
  useStreamingUI,
194
+ DEFAULT_STREAMING_UI_MESSAGES,
180
195
  useAction,
181
196
  useToolAction,
182
197
  useConditionalField,
@@ -195,6 +210,7 @@ export {
195
210
 
196
211
  export type {
197
212
  UseStreamingUIOptions,
213
+ StreamingUIMessages,
198
214
  StreamingUIState,
199
215
  StreamProgress,
200
216
  StreamError,
@@ -327,6 +343,9 @@ export {
327
343
  validateLayout,
328
344
  validateIframeDomain,
329
345
  getIframeSandbox,
346
+ // v6.21.0 — the trusted-host test behind `getIframeSandbox` and the
347
+ // `credentialless` iframe attribute.
348
+ isTrustedIframeDomain,
330
349
  DEFAULT_RESOURCE_LIMITS,
331
350
  DEFAULT_IFRAME_DOMAINS,
332
351
  TRUSTED_IFRAME_DOMAINS,
@@ -338,6 +357,25 @@ export {
338
357
  validateAgainstSource,
339
358
  } from './services'
340
359
 
360
+ // i18n of the pure helpers (v6.20.0) — English baselines a host spreads and
361
+ // overrides when it renders in another language. The helpers themselves take
362
+ // the table as a trailing optional parameter, so nothing else changes.
363
+ export {
364
+ validateFieldValue,
365
+ validateFormData,
366
+ DEFAULT_VALIDATION_MESSAGES,
367
+ } from './services/validation'
368
+ export type { FormValidationMessages } from './services/validation'
369
+ export {
370
+ graphToDegradedTable,
371
+ mapToDegradedTable,
372
+ chartToDegradedTable,
373
+ DEGRADED_PROJECTION_LABELS,
374
+ } from './utils/degraded-projections'
375
+ export type { DegradedProjectionLabels } from './utils/degraded-projections'
376
+ export { chartToDataTable, CHART_DATA_TABLE_LABELS } from './components/chart-data-table'
377
+ export type { ChartDataTableLabels } from './components/chart-data-table'
378
+
341
379
  // Clarification → Prompt helper (v4.3.9)
342
380
  export { clarificationToPromptConfig } from './services/chat-bus'
343
381
 
@@ -9,6 +9,7 @@ export {
9
9
  validateLayout,
10
10
  validateIframeDomain,
11
11
  getIframeSandbox,
12
+ isTrustedIframeDomain,
12
13
  DEFAULT_RESOURCE_LIMITS,
13
14
  DEFAULT_IFRAME_DOMAINS,
14
15
  TRUSTED_IFRAME_DOMAINS,
@@ -42,6 +42,9 @@ import type {
42
42
  ValidationOptions,
43
43
  ComponentType,
44
44
  } from '../types';
45
+ // Runtime-free template helper: `src/validation.ts` (the SSR-safe subpath)
46
+ // re-exports this module, so it must stay free of any solid-js import.
47
+ import { formatMCPUIString } from '../utils/format-string';
45
48
 
46
49
  /**
47
50
  * All known ComponentType values — used to distinguish known-but-unvalidated
@@ -686,6 +689,42 @@ export function validateIframeDomain(
686
689
  }
687
690
  }
688
691
 
692
+ /**
693
+ * Is this URL's host a trusted iframe host?
694
+ *
695
+ * A host matches when it equals an entry of `TRUSTED_IFRAME_DOMAINS` (plus
696
+ * `options.customTrustedDomains`) or is a subdomain of one. Trusted hosts
697
+ * need their own cookies/storage to authenticate, which is why they receive
698
+ * `allow-same-origin` in `getIframeSandbox` and deliberately do NOT receive
699
+ * the `credentialless` iframe attribute (an authenticated embed loaded
700
+ * without its cookies only shows a login screen).
701
+ *
702
+ * An unparsable URL is never trusted.
703
+ *
704
+ * @param url - The iframe URL
705
+ * @param options - Optional custom trusted domains, merged over the defaults
706
+ * @returns `true` when the URL's host is trusted
707
+ * @since v6.21.0 — extracted from `getIframeSandbox`, identical matching.
708
+ */
709
+ export function isTrustedIframeDomain(
710
+ url: string,
711
+ options?: { customTrustedDomains?: string[] }
712
+ ): boolean {
713
+ try {
714
+ const domain = new URL(url).hostname;
715
+ const trustedList = options?.customTrustedDomains
716
+ ? [...TRUSTED_IFRAME_DOMAINS, ...options.customTrustedDomains]
717
+ : TRUSTED_IFRAME_DOMAINS;
718
+
719
+ return trustedList.some(
720
+ (trusted) => domain === trusted || domain.endsWith(`.${trusted}`)
721
+ );
722
+ } catch {
723
+ // Invalid URL — never trusted.
724
+ return false;
725
+ }
726
+ }
727
+
689
728
  /**
690
729
  * Get the appropriate sandbox attribute for an iframe URL.
691
730
  *
@@ -704,22 +743,8 @@ export function getIframeSandbox(
704
743
  ): string {
705
744
  const baseSandbox = 'allow-scripts allow-popups';
706
745
 
707
- try {
708
- const domain = new URL(url).hostname;
709
- let trustedList = TRUSTED_IFRAME_DOMAINS;
710
- if (options?.customTrustedDomains) {
711
- trustedList = [...TRUSTED_IFRAME_DOMAINS, ...options.customTrustedDomains];
712
- }
713
-
714
- const isTrusted = trustedList.some(
715
- (trusted) => domain === trusted || domain.endsWith(`.${trusted}`)
716
- );
717
-
718
- if (isTrusted) {
719
- return `${baseSandbox} allow-same-origin allow-forms`;
720
- }
721
- } catch {
722
- // Invalid URL — use restrictive sandbox
746
+ if (isTrustedIframeDomain(url, options)) {
747
+ return `${baseSandbox} allow-same-origin allow-forms`;
723
748
  }
724
749
 
725
750
  return baseSandbox;
@@ -919,20 +944,85 @@ export function validateLayout(layout: UILayout, options?: ValidationOptions): V
919
944
  };
920
945
  }
921
946
 
947
+ /**
948
+ * The end-user form-validation wording.
949
+ *
950
+ * `validateFieldValue` / `validateFormData` are pure and SSR-safe (they are
951
+ * re-exported from the `@seed-ship/mcp-ui-solid/validation` subpath), so
952
+ * they cannot read `MCPUIStrings`. The wording is injected instead: both
953
+ * take an optional trailing `messages` partial, and `FormRenderer` feeds it
954
+ * from `useMCPUIStrings()` (keys `fieldRequired`, `fieldMinLength`, …).
955
+ *
956
+ * The STRUCTURAL validators (`validateComponent`, `validateLayout`, …) keep
957
+ * their English `ValidationError.message`: those are payload diagnostics
958
+ * addressed to whoever produced the payload, not end-user chrome.
959
+ *
960
+ * @since v6.20.0
961
+ */
962
+ export interface FormValidationMessages {
963
+ /** Template \u2014 `{field}`. */
964
+ required: string;
965
+ /** Template \u2014 `{field}`. */
966
+ mustBeChecked: string;
967
+ /** Template \u2014 `{min}`. */
968
+ minLength: string;
969
+ /** Template \u2014 `{max}`. */
970
+ maxLength: string;
971
+ /** Value does not match `field.pattern`. */
972
+ invalidPattern: string;
973
+ /** Value is not a valid email address. */
974
+ invalidEmail: string;
975
+ /** Value is not a number. */
976
+ invalidNumber: string;
977
+ /** Template \u2014 `{min}`. */
978
+ minValue: string;
979
+ /** Template \u2014 `{max}`. */
980
+ maxValue: string;
981
+ /** Template \u2014 `{min}`. */
982
+ minDate: string;
983
+ /** Template \u2014 `{max}`. */
984
+ maxDate: string;
985
+ /** Value is not one of `field.options`. */
986
+ invalidOption: string;
987
+ /** Template \u2014 `{format}`. `field.valueFormatHint` still wins. */
988
+ invalidFormat: string;
989
+ }
990
+
991
+ /** English defaults for {@link FormValidationMessages}. */
992
+ export const DEFAULT_VALIDATION_MESSAGES: FormValidationMessages = {
993
+ required: '{field} is required',
994
+ mustBeChecked: '{field} must be checked',
995
+ minLength: 'Minimum {min} characters required',
996
+ maxLength: 'Maximum {max} characters allowed',
997
+ invalidPattern: 'Invalid format',
998
+ invalidEmail: 'Invalid email address',
999
+ invalidNumber: 'Must be a valid number',
1000
+ minValue: 'Minimum value is {min}',
1001
+ maxValue: 'Maximum value is {max}',
1002
+ minDate: 'Date must be after {min}',
1003
+ maxDate: 'Date must be before {max}',
1004
+ invalidOption: 'Please select a valid option',
1005
+ invalidFormat: 'Invalid format (expected: {format})',
1006
+ };
1007
+
922
1008
  /**
923
1009
  * Validate a single form field value against field rules
924
1010
  */
925
1011
  export function validateFieldValue(
926
1012
  value: any,
927
- field: FormFieldParams
1013
+ field: FormFieldParams,
1014
+ messages?: Partial<FormValidationMessages>
928
1015
  ): { valid: boolean; error?: string } {
1016
+ const m: FormValidationMessages = { ...DEFAULT_VALIDATION_MESSAGES, ...messages };
1017
+ const fieldName = field.label || field.name;
1018
+
929
1019
  // Required check
930
1020
  if (field.required) {
931
1021
  if (value === undefined || value === null || value === '') {
932
- return { valid: false, error: `${field.label || field.name} is required` };
1022
+ return { valid: false, error: formatMCPUIString(m.required, { field: fieldName }) };
933
1023
  }
934
1024
  if (field.type === 'checkbox' && value !== true) {
935
- return { valid: false, error: `${field.label || field.name} must be checked` };
1025
+ return { valid: false, error: formatMCPUIString(m.mustBeChecked, { field: fieldName }) };
936
1026
  }
937
1027
  }
938
1028
 
@@ -947,42 +1037,48 @@ export function validateFieldValue(
947
1037
  case 'textarea':
948
1038
  case 'password':
949
1039
  if (field.minLength && String(value).length < field.minLength) {
950
- return { valid: false, error: `Minimum ${field.minLength} characters required` };
1040
+ return {
1041
+ valid: false,
1042
+ error: formatMCPUIString(m.minLength, { min: field.minLength }),
1043
+ };
951
1044
  }
952
1045
  if (field.maxLength && String(value).length > field.maxLength) {
953
- return { valid: false, error: `Maximum ${field.maxLength} characters allowed` };
1046
+ return {
1047
+ valid: false,
1048
+ error: formatMCPUIString(m.maxLength, { max: field.maxLength }),
1049
+ };
954
1050
  }
955
1051
  if (field.pattern && !new RegExp(field.pattern).test(String(value))) {
956
- return { valid: false, error: 'Invalid format' };
1052
+ return { valid: false, error: m.invalidPattern };
957
1053
  }
958
1054
  break;
959
1055
 
960
1056
  case 'email':
961
1057
  if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(value))) {
962
- return { valid: false, error: 'Invalid email address' };
1058
+ return { valid: false, error: m.invalidEmail };
963
1059
  }
964
1060
  break;
965
1061
 
966
1062
  case 'number': {
967
1063
  const numValue = Number(value);
968
1064
  if (isNaN(numValue)) {
969
- return { valid: false, error: 'Must be a valid number' };
1065
+ return { valid: false, error: m.invalidNumber };
970
1066
  }
971
1067
  if (field.min !== undefined && numValue < field.min) {
972
- return { valid: false, error: `Minimum value is ${field.min}` };
1068
+ return { valid: false, error: formatMCPUIString(m.minValue, { min: field.min }) };
973
1069
  }
974
1070
  if (field.max !== undefined && numValue > field.max) {
975
- return { valid: false, error: `Maximum value is ${field.max}` };
1071
+ return { valid: false, error: formatMCPUIString(m.maxValue, { max: field.max }) };
976
1072
  }
977
1073
  break;
978
1074
  }
979
1075
 
980
1076
  case 'date':
981
1077
  if (field.minDate && value < field.minDate) {
982
- return { valid: false, error: `Date must be after ${field.minDate}` };
1078
+ return { valid: false, error: formatMCPUIString(m.minDate, { min: field.minDate }) };
983
1079
  }
984
1080
  if (field.maxDate && value > field.maxDate) {
985
- return { valid: false, error: `Date must be before ${field.maxDate}` };
1081
+ return { valid: false, error: formatMCPUIString(m.maxDate, { max: field.maxDate }) };
986
1082
  }
987
1083
  break;
988
1084
 
@@ -992,7 +1088,7 @@ export function validateFieldValue(
992
1088
  if (field.options && field.options.length > 0) {
993
1089
  const validValues = field.options.map((opt) => opt.value);
994
1090
  if (!validValues.includes(String(value))) {
995
- return { valid: false, error: 'Please select a valid option' };
1091
+ return { valid: false, error: m.invalidOption };
996
1092
  }
997
1093
  }
998
1094
  break;
@@ -1005,7 +1101,9 @@ export function validateFieldValue(
1005
1101
  if (!new RegExp(field.valueFormat).test(v)) {
1006
1102
  return {
1007
1103
  valid: false,
1008
- error: field.valueFormatHint || `Invalid format (expected: ${field.valueFormat})`,
1104
+ error:
1105
+ field.valueFormatHint ||
1106
+ formatMCPUIString(m.invalidFormat, { format: field.valueFormat }),
1009
1107
  };
1010
1108
  }
1011
1109
  }
@@ -1019,12 +1117,13 @@ export function validateFieldValue(
1019
1117
  */
1020
1118
  export function validateFormData(
1021
1119
  data: Record<string, any>,
1022
- fields: FormFieldParams[]
1120
+ fields: FormFieldParams[],
1121
+ messages?: Partial<FormValidationMessages>
1023
1122
  ): { valid: boolean; errors: Record<string, string> } {
1024
1123
  const errors: Record<string, string> = {};
1025
1124
 
1026
1125
  for (const field of fields) {
1027
- const result = validateFieldValue(data[field.name], field);
1126
+ const result = validateFieldValue(data[field.name], field, messages);
1028
1127
  if (!result.valid && result.error) {
1029
1128
  errors[field.name] = result.error;
1030
1129
  }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Solid JSX augmentation — `attr:credentialless` (v6.21.0).
3
+ *
4
+ * `credentialless` is listed in dom-expressions' `Properties` set, so the
5
+ * plain `credentialless={…}` form compiles to a JS property assignment
6
+ * (`el.credentialless = v`) and never reaches the DOM as an attribute in an
7
+ * engine that does not reflect it (jsdom, and any pre-110 browser). The
8
+ * `attr:` namespace forces `setAttribute` / `ssrAttribute` instead.
9
+ *
10
+ * Solid types `attr:*` only for keys declared in `JSX.ExplicitAttributes`,
11
+ * which ships empty — hence this augmentation. The value type is
12
+ * `true | undefined` on purpose: `undefined` removes the attribute, so the
13
+ * boolean attribute is never emitted as `credentialless="false"`.
14
+ */
15
+
16
+ import 'solid-js'
17
+
18
+ declare module 'solid-js' {
19
+ namespace JSX {
20
+ interface ExplicitAttributes {
21
+ credentialless: true | undefined
22
+ }
23
+ }
24
+ }
@@ -11,6 +11,63 @@
11
11
  * map/graph.
12
12
  */
13
13
 
14
+ import { formatMCPUIString } from './format-string';
15
+
16
+ /**
17
+ * Column headers and series names of the degraded tables.
18
+ *
19
+ * The projections are pure helpers with no access to `MCPUIStrings`, so the
20
+ * wording is injected: each function takes an optional trailing `labels`
21
+ * partial, and the renderers feed it from `useMCPUIStrings()`. Left out, the
22
+ * English defaults below apply — the pre-v6.20.0 behaviour.
23
+ *
24
+ * @since v6.20.0
25
+ */
26
+ export interface DegradedProjectionLabels {
27
+ /** Graph edge table — source column. */
28
+ source: string;
29
+ /** Graph edge table — target column. */
30
+ target: string;
31
+ /** Graph edge / node table — label column. */
32
+ label: string;
33
+ /** Graph node table — node id column. */
34
+ node: string;
35
+ /** Map table — feature type column. */
36
+ type: string;
37
+ /** Map table — latitude column. */
38
+ lat: string;
39
+ /** Map table — longitude column. */
40
+ lng: string;
41
+ /** Map table — property summary column. */
42
+ info: string;
43
+ /** Map table — `Type` cell of a marker row (v6.20.0, 6th pass). */
44
+ marker: string;
45
+ /** Map table — `Type` cell of a GeoJSON feature without a geometry type (v6.20.0). */
46
+ feature: string;
47
+ /** Chart table — fallback dataset name. Template — `{n}`. */
48
+ series: string;
49
+ }
50
+
51
+ /** English defaults for {@link DegradedProjectionLabels}. */
52
+ export const DEGRADED_PROJECTION_LABELS: DegradedProjectionLabels = {
53
+ source: 'Source',
54
+ target: 'Target',
55
+ label: 'Label',
56
+ node: 'Node',
57
+ type: 'Type',
58
+ lat: 'Lat',
59
+ lng: 'Lng',
60
+ info: 'Info',
61
+ marker: 'marker',
62
+ feature: 'feature',
63
+ series: 'Series {n}',
64
+ };
65
+
66
+ const withLabels = (labels?: Partial<DegradedProjectionLabels>): DegradedProjectionLabels => ({
67
+ ...DEGRADED_PROJECTION_LABELS,
68
+ ...labels,
69
+ });
70
+
14
71
  export interface DegradedTable {
15
72
  columns: string[];
16
73
  rows: Array<Array<string | number>>;
@@ -41,11 +98,12 @@ function cell(value: unknown): string {
41
98
  export function graphToDegradedTable(params: {
42
99
  nodes?: Array<{ id: string; label?: string }>;
43
100
  edges?: Array<{ source: string; target: string; label?: string; weight?: number }>;
44
- }): DegradedTable {
101
+ }, labels?: Partial<DegradedProjectionLabels>): DegradedTable {
102
+ const l = withLabels(labels);
45
103
  const edges = params.edges ?? [];
46
104
  if (edges.length > 0) {
47
105
  return {
48
- columns: ['Source', 'Target', 'Label'],
106
+ columns: [l.source, l.target, l.label],
49
107
  rows: edges.slice(0, MAX_PROJECTED_ROWS).map((e) => {
50
108
  const label = [e.weight != null ? String(e.weight) : '', e.label ?? '']
51
109
  .filter(Boolean)
@@ -56,7 +114,7 @@ export function graphToDegradedTable(params: {
56
114
  }
57
115
  const nodes = params.nodes ?? [];
58
116
  return {
59
- columns: ['Node', 'Label'],
117
+ columns: [l.node, l.label],
60
118
  rows: nodes.slice(0, MAX_PROJECTED_ROWS).map((n) => [cell(n.id), cell(n.label ?? n.id)]),
61
119
  };
62
120
  }
@@ -91,13 +149,14 @@ export function mapToDegradedTable(params: {
91
149
  popup?: string;
92
150
  }>;
93
151
  geojson?: unknown;
94
- }): DegradedTable {
152
+ }, labels?: Partial<DegradedProjectionLabels>): DegradedTable {
153
+ const l = withLabels(labels);
95
154
  const rows: Array<Array<string | number>> = [];
96
155
 
97
156
  for (const m of params.markers ?? []) {
98
157
  const lat = Array.isArray(m.position) ? m.position[0] : m.position?.lat;
99
158
  const lng = Array.isArray(m.position) ? m.position[1] : m.position?.lng;
100
- rows.push(['marker', cell(lat), cell(lng), cell(m.tooltip ?? m.popup ?? '')]);
159
+ rows.push([l.marker, cell(lat), cell(lng), cell(m.tooltip ?? m.popup ?? '')]);
101
160
  }
102
161
 
103
162
  const fc = params.geojson as { features?: GeoJSONLikeFeature[] } | undefined;
@@ -110,7 +169,7 @@ export function mapToDegradedTable(params: {
110
169
  .map((k) => `${k}=${cell(props[k])}`)
111
170
  .join(', ');
112
171
  rows.push([
113
- cell(f.geometry?.type ?? 'feature'),
172
+ cell(f.geometry?.type ?? l.feature),
114
173
  ll ? cell(ll[1]) : '',
115
174
  ll ? cell(ll[0]) : '',
116
175
  propSummary,
@@ -118,7 +177,7 @@ export function mapToDegradedTable(params: {
118
177
  }
119
178
 
120
179
  return {
121
- columns: ['Type', 'Lat', 'Lng', 'Info'],
180
+ columns: [l.type, l.lat, l.lng, l.info],
122
181
  rows: rows.slice(0, MAX_PROJECTED_ROWS),
123
182
  };
124
183
  }
@@ -135,15 +194,19 @@ export function chartToDegradedTable(params: {
135
194
  labels?: Array<string | number>;
136
195
  datasets?: Array<{ label?: string; data?: unknown[] }>;
137
196
  };
138
- }): DegradedTable {
197
+ }, labels?: Partial<DegradedProjectionLabels>): DegradedTable {
198
+ const l = withLabels(labels);
139
199
  const datasets = params.data?.datasets ?? [];
140
- const labels = params.data?.labels ?? [];
141
- const rowCount = Math.max(labels.length, ...datasets.map((d) => d.data?.length ?? 0), 0);
200
+ const dataLabels = params.data?.labels ?? [];
201
+ const rowCount = Math.max(dataLabels.length, ...datasets.map((d) => d.data?.length ?? 0), 0);
142
202
 
143
- const columns = ['', ...datasets.map((d, i) => d.label ?? `Series ${i + 1}`)];
203
+ const columns = [
204
+ '',
205
+ ...datasets.map((d, i) => d.label ?? formatMCPUIString(l.series, { n: i + 1 })),
206
+ ];
144
207
  const rows: Array<Array<string | number>> = [];
145
208
  for (let r = 0; r < Math.min(rowCount, MAX_PROJECTED_ROWS); r++) {
146
- rows.push([cell(labels[r] ?? r + 1), ...datasets.map((d) => cell(d.data?.[r]))]);
209
+ rows.push([cell(dataLabels[r] ?? r + 1), ...datasets.map((d) => cell(d.data?.[r]))]);
147
210
  }
148
211
  return { columns, rows };
149
212
  }