@elabs-ai/components-ai 4.2.0 → 5.1.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 (462) hide show
  1. package/README.md +1 -1
  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-BA3AEMOU.js +21 -0
  106. package/dist/chunk-BA3AEMOU.js.map +1 -0
  107. package/dist/chunk-BBHFOYML.js +336 -0
  108. package/dist/chunk-BBHFOYML.js.map +1 -0
  109. package/dist/chunk-BHUVISIY.js +221 -0
  110. package/dist/chunk-BHUVISIY.js.map +1 -0
  111. package/dist/chunk-BM2LWERA.js +100 -0
  112. package/dist/chunk-BM2LWERA.js.map +1 -0
  113. package/dist/chunk-CMOYXNPJ.js +211 -0
  114. package/dist/chunk-CMOYXNPJ.js.map +1 -0
  115. package/dist/chunk-CZP3RHC4.js +74 -0
  116. package/dist/chunk-CZP3RHC4.js.map +1 -0
  117. package/dist/chunk-DDG5JHQT.js +112 -0
  118. package/dist/chunk-DDG5JHQT.js.map +1 -0
  119. package/dist/chunk-DJODXO7I.js +38 -0
  120. package/dist/chunk-DJODXO7I.js.map +1 -0
  121. package/dist/chunk-DWJDJLPU.js +192 -0
  122. package/dist/chunk-DWJDJLPU.js.map +1 -0
  123. package/dist/chunk-DZW6LM22.js +45 -0
  124. package/dist/chunk-DZW6LM22.js.map +1 -0
  125. package/dist/chunk-ELGCZLZF.js +305 -0
  126. package/dist/chunk-ELGCZLZF.js.map +1 -0
  127. package/dist/chunk-EV3U6ZZB.js +209 -0
  128. package/dist/chunk-EV3U6ZZB.js.map +1 -0
  129. package/dist/chunk-EY7LVUKQ.js +87 -0
  130. package/dist/chunk-EY7LVUKQ.js.map +1 -0
  131. package/dist/chunk-F4C74F7M.js +230 -0
  132. package/dist/chunk-F4C74F7M.js.map +1 -0
  133. package/dist/chunk-FERYDFZA.js +108 -0
  134. package/dist/chunk-FERYDFZA.js.map +1 -0
  135. package/dist/chunk-FUIYG2DS.js +166 -0
  136. package/dist/chunk-FUIYG2DS.js.map +1 -0
  137. package/dist/chunk-GA7M34KU.js +236 -0
  138. package/dist/chunk-GA7M34KU.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-L4SU3QJV.js +174 -0
  156. package/dist/chunk-L4SU3QJV.js.map +1 -0
  157. package/dist/chunk-LMNSBOBG.js +19 -0
  158. package/dist/chunk-LMNSBOBG.js.map +1 -0
  159. package/dist/chunk-MBI7D6IL.js +42 -0
  160. package/dist/chunk-MBI7D6IL.js.map +1 -0
  161. package/dist/chunk-MLFMYCPH.js +192 -0
  162. package/dist/chunk-MLFMYCPH.js.map +1 -0
  163. package/dist/chunk-MOK66734.js +33 -0
  164. package/dist/chunk-MOK66734.js.map +1 -0
  165. package/dist/chunk-MXTAWPYM.js +196 -0
  166. package/dist/chunk-MXTAWPYM.js.map +1 -0
  167. package/dist/chunk-NCL2UZKI.js +96 -0
  168. package/dist/chunk-NCL2UZKI.js.map +1 -0
  169. package/dist/chunk-NET4YLRY.js +107 -0
  170. package/dist/chunk-NET4YLRY.js.map +1 -0
  171. package/dist/chunk-NIG67CFM.js +329 -0
  172. package/dist/chunk-NIG67CFM.js.map +1 -0
  173. package/dist/chunk-NMVNNO3L.js +10 -0
  174. package/dist/chunk-NMVNNO3L.js.map +1 -0
  175. package/dist/chunk-NP7VID4K.js +2 -0
  176. package/dist/chunk-NP7VID4K.js.map +1 -0
  177. package/dist/chunk-NPUCMAG2.js +382 -0
  178. package/dist/chunk-NPUCMAG2.js.map +1 -0
  179. package/dist/chunk-NSCLRHES.js +102 -0
  180. package/dist/chunk-NSCLRHES.js.map +1 -0
  181. package/dist/chunk-OEUXHVFW.js +86 -0
  182. package/dist/chunk-OEUXHVFW.js.map +1 -0
  183. package/dist/chunk-ONBHFIMZ.js +137 -0
  184. package/dist/chunk-ONBHFIMZ.js.map +1 -0
  185. package/dist/chunk-OPH2HHL2.js +228 -0
  186. package/dist/chunk-OPH2HHL2.js.map +1 -0
  187. package/dist/chunk-OQERHIQP.js +287 -0
  188. package/dist/chunk-OQERHIQP.js.map +1 -0
  189. package/dist/chunk-P6YJAZFG.js +103 -0
  190. package/dist/chunk-P6YJAZFG.js.map +1 -0
  191. package/dist/chunk-PG7ERDOD.js +65 -0
  192. package/dist/chunk-PG7ERDOD.js.map +1 -0
  193. package/dist/chunk-PIB5JBMA.js +90 -0
  194. package/dist/chunk-PIB5JBMA.js.map +1 -0
  195. package/dist/chunk-PQ5BPIWQ.js +22 -0
  196. package/dist/chunk-PQ5BPIWQ.js.map +1 -0
  197. package/dist/chunk-PQBF6ZIQ.js +55 -0
  198. package/dist/chunk-PQBF6ZIQ.js.map +1 -0
  199. package/dist/chunk-PTWCTSZL.js +173 -0
  200. package/dist/chunk-PTWCTSZL.js.map +1 -0
  201. package/dist/chunk-QJUNPVOS.js +101 -0
  202. package/dist/chunk-QJUNPVOS.js.map +1 -0
  203. package/dist/chunk-QOBSEGCG.js +996 -0
  204. package/dist/chunk-QOBSEGCG.js.map +1 -0
  205. package/dist/chunk-QUDZOTRV.js +43 -0
  206. package/dist/chunk-QUDZOTRV.js.map +1 -0
  207. package/dist/chunk-RB7G65JC.js +213 -0
  208. package/dist/chunk-RB7G65JC.js.map +1 -0
  209. package/dist/chunk-RPHAOR7P.js +45 -0
  210. package/dist/chunk-RPHAOR7P.js.map +1 -0
  211. package/dist/chunk-RTCSH2AN.js +331 -0
  212. package/dist/chunk-RTCSH2AN.js.map +1 -0
  213. package/dist/chunk-S3ISPW3A.js +28 -0
  214. package/dist/chunk-S3ISPW3A.js.map +1 -0
  215. package/dist/chunk-SEVN656N.js +36 -0
  216. package/dist/chunk-SEVN656N.js.map +1 -0
  217. package/dist/chunk-SKVG672B.js +64 -0
  218. package/dist/chunk-SKVG672B.js.map +1 -0
  219. package/dist/chunk-SSGJ6MEM.js +1076 -0
  220. package/dist/chunk-SSGJ6MEM.js.map +1 -0
  221. package/dist/chunk-SSKZ2AED.js +118 -0
  222. package/dist/chunk-SSKZ2AED.js.map +1 -0
  223. package/dist/chunk-T56JFGXI.js +561 -0
  224. package/dist/chunk-T56JFGXI.js.map +1 -0
  225. package/dist/chunk-TCB6O74A.js +137 -0
  226. package/dist/chunk-TCB6O74A.js.map +1 -0
  227. package/dist/chunk-TTEYPO6X.js +57 -0
  228. package/dist/chunk-TTEYPO6X.js.map +1 -0
  229. package/dist/chunk-TWPHAJNM.js +462 -0
  230. package/dist/chunk-TWPHAJNM.js.map +1 -0
  231. package/dist/chunk-TXJJLP4O.js +336 -0
  232. package/dist/chunk-TXJJLP4O.js.map +1 -0
  233. package/dist/chunk-TZ6TB7VM.js +245 -0
  234. package/dist/chunk-TZ6TB7VM.js.map +1 -0
  235. package/dist/chunk-UBLKJJ7K.js +278 -0
  236. package/dist/chunk-UBLKJJ7K.js.map +1 -0
  237. package/dist/chunk-UCJ6IUFS.js +52 -0
  238. package/dist/chunk-UCJ6IUFS.js.map +1 -0
  239. package/dist/chunk-UCKFJ3QF.js +197 -0
  240. package/dist/chunk-UCKFJ3QF.js.map +1 -0
  241. package/dist/chunk-UPXSLILN.js +20 -0
  242. package/dist/chunk-UPXSLILN.js.map +1 -0
  243. package/dist/chunk-V4DORGWJ.js +92 -0
  244. package/dist/chunk-V4DORGWJ.js.map +1 -0
  245. package/dist/chunk-V6E5DYZD.js +80 -0
  246. package/dist/chunk-V6E5DYZD.js.map +1 -0
  247. package/dist/chunk-VLLYKF3P.js +96 -0
  248. package/dist/chunk-VLLYKF3P.js.map +1 -0
  249. package/dist/chunk-VLNBTOH6.js +43 -0
  250. package/dist/chunk-VLNBTOH6.js.map +1 -0
  251. package/dist/chunk-WMDSMSPL.js +303 -0
  252. package/dist/chunk-WMDSMSPL.js.map +1 -0
  253. package/dist/chunk-WU5BLGTF.js +149 -0
  254. package/dist/chunk-WU5BLGTF.js.map +1 -0
  255. package/dist/chunk-WYTZVSGK.js +245 -0
  256. package/dist/chunk-WYTZVSGK.js.map +1 -0
  257. package/dist/chunk-X47QMM6K.js +187 -0
  258. package/dist/chunk-X47QMM6K.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 +366 -98
  300. package/dist/index.js +672 -13577
  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 +11 -9
  405. package/schemas/a2ui-surface.v1.schema.json +5434 -0
  406. package/src/_chat-shell-rail.tsx +3 -1
  407. package/src/_flow-boundary.tsx +2 -2
  408. package/src/_persona-rive.tsx +27 -41
  409. package/src/a2ui/a2ui-surface.stories.tsx +662 -0
  410. package/src/a2ui/a2ui-surface.test.tsx +201 -0
  411. package/src/a2ui/a2ui-surface.tsx +354 -0
  412. package/src/a2ui/catalog.source.json +772 -0
  413. package/src/a2ui/core/a2ui-core.test.ts +178 -0
  414. package/src/a2ui/core/catalog.generated.ts +1019 -0
  415. package/src/a2ui/core/complete-json.ts +81 -0
  416. package/src/a2ui/core/index.ts +10 -0
  417. package/src/a2ui/core/schema.ts +111 -0
  418. package/src/a2ui/core/spec.ts +148 -0
  419. package/src/a2ui/core/validate.ts +266 -0
  420. package/src/a2ui/index.ts +7 -0
  421. package/src/a2ui/ui-catalog.tsx +225 -0
  422. package/src/a2ui/wave1-chart-spec.test.ts +87 -0
  423. package/src/a2ui/wave2-chart-spec.test.ts +108 -0
  424. package/src/agentic-workspace.stories.tsx +6 -45
  425. package/src/chat-shell.test.tsx +40 -10
  426. package/src/chat-shell.tsx +90 -44
  427. package/src/chat.stories.tsx +47 -42
  428. package/src/composer.stories.tsx +4 -5
  429. package/src/composer.test.tsx +45 -0
  430. package/src/composer.tsx +104 -21
  431. package/src/context-panel.stories.tsx +1 -1
  432. package/src/context-panel.tsx +1 -1
  433. package/src/conversation.stories.tsx +1 -0
  434. package/src/conversation.test.tsx +16 -0
  435. package/src/conversation.tsx +16 -2
  436. package/src/diff-view.stories.tsx +79 -0
  437. package/src/diff-view.tsx +3 -2
  438. package/src/file-tree.tsx +24 -17
  439. package/src/gallery.stories.tsx +42 -0
  440. package/src/gallery.tsx +2 -2
  441. package/src/grouped-parts.stories.tsx +23 -0
  442. package/src/grouped-parts.tsx +1 -1
  443. package/src/index.ts +1 -0
  444. package/src/message-compare.stories.tsx +40 -0
  445. package/src/message-compare.tsx +2 -1
  446. package/src/message-form.tsx +1 -1
  447. package/src/message-table.stories.tsx +24 -0
  448. package/src/message-table.tsx +1 -1
  449. package/src/package-info.tsx +12 -9
  450. package/src/reasoning.stories.tsx +27 -0
  451. package/src/reasoning.tsx +1 -1
  452. package/src/session-header.stories.tsx +35 -1
  453. package/src/session-header.tsx +1 -1
  454. package/src/sources.stories.tsx +30 -0
  455. package/src/sources.tsx +4 -1
  456. package/src/templates-ai-assistant.stories.tsx +151 -87
  457. package/src/tool.stories.tsx +39 -0
  458. package/src/tool.tsx +3 -3
  459. package/src/toolbar.tsx +0 -10
  460. package/dist/_flow-boundary-SHNWLQG5.js.map +0 -1
  461. package/dist/_persona-rive-JEG44YHX.js.map +0 -1
  462. /package/dist/{_audio-player-media-chrome-T3XVXWRZ.js.map → _audio-player-media-chrome.js.map} +0 -0
