@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
@@ -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 { Radio } from '../ui/radio';
17
18
  import { HoverCard } from '../ui/hover-card';
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, X } from 'lucide-solid';
25
+ import { Check } from 'lucide-solid';
25
26
 
26
27
  // ─────────────────────────────────────────────────────────────────────────────
27
28
  // Types (choice.schema.json) — AUTHORED IN ../primitives/card-data-types.ts.
@@ -430,6 +431,10 @@ export function ChoiceCard(props: ChoiceCardProps): JSX.Element {
430
431
  const groupLabel = (): string => local.heading ?? local.data?.prompt ?? 'Choose an option';
431
432
  const promptId = `kai-choice-prompt-${uid}`;
432
433
  const otherInputId = `kai-choice-other-${uid}`;
434
+ // The shared `name` is what makes the rows ONE native control: mutual exclusion,
435
+ // form participation and "2 of 4" from a screen reader all come from it. Scoped to
436
+ // this card's uid so two ChoiceCards on a page never de-select each other.
437
+ const radioName = `kai-choice-${uid}`;
433
438
 
434
439
  return (
435
440
  <Show when={valid()} fallback={<Card heading={local.heading} errorMessage={errorMessage()} />}>
@@ -443,14 +448,18 @@ export function ChoiceCard(props: ChoiceCardProps): JSX.Element {
443
448
  when={!res.isDeferred()}
444
449
  fallback={<DismissedStub type={CHOICE_CARD_TYPE} title={local.heading} onReopen={onReopen} />}
445
450
  >
446
- <Card heading={local.heading}>
451
+ {/* `prompt` is the card's supporting sentence — the same role `form`'s
452
+ `description` plays — so it goes through the shell's `description`
453
+ slot rather than being re-implemented in the body. That is what gives
454
+ it the muted colour: at full `text-foreground` it was the same colour
455
+ as the heading above it, which flattened the hierarchy. `descriptionId`
456
+ keeps the radiogroup's `aria-describedby` pointing at it. */}
457
+ <Card
458
+ heading={local.heading}
459
+ description={local.data?.prompt}
460
+ descriptionId={promptId}
461
+ >
447
462
  <div class={cn('flex flex-col gap-3', local.class)}>
448
- <Show when={local.data?.prompt}>
449
- <p id={promptId} class="text-sm text-foreground">
450
- {local.data?.prompt}
451
- </p>
452
- </Show>
453
-
454
463
  <Show
455
464
  when={!res.isResolved()}
456
465
  fallback={<ResolvedChoice choice={resolvedChoice()!} optimistic={res.isOptimistic()} />}
@@ -462,7 +471,7 @@ export function ChoiceCard(props: ChoiceCardProps): JSX.Element {
462
471
  aria-describedby={local.data?.prompt ? promptId : undefined}
463
472
  aria-disabled={isDisabled() ? 'true' : undefined}
464
473
  class={cn(
465
- 'divide-y divide-border overflow-hidden rounded-lg border border-input',
474
+ 'divide-y divide-border overflow-hidden rounded-lg border border-border',
466
475
  isDisabled() && 'cursor-not-allowed opacity-60',
467
476
  )}
468
477
  onKeyDown={onGroupKeyDown}
@@ -477,8 +486,10 @@ export function ChoiceCard(props: ChoiceCardProps): JSX.Element {
477
486
  return (
478
487
  <ListRow
479
488
  opt={opt}
489
+ name={radioName}
480
490
  checked={checked()}
481
491
  tabStop={tabStop()}
492
+ groupDisabled={isDisabled()}
482
493
  descId={descId}
483
494
  hasDesc={hasDesc()}
484
495
  onPick={() => {
@@ -518,14 +529,23 @@ export function ChoiceCard(props: ChoiceCardProps): JSX.Element {
518
529
 
519
530
  <div class="flex w-full flex-wrap items-center justify-between gap-2">
520
531
  <Show when={local.data?.dismissible === true}>
532
+ {/* The contract `dismiss` verb — a footer ACTION (it emits
533
+ `{kind:'dismiss'}` and collapses the card to a re-openable
534
+ stub), not the chrome close in `Card`'s own `dismissible`
535
+ prop, which the contract cards deliberately never set.
536
+ Labelled rather than a bare ghost ✕: every other control in
537
+ this row says what it does, and an unlabelled 28px glyph
538
+ 300px from the primary button reads as stray chrome. Same
539
+ shape in all four cards — `form` already looked like this.
540
+ `aria-label` is kept (redundant with the text) so the
541
+ element tests that select on it keep working. */}
521
542
  <Button
522
543
  type="button"
523
544
  variant="ghost"
524
- size="icon-sm"
525
545
  aria-label="Dismiss"
526
546
  onClick={onDismiss}
527
547
  >
528
- <X size={16} aria-hidden="true" />
548
+ Dismiss
529
549
  </Button>
530
550
  </Show>
531
551
  <Button type="button" class="ml-auto" disabled={!canSubmit()} onClick={submit}>
@@ -542,14 +562,19 @@ export function ChoiceCard(props: ChoiceCardProps): JSX.Element {
542
562
  }
543
563
 
544
564
  // ─────────────────────────────────────────────────────────────────────────────
545
- // Internal list-row presentation: a `role="radio"` with the kit's
546
- // selectable-list styling.
565
+ // Internal list-row presentation: a `<label>` row wrapping a REAL
566
+ // `<input type="radio">` (the `Radio` primitive), with the kit's selectable-list
567
+ // styling.
547
568
  // ─────────────────────────────────────────────────────────────────────────────
548
569
 
549
570
  interface RowProps {
550
571
  opt: ChoiceOption;
572
+ /** The shared form-control name every radio in this card's group carries. */
573
+ name: string;
551
574
  checked: boolean;
552
575
  tabStop: boolean;
576
+ /** The whole group is frozen (`disabled` prop), not just this row. */
577
+ groupDisabled: boolean;
553
578
  descId: string;
554
579
  hasDesc: boolean;
555
580
  onPick: () => void;
@@ -558,7 +583,7 @@ interface RowProps {
558
583
 
559
584
  function RecommendedPill(): JSX.Element {
560
585
  return (
561
- <span class="inline-flex items-center rounded-full bg-[var(--color-primary)] px-1.5 py-0.5 text-[0.625rem] font-medium uppercase leading-none tracking-wide text-[var(--color-primary-foreground,white)]">
586
+ <span class="inline-flex items-center rounded-full bg-[var(--color-primary)] px-1.5 py-0.5 text-micro font-medium uppercase leading-none tracking-wide text-[var(--color-primary-foreground,white)]">
562
587
  Recommended
563
588
  </span>
564
589
  );
@@ -587,25 +612,76 @@ function IconBadge(props: { name: string }): JSX.Element {
587
612
  );
588
613
  }
589
614
 
615
+ /**
616
+ * One row of the radiogroup.
617
+ *
618
+ * The control is a REAL `<input type="radio">` (the `Radio` primitive over
619
+ * `.kai-radio`), not the `aria-hidden` ring-shaped `<span>` this row used to draw.
620
+ * That is what buys form participation — a `name`, a `value`, and a real entry in a
621
+ * native `FormData` — which a `<div role="radio">` can never have, and it removes the
622
+ * fourth hand-rolled radio from the kit.
623
+ *
624
+ * WHY `role="radio"` IS ON THE INPUT AND NOT ON THE ROW. `choice-card.tsx`'s own
625
+ * keyboard handler and `response-compare.tsx` both drive `[role="radio"]`, an
626
+ * ATTRIBUTE selector that a native radio's implicit role does not match, so the role
627
+ * has to be written somewhere. Putting it on the `<label>` wrapper (which the plan
628
+ * suggested) would nest a radio inside a radio and announce the row twice; putting it
629
+ * on the input states the role the input already has. `aria-checked` / `aria-disabled`
630
+ * are likewise redundant with `checked` / `disabled` and kept in lockstep with them —
631
+ * they are the state the element tests read, and a redundant ARIA attribute that never
632
+ * disagrees with the DOM is harmless where a second radio in the a11y tree is not.
633
+ *
634
+ * The row's `<label>` is what makes a click anywhere on it select the option, which is
635
+ * the browser's own behaviour rather than the `onClick` re-implementation it replaces.
636
+ * The `onClick` is kept as well: it is the path the existing tests fire, and both paths
637
+ * land on the same idempotent `select()`.
638
+ */
590
639
  function ListRow(props: RowProps): JSX.Element {
640
+ // The row is inert when the option itself is disabled OR the whole group is frozen.
641
+ // The group case has to reach the native `disabled` attribute: an enabled input
642
+ // would visibly check itself on click while `select()` refuses the state change, and
643
+ // the DOM would then be lying about the selection.
644
+ const inert = (): boolean => props.opt.disabled === true || props.groupDisabled;
645
+ const pick = (): void => {
646
+ if (inert()) return;
647
+ props.onPick();
648
+ };
591
649
  return (
592
- <div
593
- role="radio"
594
- aria-checked={props.checked}
595
- aria-disabled={props.opt.disabled ? 'true' : undefined}
596
- aria-describedby={props.hasDesc ? props.descId : undefined}
597
- data-option-id={props.opt.id}
598
- tabindex={props.opt.disabled ? -1 : props.tabStop ? 0 : -1}
599
- onClick={() => !props.opt.disabled && props.onPick()}
600
- onFocus={props.onFocus}
650
+ <label
651
+ onClick={pick}
601
652
  class={cn(
602
- 'flex items-center gap-3 px-3 py-2.5 text-sm outline-none transition-colors focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring',
653
+ 'flex items-center gap-3 px-3 py-2.5 text-sm transition-colors',
654
+ // The focus ring stays on the ROW and stays inset — the same 2px inset ring the
655
+ // focusable `div` painted. Focus now lands on the input inside it, so the
656
+ // selector moves from `focus-visible:` to `has-[:focus-visible]:`; the input's
657
+ // own outline is suppressed below so there is one ring, not two. That is the
658
+ // pattern src/elements/styles.css sanctions at its `:focus-visible` rule.
659
+ 'has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-inset has-[:focus-visible]:ring-ring',
603
660
  props.opt.disabled
604
661
  ? 'cursor-not-allowed opacity-60 text-foreground'
605
662
  : 'cursor-pointer hover:bg-muted/50',
606
663
  props.checked ? 'bg-accent font-medium text-accent-foreground' : 'text-foreground',
607
664
  )}
608
665
  >
666
+ {/* FIRST in the row, ahead of any media. It used to sit AFTER the thumbnail/icon
667
+ badge, so a list where only some options carried media had a ragged control
668
+ column (measured 98px vs 50px in the `WithOther` story). The control column is
669
+ the one thing every row shares, so it leads. */}
670
+ <Radio
671
+ role="radio"
672
+ name={props.name}
673
+ value={props.opt.id}
674
+ checked={props.checked}
675
+ disabled={inert()}
676
+ aria-checked={props.checked}
677
+ aria-disabled={props.opt.disabled ? 'true' : undefined}
678
+ aria-describedby={props.hasDesc ? props.descId : undefined}
679
+ data-option-id={props.opt.id}
680
+ tabindex={props.opt.disabled ? -1 : props.tabStop ? 0 : -1}
681
+ class="focus-visible:outline-none"
682
+ onChange={pick}
683
+ onFocus={props.onFocus}
684
+ />
609
685
  <Show when={props.opt.media?.image}>
610
686
  <HoverCard
611
687
  openDelay={150}
@@ -623,17 +699,6 @@ function ListRow(props: RowProps): JSX.Element {
623
699
  <Show when={!props.opt.media?.image && props.opt.media?.icon}>
624
700
  <IconBadge name={props.opt.media!.icon!} />
625
701
  </Show>
626
- <span
627
- aria-hidden="true"
628
- class={cn(
629
- 'flex h-[1.125rem] w-[1.125rem] shrink-0 items-center justify-center rounded-full border-[1.5px] transition-colors',
630
- props.checked ? 'border-[var(--color-primary)]' : 'border-input bg-background',
631
- )}
632
- >
633
- <Show when={props.checked}>
634
- <span class="h-2 w-2 rounded-full bg-[var(--color-primary)]" />
635
- </Show>
636
- </span>
637
702
  <span class="flex min-w-0 flex-col gap-0.5">
638
703
  <span class="flex items-center gap-2">
639
704
  <span class="truncate">{props.opt.label}</span>
@@ -652,7 +717,7 @@ function ListRow(props: RowProps): JSX.Element {
652
717
  {props.opt.meta}
653
718
  </span>
654
719
  </Show>
655
- </div>
720
+ </label>
656
721
  );
657
722
  }
658
723
 
@@ -663,7 +728,7 @@ function ResolvedChoice(props: {
663
728
  const c = props.choice;
664
729
  return (
665
730
  <div
666
- class="flex items-center gap-3 rounded-lg border border-input bg-accent px-3 py-2.5 text-sm font-medium text-accent-foreground"
731
+ class="flex items-center gap-3 rounded-lg border border-border bg-accent px-3 py-2.5 text-sm font-medium text-accent-foreground"
667
732
  role={props.optimistic ? 'status' : undefined}
668
733
  >
669
734
  <Check size={16} aria-hidden="true" />
@@ -228,7 +228,7 @@ export function Coachmark(props: CoachmarkProps) {
228
228
  <Show when={props.badge}>
229
229
  <span
230
230
  part="badge"
231
- class={cn('inline-flex items-center rounded-full px-1.5 py-0.5 text-[0.625rem] font-medium uppercase leading-none tracking-wide', BADGE_CLS)}
231
+ class={cn('inline-flex items-center rounded-full px-1.5 py-0.5 text-micro font-medium uppercase leading-none tracking-wide', BADGE_CLS)}
232
232
  >
233
233
  {props.badge}
234
234
  </span>
@@ -122,8 +122,8 @@ function CodeBlockCode(props: CodeBlockCodeProps) {
122
122
 
123
123
  const codeTextSize = () => {
124
124
  switch (config.proseSize()) {
125
- case 'xs': return 'text-[11px]';
126
- case 'sm': return 'text-[13px]';
125
+ case 'xs': return 'text-caption';
126
+ case 'sm': return 'text-compact';
127
127
  case 'base': return 'text-sm';
128
128
  case 'lg': return 'text-base';
129
129
  }
@@ -11,6 +11,7 @@ import {
11
11
  For,
12
12
  } from 'solid-js';
13
13
  import { cn } from '../utils/cn';
14
+ import { useChatConfig, textClass } from '../primitives/chat-config';
14
15
  import {
15
16
  type ComposerDoc,
16
17
  type EntityRef,
@@ -242,6 +243,7 @@ function setCaretToOffset(root: HTMLElement, offset: number): void {
242
243
  }
243
244
 
244
245
  export function Composer(props: ComposerProps): JSX.Element {
246
+ const config = useChatConfig();
245
247
  let editable!: HTMLDivElement;
246
248
  // A SIGNAL (not a plain `let`): usePosition's floating ref must be reactive so
247
249
  // Floating UI recomputes once the menu mounts. A non-reactive ref left the menu
@@ -799,23 +801,78 @@ export function Composer(props: ComposerProps): JSX.Element {
799
801
  // This is covered by updateTriggerState() in handleInput.
800
802
 
801
803
  const maxH = () => props.maxHeight ?? 240;
802
- // No `outline-none` in the default class. This element is `contenteditable`
803
- // with `role="textbox"` — a real tab stop, and the primary one in the kit —
804
- // and suppressing the outline left keyboard users with no indication that the
805
- // message input was focused. `kai-focus-inset` draws the ring inside the
806
- // border box, because the editable fills a rounded, clipping composer shell
807
- // that would otherwise erase an outset one. A consumer passing
808
- // `editableClass` still replaces this wholesale.
804
+ // This element is `contenteditable` with `role="textbox"` a real tab stop,
805
+ // and the primary one in the kit — so it must never be left with NO focus
806
+ // affordance. WHICH element draws it depends on who owns the frame:
807
+ //
808
+ // - Standalone (the `else` branch below renders the rounded shell): the FRAME
809
+ // owns the ring via `focus-within:ring-*`, exactly like `PromptInput`, so
810
+ // the editable neutralizes its own. The editable is a square, full-bleed
811
+ // box inside a `rounded-xl` shell, so an outline on IT painted a sharp
812
+ // rectangle hugging the text region inside the rounded border — the bug.
813
+ // - Bare: the host owns radius/bg/padding and may draw nothing at all
814
+ // (`PromptInput` passes its own `editableClass` and rings its frame), so
815
+ // the editable keeps `kai-focus-inset` — a ring inside the border box,
816
+ // since an outset one would be clipped by a rounded host shell.
817
+ //
818
+ // A consumer passing `editableClass` still replaces this wholesale — including
819
+ // the `text-start` pin below, so a replacement string owes its own (`PromptInput`,
820
+ // the one in-repo caller, carries it).
809
821
  const editableCls = () =>
810
822
  props.editableClass ??
811
- 'text-foreground min-h-[44px] w-full overflow-y-auto kai-focus-inset whitespace-pre-wrap break-words';
823
+ cn(
824
+ // `text-start` is a PIN, not a style choice: `text-align` inherits, so any
825
+ // centered ancestor (`Empty`'s root did exactly this) reached in and centered
826
+ // the placeholder AND the typed text. An input control's text alignment is a
827
+ // fact about the control, so it states it rather than inheriting it. LOGICAL
828
+ // (`start`), not `text-left` — in RTL the correct edge is the right one, and
829
+ // pinning the physical value would be a worse bug than the one it fixes.
830
+ 'text-foreground min-h-[44px] w-full text-start overflow-y-auto whitespace-pre-wrap break-words',
831
+ // Size from the SAME token every other text surface reads (`ChatConfig`'s
832
+ // proseSize, default `sm` = the 14px the rest of the kit sets explicitly) —
833
+ // `Markdown`, `MessageContent` and `PromptInputTextarea` all go through
834
+ // `textClass`. Without it the editable set no size at all and just inherited
835
+ // whatever the shadow root's base was (16px in a stock page), so the composer
836
+ // read a step larger than every component beside it. Going through the token
837
+ // also means `proseSize` moves the composer like it moves `PromptInput`,
838
+ // rather than leaving one input deaf to it.
839
+ textClass(config.proseSize()),
840
+ props.bare
841
+ ? 'kai-focus-inset'
842
+ : 'outline-none focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0',
843
+ );
812
844
 
813
845
  const inner = (
814
846
  <>
815
847
  {/* Static style for CSS Custom Highlight API decoration.
816
848
  No-op in browsers that don't support ::highlight(); the selector
817
849
  is simply unrecognized and dropped. */}
818
- <style>{`::highlight(${highlightName}) { background-color: color-mix(in srgb, var(--color-primary, #6366f1) 22%, transparent); }`}</style>
850
+ <style>{`
851
+ /* MARKED text, not selected text. Reads --color-highlight (overridable via
852
+ --kai-color-highlight), a warm <mark> yellow. It used to be a 22% tint of
853
+ --color-primary with a #6366f1 indigo fallback — but the kit's
854
+ --color-primary is a NEUTRAL (near-black in light, near-white in dark),
855
+ so the indigo the fallback implied never appeared and the highlight
856
+ rendered plain grey in both themes.
857
+
858
+ NO \`prefers-color-scheme\` here, deliberately — the same trap that put
859
+ the pill hues below at 2.33:1. The OS scheme and the kit's RESOLVED
860
+ theme are different questions, and \`theme="light"\` on a dark-OS machine
861
+ is an ordinary configuration; a scheme-keyed value would paint the dark
862
+ amber on the light field. The pills answer this by scoping to \`.dark\`,
863
+ which is NOT available here: \`::highlight()\` names a document-registered
864
+ highlight, so there is no wrapper to hang a descendant selector on.
865
+
866
+ It does not need one. \`--color-highlight\` reaches this pseudo through
867
+ highlight inheritance from the ORIGINATING element — the text inside the
868
+ \`.dark\` wrapper — so the token already answers the resolved theme,
869
+ verified by rendering the dark amber under \`theme="dark"\` with only the
870
+ LIGHT literal below as a fallback. The literal is therefore reachable
871
+ only when no kit stylesheet is loaded at all, and it is deliberately the
872
+ light one: an unstyled page is a white page, and guessing from the OS is
873
+ exactly the failure above. */
874
+ ::highlight(${highlightName}) { background-color: var(--color-highlight, hsl(45 96% 78%)); }
875
+ `}</style>
819
876
  {/* Atomic entity pill styling. Self-contained (currentColor-based) so it
820
877
  renders correctly without depending on a Tailwind rebuild for the
821
878
  custom .kai-composer-pill class. */}
@@ -846,12 +903,24 @@ export function Composer(props: ComposerProps): JSX.Element {
846
903
  .kai-composer-pill[data-kind="agent"] { color: var(--kai-pill-agent, #7c3aed); }
847
904
  /* Brighter hues on dark so they don't muddy against a dark field. The
848
905
  consumer's --kai-pill-* override still wins (it's the var value); only
849
- the FALLBACK shifts per scheme. */
850
- @media (prefers-color-scheme: dark) {
851
- .kai-composer-pill[data-kind="skill"] { color: var(--kai-pill-skill, #6ea8fe); }
852
- .kai-composer-pill[data-kind="agent"] { color: var(--kai-pill-agent, #c4a7fc); }
853
- }
854
- .kai-composer-pill-sigil { opacity: 0.6; font-weight: 600; }
906
+ the FALLBACK shifts per scheme.
907
+
908
+ Keyed on the kit's RESOLVED theme (the \`.dark\` wrapper an element
909
+ facade puts inside its shadow root, and the \`.dark\` class the docs /
910
+ Storybook put on <html>) — NOT \`prefers-color-scheme\`. Those are
911
+ different questions: \`theme="light"\` on a machine whose OS is dark is
912
+ an ordinary, common configuration, and the media query answered the OS
913
+ while the surface under the pill answered the attribute. That painted
914
+ the DARK hue (#6ea8fe) on the LIGHT field: 2.33:1, under both the
915
+ 4.5:1 text floor (SC 1.4.3) and even 3:1. Matched pairs looked fine,
916
+ which is why it survived — never scheme-match a colour whose backdrop
917
+ is chosen by the theme attribute. */
918
+ .dark .kai-composer-pill[data-kind="skill"] { color: var(--kai-pill-skill, #6ea8fe); }
919
+ .dark .kai-composer-pill[data-kind="agent"] { color: var(--kai-pill-agent, #c4a7fc); }
920
+ /* Dimmer than the label, but the sigil is what makes a pill read as
921
+ \`/skill\` vs \`@agent\`, so it is a meaningful glyph and owes 3:1
922
+ (SC 1.4.11). At 0.6 the light-mode composite was 2.52:1. */
923
+ .kai-composer-pill-sigil { opacity: 0.8; font-weight: 600; }
855
924
  /* Plugins (and any other/unknown kind): the richer CHIP — background +
856
925
  icon — so a composite bundle reads differently at a glance. */
857
926
  .kai-composer-pill:not([data-kind="skill"]):not([data-kind="agent"]) {
@@ -991,7 +1060,12 @@ export function Composer(props: ComposerProps): JSX.Element {
991
1060
  ) : (
992
1061
  <div
993
1062
  class={cn(
1063
+ // Same frame-owns-the-ring pattern as `PromptInput`: the editable inside
1064
+ // neutralizes its own outline (see `editableCls`), so this rounded shell
1065
+ // draws the focus affordance and the ring follows `rounded-xl` instead of
1066
+ // painting a square box around the inner text region.
994
1067
  'kai-composer relative rounded-xl bg-surface p-2',
1068
+ 'focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-0',
995
1069
  props.disabled && 'cursor-not-allowed opacity-60',
996
1070
  )}
997
1071
  >
@@ -18,7 +18,7 @@ import type { CardEnvelope, CardEvent, CardHost, CardResolution } from '../primi
18
18
  import { useCardResolution } from './use-card-resolution';
19
19
  import { emitCardEvent } from '../primitives/card-routing';
20
20
  import { useCardHost } from '../primitives/card-host';
21
- import { AlertTriangle, Check, X } from 'lucide-solid';
21
+ import { AlertTriangle, Check } from 'lucide-solid';
22
22
 
23
23
  // ─────────────────────────────────────────────────────────────────────────────
24
24
  // Types (confirm.schema.json) — AUTHORED IN ../primitives/card-data-types.ts.
@@ -316,14 +316,23 @@ export function ConfirmCard(props: ConfirmCardProps): JSX.Element {
316
316
  >
317
317
  <div class="flex w-full flex-wrap items-center justify-between gap-2">
318
318
  <Show when={local.data?.dismissible === true}>
319
+ {/* The contract `dismiss` verb — a footer ACTION (it emits
320
+ `{kind:'dismiss'}` and collapses the card to a re-openable
321
+ stub), not the chrome close in `Card`'s own `dismissible`
322
+ prop, which the contract cards deliberately never set.
323
+ Labelled rather than a bare ghost ✕: every other control in
324
+ this row says what it does, and an unlabelled 28px glyph
325
+ 300px from the primary button reads as stray chrome. Same
326
+ shape in all four cards — `form` already looked like this.
327
+ `aria-label` is kept (redundant with the text) so the
328
+ element tests that select on it keep working. */}
319
329
  <Button
320
330
  type="button"
321
331
  variant="ghost"
322
- size="icon-sm"
323
332
  aria-label="Dismiss"
324
333
  onClick={onDismiss}
325
334
  >
326
- <X size={16} aria-hidden="true" />
335
+ Dismiss
327
336
  </Button>
328
337
  </Show>
329
338
  <div ref={actionsRef} class="ml-auto flex flex-wrap items-center gap-2">
@@ -375,7 +384,14 @@ export function ConfirmCard(props: ConfirmCardProps): JSX.Element {
375
384
  <p class="text-sm font-semibold text-foreground">{local.data?.heading}</p>
376
385
  </Show>
377
386
  <Show when={local.data?.body}>
378
- <p class="text-sm text-foreground">{local.data?.body}</p>
387
+ {/* The supporting sentence sits directly under the heading it
388
+ supports, muted — the same contrast step the shell header makes
389
+ between `heading` and `description`. It stays in the BODY (not
390
+ the shell's `description` slot, where `form`'s and `choice`'s
391
+ live) because `data.heading` is a documented IN-BODY heading
392
+ that this sentence has to stay beneath, and because this div is
393
+ the Enter-to-confirm tab stop. */}
394
+ <p class="text-sm text-muted-foreground">{local.data?.body}</p>
379
395
  </Show>
380
396
  </div>
381
397
  </Card>
@@ -31,9 +31,32 @@ export function relativeTimeShort(iso?: string, now: number = Date.now()): strin
31
31
  return `${Math.floor(hours / 24)}d ago`;
32
32
  }
33
33
 
34
+ /**
35
+ * Whether a conversation should show an unread indicator (owner round,
36
+ * 2026-08-26). `lastReadAt`'s own doc (`types.ts`) has the full contract;
37
+ * this is the one place that reads it, so both list surfaces (this file's
38
+ * `ConversationItem` and the widget-box `ConversationPanel`) derive it
39
+ * identically rather than each restating the comparison.
40
+ *
41
+ * Absent `lastReadAt` reads as NOT unread — the decide-loudly default for a
42
+ * store that never implements `ConversationStore.markRead` at all (every
43
+ * summary it returns leaves the field undefined forever, so this always
44
+ * returns `false` for it) rather than guessing "probably unread" from a
45
+ * signal the store never actually provided. Defensive `Date.parse`, same
46
+ * pattern as `relativeTimeShort` above: an unparsable date reads as not
47
+ * unread rather than throwing.
48
+ */
49
+ export function isConversationUnread(conv: Pick<ConversationSummary, 'updatedAt' | 'lastReadAt'>): boolean {
50
+ if (!conv.lastReadAt) return false;
51
+ const updated = Date.parse(conv.updatedAt);
52
+ const read = Date.parse(conv.lastReadAt);
53
+ if (Number.isNaN(updated) || Number.isNaN(read)) return false;
54
+ return updated > read;
55
+ }
56
+
34
57
  /**
35
58
  * The slotted-item shape rendered by `<kai-conversation-item>` — the composed
36
- * row of the consumer-owned loop (spec 2026-08-20 § 2a). Distinct from the
59
+ * row of the consumer-owned loop. Distinct from the
37
60
  * data-mode `ConversationItem` above, which batteries mode keeps rendering
38
61
  * unchanged: this one takes REGIONS, not a `ConversationSummary`.
39
62
  *
@@ -41,16 +64,19 @@ export function relativeTimeShort(iso?: string, now: number = Date.now()): strin
41
64
  * title), plus `leading`, `meta`, and `menu`. The `menu` region takes the
42
65
  * consumer's OWN popover (rename / fork / archive live there); the component
43
66
  * provides only the region plus focus and ARIA plumbing, never a declarative
44
- * actions prop. Activation semantics live in the CONTAINER
45
- * (`createConversationItemsController` in conversation-list.tsx): this row is
46
- * presentational, and the `data-kai-item-menu` marker on the menu region is what
47
- * the container's activation guard keys off so a click in the consumer's menu
48
- * never also selects the row.
67
+ * actions prop. Activation has two modes: inside `<kai-conversations>` it
68
+ * lives in the CONTAINER (`createConversationItemsController` in
69
+ * conversation-list.tsx this row renders no handler and the
70
+ * `data-kai-item-menu` marker on the menu region is what the container's
71
+ * activation guard keys off so a click in the consumer's menu never also
72
+ * selects the row); STANDALONE, `onActivate` makes the row body
73
+ * its own tabbable button-role control — click / Enter / Space — with the menu
74
+ * still outside the control as the body's sibling.
49
75
  *
50
76
  * ARIA contract for direct Solid use: the row renders `role="listitem"` holding
51
77
  * a `role="button"` body (`aria-current` marks the active row, the same dialect
52
78
  * as the batteries-mode row above) with the menu as the body's tabbable SIBLING
53
- * (ratified 2026-08-20; axe `nested-interactive` bans focusable descendants of
79
+ * (axe `nested-interactive` bans focusable descendants of
54
80
  * a control, and `aria-required-children` outlaws non-option content anywhere
55
81
  * inside a listbox, which is why the vocabulary is not listbox/option). Render
56
82
  * your loop's rows inside a `role="list"` ancestor; element mode has no such
@@ -80,13 +106,22 @@ export interface SlottedConversationItemProps {
80
106
  * Solid consumers rendering the component directly leave it off and the row
81
107
  * itself is the group. */
82
108
  hostSemantics?: boolean;
109
+ /** STANDALONE activation: when set, the row body is itself the
110
+ * activation control — `tabindex="0"` on the `role="button"` body, and
111
+ * click / Enter / Space call this. The menu region never triggers it (it is
112
+ * the body's sibling, outside the control). Inside `<kai-conversations>`
113
+ * leave it UNSET: the container's controller owns activation (its delegated
114
+ * click/keydown → `kai-conversation-select`) plus roving tabindex, and a
115
+ * handler here would double-fire. The `kai-conversation-item` facade passes
116
+ * it only when the item is standalone. */
117
+ onActivate?: () => void;
83
118
  class?: string;
84
119
  }
85
120
 
86
121
  export function SlottedConversationItem(props: SlottedConversationItemProps) {
87
- const [local] = splitProps(props, ['conversationId', 'active', 'compact', 'leading', 'meta', 'menu', 'children', 'hostSemantics', 'class']);
122
+ const [local] = splitProps(props, ['conversationId', 'active', 'compact', 'leading', 'meta', 'menu', 'children', 'hostSemantics', 'onActivate', 'class']);
88
123
  return (
89
- // The sibling restructure (ratified 2026-08-20): axe nested-interactive
124
+ // The sibling restructure: axe nested-interactive
90
125
  // bans focusable descendants of an activation control, so the control role
91
126
  // sits on the row BODY and the consumer's menu is its tabbable SIBLING
92
127
  // (the nav.tsx TrailingActions precedent). The vocabulary is
@@ -112,6 +147,18 @@ export function SlottedConversationItem(props: SlottedConversationItemProps) {
112
147
  role="button"
113
148
  data-kai-item-body
114
149
  aria-current={local.active ? 'true' : 'false'}
150
+ // Standalone activation only: with `onActivate` unset —
151
+ // the inside-a-container case — no tabindex and no handlers render, so
152
+ // the container's delegated activation stays the single path.
153
+ tabindex={local.onActivate ? 0 : undefined}
154
+ onClick={() => local.onActivate?.()}
155
+ onKeyDown={(e: KeyboardEvent) => {
156
+ if (!local.onActivate) return;
157
+ if (e.key === 'Enter' || e.key === ' ') {
158
+ e.preventDefault(); // Space must not scroll the page
159
+ local.onActivate();
160
+ }
161
+ }}
115
162
  class="flex min-w-0 flex-1 cursor-pointer items-center gap-2.5 rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
116
163
  >
117
164
  <Show when={local.leading}>