@elabs-ai/components-ai 4.1.0 → 5.0.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 (527) hide show
  1. package/README.md +3 -3
  2. package/dist/{_audio-player-media-chrome-T3XVXWRZ.js → _audio-player-media-chrome.js} +1 -1
  3. package/dist/_code-block-theme.js +12 -0
  4. package/dist/_code-block-theme.js.map +1 -0
  5. package/dist/{_flow-boundary-SHNWLQG5.js → _flow-boundary.js} +1 -1
  6. package/dist/_flow-boundary.js.map +1 -0
  7. package/dist/_flow-lazy.js +8 -0
  8. package/dist/_flow-lazy.js.map +1 -0
  9. package/dist/_lazy-boundary-conformance.js +2 -0
  10. package/dist/_lazy-boundary-conformance.js.map +1 -0
  11. package/dist/_lazy-cjk.js +13 -0
  12. package/dist/_lazy-cjk.js.map +1 -0
  13. package/dist/_lazy-engine-boundary.js +9 -0
  14. package/dist/_lazy-engine-boundary.js.map +1 -0
  15. package/dist/_lazy-math.js +13 -0
  16. package/dist/_lazy-math.js.map +1 -0
  17. package/dist/_lazy-mermaid.js +12 -0
  18. package/dist/_lazy-mermaid.js.map +1 -0
  19. package/dist/_mermaid-error-panel.js +9 -0
  20. package/dist/_mermaid-error-panel.js.map +1 -0
  21. package/dist/{_persona-rive-JEG44YHX.js → _persona-rive.js} +19 -24
  22. package/dist/_persona-rive.js.map +1 -0
  23. package/dist/_streamdown-i18n.js +21 -0
  24. package/dist/_streamdown-i18n.js.map +1 -0
  25. package/dist/_streamdown-safety.js +10 -0
  26. package/dist/_streamdown-safety.js.map +1 -0
  27. package/dist/_theme-scope-store.js +13 -0
  28. package/dist/_theme-scope-store.js.map +1 -0
  29. package/dist/a2ui/a2ui-surface.js +17 -0
  30. package/dist/a2ui/a2ui-surface.js.map +1 -0
  31. package/dist/a2ui/core/catalog.generated.js +10 -0
  32. package/dist/a2ui/core/catalog.generated.js.map +1 -0
  33. package/dist/a2ui/core/complete-json.js +10 -0
  34. package/dist/a2ui/core/complete-json.js.map +1 -0
  35. package/dist/a2ui/core/index.js +37 -0
  36. package/dist/a2ui/core/index.js.map +1 -0
  37. package/dist/a2ui/core/schema.js +11 -0
  38. package/dist/a2ui/core/schema.js.map +1 -0
  39. package/dist/a2ui/core/spec.js +10 -0
  40. package/dist/a2ui/core/spec.js.map +1 -0
  41. package/dist/a2ui/core/validate.js +16 -0
  42. package/dist/a2ui/core/validate.js.map +1 -0
  43. package/dist/a2ui/index.js +54 -0
  44. package/dist/a2ui/index.js.map +1 -0
  45. package/dist/a2ui/ui-catalog.js +18 -0
  46. package/dist/a2ui/ui-catalog.js.map +1 -0
  47. package/dist/agent-event.js +10 -0
  48. package/dist/agent-event.js.map +1 -0
  49. package/dist/agent-timeline.js +11 -0
  50. package/dist/agent-timeline.js.map +1 -0
  51. package/dist/agent.js +25 -0
  52. package/dist/agent.js.map +1 -0
  53. package/dist/artifact.js +23 -0
  54. package/dist/artifact.js.map +1 -0
  55. package/dist/asset-preview.js +22 -0
  56. package/dist/asset-preview.js.map +1 -0
  57. package/dist/attachments.js +33 -0
  58. package/dist/attachments.js.map +1 -0
  59. package/dist/audio-player.js +30 -0
  60. package/dist/audio-player.js.map +1 -0
  61. package/dist/audio-visualizer.js +9 -0
  62. package/dist/audio-visualizer.js.map +1 -0
  63. package/dist/canvas.js +10 -0
  64. package/dist/canvas.js.map +1 -0
  65. package/dist/chain-of-thought.js +22 -0
  66. package/dist/chain-of-thought.js.map +1 -0
  67. package/dist/chat-greeting.js +8 -0
  68. package/dist/chat-greeting.js.map +1 -0
  69. package/dist/chat-shell.js +9 -0
  70. package/dist/chat-shell.js.map +1 -0
  71. package/dist/checkpoint.js +13 -0
  72. package/dist/checkpoint.js.map +1 -0
  73. package/dist/chunk-2XOIG5TG.js +72 -0
  74. package/dist/chunk-2XOIG5TG.js.map +1 -0
  75. package/dist/chunk-34CQ2VHM.js +172 -0
  76. package/dist/chunk-34CQ2VHM.js.map +1 -0
  77. package/dist/chunk-3TDHPXZZ.js +60 -0
  78. package/dist/chunk-3TDHPXZZ.js.map +1 -0
  79. package/dist/chunk-3XNPMDZY.js +478 -0
  80. package/dist/chunk-3XNPMDZY.js.map +1 -0
  81. package/dist/chunk-3YPT3V7L.js +56 -0
  82. package/dist/chunk-3YPT3V7L.js.map +1 -0
  83. package/dist/chunk-475UEU2Q.js +87 -0
  84. package/dist/chunk-475UEU2Q.js.map +1 -0
  85. package/dist/chunk-4CBTII2I.js +95 -0
  86. package/dist/chunk-4CBTII2I.js.map +1 -0
  87. package/dist/chunk-4XRNIZMG.js +22 -0
  88. package/dist/chunk-4XRNIZMG.js.map +1 -0
  89. package/dist/chunk-5NNWUKK5.js +2 -0
  90. package/dist/chunk-5NNWUKK5.js.map +1 -0
  91. package/dist/chunk-5UOMU27Z.js +164 -0
  92. package/dist/chunk-5UOMU27Z.js.map +1 -0
  93. package/dist/chunk-5VGYZ62I.js +264 -0
  94. package/dist/chunk-5VGYZ62I.js.map +1 -0
  95. package/dist/chunk-6IQPH2OS.js +21 -0
  96. package/dist/chunk-6IQPH2OS.js.map +1 -0
  97. package/dist/chunk-6PB2MXCI.js +482 -0
  98. package/dist/chunk-6PB2MXCI.js.map +1 -0
  99. package/dist/chunk-7FD5CZEU.js +215 -0
  100. package/dist/chunk-7FD5CZEU.js.map +1 -0
  101. package/dist/chunk-7RL6KZDF.js +266 -0
  102. package/dist/chunk-7RL6KZDF.js.map +1 -0
  103. package/dist/chunk-ADOYAGGK.js +482 -0
  104. package/dist/chunk-ADOYAGGK.js.map +1 -0
  105. package/dist/chunk-AXMNJMMH.js +984 -0
  106. package/dist/chunk-AXMNJMMH.js.map +1 -0
  107. package/dist/chunk-BA3AEMOU.js +21 -0
  108. package/dist/chunk-BA3AEMOU.js.map +1 -0
  109. package/dist/chunk-BBHFOYML.js +336 -0
  110. package/dist/chunk-BBHFOYML.js.map +1 -0
  111. package/dist/chunk-BHUVISIY.js +221 -0
  112. package/dist/chunk-BHUVISIY.js.map +1 -0
  113. package/dist/chunk-BM2LWERA.js +100 -0
  114. package/dist/chunk-BM2LWERA.js.map +1 -0
  115. package/dist/chunk-CMOYXNPJ.js +211 -0
  116. package/dist/chunk-CMOYXNPJ.js.map +1 -0
  117. package/dist/chunk-CZP3RHC4.js +74 -0
  118. package/dist/chunk-CZP3RHC4.js.map +1 -0
  119. package/dist/chunk-DDG5JHQT.js +112 -0
  120. package/dist/chunk-DDG5JHQT.js.map +1 -0
  121. package/dist/chunk-DJODXO7I.js +38 -0
  122. package/dist/chunk-DJODXO7I.js.map +1 -0
  123. package/dist/chunk-DWJDJLPU.js +192 -0
  124. package/dist/chunk-DWJDJLPU.js.map +1 -0
  125. package/dist/chunk-DZW6LM22.js +45 -0
  126. package/dist/chunk-DZW6LM22.js.map +1 -0
  127. package/dist/chunk-ELGCZLZF.js +305 -0
  128. package/dist/chunk-ELGCZLZF.js.map +1 -0
  129. package/dist/chunk-EV3U6ZZB.js +209 -0
  130. package/dist/chunk-EV3U6ZZB.js.map +1 -0
  131. package/dist/chunk-EY7LVUKQ.js +87 -0
  132. package/dist/chunk-EY7LVUKQ.js.map +1 -0
  133. package/dist/chunk-F4C74F7M.js +230 -0
  134. package/dist/chunk-F4C74F7M.js.map +1 -0
  135. package/dist/chunk-FERYDFZA.js +108 -0
  136. package/dist/chunk-FERYDFZA.js.map +1 -0
  137. package/dist/chunk-FUIYG2DS.js +166 -0
  138. package/dist/chunk-FUIYG2DS.js.map +1 -0
  139. package/dist/chunk-GKYZT3LA.js +136 -0
  140. package/dist/chunk-GKYZT3LA.js.map +1 -0
  141. package/dist/chunk-H42LRUQ4.js +102 -0
  142. package/dist/chunk-H42LRUQ4.js.map +1 -0
  143. package/dist/chunk-HVXM2G5F.js +222 -0
  144. package/dist/chunk-HVXM2G5F.js.map +1 -0
  145. package/dist/chunk-I4RDAGVY.js +53 -0
  146. package/dist/chunk-I4RDAGVY.js.map +1 -0
  147. package/dist/chunk-IQJG4IDX.js +208 -0
  148. package/dist/chunk-IQJG4IDX.js.map +1 -0
  149. package/dist/chunk-IX2PXG4Y.js +144 -0
  150. package/dist/chunk-IX2PXG4Y.js.map +1 -0
  151. package/dist/chunk-IXICSOOP.js +287 -0
  152. package/dist/chunk-IXICSOOP.js.map +1 -0
  153. package/dist/chunk-JY5HSN4D.js +84 -0
  154. package/dist/chunk-JY5HSN4D.js.map +1 -0
  155. package/dist/chunk-LMNSBOBG.js +19 -0
  156. package/dist/chunk-LMNSBOBG.js.map +1 -0
  157. package/dist/chunk-MBI7D6IL.js +42 -0
  158. package/dist/chunk-MBI7D6IL.js.map +1 -0
  159. package/dist/chunk-MLFMYCPH.js +192 -0
  160. package/dist/chunk-MLFMYCPH.js.map +1 -0
  161. package/dist/chunk-MOK66734.js +33 -0
  162. package/dist/chunk-MOK66734.js.map +1 -0
  163. package/dist/chunk-MXTAWPYM.js +196 -0
  164. package/dist/chunk-MXTAWPYM.js.map +1 -0
  165. package/dist/chunk-NCL2UZKI.js +96 -0
  166. package/dist/chunk-NCL2UZKI.js.map +1 -0
  167. package/dist/chunk-NET4YLRY.js +107 -0
  168. package/dist/chunk-NET4YLRY.js.map +1 -0
  169. package/dist/chunk-NIG67CFM.js +329 -0
  170. package/dist/chunk-NIG67CFM.js.map +1 -0
  171. package/dist/chunk-NMVNNO3L.js +10 -0
  172. package/dist/chunk-NMVNNO3L.js.map +1 -0
  173. package/dist/chunk-NP7VID4K.js +2 -0
  174. package/dist/chunk-NP7VID4K.js.map +1 -0
  175. package/dist/chunk-NPUCMAG2.js +382 -0
  176. package/dist/chunk-NPUCMAG2.js.map +1 -0
  177. package/dist/chunk-NSCLRHES.js +102 -0
  178. package/dist/chunk-NSCLRHES.js.map +1 -0
  179. package/dist/chunk-OEUXHVFW.js +86 -0
  180. package/dist/chunk-OEUXHVFW.js.map +1 -0
  181. package/dist/chunk-ONBHFIMZ.js +137 -0
  182. package/dist/chunk-ONBHFIMZ.js.map +1 -0
  183. package/dist/chunk-OPH2HHL2.js +228 -0
  184. package/dist/chunk-OPH2HHL2.js.map +1 -0
  185. package/dist/chunk-OQERHIQP.js +287 -0
  186. package/dist/chunk-OQERHIQP.js.map +1 -0
  187. package/dist/chunk-P6YJAZFG.js +103 -0
  188. package/dist/chunk-P6YJAZFG.js.map +1 -0
  189. package/dist/chunk-PG7ERDOD.js +65 -0
  190. package/dist/chunk-PG7ERDOD.js.map +1 -0
  191. package/dist/chunk-PIB5JBMA.js +90 -0
  192. package/dist/chunk-PIB5JBMA.js.map +1 -0
  193. package/dist/chunk-PQ5BPIWQ.js +22 -0
  194. package/dist/chunk-PQ5BPIWQ.js.map +1 -0
  195. package/dist/chunk-PQBF6ZIQ.js +55 -0
  196. package/dist/chunk-PQBF6ZIQ.js.map +1 -0
  197. package/dist/chunk-PTWCTSZL.js +173 -0
  198. package/dist/chunk-PTWCTSZL.js.map +1 -0
  199. package/dist/chunk-QJUNPVOS.js +101 -0
  200. package/dist/chunk-QJUNPVOS.js.map +1 -0
  201. package/dist/chunk-QUDZOTRV.js +43 -0
  202. package/dist/chunk-QUDZOTRV.js.map +1 -0
  203. package/dist/chunk-RB7G65JC.js +213 -0
  204. package/dist/chunk-RB7G65JC.js.map +1 -0
  205. package/dist/chunk-RPHAOR7P.js +45 -0
  206. package/dist/chunk-RPHAOR7P.js.map +1 -0
  207. package/dist/chunk-RTCSH2AN.js +331 -0
  208. package/dist/chunk-RTCSH2AN.js.map +1 -0
  209. package/dist/chunk-S3ISPW3A.js +28 -0
  210. package/dist/chunk-S3ISPW3A.js.map +1 -0
  211. package/dist/chunk-SEVN656N.js +36 -0
  212. package/dist/chunk-SEVN656N.js.map +1 -0
  213. package/dist/chunk-SKVG672B.js +64 -0
  214. package/dist/chunk-SKVG672B.js.map +1 -0
  215. package/dist/chunk-SPQIU7GA.js +236 -0
  216. package/dist/chunk-SPQIU7GA.js.map +1 -0
  217. package/dist/chunk-SSGJ6MEM.js +1076 -0
  218. package/dist/chunk-SSGJ6MEM.js.map +1 -0
  219. package/dist/chunk-SSKZ2AED.js +118 -0
  220. package/dist/chunk-SSKZ2AED.js.map +1 -0
  221. package/dist/chunk-T56JFGXI.js +561 -0
  222. package/dist/chunk-T56JFGXI.js.map +1 -0
  223. package/dist/chunk-TCB6O74A.js +137 -0
  224. package/dist/chunk-TCB6O74A.js.map +1 -0
  225. package/dist/chunk-TTEYPO6X.js +57 -0
  226. package/dist/chunk-TTEYPO6X.js.map +1 -0
  227. package/dist/chunk-TWPHAJNM.js +462 -0
  228. package/dist/chunk-TWPHAJNM.js.map +1 -0
  229. package/dist/chunk-TXJJLP4O.js +336 -0
  230. package/dist/chunk-TXJJLP4O.js.map +1 -0
  231. package/dist/chunk-TZ6TB7VM.js +245 -0
  232. package/dist/chunk-TZ6TB7VM.js.map +1 -0
  233. package/dist/chunk-UBLKJJ7K.js +278 -0
  234. package/dist/chunk-UBLKJJ7K.js.map +1 -0
  235. package/dist/chunk-UCJ6IUFS.js +52 -0
  236. package/dist/chunk-UCJ6IUFS.js.map +1 -0
  237. package/dist/chunk-UCKFJ3QF.js +197 -0
  238. package/dist/chunk-UCKFJ3QF.js.map +1 -0
  239. package/dist/chunk-UPXSLILN.js +20 -0
  240. package/dist/chunk-UPXSLILN.js.map +1 -0
  241. package/dist/chunk-V4DORGWJ.js +92 -0
  242. package/dist/chunk-V4DORGWJ.js.map +1 -0
  243. package/dist/chunk-V6E5DYZD.js +80 -0
  244. package/dist/chunk-V6E5DYZD.js.map +1 -0
  245. package/dist/chunk-VLLYKF3P.js +96 -0
  246. package/dist/chunk-VLLYKF3P.js.map +1 -0
  247. package/dist/chunk-VLNBTOH6.js +43 -0
  248. package/dist/chunk-VLNBTOH6.js.map +1 -0
  249. package/dist/chunk-WMDSMSPL.js +303 -0
  250. package/dist/chunk-WMDSMSPL.js.map +1 -0
  251. package/dist/chunk-WU5BLGTF.js +149 -0
  252. package/dist/chunk-WU5BLGTF.js.map +1 -0
  253. package/dist/chunk-WYTZVSGK.js +245 -0
  254. package/dist/chunk-WYTZVSGK.js.map +1 -0
  255. package/dist/chunk-X47QMM6K.js +187 -0
  256. package/dist/chunk-X47QMM6K.js.map +1 -0
  257. package/dist/chunk-XNBKO32Y.js +174 -0
  258. package/dist/chunk-XNBKO32Y.js.map +1 -0
  259. package/dist/chunk-YHUSW7D2.js +40 -0
  260. package/dist/chunk-YHUSW7D2.js.map +1 -0
  261. package/dist/chunk-YSKD7UXX.js +126 -0
  262. package/dist/chunk-YSKD7UXX.js.map +1 -0
  263. package/dist/chunk-YYRDGWOU.js +72 -0
  264. package/dist/chunk-YYRDGWOU.js.map +1 -0
  265. package/dist/chunk-ZIGZMWM7.js +19 -0
  266. package/dist/chunk-ZIGZMWM7.js.map +1 -0
  267. package/dist/chunk-ZKRTUKNP.js +40 -0
  268. package/dist/chunk-ZKRTUKNP.js.map +1 -0
  269. package/dist/code-block.js +38 -0
  270. package/dist/code-block.js.map +1 -0
  271. package/dist/commit.js +51 -0
  272. package/dist/commit.js.map +1 -0
  273. package/dist/composer.js +15 -0
  274. package/dist/composer.js.map +1 -0
  275. package/dist/confirmation.js +53 -0
  276. package/dist/confirmation.js.map +1 -0
  277. package/dist/connection.js +8 -0
  278. package/dist/connection.js.map +1 -0
  279. package/dist/context-panel.js +25 -0
  280. package/dist/context-panel.js.map +1 -0
  281. package/dist/controls.js +10 -0
  282. package/dist/controls.js.map +1 -0
  283. package/dist/conversation.js +19 -0
  284. package/dist/conversation.js.map +1 -0
  285. package/dist/diff-view.js +15 -0
  286. package/dist/diff-view.js.map +1 -0
  287. package/dist/edge.js +10 -0
  288. package/dist/edge.js.map +1 -0
  289. package/dist/environment-variables.js +29 -0
  290. package/dist/environment-variables.js.map +1 -0
  291. package/dist/file-tree.js +23 -0
  292. package/dist/file-tree.js.map +1 -0
  293. package/dist/gallery.js +9 -0
  294. package/dist/gallery.js.map +1 -0
  295. package/dist/grouped-parts.js +21 -0
  296. package/dist/grouped-parts.js.map +1 -0
  297. package/dist/image.js +9 -0
  298. package/dist/image.js.map +1 -0
  299. package/dist/index.d.ts +403 -108
  300. package/dist/index.js +671 -13205
  301. package/dist/index.js.map +1 -1
  302. package/dist/inline-citation.js +39 -0
  303. package/dist/inline-citation.js.map +1 -0
  304. package/dist/jsx-preview.js +17 -0
  305. package/dist/jsx-preview.js.map +1 -0
  306. package/dist/markdown-view.js +17 -0
  307. package/dist/markdown-view.js.map +1 -0
  308. package/dist/message-compare.js +12 -0
  309. package/dist/message-compare.js.map +1 -0
  310. package/dist/message-edit.js +19 -0
  311. package/dist/message-edit.js.map +1 -0
  312. package/dist/message-feedback.js +11 -0
  313. package/dist/message-feedback.js.map +1 -0
  314. package/dist/message-form-spec.js +38 -0
  315. package/dist/message-form-spec.js.map +1 -0
  316. package/dist/message-form.js +26 -0
  317. package/dist/message-form.js.map +1 -0
  318. package/dist/message-table-spec.js +26 -0
  319. package/dist/message-table-spec.js.map +1 -0
  320. package/dist/message-table.js +12 -0
  321. package/dist/message-table.js.map +1 -0
  322. package/dist/message.js +51 -0
  323. package/dist/message.js.map +1 -0
  324. package/dist/mic-selector.js +27 -0
  325. package/dist/mic-selector.js.map +1 -0
  326. package/dist/model-provider-logo.js +13 -0
  327. package/dist/model-provider-logo.js.map +1 -0
  328. package/dist/motion-config.js +9 -0
  329. package/dist/motion-config.js.map +1 -0
  330. package/dist/node.js +22 -0
  331. package/dist/node.js.map +1 -0
  332. package/dist/open-in-chat.js +31 -0
  333. package/dist/open-in-chat.js.map +1 -0
  334. package/dist/package-info.js +25 -0
  335. package/dist/package-info.js.map +1 -0
  336. package/dist/panel.js +10 -0
  337. package/dist/panel.js.map +1 -0
  338. package/dist/part-groups.js +18 -0
  339. package/dist/part-groups.js.map +1 -0
  340. package/dist/permission-mode-select.js +9 -0
  341. package/dist/permission-mode-select.js.map +1 -0
  342. package/dist/persona-sources.js +8 -0
  343. package/dist/persona-sources.js.map +1 -0
  344. package/dist/persona.js +14 -0
  345. package/dist/persona.js.map +1 -0
  346. package/dist/plan.js +32 -0
  347. package/dist/plan.js.map +1 -0
  348. package/dist/prompt-input-effort.js +9 -0
  349. package/dist/prompt-input-effort.js.map +1 -0
  350. package/dist/prompt-input-mode.js +10 -0
  351. package/dist/prompt-input-mode.js.map +1 -0
  352. package/dist/prompt-input-slash.js +12 -0
  353. package/dist/prompt-input-slash.js.map +1 -0
  354. package/dist/prompt-input.js +89 -0
  355. package/dist/prompt-input.js.map +1 -0
  356. package/dist/queue.js +37 -0
  357. package/dist/queue.js.map +1 -0
  358. package/dist/reasoning.js +23 -0
  359. package/dist/reasoning.js.map +1 -0
  360. package/dist/sandbox.js +28 -0
  361. package/dist/sandbox.js.map +1 -0
  362. package/dist/schema-display.js +33 -0
  363. package/dist/schema-display.js.map +1 -0
  364. package/dist/selection-toolbar.js +11 -0
  365. package/dist/selection-toolbar.js.map +1 -0
  366. package/dist/session-header.js +9 -0
  367. package/dist/session-header.js.map +1 -0
  368. package/dist/session-status-bar.js +9 -0
  369. package/dist/session-status-bar.js.map +1 -0
  370. package/dist/shimmer.js +9 -0
  371. package/dist/shimmer.js.map +1 -0
  372. package/dist/snippet.js +17 -0
  373. package/dist/snippet.js.map +1 -0
  374. package/dist/sources.js +17 -0
  375. package/dist/sources.js.map +1 -0
  376. package/dist/speech-input.js +9 -0
  377. package/dist/speech-input.js.map +1 -0
  378. package/dist/stack-trace.js +27 -0
  379. package/dist/stack-trace.js.map +1 -0
  380. package/dist/suggestion.js +16 -0
  381. package/dist/suggestion.js.map +1 -0
  382. package/dist/task.js +18 -0
  383. package/dist/task.js.map +1 -0
  384. package/dist/test-results.js +43 -0
  385. package/dist/test-results.js.map +1 -0
  386. package/dist/token-usage.js +27 -0
  387. package/dist/token-usage.js.map +1 -0
  388. package/dist/tool-result-card.js +9 -0
  389. package/dist/tool-result-card.js.map +1 -0
  390. package/dist/tool.js +27 -0
  391. package/dist/tool.js.map +1 -0
  392. package/dist/toolbar.js +10 -0
  393. package/dist/toolbar.js.map +1 -0
  394. package/dist/transcription.js +11 -0
  395. package/dist/transcription.js.map +1 -0
  396. package/dist/turn-status.js +9 -0
  397. package/dist/turn-status.js.map +1 -0
  398. package/dist/use-audio-level.js +9 -0
  399. package/dist/use-audio-level.js.map +1 -0
  400. package/dist/voice-selector.js +47 -0
  401. package/dist/voice-selector.js.map +1 -0
  402. package/dist/web-preview.js +19 -0
  403. package/dist/web-preview.js.map +1 -0
  404. package/package.json +12 -10
  405. package/schemas/a2ui-surface.v1.schema.json +5425 -0
  406. package/src/__contract__/audio-visualizer.contract.test.tsx +49 -0
  407. package/src/__contract__/chat-shell.contract.test.tsx +49 -0
  408. package/src/__contract__/grouped-parts.contract.test.tsx +49 -0
  409. package/src/__contract__/image.contract.test.tsx +49 -0
  410. package/src/__contract__/markdown-view.contract.test.tsx +49 -0
  411. package/src/__contract__/message-feedback.contract.test.tsx +49 -0
  412. package/src/__contract__/message-form.contract.test.tsx +49 -0
  413. package/src/__contract__/message-table.contract.test.tsx +49 -0
  414. package/src/__contract__/model-provider-logo.contract.test.tsx +49 -0
  415. package/src/__contract__/persona.contract.test.tsx +49 -0
  416. package/src/__contract__/prompt-input-effort.contract.test.tsx +49 -0
  417. package/src/__contract__/prompt-input-mode.contract.test.tsx +49 -0
  418. package/src/_chat-shell-rail.tsx +4 -2
  419. package/src/_flow-boundary.tsx +2 -2
  420. package/src/_lazy-cjk.test.ts +43 -0
  421. package/src/_lazy-cjk.ts +73 -0
  422. package/src/_lazy-math.test.ts +63 -0
  423. package/src/_lazy-math.ts +90 -0
  424. package/src/_persona-rive.tsx +27 -41
  425. package/src/_streamdown-i18n.ts +73 -21
  426. package/src/_streamdown-safety.ts +2 -2
  427. package/src/_theme-scope-store.test.ts +83 -0
  428. package/src/_theme-scope-store.ts +103 -0
  429. package/src/a2ui/a2ui-surface.stories.tsx +662 -0
  430. package/src/a2ui/a2ui-surface.test.tsx +201 -0
  431. package/src/a2ui/a2ui-surface.tsx +354 -0
  432. package/src/a2ui/catalog.source.json +772 -0
  433. package/src/a2ui/core/a2ui-core.test.ts +178 -0
  434. package/src/a2ui/core/catalog.generated.ts +1006 -0
  435. package/src/a2ui/core/complete-json.ts +81 -0
  436. package/src/a2ui/core/index.ts +10 -0
  437. package/src/a2ui/core/schema.ts +111 -0
  438. package/src/a2ui/core/spec.ts +148 -0
  439. package/src/a2ui/core/validate.ts +266 -0
  440. package/src/a2ui/index.ts +7 -0
  441. package/src/a2ui/ui-catalog.tsx +225 -0
  442. package/src/a2ui/wave1-chart-spec.test.ts +87 -0
  443. package/src/a2ui/wave2-chart-spec.test.ts +108 -0
  444. package/src/agent-event.tsx +1 -1
  445. package/src/agent.tsx +18 -13
  446. package/src/agentic-workspace.stories.tsx +6 -45
  447. package/src/artifact.tsx +2 -2
  448. package/src/asset-preview.test.tsx +40 -0
  449. package/src/asset-preview.tsx +83 -12
  450. package/src/attachments.tsx +7 -4
  451. package/src/chat-shell.test.tsx +40 -10
  452. package/src/chat-shell.tsx +90 -44
  453. package/src/chat.stories.tsx +47 -42
  454. package/src/code-block.test.tsx +100 -1
  455. package/src/code-block.tsx +166 -103
  456. package/src/commit.tsx +30 -41
  457. package/src/composer.stories.tsx +4 -5
  458. package/src/composer.test.tsx +45 -0
  459. package/src/composer.tsx +104 -21
  460. package/src/confirmation.tsx +1 -1
  461. package/src/context-panel.stories.tsx +1 -1
  462. package/src/context-panel.tsx +2 -2
  463. package/src/conversation.stories.tsx +24 -0
  464. package/src/conversation.test.tsx +69 -0
  465. package/src/conversation.tsx +47 -8
  466. package/src/diff-view.stories.tsx +79 -0
  467. package/src/diff-view.test.tsx +52 -2
  468. package/src/diff-view.tsx +92 -36
  469. package/src/environment-variables.tsx +20 -37
  470. package/src/file-tree.test.tsx +21 -0
  471. package/src/file-tree.tsx +33 -16
  472. package/src/gallery.stories.tsx +42 -0
  473. package/src/gallery.tsx +2 -2
  474. package/src/grouped-parts.stories.tsx +23 -0
  475. package/src/grouped-parts.tsx +1 -1
  476. package/src/index.ts +1 -0
  477. package/src/inline-citation.tsx +5 -5
  478. package/src/jsx-preview.tsx +151 -43
  479. package/src/markdown-view.test.tsx +7 -3
  480. package/src/markdown-view.tsx +8 -1
  481. package/src/message-compare.stories.tsx +40 -0
  482. package/src/message-compare.tsx +2 -1
  483. package/src/message-form.stories.tsx +36 -3
  484. package/src/message-form.test.tsx +8 -2
  485. package/src/message-form.tsx +16 -8
  486. package/src/message-table.stories.tsx +26 -2
  487. package/src/message-table.test.tsx +7 -0
  488. package/src/message-table.tsx +9 -5
  489. package/src/message.test.tsx +73 -3
  490. package/src/message.tsx +36 -12
  491. package/src/model-provider-logo.test.tsx +57 -3
  492. package/src/model-provider-logo.tsx +45 -11
  493. package/src/open-in-chat.tsx +50 -29
  494. package/src/package-info.tsx +23 -20
  495. package/src/prompt-input-slash.stories.tsx +1 -1
  496. package/src/prompt-input.test.tsx +67 -1
  497. package/src/prompt-input.tsx +42 -4
  498. package/src/queue.tsx +4 -4
  499. package/src/reasoning.stories.tsx +27 -0
  500. package/src/reasoning.tsx +17 -6
  501. package/src/sandbox.tsx +3 -3
  502. package/src/schema-display.test.tsx +56 -0
  503. package/src/schema-display.tsx +68 -37
  504. package/src/session-header.stories.tsx +35 -1
  505. package/src/session-header.tsx +1 -1
  506. package/src/snippet.test.tsx +6 -2
  507. package/src/snippet.tsx +14 -34
  508. package/src/sources.stories.tsx +30 -0
  509. package/src/sources.tsx +4 -1
  510. package/src/speech-input.test.tsx +109 -0
  511. package/src/speech-input.tsx +31 -3
  512. package/src/stack-trace.tsx +20 -36
  513. package/src/streamdown-i18n.test.tsx +23 -0
  514. package/src/templates-ai-assistant.stories.tsx +151 -87
  515. package/src/test-results.tsx +47 -27
  516. package/src/token-usage.tsx +6 -6
  517. package/src/tool.stories.tsx +39 -0
  518. package/src/tool.test.tsx +65 -0
  519. package/src/tool.tsx +61 -25
  520. package/src/toolbar.tsx +0 -10
  521. package/src/transcription.tsx +1 -1
  522. package/src/voice-selector.tsx +5 -5
  523. package/src/web-preview.test.tsx +51 -1
  524. package/src/web-preview.tsx +39 -8
  525. package/dist/_flow-boundary-SHNWLQG5.js.map +0 -1
  526. package/dist/_persona-rive-JEG44YHX.js.map +0 -1
  527. /package/dist/{_audio-player-media-chrome-T3XVXWRZ.js.map → _audio-player-media-chrome.js.map} +0 -0
