@kitn.ai/ui 0.20.0 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (500) hide show
  1. package/README.md +44 -45
  2. package/dist/components/artifact-card.d.ts +51 -0
  3. package/dist/components/artifact.d.ts +1 -1
  4. package/dist/components/attachments.d.ts +2 -2
  5. package/dist/components/audio-visualizer/index.d.ts +3 -3
  6. package/dist/components/audio-visualizer/labs/lab-choreography.d.ts +59 -0
  7. package/dist/components/audio-visualizer/labs/lab-shaders.d.ts +22 -0
  8. package/dist/components/audio-visualizer/labs/lab-visualizer.d.ts +18 -0
  9. package/dist/components/audio-visualizer/variant-aurora.d.ts +2 -2
  10. package/dist/components/audio-visualizer/variant-bar.d.ts +2 -2
  11. package/dist/components/audio-visualizer/variant-custom.d.ts +2 -2
  12. package/dist/components/audio-visualizer/variant-grid.d.ts +1 -1
  13. package/dist/components/audio-visualizer/variant-radial.d.ts +1 -1
  14. package/dist/components/audio-visualizer/variant-wave.d.ts +1 -1
  15. package/dist/components/card-fallback.d.ts +12 -2
  16. package/dist/components/card-renderer.d.ts +63 -2
  17. package/dist/components/chat-container.d.ts +3 -0
  18. package/dist/components/chat-scope-picker.d.ts +2 -2
  19. package/dist/components/chat-thread.d.ts +68 -18
  20. package/dist/components/choice-card.d.ts +3 -28
  21. package/dist/components/composer-dom.d.ts +1 -1
  22. package/dist/components/composer-highlight.d.ts +1 -1
  23. package/dist/components/composer-history.d.ts +1 -1
  24. package/dist/components/composer.d.ts +1 -1
  25. package/dist/components/confirm-card.d.ts +3 -18
  26. package/dist/components/context.d.ts +8 -4
  27. package/dist/components/conversation-item.d.ts +1 -1
  28. package/dist/components/conversation-list.d.ts +4 -3
  29. package/dist/components/embed.d.ts +3 -3
  30. package/dist/components/file-tree.d.ts +11 -22
  31. package/dist/components/form-widgets.d.ts +1 -1
  32. package/dist/components/form.d.ts +3 -44
  33. package/dist/components/link-preview.d.ts +3 -3
  34. package/dist/components/loader.d.ts +30 -48
  35. package/dist/components/message-skills.d.ts +4 -0
  36. package/dist/components/message.d.ts +84 -21
  37. package/dist/components/model-switcher.d.ts +2 -2
  38. package/dist/components/prompt-input.d.ts +2 -2
  39. package/dist/components/response-compare-types.d.ts +11 -2
  40. package/dist/components/response-compare.d.ts +3 -3
  41. package/dist/components/source.d.ts +12 -3
  42. package/dist/components/tasks-card.d.ts +3 -31
  43. package/dist/components/thread.d.ts +17 -5
  44. package/dist/components/toast.d.ts +2 -2
  45. package/dist/components/tool-classify.d.ts +8 -0
  46. package/dist/components/tool-types.d.ts +29 -1
  47. package/dist/components/tool.d.ts +3 -3
  48. package/dist/components/use-card-resolution.d.ts +1 -1
  49. package/dist/create-tween-D3Y5qiij.js +338 -0
  50. package/dist/create-tween-DGKY1FDY.js +341 -0
  51. package/dist/create-tween-paho5Rh-.js +7 -0
  52. package/dist/custom-elements.json +1681 -454
  53. package/dist/elements/agent-card.js +1 -1
  54. package/dist/elements/artifact.js +1 -1
  55. package/dist/elements/attachments.js +1 -1
  56. package/dist/elements/audio-visualizer.js +1 -1
  57. package/dist/elements/avatar.js +1 -1
  58. package/dist/elements/badge.js +1 -1
  59. package/dist/elements/button.js +1 -1
  60. package/dist/elements/card.js +1 -1
  61. package/dist/elements/cards.js +1 -1
  62. package/dist/elements/chain-of-thought.js +1 -1
  63. package/dist/elements/chat-scope-picker.js +1 -1
  64. package/dist/elements/chat-types.d.ts +70 -10
  65. package/dist/elements/chat-workspace.js +1 -1
  66. package/dist/elements/chat.js +1 -1
  67. package/dist/elements/checkpoint.js +1 -1
  68. package/dist/elements/choice.js +1 -1
  69. package/dist/elements/chunks/{Icon-Tj58ES_d.js → Icon-C-2LvGDG.js} +1 -1
  70. package/dist/elements/chunks/{action-icons-ns1Fnedj.js → action-icons-CaYoImEk.js} +1 -1
  71. package/dist/elements/chunks/arrow-left-Bb46H7w9.js +1 -0
  72. package/dist/elements/chunks/artifact-BBIscOxJ.js +1 -0
  73. package/dist/elements/chunks/{attachments-mCB5F1Fq.js → attachments-BB56zoji.js} +1 -1
  74. package/dist/elements/chunks/audio-visualizer-BxOx4THz.js +1 -0
  75. package/dist/elements/chunks/{badge-B4Zuajl2.js → badge-SxLSZ19R.js} +1 -1
  76. package/dist/elements/chunks/{button-ZWNif3--.js → button-CmkUkz6c.js} +1 -1
  77. package/dist/elements/chunks/card-renderer-Db4ffkWv.js +1 -0
  78. package/dist/elements/chunks/card-tags-D8lZ-C_U.js +1 -0
  79. package/dist/elements/chunks/chat-thread-B3Ks-cKr.js +1 -0
  80. package/dist/elements/chunks/check-C8desc_h.js +1 -0
  81. package/dist/elements/chunks/chevron-down-DKtc2HNB.js +1 -0
  82. package/dist/elements/chunks/chevron-right-Dw4GPhwL.js +1 -0
  83. package/dist/elements/chunks/choice-card-Q9cYYDyM.js +1 -0
  84. package/dist/elements/chunks/circle-BeokBsvB.js +1 -0
  85. package/dist/elements/chunks/circle-check-wJSqt859.js +1 -0
  86. package/dist/elements/chunks/{circle-x-B7faJFlY.js → circle-x-D0MEfxOM.js} +1 -1
  87. package/dist/elements/chunks/code-block-C8VzAraE.js +1 -0
  88. package/dist/elements/chunks/{collapsible-DM1Ckd4e.js → collapsible-Cvj61obE.js} +1 -1
  89. package/dist/elements/chunks/{composer-BguS0QL_.js → composer-D86ztyL0.js} +2 -2
  90. package/dist/elements/chunks/confirm-card-BdyDAGLw.js +1 -0
  91. package/dist/elements/chunks/context-WHDl-xSx.js +1 -0
  92. package/dist/elements/chunks/conversation-list-CdR14_Y4.js +1 -0
  93. package/dist/elements/chunks/create-tween-C_A_B638.js +7 -0
  94. package/dist/elements/chunks/{default-input-Cqqilg9t.js → default-input-CSZqUM81.js} +1 -1
  95. package/dist/elements/chunks/define-BzPnNsXg.js +1 -0
  96. package/dist/elements/chunks/{disclosure-CIZ5c8nO.js → disclosure-DVKsoc1F.js} +1 -1
  97. package/dist/elements/chunks/{download-Dsbg3ste.js → download-BjA54IMJ.js} +1 -1
  98. package/dist/elements/chunks/{dropdown-BDd1-Zgi.js → dropdown-BjVVHWXH.js} +1 -1
  99. package/dist/elements/chunks/{ellipsis-Dw6qrss-.js → ellipsis-BNedmqfi.js} +1 -1
  100. package/dist/elements/chunks/embed-DQsl4XEq.js +1 -0
  101. package/dist/elements/chunks/{external-link-Vt74zAoL.js → external-link-Di-dw2CU.js} +1 -1
  102. package/dist/elements/chunks/{file-text-CZrZF6_v.js → file-text-B3sAODCV.js} +1 -1
  103. package/dist/elements/chunks/{file-tree-DF4okKyo.js → file-tree-Dgto-bMn.js} +1 -1
  104. package/dist/elements/chunks/{folder-glP5rha4.js → folder-D6rMadFV.js} +1 -1
  105. package/dist/elements/chunks/form-CILxvhQ7.js +1 -0
  106. package/dist/elements/chunks/hover-card-BtiF4XiU.js +1 -0
  107. package/dist/elements/chunks/{icon-BLb8beoi.js → icon-DKkmze3y.js} +1 -1
  108. package/dist/elements/chunks/{info-CIsv92JK.js → info-CWRjZkuy.js} +1 -1
  109. package/dist/elements/chunks/{input-Dds1kFyt.js → input--QJBK6BA.js} +1 -1
  110. package/dist/elements/chunks/{kbd-De_xbgi9.js → kbd-C3zuKh8R.js} +1 -1
  111. package/dist/elements/chunks/{link-i4r_k7z4.js → link-BL5JA7Q0.js} +1 -1
  112. package/dist/elements/chunks/link-preview-CU37bbXh.js +1 -0
  113. package/dist/elements/chunks/{loader-BmQH4s3V.js → loader-XmpyxyRH.js} +1 -1
  114. package/dist/elements/chunks/{markdown-Zi21WhUz.js → markdown-3Gv5UZ_o.js} +1 -1
  115. package/dist/elements/chunks/message-CnBA2OUt.js +1 -0
  116. package/dist/elements/chunks/message-X5NZbq-W.js +1 -0
  117. package/dist/elements/chunks/message-square-CnpAm_P0.js +1 -0
  118. package/dist/elements/chunks/{minimize-2--ifeCGrl.js → minimize-2-DwyrUTcM.js} +1 -1
  119. package/dist/elements/chunks/{model-switcher-B94YAKsM.js → model-switcher-CclaJEHt.js} +1 -1
  120. package/dist/elements/chunks/{overlay-kD8dwRv_.js → overlay-Cepd-KcS.js} +1 -1
  121. package/dist/elements/chunks/{paperclip-q21dYOSK.js → paperclip-CdwKjrlu.js} +1 -1
  122. package/dist/elements/chunks/{progress-bar-BrAIIaaJ.js → progress-bar-DMVXBD3b.js} +1 -1
  123. package/dist/elements/chunks/{prompt-suggestion-Bjn2K-fR.js → prompt-suggestion-Di_WZE_5.js} +1 -1
  124. package/dist/elements/chunks/reasoning-D0jMch5u.js +1 -0
  125. package/dist/elements/chunks/{resizable-Dp4JExql.js → resizable-1znbL1Gv.js} +1 -1
  126. package/dist/elements/chunks/{rotate-cw-CFWWkpj2.js → rotate-cw-DVhhgx8n.js} +1 -1
  127. package/dist/elements/chunks/{scroll-area-Bb0kHH1j.js → scroll-area-Bs4bFS1P.js} +1 -1
  128. package/dist/elements/chunks/scroll-button-Bet5NLc4.js +1 -0
  129. package/dist/elements/chunks/{separator-9kRgr1X9.js → separator-C5Gh8qGC.js} +1 -1
  130. package/dist/elements/chunks/{settings-B8lmec4D.js → settings-5vmEFg0K.js} +1 -1
  131. package/dist/elements/chunks/{settings-group-I0hEuXiN.js → settings-group-BWLB38kz.js} +1 -1
  132. package/dist/elements/chunks/{share-C3lTiYGt.js → share-BZ3G_DaB.js} +1 -1
  133. package/dist/elements/chunks/skeleton-DafPJQTp.js +1 -0
  134. package/dist/elements/chunks/slots-CIw9RlAe.js +1 -0
  135. package/dist/elements/chunks/source-D_qj_6uJ.js +1 -0
  136. package/dist/elements/chunks/{star-CSJtGxgv.js → star-Cuv7j2IE.js} +1 -1
  137. package/dist/elements/chunks/{store-CHyW95TP.js → store-C1StPHfW.js} +1 -1
  138. package/dist/elements/chunks/tasks-card-EyoBZfpA.js +1 -0
  139. package/dist/elements/chunks/{text-shimmer-Cn9TmNpI.js → text-shimmer-D9gRSh34.js} +1 -1
  140. package/dist/elements/chunks/textarea-DuhtHfcm.js +1 -0
  141. package/dist/elements/chunks/{thumbs-up-PNVVThEF.js → thumbs-up-B9hwv43w.js} +1 -1
  142. package/dist/elements/chunks/{toast-store-DKbQn3Wn.js → toast-store-Bm3DUEDe.js} +1 -1
  143. package/dist/elements/chunks/{tool-DjTRpIpl.js → tool-CDtW7L0z.js} +1 -1
  144. package/dist/elements/chunks/tooltip-eB226mGK.js +1 -0
  145. package/dist/elements/chunks/{triangle-alert-DsHTHCPm.js → triangle-alert-C1SNzTqK.js} +1 -1
  146. package/dist/elements/chunks/use-card-resolution-YZh2MvI5.js +1 -0
  147. package/dist/elements/chunks/{variant-aurora-Dc4nQUia.js → variant-aurora-Cj1F7D0l.js} +2 -2
  148. package/dist/elements/chunks/{variant-custom-y_3kVQhS.js → variant-custom-T9gpt7QE.js} +1 -1
  149. package/dist/elements/chunks/{variant-wave-D5OR1-9Z.js → variant-wave-CxU9z-TE.js} +2 -2
  150. package/dist/elements/chunks/{video-B7vmenoC.js → video-CK4R7Rus.js} +1 -1
  151. package/dist/elements/chunks/x-D1qx0aLd.js +1 -0
  152. package/dist/elements/coachmark.js +1 -1
  153. package/dist/elements/code-block.js +1 -1
  154. package/dist/elements/command.js +1 -1
  155. package/dist/elements/compare.js +1 -1
  156. package/dist/elements/composer.js +1 -1
  157. package/dist/elements/confirm-card.js +1 -1
  158. package/dist/elements/context-meter.js +1 -1
  159. package/dist/elements/conversation-list.js +1 -1
  160. package/dist/elements/default-input.d.ts +7 -7
  161. package/dist/elements/define.d.ts +1 -0
  162. package/dist/elements/dialog.js +1 -1
  163. package/dist/elements/disclosure.d.ts +1 -1
  164. package/dist/elements/editable-label.js +1 -1
  165. package/dist/elements/element-data-types.d.ts +107 -0
  166. package/dist/elements/embed.js +1 -1
  167. package/dist/elements/empty.js +1 -1
  168. package/dist/elements/feedback-bar.js +1 -1
  169. package/dist/elements/file-tree.js +1 -1
  170. package/dist/elements/file-upload.js +1 -1
  171. package/dist/elements/form.js +1 -1
  172. package/dist/elements/hover-card.js +1 -1
  173. package/dist/elements/icon.js +1 -1
  174. package/dist/elements/image.js +1 -1
  175. package/dist/elements/input.js +1 -1
  176. package/dist/elements/kbd.js +1 -1
  177. package/dist/elements/link-preview.js +1 -1
  178. package/dist/elements/loader.js +1 -1
  179. package/dist/elements/markdown.js +1 -1
  180. package/dist/elements/menu.js +1 -1
  181. package/dist/elements/message-skills.js +1 -1
  182. package/dist/elements/message.js +1 -1
  183. package/dist/elements/model-switcher.js +1 -1
  184. package/dist/elements/nav.js +1 -1
  185. package/dist/elements/notice.js +1 -1
  186. package/dist/elements/pane-group.js +1 -1
  187. package/dist/elements/pane.js +1 -1
  188. package/dist/elements/popover.js +1 -1
  189. package/dist/elements/progress-bar.js +1 -1
  190. package/dist/elements/prompt-dock.js +1 -1
  191. package/dist/elements/prompt-input.js +1 -1
  192. package/dist/elements/prompt-suggestions.js +1 -1
  193. package/dist/elements/reasoning.js +1 -1
  194. package/dist/elements/register.d.ts +8 -6
  195. package/dist/elements/remote.js +1 -1
  196. package/dist/elements/resizable.js +1 -1
  197. package/dist/elements/response-stream.js +2 -2
  198. package/dist/elements/screen.js +1 -1
  199. package/dist/elements/scroll-area.js +1 -1
  200. package/dist/elements/scroll-button.js +1 -1
  201. package/dist/elements/search.js +1 -1
  202. package/dist/elements/segmented.js +1 -1
  203. package/dist/elements/separator.js +1 -1
  204. package/dist/elements/setting-item.js +1 -1
  205. package/dist/elements/settings-group.js +1 -1
  206. package/dist/elements/skeleton.js +1 -1
  207. package/dist/elements/slots.d.ts +16 -0
  208. package/dist/elements/source.js +1 -1
  209. package/dist/elements/status.js +1 -1
  210. package/dist/elements/switch.js +1 -1
  211. package/dist/elements/tabs.js +1 -1
  212. package/dist/elements/tasks.js +1 -1
  213. package/dist/elements/text-shimmer.js +1 -1
  214. package/dist/elements/thinking-bar.js +1 -1
  215. package/dist/elements/thread.js +1 -1
  216. package/dist/elements/toast.js +1 -1
  217. package/dist/elements/tool.js +1 -1
  218. package/dist/elements/tooltip.js +1 -1
  219. package/dist/elements/validate-messages.d.ts +25 -0
  220. package/dist/elements/voice-input.js +1 -1
  221. package/dist/elements/voice-output.js +1 -1
  222. package/dist/elements.d.ts +2725 -214
  223. package/dist/index.d.ts +163 -125
  224. package/dist/index.js +6336 -5802
  225. package/dist/index.server.d.ts +5 -0
  226. package/dist/index.server.js +16913 -0
  227. package/dist/kai-provider.es.d.ts +5 -0
  228. package/dist/kai-provider.es.js +79 -77
  229. package/dist/kai.es.d.ts +5 -0
  230. package/dist/kai.es.js +1 -1
  231. package/dist/llms/llms-full.txt +762 -148
  232. package/dist/llms/llms.txt +57 -5
  233. package/dist/mcp.es.js +4010 -770
  234. package/dist/primitives/card-component-types.d.ts +11 -0
  235. package/dist/primitives/card-data-types.d.ts +196 -0
  236. package/dist/primitives/card-host.d.ts +1 -1
  237. package/dist/primitives/card-recovery.d.ts +3 -3
  238. package/dist/primitives/card-registry.d.ts +4 -14
  239. package/dist/primitives/card-resolution.d.ts +1 -1
  240. package/dist/primitives/card-routing.d.ts +1 -1
  241. package/dist/primitives/card-tags.d.ts +11 -0
  242. package/dist/primitives/card-validate-cards.d.ts +57 -0
  243. package/dist/primitives/card-validate-generator.testlib.d.ts +20 -0
  244. package/dist/primitives/card-validate-schemas.d.ts +17 -0
  245. package/dist/primitives/card-validate.d.ts +24 -0
  246. package/dist/primitives/create-kai-chat.d.ts +3 -3
  247. package/dist/primitives/embed-providers.d.ts +1 -1
  248. package/dist/primitives/link-preview.d.ts +1 -1
  249. package/dist/primitives/message-feedback.d.ts +2 -2
  250. package/dist/primitives/toast-store.d.ts +1 -1
  251. package/dist/react/index.d.ts +717 -299
  252. package/dist/react/use-kai-chat.d.ts +8 -4
  253. package/dist/react.d.ts +5 -0
  254. package/dist/react.js +417 -316
  255. package/dist/register-impl-bzrZ44ft.js +145 -0
  256. package/dist/remote/host-embed.d.ts +1 -1
  257. package/dist/remote/index.d.ts +2 -2
  258. package/dist/remote/provider-runtime.d.ts +2 -2
  259. package/dist/remote/provider.d.ts +2 -2
  260. package/dist/remote/validate.d.ts +1 -1
  261. package/dist/remote/wire.d.ts +1 -1
  262. package/dist/schemas/artifact.schema.json +92 -0
  263. package/dist/schemas/embed.schema.json +0 -2
  264. package/dist/schemas/from-tool-call.d.ts +48 -0
  265. package/dist/schemas/index.d.ts +48 -0
  266. package/dist/schemas/link.schema.json +0 -2
  267. package/dist/schemas/provider-subsets.d.ts +136 -0
  268. package/dist/schemas/registry.d.ts +140 -0
  269. package/dist/schemas/tool-defs.d.ts +121 -0
  270. package/dist/schemas.d.ts +5 -0
  271. package/dist/schemas.js +717 -0
  272. package/dist/solid-BCJUQqwS.js +22750 -0
  273. package/dist/solid-BdENBcEq.js +20108 -0
  274. package/dist/solid.d.ts +66 -0
  275. package/dist/solid.js +261 -0
  276. package/dist/solid.server.d.ts +5 -0
  277. package/dist/solid.server.js +261 -0
  278. package/dist/state/index.d.ts +14 -4
  279. package/dist/state/messages.d.ts +8 -3
  280. package/dist/state/mock.d.ts +46 -0
  281. package/dist/state/parts.d.ts +85 -0
  282. package/dist/state/stream.d.ts +15 -9
  283. package/dist/state.d.ts +5 -0
  284. package/dist/state.js +253 -84
  285. package/dist/ui/action-icons.d.ts +1 -1
  286. package/dist/ui/collapsible.d.ts +11 -6
  287. package/dist/ui/dropdown.d.ts +47 -42
  288. package/dist/ui/pane-group.d.ts +1 -1
  289. package/dist/variant-aurora-1wMcXRUS.js +381 -0
  290. package/dist/variant-aurora-BKHHiOgL.js +391 -0
  291. package/dist/{variant-aurora-DVtGqVH6.js → variant-aurora-DOb_o4pC.js} +1 -1
  292. package/dist/{variant-custom-Dif-IV4u.js → variant-custom-CJGB0Kbw.js} +1 -1
  293. package/dist/variant-custom-Co0CkXJF.js +200 -0
  294. package/dist/variant-custom-DWSSgmd2.js +170 -0
  295. package/dist/variant-wave-C3-45cL6.js +218 -0
  296. package/dist/{variant-wave-DbfNdvSi.js → variant-wave-CEODq-9q.js} +1 -1
  297. package/dist/variant-wave-jdXBnCUZ.js +208 -0
  298. package/dist/wire/chunk.d.ts +171 -0
  299. package/dist/wire/consume.d.ts +26 -0
  300. package/dist/wire/encode.d.ts +148 -0
  301. package/dist/wire/formats/anthropic.d.ts +2 -0
  302. package/dist/wire/formats/openai.d.ts +5 -0
  303. package/dist/wire/index.d.ts +14 -0
  304. package/dist/wire/read.d.ts +26 -0
  305. package/dist/wire/sink-helpers.d.ts +21 -0
  306. package/dist/wire/sse.d.ts +33 -0
  307. package/dist/wire.d.ts +5 -53
  308. package/dist/wire.js +898 -0
  309. package/frameworks/react/index.tsx +183 -154
  310. package/frameworks/react/runtime.tsx +21 -1
  311. package/frameworks/react/use-kai-chat.tsx +11 -1
  312. package/llms-full.txt +762 -148
  313. package/llms.txt +57 -5
  314. package/package.json +65 -7
  315. package/src/agent-tooling/archetypes.ts +96 -0
  316. package/src/agent-tooling/integrations/anthropic.ts +412 -0
  317. package/src/agent-tooling/integrations/cloudflare.ts +136 -52
  318. package/src/agent-tooling/integrations/langgraph.ts +67 -13
  319. package/src/agent-tooling/integrations/mastra.ts +120 -12
  320. package/src/agent-tooling/integrations/mock.ts +43 -8
  321. package/src/agent-tooling/integrations/ollama.ts +61 -24
  322. package/src/agent-tooling/integrations/openai.ts +87 -0
  323. package/src/agent-tooling/integrations/openrouter.ts +56 -8
  324. package/src/agent-tooling/integrations/pi.ts +79 -23
  325. package/src/agent-tooling/integrations/pydantic-ai.ts +24 -2
  326. package/src/agent-tooling/integrations/vercel-ai-sdk.ts +84 -13
  327. package/src/agent-tooling/mcp/manifest.ts +206 -25
  328. package/src/agent-tooling/mcp/tools/debug.ts +13 -7
  329. package/src/agent-tooling/mcp/tools/reference.ts +285 -6
  330. package/src/agent-tooling/mcp/tools/scaffold.ts +3345 -675
  331. package/src/agent-tooling/registry.ts +17 -0
  332. package/src/agent-tooling/types.ts +229 -1
  333. package/src/components/artifact-card.tsx +120 -0
  334. package/src/components/audio-visualizer/index.tsx +9 -0
  335. package/src/components/audio-visualizer/labs/lab-choreography.ts +187 -0
  336. package/src/components/audio-visualizer/labs/lab-shaders.ts +363 -0
  337. package/src/components/audio-visualizer/labs/lab-visualizer.tsx +102 -0
  338. package/src/components/audio-visualizer/shader-canvas.tsx +31 -1
  339. package/src/components/card-fallback.tsx +42 -10
  340. package/src/components/card-renderer.tsx +146 -9
  341. package/src/components/chat-container.tsx +4 -0
  342. package/src/components/chat-thread.tsx +118 -51
  343. package/src/components/choice-card.tsx +17 -29
  344. package/src/components/composer.tsx +12 -2
  345. package/src/components/confirm-card.tsx +24 -20
  346. package/src/components/context.tsx +12 -4
  347. package/src/components/conversation-list.tsx +18 -2
  348. package/src/components/file-tree.tsx +11 -22
  349. package/src/components/file-upload.tsx +14 -8
  350. package/src/components/form.tsx +14 -47
  351. package/src/components/loader.tsx +39 -12
  352. package/src/components/message-skills.tsx +4 -0
  353. package/src/components/message.tsx +294 -46
  354. package/src/components/reasoning.tsx +92 -5
  355. package/src/components/response-compare-types.ts +25 -0
  356. package/src/components/response-compare.tsx +4 -9
  357. package/src/components/source.tsx +50 -24
  358. package/src/components/tasks-card.tsx +10 -33
  359. package/src/components/thread.tsx +84 -42
  360. package/src/components/toast.tsx +11 -5
  361. package/src/components/tool-classify.ts +26 -0
  362. package/src/components/tool-types.ts +31 -1
  363. package/src/elements/agent-card.tsx +11 -11
  364. package/src/elements/artifact.tsx +15 -7
  365. package/src/elements/attachments.tsx +6 -4
  366. package/src/elements/avatar.tsx +1 -1
  367. package/src/elements/button.tsx +3 -3
  368. package/src/elements/card.tsx +2 -2
  369. package/src/elements/cards.tsx +109 -14
  370. package/src/elements/chain-of-thought.tsx +14 -10
  371. package/src/elements/chat-scope-picker.tsx +9 -7
  372. package/src/elements/chat-types.ts +59 -6
  373. package/src/elements/chat-workspace.tsx +50 -9
  374. package/src/elements/chat.tsx +64 -3
  375. package/src/elements/choice.tsx +8 -8
  376. package/src/elements/coachmark.tsx +1 -1
  377. package/src/elements/command.tsx +8 -23
  378. package/src/elements/compare.tsx +7 -6
  379. package/src/elements/compiled.css +1 -1
  380. package/src/elements/composer.tsx +8 -8
  381. package/src/elements/confirm-card.tsx +9 -9
  382. package/src/elements/context-meter.tsx +4 -18
  383. package/src/elements/conversation-list.tsx +14 -9
  384. package/src/elements/default-input.tsx +3 -3
  385. package/src/elements/define.tsx +178 -43
  386. package/src/elements/editable-label.tsx +17 -2
  387. package/src/elements/element-data-types.ts +112 -0
  388. package/src/elements/element-meta.json +1596 -304
  389. package/src/elements/element-types.d.ts +2636 -211
  390. package/src/elements/feedback-bar.tsx +1 -1
  391. package/src/elements/file-tree.tsx +1 -1
  392. package/src/elements/file-upload.tsx +1 -1
  393. package/src/elements/form.tsx +14 -6
  394. package/src/elements/input.tsx +1 -1
  395. package/src/elements/kbd.tsx +1 -1
  396. package/src/elements/menu.tsx +10 -29
  397. package/src/elements/message-skills.tsx +7 -9
  398. package/src/elements/message.tsx +200 -32
  399. package/src/elements/model-switcher.tsx +5 -3
  400. package/src/elements/pane.tsx +1 -1
  401. package/src/elements/prompt-dock.tsx +8 -8
  402. package/src/elements/prompt-input.tsx +10 -10
  403. package/src/elements/prompt-suggestions.tsx +7 -4
  404. package/src/elements/reasoning.tsx +1 -1
  405. package/src/elements/register.ts +9 -0
  406. package/src/elements/resizable.globals.d.ts +36 -0
  407. package/src/elements/resizable.tsx +24 -13
  408. package/src/elements/response-stream.tsx +1 -1
  409. package/src/elements/scroll-button.tsx +1 -1
  410. package/src/elements/search.tsx +6 -1
  411. package/src/elements/segmented.tsx +5 -11
  412. package/src/elements/separator.tsx +1 -1
  413. package/src/elements/slots.ts +57 -24
  414. package/src/elements/source.tsx +21 -22
  415. package/src/elements/switch.tsx +3 -3
  416. package/src/elements/tasks.tsx +5 -4
  417. package/src/elements/thread.tsx +36 -4
  418. package/src/elements/toast.tsx +3 -2
  419. package/src/elements/tool.tsx +1 -1
  420. package/src/elements/validate-messages.ts +76 -0
  421. package/src/elements/voice-input.tsx +7 -7
  422. package/src/elements/voice-output.tsx +4 -4
  423. package/src/index.ts +141 -6
  424. package/src/primitives/card-component-types.ts +63 -0
  425. package/src/primitives/card-data-types.ts +312 -0
  426. package/src/primitives/card-recovery.ts +13 -2
  427. package/src/primitives/card-registry.tsx +34 -23
  428. package/src/primitives/card-schemas/artifact.schema.json +92 -0
  429. package/src/primitives/card-schemas/embed.schema.json +0 -2
  430. package/src/primitives/card-schemas/link.schema.json +0 -2
  431. package/src/primitives/card-tags.ts +61 -0
  432. package/src/primitives/card-validate-cards.ts +255 -0
  433. package/src/primitives/card-validate-generator.testlib.ts +38 -0
  434. package/src/primitives/card-validate-schemas.ts +58 -0
  435. package/src/primitives/card-validate.ts +48 -20
  436. package/src/primitives/create-tween.ts +54 -2
  437. package/src/primitives/message-feedback.ts +3 -2
  438. package/src/primitives/use-audio-analysis.ts +20 -1
  439. package/src/primitives/use-sequencer.ts +20 -1
  440. package/src/schemas/from-tool-call.ts +151 -0
  441. package/src/schemas/index.ts +340 -0
  442. package/src/schemas/provider-subsets.ts +538 -0
  443. package/src/schemas/registry.ts +355 -0
  444. package/src/schemas/tool-defs.ts +492 -0
  445. package/src/solid.ts +150 -0
  446. package/src/state/index.ts +27 -1
  447. package/src/state/messages.ts +21 -4
  448. package/src/state/mock.ts +211 -0
  449. package/src/state/parts.ts +295 -0
  450. package/src/state/stream.ts +56 -58
  451. package/src/ui/collapsible.tsx +17 -5
  452. package/src/ui/dropdown.tsx +46 -12
  453. package/src/ui/overlay.tsx +10 -4
  454. package/src/wire/chunk.ts +201 -0
  455. package/src/wire/consume.ts +440 -0
  456. package/src/wire/encode.ts +474 -0
  457. package/src/wire/formats/anthropic.ts +267 -0
  458. package/src/wire/formats/openai.ts +187 -0
  459. package/src/wire/index.ts +52 -0
  460. package/src/wire/read.ts +152 -0
  461. package/src/wire/sink-helpers.ts +50 -0
  462. package/src/wire/sse.ts +140 -0
  463. package/dist/create-tween-hvpmSuwW.js +0 -7
  464. package/dist/elements/chunks/arrow-left-DQocUFvy.js +0 -1
  465. package/dist/elements/chunks/audio-visualizer-BqNy7dfm.js +0 -1
  466. package/dist/elements/chunks/card-BkHbp4BA.js +0 -1
  467. package/dist/elements/chunks/chat-thread-CkRN6pZ7.js +0 -1
  468. package/dist/elements/chunks/check-B9-gn6SS.js +0 -1
  469. package/dist/elements/chunks/chevron-down-BNsxc2hk.js +0 -1
  470. package/dist/elements/chunks/chevron-right-Hv00StBO.js +0 -1
  471. package/dist/elements/chunks/circle-0e3KJTDr.js +0 -1
  472. package/dist/elements/chunks/circle-check-BNAS0JEy.js +0 -1
  473. package/dist/elements/chunks/code-block-BFUHn6Va.js +0 -1
  474. package/dist/elements/chunks/context-Cb7k4F-v.js +0 -1
  475. package/dist/elements/chunks/conversation-list-CvoAkWhN.js +0 -1
  476. package/dist/elements/chunks/create-tween-oVvAIG8r.js +0 -7
  477. package/dist/elements/chunks/define-D1din7Ld.js +0 -1
  478. package/dist/elements/chunks/hover-card-Cmm6Q53m.js +0 -1
  479. package/dist/elements/chunks/message-BqyXDN3-.js +0 -1
  480. package/dist/elements/chunks/message-feedback-Djr3z6Yb.js +0 -1
  481. package/dist/elements/chunks/message-square-vFAgbDaX.js +0 -1
  482. package/dist/elements/chunks/reasoning-DTpCGtnY.js +0 -1
  483. package/dist/elements/chunks/scroll-button-B0cH5fXf.js +0 -1
  484. package/dist/elements/chunks/skeleton-DSXW5Yya.js +0 -1
  485. package/dist/elements/chunks/slots--V19HwUb.js +0 -1
  486. package/dist/elements/chunks/textarea-CUwqAX0G.js +0 -1
  487. package/dist/elements/chunks/tooltip-CgBZCVKp.js +0 -1
  488. package/dist/elements/chunks/use-card-resolution-BSu_XHjH.js +0 -1
  489. package/dist/elements/chunks/x-GRUeeQFT.js +0 -1
  490. package/dist/host-embed.d.ts +0 -33
  491. package/dist/kai-provider.d.ts +0 -2
  492. package/dist/origin.d.ts +0 -5
  493. package/dist/provider-runtime.d.ts +0 -27
  494. package/dist/register-impl-DQyejdfb.js +0 -145
  495. package/dist/validate.d.ts +0 -4
  496. package/dist/version.d.ts +0 -4
  497. package/src/components/component-meta.json +0 -4550
  498. package/src/elements/card-media.jpg +0 -0
  499. package/src/elements/resizable.d.ts +0 -27
  500. package/src/elements/video-poster.jpg +0 -0