@@ -194,3 +194,82 @@ export const ChangeReviewComposition: Story = {
194
194
  </div>
195
195
  ),
196
196
  };
197
+
198
+ /**
199
+ * Focus indicator on expandable line (#313): Tab to the "show more" button
200
+ * and assert the compound indicator's OUTLINE layer (`outlineStyle: "solid"`,
201
+ * `--ring-contour`) that `focus-ring` adds — not just `boxShadow !== "none"`,
202
+ * which the legacy `ring-2` pattern also produces.
203
+ *
204
+ * The prior version passed a `hiddenLineCount` prop that `DiffViewProps`
205
+ * (`diff-view.tsx:77`) does not define — React spread it onto the DOM as a
206
+ * meaningless `hiddenlinecount` attribute and warned about it, and the "show
207
+ * more" row NEVER rendered (a real diff never mentions "50" anywhere).
208
+ * `getByRole("button", …)` then always threw before the focus check ran.
209
+ * `CollapsedRow` (`diff-view.tsx:289`) only appears when `collapseDiffRows`
210
+ * (`packages/ui/src/lib/diff-rows.ts`) actually collapses a context run
211
+ * longer than `contextLines` — so this story now supplies a real 10-line
212
+ * context run with `contextLines={2}`, which collapses to
213
+ * `top(1) + collapsed(8) + bottom(1)`, i.e. a real "Show 8 more lines"
214
+ * button. The button's accessible name is
215
+ * `t("ai.diffView.showMore", { count })`, not the literal "Show more" —
216
+ * matched by a count-tolerant regex so it doesn't drift with the wording.
217
+ */
218
+ export const FocusIndicatorExpand: Story = {
219
+ name: "Keyboard focus — expand",
220
+ render: () => (
221
+ <div className="max-w-2xl">
222
+ <DiffView
223
+ lines={Array.from({ length: 10 }, (_, i) => ({
224
+ type: "context" as const,
225
+ oldNumber: i + 1,
226
+ newNumber: i + 1,
227
+ text: `line ${i + 1}`,
228
+ }))}
229
+ contextLines={2}
230
+ />
231
+ </div>
232
+ ),
233
+ play: async ({ canvas, userEvent }) => {
234
+ const button = canvas.getByRole("button", { name: /show \d+ more lines?/i });
235
+ await userEvent.tab();
236
+ await expect(button).toHaveFocus();
237
+ const focused = getComputedStyle(button);
238
+ await expect(focused.boxShadow).not.toBe("none");
239
+ await expect(focused.outlineStyle).toBe("solid");
240
+ await expect(parseFloat(focused.outlineWidth)).toBeGreaterThan(0);
241
+ },
242
+ };
243
+
244
+ /**
245
+ * Focus indicator on scrollable pager (#313): Tab to the diff body and
246
+ * assert the compound indicator's OUTLINE layer on the actual scroll
247
+ * container. The prior version located it via `getByText(/^export/)`, which
248
+ * throws "multiple elements" under `variant="split"` (the same line renders
249
+ * once per pane) — and even resolved, `scrollDiv.parentElement` was never
250
+ * the scroll container itself (the text sits several nodes deep inside it).
251
+ * Query the tabbable scroll container directly — it carries `tabIndex={0}`
252
+ * and `focus-ring-inset` (`diff-view.tsx`'s `DiffViewPager`) — instead of
253
+ * inferring it from rendered content.
254
+ */
255
+ export const FocusIndicatorPager: Story = {
256
+ name: "Keyboard focus — pager",
257
+ render: () => (
258
+ <div className="max-w-2xl">
259
+ <DiffView lines={SAMPLE_LINES} pager variant="split" />
260
+ </div>
261
+ ),
262
+ play: async ({ canvas, userEvent }) => {
263
+ const pager = canvas.getByRole("region");
264
+ const scrollDiv = pager.querySelector<HTMLElement>('[tabindex="0"]');
265
+ if (scrollDiv == null) {
266
+ throw new Error("expected a tabbable scroll container inside the pager region");
267
+ }
268
+ await userEvent.tab();
269
+ await expect(scrollDiv).toHaveFocus();
270
+ const focused = getComputedStyle(scrollDiv);
271
+ await expect(focused.boxShadow).not.toBe("none");
272
+ await expect(focused.outlineStyle).toBe("solid");
273
+ await expect(parseFloat(focused.outlineWidth)).toBeGreaterThan(0);
274
+ },
275
+ };
package/src/diff-view.tsx CHANGED
@@ -296,7 +296,7 @@ function CollapsedRow({ hiddenCount, onExpand }: { hiddenCount: number; onExpand
296
296
  <button
297
297
  type="button"
298
298
  onClick={onExpand}
299
- className="col-start-4 w-fit rounded-sm px-1 text-info-text text-meta hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
299
+ className="col-start-4 w-fit rounded-sm px-1 text-info-text text-meta hover:underline focus-ring"
300
300
  >
301
301
  {t("ai.diffView.showMore", { count: hiddenCount })}
302
302
  </button>
@@ -486,6 +486,7 @@ function DiffViewHeader({
486
486
  }) {
487
487
  const { t } = useLocale();
488
488
  return (
489
+ // header-band-exempt: a file header inside the diff card, not shell chrome
489
490
  <div
490
491
  data-slot="diff-view-header"
491
492
  className="flex items-center justify-between gap-3 border-b bg-muted/80 px-3 py-2 text-meta text-muted-foreground"
@@ -579,7 +580,7 @@ function DiffViewPager({
579
580
  tabIndex={0}
580
581
  onScroll={updatePercent}
581
582
  onKeyDown={onKeyDown}
582
- className="max-h-[32rem] overflow-y-auto focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset"
583
+ className="max-h-[32rem] overflow-y-auto focus-ring-inset"
583
584
  >
584
585
  {children}
585
586
  </div>
package/src/file-tree.tsx CHANGED
@@ -369,6 +369,19 @@ export const ProducedAssetTree = forwardRef<HTMLDivElement, ProducedAssetTreePro
369
369
  },
370
370
  [assetsById, onSelect],
371
371
  );
372
+ // Empty: a plain note, not an empty `role="tree"` — a tree with no
373
+ // `treeitem` fails `aria-required-children` and announces nothing useful.
374
+ if (assets.length === 0) {
375
+ return (
376
+ <div
377
+ ref={ref}
378
+ data-slot="file-tree-empty"
379
+ className={cn("px-2 py-1 text-body text-muted-foreground", props.className)}
380
+ >
381
+ {t("ai.fileTree.noAssetsProduced")}
382
+ </div>
383
+ );
384
+ }
372
385
  return (
373
386
  <FileTree
374
387
  ref={ref}
@@ -377,23 +390,17 @@ export const ProducedAssetTree = forwardRef<HTMLDivElement, ProducedAssetTreePro
377
390
  onSelect={handleSelect}
378
391
  {...props}
379
392
  >
380
- {assets.length === 0 ? (
381
- <p className="px-2 py-1 text-body text-muted-foreground">
382
- {t("ai.fileTree.noAssetsProduced")}
383
- </p>
384
- ) : (
385
- assets.map((asset) => {
386
- const Icon = assetIcon(asset);
387
- return (
388
- <FileTreeFile key={asset.id} path={asset.id} name={asset.name}>
389
- <FileTreeIcon>
390
- <Icon aria-hidden="true" className="size-4 text-muted-foreground" />
391
- </FileTreeIcon>
392
- <FileTreeName>{asset.name}</FileTreeName>
393
- </FileTreeFile>
394
- );
395
- })
396
- )}
393
+ {assets.map((asset) => {
394
+ const Icon = assetIcon(asset);
395
+ return (
396
+ <FileTreeFile key={asset.id} path={asset.id} name={asset.name}>
397
+ <FileTreeIcon>
398
+ <Icon aria-hidden="true" className="size-4 text-muted-foreground" />
399
+ </FileTreeIcon>
400
+ <FileTreeName>{asset.name}</FileTreeName>
401
+ </FileTreeFile>
402
+ );
403
+ })}
397
404
  </FileTree>
398
405
  );
399
406
  },
@@ -180,3 +180,45 @@ export const PartialSet: Story = {
180
180
  await expect(canvas.queryByText(/more/i)).not.toBeInTheDocument();
181
181
  },
182
182
  };
183
+
184
+ /**
185
+ * Focus indicator on image thumbnails (#313): Tab to an image button and
186
+ * assert the compound indicator's OUTLINE layer — `outlineStyle: "solid"`
187
+ * sourced from `--ring-contour`. The pre-#67 `focus-visible:outline-none
188
+ * focus-visible:ring-2 focus-visible:ring-ring` pattern also paints a
189
+ * non-"none" `boxShadow` (Tailwind's `ring-2` box-shadow trick), so asserting
190
+ * boxShadow alone passes on the legacy class string too; the outline layer is
191
+ * the one thing only `focus-ring`/`focus-ring-inset` add.
192
+ */
193
+ export const FocusIndicator: Story = {
194
+ name: "Keyboard focus",
195
+ args: { images: IMAGES.slice(0, 4) },
196
+ play: async ({ canvas, userEvent }) => {
197
+ const buttons = canvas.getAllByRole("button", { name: /fjord|desert/i });
198
+ await userEvent.tab();
199
+ await expect(buttons[0]).toHaveFocus();
200
+ const focused = getComputedStyle(buttons[0]);
201
+ await expect(focused.boxShadow).not.toBe("none");
202
+ await expect(focused.outlineStyle).toBe("solid");
203
+ await expect(parseFloat(focused.outlineWidth)).toBeGreaterThan(0);
204
+ },
205
+ };
206
+
207
+ /**
208
+ * Focus indicator on the overflow ("+N more") tile (#313): a separate DOM
209
+ * node from the plain thumbnails, styled the same way — never covered by any
210
+ * prior test (`gallery.tsx:409`, `GalleryOverflowTile`).
211
+ */
212
+ export const OverflowFocusIndicator: Story = {
213
+ name: "Overflow tile keyboard focus",
214
+ args: { images: IMAGES },
215
+ play: async ({ canvas, userEvent }) => {
216
+ const overflow = canvas.getByRole("button", { name: /show all 12 images/i });
217
+ overflow.focus();
218
+ await expect(overflow).toHaveFocus();
219
+ const focused = getComputedStyle(overflow);
220
+ await expect(focused.boxShadow).not.toBe("none");
221
+ await expect(focused.outlineStyle).toBe("solid");
222
+ await expect(parseFloat(focused.outlineWidth)).toBeGreaterThan(0);
223
+ },
224
+ };
package/src/gallery.tsx CHANGED
@@ -372,7 +372,7 @@ function GalleryTile({ image, index }: { image: GalleryImage; index: number }) {
372
372
  type="button"
373
373
  onClick={() => actions.open(index)}
374
374
  aria-label={label}
375
- className="block w-full rounded-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
375
+ className="block w-full rounded-lg focus-ring"
376
376
  >
377
377
  <AspectRatio ratio={aspectRatio} className="overflow-hidden rounded-lg">
378
378
  {/* Decorative: the button carries the accessible name. */}
@@ -406,7 +406,7 @@ function GalleryOverflowTile() {
406
406
  type="button"
407
407
  onClick={() => actions.open(maxVisible)}
408
408
  aria-label={`Show all ${total} images`}
409
- className="block w-full rounded-lg border border-border bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
409
+ className="block w-full rounded-lg border border-border bg-muted focus-ring"
410
410
  >
411
411
  <AspectRatio ratio={aspectRatio} className="overflow-hidden rounded-lg">
412
412
  <span className="flex size-full items-center justify-center text-subtitle text-muted-foreground transition-colors duration-fast ease-standard group-hover:text-foreground motion-reduce:transition-none">
@@ -1,4 +1,5 @@
1
1
  import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { expect, userEvent } from "storybook/test";
2
3
  import { GroupedParts } from "./grouped-parts";
3
4
  import { groupPartByType, type GroupablePart } from "./part-groups";
4
5
 
@@ -102,3 +103,25 @@ export const DarkTheme: Story = {
102
103
  ),
103
104
  ],
104
105
  };
106
+
107
+ /**
108
+ * Focus indicator on group triggers (#313): Tab to a collapsible trigger and
109
+ * assert the compound indicator's OUTLINE layer (`outlineStyle: "solid"`,
110
+ * `--ring-contour`) that `focus-ring-inset` adds. `boxShadow !== "none"`
111
+ * alone also passes on the legacy `focus-visible:outline-none
112
+ * focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset`
113
+ * pattern (Tailwind's `ring-2` still paints a box-shadow), so it does not
114
+ * lock the fix.
115
+ */
116
+ export const FocusIndicator: Story = {
117
+ args: { parts },
118
+ play: async ({ canvas, userEvent }) => {
119
+ const triggers = canvas.getAllByRole("button");
120
+ await userEvent.tab();
121
+ await expect(triggers[0]).toHaveFocus();
122
+ const focused = getComputedStyle(triggers[0]);
123
+ await expect(focused.boxShadow).not.toBe("none");
124
+ await expect(focused.outlineStyle).toBe("solid");
125
+ await expect(parseFloat(focused.outlineWidth)).toBeGreaterThan(0);
126
+ },
127
+ };
@@ -87,7 +87,7 @@ function DefaultGroup({
87
87
  className={cn(
88
88
  "group flex w-full items-center gap-2 p-3 text-body text-muted-foreground",
89
89
  "transition-colors duration-fast ease-standard hover:text-foreground motion-reduce:transition-none",
90
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset",
90
+ "focus-ring-inset",
91
91
  )}
92
92
  >
93
93
  <BrainIcon aria-hidden="true" className="size-4 shrink-0" />
package/src/index.ts CHANGED
@@ -47,6 +47,7 @@ export * from "./gallery";
47
47
  export * from "./grouped-parts";
48
48
  export * from "./image";
49
49
  export * from "./inline-citation";
50
+ export * from "./a2ui";
50
51
  export * from "./jsx-preview";
51
52
  // Mermaid loads on first diagram render, not in the entry chunk. `preloadMermaid`
52
53
  // warms it early when a surface is known to render diagrams.
@@ -1,5 +1,6 @@
1
1
  import { useState } from "react";
2
2
  import type { Meta, StoryObj } from "@storybook/react-vite";
3
+ import { expect, userEvent, within } from "storybook/test";
3
4
  import { MessageCompare, MessageCompareColumn } from "./message-compare";
4
5
  import { MessageFeedback } from "./message-feedback";
5
6
  import { MessageResponse } from "./message";
@@ -173,3 +174,42 @@ export const Mobile: Story = {
173
174
  </div>
174
175
  ),
175
176
  };
177
+
178
+ /**
179
+ * Focus indicator on scrollable compare column (#313): Tab to the column
180
+ * body and assert the compound indicator's OUTLINE layer. The prior version
181
+ * read `role="region"` — that's `MessageCompareColumn`'s OUTER wrapper
182
+ * (`message-compare.tsx:416`, header + body), which never receives focus or
183
+ * `focus-ring-inset`. The actual scrollable, tabbable, styled element is the
184
+ * nested `[data-slot="message-compare-column-body"]` div
185
+ * (`message-compare.tsx:441-446`) — querying the region always read
186
+ * `boxShadow: "none"` off the wrong node, regardless of the fix.
187
+ */
188
+ export const FocusIndicator: Story = {
189
+ render: () => (
190
+ <div className="h-96">
191
+ <MessageCompare columns={2}>
192
+ <MessageCompareColumn model={{ name: "GPT-5" }} status="ready">
193
+ <MessageResponse>{GPT_ANSWER}</MessageResponse>
194
+ </MessageCompareColumn>
195
+ <MessageCompareColumn model={{ name: "Claude" }} status="ready">
196
+ <MessageResponse>{CLAUDE_ANSWER}</MessageResponse>
197
+ </MessageCompareColumn>
198
+ </MessageCompare>
199
+ </div>
200
+ ),
201
+ play: async ({ canvasElement, userEvent }) => {
202
+ const body = canvasElement.querySelector<HTMLElement>(
203
+ '[data-slot="message-compare-column-body"]',
204
+ );
205
+ if (body == null) {
206
+ throw new Error("expected a message-compare-column-body scroll container");
207
+ }
208
+ await userEvent.tab();
209
+ await expect(body).toHaveFocus();
210
+ const focused = getComputedStyle(body);
211
+ await expect(focused.boxShadow).not.toBe("none");
212
+ await expect(focused.outlineStyle).toBe("solid");
213
+ await expect(parseFloat(focused.outlineWidth)).toBeGreaterThan(0);
214
+ },
215
+ };
@@ -416,6 +416,7 @@ export const MessageCompareColumn = forwardRef<HTMLDivElement, MessageCompareCol
416
416
  role="region"
417
417
  {...props}
418
418
  >
419
+ {/* header-band-exempt: a column header inside the comparison grid, not shell chrome */}
419
420
  <div
420
421
  className="flex shrink-0 items-center justify-between gap-2 border-b px-3 py-2"
421
422
  data-slot="message-compare-column-header"
@@ -439,7 +440,7 @@ export const MessageCompareColumn = forwardRef<HTMLDivElement, MessageCompareCol
439
440
  )}
440
441
  </div>
441
442
  <div
442
- className="min-h-0 flex-1 overflow-y-auto p-4 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset"
443
+ className="min-h-0 flex-1 overflow-y-auto p-4 focus-ring-inset"
443
444
  data-slot="message-compare-column-body"
444
445
  onScroll={handleScroll}
445
446
  ref={setScrollRef}
@@ -689,7 +689,7 @@ export const MessageFormTitle = forwardRef<HTMLParagraphElement, MessageFormTitl
689
689
  <p
690
690
  ref={ref}
691
691
  id={headingId}
692
- className={cn("text-subtitle font-semibold text-foreground text-balance", className)}
692
+ className={cn("text-subtitle text-foreground text-balance", className)}
693
693
  {...props}
694
694
  >
695
695
  {content}
@@ -1,4 +1,5 @@
1
1
  import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { expect, userEvent } from "storybook/test";
2
3
  import { useState } from "react";
3
4
  import { MessageTable, type TableSort } from "./message-table";
4
5
  import type { TableSpec } from "./message-table-spec";
@@ -174,3 +175,26 @@ export const DarkTheme: Story = {
174
175
  ),
175
176
  ],
