@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
@@ -116,6 +116,8 @@ export interface MessageTableProps extends Omit<HTMLAttributes<HTMLDivElement>,
116
116
  /** Per-cell render override for custom composition. */
117
117
  renderCell?: RenderCell;
118
118
  /** The spec is still streaming (columns-but-no-rows shows skeleton rows). */
119
+ isStreaming?: boolean;
120
+ /** @deprecated Use `isStreaming`. */
119
121
  streaming?: boolean;
120
122
  }
121
123
 
@@ -145,12 +147,14 @@ export const MessageTable = forwardRef<HTMLDivElement, MessageTableProps>(functi
145
147
  onSortChange,
146
148
  maxRows,
147
149
  renderCell,
150
+ isStreaming,
148
151
  streaming = false,
149
152
  className,
150
153
  ...props
151
154
  },
152
155
  ref,
153
156
  ) {
157
+ const resolvedStreaming = isStreaming ?? streaming;
154
158
  const { t } = useLocale();
155
159
  const reactId = useId();
156
160
  const isControlled = sortProp !== undefined;
@@ -181,7 +185,7 @@ export const MessageTable = forwardRef<HTMLDivElement, MessageTableProps>(functi
181
185
 
182
186
  // No columns: skeleton while streaming, otherwise a fallback.
183
187
  if (columns.length === 0) {
184
- if (streaming) {
188
+ if (resolvedStreaming) {
185
189
  return (
186
190
  <div ref={ref} className={cn("w-full", className)} {...props}>
187
191
  <span className="sr-only" role="status" aria-live="polite">
@@ -256,7 +260,7 @@ export const MessageTable = forwardRef<HTMLDivElement, MessageTableProps>(functi
256
260
  </p>
257
261
  )}
258
262
  <div className="overflow-hidden rounded-md border border-border">
259
- {streaming && cappedRows.length === 0 && (
263
+ {resolvedStreaming && cappedRows.length === 0 && (
260
264
  <span className="sr-only" role="status" aria-live="polite">
261
265
  Loading table…
262
266
  </span>
@@ -264,7 +268,7 @@ export const MessageTable = forwardRef<HTMLDivElement, MessageTableProps>(functi
264
268
  <Table
265
269
  aria-labelledby={titleId}
266
270
  aria-label={title ? undefined : t("ai.messageTable.label")}
267
- aria-busy={(streaming && cappedRows.length === 0) || undefined}
271
+ aria-busy={(resolvedStreaming && cappedRows.length === 0) || undefined}
268
272
  >
269
273
  {truncatedCount > 0 && (
270
274
  <TableCaption className="mb-2 mt-3 px-3 text-caption">
@@ -297,7 +301,7 @@ export const MessageTable = forwardRef<HTMLDivElement, MessageTableProps>(functi
297
301
  onClick={() => toggleSort(column)}
298
302
  className={cn(
299
303
  "inline-flex items-center gap-1 rounded-sm font-medium",
300
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background",
304
+ "focus-ring",
301
305
  "hover:text-foreground transition-colors duration-fast ease-standard motion-reduce:transition-none",
302
306
  numeric && "flex-row-reverse",
303
307
  )}
@@ -322,7 +326,7 @@ export const MessageTable = forwardRef<HTMLDivElement, MessageTableProps>(functi
322
326
  </TableRow>
323
327
  </TableHeader>
324
328
  <TableBody>
325
- {cappedRows.length === 0 && streaming ? (
329
+ {cappedRows.length === 0 && resolvedStreaming ? (
326
330
  [0, 1, 2].map((i) => (
327
331
  <TableRow key={`skeleton-${i}`} aria-hidden="true">
328
332
  {columns.map((column) => (
@@ -2,7 +2,7 @@ import { math } from "@streamdown/math";
2
2
  import { useState } from "react";
3
3
  import type { ComponentProps } from "react";
4
4
  import { describe, expect, it, vi } from "vitest";
5
- import { render, screen } from "@testing-library/react";
5
+ import { render, screen, waitFor } from "@testing-library/react";
6
6
  import userEvent from "@testing-library/user-event";
7
7
  import {
8
8
  AgentMessage,
@@ -185,8 +185,47 @@ describe("MessageResponse loading (#269, loading-states.md)", () => {
185
185
  });
186
186
  });
187
187
 
188
+ describe("MessageResponse memo comparator covers every prop, not a hand-picked field list (perf review §2)", () => {
189
+ it("re-renders when only `className` changes (children/isAnimating/loading held constant)", () => {
190
+ const { container, rerender } = render(
191
+ <MessageResponse className="response-a">{"same content"}</MessageResponse>,
192
+ );
193
+ // Streamdown's own root `<div>` does not forward `data-slot` (it spreads
194
+ // rest props onto the inner markdown renderer, not its wrapper div), so
195
+ // key off the fixed `size-full` class `MessageResponse` always applies.
196
+ const responseEl = () => container.querySelector(".size-full");
197
+ expect(responseEl()).toHaveClass("response-a");
198
+
199
+ rerender(<MessageResponse className="response-b">{"same content"}</MessageResponse>);
200
+
201
+ // Under the old comparator (children/isAnimating/loading only), this
202
+ // rerender was skipped entirely — the DOM kept `response-a` even though
203
+ // the caller supplied a new `className`.
204
+ expect(responseEl()).toHaveClass("response-b");
205
+ expect(responseEl()).not.toHaveClass("response-a");
206
+ });
207
+
208
+ it("re-renders when only `mode` changes (children/isAnimating/loading held constant)", () => {
209
+ // `mode="streaming"` (the default) leniently auto-closes incomplete
210
+ // markdown syntax; `mode="static"` renders it literally. A real,
211
+ // discriminating, upstream-independent signal for this prop.
212
+ const { container, rerender } = render(
213
+ <MessageResponse mode="streaming">{"**bold"}</MessageResponse>,
214
+ );
215
+ expect(container.querySelector('[data-streamdown="strong"]')).toBeInTheDocument();
216
+
217
+ rerender(<MessageResponse mode="static">{"**bold"}</MessageResponse>);
218
+
219
+ // Under the old comparator (children/isAnimating/loading only), this
220
+ // rerender was skipped, so the switch to `mode="static"` never took
221
+ // effect and the incomplete markdown stayed auto-completed.
222
+ expect(container.querySelector('[data-streamdown="strong"]')).not.toBeInTheDocument();
223
+ expect(screen.getByText("**bold")).toBeInTheDocument();
224
+ });
225
+ });
226
+
188
227
  describe("MessageResponse plugins/components overrides (#10 — merge-not-replace semantics)", () => {
189
- it("merges a real `plugins.cjk` override in (append), keeps sanitisation on, and keeps the untouched `plugins.math` default alive (#10)", () => {
228
+ it("merges a real `plugins.cjk` override in (append), keeps sanitisation on, and keeps the untouched `plugins.math` default alive (#10)", async () => {
190
229
  // A real, discriminating lock — NOT `plugins={{}}` (that exercises zero
191
230
  // slots and passes identically under merge, replace, or a no-op; #10
192
231
  // review I3). This test supplies a genuine `cjk` plugin (one of the two
@@ -228,7 +267,14 @@ $$x^2$$
228
267
  expect(cjkRemarkSpy).toHaveBeenCalled();
229
268
  // (2b) …and the internal `math` default the consumer did not set is
230
269
  // still active — real KaTeX markup, not the literal `$$x^2$$` text.
231
- expect(document.querySelector(".katex")).toBeInTheDocument();
270
+ // `math` is now lazy-loaded off the source text (#perf-5) — it starts
271
+ // `undefined` and arrives after a dynamic import, hence `waitFor`.
272
+ await waitFor(
273
+ () => {
274
+ expect(document.querySelector(".katex")).toBeInTheDocument();
275
+ },
276
+ { timeout: 5000 },
277
+ );
232
278
  });
233
279
  });
234
280
 
@@ -381,6 +427,30 @@ function BranchHarness(props: { branch?: number; onBranchChange?: (n: number) =>
381
427
  );
382
428
  }
383
429
 
430
+ describe("MessageBranchContent — id uniqueness and content sync (perf review §2)", () => {
431
+ it("does not spread a caller-supplied id onto every branch (each branch keeps a unique id)", () => {
432
+ const { container } = render(
433
+ <MessageBranch>
434
+ <MessageBranchContent id="branch-content">
435
+ <div key="a">Branch A</div>
436
+ <div key="b">Branch B</div>
437
+ <div key="c">Branch C</div>
438
+ </MessageBranchContent>
439
+ </MessageBranch>,
440
+ );
441
+
442
+ const contentDivs = container.querySelectorAll('[data-slot="message-branch-content"]');
443
+ expect(contentDivs).toHaveLength(3);
444
+ const ids = Array.from(contentDivs).map((el) => el.getAttribute("id"));
445
+ // Under the bug, `{...props}` spread the literal `id="branch-content"`
446
+ // onto every mapped div — three duplicate DOM ids for one supplied id.
447
+ expect(new Set(ids).size).toBe(ids.length);
448
+ for (const id of ids) {
449
+ expect(id).toMatch(/^branch-content-\d+$/);
450
+ }
451
+ });
452
+ });
453
+
384
454
  describe("MessageBranch — controlled mode (#361)", () => {
385
455
  it("is uncontrolled by default: defaultBranch + internal Next/Previous navigation are unaffected", async () => {
386
456
  const onBranchChange = vi.fn();
package/src/message.tsx CHANGED
@@ -8,6 +8,7 @@ import { useLocale } from "@elabs-ai/components-ui";
8
8
  import { cva, type VariantProps } from "class-variance-authority";
9
9
  import { stripSanitizerOverrides, warnOnTrustedPluginSlots } from "./_streamdown-safety";
10
10
  import {
11
+ getStreamdownPluginsKey,
11
12
  useStreamdownMermaidOptions,
12
13
  useStreamdownPlugins,
13
14
  useStreamdownTranslations,
@@ -433,19 +434,20 @@ export const MessageBranch = ({
433
434
 
434
435
  export type MessageBranchContentProps = HTMLAttributes<HTMLDivElement>;
435
436
 
436
- export const MessageBranchContent = ({ children, ...props }: MessageBranchContentProps) => {
437
- const { currentBranch, setBranches, branches } = useMessageBranch();
437
+ export const MessageBranchContent = ({ children, id, ...props }: MessageBranchContentProps) => {
438
+ const { currentBranch, setBranches } = useMessageBranch();
438
439
  const childrenArray = useMemo(
439
440
  () => (Array.isArray(children) ? children : [children]),
440
441
  [children],
441
442
  );
442
443
 
443
- // Use useEffect to update branches when they change
444
+ // Always resync — an earlier length-gate (`branches.length !==
445
+ // childrenArray.length`) skipped this when a branch's CONTENT changed
446
+ // (e.g. an edited message) without the branch COUNT changing, leaving
447
+ // `context.branches` stale relative to what is actually rendered.
444
448
  useEffect(() => {
445
- if (branches.length !== childrenArray.length) {
446
- setBranches(childrenArray);
447
- }
448
- }, [childrenArray, branches, setBranches]);
449
+ setBranches(childrenArray);
450
+ }, [childrenArray, setBranches]);
449
451
 
450
452
  return childrenArray.map((branch, index) => (
451
453
  <div
@@ -454,6 +456,10 @@ export const MessageBranchContent = ({ children, ...props }: MessageBranchConten
454
456
  index === currentBranch ? "block" : "hidden",
455
457
  )}
456
458
  data-slot="message-branch-content"
459
+ // Spreading a caller-supplied `id` onto every mapped branch produced
460
+ // duplicate DOM ids (one per branch); suffix it per index instead so
461
+ // each branch keeps a unique, stable id.
462
+ id={id ? `${id}-${index}` : undefined}
457
463
  key={branch.key}
458
464
  {...props}
459
465
  >
@@ -624,7 +630,12 @@ export const MessageResponse = memo(
624
630
  const { t } = useLocale();
625
631
  // Brand-token-derived `code` plugin, not the package's static github-*
626
632
  // default (#315 follow-up) — re-derives when the active theme changes.
627
- const internalPlugins = useStreamdownPlugins();
633
+ // `math`/`cjk` are lazy-loaded off the raw markdown source (#perf-5, see
634
+ // `_streamdown-i18n.ts`) — pass `props.children` so they load only when
635
+ // this message actually needs them.
636
+ const internalPlugins = useStreamdownPlugins(
637
+ typeof props.children === "string" ? props.children : "",
638
+ );
628
639
  // MERGED per key over the internal defaults — the same semantics as
629
640
  // `MarkdownView`'s `plugins` prop (#10 fix round, M4): a consumer entry
630
641
  // wins for its key; every key the consumer does not set keeps the
@@ -676,6 +687,9 @@ export const MessageResponse = memo(
676
687
  "[&_p]:text-body [&_li]:text-body [&_code]:text-code",
677
688
  className,
678
689
  )}
690
+ // Forces a remount the first time the lazy math/cjk slot resolves —
691
+ // see `getStreamdownPluginsKey`'s doc in `_streamdown-i18n.ts` for why.
692
+ key={getStreamdownPluginsKey(internalPlugins)}
679
693
  data-slot="message-response"
680
694
  plugins={plugins}
681
695
  translations={translations}
@@ -684,10 +698,20 @@ export const MessageResponse = memo(
684
698
  />
685
699
  );
686
700
  },
687
- (prevProps, nextProps) =>
688
- prevProps.children === nextProps.children &&
689
- nextProps.isAnimating === prevProps.isAnimating &&
690
- nextProps.loading === prevProps.loading,
701
+ // A hand-picked field list (`children`/`isAnimating`/`loading`) silently
702
+ // went stale as `MessageResponseProps` grew: `className`, `components`,
703
+ // `plugins`, `mode` and every other `Streamdown` prop passed through
704
+ // `...props` were never compared, so a caller-driven change to any of them
705
+ // (e.g. toggling `mode="static"`/`"streaming"`, swapping `components`, or
706
+ // overriding a `plugins` slot) rendered stale content until an unrelated
707
+ // prop also changed. Compare every own key on BOTH sides with `Object.is`
708
+ // instead, so a newly added prop is covered for free.
709
+ (prevProps, nextProps) => {
710
+ const prevKeys = Object.keys(prevProps) as (keyof MessageResponseProps)[];
711
+ const nextKeys = Object.keys(nextProps) as (keyof MessageResponseProps)[];
712
+ if (prevKeys.length !== nextKeys.length) return false;
713
+ return prevKeys.every((key) => Object.is(prevProps[key], nextProps[key]));
714
+ },
691
715
  );
692
716
 
693
717
  MessageResponse.displayName = "MessageResponse";
@@ -1,7 +1,17 @@
1
- import { describe, expect, it, vi } from "vitest";
2
- import { fireEvent, render, screen } from "@testing-library/react";
1
+ import { afterEach, describe, expect, it, vi } from "vitest";
2
+ import { cleanup, fireEvent, render, screen } from "@testing-library/react";
3
3
 
4
- import { MODEL_PROVIDER_LOGO_BASE_URL, ModelProviderLogo } from "./model-provider-logo";
4
+ import {
5
+ MODEL_PROVIDER_LOGO_BASE_URL,
6
+ ModelProviderLogo,
7
+ ModelProviderLogoGroup,
8
+ } from "./model-provider-logo";
9
+
10
+ afterEach(() => {
11
+ cleanup();
12
+ document.documentElement.removeAttribute("data-theme");
13
+ document.documentElement.style.colorScheme = "";
14
+ });
5
15
 
6
16
  describe("ModelProviderLogo — remote origin escape hatches", () => {
7
17
  it("defaults to the models.dev logo for the provider", () => {
@@ -53,3 +63,47 @@ describe("ModelProviderLogo — remote origin escape hatches", () => {
53
63
  expect(screen.getByRole("img", { name: "openai logo" })).toBeInTheDocument();
54
64
  });
55
65
  });
66
+
67
+ // Was `dark:invert` — a bare Tailwind dark: variant, which per theming.md only
68
+ // covers the two SHIPPED themes and misses any consumer-registered dark theme
69
+ // (ADR 0029, open theme registry). Now driven by `resolveThemeIsDark`, the
70
+ // same signal `Toaster` uses, so it follows the ACTIVE theme's own
71
+ // `color-scheme`, not a hardcoded theme name (review #1).
72
+ describe("ModelProviderLogo — theme-driven invert (review #1)", () => {
73
+ it("does not invert under the light theme", () => {
74
+ document.documentElement.setAttribute("data-theme", "light");
75
+ render(<ModelProviderLogo provider="anthropic" />);
76
+ expect(screen.getByRole("img", { name: "anthropic logo" })).not.toHaveClass("invert");
77
+ });
78
+
79
+ it("inverts under the dark theme", () => {
80
+ document.documentElement.setAttribute("data-theme", "dark");
81
+ render(<ModelProviderLogo provider="anthropic" />);
82
+ expect(screen.getByRole("img", { name: "anthropic logo" })).toHaveClass("invert");
83
+ });
84
+
85
+ it("also inverts under a consumer-registered dark theme, not only the shipped one", () => {
86
+ // A raw `dark:` utility only ever matches `data-theme="dark"` — this
87
+ // is exactly the gap `resolveThemeIsDark`'s `color-scheme` reading closes.
88
+ document.documentElement.setAttribute("data-theme", "midnight");
89
+ document.documentElement.style.colorScheme = "dark";
90
+ render(<ModelProviderLogo provider="anthropic" />);
91
+ expect(screen.getByRole("img", { name: "anthropic logo" })).toHaveClass("invert");
92
+ });
93
+ });
94
+
95
+ describe("ModelProviderLogoGroup — theme-driven backdrop (review #1)", () => {
96
+ it("backs logos with --background in the light theme", () => {
97
+ document.documentElement.setAttribute("data-theme", "light");
98
+ const { container } = render(<ModelProviderLogoGroup />);
99
+ expect(container.firstElementChild?.className).toContain("[&>img]:bg-background");
100
+ expect(container.firstElementChild?.className).not.toContain("[&>img]:bg-foreground");
101
+ });
102
+
103
+ it("backs logos with --foreground in the dark theme", () => {
104
+ document.documentElement.setAttribute("data-theme", "dark");
105
+ const { container } = render(<ModelProviderLogoGroup />);
106
+ expect(container.firstElementChild?.className).toContain("[&>img]:bg-foreground");
107
+ expect(container.firstElementChild?.className).not.toContain("[&>img]:bg-background");
108
+ });
109
+ });
@@ -1,7 +1,32 @@
1
+ "use client";
2
+
1
3
  import { cn } from "@elabs-ai/components-ui/lib/cn";
4
+ import { resolveThemeIsDark } from "@elabs-ai/components-tokens";
2
5
  import { BotIcon } from "lucide-react";
3
6
  import type { ComponentProps, ReactNode } from "react";
4
- import { useState } from "react";
7
+ import { useEffect, useState } from "react";
8
+
9
+ /**
10
+ * Tracks the ACTIVE theme's own `color-scheme` (`resolveThemeIsDark`), not the
11
+ * two shipped `light`/`dark` themes — a consumer-authored dark theme still
12
+ * gets an inverted (visible) logo with no registration step (theming.md:
13
+ * "Use semantic tokens, not `dark:` — `@custom-variant dark` covers only the
14
+ * themes shipped HERE"). Mirrors `Toaster`'s `useDocumentThemeMode`
15
+ * (`packages/ui/src/components/sonner/sonner.tsx`).
16
+ */
17
+ function useIsDarkTheme(): boolean {
18
+ const [isDark, setIsDark] = useState(false);
19
+ useEffect(() => {
20
+ if (typeof document === "undefined") return;
21
+ const el = document.documentElement;
22
+ const read = () => setIsDark(resolveThemeIsDark(el));
23
+ read();
24
+ const obs = new MutationObserver(read);
25
+ obs.observe(el, { attributes: true, attributeFilter: ["data-theme"] });
26
+ return () => obs.disconnect();
27
+ }, []);
28
+ return isDark;
29
+ }
5
30
 
6
31
  /**
7
32
  * Where provider logos are fetched from by default.
@@ -106,6 +131,7 @@ export const ModelProviderLogo = ({
106
131
  ...props
107
132
  }: ModelProviderLogoProps) => {
108
133
  const [failed, setFailed] = useState(false);
134
+ const isDark = useIsDarkTheme();
109
135
 
110
136
  // A blocked/missing logo must not leave a broken-image glyph in the row.
111
137
  if (failed) {
@@ -122,7 +148,7 @@ export const ModelProviderLogo = ({
122
148
  <img
123
149
  {...props}
124
150
  alt={`${provider} logo`}
125
- className={cn("size-3 dark:invert", className)}
151
+ className={cn("size-3", isDark && "invert", className)}
126
152
  height={12}
127
153
  onError={(event) => {
128
154
  setFailed(true);
@@ -138,12 +164,20 @@ export const ModelProviderLogo = ({
138
164
 
139
165
  export type ModelProviderLogoGroupProps = ComponentProps<"div">;
140
166
 
141
- export const ModelProviderLogoGroup = ({ className, ...props }: ModelProviderLogoGroupProps) => (
142
- <div
143
- className={cn(
144
- "flex shrink-0 items-center -space-x-1 [&>img]:rounded-full [&>img]:bg-background [&>img]:p-px [&>img]:ring-1 dark:[&>img]:bg-foreground",
145
- className,
146
- )}
147
- {...props}
148
- />
149
- );
167
+ export const ModelProviderLogoGroup = ({ className, ...props }: ModelProviderLogoGroupProps) => {
168
+ // Same active-theme signal as the single logo above (not the two shipped
169
+ // `light`/`dark` themes) — the backdrop swaps to `--foreground` so a
170
+ // transparent-background SVG still reads against a dark chip in ANY dark
171
+ // theme a consumer registers.
172
+ const isDark = useIsDarkTheme();
173
+ return (
174
+ <div
175
+ className={cn(
176
+ "flex shrink-0 items-center -space-x-1 [&>img]:rounded-full [&>img]:p-px [&>img]:ring-1",
177
+ isDark ? "[&>img]:bg-foreground" : "[&>img]:bg-background",
178
+ className,
179
+ )}
180
+ {...props}
181
+ />
182
+ );
183
+ };
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import { Button } from "@elabs-ai/components-ui";
3
+ import { Button, useLocale } from "@elabs-ai/components-ui";
4
4
  import {
5
5
  DropdownMenu,
6
6
  DropdownMenuContent,
@@ -23,11 +23,11 @@ const providers = {
23
23
  })}`,
24
24
  icon: (
25
25
  <svg fill="currentColor" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
26
- <title>OpenAI</title>
26
+ <title>OpenAI</title> {/* i18n-exempt: brand name */}
27
27
  <path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z" />
28
28
  </svg>
29
29
  ),
30
- title: "Open in ChatGPT",
30
+ title: "ChatGPT", // i18n-exempt: brand name
31
31
  },
32
32
  claude: {
33
33
  createUrl: (q: string) =>
@@ -36,7 +36,7 @@ const providers = {
36
36
  })}`,
37
37
  icon: (
38
38
  <svg fill="currentColor" role="img" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">
39
- <title>Claude</title>
39
+ <title>Claude</title> {/* i18n-exempt: brand name */}
40
40
  <path
41
41
  clipRule="evenodd"
42
42
  d="M2.3545 7.9775L4.7145 6.654L4.7545 6.539L4.7145 6.475H4.6L4.205 6.451L2.856 6.4145L1.6865 6.366L0.5535 6.305L0.268 6.2445L0 5.892L0.0275 5.716L0.2675 5.5555L0.6105 5.5855L1.3705 5.637L2.5095 5.716L3.3355 5.7645L4.56 5.892H4.7545L4.782 5.8135L4.715 5.7645L4.6635 5.716L3.4845 4.918L2.2085 4.074L1.5405 3.588L1.1785 3.3425L0.9965 3.1115L0.9175 2.6075L1.2455 2.2465L1.686 2.2765L1.7985 2.307L2.245 2.65L3.199 3.388L4.4445 4.3045L4.627 4.4565L4.6995 4.405L4.709 4.3685L4.627 4.2315L3.9495 3.0085L3.2265 1.7635L2.9045 1.2475L2.8195 0.938C2.78711 0.819128 2.76965 0.696687 2.7675 0.5735L3.1415 0.067L3.348 0L3.846 0.067L4.056 0.249L4.366 0.956L4.867 2.0705L5.6445 3.5855L5.8725 4.0345L5.994 4.4505L6.0395 4.578H6.1185V4.505L6.1825 3.652L6.301 2.6045L6.416 1.257L6.456 0.877L6.644 0.422L7.0175 0.176L7.3095 0.316L7.5495 0.6585L7.516 0.8805L7.373 1.806L7.0935 3.2575L6.9115 4.2285H7.0175L7.139 4.1075L7.6315 3.4545L8.4575 2.4225L8.8225 2.0125L9.2475 1.5605L9.521 1.345H10.0375L10.4175 1.9095L10.2475 2.4925L9.7155 3.166L9.275 3.737L8.643 4.587L8.248 5.267L8.2845 5.322L8.3785 5.312L9.8065 5.009L10.578 4.869L11.4985 4.7115L11.915 4.9055L11.9605 5.103L11.7965 5.5065L10.812 5.7495L9.6575 5.9805L7.938 6.387L7.917 6.402L7.9415 6.4325L8.716 6.5055L9.047 6.5235H9.858L11.368 6.636L11.763 6.897L12 7.216L11.9605 7.4585L11.353 7.7685L10.533 7.574L8.6185 7.119L7.9625 6.9545H7.8715V7.0095L8.418 7.5435L9.421 8.4485L10.6755 9.6135L10.739 9.9025L10.578 10.13L10.408 10.1055L9.3055 9.277L8.88 8.9035L7.917 8.0935H7.853V8.1785L8.075 8.503L9.2475 10.2635L9.3085 10.8035L9.2235 10.98L8.9195 11.0865L8.5855 11.0255L7.8985 10.063L7.191 8.9795L6.6195 8.008L6.5495 8.048L6.2125 11.675L6.0545 11.86L5.69 12L5.3865 11.7695L5.2255 11.396L5.3865 10.658L5.581 9.696L5.7385 8.931L5.8815 7.981L5.9665 7.665L5.9605 7.644L5.8905 7.653L5.1735 8.6365L4.0835 10.109L3.2205 11.0315L3.0135 11.1135L2.655 10.9285L2.6885 10.5975L2.889 10.303L4.083 8.785L4.803 7.844L5.268 7.301L5.265 7.222H5.2375L2.066 9.28L1.501 9.353L1.2575 9.125L1.288 8.752L1.4035 8.6305L2.3575 7.9745L2.3545 7.9775Z"
@@ -44,7 +44,7 @@ const providers = {
44
44
  />
45
45
  </svg>
46
46
  ),
47
- title: "Open in Claude",
47
+ title: "Claude", // i18n-exempt: brand name
48
48
  },
49
49
  cursor: {
50
50
  createUrl: (text: string) => {
@@ -54,24 +54,24 @@ const providers = {
54
54
  },
55
55
  icon: (
56
56
  <svg version="1.1" viewBox="0 0 466.73 532.09" xmlns="http://www.w3.org/2000/svg">
57
- <title>Cursor</title>
57
+ <title>Cursor</title> {/* i18n-exempt: brand name */}
58
58
  <path
59
59
  d="M457.43,125.94L244.42,2.96c-6.84-3.95-15.28-3.95-22.12,0L9.3,125.94c-5.75,3.32-9.3,9.46-9.3,16.11v247.99c0,6.65,3.55,12.79,9.3,16.11l213.01,122.98c6.84,3.95,15.28,3.95,22.12,0l213.01-122.98c5.75-3.32,9.3-9.46,9.3-16.11v-247.99c0-6.65-3.55-12.79-9.3-16.11h-.01ZM444.05,151.99l-205.63,356.16c-1.39,2.4-5.06,1.42-5.06-1.36v-233.21c0-4.66-2.49-8.97-6.53-11.31L24.87,145.67c-2.4-1.39-1.42-5.06,1.36-5.06h411.26c5.84,0,9.49,6.33,6.57,11.39h-.01Z"
60
60
  fill="currentColor"
61
61
  />
62
62
  </svg>
63
63
  ),
64
- title: "Open in Cursor",
64
+ title: "Cursor", // i18n-exempt: brand name
65
65
  },
66
66
  github: {
67
67
  createUrl: (url: string) => url,
68
68
  icon: (
69
69
  <svg fill="currentColor" role="img" viewBox="0 0 24 24">
70
- <title>GitHub</title>
70
+ <title>GitHub</title> {/* i18n-exempt: brand name */}
71
71
  <path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
72
72
  </svg>
73
73
  ),
74
- title: "Open in GitHub",
74
+ title: "GitHub", // i18n-exempt: brand name
75
75
  },
76
76
  scira: {
77
77
  createUrl: (q: string) =>
@@ -86,7 +86,7 @@ const providers = {
86
86
  width="910"
87
87
  xmlns="http://www.w3.org/2000/svg"
88
88
  >
89
- <title>Scira AI</title>
89
+ <title>Scira AI</title> {/* i18n-exempt: brand name */}
90
90
  <path
91
91
  d="M647.664 197.775C569.13 189.049 525.5 145.419 516.774 66.8849C508.048 145.419 464.418 189.049 385.884 197.775C464.418 206.501 508.048 250.131 516.774 328.665C525.5 250.131 569.13 206.501 647.664 197.775Z"
92
92
  fill="currentColor"
@@ -136,7 +136,7 @@ const providers = {
136
136
  />
137
137
  </svg>
138
138
  ),
139
- title: "Open in Scira",
139
+ title: "Scira", // i18n-exempt: brand name
140
140
  },
141
141
  t3: {
142
142
  createUrl: (q: string) =>
@@ -144,7 +144,7 @@ const providers = {
144
144
  q,
145
145
  })}`,
146
146
  icon: <MessageCircleIcon />,
147
- title: "Open in T3 Chat",
147
+ title: "T3 Chat", // i18n-exempt: brand name
148
148
  },
149
149
  v0: {
150
150
  createUrl: (q: string) =>
@@ -158,7 +158,7 @@ const providers = {
158
158
  <path d="M147 56H133V23.9531L100.953 56H133V70H96.6875C85.8144 70 77 61.1856 77 50.3125V14H91V46.1562L123.156 14H91V0H127.312C138.186 0 147 8.81439 147 19.6875V56Z" />
159
159
  </svg>
160
160
  ),
161
- title: "Open in v0",
161
+ title: "v0", // i18n-exempt: brand name
162
162
  },
163
163
  };
164
164
 
@@ -208,21 +208,25 @@ export const OpenInSeparator = (props: OpenInSeparatorProps) => (
208
208
 
209
209
  export type OpenInTriggerProps = ComponentProps<typeof DropdownMenuTrigger>;
210
210
 
211
- export const OpenInTrigger = ({ children, ...props }: OpenInTriggerProps) => (
212
- <DropdownMenuTrigger {...props} asChild>
213
- {children ?? (
214
- <Button type="button" variant="outline">
215
- Open in chat
216
- <ChevronDownIcon className="size-4" />
217
- </Button>
218
- )}
219
- </DropdownMenuTrigger>
220
- );
211
+ export const OpenInTrigger = ({ children, ...props }: OpenInTriggerProps) => {
212
+ const { t } = useLocale();
213
+ return (
214
+ <DropdownMenuTrigger {...props} asChild>
215
+ {children ?? (
216
+ <Button type="button" variant="outline">
217
+ {t("ai.openInChat.trigger")}
218
+ <ChevronDownIcon className="size-4" />
219
+ </Button>
220
+ )}
221
+ </DropdownMenuTrigger>
222
+ );
223
+ };
221
224
 
222
225
  export type OpenInChatGPTProps = ComponentProps<typeof DropdownMenuItem>;
223
226
 
224
227
  export const OpenInChatGPT = (props: OpenInChatGPTProps) => {
225
228
  const { query } = useOpenInContext();
229
+ const { t } = useLocale();
226
230
  return (
227
231
  <DropdownMenuItem asChild {...props}>
228
232
  <a
@@ -232,7 +236,9 @@ export const OpenInChatGPT = (props: OpenInChatGPTProps) => {
232
236
  target="_blank"
233
237
  >
234
238
  <span className="shrink-0">{providers.chatgpt.icon}</span>
235
- <span className="flex-1">{providers.chatgpt.title}</span>
239
+ <span className="flex-1">
240
+ {t("ai.openInChat.openInProvider", { provider: providers.chatgpt.title })}
241
+ </span>
236
242
  <ExternalLinkIcon className="size-4 shrink-0" />
237
243
  </a>
238
244
  </DropdownMenuItem>
@@ -243,6 +249,7 @@ export type OpenInClaudeProps = ComponentProps<typeof DropdownMenuItem>;
243
249
 
244
250
  export const OpenInClaude = (props: OpenInClaudeProps) => {
245
251
  const { query } = useOpenInContext();
252
+ const { t } = useLocale();
246
253
  return (
247
254
  <DropdownMenuItem asChild {...props}>
248
255
  <a
@@ -252,7 +259,9 @@ export const OpenInClaude = (props: OpenInClaudeProps) => {
252
259
  target="_blank"
253
260
  >
254
261
  <span className="shrink-0">{providers.claude.icon}</span>
255
- <span className="flex-1">{providers.claude.title}</span>
262
+ <span className="flex-1">
263
+ {t("ai.openInChat.openInProvider", { provider: providers.claude.title })}
264
+ </span>
256
265
  <ExternalLinkIcon className="size-4 shrink-0" />
257
266
  </a>
258
267
  </DropdownMenuItem>
@@ -263,6 +272,7 @@ export type OpenInT3Props = ComponentProps<typeof DropdownMenuItem>;
263
272
 
264
273
  export const OpenInT3 = (props: OpenInT3Props) => {
265
274
  const { query } = useOpenInContext();
275
+ const { t } = useLocale();
266
276
  return (
267
277
  <DropdownMenuItem asChild {...props}>
268
278
  <a
@@ -272,7 +282,9 @@ export const OpenInT3 = (props: OpenInT3Props) => {
272
282
  target="_blank"
273
283
  >
274
284
  <span className="shrink-0">{providers.t3.icon}</span>
275
- <span className="flex-1">{providers.t3.title}</span>
285
+ <span className="flex-1">
286
+ {t("ai.openInChat.openInProvider", { provider: providers.t3.title })}
287
+ </span>
276
288
  <ExternalLinkIcon className="size-4 shrink-0" />
277
289
  </a>
278
290
  </DropdownMenuItem>
@@ -283,6 +295,7 @@ export type OpenInSciraProps = ComponentProps<typeof DropdownMenuItem>;
283
295
 
284
296
  export const OpenInScira = (props: OpenInSciraProps) => {
285
297
  const { query } = useOpenInContext();
298
+ const { t } = useLocale();
286
299
  return (
287
300
  <DropdownMenuItem asChild {...props}>
288
301
  <a
@@ -292,7 +305,9 @@ export const OpenInScira = (props: OpenInSciraProps) => {
292
305
  target="_blank"
293
306
  >
294
307
  <span className="shrink-0">{providers.scira.icon}</span>
295
- <span className="flex-1">{providers.scira.title}</span>
308
+ <span className="flex-1">
309
+ {t("ai.openInChat.openInProvider", { provider: providers.scira.title })}
310
+ </span>
296
311
  <ExternalLinkIcon className="size-4 shrink-0" />
297
312
  </a>
298
313
  </DropdownMenuItem>
@@ -303,6 +318,7 @@ export type OpenInv0Props = ComponentProps<typeof DropdownMenuItem>;
303
318
 
304
319
  export const OpenInv0 = (props: OpenInv0Props) => {
305
320
  const { query } = useOpenInContext();
321
+ const { t } = useLocale();
306
322
  return (
307
323
  <DropdownMenuItem asChild {...props}>
308
324
  <a
@@ -312,7 +328,9 @@ export const OpenInv0 = (props: OpenInv0Props) => {
312
328
  target="_blank"
313
329
  >
314
330
  <span className="shrink-0">{providers.v0.icon}</span>
315
- <span className="flex-1">{providers.v0.title}</span>
331
+ <span className="flex-1">
332
+ {t("ai.openInChat.openInProvider", { provider: providers.v0.title })}
333
+ </span>
316
334
  <ExternalLinkIcon className="size-4 shrink-0" />
317
335
  </a>
318
336
  </DropdownMenuItem>
@@ -323,6 +341,7 @@ export type OpenInCursorProps = ComponentProps<typeof DropdownMenuItem>;
323
341
 
324
342
  export const OpenInCursor = (props: OpenInCursorProps) => {
325
343
  const { query } = useOpenInContext();
344
+ const { t } = useLocale();
326
345
  return (
327
346
  <DropdownMenuItem asChild {...props}>
328
347
  <a
@@ -332,7 +351,9 @@ export const OpenInCursor = (props: OpenInCursorProps) => {
332
351
  target="_blank"
333
352
  >
334
353
  <span className="shrink-0">{providers.cursor.icon}</span>
335
- <span className="flex-1">{providers.cursor.title}</span>
354
+ <span className="flex-1">
355
+ {t("ai.openInChat.openInProvider", { provider: providers.cursor.title })}
356
+ </span>
336
357
  <ExternalLinkIcon className="size-4 shrink-0" />
337
358
  </a>
338
359
  </DropdownMenuItem>