@kitn.ai/ui 0.25.2 → 0.26.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 (409) hide show
  1. package/README.md +2 -2
  2. package/dist/components/attachment-types.d.ts +46 -1
  3. package/dist/components/attachments.d.ts +31 -0
  4. package/dist/components/audio-visualizer/fit-scale.d.ts +50 -0
  5. package/dist/components/audio-visualizer/index.d.ts +15 -1
  6. package/dist/components/audio-visualizer/variant-bar.d.ts +24 -1
  7. package/dist/components/chat-thread.d.ts +3 -41
  8. package/dist/components/code-block.d.ts +22 -0
  9. package/dist/components/conversation-item.d.ts +54 -1
  10. package/dist/components/conversation-list.d.ts +65 -0
  11. package/dist/components/form.d.ts +42 -0
  12. package/dist/components/message.d.ts +7 -0
  13. package/dist/components/toast.d.ts +2 -1
  14. package/dist/components/voice-input.d.ts +8 -0
  15. package/dist/components/voice-output.d.ts +12 -1
  16. package/dist/components/workspace-shell.d.ts +87 -0
  17. package/dist/{create-tween-B5Y4Im8G.js → create-tween-BFT6c-Y8.js} +1 -1
  18. package/dist/{create-tween-4z1XYLkR.js → create-tween-CJNlzoaO.js} +1 -1
  19. package/dist/{create-tween-CUzOlaMS.js → create-tween-CQED4T0g.js} +1 -1
  20. package/dist/custom-elements.json +2931 -2572
  21. package/dist/diagnostics/hook.d.ts +81 -0
  22. package/dist/diagnostics/index.d.ts +7 -0
  23. package/dist/diagnostics/report-request.d.ts +61 -0
  24. package/dist/diagnostics.d.ts +5 -0
  25. package/dist/diagnostics.js +209 -0
  26. package/dist/elements/agent-card.js +1 -1
  27. package/dist/elements/artifact.js +1 -1
  28. package/dist/elements/attachments.js +1 -1
  29. package/dist/elements/audio-visualizer.js +1 -1
  30. package/dist/elements/autoloader.js +1 -1
  31. package/dist/elements/avatar.js +1 -1
  32. package/dist/elements/badge.js +1 -1
  33. package/dist/elements/button.js +1 -1
  34. package/dist/elements/card.js +1 -1
  35. package/dist/elements/cards.js +1 -1
  36. package/dist/elements/chain-of-thought.js +1 -1
  37. package/dist/elements/chat-scope-picker.js +1 -1
  38. package/dist/elements/chat-workspace.js +1 -1
  39. package/dist/elements/chat.js +1 -1
  40. package/dist/elements/checkpoint.js +1 -1
  41. package/dist/elements/choice.js +1 -1
  42. package/dist/elements/chunks/{Icon-b5A8hlHs.js → Icon--Y8vsFal.js} +1 -1
  43. package/dist/elements/chunks/action-icons-D9u3OzYW.js +1 -0
  44. package/dist/elements/chunks/arrow-left-CVh1TeN7.js +1 -0
  45. package/dist/elements/chunks/{artifact-BSuPYFHY.js → artifact-BkYcfIlZ.js} +1 -1
  46. package/dist/elements/chunks/attachments-DwfNlubq.js +1 -0
  47. package/dist/elements/chunks/audio-visualizer-D7lB6mvR.js +1 -0
  48. package/dist/elements/chunks/{badge-TIMiJFKc.js → badge-Q0-Cmwzz.js} +1 -1
  49. package/dist/elements/chunks/{button-B9okqG73.js → button-flUbeufF.js} +1 -1
  50. package/dist/elements/chunks/{card-renderer-CSoTL_e1.js → card-renderer-3Dyl-FpN.js} +1 -1
  51. package/dist/elements/chunks/check-B5kBPKeo.js +1 -0
  52. package/dist/elements/chunks/chevron-down-DZ1GS39h.js +1 -0
  53. package/dist/elements/chunks/chevron-right-CvP7OUd2.js +1 -0
  54. package/dist/elements/chunks/{choice-card-CGMc3kek.js → choice-card-D4QUIOWZ.js} +1 -1
  55. package/dist/elements/chunks/circle-CDf9g6MQ.js +1 -0
  56. package/dist/elements/chunks/circle-check-CA47Nqbz.js +1 -0
  57. package/dist/elements/chunks/{circle-x-mhH7pOK2.js → circle-x-CJ_HVbC8.js} +1 -1
  58. package/dist/elements/chunks/code-block-BdLiTitd.js +1 -0
  59. package/dist/elements/chunks/collapsible-BzbXpPzr.js +1 -0
  60. package/dist/elements/chunks/{composer-CZZmtYOT.js → composer-Byve-n8K.js} +2 -2
  61. package/dist/elements/chunks/{confirm-card-BWjf2-xo.js → confirm-card-B0flE3MN.js} +1 -1
  62. package/dist/elements/chunks/context-VLIL3sJ-.js +1 -0
  63. package/dist/elements/chunks/controllable-DTwTIlwp.js +1 -0
  64. package/dist/elements/chunks/conversation-item-BDztezOG.js +1 -0
  65. package/dist/elements/chunks/copy-Nf-kqxXH.js +1 -0
  66. package/dist/elements/chunks/{create-tween-CUrM6D_o.js → create-tween-c4Z8TDYz.js} +2 -2
  67. package/dist/elements/chunks/default-input-C2iLFq0b.js +1 -0
  68. package/dist/elements/chunks/define-wjHR1MkU.js +1 -0
  69. package/dist/elements/chunks/disclosure-CcNbyLy0.js +1 -0
  70. package/dist/elements/chunks/{download-DnCyWFJE.js → download-C4V7tBdb.js} +1 -1
  71. package/dist/elements/chunks/dropdown-BgWTthUx.js +1 -0
  72. package/dist/elements/chunks/{ellipsis-CVxqfsDZ.js → ellipsis-7gU33mL2.js} +1 -1
  73. package/dist/elements/chunks/embed-D3KewsIJ.js +1 -0
  74. package/dist/elements/chunks/{external-link-DG2sRdQL.js → external-link-CSOEsNlx.js} +1 -1
  75. package/dist/elements/chunks/{file-text-D0J9F6M7.js → file-text-D1G8khle.js} +1 -1
  76. package/dist/elements/chunks/{file-tree-yGWNK0Js.js → file-tree-zvB2i7P9.js} +1 -1
  77. package/dist/elements/chunks/{folder-NV1pi_ud.js → folder-WW2LGLid.js} +1 -1
  78. package/dist/elements/chunks/form-CQirdC4W.js +1 -0
  79. package/dist/elements/chunks/hover-card-CJJkfHTK.js +1 -0
  80. package/dist/elements/chunks/icon-Cfgmf4fa.js +1 -0
  81. package/dist/elements/chunks/{info-C8r5Q6cc.js → info-DX3YMOt6.js} +1 -1
  82. package/dist/elements/chunks/input-BtW9U6Sq.js +1 -0
  83. package/dist/elements/chunks/{kbd-OGJDUT-O.js → kbd-LGXwuT_V.js} +1 -1
  84. package/dist/elements/chunks/{link-BdsfLB7b.js → link-2My69BqT.js} +1 -1
  85. package/dist/elements/chunks/{link-preview-Cf5KOP-i.js → link-preview-OQ9IEq0f.js} +1 -1
  86. package/dist/elements/chunks/{loader-B5QzK_8_.js → loader-iOxc_Tu-.js} +1 -1
  87. package/dist/elements/chunks/{markdown-DbozyYqa.js → markdown-DgBFaGAz.js} +1 -1
  88. package/dist/elements/chunks/{message-DtXKPqiR.js → message-VMZohrXx.js} +1 -1
  89. package/dist/elements/chunks/message-circle-CpXMM-_n.js +1 -0
  90. package/dist/elements/chunks/message-square-DxcWtETQ.js +1 -0
  91. package/dist/elements/chunks/message-tZmV2A3p.js +1 -0
  92. package/dist/elements/chunks/{minimize-2-SegJ_oku.js → minimize-2-DPIUKTrT.js} +1 -1
  93. package/dist/elements/chunks/{model-switcher-D7JrFFHp.js → model-switcher-DsT8tFB0.js} +1 -1
  94. package/dist/elements/chunks/{overlay-CV7z2YuX.js → overlay-DuiwdXjm.js} +1 -1
  95. package/dist/elements/chunks/{paperclip-Bg0ITU3b.js → paperclip-CRZVp4z_.js} +1 -1
  96. package/dist/elements/chunks/{progress-bar-D1_rzt6N.js → progress-bar-DNy66e_E.js} +1 -1
  97. package/dist/elements/chunks/{prompt-suggestion-D5Sm8xF1.js → prompt-suggestion-Bn64Nl7L.js} +1 -1
  98. package/dist/elements/chunks/reasoning-Emn9N4-T.js +1 -0
  99. package/dist/elements/chunks/{resizable-Dpzjlvp4.js → resizable-D181b4fM.js} +1 -1
  100. package/dist/elements/chunks/{rotate-cw-DzVDJOA1.js → rotate-cw-CPWe37D9.js} +1 -1
  101. package/dist/elements/chunks/{scroll-area-93A1bbeG.js → scroll-area-ClZkZxIk.js} +1 -1
  102. package/dist/elements/chunks/scroll-button-DPE-t3x_.js +1 -0
  103. package/dist/elements/chunks/{separator-DH2r_ZSZ.js → separator-CeJHQqLW.js} +1 -1
  104. package/dist/elements/chunks/{settings-BFMOd6yB.js → settings-CqaHgJF9.js} +1 -1
  105. package/dist/elements/chunks/{settings-group-B3VZKIcz.js → settings-group-CjqjLCOn.js} +1 -1
  106. package/dist/elements/chunks/share-CyfnNEqp.js +1 -0
  107. package/dist/elements/chunks/{skeleton-N8BuWYmf.js → skeleton-D8akBnKS.js} +1 -1
  108. package/dist/elements/chunks/slots-DaNjX0rc.js +1 -0
  109. package/dist/elements/chunks/{source-w9V0NnBn.js → source-UWCvmVak.js} +1 -1
  110. package/dist/elements/chunks/{star-DHvACxtm.js → star-DoHFAikQ.js} +1 -1
  111. package/dist/elements/chunks/store-Cjq0qwYN.js +1 -0
  112. package/dist/elements/chunks/{tasks-card-lPxoodlA.js → tasks-card-CmZ5xh6v.js} +1 -1
  113. package/dist/elements/chunks/{text-shimmer-BY_rI3s5.js → text-shimmer-DYn09zrz.js} +1 -1
  114. package/dist/elements/chunks/textarea-skiGHURM.js +1 -0
  115. package/dist/elements/chunks/{thumbs-up-DQe0hvkZ.js → thumbs-up-DX2Ijk3H.js} +1 -1
  116. package/dist/elements/chunks/{toast-store-VUZZBhzH.js → toast-store-CFnQpThX.js} +1 -1
  117. package/dist/elements/chunks/{tool-C1YtxwPu.js → tool-DKQ2D1DP.js} +1 -1
  118. package/dist/elements/chunks/{tooltip-BqwnFWhn.js → tooltip-B0G99y8w.js} +1 -1
  119. package/dist/elements/chunks/{triangle-alert-B6or7F5u.js → triangle-alert-F9cyUEyy.js} +1 -1
  120. package/dist/elements/chunks/{use-card-resolution-DJg32jGH.js → use-card-resolution-DoA2f_EU.js} +1 -1
  121. package/dist/elements/chunks/{variant-aurora-QXTZPyST.js → variant-aurora-qt6Zct8M.js} +2 -2
  122. package/dist/elements/chunks/variant-custom-tJp22_BQ.js +1 -0
  123. package/dist/{variant-wave-BwzrHD1s.js → elements/chunks/variant-wave-CaQ5ti39.js} +2 -2
  124. package/dist/elements/chunks/x-B6UnOiLw.js +1 -0
  125. package/dist/elements/coachmark.js +1 -1
  126. package/dist/elements/code-block.js +1 -1
  127. package/dist/elements/command.js +1 -1
  128. package/dist/elements/compare.js +1 -1
  129. package/dist/elements/composer.js +1 -1
  130. package/dist/elements/confirm-card.js +1 -1
  131. package/dist/elements/context-meter.js +1 -1
  132. package/dist/elements/conversation-item.d.ts +1 -0
  133. package/dist/elements/conversation-item.js +1 -0
  134. package/dist/elements/conversation-list.js +1 -1
  135. package/dist/elements/default-input.d.ts +3 -3
  136. package/dist/elements/define.d.ts +27 -0
  137. package/dist/elements/diagnostic-events.d.ts +74 -0
  138. package/dist/elements/dialog.js +1 -1
  139. package/dist/elements/dock.d.ts +1 -0
  140. package/dist/elements/dock.js +149 -0
  141. package/dist/elements/dropdown.d.ts +1 -0
  142. package/dist/elements/dropdown.js +1 -0
  143. package/dist/elements/editable-label.js +1 -1
  144. package/dist/elements/element-diagnostics.d.ts +81 -0
  145. package/dist/elements/embed.js +1 -1
  146. package/dist/elements/empty.js +1 -1
  147. package/dist/elements/feedback-bar.js +1 -1
  148. package/dist/elements/file-tree.js +1 -1
  149. package/dist/elements/file-upload.js +1 -1
  150. package/dist/elements/form.js +1 -1
  151. package/dist/elements/hover-card.js +1 -1
  152. package/dist/elements/icon.js +1 -1
  153. package/dist/elements/image.js +1 -1
  154. package/dist/elements/input.js +1 -1
  155. package/dist/elements/kbd.js +1 -1
  156. package/dist/elements/link-preview.js +1 -1
  157. package/dist/elements/loader.js +1 -1
  158. package/dist/elements/markdown.js +1 -1
  159. package/dist/elements/menu.js +1 -1
  160. package/dist/elements/message-skills.js +1 -1
  161. package/dist/elements/message.js +1 -1
  162. package/dist/elements/model-switcher.js +1 -1
  163. package/dist/elements/nav.js +1 -1
  164. package/dist/elements/notice.js +1 -1
  165. package/dist/elements/pane-group.js +1 -1
  166. package/dist/elements/pane.js +1 -1
  167. package/dist/elements/popover.js +1 -1
  168. package/dist/elements/progress-bar.js +1 -1
  169. package/dist/elements/prompt-dock.js +1 -1
  170. package/dist/elements/prompt-input.js +1 -1
  171. package/dist/elements/prompt-suggestions.js +1 -1
  172. package/dist/elements/reasoning.js +1 -1
  173. package/dist/elements/register.d.ts +2 -0
  174. package/dist/elements/remote.js +1 -1
  175. package/dist/elements/resizable.js +1 -1
  176. package/dist/elements/response-stream.js +3 -3
  177. package/dist/elements/screen.js +1 -1
  178. package/dist/elements/scroll-area.js +1 -1
  179. package/dist/elements/scroll-button.js +1 -1
  180. package/dist/elements/search.js +1 -1
  181. package/dist/elements/segmented.js +1 -1
  182. package/dist/elements/separator.js +1 -1
  183. package/dist/elements/setting-item.js +1 -1
  184. package/dist/elements/settings-group.js +1 -1
  185. package/dist/elements/skeleton.js +1 -1
  186. package/dist/elements/slots.d.ts +35 -3
  187. package/dist/elements/source.js +1 -1
  188. package/dist/elements/status.js +1 -1
  189. package/dist/elements/switch.js +1 -1
  190. package/dist/elements/tabs.js +1 -1
  191. package/dist/elements/tasks.js +1 -1
  192. package/dist/elements/text-shimmer.js +1 -1
  193. package/dist/elements/thinking-bar.js +1 -1
  194. package/dist/elements/thread.js +1 -1
  195. package/dist/elements/toast.js +1 -1
  196. package/dist/elements/tool.js +1 -1
  197. package/dist/elements/tooltip.js +1 -1
  198. package/dist/elements/voice-input.js +1 -1
  199. package/dist/elements/voice-output.js +1 -1
  200. package/dist/elements.d.ts +232 -148
  201. package/dist/index.d.ts +2 -0
  202. package/dist/index.js +5456 -4931
  203. package/dist/index.server.js +4142 -3669
  204. package/dist/kai-provider.es.js +40 -40
  205. package/dist/kai.es.js +1 -1
  206. package/dist/llms/llms-full.txt +184 -69
  207. package/dist/llms/llms.txt +3 -3
  208. package/dist/mcp.es.js +2669 -546
  209. package/dist/primitives/use-resize-observer.d.ts +23 -0
  210. package/dist/primitives/use-speech-recognition.d.ts +8 -0
  211. package/dist/primitives/use-text-stream.d.ts +5 -0
  212. package/dist/react/index.d.ts +115 -266
  213. package/dist/react.js +247 -221
  214. package/dist/register-impl-DpE7icIb.js +293 -0
  215. package/dist/schemas/index.d.ts +1 -1
  216. package/dist/schemas/tool-defs.d.ts +35 -0
  217. package/dist/schemas.js +403 -310
  218. package/dist/{solid-BMuDmFo_.js → solid-BtdqNZI7.js} +8435 -7394
  219. package/dist/{solid-CGYNdLRR.js → solid-CnavwBye.js} +5987 -5035
  220. package/dist/solid.d.ts +5 -0
  221. package/dist/solid.js +210 -203
  222. package/dist/solid.server.js +210 -203
  223. package/dist/state/index.d.ts +4 -0
  224. package/dist/state/persistence.d.ts +51 -0
  225. package/dist/state/stream.d.ts +50 -0
  226. package/dist/state/threads.d.ts +71 -0
  227. package/dist/state.js +269 -169
  228. package/dist/types.d.ts +4 -2
  229. package/dist/ui/dock.d.ts +109 -0
  230. package/dist/ui/hover-card.d.ts +44 -0
  231. package/dist/{variant-aurora-DwSOfraQ.js → variant-aurora-B8II3-I1.js} +27 -27
  232. package/dist/{variant-aurora-wKvAQ5wp.js → variant-aurora-CMNWOSJR.js} +28 -28
  233. package/dist/{variant-aurora-XEkPT0_z.js → variant-aurora-CcuDMWwQ.js} +2 -2
  234. package/dist/{variant-custom-eFIg2XWQ.js → variant-custom-BdvYC7af.js} +40 -40
  235. package/dist/{variant-custom-BhS9wIpU.js → variant-custom-C714mqKl.js} +65 -65
  236. package/dist/variant-custom-L16JLLZy.js +1 -0
  237. package/dist/{elements/chunks/variant-wave-DDCwhLas.js → variant-wave-2S4La2Ka.js} +2 -2
  238. package/dist/{variant-wave-CbVsmgwT.js → variant-wave-D7ruiA1W.js} +25 -25
  239. package/dist/{variant-wave-CUWGx9Bn.js → variant-wave-ecwykeyL.js} +21 -21
  240. package/dist/wire/chunk.d.ts +47 -0
  241. package/dist/wire/diagnostics.d.ts +575 -0
  242. package/dist/wire/encode-probe.d.ts +70 -0
  243. package/dist/wire/encode.d.ts +24 -0
  244. package/dist/wire/index.d.ts +2 -0
  245. package/dist/wire/sse.d.ts +10 -2
  246. package/dist/wire.js +917 -480
  247. package/frameworks/react/index.tsx +132 -93
  248. package/llms-full.txt +184 -69
  249. package/llms.txt +3 -3
  250. package/package.json +26 -6
  251. package/src/agent-tooling/archetypes.ts +41 -9
  252. package/src/agent-tooling/catalog/README.md +676 -0
  253. package/src/agent-tooling/catalog/catalog-types.ts +231 -0
  254. package/src/agent-tooling/catalog/fabrications.ts +96 -0
  255. package/src/agent-tooling/catalog/invariants.ts +284 -0
  256. package/src/agent-tooling/catalog/labs-titles.ts +114 -0
  257. package/src/agent-tooling/catalog/scenarios.ts +87 -0
  258. package/src/agent-tooling/catalog/surfaces.ts +275 -0
  259. package/src/agent-tooling/integrations/anthropic.ts +7 -1
  260. package/src/agent-tooling/integrations/cloudflare.ts +43 -2
  261. package/src/agent-tooling/integrations/langgraph.ts +7 -1
  262. package/src/agent-tooling/integrations/mastra.ts +7 -1
  263. package/src/agent-tooling/integrations/mock.ts +92 -17
  264. package/src/agent-tooling/integrations/ollama.ts +7 -1
  265. package/src/agent-tooling/integrations/openai.ts +7 -1
  266. package/src/agent-tooling/integrations/openrouter.ts +7 -1
  267. package/src/agent-tooling/integrations/pi.ts +56 -1
  268. package/src/agent-tooling/integrations/vercel-ai-sdk.ts +7 -1
  269. package/src/agent-tooling/mcp/css-raw.d.ts +12 -0
  270. package/src/agent-tooling/mcp/manifest.ts +100 -1
  271. package/src/agent-tooling/mcp/server.ts +61 -5
  272. package/src/agent-tooling/mcp/tools/reference.ts +440 -4
  273. package/src/agent-tooling/mcp/tools/scaffold.ts +1834 -307
  274. package/src/agent-tooling/mcp/tools/theme.ts +146 -30
  275. package/src/agent-tooling/mcp/validate-args.ts +141 -0
  276. package/src/agent-tooling/registry.ts +19 -7
  277. package/src/agent-tooling/route-emit.ts +26 -2
  278. package/src/components/attachment-types.ts +47 -1
  279. package/src/components/attachments.tsx +169 -22
  280. package/src/components/audio-visualizer/fit-scale.ts +112 -0
  281. package/src/components/audio-visualizer/index.tsx +80 -3
  282. package/src/components/audio-visualizer/variant-aurora.tsx +10 -4
  283. package/src/components/audio-visualizer/variant-bar.tsx +38 -6
  284. package/src/components/audio-visualizer/variant-custom.tsx +8 -2
  285. package/src/components/audio-visualizer/variant-grid.tsx +16 -11
  286. package/src/components/audio-visualizer/variant-radial.tsx +15 -10
  287. package/src/components/audio-visualizer/variant-wave.tsx +7 -2
  288. package/src/components/chat-container.tsx +4 -1
  289. package/src/components/chat-thread.tsx +36 -7
  290. package/src/components/code-block.tsx +79 -3
  291. package/src/components/composer.tsx +8 -1
  292. package/src/components/conversation-item.tsx +114 -4
  293. package/src/components/conversation-list.tsx +212 -12
  294. package/src/components/embed.tsx +4 -0
  295. package/src/components/form.tsx +41 -14
  296. package/src/components/message.tsx +75 -5
  297. package/src/components/response-stream.tsx +13 -7
  298. package/src/components/screen.tsx +5 -0
  299. package/src/components/scroll-button.tsx +7 -0
  300. package/src/components/toast.tsx +11 -5
  301. package/src/components/voice-input.tsx +35 -3
  302. package/src/components/voice-output.tsx +46 -6
  303. package/src/components/workspace-shell.tsx +285 -0
  304. package/src/diagnostics/hook.ts +352 -0
  305. package/src/diagnostics/index.ts +82 -0
  306. package/src/diagnostics/report-request.ts +296 -0
  307. package/src/elements/attachments.tsx +6 -7
  308. package/src/elements/audio-visualizer.tsx +48 -4
  309. package/src/elements/chat-workspace.tsx +159 -287
  310. package/src/elements/chat.tsx +12 -9
  311. package/src/elements/code-block.tsx +19 -1
  312. package/src/elements/command.tsx +4 -1
  313. package/src/elements/compiled.css +1 -1
  314. package/src/elements/conversation-item.tsx +80 -0
  315. package/src/elements/conversation-list.tsx +60 -11
  316. package/src/elements/default-input.tsx +5 -5
  317. package/src/elements/define.tsx +168 -8
  318. package/src/elements/diagnostic-events.ts +114 -0
  319. package/src/elements/dialog.tsx +47 -0
  320. package/src/elements/disclosure.ts +34 -12
  321. package/src/elements/dock.tsx +204 -0
  322. package/src/elements/dropdown.tsx +123 -0
  323. package/src/elements/element-diagnostics.ts +392 -0
  324. package/src/elements/element-manifest.json +12 -0
  325. package/src/elements/element-meta.json +672 -439
  326. package/src/elements/element-nonscalar.json +170 -0
  327. package/src/elements/element-types.d.ts +232 -148
  328. package/src/elements/menu.tsx +13 -0
  329. package/src/elements/prompt-dock.tsx +2 -1
  330. package/src/elements/prompt-input.tsx +8 -8
  331. package/src/elements/register-impl.ts +46 -0
  332. package/src/elements/register.ts +28 -0
  333. package/src/elements/resizable.tsx +52 -25
  334. package/src/elements/scroll-button.tsx +5 -0
  335. package/src/elements/slots.ts +107 -12
  336. package/src/elements/styles.css +30 -6
  337. package/src/elements/thinking-bar.tsx +16 -7
  338. package/src/elements/toast.tsx +3 -0
  339. package/src/elements/tool.tsx +26 -5
  340. package/src/elements/voice-input.tsx +11 -2
  341. package/src/elements/voice-output.tsx +16 -3
  342. package/src/index.ts +2 -0
  343. package/src/primitives/use-resize-observer.ts +33 -0
  344. package/src/primitives/use-speech-recognition.ts +12 -1
  345. package/src/primitives/use-stick-to-bottom.ts +34 -2
  346. package/src/primitives/use-text-stream.ts +33 -2
  347. package/src/remote/provider-runtime.ts +6 -3
  348. package/src/schemas/index.ts +1 -0
  349. package/src/schemas/tool-defs.ts +297 -6
  350. package/src/solid.ts +5 -0
  351. package/src/state/index.ts +12 -0
  352. package/src/state/persistence.ts +180 -0
  353. package/src/state/stream.ts +71 -4
  354. package/src/state/threads.ts +142 -0
  355. package/src/types.ts +4 -2
  356. package/src/ui/dialog.tsx +6 -0
  357. package/src/ui/dock.tsx +445 -0
  358. package/src/ui/dropdown.tsx +67 -23
  359. package/src/ui/hover-card.tsx +156 -6
  360. package/src/ui/input.tsx +29 -10
  361. package/src/ui/resizable.tsx +11 -0
  362. package/src/wire/chunk.ts +47 -0
  363. package/src/wire/consume.ts +185 -26
  364. package/src/wire/diagnostics.ts +727 -0
  365. package/src/wire/encode-probe.ts +214 -0
  366. package/src/wire/encode.ts +267 -14
  367. package/src/wire/formats/anthropic.ts +8 -1
  368. package/src/wire/formats/openai.ts +5 -0
  369. package/src/wire/index.ts +22 -0
  370. package/src/wire/read.ts +250 -6
  371. package/src/wire/sse.ts +16 -3
  372. package/dist/elements/chunks/action-icons-DW9muWrY.js +0 -1
  373. package/dist/elements/chunks/arrow-left-iJVnQmxA.js +0 -1
  374. package/dist/elements/chunks/attachments-CL6TaqIb.js +0 -1
  375. package/dist/elements/chunks/audio-visualizer-BuUqQmaO.js +0 -1
  376. package/dist/elements/chunks/chat-thread-DxoKaStk.js +0 -1
  377. package/dist/elements/chunks/check-MdLzbxrm.js +0 -1
  378. package/dist/elements/chunks/chevron-down-MK7IzDqe.js +0 -1
  379. package/dist/elements/chunks/chevron-right-C5jXhmtV.js +0 -1
  380. package/dist/elements/chunks/circle-BG7VkPep.js +0 -1
  381. package/dist/elements/chunks/circle-check-GqvO2UBs.js +0 -1
  382. package/dist/elements/chunks/code-block-ClhQO9RW.js +0 -1
  383. package/dist/elements/chunks/collapsible-DBcBkFPh.js +0 -1
  384. package/dist/elements/chunks/context-hKjNqiyQ.js +0 -1
  385. package/dist/elements/chunks/conversation-list-CsGJjSKG.js +0 -1
  386. package/dist/elements/chunks/default-input-B1Mem5wu.js +0 -1
  387. package/dist/elements/chunks/define-B_tXMYRm.js +0 -1
  388. package/dist/elements/chunks/disclosure-DAoYOKew.js +0 -1
  389. package/dist/elements/chunks/dropdown-CmNna5lT.js +0 -1
  390. package/dist/elements/chunks/embed-CjAD2jSi.js +0 -1
  391. package/dist/elements/chunks/form-Cp8Y00Wn.js +0 -1
  392. package/dist/elements/chunks/hover-card-6KtXpLwT.js +0 -1
  393. package/dist/elements/chunks/icon-97R8SR2p.js +0 -1
  394. package/dist/elements/chunks/input-DrseEjhp.js +0 -1
  395. package/dist/elements/chunks/message-CE4k-qaj.js +0 -1
  396. package/dist/elements/chunks/message-square-C9ovmtOo.js +0 -1
  397. package/dist/elements/chunks/reasoning-BrxQfpfI.js +0 -1
  398. package/dist/elements/chunks/scroll-button-CrTo7wm4.js +0 -1
  399. package/dist/elements/chunks/share-DgNbkJKY.js +0 -1
  400. package/dist/elements/chunks/slots-CIw9RlAe.js +0 -1
  401. package/dist/elements/chunks/store-HIPGhNz9.js +0 -1
  402. package/dist/elements/chunks/textarea-DPfuhswM.js +0 -1
  403. package/dist/elements/chunks/variant-custom-D5ZlmziF.js +0 -1
  404. package/dist/elements/chunks/video-Bb86fVYI.js +0 -1
  405. package/dist/elements/chunks/x-BOhjBeOd.js +0 -1
  406. package/dist/primitives/card-validate-generator.testlib.d.ts +0 -20
  407. package/dist/register-impl-DGmGzrEM.js +0 -145
  408. package/dist/variant-custom-MkTNTycd.js +0 -1
  409. package/src/primitives/card-validate-generator.testlib.ts +0 -38