176
177
  };
178
+
179
+ /**
180
+ * Focus indicator on sortable column headers (#313): Tab to a sort header and
181
+ * assert the compound indicator's OUTLINE layer (`outlineStyle: "solid"`,
182
+ * `--ring-contour`) that `focus-ring` adds. `boxShadow !== "none"` alone also
183
+ * passes on the legacy `focus-visible:outline-none focus-visible:ring-2
184
+ * focus-visible:ring-ring focus-visible:ring-offset-1` pattern (Tailwind's
185
+ * `ring-2` still paints a box-shadow), so it does not lock the fix.
186
+ */
187
+ export const FocusIndicator: Story = {
188
+ args: { spec: invoicesSpec, sortable: true },
189
+ play: async ({ canvas, userEvent }) => {
190
+ const buttons = canvas.getAllByRole("button");
191
+ if (buttons.length > 0) {
192
+ await userEvent.tab();
193
+ await expect(buttons[0]).toHaveFocus();
194
+ const focused = getComputedStyle(buttons[0]);
195
+ await expect(focused.boxShadow).not.toBe("none");
196
+ await expect(focused.outlineStyle).toBe("solid");
197
+ await expect(parseFloat(focused.outlineWidth)).toBeGreaterThan(0);
198
+ }
199
+ },
200
+ };
@@ -301,7 +301,7 @@ export const MessageTable = forwardRef<HTMLDivElement, MessageTableProps>(functi
301
301
  onClick={() => toggleSort(column)}
302
302
  className={cn(
303
303
  "inline-flex items-center gap-1 rounded-sm font-medium",
304
- "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",
305
305
  "hover:text-foreground transition-colors duration-fast ease-standard motion-reduce:transition-none",
306
306
  numeric && "flex-row-reverse",
307
307
  )}
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import { Badge } from "@elabs-ai/components-ui";
3
+ import { Badge, useLocale } from "@elabs-ai/components-ui";
4
4
  import { cn } from "@elabs-ai/components-ui/lib/cn";
5
5
  import { ArrowRightIcon, MinusIcon, PackageIcon, PlusIcon } from "lucide-react";
6
6
  import type { HTMLAttributes } from "react";
@@ -173,14 +173,17 @@ export const PackageInfoDependencies = ({
173
173
  className,
174
174
  children,
175
175
  ...props
176
- }: PackageInfoDependenciesProps) => (
177
- <div className={cn("space-y-2", className)} {...props}>
178
- <span className="font-medium text-muted-foreground text-meta uppercase tracking-wide">
179
- Dependencies
180
- </span>
181
- <div className="space-y-1">{children}</div>
182
- </div>
183
- );
176
+ }: PackageInfoDependenciesProps) => {
177
+ const { t } = useLocale();
178
+ return (
179
+ <div className={cn("space-y-2", className)} {...props}>
180
+ <span className="text-eyebrow uppercase text-muted-foreground">
181
+ {t("ai.packageInfo.dependencies")}
182
+ </span>
183
+ <div className="space-y-1">{children}</div>
184
+ </div>
185
+ );
186
+ };
184
187
 