@@ -0,0 +1,201 @@
1
+ import { fireEvent, render, screen } from "@testing-library/react";
2
+ import { describe, expect, it, vi } from "vitest";
3
+
4
+ import { A2uiSurface } from "./a2ui-surface";
5
+ import { A2UI_CATALOG_SCHEMA } from "./core/catalog.generated";
6
+ import type { A2uiSurfaceSpec } from "./core/spec";
7
+ import { UI_CATALOG_BINDINGS, createA2uiCatalog, defineA2uiType, uiCatalog } from "./ui-catalog";
8
+
9
+ const ORDER: A2uiSurfaceSpec = {
10
+ a2ui: "1",
11
+ title: "Order 4711",
12
+ root: {
13
+ type: "Card",
14
+ id: "order",
15
+ children: [
16
+ {
17
+ type: "CardHeader",
18
+ children: [
19
+ { type: "CardTitle", children: ["Order 4711"] },
20
+ { type: "CardDescription", children: ["Placed today"] },
21
+ ],
22
+ },
23
+ {
24
+ type: "CardContent",
25
+ children: [
26
+ {
27
+ type: "Grid",
28
+ props: { columns: 2 },
29
+ children: [
30
+ { type: "MetricCard", props: { label: "Total", value: "€ 1,240" } },
31
+ { type: "StatusBadge", props: { status: "running" } },
32
+ ],
33
+ },
34
+ {
35
+ type: "SectionHeader",
36
+ props: { title: "Lines", actions: { type: "Badge", children: ["3 items"] } },
37
+ },
38
+ ],
39
+ },
40
+ {
41
+ type: "CardFooter",
42
+ children: [
43
+ {
44
+ type: "Button",
45
+ props: { variant: "default" },
46
+ on: { click: { name: "approve", payload: { id: 4711 } } },
47
+ children: ["Approve"],
48
+ },
49
+ {
50
+ type: "Input",
51
+ props: { "aria-label": "Note", defaultValue: "" },
52
+ on: { change: { name: "note" } },
53
+ },
54
+ ],
55
+ },
56
+ ],
57
+ },
58
+ };
59
+
60
+ describe("A2uiSurface", () => {
61
+ it("renders a valid surface with the real components and forwards actions", () => {
62
+ const onAction = vi.fn();
63
+ render(<A2uiSurface surface={ORDER} onAction={onAction} />);
64
+ const region = screen.getByRole("group", { name: "Order 4711" });
65
+ expect(region).toHaveAttribute("data-status", "ready");
66
+ expect(
67
+ screen.getByText("Order 4711", { selector: "[data-slot='card-title']" }),
68
+ ).toBeInTheDocument();
69
+ expect(screen.getByText("€ 1,240")).toBeInTheDocument();
70
+ expect(screen.getByText("3 items")).toBeInTheDocument(); // a node inside a `node` prop
71
+ expect(region.querySelector("[data-a2ui-id='order']")).toHaveAttribute(
72
+ "data-a2ui-type",
73
+ "Card",
74
+ );
75
+
76
+ fireEvent.click(screen.getByRole("button", { name: "Approve" }));
77
+ expect(onAction).toHaveBeenCalledWith(
78
+ { name: "approve", payload: { id: 4711 } },
79
+ expect.objectContaining({ event: "click", path: "root.children[2].children[0]" }),
80
+ );
81
+ fireEvent.change(screen.getByRole("textbox", { name: "Note" }), { target: { value: "ok" } });
82
+ expect(onAction).toHaveBeenLastCalledWith(
83
+ { name: "note" },
84
+ expect.objectContaining({ event: "change", value: "ok" }),
85
+ );
86
+ });
87
+
88
+ it("settled + invalid → role=alert with every problem, onError once; nothing drawn", () => {
89
+ const onError = vi.fn();
90
+ const { rerender } = render(
91
+ <A2uiSurface
92
+ surface={{
93
+ a2ui: "1",
94
+ root: {
95
+ type: "Stack",
96
+ children: [{ type: "Sparkle" }, { type: "Button", props: { variant: "loud" } }],
97
+ },
98
+ }}
99
+ onError={onError}
100
+ />,
101
+ );
102
+ const alert = screen.getByRole("alert");
103
+ expect(alert).toHaveTextContent("root.children[0].type");
104
+ expect(alert).toHaveTextContent("root.children[1].props.variant");
105
+ expect(screen.queryByRole("button")).toBeNull();
106
+ expect(onError).toHaveBeenCalledTimes(1);
107
+ expect(onError.mock.calls[0]![0]).toHaveLength(2);
108
+ rerender(
109
+ <A2uiSurface
110
+ surface={{
111
+ a2ui: "1",
112
+ root: {
113
+ type: "Stack",
114
+ children: [{ type: "Sparkle" }, { type: "Button", props: { variant: "loud" } }],
115
+ },
116
+ }}
117
+ onError={onError}
118
+ />,
119
+ );
120
+ expect(onError).toHaveBeenCalledTimes(1);
121
+ });
122
+
123
+ it("streams: a JSON prefix builds up, invalid-yet nodes are pruned, no alert", () => {
124
+ const full = JSON.stringify({
125
+ a2ui: "1",
126
+ root: {
127
+ type: "Stack",
128
+ children: [
129
+ { type: "Heading", props: { level: 2 }, children: ["Revenue"] },
130
+ { type: "Text", children: ["Up 12% quarter over quarter."] },
131
+ { type: "Button", props: { variant: "outline" }, children: ["Details"] },
132
+ ],
133
+ },
134
+ });
135
+ const cut = full.indexOf('"Up 12%') + 6;
136
+ const { rerender, container } = render(
137
+ <A2uiSurface surface={full.slice(0, cut)} isStreaming />,
138
+ );
139
+ expect(screen.getByRole("heading", { name: "Revenue" })).toBeInTheDocument();
140
+ expect(screen.getByText("Up 12")).toBeInTheDocument(); // the half-arrived text node
141
+ expect(screen.queryByRole("button")).toBeNull();
142
+ expect(screen.queryByRole("alert")).toBeNull();
143
+ expect(container.querySelector("[data-slot='a2ui-surface']")).toHaveAttribute(
144
+ "data-status",
145
+ "pending",
146
+ );
147
+
148
+ // A prefix that ends inside the Button's props — the Button is pruned, siblings stay.
149
+ const cut2 = full.indexOf('"variant":"out') + 12;
150
+ rerender(<A2uiSurface surface={full.slice(0, cut2)} isStreaming />);
151
+ expect(screen.getByRole("heading", { name: "Revenue" })).toBeInTheDocument();
152
+ expect(screen.queryByRole("button")).toBeNull();
153
+
154
+ rerender(<A2uiSurface surface={full} isStreaming={false} />);
155
+ expect(screen.getByRole("button", { name: "Details" })).toBeInTheDocument();
156
+ expect(container.querySelector("[data-slot='a2ui-surface']")).toHaveAttribute(
157
+ "data-status",
158
+ "ready",
159
+ );
160
+ });
161
+
162
+ it("loading → skeleton with a polite live region; empty → idle, nothing", () => {
163
+ const { container, rerender } = render(<A2uiSurface surface={null} loading />);
164
+ expect(screen.getByRole("status")).toHaveTextContent("Loading surface…");
165
+ rerender(<A2uiSurface surface={null} />);
166
+ expect(screen.queryByRole("status")).toBeNull();
167
+ expect(container.querySelector("[data-slot='a2ui-surface']")).toHaveAttribute(
168
+ "data-status",
169
+ "idle",
170
+ );
171
+ });
172
+
173
+ it("an app extends the catalog with its own type", () => {
174
+ const Sparkle = ({ level }: { level: string }) => <output>sparkle:{level}</output>;
175
+ const catalog = createA2uiCatalog(
176
+ { ...UI_CATALOG_BINDINGS, Sparkle },
177
+ {
178
+ ...A2UI_CATALOG_SCHEMA,
179
+ Sparkle: defineA2uiType({
180
+ props: { level: { type: "string", enum: ["lo", "hi"], required: true } },
181
+ }),
182
+ },
183
+ );
184
+ render(
185
+ <A2uiSurface
186
+ catalog={catalog}
187
+ surface={{ a2ui: "1", root: { type: "Sparkle", props: { level: "hi" } } }}
188
+ />,
189
+ );
190
+ expect(screen.getByText("sparkle:hi")).toBeInTheDocument();
191
+ });
192
+
193
+ it("the shipped catalog binds exactly the generated schema's non-builtin types", () => {
194
+ const schemaTypes = Object.entries(A2UI_CATALOG_SCHEMA)
195
+ .filter(([, s]) => !s.builtin)
196
+ .map(([t]) => t)
197
+ .sort();
198
+ expect(Object.keys(UI_CATALOG_BINDINGS).sort()).toEqual(schemaTypes);
199
+ expect(Object.keys(uiCatalog).sort()).toEqual(Object.keys(A2UI_CATALOG_SCHEMA).sort());
200
+ });
201
+ });
@@ -0,0 +1,354 @@
1
+ "use client";
2
+
3
+ /**
4
+ * A2uiSurface — render an agent-DESIGNED screen from data (D2's safe generative
5
+ * path). The agent emits an `A2uiSurfaceSpec` (JSON: a tree of catalog nodes);
6
+ * this component validates it against the catalog and draws it with the real
7
+ * components — never `eval`, never arbitrary tags, never agent styling.
8
+ *
9
+ * Readiness follows the house model (loading-states): `loading` → skeleton;
10
+ * `isStreaming` (or a string surface that is still incomplete JSON) → build up,
11
+ * pruning only the nodes that do not validate YET and suppressing errors; a
12
+ * settled surface that fails validation → `role="alert"` with every problem and
13
+ * `onError`. Interaction is data too: a node's `on.<event>` names a host action
14
+ * that arrives at `onAction` — the app decides what "approve" means (D5).
15
+ */
16
+ import { Skeleton, useLocale } from "@elabs-ai/components-ui";
17
+ import { cn } from "@elabs-ai/components-ui/lib/cn";
18
+ import { AlertCircle } from "lucide-react";
19
+ import type { ComponentProps, ReactNode, SyntheticEvent } from "react";
20
+ import { createElement, forwardRef, memo, useEffect, useMemo, useRef, useState } from "react";
21
+
22
+ import { completeJson, parseSurfaceJson } from "./core/complete-json";
23
+ import type {
24
+ A2uiAction,
25
+ A2uiElement,
26
+ A2uiError,
27
+ A2uiNode,
28
+ A2uiPropValue,
29
+ A2uiSurfaceSpec,
30
+ } from "./core/spec";
31
+ import { invalidNodePaths, isA2uiElement, validateA2uiSurface } from "./core/validate";
32
+ import { catalogSchema, uiCatalog, type A2uiCatalog } from "./ui-catalog";
33
+
34
+ export type A2uiSurfaceStatus = "idle" | "pending" | "ready" | "error";
35
+
36
+ /** What `onAction` receives besides the action the agent named. */
37
+ export interface A2uiActionContext {
38
+ /** The catalog event that fired (`click`, `change`, …). */
39
+ event: string;
40
+ /** The control's value for `change` events (text, checked, selected value…). */
41
+ value?: unknown;
42
+ /** The node that carried the binding, and its path in the spec. */
43
+ node: A2uiElement;
44
+ path: string;
45
+ }
46
+
47
+ export type A2uiActionHandler = (action: A2uiAction, context: A2uiActionContext) => void;
48
+
49
+ export type A2uiSurfaceProps = Omit<ComponentProps<"div">, "children" | "onError"> & {
50
+ /** The agent's surface: a parsed spec, or its JSON text (may be a streaming prefix). */
51
+ surface: A2uiSurfaceSpec | string | null | undefined;
52
+ /** Which types render, and how. Defaults to the shipped `uiCatalog`. */
53
+ catalog?: A2uiCatalog;
54
+ /** Receives every `on.<event>` binding the user triggers. */
55
+ onAction?: A2uiActionHandler;
56
+ /** Content is arriving incrementally — build up, suppress transient errors. */
57
+ isStreaming?: boolean;
58
+ /** No surface yet (fetch-then-show) — forces the skeleton. */
59
+ loading?: boolean;
60
+ /** Fires once per settled, invalid surface with the validation errors. */
61
+ onError?: (errors: A2uiError[]) => void;
62
+ /** Replace the default skeleton (shown before the first paint while pending). */
63
+ skeleton?: ReactNode;
64
+ };
65
+
66
+ // ---------------------------------------------------------------------------
67
+ // Builtins — the two layout nodes the surface draws itself, on tokens only.
68
+ // ---------------------------------------------------------------------------
69
+ const GAP: Record<string, string> = {
70
+ none: "gap-0",
71
+ xs: "gap-1",
72
+ sm: "gap-2",
73
+ md: "gap-4",
74
+ lg: "gap-6",
75
+ };
76
+ const ALIGN: Record<string, string> = {
77
+ start: "items-start",
78
+ center: "items-center",
79
+ end: "items-end",
80
+ stretch: "items-stretch",
81
+ };
82
+ const JUSTIFY: Record<string, string> = {
83
+ start: "justify-start",
84
+ center: "justify-center",
85
+ end: "justify-end",
86
+ between: "justify-between",
87
+ };
88
+ const COLUMNS: Record<number, string> = {
89
+ 1: "grid-cols-1",
90
+ 2: "grid-cols-1 sm:grid-cols-2",
91
+ 3: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3",
92
+ 4: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-4",
93
+ };
94
+
95
+ function builtinClass(type: string, props: Record<string, unknown>): string {
96
+ const gap = GAP[String(props.gap ?? "md")] ?? GAP.md;
97
+ if (type === "Grid") {
98
+ return cn("grid", COLUMNS[Number(props.columns ?? 2)] ?? COLUMNS[2], gap);
99
+ }
100
+ return cn(
101
+ "flex min-w-0",
102
+ props.direction === "row" ? "flex-row" : "flex-col",
103
+ props.wrap ? "flex-wrap" : "",
104
+ ALIGN[String(props.align ?? "stretch")],
105
+ JUSTIFY[String(props.justify ?? "start")],
106
+ gap,
107
+ );
108
+ }
109
+
110
+ // ---------------------------------------------------------------------------
111
+ // Tree rendering
112
+ // ---------------------------------------------------------------------------
113
+ interface RenderCtx {
114
+ catalog: A2uiCatalog;
115
+ invalid: Set<string>;
116
+ onAction?: A2uiActionHandler;
117
+ }
118
+
119
+ const isEvent = (v: unknown): v is SyntheticEvent =>
120
+ typeof v === "object" && v !== null && "nativeEvent" in v && "target" in v;
121
+
122
+ /** The value a `change`-style handler carries: the control's value, or the handler's first arg. */
123
+ function eventValue(args: unknown[]): unknown {
124
+ const first = args[0];
125
+ if (isEvent(first)) {
126
+ const target = first.target as HTMLInputElement | null;
127
+ if (target && typeof target.type === "string" && target.type === "checkbox")
128
+ return target.checked;
129
+ return target && "value" in target ? target.value : undefined;
130
+ }
131
+ return first;
132
+ }
133
+
134
+ function renderPropValue(value: A2uiPropValue, path: string, ctx: RenderCtx): unknown {
135
+ if (Array.isArray(value) && value.some(isA2uiElement)) {
136
+ return value.map((item, i) => renderNode(item as A2uiNode, `${path}[${i}]`, ctx));
137
+ }
138
+ if (isA2uiElement(value)) return renderNode(value, path, ctx);
139
+ return value;
140
+ }
141
+
142
+ function renderNode(node: A2uiNode, path: string, ctx: RenderCtx): ReactNode {
143
+ if (typeof node === "string" || typeof node === "number") return node;
144
+ if (ctx.invalid.has(path)) return null;
145
+ const entry = ctx.catalog[node.type];
146
+ if (!entry) return null;
147
+ const props: Record<string, unknown> = {};
148
+ for (const [name, value] of Object.entries(node.props ?? {})) {
149
+ const schema = entry.schema.props[name];
150
+ props[name] =
151
+ schema?.type === "node" ? renderPropValue(value, `${path}.props.${name}`, ctx) : value;
152
+ }
153
+ for (const [event, action] of Object.entries(node.on ?? {})) {
154
+ const handlerName = entry.schema.events[event];
155
+ if (!handlerName) continue;
156
+ props[handlerName] = (...args: unknown[]) => {
157
+ // A control's value travels only with `change`-style events; a click
158
+ // carries no value (a button's `target.value` is an empty string).
159
+ const value = event === "click" ? undefined : eventValue(args);
160
+ ctx.onAction?.(action, { event, value, node, path });
161
+ };
162
+ }
163
+ const children = entry.schema.children
164
+ ? (node.children ?? []).map((child, i) => renderNode(child, `${path}.children[${i}]`, ctx))
165
+ : undefined;
166
+ const key = path;
167
+ const dataAttrs = {
168
+ "data-a2ui-type": node.type,
169
+ ...(node.id ? { "data-a2ui-id": node.id } : {}),
170
+ };
171
+ if (entry.schema.builtin) {
172
+ return createElement(
173
+ "div",
174
+ { key, className: builtinClass(node.type, props), ...dataAttrs },
175
+ ...(children ?? []),
176
+ );
177
+ }
178
+ return createElement(
179
+ entry.component as never,
180
+ { key, ...props, ...dataAttrs },
181
+ ...(children ?? []),
182
+ );
183
+ }
184
+
185
+ /** A memoised subtree so a streaming parent re-render does not redraw settled siblings. */
186
+ const SurfaceTree = memo(function SurfaceTree({
187
+ spec,
188
+ catalog,
189
+ invalid,
190
+ onAction,
191
+ }: {
192
+ spec: A2uiSurfaceSpec;
193
+ catalog: A2uiCatalog;
194
+ invalid: Set<string>;
195
+ onAction?: A2uiActionHandler;
196
+ }) {
197
+ return <>{renderNode(spec.root, "root", { catalog, invalid, onAction })}</>;
198
+ });
199
+
200
+ // ---------------------------------------------------------------------------
201
+ // The surface
202
+ // ---------------------------------------------------------------------------
203
+ export const A2uiSurface = forwardRef<HTMLDivElement, A2uiSurfaceProps>(function A2uiSurface(
204
+ {
205
+ surface,
206
+ catalog = uiCatalog,
207
+ onAction,
208
+ isStreaming = false,
209
+ loading = false,
210
+ onError,
211
+ skeleton,
212
+ className,
213
+ ...props
214
+ },
215
+ ref,
216
+ ) {
217
+ const { t } = useLocale();
218
+ const schema = useMemo(() => catalogSchema(catalog), [catalog]);
219
+
220
+ // Parse (completing a streaming prefix), then validate. A string that is not
221
+ // yet valid JSON even after completion is "incomplete", not invalid.
222
+ const parsed = useMemo(() => {
223
+ if (loading || surface == null) return { value: null, incomplete: false };
224
+ if (typeof surface !== "string") return { value: surface, incomplete: false };
225
+ const complete = parseSurfaceJson(surface, false);
226
+ if (complete !== null) return { value: complete, incomplete: false };
227
+ const partial = parseSurfaceJson(surface, true);
228
+ return { value: partial, incomplete: surface.trim() !== "" };
229
+ }, [surface, loading]);
230
+
231
+ const validation = useMemo(
232
+ () => (parsed.value === null ? null : validateA2uiSurface(parsed.value, schema)),
233
+ [parsed.value, schema],
234
+ );
235
+
236
+ const pending = loading || isStreaming || parsed.incomplete;
237
+ const empty = surface == null || (typeof surface === "string" && surface.trim() === "");
238
+
239
+ // While pending, prune the failing subtrees and draw the rest; a settled
240
+ // surface is all-or-nothing.
241
+ const invalid = useMemo(
242
+ () =>
243
+ validation && !validation.ok && pending
244
+ ? invalidNodePaths(validation.errors)
245
+ : new Set<string>(),
246
+ [validation, pending],
247
+ );
248
+ const drawable =
249
+ validation?.spec && (validation.ok || (pending && !invalid.has("root")))
250
+ ? validation.spec
251
+ : null;
252
+
253
+ // The last tree that painted, so an unparsable streaming chunk (mid-token) or
254
+ // a momentarily root-invalid prefix keeps the previous frame on screen.
255
+ const lastGoodRef = useRef<A2uiSurfaceSpec | null>(null);
256
+ if (drawable) lastGoodRef.current = drawable;
257
+ const shown = drawable ?? (pending ? lastGoodRef.current : null);
258
+ const [hasRendered, setHasRendered] = useState(false);
259
+ useEffect(() => {
260
+ if (shown && !hasRendered) setHasRendered(true);
261
+ }, [shown, hasRendered]);
262
+
263
+ const status: A2uiSurfaceStatus = loading
264
+ ? "pending"
265
+ : empty
266
+ ? hasRendered
267
+ ? "ready"
268
+ : "idle"
269
+ : pending
270
+ ? "pending"
271
+ : validation && !validation.ok
272
+ ? "error"
273
+ : "ready";
274
+
275
+ // Terminal errors only — once per settled, invalid surface.
276
+ // Keyed by the error LIST, not the input's identity, so a parent re-render
277
+ // that passes an equal object literal does not report the same failure twice.
278
+ const reportedRef = useRef<string | null>(null);
279
+ useEffect(() => {
280
+ if (status !== "error" || !validation || validation.ok) return;
281
+ const key = JSON.stringify(validation.errors);
282
+ if (reportedRef.current === key) return;
283
+ reportedRef.current = key;
284
+ onError?.(validation.errors);
285
+ }, [status, validation, onError]);
286
+
287
+ const showSkeleton = status === "pending" && !shown;
288
+ const title = shown?.title;
289
+
290
+ return (
291
+ <div
292
+ ref={ref}
293
+ data-slot="a2ui-surface"
294
+ data-status={status}
295
+ className={cn("relative min-w-0", className)}
296
+ {...(title ? { role: "group", "aria-label": title } : {})}
297
+ {...props}
298
+ >
299
+ {showSkeleton ? (
300
+ <div
301
+ data-slot="a2ui-surface-skeleton"
302
+ role="status"
303
+ aria-live="polite"
304
+ className="space-y-2"
305
+ >
306
+ <span className="sr-only">{t("ai.a2ui.loading")}</span>
307
+ {skeleton ?? (
308
+ <>
309
+ <Skeleton className="h-6 w-1/3" />
310
+ <div className="grid grid-cols-3 gap-2">
311
+ <Skeleton className="h-16" />
312
+ <Skeleton className="h-16" />
313
+ <Skeleton className="h-16" />
314
+ </div>
315
+ <Skeleton className="h-24 w-full" />
316
+ </>
317
+ )}
318
+ </div>
319
+ ) : null}
320
+ {shown ? (
321
+ <SurfaceTree spec={shown} catalog={catalog} invalid={invalid} onAction={onAction} />
322
+ ) : null}
323
+ {status === "error" && validation && !validation.ok ? (
324
+ <div
325
+ data-slot="a2ui-surface-error"
326
+ role="alert"
327
+ // The status wash is the one separation gesture (surface-separation);
328
+ // `-text` is the AA ink rung on a wash (status-rung).
329
+ className="flex gap-2 rounded-md bg-destructive/10 p-3 text-body text-destructive-text"
330
+ >
331
+ <AlertCircle className="mt-0.5 size-4 shrink-0" aria-hidden="true" />
332
+ <div className="min-w-0 space-y-1">
333
+ <p className="font-medium">{t("ai.a2ui.invalid")}</p>
334
+ <ul className="space-y-0.5 text-caption">
335
+ {validation.errors.slice(0, 8).map((e) => (
336
+ <li key={`${e.path}:${e.code}`} className="break-words">
337
+ <code className="text-code">{e.path || "root"}</code> — {e.message}
338
+ </li>
339
+ ))}
340
+ {validation.errors.length > 8 ? (
341
+ <li>{t("ai.a2ui.moreErrors", { count: validation.errors.length - 8 })}</li>
342
+ ) : null}
343
+ </ul>
344
+ </div>
345
+ </div>
346
+ ) : null}
347
+ </div>
348
+ );
349
+ });
350
+
351
+ A2uiSurface.displayName = "A2uiSurface";
352
+
353
+ /** Re-exported so a host can pre-complete a streaming prefix itself (e.g. for a raw-JSON view). */
354
+ export { completeJson };