@@ -51,6 +51,10 @@ interface Props extends Record<string, unknown> {
51
51
  * and an accessible name has to contain the visible text. Then either drop
52
52
  * `label` or make it contain the word you slotted. */
53
53
  label?: string;
54
+ /** Stretch the trigger to the full width of the menu's container (a block
55
+ * row), e.g. a sidebar-footer account row. Same affordance as
56
+ * `<kai-button full>`. Attribute: `full`. */
57
+ full?: boolean;
54
58
  /** Drive/observe open state (Shoelace-style: settable + reflected to the `open`
55
59
  * attribute, the menu still self-manages on click/keyboard). Set `el.open = true`,
56
60
  * or `<kai-menu open>`; listen for `kai-open-change`. */
@@ -105,6 +109,7 @@ defineWebComponent<Props, Events>('kai-menu', {
105
109
  triggerLabel: undefined,
106
110
  triggerIconTrailing: undefined,
107
111
  label: undefined,
112
+ full: false,
108
113
  open: undefined,
109
114
  defaultOpen: undefined,
110
115
  disabled: undefined,
@@ -187,6 +192,12 @@ defineWebComponent<Props, Events>('kai-menu', {
187
192
  }
188
193
 
189
194
  return (
195
+ <>
196
+ {/* The host shrinks to the trigger by default (UA inline); `full` makes it
197
+ a block whose trigger button fills the width — the kai-button pattern
198
+ (`:host([full])`), for sidebar-footer account rows and other stretched
199
+ placements. */}
200
+ <style>{':host([full]){display:block}'}</style>
190
201
  <Dropdown
191
202
  defaultOpen={flag('defaultOpen')}
192
203
  disabled={flag('disabled')}
@@ -198,6 +209,7 @@ defineWebComponent<Props, Events>('kai-menu', {
198
209
  props.triggerLabel
199
210
  ? 'gap-1.5 px-2 py-1.5 text-sm font-medium text-muted-foreground hover:bg-accent hover:text-foreground'
200
211
  : 'justify-center p-1.5 text-foreground hover:bg-muted',
212
+ flag('full') && 'w-full',
201
213
  )}
202
214
  // A visible `triggerLabel` IS the name (name-from-contents reaches the
203
215
  // slotted/shadow text), so `label` must not be layered over it: aria-label
@@ -224,5 +236,6 @@ defineWebComponent<Props, Events>('kai-menu', {
224
236
  {renderItems((props.items as KaiMenuItem[] | undefined) ?? [])}
225
237
  </DropdownContent>
226
238
  </Dropdown>
239
+ </>
227
240
  );
228
241
  });
@@ -23,7 +23,8 @@ type SlotName = (typeof SLOT_NAMES)[number];
23
23
 
24
24
  /**
25
25
  * `<kai-prompt-dock>` — a recessed tray that frames a prompt input and can extend
26
- * with optional "lip" regions above and/or below it. The input is the raised card
26
+ * with optional "lip" regions above and/or below it. It sits in the page flow and
27
+ * launches nothing; the floating corner launcher is `<kai-dock>`. The input is the raised card
27
28
  * that floats on the tray; the lips sit in a slightly darker recessed band sharing
28
29
  * the tray's rounding, so the whole thing reads as one cohesive control.
29
30
  *
@@ -27,9 +27,9 @@ interface Props extends Record<string, unknown> {
27
27
  /** What clicking a suggestion does: `'submit'` (default) sends it immediately
28
28
  * as if typed and submitted; `'fill'` just places it in the input. */
29
29
  suggestionMode?: 'submit' | 'fill';
30
- /** Show a Search (Globe) button in the left toolbar; clicking it fires a
31
- * `search` event. */
32
- search?: boolean;
30
+ /** Show a web-search (Globe) button in the left toolbar; clicking it fires a
31
+ * `kai-web-search` event. Attribute: `web-search`. */
32
+ webSearch?: boolean;
33
33
  /** Show a Voice (Mic) button in the left toolbar; clicking it fires a `voice`
34
34
  * event. */
35
35
  voice?: boolean;
@@ -75,8 +75,8 @@ interface Events {
75
75
  'kai-attachments-change': { attachments: AttachmentData[] };
76
76
  /** A suggestion was clicked while `suggestion-mode="fill"`. */
77
77
  'kai-suggestion-click': { value: string };
78
- /** The Search (Globe) toolbar button was clicked. */
79
- 'kai-search': Record<string, never>;
78
+ /** The web-search (Globe) toolbar button was clicked. */
79
+ 'kai-web-search': Record<string, never>;
80
80
  /** The Voice (Mic) toolbar button was clicked. */
81
81
  'kai-voice': Record<string, never>;
82
82
  /** The Stop button was clicked while `stoppable` and `loading` are both true. */
@@ -93,7 +93,7 @@ defineWebComponent<Props, Events>('kai-prompt-input', {
93
93
  loading: false,
94
94
  suggestions: undefined,
95
95
  suggestionMode: 'submit',
96
- search: false,
96
+ webSearch: false,
97
97
  voice: false,
98
98
  stoppable: false,
99
99
  submit: 'always',
@@ -215,7 +215,7 @@ defineWebComponent<Props, Events>('kai-prompt-input', {
215
215
  attach={flag('attach')}
216
216
  suggestions={props.suggestions}
217
217
  attachments={attachments()}
218
- search={flag('search')}
218
+ webSearch={flag('webSearch')}
219
219
  voice={flag('voice')}
220
220
  toolbarActions={toolbarActions()}
221
221
  triggers={props.triggers}
@@ -225,7 +225,7 @@ defineWebComponent<Props, Events>('kai-prompt-input', {
225
225
  onSubmit={handleSubmit}
226
226
  onSuggestionClick={handleSuggestionClick}
227
227
  onAttachmentsChange={(a) => { setAttachments(a); dispatch('kai-attachments-change', { attachments: a }); }}
228
- onSearch={() => dispatch('kai-search')}
228
+ onWebSearch={() => dispatch('kai-web-search')}
229
229
  onVoice={() => dispatch('kai-voice')}
230
230
  onStop={() => dispatch('kai-stop')}
231
231
  onAction={(id) => dispatch('kai-toolbar-action', { action: id })}
@@ -3,7 +3,10 @@
3
3
  // from ./register.ts so the elements entry is SSR-import-safe — see the comment
4
4
  // there. This file is hand-maintained (the generator gen-element-api.mjs SKIPs
5
5
  // register.ts AND this file); keep the component import list here in sync.
6
+ import { installKaiDevtoolsHook } from '../diagnostics/hook';
7
+ import { emitElementRegistry } from './element-diagnostics';
6
8
  import './conversation-list';
9
+ import './conversation-item';
7
10
  import './prompt-input';
8
11
  import './chat';
9
12
  import './chat-workspace';
@@ -77,6 +80,10 @@ import './compare';
77
80
  import './composer';
78
81
  // W3 phase 2: cascading action menu from a JSON items-tree
79
82
  import './menu';
83
+ // The same Dropdown surface with a consumer-owned body: you slot the rows
84
+ // instead of handing over an items tree (the shape a framework consumer needs
85
+ // when the rows are their own components).
86
+ import './dropdown';
80
87
  // W4 phase 1: grouped filterable command/mention palette
81
88
  import './command';
82
89
  // Prompt dock + settings building blocks (graduated from SolidJS prototypes)
@@ -89,8 +96,47 @@ import './pane';
89
96
  import './pane-group';
90
97
  import './agent-card';
91
98
  import './dialog';
99
+ import './dock';
92
100
  // Input & search field family
93
101
  import './input';
94
102
  import './search';
95
103
  import './kbd';
96
104
  import './editable-label';
105
+
106
+ // The devtools recorder hook, installed HERE because this file is already the
107
+ // browser-only half of the elements entry (register.ts gates it behind a window
108
+ // check and a dynamic import). So an app that registers the kai-* elements gets
109
+ // the hook with no work, while an SSR import of the entry still touches no
110
+ // global.
111
+ //
112
+ // At the BOTTOM, and it makes no difference: `import` declarations are hoisted,
113
+ // so every element module above has already evaluated by the time any statement
114
+ // in this file runs. Written here anyway so the order you read is the order that
115
+ // happens. Nothing above emits a diagnostic event, so there is nothing to miss.
116
+ //
117
+ // Idempotent and near-free: with no activation signal it allocates no buffer,
118
+ // makes no subscription, and leaves emission a guarded no-op. An app importing
119
+ // the SolidJS components directly never runs this file and must call
120
+ // `installKaiDevtoolsHook()` itself -- see the docblock in ../diagnostics/index.
121
+ installKaiDevtoolsHook();
122
+
123
+ // One registry snapshot, right after the hook installs and therefore after every
124
+ // element above has registered (import declarations are hoisted, so they all ran
125
+ // before this line).
126
+ //
127
+ // WHY HERE AND NOT INSIDE THE HOOK. The hook lives in ../diagnostics, which is
128
+ // its own rollup bundle and must stay free of any element import -- pulling the
129
+ // manifest in there would drag element bytes into a consumer who only parses
130
+ // streams. The dependency goes elements -> diagnostics, never back.
131
+ //
132
+ // WHY EMIT AT ALL WHEN A PANEL IS USUALLY NOT THERE YET. On the recording branch
133
+ // the hook has already subscribed by this point, so this snapshot lands in the
134
+ // history buffer and a panel attaching seconds later still sees the state at
135
+ // load -- which is the state that answers the hydration question. On the dormant
136
+ // branch `emitElementRegistry()` short-circuits on the active check and costs a
137
+ // symbol read.
138
+ //
139
+ // A panel attaching mid-session with no history gets no snapshot from here and
140
+ // should ask for a fresh one: `emitElementRegistry()` is exported from
141
+ // @kitn.ai/ui/elements for exactly that.
142
+ emitElementRegistry();
@@ -38,6 +38,34 @@ export type { CodeHighlightingOptions } from '../primitives/highlighter';
38
38
  export { classifyTool } from '../components/tool-classify';
39
39
  export type { ToolKind } from '../components/tool-classify';
40
40
 
41
+ // Element-layer diagnostics. `emitElementRegistry()` re-emits the which-elements-
42
+ // are-defined snapshot on demand, for a panel that attached mid-session and so
43
+ // missed the one register-impl emits at load. SSR-safe and a no-op with no
44
+ // subscriber, so calling it unconditionally is fine.
45
+ //
46
+ // WHY THIS ONE PRODUCER IS PUBLIC while `@kitn.ai/ui/diagnostics` deliberately
47
+ // keeps `emitWireDiagnostic`, `nextStreamId` and `setWirePayloadCapture`
48
+ // internal. That rule exists because a consumer able to forge events or reset
49
+ // the stream counter can make a panel lie about a stream that never happened,
50
+ // and one able to flip payload capture can start recording an app's users'
51
+ // conversations without the app deciding to. This function takes no arguments
52
+ // and reports nothing but `customElements.get()` over the manifest: there is no
53
+ // value to forge, no counter to move and no capture to arm. It can only tell the
54
+ // truth about the registry, which is the one thing a late-attaching panel cannot
55
+ // find out for itself.
56
+ //
57
+ // The event TYPES are re-exported here as well as from @kitn.ai/ui/diagnostics:
58
+ // a consumer already importing the elements entry should not have to add a
59
+ // second specifier to name the events it is handed.
60
+ export { emitElementRegistry } from './element-diagnostics';
61
+ export type {
62
+ ElementDiagnosticBase,
63
+ ElementDiagnosticEvent,
64
+ ElementRegistryEvent,
65
+ ElementViolationEvent,
66
+ ElementViolationKind,
67
+ } from './diagnostic-events';
68
+
41
69
  // Imperative toast API — usable directly from the elements entry so consumers
42
70
  // who only import the web-components bundle still get `toast()`. The store is
43
71
  // SSR-safe (no DOM touched until the first toast is raised on the client).
@@ -129,9 +129,16 @@ defineWebComponent<GroupProps, GroupEvents>('kai-resizable', {
129
129
  min: el.getAttribute('min') ?? undefined,
130
130
  max: el.getAttribute('max') ?? undefined,
131
131
  locked: el.hasAttribute('locked') && el.getAttribute('locked') !== 'false',
132
- // Honour both the `hidden` boolean attribute and the IDL property Solid
133
- // (and direct `el.hidden = true`) set the property, which doesn't reflect
134
- // to the attribute on a custom element.
132
+ // Honour both the `hidden` attribute and the property, because on THIS element
133
+ // they are two independent halves of the state and either can be the only one
134
+ // set. Not a fact about custom elements in general — a custom element that does
135
+ // not declare a `hidden` prop keeps the native reflecting accessor. It is a fact
136
+ // about this one: `<kai-resizable-item>` declares `hidden: false`, so
137
+ // component-register owns the property and nothing reflects. Measured:
138
+ // `el.hidden = true` writes no attribute, and after maximize writes the
139
+ // attribute `el.hidden` reads `undefined`. This union is what absorbs that, and
140
+ // it is why `hidden` is exempt from the reflectFlag migration rather than broken
141
+ // by it — see reflectItemConfig's doc below.
135
142
  hidden: el.hidden || (el.hasAttribute('hidden') && el.getAttribute('hidden') !== 'false'),
136
143
  collapsed,
137
144
  };
@@ -543,10 +550,22 @@ defineWebComponent<GroupProps, GroupEvents>('kai-resizable', {
543
550
  * *properties* — `el.size = "280px"` — but component-register only mirrors
544
551
  * attribute→property, never the reverse, so a property-set size never becomes an
545
552
  * attribute and the parent silently falls back to a flexible split. This is the
546
- * exact class of bug the `collapsed` reflection already fixed; extended here to
547
- * `size`/`min`/`max` (strings) and `locked` (boolean). `hidden` is deliberately
548
- * NOT reflected — native `el.hidden` reflects to the attribute on its own and the
549
- * parent honours the `hidden` property directly (and maximize drives `hidden`).
553
+ * exact class of bug the `collapsed` reflection already fixed; handled here for
554
+ * `size`/`min`/`max` (the STRINGS). The booleans — `collapsed` and `locked` go
555
+ * through `reflectFlag` in the facade body instead, which reflects AND keeps the
556
+ * property readable; see WebComponentContext.reflectFlag.
557
+ *
558
+ * `hidden` is deliberately NOT reflected, and NOT because it is already fine — it is
559
+ * split exactly like the others, and measurably so: `el.hidden = true` leaves no
560
+ * attribute (a plain `<div>` gets one), and after maximize writes the attribute
561
+ * `el.hidden` reads `undefined`. Declaring `hidden: false` is what does it — it makes
562
+ * `hidden` a component-register prop, whose non-reflecting accessor shadows the native
563
+ * reflecting one. What makes the split HARMLESS here is that both readers take the
564
+ * union, `el.hidden || el.hasAttribute('hidden')`, so neither half can be missed.
565
+ * Routing it through `reflectFlag` would put a second writer on an attribute the
566
+ * maximize/restore path already owns, so it is a behaviour decision of its own rather
567
+ * than part of the read-back fix. See the exemption in
568
+ * tests/elements/reflected-boolean-coverage.test.ts.
550
569
  *
551
570
  * Durability (don't fight drags): the parent WRITES `size` back to the live percent
552
571
  * during a drag (`persistSizes` → `setAttribute('size', …)`), and that attribute
@@ -568,7 +587,6 @@ defineWebComponent<GroupProps, GroupEvents>('kai-resizable', {
568
587
  export function reflectItemConfig(
569
588
  element: HTMLElement,
570
589
  props: { readonly size?: string; readonly min?: string; readonly max?: string },
571
- flag: (name: string) => boolean,
572
590
  ) {
573
591
  const norm = (v: unknown): string | null => (v == null || v === '' ? null : String(v));
574
592
  const reflectString = (name: 'size' | 'min' | 'max') => {
@@ -591,11 +609,11 @@ export function reflectItemConfig(
591
609
  reflectString('size');
592
610
  reflectString('min');
593
611
  reflectString('max');
594
- // Boolean: mirror the `collapsed`/`loading` precedent a bare `locked` attribute
595
- // and the IDL property are both honoured via `flag`. The parent also writes
596
- // `locked` during maximize/restore; because `flag` re-derives from that same
597
- // attribute state, the reflection stays self-consistent (no fight).
598
- createEffect(() => { element.toggleAttribute('locked', flag('locked')); });
612
+ // `locked` used to be reflected here too. It is a BOOLEAN, so it now goes through
613
+ // `reflectFlag` in the facade body next to `collapsed` a boolean reflected with a
614
+ // bare `toggleAttribute` reads back `undefined` (see WebComponentContext.reflectFlag),
615
+ // and `reflectFlag` is only reachable from the facade's context. What is left here is
616
+ // the string props, which is this helper's real subject: the drag-durability guard.
599
617
  }
600
618
 
601
619
  interface ItemProps extends Record<string, unknown> {
@@ -633,23 +651,32 @@ defineWebComponent<ItemProps>('kai-resizable-item', {
633
651
  locked: false,
634
652
  hidden: false,
635
653
  collapsed: false,
636
- }, (props, { element, flag }) => {
654
+ }, (props, { element, reflectFlag }) => {
637
655
  // Reflect the reactive `collapsed` prop to the `collapsed` ATTRIBUTE the parent
638
656
  // <kai-resizable> reads (and observes). This is what makes a bare JSX boolean
639
657
  // collapse the panel at INITIAL render from any framework: component-register
640
658
  // parses `<kai-resizable-item collapsed>` to `undefined` (not `true`), and a JSX
641
- // boolean sets neither `hidden` nor the IDL property — so `flag('collapsed')`
642
- // (which also honours the attribute) is the reliable source, and toggleAttribute
643
- // mirrors it back so the parent's readItems()/MutationObserver lay it out. Same
644
- // pattern as <kai-chat>'s `loading` reflection. We touch only `collapsed`, never
645
- // `hidden`, so the back-compat hidden path and maximize (which drive `hidden`)
646
- // are untouched.
647
- createEffect(() => { element.toggleAttribute('collapsed', flag('collapsed')); });
648
- // Reflect the other config props (`size`/`min`/`max`/`locked`) property→attribute
649
- // for the SAME reason a framework that sets them as DOM properties (React et al.)
650
- // would otherwise be invisible to the parent. See reflectItemConfig for the
659
+ // boolean sets neither `hidden` nor the IDL property — so the flag resolution
660
+ // (which also honours the attribute) is the reliable source, and the attribute is
661
+ // mirrored back so the parent's readItems()/MutationObserver lay it out. Same
662
+ // pattern as <kai-chat>'s `loading` reflection, and now literally the same code:
663
+ // `reflectFlag` also makes `el.collapsed` READ BACK, which a hand-rolled
664
+ // toggleAttribute effect does not (WebComponentContext.reflectFlag).
665
+ //
666
+ // We touch only `collapsed` and `locked`, never `hidden`, so the back-compat hidden
667
+ // path and maximize (which drive `hidden`) are untouched see the named exemption
668
+ // in tests/elements/reflected-boolean-coverage.test.ts for why `hidden` is a
669
+ // separate decision rather than an oversight.
670
+ reflectFlag('collapsed');
671
+ // `locked` is the parent's other boolean input; the parent also writes it during
672
+ // maximize/restore, and because the flag resolution re-derives from that same
673
+ // attribute state the reflection stays self-consistent (no fight).
674
+ reflectFlag('locked');
675
+ // Reflect the STRING config props (`size`/`min`/`max`) property→attribute for the
676
+ // SAME reason — a framework that sets them as DOM properties (React et al.) would
677
+ // otherwise be invisible to the parent. See reflectItemConfig for the
651
678
  // drag-durability guard on `size`.
652
- reflectItemConfig(element, props, flag);
679
+ reflectItemConfig(element, props);
653
680
  return (
654
681
  <>
655
682
  {/* The item host fills the panel's single grid cell (the panel stretches it on
@@ -109,6 +109,11 @@ defineWebComponent<Props, Events>('kai-scroll-button', {
109
109
  variant={props.variant ?? 'outline'}
110
110
  size={props.size ?? 'icon'}
111
111
  aria-label="Scroll to bottom"
112
+ // While at the bottom this button is fully transparent and pointer-inert,
113
+ // but it stayed in the tab order — a keyboard user landed on a control
114
+ // they could not see. Mirrors ScrollButton in components/scroll-button.tsx.
115
+ tabindex={isAtBottom() ? -1 : 0}
116
+ aria-hidden={isAtBottom() ? 'true' : undefined}
112
117
  class={cn(
113
118
  'rounded-full transition-all duration-150 ease-out',
114
119
  !isAtBottom()
@@ -62,6 +62,39 @@ export const CONVERSATIONS_PARTS: PartDef[] = [
62
62
  doc: 'The right-aligned trailing text on each conversation row (a count, status, or relative time). Set it per item via the `trailing` field; otherwise a short auto relative time is derived from `updatedAt`. Recolor or resize it from outside.',
63
63
  recipe: 'kai-conversations::part(trailing) { color: var(--color-primary); font-variant-numeric: tabular-nums }',
64
64
  },
65
+ {
66
+ name: 'items',
67
+ doc: 'The item-mode listbox region wrapping your slotted `<kai-conversation-item>` children.',
68
+ recipe: 'kai-conversations::part(items) { gap: 2px }',
69
+ },
70
+ ];
71
+
72
+ /** Slots of `<kai-conversation-item>` — one composed row of a consumer-owned
73
+ * conversation loop (the default slot is the title). */
74
+ export const CONVERSATION_ITEM_SLOTS: SlotDef[] = [
75
+ { name: 'leading', mode: 'inject', part: true, doc: 'Leading region before the title (an icon or avatar).' },
76
+ { name: 'meta', mode: 'inject', part: true, doc: 'Meta region under the title (a timestamp or status line).' },
77
+ { name: 'menu', mode: 'inject', part: true, doc: 'Your own row menu (a popover trigger). Never selects the row.' },
78
+ ];
79
+
80
+ /** Styleable `::part`s of `<kai-conversation-item>`. (`leading`/`meta`/`menu`
81
+ * are covered by their slot defs' `part: true` flags.) */
82
+ export const CONVERSATION_ITEM_PARTS: PartDef[] = [
83
+ {
84
+ name: 'body',
85
+ doc: 'The activation surface inside the row (button role; carries aria-current and the roving tabindex). The focus ring paints here.',
86
+ recipe: 'kai-conversation-item::part(body) { outline-offset: 2px }',
87
+ },
88
+ {
89
+ name: 'row',
90
+ doc: 'The whole row surface. Carries `data-active` while selected.',
91
+ recipe: 'kai-conversation-item::part(row) { border-radius: 0.5rem }',
92
+ },
93
+ {
94
+ name: 'title',
95
+ doc: 'The title line (the default slot renders inside it).',
96
+ recipe: 'kai-conversation-item::part(title) { font-weight: 600 }',
97
+ },
65
98
  ];
66
99
 
67
100
  /** Slots of `<kai-message>` — per-message composition seams. `before-body` and
@@ -183,6 +216,33 @@ export const SKELETON_PARTS: PartDef[] = [
183
216
  },
184
217
  ];
185
218
 
219
+ /**
220
+ * The parts of ONE attachment item, declared by `components/attachments.tsx`
221
+ * and therefore surfaced by every element that renders an attachment — the
222
+ * standalone `<kai-attachments>` AND the thread inside `<kai-message>` /
223
+ * `<kai-chat>`. Shared rather than duplicated so the two can never document
224
+ * different names for the same node.
225
+ *
226
+ * These are a styling seam and a TESTING seam, and the second is why they exist
227
+ * at all. `examples/internal/openrouter-spike` proves "a user can see which file
228
+ * was attached" by locating these; before they existed it pinned the chip by
229
+ * `span.truncate` and broke the day the thread moved to the grid tile, while the
230
+ * filename was still plainly on screen. A published part is what lets an
231
+ * assertion name the thing rather than its current markup.
232
+ */
233
+ export const ATTACHMENT_ITEM_PARTS: PartDef[] = [
234
+ {
235
+ name: 'attachment',
236
+ doc: 'One attachment item: the chip, row or tile, whichever variant is rendering. Restyle its background, radius or border from outside without caring which layout it is.',
237
+ recipe: 'kai-chat::part(attachment) { border-radius: 0.25rem }',
238
+ },
239
+ {
240
+ name: 'attachment-name',
241
+ doc: 'The attachment’s filename label. Present in every variant that shows one (a grid tile omits it for an image, which is its own label). Retune its type or hide it entirely.',
242
+ recipe: 'kai-chat::part(attachment-name) { font-size: 0.75rem }',
243
+ },
244
+ ];
245
+
186
246
  /** Styleable `::part`s of `<kai-message>`. (The `avatar` part is contributed by
187
247
  * the `avatar` slot's `part: true` flag, so it is not repeated here.) */
188
248
  export const MESSAGE_PARTS: PartDef[] = [
@@ -211,6 +271,10 @@ export const MESSAGE_PARTS: PartDef[] = [
211
271
  doc: 'The citation row rendered from the message’s `source` parts: a wrapped row of chips below the bubble, never inside it. Restyle its spacing or hide it entirely from outside.',
212
272
  recipe: 'kai-message::part(citations) { gap: 0.5rem }',
213
273
  },
274
+ // A message renders its `file` parts through the same Attachment chrome
275
+ // `<kai-attachments>` uses, so it exposes the same handles. Shared, not
276
+ // restated — see ATTACHMENT_ITEM_PARTS.
277
+ ...ATTACHMENT_ITEM_PARTS,
214
278
  ];
215
279
 
216
280
  /** Styleable `::part`s of `<kai-attachments>`. */
@@ -220,6 +284,7 @@ export const ATTACHMENTS_PARTS: PartDef[] = [
220
284
  doc: 'The image shown in an attachment’s hover-card preview. Bounded by default (max ~320×256, aspect preserved) so a large image never blows up the card. Raise or lower the cap from outside.',
221
285
  recipe: 'kai-attachments::part(preview) { max-width: 32rem; max-height: 24rem }',
222
286
  },
287
+ ...ATTACHMENT_ITEM_PARTS,
223
288
  ];
224
289
 
225
290
  /** Styleable `::part`s of `<kai-status>`. */
@@ -323,21 +388,21 @@ export const CARD_PARTS: PartDef[] = [
323
388
  },
324
389
  ];
325
390
 
326
- /** Injection slots of `<kai-workspace>` (the carrier regions the consumer drops
327
- * content into: brand/tabs, the upgrade/Design/user-menu cluster, a top banner). */
391
+ /** Slots of `<kai-workspace>` (the five shell regions; main also takes the default slot). */
328
392
  export const WORKSPACE_SLOTS: SlotDef[] = [
329
- { name: 'sidebar-header', mode: 'inject', doc: 'Top of the conversation rail (brand, a kai-tabs strip).' },
330
- { name: 'sidebar-footer', mode: 'inject', doc: 'Bottom of the rail: an upgrade card, a Design trigger, a user-menu cluster.' },
331
- { name: 'main-header', mode: 'inject', doc: 'Top of the main region (a top-placed banner or a corner action).' },
332
- { name: 'main', mode: 'replace', doc: 'Replace the built-in chat thread with your own main view (a home or dashboard screen). Omit to keep the thread.' },
393
+ { name: 'header', mode: 'inject', part: true, doc: 'The top band across the full shell width (app bar, tabs, breadcrumbs).' },
394
+ { name: 'start', mode: 'inject', part: true, doc: 'The inline-start aside column (a conversation rail, a nav, a file tree). Resizable and collapsible.' },
395
+ { name: 'main', mode: 'inject', part: true, doc: 'The main region. Unnamed children project here too, via the default slot.' },
396
+ { name: 'end', mode: 'inject', part: true, doc: 'The inline-end aside column (inspector, notes, preview). Resizable and collapsible.' },
397
+ { name: 'footer', mode: 'inject', part: true, doc: 'The bottom band across the full shell width (status bar, disclaimers).' },
333
398
  ];
334
399
 
335
- /** Styleable `::part`s of `<kai-workspace>`. */
400
+ /** Styleable `::part`s of `<kai-workspace>` (beyond the slot-backed header/start/main/end/footer parts). */
336
401
  export const WORKSPACE_PARTS: PartDef[] = [
337
402
  {
338
- name: 'sidebar',
339
- doc: 'The conversation rail. Carries a subtle, theme-aware default background (bg-surface); override its background, border, or width from outside. `sidebar-min-width` sets its min px width and `collapse-below` auto-collapses it under a width.',
340
- recipe: 'kai-workspace::part(sidebar) { background: var(--color-card); border-right: 1px solid var(--color-border) }',
403
+ name: 'aside',
404
+ doc: 'Both aside columns match this part (each also matches its own start/end part). Restyle the shared aside surface or border from outside; the --kai-workspace-start-* and --kai-workspace-end-* custom properties set the widths.',
405
+ recipe: 'kai-workspace::part(aside) { background: var(--color-card) }',
341
406
  },
342
407
  ];
343
408
 
@@ -474,6 +539,18 @@ export const PROMPT_DOCK_PARTS: PartDef[] = [
474
539
  },
475
540
  ];
476
541
 
542
+ /** Slots + styleable `::part`s of `<kai-dock>` (the floating corner launcher — not
543
+ * `<kai-prompt-dock>`, which is an in-flow tray around a prompt input). */
544
+ export const DOCK_SLOTS: SlotDef[] = [
545
+ { name: 'panel', mode: 'replace', part: true, doc: 'The panel body. ANY element: a `<kai-chat>`, a form, your own component. The dock never reads or types it, and the default slot is the same region.' },
546
+ { name: 'launcher', mode: 'inject', doc: 'Content inside the built-in button while CLOSED; defaults to a chat glyph. Text works as well as an icon: the button keeps its height and grows sideways into a pill, so a label like "Support" is not clipped. The BUTTON is never slotted away, because it owns aria-expanded, aria-controls, the toggle wiring and the focus return.' },
547
+ { name: 'launcher-open', mode: 'inject', doc: 'Content inside the button while OPEN; defaults to a ✕. Fill only `launcher` and that glyph stays while open rather than morphing into a built-in that clashes with it.' },
548
+ ];
549
+ export const DOCK_PARTS: PartDef[] = [
550
+ { name: 'launcher', doc: 'The launcher button pinned to the corner: a disc by default, a pill once you slot a text label. Restyle its surface or shadow; --kai-dock-launcher-size sets its height and its minimum width.', recipe: 'kai-dock::part(launcher) { background: var(--color-info) }' },
551
+ { name: 'badge', doc: 'The unread dot on the launcher, rendered only while closed and only when `unread` is set. Restyle its color or size.', recipe: 'kai-dock::part(badge) { background: var(--color-success) }' },
552
+ ];
553
+
477
554
  /** Styleable `::part`s of `<kai-segmented>`. */
478
555
  export const SEGMENTED_PARTS: PartDef[] = [
479
556
  { name: 'track', doc: 'The segmented track (the pill container holding the segments). Restyle its background, radius, or padding.', recipe: 'kai-segmented::part(track) { border-radius: 9999px }' },
@@ -512,6 +589,13 @@ export const AGENT_CARD_PARTS: PartDef[] = [
512
589
  { name: 'menu', doc: 'The trailing overflow ("...") menu button.', recipe: 'kai-agent-card::part(menu) { opacity: 1 }' },
513
590
  ];
514
591
 
592
+ /** Styleable `::part`s of `<kai-code-block>` (one syntax-highlighted code block).
593
+ * The code region itself is not a part — it is rebuilt when the highlight lands,
594
+ * and its look is owned by the Shiki theme (`code-theme`). */
595
+ export const CODE_BLOCK_PARTS: PartDef[] = [
596
+ { name: 'copy', doc: 'The copy-to-clipboard button in the header row. Hide it with `copy="false"` rather than CSS.', recipe: 'kai-code-block::part(copy) { color: var(--color-primary) }' },
597
+ ];
598
+
515
599
  /** Slots + styleable `::part`s of `<kai-dialog>` (the centered modal). */
516
600
  export const DIALOG_SLOTS: SlotDef[] = [
517
601
  { name: 'header', mode: 'inject', part: true, doc: 'Optional title region at the top of the panel.' },
@@ -594,6 +678,13 @@ export const POPOVER_SLOTS: SlotDef[] = [
594
678
  { name: 'trigger', mode: 'replace', doc: 'The control that opens the popover (a button, an avatar, …). The panel anchors to it.' },
595
679
  ];
596
680
 
681
+ /** Slots of `<kai-dropdown>`. The menu body is the default slot; `trigger` is the
682
+ * VISUAL content of the built-in trigger button (never your own `<button>`/`<a>`,
683
+ * which would nest interactive elements — the `kai-menu` rule). */
684
+ export const DROPDOWN_SLOTS: SlotDef[] = [
685
+ { name: 'trigger', mode: 'replace', doc: 'Visual content of the trigger button (an icon, text, an `<svg>`). Replaces the built-in trigger* content; name it with `label`.' },
686
+ ];
687
+
597
688
  /** Styleable `::part`s of `<kai-audio-visualizer>`. Every DOM variant (`bar`,
598
689
  * `grid`, `radial`) shares the `bar`/`cell` markup pattern; the shader variants
599
690
  * (`wave`, `aurora`) share the `canvas` part. Lit items ALSO carry the
@@ -631,7 +722,8 @@ export const AUDIO_VISUALIZER_PARTS: PartDef[] = [
631
722
  export const ELEMENT_COMPOSITION: Record<string, ElementComposition> = {
632
723
  'kai-chat': { slots: CHAT_SLOTS, parts: CHAT_PARTS },
633
724
  'kai-command': { parts: COMMAND_PARTS },
634
- 'kai-conversations': { slots: CONVERSATIONS_SLOTS, parts: CONVERSATIONS_PARTS },
725
+ 'kai-conversations': { slots: CONVERSATIONS_SLOTS, parts: CONVERSATIONS_PARTS, children: 'Your own `<kai-conversation-item>` rows (item mode: the consumer-owned loop). Data rows do not render while any are present.' },
726
+ 'kai-conversation-item': { slots: CONVERSATION_ITEM_SLOTS, parts: CONVERSATION_ITEM_PARTS, children: 'The row title. `leading`, `meta` and `menu` are the named regions around it.' },
635
727
  'kai-message': { slots: MESSAGE_SLOTS, parts: MESSAGE_PARTS },
636
728
  'kai-thread': { slots: THREAD_SLOTS },
637
729
  'kai-prompt-input': { slots: PROMPT_INPUT_SLOTS, parts: PROMPT_INPUT_PARTS },
@@ -650,7 +742,7 @@ export const ELEMENT_COMPOSITION: Record<string, ElementComposition> = {
650
742
  'kai-voice-output': { parts: VOICE_OUTPUT_PARTS },
651
743
  'kai-screen': { slots: SCREEN_SLOTS, parts: SCREEN_PARTS, children: 'The screen body, below the title bar.' },
652
744
  'kai-card': { slots: CARD_SLOTS, parts: CARD_PARTS, children: 'The card body, below the header/media regions.' },
653
- 'kai-workspace': { slots: WORKSPACE_SLOTS, parts: WORKSPACE_PARTS },
745
+ 'kai-workspace': { slots: WORKSPACE_SLOTS, parts: WORKSPACE_PARTS, children: 'The main region content (same region as the `main` slot): your `<kai-chat>`, or any app view.' },
654
746
  'kai-nav': { parts: NAV_PARTS },
655
747
  'kai-coachmark': { slots: COACHMARK_SLOTS, parts: COACHMARK_PARTS, children: 'The ANCHOR the coachmark points at: the element it attaches to and positions against. The bubble body is the `content` slot.' },
656
748
  'kai-progress-bar': { parts: PROGRESS_BAR_PARTS },
@@ -664,6 +756,7 @@ export const ELEMENT_COMPOSITION: Record<string, ElementComposition> = {
664
756
  'kai-pane-group': { parts: PANE_GROUP_PARTS, children: 'Content shown for every tab. Use it INSTEAD of the per-tab `slot="<tab id>"` seams when you swap the content yourself.' },
665
757
  'kai-agent-card': { parts: AGENT_CARD_PARTS },
666
758
  'kai-dialog': { slots: DIALOG_SLOTS, parts: DIALOG_PARTS, children: 'The dialog body, between the `header` and `footer` slots.' },
759
+ 'kai-dock': { slots: DOCK_SLOTS, parts: DOCK_PARTS, children: 'The panel body, the same region as `slot="panel"`.' },
667
760
  'kai-input': { slots: INPUT_SLOTS, parts: INPUT_PARTS },
668
761
  'kai-search': { parts: SEARCH_PARTS },
669
762
  'kai-kbd': { parts: KBD_PARTS, children: 'Literal key text, when you are not using the `keys` prop to render key caps.' },
@@ -671,9 +764,11 @@ export const ELEMENT_COMPOSITION: Record<string, ElementComposition> = {
671
764
  'kai-empty': { slots: EMPTY_SLOTS, children: 'The empty-state body below the title/description, usually the call to action.' },
672
765
  'kai-file-upload': { children: 'Custom dropzone content, replacing the default label (the `label` prop is the fallback).' },
673
766
  'kai-popover': { slots: POPOVER_SLOTS, children: 'The popover panel body. The control that opens it is the `trigger` slot.' },
767
+ 'kai-dropdown': { slots: DROPDOWN_SLOTS, children: 'The menu body: your own rows. Give each `role="menuitem"`. The control that opens it is the `trigger` slot.' },
674
768
  'kai-resizable': { children: 'The `<kai-resizable-item>` panels, in order. Dividers are inserted between them.' },
675
769
  'kai-resizable-item': { children: 'This panel\'s content.' },
676
770
  'kai-tooltip': { children: 'The TRIGGER the tooltip describes. The tip text is the `text` prop.' },
771
+ 'kai-code-block': { parts: CODE_BLOCK_PARTS },
677
772
  'kai-audio-visualizer': { parts: AUDIO_VISUALIZER_PARTS },
678
773
  };
679
774
 
@@ -40,12 +40,23 @@
40
40
  /* One token-driven focus ring for EVERY focusable element in the shadow root.
41
41
  Blue by default (via --color-ring), consistent in light + dark, and
42
42
  overridable through --kai-color-ring. Uses `outline` (not a box-shadow
43
- ring) so it never clips on overflow and follows the element's radius;
44
- `:focus-visible` so it only appears for keyboard navigation, never on a
45
- mouse click. Components that render their own ring set
46
- `focus-visible:outline-none` (a utility in a later cascade layer) and win
47
- over this so there's no double ring, just a guaranteed blue fallback for
48
- the many controls that have no explicit focus style. */
43
+ ring) because it follows the element's radius and unlike a `ring-*`
44
+ utility is written in literal values, so it does not depend on any
45
+ `--tw-*` custom property. That matters more than it looks: `@property`
46
+ registration is document-global and is ignored inside a shadow root, so
47
+ every `--tw-*` here is unset and every `ring-*` utility computes to
48
+ nothing. This literal rule is the only reason most of the kit had a focus
49
+ indicator at all. `:focus-visible` so it only appears for keyboard
50
+ navigation, never on a mouse click. Components that render their own ring
51
+ set `focus-visible:outline-none` (a utility in a later cascade layer) and
52
+ win over this — so there's no double ring, just a guaranteed blue fallback
53
+ for the many controls that have no explicit focus style.
54
+
55
+ CAVEAT, learned the hard way: an outline is NOT immune to clipping. It is
56
+ unaffected by the element's OWN overflow, but `outline-offset: 2px` draws
57
+ it entirely outside the border box, where an ANCESTOR with
58
+ `overflow: clip`/`hidden` erases it completely. Controls that sit flush
59
+ inside such a container need `.kai-focus-inset` below. */
49
60
  :focus-visible {
50
61
  outline: 2px solid var(--color-ring);
51
62
  outline-offset: 2px;
@@ -87,6 +98,19 @@
87
98
  }
88
99
 
89
100
  @layer components {
101
+ /* Focus ring drawn INSIDE the element's border box, for focusable regions
102
+ that sit flush against a clipping ancestor. The scrollable code-block body
103
+ and the message log are both `overflow-*` regions filling a rounded
104
+ `overflow: clip` container, so the default `outline-offset: 2px` put the
105
+ whole ring outside the clip and it never painted — the outline computed
106
+ correctly, matched `:focus-visible`, and was erased before it reached the
107
+ screen. A negative offset keeps it clear of the boundary. Same token and
108
+ width as the base ring, so it reads identically. */
109
+ .kai-focus-inset:focus-visible {
110
+ outline: 2px solid var(--color-ring);
111
+ outline-offset: -2px;
112
+ }
113
+
90
114
  /* Designed radio + checkbox (appearance-none) so the form controls read as
91
115
  intentional, theme correctly in light/dark, and have comfortable targets —
92
116
  replacing the weak native defaults. Used via `.kai-radio` / `.kai-checkbox`. */