@@ -1,26 +1,70 @@
1
- import { type JSX, For, createSignal, splitProps, Show } from "solid-js";
1
+ import { type JSX, For, Index, Switch, Match, createMemo, createSignal, splitProps, Show } from "solid-js";
2
2
  import { Tooltip } from "../ui/tooltip";
3
3
  import { Copy, Check } from "lucide-solid";
4
4
  import { cn } from "../utils/cn";
5
5
  import { Markdown } from "./markdown";
6
6
  import { Button } from "../ui/button";
7
7
  import { actionIcon, BUILTIN_ACTION_LABEL } from "../ui/action-icons";
8
- import type { ChatMessageAction, CustomAction, FeedbackVote } from "../elements/chat-types";
8
+ import type { ChatMessageAction, CustomAction, FeedbackVote, MessagePart, MessageSource } from "../elements/chat-types";
9
9
  import { useChatConfig, textClass } from "../primitives/chat-config";
10
10
  import { Reasoning, ReasoningTrigger, ReasoningContent } from "./reasoning";
11
- import { Tool, type ToolPart } from "./tool";
12
- import { Attachments, Attachment, AttachmentPreview, AttachmentInfo, type AttachmentData } from "./attachments";
11
+ import { Tool } from "./tool";
12
+ import { Attachments, Attachment, AttachmentPreview, AttachmentInfo } from "./attachments";
13
+ import { Source, SourceTrigger, SourceContent, SourceList } from "./source";
14
+ import { CardRenderer, type CardSchemaMap } from "./card-renderer";
15
+ import type { CardComponentMap } from "../primitives/card-registry";
13
16
 