185
188
  export type PackageInfoDependencyProps = HTMLAttributes<HTMLDivElement> & {
186
189
  name: string;
@@ -1,4 +1,5 @@
1
1
  import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { expect } from "storybook/test";
2
3
  import { cn } from "@elabs-ai/components-ui/lib/cn";
3
4
  import { Reasoning, ReasoningContent, ReasoningTrigger } from "./reasoning";
4
5
  const meta = {
@@ -79,3 +80,29 @@ export const StructuredLedger: Story = {
79
80
  </Reasoning>
80
81
  ),
81
82
  };
83
+
84
+ /**
85
+ * Focus indicator: Tab to the trigger and assert a real box-shadow exists,
86
+ * mirroring the library's compound indicator (not the browser default).
87
+ */
88
+ export const FocusIndicator: Story = {
89
+ name: "Keyboard focus",
90
+ render: () => (
91
+ <Reasoning className="max-w-prose">
92
+ <ReasoningTrigger />
93
+ <ReasoningContent>{"Checked the schema and ran the aggregation."}</ReasoningContent>
94
+ </Reasoning>
95
+ ),
96
+ play: async ({ canvas, userEvent }) => {
97
+ // Find the trigger button inside the Collapsible component
98
+ const trigger = canvas.getByRole("button");
99
+
100
+ // Tab rather than .focus(): `:focus-visible` is what `focus-ring` keys
101
+ // on, and a programmatic focus does not reliably match it.
102
+ await userEvent.tab();
103
+ await expect(trigger).toHaveFocus();
104
+
105
+ const focused = getComputedStyle(trigger);
106
+ await expect(focused.boxShadow).not.toBe("none");
107
+ },
108
+ };
package/src/reasoning.tsx CHANGED
@@ -172,7 +172,7 @@ export const ReasoningTrigger = memo(
172
172
  return (
173
173
  <CollapsibleTrigger
174
174
  className={cn(
175
- "flex w-full items-center gap-2 text-muted-foreground text-body transition-colors hover:text-foreground",
175
+ "flex w-full items-center gap-2 text-muted-foreground text-body transition-colors hover:text-foreground focus-ring",
176
176
  className,
177
177
  )}
178
178
  {...props}
@@ -1,5 +1,5 @@
1
1
  import type { Meta, StoryObj } from "@storybook/react-vite";
2
- import { fn } from "storybook/test";
2
+ import { expect, fn, userEvent } from "storybook/test";
3
3
 
4
4
  import { ChatGreeting } from "./chat-greeting";
5
5
  import { SessionHeader } from "./session-header";
@@ -126,3 +126,37 @@ export const WithChatGreeting: Story = {
126
126
  </div>
127
127
  ),
128
128
  };
129
+
130
+ /**
131
+ * Focus indicator on whats-new link (#313): Tab to the link and assert the
132
+ * compound indicator's OUTLINE layer (`outlineStyle: "solid"`,
133
+ * `--ring-contour`) that `focus-ring` adds. `boxShadow !== "none"` alone
134
+ * also passes on the legacy `focus-visible:outline-none
135
+ * focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2`
136
+ * pattern (Tailwind's `ring-2` still paints a box-shadow), so it does not
137
+ * lock the fix.
138
+ */
139
+ export const FocusIndicator: Story = {
140
+ args: {
141
+ whatsNew: [
142
+ { label: "New feature", href: "https://example.com" },
143
+ { label: "Bug fix", href: "https://example.com" },
144
+ ],
145
+ },
146
+ render: (args) => (
147
+ <div className="mx-auto max-w-md bg-background">
148
+ <SessionHeader {...args} />
149
+ </div>
150
+ ),
151
+ play: async ({ canvas, userEvent }) => {
152
+ const links = canvas.getAllByRole("link");
153
+ if (links.length > 0) {
154
+ await userEvent.tab();
155
+ await expect(links[0]).toHaveFocus();
156
+ const focused = getComputedStyle(links[0]);
157
+ await expect(focused.boxShadow).not.toBe("none");
158
+ await expect(focused.outlineStyle).toBe("solid");
159
+ await expect(parseFloat(focused.outlineWidth)).toBeGreaterThan(0);
160
+ }
161
+ },
162
+ };
@@ -194,7 +194,7 @@ export const SessionHeader = forwardRef<HTMLDivElement, SessionHeaderProps>(func
194
194
  {item.href != null ? (
195
195
  <a
196
196
  href={item.href}
197
- className="text-link underline-offset-4 hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
197
+ className="text-link underline-offset-4 hover:underline focus-ring"
198
198
  >
199
199
  {item.label}
200
200
  </a>
@@ -1,4 +1,5 @@
1
1
  import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { expect } from "storybook/test";
2
3
  import { Source, SourceList, Sources, SourcesContent, SourcesTrigger } from "./sources";
3
4
  const meta = {
4
5
  title: "AI/Sources",
@@ -32,3 +33,32 @@ export const GroundingFooter: Story = {
32
33
  />
33
34
  ),
34
35
  };
36
+
37
+ /**
38
+ * Focus indicator: Tab to the trigger and assert a real box-shadow exists,
39
+ * mirroring the library's compound indicator (not the browser default).
40
+ */
41
+ export const FocusIndicator: Story = {
42
+ name: "Keyboard focus",
43
+ render: () => (
44
+ <Sources>
45
+ <SourcesTrigger count={2} />
46
+ <SourcesContent>
47
+ <Source href="https://docs.acme.com" title="Acme Docs" />
48
+ <Source href="https://ci.acme.com" title="CI run #42" />
49
+ </SourcesContent>
50
+ </Sources>
51
+ ),
52
+ play: async ({ canvas, userEvent }) => {
53
+ // Find the trigger button inside the Collapsible component
54
+ const trigger = canvas.getByRole("button");
55
+
56
+ // Tab rather than .focus(): `:focus-visible` is what `focus-ring` keys
57
+ // on, and a programmatic focus does not reliably match it.
58
+ await userEvent.tab();
59
+ await expect(trigger).toHaveFocus();
60
+
61
+ const focused = getComputedStyle(trigger);
62
+ await expect(focused.boxShadow).not.toBe("none");
63
+ },
64
+ };
package/src/sources.tsx CHANGED
@@ -16,7 +16,10 @@ export type SourcesTriggerProps = ComponentProps<typeof CollapsibleTrigger> & {
16
16
  };
17
17
 
18
18
  export const SourcesTrigger = ({ className, count, children, ...props }: SourcesTriggerProps) => (
19
- <CollapsibleTrigger className={cn("group flex items-center gap-2", className)} {...props}>
19
+ <CollapsibleTrigger
20
+ className={cn("group flex items-center gap-2 focus-ring", className)}
21
+ {...props}
22
+ >
20
23
  {children ?? (
21
24
  <>
22
25
  <p className="font-medium">Used {count} sources</p>