@kitn.ai/ui 0.26.0 → 0.28.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 (488) hide show
  1. package/README.md +25 -6
  2. package/bin/mcp.js +16 -3
  3. package/bin/route.js +23 -0
  4. package/bin/route.test.js +58 -0
  5. package/dist/{elements/chunks/Icon--Y8vsFal.js → Icon-DObhsRtm.js} +1 -1
  6. package/dist/action-icons-CcgNN2oT.js +1 -0
  7. package/dist/arrow-down-CjRU3JgV.js +1 -0
  8. package/dist/arrow-left-Bnpb1UEW.js +1 -0
  9. package/dist/artifact-Bwt7OgcR.js +1 -0
  10. package/dist/assets/dev-5XzXYR21.js +91 -0
  11. package/dist/attachments-Qr4kg-2b.js +1 -0
  12. package/dist/{elements/chunks/audio-visualizer-D7lB6mvR.js → audio-visualizer-DToYdfSc.js} +1 -1
  13. package/dist/badge-CdEu_jn2.js +1 -0
  14. package/dist/bash-InADTalH.js +1 -6
  15. package/dist/bell-DQAAOpDz.js +1 -0
  16. package/dist/{elements/chunks/button-flUbeufF.js → button-DkuOnGLy.js} +1 -1
  17. package/dist/card-renderer-Jx70m6_l.js +1 -0
  18. package/dist/card-routing-0YidER_3.js +1 -0
  19. package/dist/check-njkVdZu2.js +1 -0
  20. package/dist/checkbox-D7iRB3nL.js +1 -0
  21. package/dist/checkbox-group-B2_rpoPM.js +1 -0
  22. package/dist/chevron-down-B72s6PXR.js +1 -0
  23. package/dist/chevron-right-BatuHzUm.js +1 -0
  24. package/dist/choice-card-m0BYqqOK.js +1 -0
  25. package/dist/circle-check-B62pODB3.js +1 -0
  26. package/dist/circle-x-BPG0iPst.js +1 -0
  27. package/dist/circle-zx0ByzY9.js +1 -0
  28. package/dist/{elements/chunks/cn-CU7UAWzk.js → cn-DN5AWfiS.js} +1 -1
  29. package/dist/code-block-ClMLKb7C.js +1 -0
  30. package/dist/{elements/chunks/collapsible-BzbXpPzr.js → collapsible-B4DlWicL.js} +1 -1
  31. package/dist/components/attachment-types.d.ts +7 -0
  32. package/dist/components/audio-visualizer/fit-scale.d.ts +1 -1
  33. package/dist/components/card-renderer.d.ts +13 -0
  34. package/dist/components/card.d.ts +4 -0
  35. package/dist/components/chat-thread.d.ts +135 -2
  36. package/dist/components/conversation-item.d.ts +35 -7
  37. package/dist/components/conversation-list.d.ts +17 -5
  38. package/dist/components/conversation-panel.d.ts +29 -0
  39. package/dist/components/form-widgets.d.ts +67 -2
  40. package/dist/components/form.d.ts +48 -1
  41. package/dist/components/home-panel.d.ts +34 -0
  42. package/dist/components/message.d.ts +25 -3
  43. package/dist/components/reasoning.d.ts +6 -0
  44. package/dist/components/scroll-button.d.ts +11 -0
  45. package/dist/components/thread.d.ts +5 -0
  46. package/dist/components/tool-types.d.ts +9 -0
  47. package/dist/components/widget-tab-bar.d.ts +16 -0
  48. package/dist/composer-sQ0HcvyG.js +106 -0
  49. package/dist/confirm-card-rQWDQsnC.js +1 -0
  50. package/dist/construct-cli.es.js +1337 -0
  51. package/dist/{elements/chunks/context-VLIL3sJ-.js → context-0kg4ZX1J.js} +1 -1
  52. package/dist/controllable-CnGpMF6r.js +1 -0
  53. package/dist/conversation-item-BxwQLaFJ.js +1 -0
  54. package/dist/conversation-list-BCfGN1JU.js +1 -0
  55. package/dist/copy-n9u9rqpi.js +1 -0
  56. package/dist/core-AYMC6_lb.js +12 -5874
  57. package/dist/{create-tween-BFT6c-Y8.js → create-tween-C-q-JCM-.js} +1 -1
  58. package/dist/{create-tween-CJNlzoaO.js → create-tween-CRrJXxBd.js} +1 -1
  59. package/dist/{create-tween-CQED4T0g.js → create-tween-DZ3RRDHD.js} +1 -1
  60. package/dist/{elements/chunks/create-tween-c4Z8TDYz.js → create-tween-uPkArjIG.js} +2 -2
  61. package/dist/css-M7EaDHN_.js +1 -6
  62. package/dist/custom-elements.json +1433 -202
  63. package/dist/default-input-CaMrJkgH.js +1 -0
  64. package/dist/define-By-uL588.js +1 -0
  65. package/dist/define.d.ts +4 -0
  66. package/dist/define.js +510 -0
  67. package/dist/define.server.d.ts +5 -0
  68. package/dist/define.server.js +506 -0
  69. package/dist/{elements/chunks/disclosure-CcNbyLy0.js → disclosure-WUImBbDv.js} +1 -1
  70. package/dist/{elements/chunks/dropdown-BgWTthUx.js → dropdown-wZVeJOMd.js} +1 -1
  71. package/dist/elements/agent-card.js +1 -1
  72. package/dist/elements/artifact.js +1 -1
  73. package/dist/elements/attachments.js +1 -1
  74. package/dist/elements/audio-visualizer.js +1 -1
  75. package/dist/elements/autoloader.js +1 -1
  76. package/dist/elements/avatar.js +1 -1
  77. package/dist/elements/badge.js +1 -1
  78. package/dist/elements/button.js +1 -1
  79. package/dist/elements/card.js +1 -1
  80. package/dist/elements/cards.js +1 -1
  81. package/dist/elements/chain-of-thought.js +1 -1
  82. package/dist/elements/chat-scope-picker.js +1 -1
  83. package/dist/elements/chat-workspace.js +1 -1
  84. package/dist/elements/chat.js +1 -1
  85. package/dist/elements/checkbox-group.d.ts +1 -0
  86. package/dist/elements/checkbox-group.js +1 -0
  87. package/dist/elements/checkbox.d.ts +1 -0
  88. package/dist/elements/checkbox.js +1 -0
  89. package/dist/elements/checkpoint.js +1 -1
  90. package/dist/elements/choice.js +1 -1
  91. package/dist/elements/coachmark.js +1 -1
  92. package/dist/elements/code-block.js +1 -1
  93. package/dist/elements/command.js +1 -1
  94. package/dist/elements/compare.js +1 -1
  95. package/dist/elements/composer.js +1 -1
  96. package/dist/elements/confirm-card.js +1 -1
  97. package/dist/elements/context-meter.js +1 -1
  98. package/dist/elements/conversation-item.js +1 -1
  99. package/dist/elements/conversation-list.js +1 -1
  100. package/dist/elements/define-entry.d.ts +12 -0
  101. package/dist/elements/dialog.js +1 -1
  102. package/dist/elements/dock.js +102 -8
  103. package/dist/elements/dropdown.js +1 -1
  104. package/dist/elements/editable-label.js +1 -1
  105. package/dist/elements/element-data-types.d.ts +37 -0
  106. package/dist/elements/embed.js +1 -1
  107. package/dist/elements/empty.js +1 -1
  108. package/dist/elements/feedback-bar.js +1 -1
  109. package/dist/elements/file-tree.js +1 -1
  110. package/dist/elements/file-upload.js +1 -1
  111. package/dist/elements/form.js +1 -1
  112. package/dist/elements/hover-card.js +1 -1
  113. package/dist/elements/icon.js +1 -1
  114. package/dist/elements/image.js +1 -1
  115. package/dist/elements/input.js +1 -1
  116. package/dist/elements/kbd.js +1 -1
  117. package/dist/elements/link-preview.js +1 -1
  118. package/dist/elements/loader.js +1 -1
  119. package/dist/elements/markdown.js +1 -1
  120. package/dist/elements/menu.js +1 -1
  121. package/dist/elements/message-skills.js +1 -1
  122. package/dist/elements/message.js +1 -1
  123. package/dist/elements/model-switcher.js +1 -1
  124. package/dist/elements/nav.js +1 -1
  125. package/dist/elements/notice.js +1 -1
  126. package/dist/elements/pane-grid.d.ts +1 -0
  127. package/dist/elements/pane-grid.js +1 -0
  128. package/dist/elements/pane-group.js +1 -1
  129. package/dist/elements/pane.js +1 -1
  130. package/dist/elements/popover.js +1 -1
  131. package/dist/elements/progress-bar.js +1 -1
  132. package/dist/elements/prompt-dock.js +1 -1
  133. package/dist/elements/prompt-input.js +1 -1
  134. package/dist/elements/prompt-suggestions.js +1 -1
  135. package/dist/elements/radio-group.d.ts +1 -0
  136. package/dist/elements/radio-group.js +1 -0
  137. package/dist/elements/reasoning.js +1 -1
  138. package/dist/elements/remote.js +1 -1
  139. package/dist/elements/resizable.js +1 -1
  140. package/dist/elements/response-stream.js +2 -2
  141. package/dist/elements/screen.js +1 -1
  142. package/dist/elements/scroll-area.js +1 -1
  143. package/dist/elements/scroll-button.js +1 -1
  144. package/dist/elements/search.js +1 -1
  145. package/dist/elements/segmented.js +1 -1
  146. package/dist/elements/select.d.ts +1 -0
  147. package/dist/elements/select.js +1 -0
  148. package/dist/elements/separator.js +1 -1
  149. package/dist/elements/setting-item.js +1 -1
  150. package/dist/elements/settings-group.js +1 -1
  151. package/dist/elements/skeleton.js +1 -1
  152. package/dist/elements/slider.d.ts +1 -0
  153. package/dist/elements/slider.js +1 -0
  154. package/dist/elements/slots.d.ts +1 -1
  155. package/dist/elements/source.js +1 -1
  156. package/dist/elements/status.js +1 -1
  157. package/dist/elements/switch.js +1 -1
  158. package/dist/elements/tabs.js +1 -1
  159. package/dist/elements/tasks.js +1 -1
  160. package/dist/elements/text-shimmer.js +1 -1
  161. package/dist/elements/thinking-bar.js +1 -1
  162. package/dist/elements/thread.js +1 -1
  163. package/dist/elements/toast.js +1 -1
  164. package/dist/elements/tool.js +1 -1
  165. package/dist/elements/tooltip.js +1 -1
  166. package/dist/elements/voice-input.js +1 -1
  167. package/dist/elements/voice-output.js +1 -1
  168. package/dist/elements.d.ts +380 -26
  169. package/dist/{elements/chunks/ellipsis-7gU33mL2.js → ellipsis-CbOpJnU3.js} +1 -1
  170. package/dist/embed-CrfjkbUG.js +1 -0
  171. package/dist/engine-javascript-vq0WuIJl.js +14 -2516
  172. package/dist/{elements/chunks/external-link-CSOEsNlx.js → external-link-ByXAwBBQ.js} +1 -1
  173. package/dist/file-text-DMib2qwt.js +1 -0
  174. package/dist/{elements/chunks/file-tree-zvB2i7P9.js → file-tree-CJqM2aP7.js} +1 -1
  175. package/dist/folder-_BAb929C.js +1 -0
  176. package/dist/form-DSZgZQZk.js +1 -0
  177. package/dist/github-dark-dimmed-DUshB20C.js +1 -4
  178. package/dist/github-light-JYsPkUQd.js +1 -4
  179. package/dist/hover-card-BQbul9eq.js +1 -0
  180. package/dist/html-CPZ3oZQ7.js +1 -10
  181. package/dist/icon-CdcfRhYI.js +1 -0
  182. package/dist/{elements/chunks/index-BCRhi84a.js → index-xu_pdcPi.js} +1 -1
  183. package/dist/index.d.ts +4 -2
  184. package/dist/index.js +7474 -5857
  185. package/dist/index.server.js +5965 -4762
  186. package/dist/info-DsFjGuI_.js +1 -0
  187. package/dist/input-DKbmkMos.js +1 -0
  188. package/dist/javascript-C25yR2R2.js +1 -6
  189. package/dist/json-DxJze_jm.js +1 -6
  190. package/dist/kai.es.js +1 -1
  191. package/dist/kbd-PvdGhX4s.js +1 -0
  192. package/dist/{elements/chunks/link-preview-OQ9IEq0f.js → link-preview-DbM1866N.js} +1 -1
  193. package/dist/link-zF8PzWfF.js +1 -0
  194. package/dist/loader-BM4CEz4G.js +1 -0
  195. package/dist/{elements/chunks/markdown-DgBFaGAz.js → markdown-LMvmAyBk.js} +7 -7
  196. package/dist/mcp.es.js +1361 -81
  197. package/dist/message-BSsgvHvf.js +1 -0
  198. package/dist/message-BYZECyek.js +1 -0
  199. package/dist/message-circle-DruwObfX.js +1 -0
  200. package/dist/message-square-BG6q6mlm.js +1 -0
  201. package/dist/{elements/chunks/minimize-2-DPIUKTrT.js → minimize-2-BQRoBV1V.js} +1 -1
  202. package/dist/{elements/chunks/model-switcher-DsT8tFB0.js → model-switcher-CMpCOYcB.js} +1 -1
  203. package/dist/{elements/chunks/overlay-DuiwdXjm.js → overlay-Cb-MkyDa.js} +1 -1
  204. package/dist/panel-right-i8warFxT.js +1 -0
  205. package/dist/paperclip-ByY3UVO6.js +1 -0
  206. package/dist/play-BvKKq3fX.js +1 -0
  207. package/dist/primitives/card-data-types.d.ts +49 -0
  208. package/dist/primitives/card-routing.d.ts +1 -33
  209. package/dist/primitives/card-validate.d.ts +2 -2
  210. package/dist/primitives/chat-config.d.ts +29 -2
  211. package/dist/primitives/conversation-store.d.ts +54 -0
  212. package/dist/primitives/field-mask.d.ts +96 -0
  213. package/dist/primitives/field-semantics.d.ts +36 -0
  214. package/dist/primitives/input-mask.d.ts +46 -0
  215. package/dist/primitives/url-scheme-policy.d.ts +34 -0
  216. package/dist/primitives/use-resize-observer.d.ts +2 -2
  217. package/dist/progress-bar-jVAHvMUf.js +1 -0
  218. package/dist/prompt-suggestion-CD-qtNgC.js +1 -0
  219. package/dist/radio-BsoBMhkF.js +1 -0
  220. package/dist/react/index.d.ts +586 -19
  221. package/dist/react.js +223 -187
  222. package/dist/reasoning-4kBPtlTx.js +1 -0
  223. package/dist/register-impl-B1hLkrI8.js +424 -0
  224. package/dist/{elements/chunks/resizable-D181b4fM.js → resizable-uPmY9VY-.js} +1 -1
  225. package/dist/rotate-ccw-B2xn93py.js +1 -0
  226. package/dist/rotate-cw-CMVLoCyH.js +1 -0
  227. package/dist/schemas/form.schema.json +21 -1
  228. package/dist/schemas/tool-defs.d.ts +11 -1
  229. package/dist/schemas.js +81 -81
  230. package/dist/scroll-area-BqLUo8ZZ.js +1 -0
  231. package/dist/scroll-button-DslXiJyL.js +1 -0
  232. package/dist/select-2cLRtPGT.js +1 -0
  233. package/dist/{elements/chunks/separator-CeJHQqLW.js → separator-BWQtNJcM.js} +1 -1
  234. package/dist/{elements/chunks/settings-CqaHgJF9.js → settings-BBR63jLg.js} +1 -1
  235. package/dist/{elements/chunks/settings-group-CjqjLCOn.js → settings-group-B2ciy-Iw.js} +1 -1
  236. package/dist/{elements/chunks/skeleton-D8akBnKS.js → skeleton-CkHI-HgC.js} +1 -1
  237. package/dist/slider-DDR9U-i0.js +1 -0
  238. package/dist/{solid-BtdqNZI7.js → solid-BzCFdL25.js} +9771 -7594
  239. package/dist/{solid-CnavwBye.js → solid-DruVOXnf.js} +7893 -6183
  240. package/dist/solid.d.ts +16 -2
  241. package/dist/solid.js +256 -243
  242. package/dist/solid.server.js +256 -243
  243. package/dist/source-GEnFF528.js +1 -0
  244. package/dist/star-CilHq8GP.js +1 -0
  245. package/dist/state/index.d.ts +1 -1
  246. package/dist/state/mock.d.ts +32 -2
  247. package/dist/state.js +133 -108
  248. package/dist/store-Ciyk6eZG.js +1 -0
  249. package/dist/svelte--5p79yCD.js +1 -15
  250. package/dist/switch-KkeQIbuH.js +1 -0
  251. package/dist/tasks-card-BrN4bggJ.js +1 -0
  252. package/dist/text-shimmer-Dcj1MHuq.js +1 -0
  253. package/dist/textarea-Dpvbngbg.js +1 -0
  254. package/dist/theme.tokens.css +110 -13
  255. package/dist/{elements/chunks/thumbs-up-DX2Ijk3H.js → thumbs-up-CsT9Wzb9.js} +1 -1
  256. package/dist/toast-store-tgK6MuMv.js +1 -0
  257. package/dist/{elements/chunks/tool-DKQ2D1DP.js → tool-DzUjIBT8.js} +1 -1
  258. package/dist/{elements/chunks/tooltip-B0G99y8w.js → tooltip-Bkkg_4t0.js} +1 -1
  259. package/dist/trash-2-jBKxjGGQ.js +1 -0
  260. package/dist/{elements/chunks/triangle-alert-F9cyUEyy.js → triangle-alert-DlQ68ppf.js} +1 -1
  261. package/dist/tsx-B8rCNbgL.js +1 -6
  262. package/dist/types.d.ts +48 -3
  263. package/dist/typescript-RycA9KXf.js +1 -6
  264. package/dist/ui/checkbox-group.d.ts +83 -0
  265. package/dist/ui/checkbox.d.ts +40 -0
  266. package/dist/ui/dock.d.ts +51 -0
  267. package/dist/ui/icon.d.ts +3 -0
  268. package/dist/ui/input.d.ts +48 -2
  269. package/dist/ui/radio.d.ts +85 -0
  270. package/dist/ui/select.d.ts +71 -0
  271. package/dist/ui/slider.d.ts +65 -0
  272. package/dist/ui/switch.d.ts +9 -2
  273. package/dist/upload-TcAHXv9W.js +1 -0
  274. package/dist/url-scheme-policy-DR5DgvkM.js +1 -0
  275. package/dist/use-card-resolution-DaZqAdWf.js +1 -0
  276. package/dist/{elements/chunks/variant-aurora-qt6Zct8M.js → variant-aurora-BhLuYxJR.js} +1 -1
  277. package/dist/{variant-aurora-B8II3-I1.js → variant-aurora-CNmfHhcx.js} +2 -2
  278. package/dist/{variant-aurora-CMNWOSJR.js → variant-aurora-D6GsdTPA.js} +2 -2
  279. package/dist/{variant-aurora-CcuDMWwQ.js → variant-aurora-DbW_OWL2.js} +1 -1
  280. package/dist/{elements/chunks/variant-custom-tJp22_BQ.js → variant-custom-BRvaNHSi.js} +1 -1
  281. package/dist/{variant-custom-L16JLLZy.js → variant-custom-BvYHR62b.js} +1 -1
  282. package/dist/{variant-custom-BdvYC7af.js → variant-custom-DMTrEO5w.js} +2 -2
  283. package/dist/{variant-custom-C714mqKl.js → variant-custom-DV2x254s.js} +2 -2
  284. package/dist/{variant-wave-ecwykeyL.js → variant-wave-BNMGrDfj.js} +2 -2
  285. package/dist/{variant-wave-2S4La2Ka.js → variant-wave-BpDd8t3Z.js} +1 -1
  286. package/dist/{variant-wave-D7ruiA1W.js → variant-wave-CyBueXj9.js} +2 -2
  287. package/dist/{elements/chunks/variant-wave-CaQ5ti39.js → variant-wave-sp-qwsdI.js} +1 -1
  288. package/dist/vue-BmIZj4XD.js +1 -33
  289. package/dist/x-Z5UntkIJ.js +1 -0
  290. package/frameworks/react/index.tsx +210 -22
  291. package/llms-full.txt +1184 -22
  292. package/llms.txt +6 -3
  293. package/package.json +17 -4
  294. package/src/agent-tooling/README.md +192 -0
  295. package/src/agent-tooling/catalog/invariants.ts +3 -3
  296. package/src/agent-tooling/catalog/surfaces.ts +72 -0
  297. package/src/agent-tooling/construct/cli-entry.ts +8 -0
  298. package/src/agent-tooling/construct/cli.ts +147 -0
  299. package/src/agent-tooling/construct/codegen.ts +1635 -0
  300. package/src/agent-tooling/construct/construct.v1.schema.json +319 -0
  301. package/src/agent-tooling/construct/dev.ts +148 -0
  302. package/src/agent-tooling/construct/fixtures/demo-widget.construct.json +7 -0
  303. package/src/agent-tooling/construct/fixtures/ops-console.construct.json +44 -0
  304. package/src/agent-tooling/construct/fixtures/owner-widget.construct.json +32 -0
  305. package/src/agent-tooling/construct/schema.ts +452 -0
  306. package/src/agent-tooling/mcp/server.ts +2 -1
  307. package/src/agent-tooling/mcp/tools/construct.ts +130 -0
  308. package/src/agent-tooling/mcp/tools/debug.ts +126 -4
  309. package/src/agent-tooling/mcp/tools/reference.ts +104 -3
  310. package/src/agent-tooling/recipes/composed-thread.ts +714 -0
  311. package/src/agent-tooling/recipes/index.ts +22 -0
  312. package/src/agent-tooling/recipes/types.ts +32 -0
  313. package/src/agent-tooling/registry.ts +5 -0
  314. package/src/components/attachment-types.ts +7 -0
  315. package/src/components/audio-visualizer/fit-scale.ts +1 -1
  316. package/src/components/card-renderer.tsx +39 -8
  317. package/src/components/card.tsx +10 -2
  318. package/src/components/chat-scope-picker.tsx +2 -2
  319. package/src/components/chat-thread.tsx +602 -43
  320. package/src/components/choice-card.tsx +102 -37
  321. package/src/components/coachmark.tsx +1 -1
  322. package/src/components/code-block.tsx +2 -2
  323. package/src/components/composer.tsx +89 -15
  324. package/src/components/confirm-card.tsx +20 -4
  325. package/src/components/conversation-item.tsx +56 -9
  326. package/src/components/conversation-list.tsx +25 -10
  327. package/src/components/conversation-panel.tsx +118 -0
  328. package/src/components/empty.tsx +14 -3
  329. package/src/components/file-tree.tsx +5 -1
  330. package/src/components/form-widgets.tsx +257 -195
  331. package/src/components/form.tsx +297 -9
  332. package/src/components/home-panel.tsx +159 -0
  333. package/src/components/loader.tsx +12 -5
  334. package/src/components/message-skills.tsx +1 -1
  335. package/src/components/message.tsx +71 -11
  336. package/src/components/prompt-input.tsx +7 -1
  337. package/src/components/prompt-suggestion.tsx +4 -1
  338. package/src/components/reasoning.tsx +30 -3
  339. package/src/components/response-compare.tsx +2 -2
  340. package/src/components/scroll-button.tsx +56 -4
  341. package/src/components/source.tsx +6 -1
  342. package/src/components/tasks-card.tsx +58 -16
  343. package/src/components/thread.tsx +10 -1
  344. package/src/components/toast.tsx +1 -1
  345. package/src/components/tool-types.ts +9 -0
  346. package/src/components/widget-tab-bar.tsx +73 -0
  347. package/src/elements/attachments.tsx +3 -1
  348. package/src/elements/chat-workspace.tsx +1 -1
  349. package/src/elements/chat.tsx +115 -5
  350. package/src/elements/checkbox-group.tsx +164 -0
  351. package/src/elements/checkbox.tsx +134 -0
  352. package/src/elements/compiled.css +1 -1
  353. package/src/elements/composer.tsx +12 -1
  354. package/src/elements/conversation-item.tsx +62 -22
  355. package/src/elements/conversation-list.tsx +2 -2
  356. package/src/elements/define-entry.ts +12 -0
  357. package/src/elements/dock.tsx +11 -0
  358. package/src/elements/element-data-types.ts +40 -0
  359. package/src/elements/element-manifest.json +24 -0
  360. package/src/elements/element-meta.json +767 -34
  361. package/src/elements/element-nonscalar.json +17 -0
  362. package/src/elements/element-types.d.ts +380 -26
  363. package/src/elements/feedback-bar.tsx +5 -0
  364. package/src/elements/icon-names.json +29 -0
  365. package/src/elements/input.tsx +312 -23
  366. package/src/elements/message.tsx +2 -0
  367. package/src/elements/pane-grid.tsx +111 -0
  368. package/src/elements/prompt-input.tsx +8 -2
  369. package/src/elements/radio-group.tsx +129 -0
  370. package/src/elements/register-impl.ts +6 -0
  371. package/src/elements/scroll-button.tsx +54 -6
  372. package/src/elements/select.tsx +165 -0
  373. package/src/elements/slider.tsx +171 -0
  374. package/src/elements/slots.ts +1 -1
  375. package/src/elements/styles.css +89 -3
  376. package/src/elements/thread.tsx +2 -0
  377. package/src/elements/toast.tsx +14 -1
  378. package/src/index.ts +5 -2
  379. package/src/primitives/card-data-types.ts +49 -0
  380. package/src/primitives/card-host.tsx +4 -2
  381. package/src/primitives/card-routing.ts +7 -56
  382. package/src/primitives/card-schemas/form.schema.json +21 -1
  383. package/src/primitives/card-validate.ts +10 -2
  384. package/src/primitives/chat-config.tsx +33 -6
  385. package/src/primitives/conversation-store.ts +248 -0
  386. package/src/primitives/field-mask.ts +256 -0
  387. package/src/primitives/field-semantics.ts +115 -0
  388. package/src/primitives/input-mask.ts +853 -0
  389. package/src/primitives/toast-store.ts +66 -4
  390. package/src/primitives/url-scheme-policy.ts +70 -0
  391. package/src/primitives/use-resize-observer.ts +2 -2
  392. package/src/schemas/tool-defs.ts +12 -2
  393. package/src/solid.ts +20 -2
  394. package/src/state/index.ts +1 -1
  395. package/src/state/mock.ts +92 -6
  396. package/src/types.ts +45 -3
  397. package/src/ui/agent-card.tsx +2 -2
  398. package/src/ui/avatar.tsx +1 -1
  399. package/src/ui/checkbox-group.tsx +153 -0
  400. package/src/ui/checkbox.tsx +63 -0
  401. package/src/ui/dock.tsx +192 -7
  402. package/src/ui/icon.tsx +53 -0
  403. package/src/ui/input.tsx +249 -9
  404. package/src/ui/kbd.tsx +1 -1
  405. package/src/ui/nav.tsx +1 -1
  406. package/src/ui/pane-group.tsx +2 -2
  407. package/src/ui/radio.tsx +150 -0
  408. package/src/ui/select.tsx +168 -0
  409. package/src/ui/slider.tsx +178 -0
  410. package/src/ui/switch.tsx +49 -18
  411. package/src/utils/cn.ts +11 -5
  412. package/theme.css +112 -13
  413. package/dist/elements/chunks/action-icons-D9u3OzYW.js +0 -1
  414. package/dist/elements/chunks/arrow-left-CVh1TeN7.js +0 -1
  415. package/dist/elements/chunks/artifact-BkYcfIlZ.js +0 -1
  416. package/dist/elements/chunks/attachments-DwfNlubq.js +0 -1
  417. package/dist/elements/chunks/badge-Q0-Cmwzz.js +0 -1
  418. package/dist/elements/chunks/bash-InADTalH.js +0 -1
  419. package/dist/elements/chunks/card-renderer-3Dyl-FpN.js +0 -1
  420. package/dist/elements/chunks/card-routing-BK09BdmL.js +0 -1
  421. package/dist/elements/chunks/check-B5kBPKeo.js +0 -1
  422. package/dist/elements/chunks/chevron-down-DZ1GS39h.js +0 -1
  423. package/dist/elements/chunks/chevron-right-CvP7OUd2.js +0 -1
  424. package/dist/elements/chunks/choice-card-D4QUIOWZ.js +0 -1
  425. package/dist/elements/chunks/circle-CDf9g6MQ.js +0 -1
  426. package/dist/elements/chunks/circle-check-CA47Nqbz.js +0 -1
  427. package/dist/elements/chunks/circle-x-CJ_HVbC8.js +0 -1
  428. package/dist/elements/chunks/code-block-BdLiTitd.js +0 -1
  429. package/dist/elements/chunks/composer-Byve-n8K.js +0 -69
  430. package/dist/elements/chunks/confirm-card-B0flE3MN.js +0 -1
  431. package/dist/elements/chunks/controllable-DTwTIlwp.js +0 -1
  432. package/dist/elements/chunks/conversation-item-BDztezOG.js +0 -1
  433. package/dist/elements/chunks/copy-Nf-kqxXH.js +0 -1
  434. package/dist/elements/chunks/core-AYMC6_lb.js +0 -12
  435. package/dist/elements/chunks/css-M7EaDHN_.js +0 -1
  436. package/dist/elements/chunks/default-input-C2iLFq0b.js +0 -1
  437. package/dist/elements/chunks/define-wjHR1MkU.js +0 -1
  438. package/dist/elements/chunks/download-C4V7tBdb.js +0 -1
  439. package/dist/elements/chunks/embed-D3KewsIJ.js +0 -1
  440. package/dist/elements/chunks/engine-javascript-vq0WuIJl.js +0 -141
  441. package/dist/elements/chunks/file-text-D1G8khle.js +0 -1
  442. package/dist/elements/chunks/folder-WW2LGLid.js +0 -1
  443. package/dist/elements/chunks/form-CQirdC4W.js +0 -1
  444. package/dist/elements/chunks/github-dark-dimmed-DUshB20C.js +0 -1
  445. package/dist/elements/chunks/github-light-JYsPkUQd.js +0 -1
  446. package/dist/elements/chunks/hover-card-CJJkfHTK.js +0 -1
  447. package/dist/elements/chunks/html-CPZ3oZQ7.js +0 -1
  448. package/dist/elements/chunks/icon-Cfgmf4fa.js +0 -1
  449. package/dist/elements/chunks/info-DX3YMOt6.js +0 -1
  450. package/dist/elements/chunks/input-BtW9U6Sq.js +0 -1
  451. package/dist/elements/chunks/javascript-C25yR2R2.js +0 -1
  452. package/dist/elements/chunks/json-DxJze_jm.js +0 -1
  453. package/dist/elements/chunks/kbd-LGXwuT_V.js +0 -1
  454. package/dist/elements/chunks/link-2My69BqT.js +0 -1
  455. package/dist/elements/chunks/loader-iOxc_Tu-.js +0 -1
  456. package/dist/elements/chunks/message-VMZohrXx.js +0 -1
  457. package/dist/elements/chunks/message-circle-CpXMM-_n.js +0 -1
  458. package/dist/elements/chunks/message-square-DxcWtETQ.js +0 -1
  459. package/dist/elements/chunks/message-tZmV2A3p.js +0 -1
  460. package/dist/elements/chunks/paperclip-CRZVp4z_.js +0 -1
  461. package/dist/elements/chunks/progress-bar-DNy66e_E.js +0 -1
  462. package/dist/elements/chunks/prompt-suggestion-Bn64Nl7L.js +0 -1
  463. package/dist/elements/chunks/reasoning-Emn9N4-T.js +0 -1
  464. package/dist/elements/chunks/rotate-cw-CPWe37D9.js +0 -1
  465. package/dist/elements/chunks/scroll-area-ClZkZxIk.js +0 -1
  466. package/dist/elements/chunks/scroll-button-DPE-t3x_.js +0 -1
  467. package/dist/elements/chunks/share-CyfnNEqp.js +0 -1
  468. package/dist/elements/chunks/source-UWCvmVak.js +0 -1
  469. package/dist/elements/chunks/star-DoHFAikQ.js +0 -1
  470. package/dist/elements/chunks/store-Cjq0qwYN.js +0 -1
  471. package/dist/elements/chunks/svelte--5p79yCD.js +0 -1
  472. package/dist/elements/chunks/tasks-card-CmZ5xh6v.js +0 -1
  473. package/dist/elements/chunks/text-shimmer-DYn09zrz.js +0 -1
  474. package/dist/elements/chunks/textarea-skiGHURM.js +0 -1
  475. package/dist/elements/chunks/toast-store-CFnQpThX.js +0 -1
  476. package/dist/elements/chunks/tsx-B8rCNbgL.js +0 -1
  477. package/dist/elements/chunks/typescript-RycA9KXf.js +0 -1
  478. package/dist/elements/chunks/use-card-resolution-DoA2f_EU.js +0 -1
  479. package/dist/elements/chunks/vue-BmIZj4XD.js +0 -1
  480. package/dist/elements/chunks/x-B6UnOiLw.js +0 -1
  481. package/dist/llms/llms-full.txt +0 -2949
  482. package/dist/llms/llms.txt +0 -156
  483. package/dist/register-impl-DpE7icIb.js +0 -293
  484. package/dist/ui/stat.d.ts +0 -16
  485. package/src/ui/stat.tsx +0 -41
  486. /package/dist/{elements/chunks/card-tags-D8lZ-C_U.js → card-tags-D8lZ-C_U.js} +0 -0
  487. /package/dist/{elements/chunks/link-preview-DNILK391.js → link-preview-DNILK391.js} +0 -0
  488. /package/dist/{elements/chunks/slots-DaNjX0rc.js → slots-DaNjX0rc.js} +0 -0