14
17
  // --- Message ---
15
18
 
16
- export interface MessageProps extends JSX.HTMLAttributes<HTMLDivElement> {
19
+ /** Who is speaking in a message row. This is the SEMANTIC role of the message,
20
+ * not an ARIA role — see `MessageProps['role']`. */
21
+ export type MessageRole = 'user' | 'assistant' | 'system';
22
+
23
+ /** The accessible name given to a row that declares a speaker. `role="article"`
24
+ * supports naming; the bare `<div>` a role-less `<Message>` renders does not,
25
+ * so the label is only attached alongside that role. */
26
+ const MESSAGE_ROLE_LABEL: Record<MessageRole, string> = {
27
+ user: 'User message',
28
+ assistant: 'Assistant message',
29
+ system: 'System message',
30
+ };
31
+
32
+ export interface MessageProps extends Omit<JSX.HTMLAttributes<HTMLDivElement>, 'role'> {
17
33
  children: JSX.Element;
34
+ /** Who is speaking: `'user'`, `'assistant'` or `'system'`.
35
+ *
36
+ * This SHADOWS the inherited ARIA `role` attribute on purpose, and is the
37
+ * reason `JSX.HTMLAttributes` is `Omit`ted above. Before this existed, `role`
38
+ * resolved to ARIA and was spread straight onto the row, so `<Message
39
+ * role="user">` shipped `role="user"` on a div — not a valid ARIA role, and a
40
+ * critical axe `aria-roles` violation ("Role must be one of the valid ARIA
41
+ * roles: user") on every message that used the prop. Chromium discards the
42
+ * unknown token and computes `generic`, so the damage was a failed a11y audit
43
+ * and a row with no accessible role or name at all, not a mis-announced one.
44
+ *
45
+ * It is now consumed here and never reaches the DOM. What the row gets
46
+ * instead is a VALID ARIA role that can carry a name — `role="article"` plus
47
+ * an `aria-label` naming the speaker — and `data-role` for styling and
48
+ * querying. Omit the prop and the row stays an unlabelled `<div>`, exactly as
49
+ * before, so the many role-less call sites are untouched.
50
+ *
51
+ * The trade-off: the row's ARIA role can no longer be set through this prop.
52
+ * Pass `aria-label` (or any other ARIA attribute) to override the default
53
+ * naming — the spread below still wins over what this computes. */
54
+ role?: MessageRole;
18
55
  }
19
56
 
20
57
  function Message(props: MessageProps) {
21
- const [local, rest] = splitProps(props, ["children", "class"]);
58
+ const [local, rest] = splitProps(props, ["children", "class", "role"]);
22
59
  return (
23
- <div part="row" class={cn("flex items-start gap-3", local.class)} {...rest}>
60
+ <div
61
+ part="row"
62
+ class={cn("flex items-start gap-3", local.class)}
63
+ data-role={local.role}
64
+ role={local.role ? 'article' : undefined}
65
+ aria-label={local.role ? MESSAGE_ROLE_LABEL[local.role] : undefined}
66
+ {...rest}
67
+ >
24
68
  {local.children}
25
69
  </div>
26
70
  );
@@ -29,8 +73,13 @@ function Message(props: MessageProps) {
29
73
  // --- MessageAvatar ---
30
74
 
31
75
  export interface MessageAvatarProps {
32
- src: string;
33
- alt: string;
76
+ /** Avatar image URL. Optional: with no `src` the component renders `fallback`
77
+ * (initials), which is the whole point of having a fallback — requiring `src`
78
+ * forced `<MessageAvatar src="" …/>` at every initials-only call site. */
79
+ src?: string;
80
+ /** Alt text for the image. Only meaningful alongside `src`; defaults to `''`
81
+ * so an avatar stays decorative rather than announcing a filename. */
82
+ alt?: string;
34
83
  fallback?: string;
35
84
  class?: string;
36
85
  }
@@ -53,7 +102,7 @@ function MessageAvatar(props: MessageAvatarProps) {
53
102
  >
54
103
  <img
55
104
  src={props.src}
56
- alt={props.alt}
105
+ alt={props.alt ?? ''}
57
106
  class="h-full w-full object-cover"
58
107
  />
59
108
  </Show>
@@ -253,18 +302,26 @@ function MessageActionBar(props: MessageActionBarProps) {
253
302
  // --- MessageBody ---
254
303
 
255
304
  export interface MessageBodyProps {
256
- /** The message text/markdown. */
257
- content: string;
258
- /** Optional collapsible reasoning block, rendered above the content. */
259
- reasoning?: { text: string; label?: string };
260
- /** Tool-call parts, rendered above the content. */
261
- tools?: ToolPart[];
262
- /** Inline attachment previews, rendered above the content. */
263
- attachments?: AttachmentData[];
305
+ /** The message's ordered parts (text, reasoning, tool, card, source, file),
306
+ * rendered in a single pass in the order they appear. A run of consecutive
307
+ * `source` parts renders as ONE citation row (`part="citations"`), placed
308
+ * OUTSIDE the message bubble so a citation is never confused with a link the
309
+ * model typed into its prose. */
310
+ parts: MessagePart[];
311
+ /** Add/override card type -> component entries, forwarded to `CardRenderer`
312
+ * for `card` parts. */
313
+ cardTypes?: CardComponentMap;
314
+ /** JSON Schemas for the card types this app renders, keyed by envelope type,
315
+ * forwarded to `CardRenderer` for `card` parts. The companion of `cardTypes`:
316
+ * that says what DRAWS a card, this says what a VALID one looks like.
317
+ * `createCardRegistry(...).validationSchemas` is exactly this shape. Without it
318
+ * the kit checks its own seven built-ins and leaves your own card type
319
+ * unvalidated. A schema here WINS over a built-in of the same name. */
320
+ cardSchemas?: CardSchemaMap;
264
321
  /** Whether this is a user message (right-aligned bubble) vs an assistant
265
322
  * message (full-width transparent). */
266
323
  isUser: boolean;
267
- /** Whether the content renders as markdown. */
324
+ /** Whether text parts render as markdown. */
268
325
  markdown: boolean;
269
326
  /** Action-bar entries — built-in names and/or custom descriptors. When empty
270
327
  * the bar is not rendered. */
@@ -289,44 +346,235 @@ export interface MessageBodyProps {
289
346
  afterBody?: JSX.Element;
290
347
  }
291
348
 
349
+ /** One render group over an ordered `parts` array. Two part types collapse runs:
350
+ * consecutive `file` parts become a single `'files'` group so they share one
351
+ * `<Attachments>` row (matching the pre-parts layout) instead of each opening
352
+ * its own, and consecutive `source` parts become a single `'sources'` group so
353
+ * the N citations one search produced are ONE wrapped row rather than N stacked
354
+ * rows. Every other part is its own `'single'` group. Pure and order-preserving:
355
+ * it only decides where the wrapper boundaries fall, never reorders or drops
356
+ * anything, so a group sits exactly where its parts sat in `parts`. */
357
+ export type MessagePartGroup =
358
+ | { kind: 'single'; part: Exclude<MessagePart, { type: 'file' } | { type: 'source' }> }
359
+ | { kind: 'files'; parts: Extract<MessagePart, { type: 'file' }>[] }
360
+ | { kind: 'sources'; parts: Extract<MessagePart, { type: 'source' }>[] };
361
+
362
+ export function groupMessageParts(parts: MessagePart[]): MessagePartGroup[] {
363
+ const groups: MessagePartGroup[] = [];
364
+ for (const part of parts) {
365
+ if (part.type === 'file') {
366
+ const last = groups[groups.length - 1];
367
+ if (last?.kind === 'files') {
368
+ groups[groups.length - 1] = { kind: 'files', parts: [...last.parts, part] };
369
+ continue;
370
+ }
371
+ groups.push({ kind: 'files', parts: [part] });
372
+ continue;
373
+ }
374
+ if (part.type === 'source') {
375
+ const last = groups[groups.length - 1];
376
+ if (last?.kind === 'sources') {
377
+ groups[groups.length - 1] = { kind: 'sources', parts: [...last.parts, part] };
378
+ continue;
379
+ }
380
+ groups.push({ kind: 'sources', parts: [part] });
381
+ continue;
382
+ }
383
+ groups.push({ kind: 'single', part });
384
+ }
385
+ return groups;
386
+ }
387
+
388
+ /** The citation chip's label. `index` when the model numbered its citations;
389
+ * otherwise `undefined` so `SourceTrigger` falls back to the domain. A source
390
+ * with NO url has no domain to fall back to, so its title (then a generic word)
391
+ * stands in rather than rendering an empty chip. */
392
+ function citationLabel(s: MessageSource): string | number | undefined {
393
+ if (s.index !== undefined) return s.index;
394
+ if (s.url) return undefined;
395
+ return s.title || 'Source';
396
+ }
397
+
398
+ /** The hover card's headline. Every field is optional, so fall back through
399
+ * title -> url -> a generic word. */
400
+ function citationTitle(s: MessageSource): string {
401
+ return s.title || s.url || 'Source';
402
+ }
403
+
404
+ /** Narrow a group to one variant IN A SINGLE READ.
405
+ *
406
+ * The render body below reads its group/part through accessors (see the
407
+ * `<Index>` note in `MessageBody`), and TypeScript cannot narrow a
408
+ * discriminated union across two separate accessor CALLS —
409
+ * `g().kind === 'files' && g()` leaves the second call widened, because as far
410
+ * as the compiler knows the two calls could return different values. So the
411
+ * test and the cast happen together, on one already-read value. Returns
412
+ * `false` rather than `undefined` so it reads as a plain falsy `<Match when>`. */
413
+ function groupAs<T extends MessagePartGroup['kind']>(
414
+ group: MessagePartGroup,
415
+ kind: T,
416
+ ): Extract<MessagePartGroup, { kind: T }> | false {
417
+ return group.kind === kind ? (group as Extract<MessagePartGroup, { kind: T }>) : false;
418
+ }
419
+
420
+ /** The same single-read narrowing for `MessagePart`. */
421
+ function partAs<T extends MessagePart['type']>(
422
+ part: MessagePart,
423
+ type: T,
424
+ ): Extract<MessagePart, { type: T }> | false {
425
+ return part.type === type ? (part as Extract<MessagePart, { type: T }>) : false;
426
+ }
427
+
292
428
  /**
293
- * The shared message body: an optional reasoning block, tool calls, inline
294
- * attachments, the content bubble, and the action bar — in that order. This is
295
- * the single source of truth for how a message renders, consumed by `ChatThread`
429
+ * The shared message body: the message's `parts` rendered in a single ordered
430
+ * pass (text, reasoning, tool calls, generative-UI cards, citations and file
431
+ * attachments interleaved exactly as they appear), followed by the action bar.
432
+ * Runs of `source` and `file` parts each collapse into one row. This is the
433
+ * single source of truth for how a message renders, consumed by `ChatThread`
296
434
  * (the `<For>` over `messages`), the standalone `<kai-message>` facade, and (in
297
435
  * future) `kai-compare` for each candidate. Pure/prop-driven: all interaction
298
436
  * state (copied, feedback vote) is owned above and passed in.
299
437
  */
300
438
  function MessageBody(props: MessageBodyProps) {
439
+ const groups = createMemo(() => groupMessageParts(props.parts));
301
440
  return (
302
441
  <>
303
442
  {/* before-body (inject): a per-message header above everything else. */}
304
443
  <Show when={props.beforeBody}>{props.beforeBody}</Show>
305
- <Show when={props.reasoning}>
306
- {(r) => (
307
- <Reasoning class="mb-2 w-full">
308
- <ReasoningTrigger>{r().label ?? 'Reasoning'}</ReasoningTrigger>
309
- <ReasoningContent markdown>{r().text}</ReasoningContent>
310
- </Reasoning>
444
+ {/* <Index>, NOT <For>, on purpose — this is load-bearing.
445
+ *
446
+ * A streaming message re-renders once per delta with a brand-new `parts`
447
+ * array (a new reference IS the re-render signal), and
448
+ * `groupMessageParts` allocates fresh wrapper objects on top of that. A
449
+ * <For> is REFERENCE-keyed, so every chunk looks like an entirely new
450
+ * list and every row is torn down and rebuilt. Everything the user has
451
+ * done inside a row dies with it: expanding a tool panel or a reasoning
452
+ * block mid-stream silently did nothing, because the disclosure opened
453
+ * and was discarded microseconds later by the next token (a live probe
454
+ * caught the collapsibles' `createUniqueId()` walking cl-9 -> cl-11 ->
455
+ * cl-15 across three deltas).
456
+ *
457
+ * <Index> keys by POSITION and hands each row its value as a SIGNAL, so
458
+ * a row stays mounted while its content keeps updating. That is exactly
459
+ * the shape of a stream: the folds behind `parts`
460
+ * (appendTextPart/appendReasoningPart/upsertToolPart) only ever append
461
+ * to the end or replace one part IN PLACE with the same variant — never
462
+ * reorder, never change a part's type — so a part's position is a stable
463
+ * identity, and a growing text/reasoning block or a tool patched from
464
+ * `input-streaming` to `output-available` reaches the DOM through the
465
+ * accessor instead of through a remount.
466
+ *
467
+ * The trade-off of position-keying: splicing a part out of the MIDDLE of
468
+ * a message shifts the rows after it, so their local state (an open
469
+ * disclosure) stays with the position rather than following the part.
470
+ * <For> got that right for a STATIC array and got streaming wrong every
471
+ * single time — no consumer splices mid-message, every consumer streams.
472
+ *
473
+ * This only works while the children read through the accessors below:
474
+ * capturing `g().part` once re-freezes the row at its first delta. */}
475
+ <Index each={groups()}>
476
+ {(group) => (
477
+ <Switch fallback={null}>
478
+ <Match when={groupAs(group(), 'files')}>
479
+ {(g) => (
480
+ <Attachments variant="inline" class={props.isUser ? 'mb-2 justify-end' : 'mb-2'}>
481
+ {/* Reference-keyed <For> is right HERE: the run's part objects
482
+ are carried over untouched by the folds, and an attachment
483
+ holds no state worth preserving. */}
484
+ <For each={g().parts}>
485
+ {(fp) => (
486
+ <Attachment data={fp.attachment}>
487
+ <AttachmentPreview />
488
+ <AttachmentInfo />
489
+ </Attachment>
490
+ )}
491
+ </For>
492
+ </Attachments>
493
+ )}
494
+ </Match>
495
+ <Match when={groupAs(group(), 'sources')}>
496
+ {(g) => (
497
+ // OUTSIDE the bubble, deliberately. A citation nested inside
498
+ // `MessageContent` is indistinguishable from a link the MODEL
499
+ // typed into its prose — which is exactly how the first version
500
+ // of the S12 conformance check passed while proving nothing.
501
+ // `SourceList` is its own container, so the row is a sibling of
502
+ // the content part, and `part="citations"` lets a consumer target
503
+ // it through the shadow boundary.
504
+ <SourceList part="citations" class={props.isUser ? 'justify-end' : undefined}>
505
+ {/* Reference-keyed <For> is right HERE, as with files: the
506
+ run's part objects are carried over untouched by the folds. */}
507
+ <For each={g().parts}>
508
+ {(sp) => (
509
+ <Source href={sp.source.url}>
510
+ <SourceTrigger label={citationLabel(sp.source)} />
511
+ <SourceContent
512
+ title={citationTitle(sp.source)}
513
+ description={sp.source.snippet ?? ''}
514
+ />
515
+ </Source>
516
+ )}
517
+ </For>
518
+ </SourceList>
519
+ )}
520
+ </Match>
521
+ <Match when={groupAs(group(), 'single')}>
522
+ {(g) => {
523
+ // An ACCESSOR, never a captured value: the row outlives the
524
+ // delta that rebuilt this part, so every read below has to go
525
+ // through here for the new content to land. `partAs` does the
526
+ // type test and the narrowing cast in one read (see its note).
527
+ const part = () => g().part;
528
+ const reasoning = () => partAs(part(), 'reasoning');
529
+ // A reasoning part with NO text is a round-trip carrier, not
530
+ // something to show. Anthropic's redacted_thinking blocks carry
531
+ // an opaque blob with no readable text, and the block assembled
532
+ // at content_block_stop carries the verbatim payload the encoder
533
+ // must echo back. Both are empty-text parts that MUST stay in
534
+ // `parts` (the encoder needs them, in order) and must not render
535
+ // a blank disclosure.
536
+ const carrierOnly = () => { const r = reasoning(); return r !== false && r.text === ''; };
537
+ const shownReasoning = () => { const r = reasoning(); return r !== false && r.text !== '' && r; };
538
+ return (
539
+ <Switch fallback={null}>
540
+ <Match when={partAs(part(), 'text')}>
541
+ {(p) => (
542
+ <MessageContent
543
+ part="bubble content"
544
+ markdown={props.markdown}
545
+ class={props.isUser
546
+ ? 'bg-muted text-primary max-w-[85%] rounded-2xl px-4 py-2'
547
+ : 'bg-transparent p-0'}
548
+ >
549
+ {p().text}
550
+ </MessageContent>
551
+ )}
552
+ </Match>
553
+ <Match when={carrierOnly()}>{null}</Match>
554
+ <Match when={shownReasoning()}>
555
+ {(p) => (
556
+ <Reasoning class="mb-2 w-full">
557
+ <ReasoningTrigger>{p().label ?? 'Reasoning'}</ReasoningTrigger>
558
+ <ReasoningContent markdown>{p().text}</ReasoningContent>
559
+ </Reasoning>
560
+ )}
561
+ </Match>
562
+ <Match when={partAs(part(), 'tool')}>
563
+ {(p) => <Tool toolPart={p().tool} class="mb-2 w-full" />}
564
+ </Match>
565
+ <Match when={partAs(part(), 'card')}>
566
+ {(p) => <CardRenderer envelope={p().envelope} types={props.cardTypes} schemas={props.cardSchemas} />}
567
+ </Match>
568
+ {/* No `source` match here on purpose: source parts never
569
+ reach a 'single' group — they are collapsed into a
570
+ 'sources' run above and rendered as one citation row. */}
571
+ </Switch>
572
+ );
573
+ }}
574
+ </Match>
575
+ </Switch>
311
576
  )}
312
- </Show>
313
- <For each={props.tools ?? []}>{(tp) => <Tool toolPart={tp} class="mb-2 w-full" />}</For>
314
- <Show when={props.attachments?.length}>
315
- <Attachments variant="inline" class={props.isUser ? 'mb-2 justify-end' : 'mb-2'}>
316
- <For each={props.attachments!}>
317
- {(att) => (<Attachment data={att}><AttachmentPreview /><AttachmentInfo /></Attachment>)}
318
- </For>
319
- </Attachments>
320
- </Show>
321
- <MessageContent
322
- part="bubble content"
323
- markdown={props.markdown}
324
- class={props.isUser
325
- ? 'bg-muted text-primary max-w-[85%] rounded-2xl px-4 py-2'
326
- : 'bg-transparent p-0'}
327
- >
328
- {props.content}
329
- </MessageContent>
577
+ </Index>
330
578
  <Show when={(props.actions?.length ?? 0) > 0}>
331
579
  <MessageActionBar
332
580
  actions={props.actions!}
@@ -1,4 +1,4 @@
1
- import { type JSX, type Accessor, splitProps, createSignal, createContext, useContext, createEffect, onCleanup, Show } from 'solid-js';
1
+ import { type JSX, type Accessor, splitProps, createSignal, createContext, useContext, createEffect, createUniqueId, onCleanup, Show } from 'solid-js';
2
2
  import { cn } from '../utils/cn';
3
3
  import { ChevronDown } from 'lucide-solid';
4
4
  import { Markdown } from './markdown';
@@ -8,6 +8,16 @@ interface ReasoningContextValue {
8
8
  isOpen: () => boolean;
9
9
  onOpenChange: (open: boolean) => void;
10
10
  disabled: () => boolean;
11
+ /** Id minted on the root and shared by the trigger's `aria-controls` and the
12
+ * content's `id` — the same wiring `Collapsible` uses, so a screen reader can
13
+ * name the panel this disclosure toggles. */
14
+ contentId: string;
15
+ /** ReasoningTrigger registers its button here on mount; the content reads it
16
+ * when a panel holding focus collapses, so focus has somewhere to land. */
17
+ registerTrigger: (el: HTMLElement | undefined) => void;
18
+ /** The registered trigger. Deliberately a plain getter over a mutable
19
+ * variable, NOT a signal — registration must not re-run the content effect. */
20
+ trigger: () => HTMLElement | undefined;
11
21
  }
12
22
 
13
23
  /** Imperative open controller, handed to a parent (the kai-reasoning facade) via
@@ -78,8 +88,24 @@ function Reasoning(props: ReasoningProps) {
78
88
  // by disabled — the facade's wireDisclosure applies disabled-gating itself.
79
89
  local.controllerRef?.({ open: isOpen, setOpen: handleOpenChange });
80
90
 
91
+ const contentId = createUniqueId();
92
+
93
+ // The trigger element, for the focus hand-off in ReasoningContent. Read once,
94
+ // at the moment a panel collapses — a signal would only add a spurious
95
+ // dependency to the content effect when the trigger mounts.
96
+ let triggerEl: HTMLElement | undefined;
97
+
81
98
  return (
82
- <ReasoningContext.Provider value={{ isOpen, onOpenChange: handleOpenChange, disabled }}>
99
+ <ReasoningContext.Provider
100
+ value={{
101
+ isOpen,
102
+ onOpenChange: handleOpenChange,
103
+ disabled,
104
+ contentId,
105
+ registerTrigger: (el) => { triggerEl = el; },
106
+ trigger: () => triggerEl,
107
+ }}
108
+ >
83
109
  <div class={local.class}>{local.children}</div>
84
110
  </ReasoningContext.Provider>
85
111
  );
@@ -92,13 +118,34 @@ export interface ReasoningTriggerProps extends JSX.ButtonHTMLAttributes<HTMLButt
92
118
  }
93
119
 
94
120
  function ReasoningTrigger(props: ReasoningTriggerProps) {
95
- const [local, rest] = splitProps(props, ['children', 'class']);
96
- const { isOpen, onOpenChange, disabled } = useReasoningContext();
121
+ // `ref` is split out rather than left in `rest`: the trigger takes its own ref
122
+ // to register as the panel's focus-return target, and a spread `ref` would
123
+ // silently replace it. The consumer's ref is forwarded below instead.
124
+ const [local, rest] = splitProps(props, ['children', 'class', 'ref']);
125
+ const { isOpen, onOpenChange, disabled, contentId, registerTrigger } = useReasoningContext();
97
126
 
98
127
  return (
128
+ // The same disclosure contract as CollapsibleTrigger: an explicit
129
+ // `type="button"` (so a trigger inside a form never submits it),
130
+ // `aria-expanded` + `aria-controls` so assistive tech can read the state and
131
+ // find the panel, and the `data-state` / `data-expanded` / `data-closed`
132
+ // handles styling and tests hang off. `{...rest}` stays LAST so a consumer
133
+ // can still override any of them.
99
134
  <button
135
+ ref={(el) => {
136
+ registerTrigger(el);
137
+ // Solid hands a component's `ref` down as a callback; tolerate a raw
138
+ // element for the hand-written case.
139
+ if (typeof local.ref === 'function') (local.ref as (el: HTMLButtonElement) => void)(el);
140
+ }}
141
+ type="button"
100
142
  class={cn('flex cursor-pointer items-center gap-2 text-meta', local.class)}
101
143
  disabled={disabled() || undefined}
144
+ aria-expanded={isOpen()}
145
+ aria-controls={contentId}
146
+ data-expanded={isOpen() ? '' : undefined}
147
+ data-closed={isOpen() ? undefined : ''}
148
+ data-state={isOpen() ? 'open' : 'closed'}
102
149
  onClick={() => { if (!disabled()) onOpenChange(!isOpen()); }}
103
150
  {...rest}
104
151
  >
@@ -125,11 +172,29 @@ export interface ReasoningContentProps extends JSX.HTMLAttributes<HTMLDivElement
125
172
 
126
173
  function ReasoningContent(props: ReasoningContentProps) {
127
174
  const [local, rest] = splitProps(props, ['children', 'class', 'contentClass', 'markdown']);
128
- const { isOpen } = useReasoningContext();
175
+ const { isOpen, contentId, trigger } = useReasoningContext();
129
176
 
130
177
  let contentRef: HTMLDivElement | undefined;
131
178
  let innerRef: HTMLDivElement | undefined;
132
179
 
180
+ /** Hand focus back to the trigger when the panel about to go inert is holding
181
+ * it. Chromium keeps `activeElement` on the now-inert node for the rest of
182
+ * the task and then resets the document's focus to `<body>` — so without this
183
+ * a user who was inside the panel loses the focus ring entirely and their
184
+ * next Tab restarts from the top. `Collapsible` has exactly this gap: it sets
185
+ * `bool:inert` and never looks at where focus was. Runs BEFORE the attribute
186
+ * lands, so the move is ours rather than the browser's fallback. */
187
+ const releaseFocus = () => {
188
+ if (!contentRef) return;
189
+ // getRootNode(), not `document`: inside the <kai-reasoning> shadow root
190
+ // document.activeElement only ever resolves to the HOST element.
191
+ const root = contentRef.getRootNode() as Document | ShadowRoot;
192
+ const active = root.activeElement;
193
+ // A disabled trigger cannot take focus; the browser then falls back to
194
+ // <body>, which is no worse than not trying.
195
+ if (active && contentRef.contains(active)) trigger()?.focus();
196
+ };
197
+
133
198
  createEffect(() => {
134
199
  if (!contentRef || !innerRef) return;
135
200
 
@@ -141,21 +206,43 @@ function ReasoningContent(props: ReasoningContentProps) {
141
206
 
142
207
  if (isOpen()) {
143
208
  contentRef.style.maxHeight = `${innerRef.scrollHeight}px`;
209
+ // Dropped in the same tick as the open, NOT on transitionend: the panel is
210
+ // interactive from the first frame, so an inert outliving the animation can
211
+ // never swallow the first click or Tab into it.
212
+ contentRef.removeAttribute('inert');
144
213
  } else {
145
214
  contentRef.style.maxHeight = '0px';
215
+ releaseFocus();
216
+ // Applied in the same tick as the collapse, again NOT on transitionend:
217
+ // `inert` has no visual effect of its own, so the 150ms max-height
218
+ // transition still runs to completion underneath it (verified in Chromium:
219
+ // transitionend fires and the final computed max-height is reached), while
220
+ // the a11y tree matches the collapsed intent immediately instead of 150ms
221
+ // late. setAttribute rather than the `inert` IDL property because Solid
222
+ // routes `inert` through Properties (el.inert = …), which jsdom does not
223
+ // reflect back to an attribute — the same reason Collapsible uses `bool:`.
224
+ contentRef.setAttribute('inert', '');
146
225
  }
147
226
 
148
227
  onCleanup(dispose);
149
228
  });
150
229
 
151
230
  return (
231
+ // `id` matches the trigger's `aria-controls`; `data-state` gives styling and
232
+ // tests an attribute handle on the panel instead of forcing them to read the
233
+ // animated `max-height`. `inert` while collapsed (as CollapsibleContent does)
234
+ // is owned by the effect above, not bound here — it has to be sequenced
235
+ // against the focus hand-off, and a second reactive owner would race it.
236
+ // `{...rest}` stays LAST so a consumer can still override.
152
237
  <div
153
238
  ref={contentRef}
239
+ id={contentId}
154
240
  class={cn(
155
241
  'overflow-hidden transition-[max-height] duration-150 ease-out',
156
242
  local.class
157
243
  )}
158
244
  style={{ 'max-height': '0px' }}
245
+ data-state={isOpen() ? 'open' : 'closed'}
159
246
  {...rest}
160
247
  >
161
248
  <div
@@ -6,6 +6,7 @@
6
6
  // isolation.
7
7
  import type { ToolPart } from './tool';
8
8
  import type { AttachmentData } from './attachments';
9
+ import type { MessagePart } from '../elements/chat-types';
9
10
 
10
11
  // ─────────────────────────────────────────────────────────────────────────────
11
12
  // Types
@@ -125,3 +126,27 @@ export function isAnyStreaming(pair: ComparePair | null): boolean {
125
126
  if (!pair) return false;
126
127
  return pair.some((c) => c.streaming === true);
127
128
  }
129
+
130
+ /** Map a `CompareCandidate`'s flat legacy fields (content/reasoning/tools/
131
+ * attachments) into the ordered `MessagePart[]` `MessageBody` now renders.
132
+ * `CompareCandidate` stays its own independent shape (it is `<kai-compare>`'s
133
+ * public `data` JSON, not a `ChatMessage`). This is purely a render-time
134
+ * adapter, applied at each `MessageBody` call site. Order: reasoning (the
135
+ * thinking trace) → tool calls (the actions taken) → attachments (anything
136
+ * produced) → the final text answer, mirroring how an assistant turn reads. */
137
+ export function candidateToParts(candidate: CompareCandidate): MessagePart[] {
138
+ const parts: MessagePart[] = [];
139
+ if (candidate.reasoning) {
140
+ parts.push({ type: 'reasoning', text: candidate.reasoning.text, label: candidate.reasoning.label });
141
+ }
142
+ for (const tool of candidate.tools ?? []) {
143
+ parts.push({ type: 'tool', tool });
144
+ }
145
+ for (const attachment of candidate.attachments ?? []) {
146
+ parts.push({ type: 'file', attachment });
147
+ }
148
+ if (candidate.content) {
149
+ parts.push({ type: 'text', text: candidate.content });
150
+ }
151
+ return parts;
152
+ }
@@ -24,6 +24,7 @@ import {
24
24
  type CompareSelection,
25
25
  type ResponseCompareData,
26
26
  buildSelection,
27
+ candidateToParts,
27
28
  isAnyStreaming,
28
29
  normalizeCandidates,
29
30
  } from './response-compare-types';
@@ -37,7 +38,7 @@ export type {
37
38
  ResponseCompareData,
38
39
  CompareSelection,
39
40
  } from './response-compare-types';
40
- export { normalizeCandidates, buildSelection, isAnyStreaming } from './response-compare-types';
41
+ export { normalizeCandidates, buildSelection, isAnyStreaming, candidateToParts } from './response-compare-types';
41
42
 
42
43
  export type CompareLayout = 'auto' | 'columns' | 'tabs';
43
44
 
@@ -386,10 +387,7 @@ function CompareColumn(props: ColumnProps): JSX.Element {
386
387
  when={props.candidate.streaming}
387
388
  fallback={
388
389
  <MessageBody
389
- content={props.candidate.content}
390
- reasoning={props.candidate.reasoning}
391
- tools={props.candidate.tools}
392
- attachments={props.candidate.attachments}
390
+ parts={candidateToParts(props.candidate)}
393
391
  isUser={false}
394
392
  markdown={true}
395
393
  />
@@ -445,10 +443,7 @@ function CollapsedWinner(props: {
445
443
  {(c) => (
446
444
  <div class="min-w-0">
447
445
  <MessageBody
448
- content={c().content}
449
- reasoning={c().reasoning}
450
- tools={c().tools}
451
- attachments={c().attachments}
446
+ parts={candidateToParts(c())}
452
447
  isUser={false}
453
448
  markdown={true}
454
449
  />