@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
@@ -1,25 +1,64 @@
1
- import { type JSX, For, Show, createSignal } from 'solid-js';
1
+ import { type JSX, For, Show, createMemo, createSignal } from 'solid-js';
2
2
  import { cn } from '../utils/cn';
3
3
  import { Textarea } from '../ui/textarea';
4
4
  import { Input, FIELD_BASE as inputBase } from '../ui/input';
5
- import { Star } from 'lucide-solid';
6
- import type { FormField } from './form';
5
+ import { Slider } from '../ui/slider';
6
+ import { Select } from '../ui/select';
7
+ import { Switch } from '../ui/switch';
8
+ import { Checkbox } from '../ui/checkbox';
9
+ import { RadioGroup } from '../ui/radio';
10
+ import { CheckboxGroup } from '../ui/checkbox-group';
11
+ import { Button } from '../ui/button';
12
+ import { Star, X } from 'lucide-solid';
13
+ import type { FieldMaskHint, FormField } from './form';
7
14
 
8
15
  /** The shared prop shape every leaf widget receives from FieldRow. */
9
16
  export interface WidgetProps {
10
17
  id: string;
11
18
  value: unknown;
12
19
  field: FormField;
20
+ /**
21
+ * The field's resolved format hints, or an empty resolution.
22
+ *
23
+ * RESOLVED IN `FieldRow`, not here. The row already has to resolve them to render
24
+ * the format hint text and put its id in `aria-describedby`, and resolving the same
25
+ * untrusted `x-kai-*` keys a second time in the widget would warn twice about the
26
+ * same bad hint and could disagree with what the row said out loud.
27
+ */
28
+ mask?: FieldMaskHint;
13
29
  disabled: boolean;
14
30
  placeholder?: string;
15
31
  required: boolean;
16
32
  invalid: boolean;
17
33
  describedBy?: string;
18
34
  label: string;
35
+ /**
36
+ * The id of the row's VISIBLE label element, for widgets whose control is a group
37
+ * rather than one labelable input.
38
+ *
39
+ * `FieldRow` used to suppress its `<label>` for those kinds and each widget named
40
+ * itself with `aria-label={props.label}`, which is a name only a screen reader can
41
+ * reach: "Severity", "Environments" and "Tags" were announced and invisible. The row
42
+ * now always renders the text and hands its id down here, so one string names the
43
+ * group and everybody can see it. Absent means the widget is on its own (a bare
44
+ * widget rendered outside `FieldRow`), so `aria-label` stays the fallback.
45
+ */
46
+ labelledBy?: string;
19
47
  onInput: (value: unknown) => void;
20
48
  onBlur: () => void;
21
49
  }
22
50
 