@@ -8,6 +8,7 @@ import { actionIcon, BUILTIN_ACTION_LABEL } from "../ui/action-icons";
8
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 { Loader } from "./loader";
11
12
  import { Tool } from "./tool";
12
13
  import {
13
14
  Attachments,
@@ -328,6 +329,12 @@ export interface MessageBodyProps {
328
329
  * the kit checks its own seven built-ins and leaves your own card type
329
330
  * unvalidated. A schema here WINS over a built-in of the same name. */
330
331
  cardSchemas?: CardSchemaMap;
332
+ /** The custom-element host node to emit card events off when no `CardProvider`
333
+ * is above this body, forwarded to `CardRenderer` as `hostElement`. The
334
+ * `<kai-chat>`/`<kai-message>`/`<kai-thread>` facades pass their own element,
335
+ * so a `card` part's events leave as the bubbling `kai-card` CustomEvent
336
+ * instead of being silently discarded. */
337
+ cardHostElement?: HTMLElement;
331
338
  /** Whether this is a user message (right-aligned bubble) vs an assistant
332
339
  * message (full-width transparent). */
333
340
  isUser: boolean;
@@ -348,11 +355,27 @@ export interface MessageBodyProps {
348
355
  copied?: boolean;
349
356
  /** Whether this message is the one currently streaming. Forwarded to each
350
357
  * reasoning part's `<Reasoning>` so the disclosure auto-opens while the
351
- * model is thinking and settles back once the stream ends (F-21: without it
352
- * the user watches a static collapsed "Reasoning" label for the whole
353
- * thinking window). The caller owns the definition of "streaming" — for
358
+ * model is thinking and settles back once the stream ends. Without it the
359
+ * user watches a static collapsed "Reasoning" label for the whole thinking
360
+ * window. The caller owns the definition of "streaming" — for
354
361
  * `ChatThread` that is `loading` + being the last assistant message. */
355
362
  isStreaming?: boolean;
363
+ /** How a `reasoning` part renders. `'full'` (default) is the current
364
+ * behavior: the collapsible `<Reasoning>` disclosure, with the "Thinking…"
365
+ * shimmer on its trigger while `isStreaming`. `'compact'` drops the
366
+ * disclosure entirely and shows only a shimmer loader while the part is
367
+ * streaming — nothing once it settles, so there is no expandable detail to
368
+ * open. `'off'` renders the part not at all, in either state. Kit-decides-
369
+ * HOW: this is a display-mode fact about the medium, not a per-message
370
+ * toggle, so it applies uniformly to every reasoning part in the body. */
371
+ reasoningMode?: 'full' | 'compact' | 'off';
372
+ /** Seeds the reasoning disclosure open AND keeps it tracking the stream
373
+ * (open while streaming, closes when it settles): the pre-Task-19f `full`
374
+ * behavior. Default false/absent: the panel starts closed (just the
375
+ * "Thinking" shimmer chip) and only opens on click, the current default
376
+ * (owner ruling, 2026-08-26). Meaningless when `reasoningMode` is
377
+ * `'compact'`/`'off'` (no disclosure exists to open). */
378
+ reasoningDefaultOpen?: boolean;
356
379
  /** INJECT slot projected at the TOP of the body — above the reasoning / tools /
357
380
  * content. A per-message header: a model-name label, a role + timestamp line.
358
381
  * In the `<kai-message>` shadow this is `<slot name="before-body" />`. */
@@ -613,7 +636,17 @@ function MessageBody(props: MessageBodyProps) {
613
636
  part="bubble content"
614
637
  markdown={props.markdown}
615
638
  class={props.isUser
616
- ? 'bg-muted text-primary max-w-[85%] rounded-2xl px-4 py-2'
639
+ // Content token, not the brand token: `--color-primary`
640
+ // is the documented consumer brand override
641
+ // (theme.css `--kai-color-primary`), so toking message
642
+ // TEXT to it means every consumer that brands primary
643
+ // gets brand-colored message text. `text-foreground` is
644
+ // already `MessageContent`'s base color (matches the
645
+ // assistant path's `bg-transparent p-0`, which carries
646
+ // no color override and falls through to the same
647
+ // base) — dropping `text-primary` here just lets that
648
+ // base apply on the user bubble too.
649
+ ? 'bg-muted max-w-[85%] rounded-2xl px-4 py-2'
617
650
  : 'bg-transparent p-0'}
618
651
  >
619
652
  {p().text}
@@ -622,18 +655,45 @@ function MessageBody(props: MessageBodyProps) {
622
655
  </Match>
623
656
  <Match when={carrierOnly()}>{null}</Match>
624
657
  <Match when={shownReasoning()}>
625
- {(p) => (
626
- <Reasoning class="mb-2 w-full" isStreaming={props.isStreaming}>
627
- <ReasoningTrigger>{p().label ?? 'Reasoning'}</ReasoningTrigger>
628
- <ReasoningContent markdown>{p().text}</ReasoningContent>
629
- </Reasoning>
630
- )}
658
+ {(p) => {
659
+ // Default 'full' is the pre-existing DISPLAY MODE byte
660
+ // for byte; the OPEN behavior changed under Task 19f —
661
+ // it no longer auto-opens while streaming by default
662
+ // (owner ruling 2026-08-26). `reasoningDefaultOpen`
663
+ // reproduces the old always-auto-opens behavior when set.
664
+ const mode = () => props.reasoningMode ?? 'full';
665
+ return (
666
+ <Switch fallback={null}>
667
+ <Match when={mode() === 'full'}>
668
+ <Reasoning
669
+ class="mb-2 w-full"
670
+ isStreaming={props.isStreaming}
671
+ defaultOpen={props.reasoningDefaultOpen}
672
+ openOnStream={props.reasoningDefaultOpen}
673
+ >
674
+ <ReasoningTrigger>{p().label ?? 'Reasoning'}</ReasoningTrigger>
675
+ <ReasoningContent markdown>{p().text}</ReasoningContent>
676
+ </Reasoning>
677
+ </Match>
678
+ {/* 'compact': the same "Thinking…" shimmer the full
679
+ disclosure's trigger shows while streaming — no
680
+ <Reasoning>/<ReasoningContent>, so there is no
681
+ expandable detail and nothing left once the part
682
+ settles (isStreaming false → this Match doesn't
683
+ fire, matching 'off'). 'off' never reaches here:
684
+ its Match doesn't fire either. */}
685
+ <Match when={mode() === 'compact' && props.isStreaming}>
686
+ <Loader variant="text-shimmer" text={p().label ?? 'Reasoning'} class="mb-2" />
687
+ </Match>
688
+ </Switch>
689
+ );
690
+ }}
631
691
  </Match>
632
692
  <Match when={partAs(part(), 'tool')}>
633
693
  {(p) => <Tool toolPart={p().tool} class="mb-2 w-full" />}
634
694
  </Match>
635
695
  <Match when={partAs(part(), 'card')}>
636
- {(p) => <CardRenderer envelope={p().envelope} types={props.cardTypes} schemas={props.cardSchemas} />}
696
+ {(p) => <CardRenderer envelope={p().envelope} types={props.cardTypes} schemas={props.cardSchemas} hostElement={props.cardHostElement} />}
637
697
  </Match>
638
698
  {/* No `source` match here on purpose: source parts never
639
699
  reach a 'single' group — they are collapsed into a
@@ -115,9 +115,15 @@ function PromptInputTextarea(props: PromptInputTextareaProps) {
115
115
  // the contenteditable composer is visually identical. The frame (PromptInput
116
116
  // root) still owns radius/bg/padding/focus-ring; auto-grow is native to a
117
117
  // contenteditable block (capped via max-height on the editable).
118
+ // `text-start` is a PIN, not a style choice: `text-align` inherits, so any
119
+ // centered ancestor (`Empty`'s root did exactly this) reached in and centered
120
+ // the placeholder AND the typed text. An input control's text alignment is a
121
+ // fact about the control, so it states it rather than inheriting it. LOGICAL
122
+ // (`start`), not `text-left` — in RTL the correct edge is the right one, and
123
+ // pinning the physical value would be a worse bug than the one it fixes.
118
124
  const editableClass = () =>
119
125
  cn(
120
- 'text-foreground min-h-[44px] w-full bg-transparent shadow-none outline-none focus-visible:ring-0 focus-visible:ring-offset-0 overflow-y-auto whitespace-pre-wrap break-words',
126
+ 'text-foreground min-h-[44px] w-full bg-transparent text-start shadow-none outline-none focus-visible:ring-0 focus-visible:ring-offset-0 overflow-y-auto whitespace-pre-wrap break-words',
121
127
  textClass(config.proseSize()),
122
128
  local.class,
123
129
  );
@@ -136,7 +136,10 @@ function renderHighlighted(text: string, highlight: string) {
136
136
  <Show when={before}>
137
137
  <span class="text-foreground/70 whitespace-pre-wrap">{before}</span>
138
138
  </Show>
139
- <span class="text-primary font-medium whitespace-pre-wrap">{matched}</span>
139
+ {/* Matched-substring highlight is CONTENT, not a control — was the BRAND
140
+ token. Full-strength `text-foreground` against the `/70`-opacity
141
+ siblings above/below is what actually carries the highlight. */}
142
+ <span class="text-foreground font-medium whitespace-pre-wrap">{matched}</span>
140
143
  <Show when={after}>
141
144
  <span class="text-foreground/70 whitespace-pre-wrap">{after}</span>
142
145
  </Show>
@@ -3,6 +3,7 @@ import { cn } from '../utils/cn';
3
3
  import { ChevronDown } from 'lucide-solid';
4
4
  import { Markdown } from './markdown';
5
5
  import { observeContentHeight } from '../primitives/use-resize-observer';
6
+ import { TextShimmer } from './text-shimmer';
6
7
 
7
8
  interface ReasoningContextValue {
8
9
  isOpen: () => boolean;
@@ -18,6 +19,11 @@ interface ReasoningContextValue {
18
19
  /** The registered trigger. Deliberately a plain getter over a mutable
19
20
  * variable, NOT a signal — registration must not re-run the content effect. */
20
21
  trigger: () => HTMLElement | undefined;
22
+ /** Whether the reasoning is currently streaming — read by `ReasoningTrigger`
23
+ * to decide how its own label renders (shimmer while streaming, plain
24
+ * neutral text once settled). Kit-decides-HOW: every consumer gets the same
25
+ * "Thinking…" shimmer behavior for free, with no prop of their own. */
26
+ isStreaming: () => boolean;
21
27
  }
22
28
 
23
29
  /** Imperative open controller, handed to a parent (the kai-reasoning facade) via
@@ -49,10 +55,16 @@ export interface ReasoningProps {
49
55
  disabled?: boolean;
50
56
  /** Receive the open controller (open accessor + setOpen) once mounted. */
51
57
  controllerRef?: (api: ReasoningController) => void;
58
+ /** Keep auto-opening while streaming and auto-closing when it settles (the
59
+ * pre-Task-19f `full` behavior). Default false: the panel starts per
60
+ * `defaultOpen` and stays exactly where the user leaves it — streaming only
61
+ * changes the trigger's label (shimmer), never the open state, matching the
62
+ * "closed chip, expand on click" default (owner ruling, 2026-08-26). */
63
+ openOnStream?: boolean;
52
64
  }
53
65
 
54
66
  function Reasoning(props: ReasoningProps) {
55
- const [local] = splitProps(props, ['children', 'class', 'open', 'defaultOpen', 'onOpenChange', 'isStreaming', 'disabled', 'controllerRef']);
67
+ const [local] = splitProps(props, ['children', 'class', 'open', 'defaultOpen', 'onOpenChange', 'isStreaming', 'disabled', 'controllerRef', 'openOnStream']);
56
68
  const [internalOpen, setInternalOpen] = createSignal(local.defaultOpen ?? false);
57
69
  const [wasAutoOpened, setWasAutoOpened] = createSignal(false);
58
70
 
@@ -72,6 +84,7 @@ function Reasoning(props: ReasoningProps) {
72
84
  };
73
85
 
74
86
  createEffect(() => {
87
+ if (!local.openOnStream) return;
75
88
  const streaming = local.isStreaming;
76
89
  if (streaming && !wasAutoOpened()) {
77
90
  if (!isControlled()) setInternalOpen(true);
@@ -104,6 +117,7 @@ function Reasoning(props: ReasoningProps) {
104
117
  contentId,
105
118
  registerTrigger: (el) => { triggerEl = el; },
106
119
  trigger: () => triggerEl,
120
+ isStreaming: () => !!local.isStreaming,
107
121
  }}
108
122
  >
109
123
  <div class={local.class}>{local.children}</div>
@@ -122,7 +136,7 @@ function ReasoningTrigger(props: ReasoningTriggerProps) {
122
136
  // to register as the panel's focus-return target, and a spread `ref` would
123
137
  // silently replace it. The consumer's ref is forwarded below instead.
124
138
  const [local, rest] = splitProps(props, ['children', 'class', 'ref']);
125
- const { isOpen, onOpenChange, disabled, contentId, registerTrigger } = useReasoningContext();
139
+ const { isOpen, onOpenChange, disabled, contentId, registerTrigger, isStreaming } = useReasoningContext();
126
140
 
127
141
  return (
128
142
  // The same disclosure contract as CollapsibleTrigger: an explicit
@@ -149,7 +163,20 @@ function ReasoningTrigger(props: ReasoningTriggerProps) {
149
163
  onClick={() => { if (!disabled()) onOpenChange(!isOpen()); }}
150
164
  {...rest}
151
165
  >
152
- <span class="text-primary">{local.children}</span>
166
+ {/* Content/chrome, not a control: was `text-primary`, the BRAND token, so
167
+ a consumer's branded --kai-color-primary bled onto the "Thinking"
168
+ label. `text-foreground` matches the sibling `<Tool>` disclosure
169
+ trigger's label (rendered via `Button variant="ghost"`, itself
170
+ `text-foreground`) — the established neutral for a disclosure
171
+ trigger's own label text in this codebase.
172
+ Kit-decides-HOW: while streaming, the label renders with the kit's
173
+ own `TextShimmer` (the common "Thinking…" shimmer, already used by
174
+ `Loader`'s `text-shimmer` variant) instead of static text; once
175
+ settled it's the plain neutral span above. Every consumer gets this
176
+ for free — no prop of their own. */}
177
+ <Show when={isStreaming()} fallback={<span class="text-foreground">{local.children}</span>}>
178
+ <TextShimmer class="font-medium">{local.children}</TextShimmer>
179
+ </Show>
153
180
  <div
154
181
  class={cn(
155
182
  'transform transition-transform',
@@ -365,7 +365,7 @@ function CompareColumn(props: ColumnProps): JSX.Element {
365
365
  return (
366
366
  <div
367
367
  class={cn(
368
- 'flex min-w-0 flex-col gap-3 rounded-lg border border-input bg-background/40 p-3',
368
+ 'flex min-w-0 flex-col gap-3 rounded-lg border border-border bg-background/40 p-3',
369
369
  props.hiddenClass?.(),
370
370
  )}
371
371
  >
@@ -432,7 +432,7 @@ function CollapsedWinner(props: {
432
432
  }): JSX.Element {
433
433
  return (
434
434
  <div
435
- class="flex flex-col gap-2 rounded-lg border border-input bg-accent/40 p-3"
435
+ class="flex flex-col gap-2 rounded-lg border border-border bg-accent/40 p-3"
436
436
  role={props.optimistic ? 'status' : undefined}
437
437
  >
438
438
  <div class="flex items-center gap-2 text-sm font-medium text-accent-foreground">
@@ -1,22 +1,62 @@
1
+ import { Show } from 'solid-js';
1
2
  import { cn } from '../utils/cn';
2
3
  import { Button } from '../ui/button';
3
4
  import { useChatContainer } from './chat-container';
4
- import { ChevronDown } from 'lucide-solid';
5
+ import { ArrowDown } from 'lucide-solid';
6
+
7
+ /** The accessible name used when the consumer does not supply one. */
8
+ const DEFAULT_LABEL = 'Scroll to bottom';
5
9
 
6
10
  export interface ScrollButtonProps {
7
11
  class?: string;
8
12
  variant?: 'outline' | 'ghost' | 'default';
9
13
  size?: 'sm' | 'md' | 'lg' | 'icon' | 'icon-sm';
14
+ /**
15
+ * The button's accessible name. Used whether or not the label is visible, so
16
+ * the announced text is always localisable. Defaults to `"Scroll to bottom"`.
17
+ */
18
+ label?: string;
19
+ /**
20
+ * Also render `label` visibly beside the icon. Defaults to `false` (icon
21
+ * only). When it is visible the text IS the accessible name, so nothing is
22
+ * announced twice.
23
+ */
24
+ showLabel?: boolean;
25
+ }
26
+
27
+ /**
28
+ * The floating chip surface.
29
+ *
30
+ * The shared `outline` button variant is `bg-muted/50` — half-transparent, so
31
+ * message text showed through a control that floats over scrolling content, and
32
+ * its edge measured 1.05:1 against the thread behind it (SC 1.4.11 wants 3:1).
33
+ * `bg-card` is the same opaque surface popovers, dropdowns and hover cards sit
34
+ * on; `border-input` is the kit's control edge and clears 3:1 in both themes;
35
+ * `kai-elevation` is the shared, themeable float shadow (`--kai-shadow-color`),
36
+ * which is what keeps an opaque button reading as ABOVE the content.
37
+ *
38
+ * `ghost` and `default` are explicit consumer choices about the fill, so they
39
+ * keep theirs; every variant gets the elevation and the rounded-square radius.
40
+ */
41
+ function surfaceClasses(variant: ScrollButtonProps['variant']) {
42
+ return (variant ?? 'outline') === 'outline'
43
+ ? 'border border-input bg-card text-card-foreground kai-elevation'
44
+ : 'kai-elevation';
10
45
  }
11
46
 
12
47
  function ScrollButton(props: ScrollButtonProps) {
13
48
  const { isAtBottom, scrollToBottom } = useChatContainer();
49
+ const label = () => props.label ?? DEFAULT_LABEL;
50
+ const showLabel = () => props.showLabel === true;
14
51
 
15
52
  return (
16
53
  <Button
17
54
  variant={props.variant ?? 'outline'}
18
55
  size={props.size ?? 'sm'}
19
- aria-label="Scroll to bottom"
56
+ // When the label is visible it IS the accessible name. An aria-label on
57
+ // top of it would override the visible text and leave the two able to
58
+ // disagree, so it is only set for the icon-only case.
59
+ aria-label={showLabel() ? undefined : label()}
20
60
  // At the bottom of the thread this button is fully transparent and
21
61
  // pointer-inert, but it stayed in the tab order — a keyboard user landed
22
62
  // on a control they could not see, with a focus ring painted on nothing.
@@ -25,7 +65,10 @@ function ScrollButton(props: ScrollButtonProps) {
25
65
  tabindex={isAtBottom() ? -1 : 0}
26
66
  aria-hidden={isAtBottom() ? 'true' : undefined}
27
67
  class={cn(
28
- 'h-10 w-10 rounded-full transition-all duration-150 ease-out',
68
+ 'rounded-lg transition-all duration-150 ease-out',
69
+ surfaceClasses(props.variant),
70
+ // Square while icon-only; a pill-ish labelled button once the text is in.
71
+ showLabel() ? 'h-10 w-auto gap-1.5 px-3.5 text-sm' : 'h-10 w-10 px-0',
29
72
  !isAtBottom()
30
73
  ? 'translate-y-0 scale-100 opacity-100'
31
74
  : 'pointer-events-none translate-y-4 scale-95 opacity-0',
@@ -33,7 +76,16 @@ function ScrollButton(props: ScrollButtonProps) {
33
76
  )}
34
77
  onClick={() => scrollToBottom()}
35
78
  >
36
- <ChevronDown class="h-5 w-5" />
79
+ {/* h-4, not the h-5 the chevron used. A ChevronDown only inks the lower
80
+ part of its 24px viewBox, so at a 20px box its visible mass is small;
81
+ ArrowDown inks the full height (stem + head), and at 20px inside a 40px
82
+ button it read as half the button wide and crowded the padding. 16px
83
+ balances the icon-only square and is the size lucide pairs with
84
+ text-sm, which is what the label renders at. */}
85
+ <ArrowDown class="h-4 w-4 shrink-0" aria-hidden="true" />
86
+ <Show when={showLabel()}>
87
+ <span>{label()}</span>
88
+ </Show>
37
89
  </Button>
38
90
  );
39
91
  }
@@ -152,7 +152,12 @@ function SourceContent(props: SourceContentProps) {
152
152
  width={16}
153
153
  height={16}
154
154
  />
155
- <div class="text-primary truncate text-sm">
155
+ {/* Citation title/domain is CONTENT, not a control — was the BRAND
156
+ token. `text-foreground/80` matches how `SourceTrigger` above
157
+ already renders this same domain text (inherited from its `<a
158
+ class="... text-foreground/80 ...">`), so the hover-card header
159
+ and the trigger chip agree on the same domain's color. */}
160
+ <div class="text-foreground/80 truncate text-sm">
156
161
  {ctx.domain.replace('www.', '')}
157
162
  </div>
158
163
  </div>
@@ -14,6 +14,7 @@ import {
14
14
  } from 'solid-js';
15
15
  import { cn } from '../utils/cn';
16
16
  import { Button } from '../ui/button';
17
+ import { Checkbox } from '../ui/checkbox';
17
18
  import { ProgressBar } from '../ui/progress-bar';
18
19
  import { Card } from './card';
19
20
  import { DismissedStub } from './dismissed-stub';
@@ -21,7 +22,7 @@ import type { CardEnvelope, CardEvent, CardHost, CardResolution } from '../primi
21
22
  import { useCardResolution } from './use-card-resolution';
22
23
  import { emitCardEvent } from '../primitives/card-routing';
23
24
  import { useCardHost } from '../primitives/card-host';
24
- import { Check, Circle, CircleCheck, X } from 'lucide-solid';
25
+ import { Check, Circle, CircleCheck } from 'lucide-solid';
25
26
 
26
27
  // ─────────────────────────────────────────────────────────────────────────────
27
28
  // Types (tasks.schema.json) — AUTHORED IN ../primitives/card-data-types.ts.
@@ -415,14 +416,23 @@ export function TasksCard(props: TasksCardProps): JSX.Element {
415
416
  progress() || res.isResolved() || readOnly() ? undefined : (
416
417
  <div class="flex w-full flex-wrap items-center justify-between gap-2">
417
418
  <Show when={local.data?.dismissible === true}>
419
+ {/* The contract `dismiss` verb — a footer ACTION (it emits
420
+ `{kind:'dismiss'}` and collapses the card to a re-openable
421
+ stub), not the chrome close in `Card`'s own `dismissible`
422
+ prop, which the contract cards deliberately never set.
423
+ Labelled rather than a bare ghost ✕: every other control in
424
+ this row says what it does, and an unlabelled 28px glyph
425
+ 300px from the primary button reads as stray chrome. Same
426
+ shape in all four cards — `form` already looked like this.
427
+ `aria-label` is kept (redundant with the text) so the
428
+ element tests that select on it keep working. */}
418
429
  <Button
419
430
  type="button"
420
431
  variant="ghost"
421
- size="icon-sm"
422
432
  aria-label="Dismiss"
423
433
  onClick={onDismiss}
424
434
  >
425
- <X size={16} aria-hidden="true" />
435
+ Dismiss
426
436
  </Button>
427
437
  </Show>
428
438
  <span id={countId} aria-live="polite" class="text-xs text-muted-foreground">
@@ -485,7 +495,7 @@ export function TasksCard(props: TasksCardProps): JSX.Element {
485
495
  if (confirmEnabled()) onConfirm();
486
496
  }}
487
497
  >
488
- <div class="divide-y divide-border overflow-hidden rounded-lg border border-input">
498
+ <div class="divide-y divide-border overflow-hidden rounded-lg border border-border">
489
499
  <Show when={showMaster()}>
490
500
  {(() => {
491
501
  const indeterminate = () => masterState() === 'indeterminate';
@@ -501,17 +511,20 @@ export function TasksCard(props: TasksCardProps): JSX.Element {
501
511
  : 'text-foreground hover:bg-muted/50',
502
512
  )}
503
513
  >
504
- <input
505
- type="checkbox"
506
- class="kai-checkbox"
514
+ {/* SQUARE `Checkbox` (the `.kai-checkbox` look), and the checklist below draws a
515
+ ROUND lucide Circle/CircleCheck instead. Deliberate, not drift: this
516
+ is `select` mode — a pick-list whose rows are a pending selection that
517
+ only commits when the confirm button fires, which is what a checkbox
518
+ means everywhere else. `progress` mode has no confirm and toggling a
519
+ row IS the action, so it uses the done/not-done affordance of a todo
520
+ list. See the D-2 row in
521
+ docs/superpowers/plans/2026-08-25-form-control-primitives.md; owner has
522
+ not ruled, so do not converge these on your own. */}
523
+ <Checkbox
507
524
  checked={masterState() === 'checked'}
508
525
  disabled={groupDisabled() || readOnly()}
526
+ indeterminate={indeterminate()}
509
527
  aria-checked={indeterminate() ? 'mixed' : masterState() === 'checked'}
510
- ref={(el) => {
511
- createEffect(() => {
512
- el.indeterminate = indeterminate();
513
- });
514
- }}
515
528
  onChange={(e) => toggleAll(e.currentTarget.checked)}
516
529
  />
517
530
  <span>Select all</span>
@@ -541,9 +554,8 @@ export function TasksCard(props: TasksCardProps): JSX.Element {
541
554
  )}
542
555
  data-task-id={task.id}
543
556
  >
544
- <input
545
- type="checkbox"
546
- class="kai-checkbox mt-0.5"
557
+ <Checkbox
558
+ class="mt-0.5"
547
559
  checked={checked()}
548
560
  disabled={blocked() || readOnly()}
549
561
  aria-disabled={blocked() ? 'true' : undefined}
@@ -603,7 +615,10 @@ function ProgressChecklist(props: {
603
615
  <div class={cn('flex flex-col gap-3', props.groupClass)}>
604
616
  <Show when={props.heading}>
605
617
  <div class="flex items-center justify-between gap-3">
606
- <span id={props.headingId} class="text-sm font-medium text-foreground">
618
+ <span
619
+ id={props.headingId}
620
+ class="text-title font-semibold tracking-tight text-foreground"
621
+ >
607
622
  {props.heading}
608
623
  </span>
609
624
  <span
@@ -646,8 +661,23 @@ function ProgressChecklist(props: {
646
661
  // row doesn't leave a noisy persistent outline — and it makes a
647
662
  // Tab-focused completed row's open description perceivable. Dropped
648
663
  // entirely when readonly (the row isn't focusable then).
664
+ //
665
+ // TWO tab stops exist per row and the ring has to cover both.
666
+ // `focus-visible:` matches only when the LABEL itself is focused,
667
+ // which is completed rows only (the `tabindex` below). Every row's
668
+ // other — and for an incomplete row, only — tab stop is the sr-only
669
+ // checkbox inside, so without `has-[:focus-visible]:` tabbing to an
670
+ // unchecked row put focus on an off-screen input with no indicator
671
+ // anywhere on screen. Both halves stay on the focus-VISIBLE axis on
672
+ // purpose: `focus-within:` would also fire for a mouse click (which
673
+ // forwards focus from the label to the checkbox), which is exactly
674
+ // the persistent-outline noise the paragraph above rejects. The
675
+ // description reveal below uses `group-focus-within:` because a
676
+ // hover-or-focus reveal wants the looser condition.
649
677
  !props.readonly &&
650
678
  'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring',
679
+ !props.readonly &&
680
+ 'has-[:focus-visible]:outline-none has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-inset has-[:focus-visible]:ring-ring',
651
681
  props.readonly
652
682
  ? 'cursor-default'
653
683
  : blocked()
@@ -680,6 +710,18 @@ function ProgressChecklist(props: {
680
710
  )}
681
711
  aria-hidden="true"
682
712
  >
713
+ {/* ROUND, where `select` mode uses the square `Checkbox`. Deliberate:
714
+ this mode has no confirm button, so ticking a row IS the action and the
715
+ row is done-or-not rather than selected-or-not — the todo-list
716
+ affordance, which is how this mode has been described since it landed
717
+ (see the `mode` argType in tasks-card.stories.tsx and the JSDoc on
718
+ src/elements/tasks.tsx). D-2 in
719
+ docs/superpowers/plans/2026-08-25-form-control-primitives.md tracks
720
+ whether to keep the split; unruled, so leave both. NOTE the cost: the
721
+ icon replaces the visible native control, which is why the real input
722
+ above is `sr-only` and why the row's focus ring has to be driven by
723
+ `has-[:focus-visible]:`. Converging on `Checkbox` would take that
724
+ hazard away with it. */}
683
725
  <Show
684
726
  when={checked()}
685
727
  fallback={<Circle size={18} />}
@@ -34,6 +34,11 @@ export interface ThreadProps {
34
34
  * the kit checks its own seven built-ins and leaves your own card type
35
35
  * unvalidated. A schema here WINS over a built-in of the same name. */
36
36
  cardSchemas?: CardSchemaMap;
37
+ /** The custom-element host node to emit card events off when no `CardProvider`
38
+ * is present, forwarded through `MessageBody` to `CardRenderer`. The
39
+ * element facades pass their own host element so card events leave as the
40
+ * bubbling `kai-card` CustomEvent. */
41
+ cardHostElement?: HTMLElement;
37
42
  /** Show a typing indicator on the pending assistant turn — use while awaiting
38
43
  * the assistant's reply. */
39
44
  loading?: boolean;
@@ -146,6 +151,7 @@ export function Thread(props: ThreadProps) {
146
151
  parts={m().parts}
147
152
  cardTypes={props.cardTypes}
148
153
  cardSchemas={props.cardSchemas}
154
+ cardHostElement={props.cardHostElement}
149
155
  isUser={m().role === 'user'}
150
156
  markdown={m().role === 'assistant'}
151
157
  actions={m().actions}
@@ -209,7 +215,10 @@ export function Thread(props: ThreadProps) {
209
215
  </ChatContainerContent>
210
216
  <Show when={showScrollButton()}>
211
217
  <div class="absolute bottom-4 left-1/2 flex w-full max-w-3xl -translate-x-1/2 justify-center px-5">
212
- <ScrollButton class="shadow-sm" />
218
+ {/* The button now owns its elevation (kai-elevation); a `shadow-sm`
219
+ here would set box-shadow a second time and the winner would
220
+ be stylesheet order, not this call site. */}
221
+ <ScrollButton />
213
222
  </div>
214
223
  </Show>
215
224
  </ChatContainer>
@@ -320,7 +320,7 @@ function anchorStyle(position: ToastPosition, r: TargetRect): Record<string, str
320
320
  }
321
321
  }
322
322
 
323
- /** The overlay layer both region roots sit on (F-20). One token, one default:
323
+ /** The overlay layer both region roots sit on. One token, one default:
324
324
  * your app chrome must stay below the toast layer; set `--kai-toast-z`
325
325
  * (default 100) on the host to move the layer. */
326
326
  const TOAST_Z = 'var(--kai-toast-z, 100)';
@@ -25,6 +25,15 @@ export interface ToolPart {
25
25
  type: string;
26
26
  /** Semantic classification for rendering. Derive with classifyTool(type). */
27
27
  kind?: ToolKind;
28
+ /** The call's lifecycle. A provider stream (real or `createMockResponder`)
29
+ * only ever ANNOUNCES a call: arguments stream in (`input-streaming`), then
30
+ * the part parks at `input-available` — and stays there. The kit parses and
31
+ * renders the call; EXECUTING it and answering is the host's side of the
32
+ * seam: after the read settles, run the tool and patch the part forward with
33
+ * `stream.upsertTool(id, { state: 'output-available', output })` (or
34
+ * `upsertToolPart`), or `output-error` + `errorText` on failure. The one
35
+ * exception is a call the provider ran itself (`raw` carries the result and
36
+ * the wire sets `output` directly) — never re-execute those. */
28
37
  state: 'input-streaming' | 'input-available' | 'output-available' | 'output-error';
29
38
  /** Last VALID parsed snapshot, fingerprint-deduped. The primary channel: this is
30
39
  * what the kit renders. */
@@ -0,0 +1,73 @@
1
+ import { Show } from 'solid-js';
2
+ import { cn } from '../utils/cn';
3
+ import { renderIcon } from '../ui/icon';
4
+
5
+ export interface WidgetTabBarProps {
6
+ active: 'home' | 'messages';
7
+ onChange: (tab: 'home' | 'messages') => void;
8
+ /** Whether the Messages tab has unread activity. Reaches BOTH the visible
9
+ * dot and the tab's accessible name (`aria-label`) — a dot alone is
10
+ * invisible to assistive tech, the #336 lesson this test pins. */
11
+ unread?: boolean;
12
+ homeLabel?: string;
13
+ messagesLabel?: string;
14
+ }
15
+
16
+ /**
17
+ * The widget's Home/Messages tab bar (Intercom-pattern chrome, H-2/H-6). Real
18
+ * `tablist`/`tab` semantics — this is a persistent view switch within the
19
+ * widget, not page navigation, so it is not `<Nav>`'s `role="page"` dialect.
20
+ */
21
+ export function WidgetTabBar(props: WidgetTabBarProps) {
22
+ const home = () => props.homeLabel ?? 'Home';
23
+ const messages = () => props.messagesLabel ?? 'Messages';
24
+ const messagesLabel = () => (props.unread ? `${messages()} (unread)` : messages());
25
+
26
+ return (
27
+ <nav
28
+ role="tablist"
29
+ aria-label="Widget navigation"
30
+ class="flex h-14 shrink-0 items-stretch border-t border-border bg-background"
31
+ >
32
+ <button
33
+ type="button"
34
+ role="tab"
35
+ data-kai-tab-home
36
+ aria-selected={props.active === 'home'}
37
+ aria-label={home()}
38
+ onClick={() => props.onChange('home')}
39
+ class={cn(
40
+ 'flex flex-1 flex-col items-center justify-center gap-0.5 text-xs transition-colors',
41
+ props.active === 'home' ? 'text-primary' : 'text-muted-foreground hover:text-foreground',
42
+ )}
43
+ >
44
+ {renderIcon('home', { class: 'size-5' })}
45
+ <span>{home()}</span>
46
+ </button>
47
+ <button
48
+ type="button"
49
+ role="tab"
50
+ data-kai-tab-messages
51
+ aria-selected={props.active === 'messages'}
52
+ aria-label={messagesLabel()}
53
+ onClick={() => props.onChange('messages')}
54
+ class={cn(
55
+ 'flex flex-1 flex-col items-center justify-center gap-0.5 text-xs transition-colors',
56
+ props.active === 'messages' ? 'text-primary' : 'text-muted-foreground hover:text-foreground',
57
+ )}
58
+ >
59
+ <span class="relative">
60
+ {renderIcon('message-square', { class: 'size-5' })}
61
+ <Show when={props.unread}>
62
+ <span
63
+ data-kai-tab-unread
64
+ aria-hidden="true"
65
+ class="absolute -right-0.5 -top-0.5 size-1.5 rounded-full bg-unread"
66
+ />
67
+ </Show>
68
+ </span>
69
+ <span>{messages()}</span>
70
+ </button>
71
+ </nav>
72
+ );
73
+ }