51
+ /**
52
+ * How a GROUP widget names itself: the row's visible label when there is one,
53
+ * `aria-label` only as the fallback. Never both — two accessible names on one element
54
+ * is one too many, and `aria-labelledby` would win silently anyway.
55
+ */
56
+ function groupNameProps(p: WidgetProps): { 'aria-labelledby'?: string; 'aria-label'?: string } {
57
+ return p.labelledBy !== undefined
58
+ ? { 'aria-labelledby': p.labelledBy }
59
+ : { 'aria-label': p.label };
60
+ }
61
+
23
62
  function ariaProps(p: WidgetProps) {
24
63
  return {
25
64
  'aria-required': p.required || undefined,
@@ -65,6 +104,16 @@ export function TextWidget(
65
104
  disabled={props.disabled}
66
105
  minLength={props.field.minLength}
67
106
  maxLength={props.field.maxLength}
107
+ // Masking (spec §7.3). Each is `undefined` for a field with no format hints, so
108
+ // an unhinted field renders exactly the input it rendered before. The hint TEXT
109
+ // is deliberately not passed as `Input`'s own `hint`: `FieldRow` renders it and
110
+ // owns the `aria-describedby` chain, and `Input`'s hint would mint a second one
111
+ // (spec §6). The submitted value is the CANONICAL one — `Input` emits canonical
112
+ // through `onValueInput` whenever a mask is active (spec §4), which is what
113
+ // makes "exactly one value per field" true without this widget choosing.
114
+ format={props.mask?.format}
115
+ guide={props.mask?.guide}
116
+ semantic={props.mask?.semantic}
68
117
  {...ariaProps(props)}
69
118
  onValueInput={(value) => props.onInput(value)}
70
119
  onValueChange={() => props.onBlur()}
@@ -99,94 +148,135 @@ export function TextareaWidget(props: WidgetProps): JSX.Element {
99
148
 
100
149
  export function NumberWidget(props: WidgetProps): JSX.Element {
101
150
  const step = () => props.field['x-kai-step'] ?? (props.field.type === 'integer' ? 1 : undefined);
151
+ // `Input`, not a raw `<input class={inputBase}>` (plan step 6). The two render the
152
+ // same box — `FIELD_BASE` is the former `inputBase` — so nothing is visible. What the
153
+ // bypass lost was the focus-node-reuse fix documented at `ui/input.tsx:262-268`: with
154
+ // the class computed at the CALL SITE, `props.invalid` changing rebuilt the `<input>`
155
+ // node and took focus and caret with it, and `kai-form` derives `invalid` from the
156
+ // field's own value. `min`/`max`/`step` come from the schema and are forwarded
157
+ // untouched; no bound is invented here.
158
+ //
159
+ // No `format`/`guide`/`semantic`: masking is a text-field affordance and a
160
+ // `type="number"` field has no mask hints to resolve.
102
161
  return (
103
- <input
162
+ <Input
104
163
  id={props.id}
105
164
  data-control
106
165
  type="number"
107
- class={cn(inputBase, props.invalid && 'border-destructive dark:border-red-400/70')}
108
166
  value={props.value === undefined || props.value === null ? '' : String(props.value)}
109
167
  placeholder={props.placeholder}
168
+ invalid={props.invalid}
110
169
  disabled={props.disabled}
111
170
  min={props.field.minimum}
112
171
  max={props.field.maximum}
113
172
  step={step()}
114
173
  {...ariaProps(props)}
115
- onInput={(e) => props.onInput(e.currentTarget.value)}
116
- onBlur={props.onBlur}
174
+ onValueInput={(value) => props.onInput(value)}
175
+ onValueChange={() => props.onBlur()}
117
176
  />
118
177
  );
119
178
  }
120
179
 
121
180
  export function SliderWidget(props: WidgetProps): JSX.Element {
181
+ // The 0..100 fallback is the WIDGET's, not the primitive's (plan §4). This widget is
182
+ // reading a consumer-authored JSON-Schema field where `minimum`/`maximum` are
183
+ // optional; `Slider` itself requires both, so no other caller inherits this guess.
122
184
  const min = () => props.field.minimum ?? 0;
123
185
  const max = () => props.field.maximum ?? 100;
124
186
  const step = () => props.field['x-kai-step'] ?? (props.field.type === 'integer' ? 1 : undefined);
125
187
  const current = () => (props.value === undefined || props.value === null ? min() : Number(props.value));
126
- const fill = (): string => {
127
- const lo = min();
128
- const hi = max();
129
- return hi > lo ? `${((current() - lo) / (hi - lo)) * 100}%` : '0%';
130
- };
188
+ // The value bubble and the row that holds it BOTH live in the primitive now
189
+ // (`valueLabel`), so this widget no longer hand-builds either. Same markup, same
190
+ // classes, one owner, and the readout picked up an `aria-hidden` it did not have
191
+ // here: the slider already announces the number through `aria-valuetext` below.
131
192
  return (
132
- <div class="flex items-center gap-3">
133
- <input
134
- id={props.id}
135
- data-control
136
- type="range"
137
- class="kai-range"
138
- style={{ '--kai-range-fill': fill() }}
139
- value={current()}
140
- min={min()}
141
- max={max()}
142
- step={step()}
143
- disabled={props.disabled}
144
- aria-valuetext={String(current())}
145
- {...ariaProps(props)}
146
- onInput={(e) => props.onInput(Number(e.currentTarget.value))}
147
- onBlur={props.onBlur}
148
- />
149
- <span class="min-w-9 shrink-0 rounded-md bg-background px-2 py-1 text-center text-sm font-medium tabular-nums text-foreground shadow-sm">
150
- {current()}
151
- </span>
152
- </div>
193
+ <Slider
194
+ id={props.id}
195
+ data-control
196
+ value={current()}
197
+ min={min()}
198
+ max={max()}
199
+ step={step()}
200
+ disabled={props.disabled}
201
+ valueLabel
202
+ aria-valuetext={String(current())}
203
+ {...ariaProps(props)}
204
+ onInput={(e) => props.onInput(Number(e.currentTarget.value))}
205
+ onBlur={props.onBlur}
206
+ />
153
207
  );
154
208
  }
155
209
 
156
210
  export function RatingWidget(props: WidgetProps): JSX.Element {
157
211
  const max = () => props.field.maximum ?? 5;
212
+ const min = () => props.field.minimum ?? 1;
158
213
  const current = () => Number(props.value ?? 0);
159
214
  const stars = () => Array.from({ length: max() }, (_, i) => i + 1);
215
+ /**
216
+ * Roving tabindex. Exactly one `role="radio"` is in the tab order — the selected
217
+ * star, or the first one while nothing is selected. The GROUP is deliberately not
218
+ * a tab stop: it used to be (`tabindex={0}` with every radio at `-1`), and the
219
+ * consequence was that arrow keys changed the value while focus never left the
220
+ * group, so the focus ring sat on the whole row and a screen reader announced the
221
+ * radiogroup instead of "3 stars, selected". Roving tabindex is the pattern the
222
+ * ARIA radiogroup spec asks for and it makes both symptoms go away at once.
223
+ */
224
+ const activeStar = () => (current() >= min() && current() <= max() ? current() : min());
225
+ const refs = new Map<number, HTMLButtonElement>();
226
+ /** Set the value AND move DOM focus to the star that now owns it. */
227
+ const select = (n: number): void => {
228
+ const next = Math.min(max(), Math.max(min(), n));
229
+ props.onInput(next);
230
+ refs.get(next)?.focus();
231
+ };
160
232
  const onKey = (e: KeyboardEvent): void => {
233
+ if (props.disabled) return;
161
234
  if (e.key === 'ArrowRight' || e.key === 'ArrowUp') {
162
235
  e.preventDefault();
163
- props.onInput(Math.min(max(), current() + 1));
236
+ select(current() + 1);
164
237
  } else if (e.key === 'ArrowLeft' || e.key === 'ArrowDown') {
165
238
  e.preventDefault();
166
- props.onInput(Math.max(props.field.minimum ?? 1, current() - 1));
239
+ select(current() - 1);
240
+ } else if (e.key === 'Home') {
241
+ e.preventDefault();
242
+ select(min());
243
+ } else if (e.key === 'End') {
244
+ e.preventDefault();
245
+ select(max());
167
246
  }
168
247
  };
169
248
  return (
249
+ // `groupNameProps`: the row renders a visible "Rating" label whose `for` had
250
+ // nothing to point at (there is no single labelable control here, only stars).
251
+ // Pointing the group at that same visible text makes ONE string name the control.
170
252
  <div
171
253
  role="radiogroup"
172
- aria-label={props.label}
173
- data-control
174
- tabindex={0}
175
- class="flex items-center gap-1 rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
254
+ {...groupNameProps(props)}
255
+ class="flex items-center gap-1 rounded-md"
176
256
  {...ariaProps(props)}
177
- onKeyDown={onKey}
178
257
  >
179
258
  <For each={stars()}>
180
259
  {(n) => (
181
260
  <button
182
261
  type="button"
183
262
  role="radio"
263
+ // `data-control` rides the roving tab stop, so `form.focusField()` —
264
+ // which focuses the single `[data-control]` inside the field — lands on
265
+ // the same star Tab would reach. It used to sit on the group, which is no
266
+ // longer focusable. No `id={props.id}`: the row's `<label for>` had
267
+ // nothing to point at before either, and pointing it at a star would make
268
+ // clicking the field label set the rating to 1. The group is named by the
269
+ // row's visible label (see `groupNameProps` above), which is what names it.
270
+ data-control={activeStar() === n ? '' : undefined}
184
271
  aria-checked={current() === n}
185
272
  aria-label={`${n} ${n === 1 ? 'star' : 'stars'}`}
186
- tabindex={-1}
273
+ tabindex={activeStar() === n ? 0 : -1}
187
274
  disabled={props.disabled}
188
- class="rounded p-0.5 text-muted-foreground hover:text-foreground"
189
- onClick={() => props.onInput(n)}
275
+ ref={(el) => refs.set(n, el)}
276
+ class="rounded p-0.5 text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
277
+ onKeyDown={onKey}
278
+ onClick={() => select(n)}
279
+ onBlur={props.onBlur}
190
280
  >
191
281
  <Star
192
282
  size={20}
@@ -200,47 +290,48 @@ export function RatingWidget(props: WidgetProps): JSX.Element {
200
290
  );
201
291
  }
202
292
 
293
+ /**
294
+ * The boolean field's switch IS `ui/switch.tsx` — it is not a lookalike.
295
+ *
296
+ * This used to hand-roll its own `<button role="switch">` at 44×24 while
297
+ * `<kai-switch>` shipped the same control at 36×20, so a consumer putting the two
298
+ * side by side saw two different switches; and its thumb used `bg-background`,
299
+ * re-introducing in the copy the dark-mode disappearing-thumb bug that
300
+ * `ui/switch.tsx:80` carries a comment about having fixed. Delegating removes both.
301
+ * The size convergence (44×24 → 36×20) is visible inside `kai-form` and intended.
302
+ *
303
+ * The four form-only hooks — `id`, `data-control` and the `aria-required` /
304
+ * `aria-invalid` / `aria-describedby` trio — are ordinary props now (plan decision
305
+ * D-7). They used to be stamped onto the button through `buttonRef` + a
306
+ * `createEffect`, which worked but meant this widget reached into the primitive's
307
+ * DOM to make `form.focusField()` land. `Switch` forwards anything it does not own
308
+ * to its inner button, exactly like `Checkbox` and `Radio` forward to their inputs,
309
+ * so all four ride the same path every other widget's hooks do.
310
+ */
203
311
  export function SwitchWidget(props: WidgetProps): JSX.Element {
204
312
  const on = () => props.value === true;
205
- const toggle = (): void => {
206
- if (props.disabled) return;
207
- props.onInput(!on());
208
- props.onBlur();
209
- };
210
313
  return (
211
- <button
314
+ <Switch
212
315
  id={props.id}
213
- data-control
214
- type="button"
215
- role="switch"
216
- aria-checked={on()}
217
- aria-label={props.label}
316
+ data-control=""
317
+ checked={on()}
218
318
  disabled={props.disabled}
219
- class={cn(
220
- 'relative inline-flex h-6 w-11 shrink-0 items-center rounded-full transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
221
- on() ? 'bg-[var(--color-primary)]' : 'bg-muted',
222
- )}
319
+ label={props.label}
223
320
  {...ariaProps(props)}
224
- onClick={toggle}
225
- >
226
- <span
227
- class={cn(
228
- 'inline-block h-5 w-5 transform rounded-full bg-background shadow transition-transform',
229
- on() ? 'translate-x-5' : 'translate-x-0.5',
230
- )}
231
- />
232
- </button>
321
+ onChange={(next) => {
322
+ props.onInput(next);
323
+ props.onBlur();
324
+ }}
325
+ />
233
326
  );
234
327
  }
235
328
 
236
329
  export function CheckboxWidget(props: WidgetProps): JSX.Element {
237
330
  return (
238
331
  <label class="-mx-1.5 inline-flex cursor-pointer items-center gap-2.5 rounded-md px-1.5 py-1.5 text-sm text-foreground transition-colors hover:bg-muted/60">
239
- <input
332
+ <Checkbox
240
333
  id={props.id}
241
- data-control
242
- type="checkbox"
243
- class="kai-checkbox"
334
+ data-control=""
244
335
  checked={props.value === true}
245
336
  disabled={props.disabled}
246
337
  {...ariaProps(props)}
@@ -255,70 +346,49 @@ export function CheckboxWidget(props: WidgetProps): JSX.Element {
255
346
  }
256
347
 
257
348
  export function RadioGroupWidget(props: WidgetProps): JSX.Element {
258
- const options = () => (props.field.enum ?? []) as unknown[];
349
+ // The row chrome, the `radiogroup` wrapper and the shared `name` all live in the
350
+ // primitive now; this widget only turns a JSON-Schema `enum` into options.
351
+ const options = createMemo(() =>
352
+ ((props.field.enum ?? []) as unknown[]).map((opt) => ({ value: opt, label: String(opt) })),
353
+ );
259
354
  return (
260
- <div
261
- role="radiogroup"
262
- aria-label={props.label}
263
- data-control
264
- class="divide-y divide-border overflow-hidden rounded-lg border border-input"
355
+ <RadioGroup<unknown>
356
+ data-control=""
357
+ {...groupNameProps(props)}
358
+ name={props.id}
359
+ options={options()}
360
+ value={props.value}
361
+ disabled={props.disabled}
265
362
  {...ariaProps(props)}
266
- >
267
- <For each={options()}>
268
- {(opt) => (
269
- <label
270
- class={cn(
271
- 'flex cursor-pointer items-center gap-3 px-3 py-2.5 text-sm transition-colors',
272
- props.value === opt
273
- ? 'bg-accent font-medium text-accent-foreground'
274
- : 'text-foreground hover:bg-muted/50',
275
- )}
276
- >
277
- <input
278
- type="radio"
279
- name={props.id}
280
- class="kai-radio"
281
- value={String(opt)}
282
- checked={props.value === opt}
283
- disabled={props.disabled}
284
- onChange={() => {
285
- props.onInput(opt);
286
- props.onBlur();
287
- }}
288
- />
289
- <span>{String(opt)}</span>
290
- </label>
291
- )}
292
- </For>
293
- </div>
363
+ onChange={(value) => {
364
+ props.onInput(value);
365
+ props.onBlur();
366
+ }}
367
+ />
294
368
  );
295
369
  }
296
370
 
297
371
  export function SelectWidget(props: WidgetProps): JSX.Element {
298
- const options = () => (props.field.enum ?? []) as unknown[];
372
+ // The primitive renders no placeholder row unless it is given one; the 'Select…'
373
+ // wording is this widget's, because a schema-driven field has nowhere else to get it.
374
+ const options = createMemo(() =>
375
+ ((props.field.enum ?? []) as unknown[]).map((opt) => ({ value: opt, label: String(opt) })),
376
+ );
299
377
  return (
300
- <select
378
+ <Select<unknown>
301
379
  id={props.id}
302
380
  data-control
303
- class={cn(inputBase, props.invalid && 'border-destructive dark:border-red-400/70')}
381
+ options={options()}
382
+ value={props.value}
383
+ placeholder={props.placeholder ?? 'Select…'}
384
+ invalid={props.invalid}
304
385
  disabled={props.disabled}
305
386
  {...ariaProps(props)}
306
387
  onChange={(e) => {
307
388
  props.onInput(e.currentTarget.value);
308
389
  props.onBlur();
309
390
  }}
310
- >
311
- <option value="" disabled selected={props.value === undefined || props.value === ''}>
312
- {props.placeholder ?? 'Select…'}
313
- </option>
314
- <For each={options()}>
315
- {(opt) => (
316
- <option value={String(opt)} selected={props.value === opt}>
317
- {String(opt)}
318
- </option>
319
- )}
320
- </For>
321
- </select>
391
+ />
322
392
  );
323
393
  }
324
394
 
@@ -328,81 +398,67 @@ function itemEnum(field: FormField): unknown[] {
328
398
  return [];
329
399
  }
330
400
 
331
- export function CheckboxGroupWidget(props: WidgetProps): JSX.Element {
332
- const selected = () => (Array.isArray(props.value) ? (props.value as unknown[]) : []);
333
- const toggle = (opt: unknown): void => {
334
- const set = selected();
335
- const next = set.includes(opt) ? set.filter((v) => v !== opt) : [...set, opt];
336
- props.onInput(next);
337
- props.onBlur();
338
- };
339
- return (
340
- <div
341
- role="group"
342
- aria-label={props.label}
343
- data-control
344
- class="divide-y divide-border overflow-hidden rounded-lg border border-input"
345
- {...ariaProps(props)}
346
- >
347
- <For each={itemEnum(props.field)}>
348
- {(opt) => (
349
- <label
350
- class={cn(
351
- 'flex cursor-pointer items-center gap-3 px-3 py-2.5 text-sm transition-colors',
352
- selected().includes(opt)
353
- ? 'bg-accent font-medium text-accent-foreground'
354
- : 'text-foreground hover:bg-muted/50',
355
- )}
356
- >
357
- <input
358
- type="checkbox"
359
- class="kai-checkbox"
360
- checked={selected().includes(opt)}
361
- disabled={props.disabled}
362
- onChange={() => toggle(opt)}
363
- />
364
- <span>{String(opt)}</span>
365
- </label>
366
- )}
367
- </For>
368
- </div>
369
- );
370
- }
371
-
372
- export function MultiSelectWidget(props: WidgetProps): JSX.Element {
401
+ /**
402
+ * An array field whose items are an `enum`, as a list of checkboxes.
403
+ *
404
+ * The bordered/divided row chrome, the `role="group"` wrapper and the rows themselves
405
+ * live in `ui/checkbox-group.tsx` now this widget only turns a JSON-Schema
406
+ * `items.enum` into options and owns the array in and out. It used to hand-roll the
407
+ * identical chrome beside `RadioGroup`, which already owned it.
408
+ *
409
+ * The emitted array holds the SCHEMA's values, not their string forms, so a numeric
410
+ * or boolean enum survives a round trip.
411
+ */
412
+ export function CheckboxGroupWidget(props: WidgetProps & { class?: string }): JSX.Element {
373
413
  const selected = () => (Array.isArray(props.value) ? (props.value as unknown[]) : []);
414
+ const options = createMemo(() => itemEnum(props.field).map((opt) => ({ value: opt, label: String(opt) })));
415
+ // `name={props.id}`: a shared name so a native form submits the whole selection under
416
+ // one key and `FormData.getAll()` reads it back. `kai-form` collects through its own
417
+ // store rather than a native submit, but the control participating correctly is free
418
+ // here and losing it would be a silent downgrade.
374
419
  return (
375
- <select
376
- id={props.id}
377
- data-control
378
- multiple
379
- aria-label={props.label}
380
- class={cn(inputBase, 'min-h-[6rem]', props.invalid && 'border-destructive dark:border-red-400/70')}
420
+ <CheckboxGroup<unknown>
421
+ data-control=""
422
+ {...groupNameProps(props)}
423
+ name={props.id}
424
+ class={props.class}
425
+ options={options()}
426
+ value={selected()}
381
427
  disabled={props.disabled}
382
428
  {...ariaProps(props)}
383
- onChange={(e) => {
384
- const vals = Array.from(e.currentTarget.selectedOptions).map((o) => o.value);
385
- props.onInput(vals);
429
+ onChange={(next) => {
430
+ props.onInput(next);
386
431
  props.onBlur();
387
432
  }}
388
- >
389
- <For each={itemEnum(props.field)}>
390
- {(opt) => (
391
- <option value={String(opt)} selected={selected().includes(opt)}>
392
- {String(opt)}
393
- </option>
394
- )}
395
- </For>
396
- </select>
433
+ />
397
434
  );
398
435
  }
399
436
 
437
+ /**
438
+ * The LONG version of `checkbox-group`: an array-of-enum field whose option list is
439
+ * over the row's `inlineMax`.
440
+ *
441
+ * This was a `<select multiple>` until decision D-3 was ruled. `<select multiple>` is a
442
+ * poor control on every platform — the multi-select affordance is invisible, discovering
443
+ * it means knowing to ctrl/cmd-click, and there is no touch story at all. It is the same
444
+ * control as `checkbox-group`, so it renders as one, with a scroll cap because the only
445
+ * thing that made a long list bearable in a chat card was the select's fixed-height box.
446
+ *
447
+ * The scroll cap is a presentation decision (how a long list fits), not a limit: every
448
+ * option is rendered, nothing is truncated or dropped.
449
+ */
450
+ export function MultiSelectWidget(props: WidgetProps): JSX.Element {
451
+ return <CheckboxGroupWidget {...props} class="max-h-60 overflow-y-auto" />;
452
+ }
453
+
400
454
  export function TagListWidget(props: WidgetProps): JSX.Element {
401
455
  const tags = () => (Array.isArray(props.value) ? (props.value as string[]) : []);
402
456
  const [draft, setDraft] = createSignal('');
403
457
  const add = (): void => {
404
458
  const v = draft().trim();
405
459
  if (!v) return;
460
+ // No cap on how many tags may be added. How many is too many lands in a policy
461
+ // document, which makes it the consuming application's call (CLAUDE.md, plan §4).
406
462
  props.onInput([...tags(), v]);
407
463
  setDraft('');
408
464
  props.onBlur();
@@ -412,36 +468,47 @@ export function TagListWidget(props: WidgetProps): JSX.Element {
412
468
  props.onBlur();
413
469
  };
414
470
  return (
415
- <div class="flex flex-col gap-2" role="group" aria-label={props.label}>
471
+ <div class="flex flex-col gap-2" role="group" {...groupNameProps(props)}>
416
472
  <div class="flex flex-wrap gap-1.5">
417
473
  <For each={tags()}>
418
474
  {(tag, i) => (
419
475
  <span class="inline-flex items-center gap-1 rounded-full bg-muted px-2 py-0.5 text-xs text-foreground">
420
476
  {tag}
421
- <button
477
+ {/* `Button`, not a bare `<button>`, and a lucide `X` rather than the
478
+ literal "✕" character it used to render. A bare glyph is what a screen
479
+ reader falls back to when nothing else names the control; the icon is
480
+ `aria-hidden` and the `aria-label` is the only name, so every remove
481
+ control announces which tag it removes. */}
482
+ <Button
422
483
  type="button"
484
+ variant="ghost"
485
+ size="icon-sm"
486
+ class="size-4 rounded-full p-0 text-muted-foreground hover:bg-transparent hover:text-foreground"
423
487
  aria-label={`Remove ${tag}`}
424
488
  disabled={props.disabled}
425
- class="text-muted-foreground hover:text-foreground"
426
489
  onClick={() => remove(i())}
427
490
  >
428
-
429
- </button>
491
+ <X size={12} aria-hidden="true" />
492
+ </Button>
430
493
  </span>
431
494
  )}
432
495
  </For>
433
496
  </div>
434
497
  <div class="flex items-center gap-2">
435
- <input
498
+ {/* `Input` rather than a raw `<input class={inputBase}>` (plan step 6). Same box,
499
+ same classes; what the bypass lost was the focus-node-reuse fix at
500
+ `ui/input.tsx:262-268` and the masking `Input` owns. The draft field is not a
501
+ form control of its own — it holds text on its way to becoming a tag — so it
502
+ carries the row's `id` and `data-control` and nothing else. */}
503
+ <Input
436
504
  id={props.id}
437
505
  data-control
438
506
  type="text"
439
- class={cn(inputBase)}
440
507
  value={draft()}
441
508
  placeholder={props.placeholder ?? 'Add…'}
442
509
  disabled={props.disabled}
443
510
  {...ariaProps(props)}
444
- onInput={(e) => setDraft(e.currentTarget.value)}
511
+ onValueInput={(value) => setDraft(value)}
445
512
  onKeyDown={(e) => {
446
513
  if (e.key === 'Enter') {
447
514
  e.preventDefault();
@@ -449,14 +516,9 @@ export function TagListWidget(props: WidgetProps): JSX.Element {
449
516
  }
450
517
  }}
451
518
  />
452
- <button
453
- type="button"
454
- class="rounded-md border border-input px-3 py-2 text-sm text-foreground hover:bg-muted disabled:opacity-50"
455
- disabled={props.disabled}
456
- onClick={add}
457
- >
519
+ <Button type="button" variant="outline" size="md" disabled={props.disabled} onClick={add}>
458
520
  Add
459
- </button>
521
+ </Button>
460
522
  </div>
461
523
  </div>
462
524
  );