@kitn.ai/ui 0.26.0 → 0.27.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 (474) hide show
  1. package/README.md +16 -5
  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-BnFKqqtY.js} +1 -1
  6. package/dist/action-icons-CBskfBMz.js +1 -0
  7. package/dist/arrow-down-Br0qtpsz.js +1 -0
  8. package/dist/arrow-left-Dj7xgE12.js +1 -0
  9. package/dist/artifact-CvXEm1lS.js +1 -0
  10. package/dist/assets/dev-5XzXYR21.js +91 -0
  11. package/dist/attachments-C43ZegEq.js +1 -0
  12. package/dist/{elements/chunks/audio-visualizer-D7lB6mvR.js → audio-visualizer-B-79y1Tj.js} +1 -1
  13. package/dist/badge-BpUbq8c0.js +1 -0
  14. package/dist/bash-InADTalH.js +1 -6
  15. package/dist/bell-sn17Ip9v.js +1 -0
  16. package/dist/{elements/chunks/button-flUbeufF.js → button-BH6rN0U-.js} +1 -1
  17. package/dist/card-renderer-Cb1gQmth.js +1 -0
  18. package/dist/card-routing-DifQgS7n.js +1 -0
  19. package/dist/check-DPeeMUYx.js +1 -0
  20. package/dist/checkbox-Dc9Yov_B.js +1 -0
  21. package/dist/checkbox-group-B1kDLLYk.js +1 -0
  22. package/dist/chevron-down-CHSWk7ZT.js +1 -0
  23. package/dist/chevron-right-B7l_fMgx.js +1 -0
  24. package/dist/choice-card-BT3c1S8g.js +1 -0
  25. package/dist/circle-Bb0yOpqM.js +1 -0
  26. package/dist/circle-check-BGZpCiz7.js +1 -0
  27. package/dist/circle-x-wobWkoSx.js +1 -0
  28. package/dist/{elements/chunks/cn-CU7UAWzk.js → cn-DN5AWfiS.js} +1 -1
  29. package/dist/{elements/chunks/code-block-BdLiTitd.js → code-block-CUQ5mtv0.js} +1 -1
  30. package/dist/{elements/chunks/collapsible-BzbXpPzr.js → collapsible-CPJbf6FM.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 +46 -1
  36. package/dist/components/conversation-item.d.ts +19 -7
  37. package/dist/components/conversation-list.d.ts +17 -5
  38. package/dist/components/form-widgets.d.ts +67 -2
  39. package/dist/components/form.d.ts +48 -1
  40. package/dist/components/message.d.ts +25 -3
  41. package/dist/components/reasoning.d.ts +6 -0
  42. package/dist/components/scroll-button.d.ts +11 -0
  43. package/dist/components/thread.d.ts +5 -0
  44. package/dist/components/tool-types.d.ts +9 -0
  45. package/dist/composer-CjngLzal.js +106 -0
  46. package/dist/confirm-card-DCx_A4bU.js +1 -0
  47. package/dist/construct-cli.es.js +1169 -0
  48. package/dist/{elements/chunks/context-VLIL3sJ-.js → context-BY8V-Nlp.js} +1 -1
  49. package/dist/controllable-D43BKmtX.js +1 -0
  50. package/dist/conversation-list-D4lLU_yS.js +1 -0
  51. package/dist/copy-B6DO4PqB.js +1 -0
  52. package/dist/core-AYMC6_lb.js +12 -5874
  53. package/dist/{create-tween-CQED4T0g.js → create-tween-BPzaefTp.js} +1 -1
  54. package/dist/{create-tween-BFT6c-Y8.js → create-tween-CnMHviKB.js} +1 -1
  55. package/dist/{create-tween-CJNlzoaO.js → create-tween-D1aiGZZh.js} +1 -1
  56. package/dist/{elements/chunks/create-tween-c4Z8TDYz.js → create-tween-DUSQUKpr.js} +2 -2
  57. package/dist/css-M7EaDHN_.js +1 -6
  58. package/dist/custom-elements.json +1400 -218
  59. package/dist/default-input-CucQEOqH.js +1 -0
  60. package/dist/define-CGGX-7Vg.js +1 -0
  61. package/dist/define.d.ts +4 -0
  62. package/dist/define.js +510 -0
  63. package/dist/define.server.d.ts +5 -0
  64. package/dist/define.server.js +506 -0
  65. package/dist/{elements/chunks/disclosure-CcNbyLy0.js → disclosure-CbLtajUQ.js} +1 -1
  66. package/dist/{elements/chunks/dropdown-BgWTthUx.js → dropdown-BwhViJFd.js} +1 -1
  67. package/dist/elements/agent-card.js +1 -1
  68. package/dist/elements/artifact.js +1 -1
  69. package/dist/elements/attachments.js +1 -1
  70. package/dist/elements/audio-visualizer.js +1 -1
  71. package/dist/elements/autoloader.js +1 -1
  72. package/dist/elements/avatar.js +1 -1
  73. package/dist/elements/badge.js +1 -1
  74. package/dist/elements/button.js +1 -1
  75. package/dist/elements/card.js +1 -1
  76. package/dist/elements/cards.js +1 -1
  77. package/dist/elements/chain-of-thought.js +1 -1
  78. package/dist/elements/chat-scope-picker.js +1 -1
  79. package/dist/elements/chat-workspace.js +1 -1
  80. package/dist/elements/chat.js +1 -1
  81. package/dist/elements/checkbox-group.d.ts +1 -0
  82. package/dist/elements/checkbox-group.js +1 -0
  83. package/dist/elements/checkbox.d.ts +1 -0
  84. package/dist/elements/checkbox.js +1 -0
  85. package/dist/elements/checkpoint.js +1 -1
  86. package/dist/elements/choice.js +1 -1
  87. package/dist/elements/coachmark.js +1 -1
  88. package/dist/elements/code-block.js +1 -1
  89. package/dist/elements/command.js +1 -1
  90. package/dist/elements/compare.js +1 -1
  91. package/dist/elements/composer.js +1 -1
  92. package/dist/elements/confirm-card.js +1 -1
  93. package/dist/elements/context-meter.js +1 -1
  94. package/dist/elements/conversation-item.js +1 -1
  95. package/dist/elements/conversation-list.js +1 -1
  96. package/dist/elements/define-entry.d.ts +12 -0
  97. package/dist/elements/dialog.js +1 -1
  98. package/dist/elements/dock.js +101 -7
  99. package/dist/elements/dropdown.js +1 -1
  100. package/dist/elements/editable-label.js +1 -1
  101. package/dist/elements/element-data-types.d.ts +37 -0
  102. package/dist/elements/embed.js +1 -1
  103. package/dist/elements/empty.js +1 -1
  104. package/dist/elements/feedback-bar.js +1 -1
  105. package/dist/elements/file-tree.js +1 -1
  106. package/dist/elements/file-upload.js +1 -1
  107. package/dist/elements/form.js +1 -1
  108. package/dist/elements/hover-card.js +1 -1
  109. package/dist/elements/icon.js +1 -1
  110. package/dist/elements/image.js +1 -1
  111. package/dist/elements/input.js +1 -1
  112. package/dist/elements/kbd.js +1 -1
  113. package/dist/elements/link-preview.js +1 -1
  114. package/dist/elements/loader.js +1 -1
  115. package/dist/elements/markdown.js +1 -1
  116. package/dist/elements/menu.js +1 -1
  117. package/dist/elements/message-skills.js +1 -1
  118. package/dist/elements/message.js +1 -1
  119. package/dist/elements/model-switcher.js +1 -1
  120. package/dist/elements/nav.js +1 -1
  121. package/dist/elements/notice.js +1 -1
  122. package/dist/elements/pane-grid.d.ts +1 -0
  123. package/dist/elements/pane-grid.js +1 -0
  124. package/dist/elements/pane-group.js +1 -1
  125. package/dist/elements/pane.js +1 -1
  126. package/dist/elements/popover.js +1 -1
  127. package/dist/elements/progress-bar.js +1 -1
  128. package/dist/elements/prompt-dock.js +1 -1
  129. package/dist/elements/prompt-input.js +1 -1
  130. package/dist/elements/prompt-suggestions.js +1 -1
  131. package/dist/elements/radio-group.d.ts +1 -0
  132. package/dist/elements/radio-group.js +1 -0
  133. package/dist/elements/reasoning.js +1 -1
  134. package/dist/elements/remote.js +1 -1
  135. package/dist/elements/resizable.js +1 -1
  136. package/dist/elements/response-stream.js +2 -2
  137. package/dist/elements/screen.js +1 -1
  138. package/dist/elements/scroll-area.js +1 -1
  139. package/dist/elements/scroll-button.js +1 -1
  140. package/dist/elements/search.js +1 -1
  141. package/dist/elements/segmented.js +1 -1
  142. package/dist/elements/select.d.ts +1 -0
  143. package/dist/elements/select.js +1 -0
  144. package/dist/elements/separator.js +1 -1
  145. package/dist/elements/setting-item.js +1 -1
  146. package/dist/elements/settings-group.js +1 -1
  147. package/dist/elements/skeleton.js +1 -1
  148. package/dist/elements/slider.d.ts +1 -0
  149. package/dist/elements/slider.js +1 -0
  150. package/dist/elements/slots.d.ts +1 -1
  151. package/dist/elements/source.js +1 -1
  152. package/dist/elements/status.js +1 -1
  153. package/dist/elements/switch.js +1 -1
  154. package/dist/elements/tabs.js +1 -1
  155. package/dist/elements/tasks.js +1 -1
  156. package/dist/elements/text-shimmer.js +1 -1
  157. package/dist/elements/thinking-bar.js +1 -1
  158. package/dist/elements/thread.js +1 -1
  159. package/dist/elements/toast.js +1 -1
  160. package/dist/elements/tool.js +1 -1
  161. package/dist/elements/tooltip.js +1 -1
  162. package/dist/elements/voice-input.js +1 -1
  163. package/dist/elements/voice-output.js +1 -1
  164. package/dist/elements.d.ts +362 -24
  165. package/dist/{elements/chunks/ellipsis-7gU33mL2.js → ellipsis-DFr4Vqr3.js} +1 -1
  166. package/dist/embed-5s_vU4J0.js +1 -0
  167. package/dist/engine-javascript-vq0WuIJl.js +14 -2516
  168. package/dist/{elements/chunks/external-link-CSOEsNlx.js → external-link-u9d85ORQ.js} +1 -1
  169. package/dist/file-text-CX7_x42o.js +1 -0
  170. package/dist/{elements/chunks/file-tree-zvB2i7P9.js → file-tree-D3kBjXI5.js} +1 -1
  171. package/dist/folder-V-YdIL8f.js +1 -0
  172. package/dist/form-BRNUMCN2.js +1 -0
  173. package/dist/github-dark-dimmed-DUshB20C.js +1 -4
  174. package/dist/github-light-JYsPkUQd.js +1 -4
  175. package/dist/hover-card-DaeiYrTA.js +1 -0
  176. package/dist/html-CPZ3oZQ7.js +1 -10
  177. package/dist/icon-BpixdZNq.js +1 -0
  178. package/dist/{elements/chunks/index-BCRhi84a.js → index-xu_pdcPi.js} +1 -1
  179. package/dist/index.d.ts +1 -1
  180. package/dist/index.js +7330 -5852
  181. package/dist/index.server.js +5823 -4759
  182. package/dist/info-8mFsIjS3.js +1 -0
  183. package/dist/input-d1Yuu-hz.js +1 -0
  184. package/dist/javascript-C25yR2R2.js +1 -6
  185. package/dist/json-DxJze_jm.js +1 -6
  186. package/dist/kai.es.js +1 -1
  187. package/dist/kbd-961zMPFB.js +1 -0
  188. package/dist/link-DlZZBGaR.js +1 -0
  189. package/dist/{elements/chunks/link-preview-OQ9IEq0f.js → link-preview-DIIbV2Sd.js} +1 -1
  190. package/dist/loader-YQszHFaW.js +1 -0
  191. package/dist/{elements/chunks/markdown-DgBFaGAz.js → markdown-CVk1MPsj.js} +1 -1
  192. package/dist/mcp.es.js +1285 -79
  193. package/dist/message-CwKVEdZg.js +1 -0
  194. package/dist/message-DS8lzHc1.js +1 -0
  195. package/dist/message-circle-Xf8Q-vZp.js +1 -0
  196. package/dist/message-square-Cxnwgkns.js +1 -0
  197. package/dist/{elements/chunks/minimize-2-DPIUKTrT.js → minimize-2-UEZmP2QT.js} +1 -1
  198. package/dist/{elements/chunks/model-switcher-DsT8tFB0.js → model-switcher-Kk1jEQ5t.js} +1 -1
  199. package/dist/{elements/chunks/overlay-DuiwdXjm.js → overlay-C1od6LOz.js} +1 -1
  200. package/dist/panel-right-ryET0yyn.js +1 -0
  201. package/dist/paperclip-BDNoZFJP.js +1 -0
  202. package/dist/play-OFIvYXUQ.js +1 -0
  203. package/dist/primitives/card-data-types.d.ts +49 -0
  204. package/dist/primitives/card-routing.d.ts +1 -33
  205. package/dist/primitives/card-validate.d.ts +2 -2
  206. package/dist/primitives/chat-config.d.ts +29 -2
  207. package/dist/primitives/field-mask.d.ts +96 -0
  208. package/dist/primitives/field-semantics.d.ts +36 -0
  209. package/dist/primitives/input-mask.d.ts +46 -0
  210. package/dist/primitives/url-scheme-policy.d.ts +34 -0
  211. package/dist/primitives/use-resize-observer.d.ts +2 -2
  212. package/dist/progress-bar-BvYX0aoy.js +1 -0
  213. package/dist/prompt-suggestion-BRFu2lcu.js +1 -0
  214. package/dist/radio-DcL23cNJ.js +1 -0
  215. package/dist/react/index.d.ts +193 -19
  216. package/dist/react.js +216 -180
  217. package/dist/reasoning-Cackq4Rh.js +1 -0
  218. package/dist/register-impl-C7rENvPK.js +424 -0
  219. package/dist/{elements/chunks/resizable-D181b4fM.js → resizable-Cp9HUYih.js} +1 -1
  220. package/dist/rotate-ccw-0DLRDHH5.js +1 -0
  221. package/dist/rotate-cw-DOB9IGcU.js +1 -0
  222. package/dist/schemas/form.schema.json +21 -1
  223. package/dist/schemas/tool-defs.d.ts +11 -1
  224. package/dist/schemas.js +81 -81
  225. package/dist/scroll-area-emVmWP4K.js +1 -0
  226. package/dist/scroll-button-CeP_gv1x.js +1 -0
  227. package/dist/select-CWmu00tS.js +1 -0
  228. package/dist/{elements/chunks/separator-CeJHQqLW.js → separator-CE6vly6z.js} +1 -1
  229. package/dist/{elements/chunks/settings-CqaHgJF9.js → settings-CE-78SvZ.js} +1 -1
  230. package/dist/{elements/chunks/settings-group-CjqjLCOn.js → settings-group-ZFWNxFQR.js} +1 -1
  231. package/dist/{elements/chunks/skeleton-D8akBnKS.js → skeleton-BebdP4mf.js} +1 -1
  232. package/dist/slider-BAKRYUlz.js +1 -0
  233. package/dist/{solid-BtdqNZI7.js → solid-BE5ui6sy.js} +8727 -7212
  234. package/dist/{solid-CnavwBye.js → solid-DFhsce3O.js} +7015 -5886
  235. package/dist/solid.d.ts +10 -0
  236. package/dist/solid.js +240 -234
  237. package/dist/solid.server.js +240 -234
  238. package/dist/source-B0FlguSx.js +1 -0
  239. package/dist/star-CBMZvLuN.js +1 -0
  240. package/dist/state/index.d.ts +1 -1
  241. package/dist/state/mock.d.ts +32 -2
  242. package/dist/state.js +133 -108
  243. package/dist/{elements/chunks/store-Cjq0qwYN.js → store-7eoadydQ.js} +1 -1
  244. package/dist/svelte--5p79yCD.js +1 -15
  245. package/dist/switch-CViARZCy.js +1 -0
  246. package/dist/tasks-card-MjCcOhR_.js +1 -0
  247. package/dist/text-shimmer-DKlwRF2G.js +1 -0
  248. package/dist/textarea-3ay2Me2-.js +1 -0
  249. package/dist/theme.tokens.css +82 -13
  250. package/dist/{elements/chunks/thumbs-up-DX2Ijk3H.js → thumbs-up-DQcu2HMO.js} +1 -1
  251. package/dist/toast-store-C6yKOhbk.js +1 -0
  252. package/dist/{elements/chunks/tool-DKQ2D1DP.js → tool-BCYNlfKy.js} +1 -1
  253. package/dist/{elements/chunks/tooltip-B0G99y8w.js → tooltip-05zI4hjs.js} +1 -1
  254. package/dist/trash-2-BR1RxGky.js +1 -0
  255. package/dist/{elements/chunks/triangle-alert-F9cyUEyy.js → triangle-alert-DHohDOCy.js} +1 -1
  256. package/dist/tsx-B8rCNbgL.js +1 -6
  257. package/dist/typescript-RycA9KXf.js +1 -6
  258. package/dist/ui/checkbox-group.d.ts +83 -0
  259. package/dist/ui/checkbox.d.ts +40 -0
  260. package/dist/ui/dock.d.ts +22 -0
  261. package/dist/ui/icon.d.ts +3 -0
  262. package/dist/ui/input.d.ts +48 -2
  263. package/dist/ui/radio.d.ts +85 -0
  264. package/dist/ui/select.d.ts +71 -0
  265. package/dist/ui/slider.d.ts +65 -0
  266. package/dist/ui/switch.d.ts +9 -2
  267. package/dist/upload-BbGW6A_u.js +1 -0
  268. package/dist/url-scheme-policy-DHpoTJwB.js +1 -0
  269. package/dist/use-card-resolution-CJW4grFa.js +1 -0
  270. package/dist/{variant-aurora-B8II3-I1.js → variant-aurora-BUOVgPOB.js} +2 -2
  271. package/dist/{variant-aurora-CMNWOSJR.js → variant-aurora-CUsUJvFI.js} +2 -2
  272. package/dist/{variant-aurora-CcuDMWwQ.js → variant-aurora-CW2i4bfr.js} +1 -1
  273. package/dist/{elements/chunks/variant-aurora-qt6Zct8M.js → variant-aurora-xiMPty8f.js} +1 -1
  274. package/dist/{variant-custom-L16JLLZy.js → variant-custom-BQtOnmeO.js} +1 -1
  275. package/dist/{variant-custom-C714mqKl.js → variant-custom-CAm9L-E5.js} +2 -2
  276. package/dist/{variant-custom-BdvYC7af.js → variant-custom-CAnzzppW.js} +2 -2
  277. package/dist/{elements/chunks/variant-custom-tJp22_BQ.js → variant-custom-D91HjAKz.js} +1 -1
  278. package/dist/{variant-wave-ecwykeyL.js → variant-wave-Ca1Ls4_y.js} +2 -2
  279. package/dist/{variant-wave-D7ruiA1W.js → variant-wave-CxX39DKd.js} +2 -2
  280. package/dist/{elements/chunks/variant-wave-CaQ5ti39.js → variant-wave-DYyzIIZv.js} +1 -1
  281. package/dist/{variant-wave-2S4La2Ka.js → variant-wave-uY5crU_K.js} +1 -1
  282. package/dist/vue-BmIZj4XD.js +1 -33
  283. package/dist/x-C5m1JFgi.js +1 -0
  284. package/frameworks/react/index.tsx +198 -20
  285. package/llms-full.txt +1178 -21
  286. package/llms.txt +6 -3
  287. package/package.json +17 -4
  288. package/src/agent-tooling/catalog/invariants.ts +1 -1
  289. package/src/agent-tooling/catalog/surfaces.ts +72 -0
  290. package/src/agent-tooling/construct/cli-entry.ts +8 -0
  291. package/src/agent-tooling/construct/cli.ts +131 -0
  292. package/src/agent-tooling/construct/codegen.ts +1353 -0
  293. package/src/agent-tooling/construct/construct.v1.schema.json +250 -0
  294. package/src/agent-tooling/construct/dev.ts +148 -0
  295. package/src/agent-tooling/construct/fixtures/demo-widget.construct.json +7 -0
  296. package/src/agent-tooling/construct/fixtures/ops-console.construct.json +44 -0
  297. package/src/agent-tooling/construct/fixtures/owner-widget.construct.json +23 -0
  298. package/src/agent-tooling/construct/schema.ts +371 -0
  299. package/src/agent-tooling/mcp/server.ts +2 -1
  300. package/src/agent-tooling/mcp/tools/construct.ts +130 -0
  301. package/src/agent-tooling/mcp/tools/debug.ts +126 -4
  302. package/src/agent-tooling/mcp/tools/reference.ts +104 -3
  303. package/src/agent-tooling/recipes/composed-thread.ts +714 -0
  304. package/src/agent-tooling/recipes/index.ts +22 -0
  305. package/src/agent-tooling/recipes/types.ts +32 -0
  306. package/src/agent-tooling/registry.ts +5 -0
  307. package/src/components/attachment-types.ts +7 -0
  308. package/src/components/audio-visualizer/fit-scale.ts +1 -1
  309. package/src/components/card-renderer.tsx +39 -8
  310. package/src/components/card.tsx +10 -2
  311. package/src/components/chat-scope-picker.tsx +2 -2
  312. package/src/components/chat-thread.tsx +72 -9
  313. package/src/components/choice-card.tsx +102 -37
  314. package/src/components/coachmark.tsx +1 -1
  315. package/src/components/code-block.tsx +2 -2
  316. package/src/components/composer.tsx +89 -15
  317. package/src/components/confirm-card.tsx +20 -4
  318. package/src/components/conversation-item.tsx +33 -9
  319. package/src/components/conversation-list.tsx +25 -10
  320. package/src/components/empty.tsx +14 -3
  321. package/src/components/file-tree.tsx +5 -1
  322. package/src/components/form-widgets.tsx +257 -195
  323. package/src/components/form.tsx +297 -9
  324. package/src/components/loader.tsx +12 -5
  325. package/src/components/message-skills.tsx +1 -1
  326. package/src/components/message.tsx +71 -11
  327. package/src/components/prompt-input.tsx +7 -1
  328. package/src/components/prompt-suggestion.tsx +4 -1
  329. package/src/components/reasoning.tsx +30 -3
  330. package/src/components/response-compare.tsx +2 -2
  331. package/src/components/scroll-button.tsx +56 -4
  332. package/src/components/source.tsx +6 -1
  333. package/src/components/tasks-card.tsx +58 -16
  334. package/src/components/thread.tsx +10 -1
  335. package/src/components/toast.tsx +1 -1
  336. package/src/components/tool-types.ts +9 -0
  337. package/src/elements/attachments.tsx +3 -1
  338. package/src/elements/chat-workspace.tsx +1 -1
  339. package/src/elements/chat.tsx +22 -4
  340. package/src/elements/checkbox-group.tsx +164 -0
  341. package/src/elements/checkbox.tsx +134 -0
  342. package/src/elements/compiled.css +1 -1
  343. package/src/elements/composer.tsx +12 -1
  344. package/src/elements/conversation-item.tsx +62 -22
  345. package/src/elements/conversation-list.tsx +2 -2
  346. package/src/elements/define-entry.ts +12 -0
  347. package/src/elements/dock.tsx +11 -0
  348. package/src/elements/element-data-types.ts +40 -0
  349. package/src/elements/element-manifest.json +24 -0
  350. package/src/elements/element-meta.json +736 -46
  351. package/src/elements/element-nonscalar.json +15 -0
  352. package/src/elements/element-types.d.ts +362 -24
  353. package/src/elements/feedback-bar.tsx +5 -0
  354. package/src/elements/icon-names.json +29 -0
  355. package/src/elements/input.tsx +312 -23
  356. package/src/elements/message.tsx +2 -0
  357. package/src/elements/pane-grid.tsx +111 -0
  358. package/src/elements/prompt-input.tsx +8 -2
  359. package/src/elements/radio-group.tsx +129 -0
  360. package/src/elements/register-impl.ts +6 -0
  361. package/src/elements/scroll-button.tsx +54 -6
  362. package/src/elements/select.tsx +165 -0
  363. package/src/elements/slider.tsx +171 -0
  364. package/src/elements/slots.ts +1 -1
  365. package/src/elements/styles.css +89 -3
  366. package/src/elements/thread.tsx +2 -0
  367. package/src/elements/toast.tsx +14 -1
  368. package/src/index.ts +2 -1
  369. package/src/primitives/card-data-types.ts +49 -0
  370. package/src/primitives/card-host.tsx +4 -2
  371. package/src/primitives/card-routing.ts +7 -56
  372. package/src/primitives/card-schemas/form.schema.json +21 -1
  373. package/src/primitives/card-validate.ts +10 -2
  374. package/src/primitives/chat-config.tsx +33 -6
  375. package/src/primitives/field-mask.ts +256 -0
  376. package/src/primitives/field-semantics.ts +115 -0
  377. package/src/primitives/input-mask.ts +853 -0
  378. package/src/primitives/toast-store.ts +66 -4
  379. package/src/primitives/url-scheme-policy.ts +70 -0
  380. package/src/primitives/use-resize-observer.ts +2 -2
  381. package/src/schemas/tool-defs.ts +12 -2
  382. package/src/solid.ts +10 -0
  383. package/src/state/index.ts +1 -1
  384. package/src/state/mock.ts +92 -6
  385. package/src/ui/agent-card.tsx +2 -2
  386. package/src/ui/avatar.tsx +1 -1
  387. package/src/ui/checkbox-group.tsx +153 -0
  388. package/src/ui/checkbox.tsx +63 -0
  389. package/src/ui/dock.tsx +145 -6
  390. package/src/ui/icon.tsx +53 -0
  391. package/src/ui/input.tsx +249 -9
  392. package/src/ui/kbd.tsx +1 -1
  393. package/src/ui/nav.tsx +1 -1
  394. package/src/ui/pane-group.tsx +2 -2
  395. package/src/ui/radio.tsx +150 -0
  396. package/src/ui/select.tsx +168 -0
  397. package/src/ui/slider.tsx +178 -0
  398. package/src/ui/switch.tsx +49 -18
  399. package/src/utils/cn.ts +11 -5
  400. package/theme.css +84 -13
  401. package/dist/elements/chunks/action-icons-D9u3OzYW.js +0 -1
  402. package/dist/elements/chunks/arrow-left-CVh1TeN7.js +0 -1
  403. package/dist/elements/chunks/artifact-BkYcfIlZ.js +0 -1
  404. package/dist/elements/chunks/attachments-DwfNlubq.js +0 -1
  405. package/dist/elements/chunks/badge-Q0-Cmwzz.js +0 -1
  406. package/dist/elements/chunks/bash-InADTalH.js +0 -1
  407. package/dist/elements/chunks/card-renderer-3Dyl-FpN.js +0 -1
  408. package/dist/elements/chunks/card-routing-BK09BdmL.js +0 -1
  409. package/dist/elements/chunks/check-B5kBPKeo.js +0 -1
  410. package/dist/elements/chunks/chevron-down-DZ1GS39h.js +0 -1
  411. package/dist/elements/chunks/chevron-right-CvP7OUd2.js +0 -1
  412. package/dist/elements/chunks/choice-card-D4QUIOWZ.js +0 -1
  413. package/dist/elements/chunks/circle-CDf9g6MQ.js +0 -1
  414. package/dist/elements/chunks/circle-check-CA47Nqbz.js +0 -1
  415. package/dist/elements/chunks/circle-x-CJ_HVbC8.js +0 -1
  416. package/dist/elements/chunks/composer-Byve-n8K.js +0 -69
  417. package/dist/elements/chunks/confirm-card-B0flE3MN.js +0 -1
  418. package/dist/elements/chunks/controllable-DTwTIlwp.js +0 -1
  419. package/dist/elements/chunks/conversation-item-BDztezOG.js +0 -1
  420. package/dist/elements/chunks/copy-Nf-kqxXH.js +0 -1
  421. package/dist/elements/chunks/core-AYMC6_lb.js +0 -12
  422. package/dist/elements/chunks/css-M7EaDHN_.js +0 -1
  423. package/dist/elements/chunks/default-input-C2iLFq0b.js +0 -1
  424. package/dist/elements/chunks/define-wjHR1MkU.js +0 -1
  425. package/dist/elements/chunks/download-C4V7tBdb.js +0 -1
  426. package/dist/elements/chunks/embed-D3KewsIJ.js +0 -1
  427. package/dist/elements/chunks/engine-javascript-vq0WuIJl.js +0 -141
  428. package/dist/elements/chunks/file-text-D1G8khle.js +0 -1
  429. package/dist/elements/chunks/folder-WW2LGLid.js +0 -1
  430. package/dist/elements/chunks/form-CQirdC4W.js +0 -1
  431. package/dist/elements/chunks/github-dark-dimmed-DUshB20C.js +0 -1
  432. package/dist/elements/chunks/github-light-JYsPkUQd.js +0 -1
  433. package/dist/elements/chunks/hover-card-CJJkfHTK.js +0 -1
  434. package/dist/elements/chunks/html-CPZ3oZQ7.js +0 -1
  435. package/dist/elements/chunks/icon-Cfgmf4fa.js +0 -1
  436. package/dist/elements/chunks/info-DX3YMOt6.js +0 -1
  437. package/dist/elements/chunks/input-BtW9U6Sq.js +0 -1
  438. package/dist/elements/chunks/javascript-C25yR2R2.js +0 -1
  439. package/dist/elements/chunks/json-DxJze_jm.js +0 -1
  440. package/dist/elements/chunks/kbd-LGXwuT_V.js +0 -1
  441. package/dist/elements/chunks/link-2My69BqT.js +0 -1
  442. package/dist/elements/chunks/loader-iOxc_Tu-.js +0 -1
  443. package/dist/elements/chunks/message-VMZohrXx.js +0 -1
  444. package/dist/elements/chunks/message-circle-CpXMM-_n.js +0 -1
  445. package/dist/elements/chunks/message-square-DxcWtETQ.js +0 -1
  446. package/dist/elements/chunks/message-tZmV2A3p.js +0 -1
  447. package/dist/elements/chunks/paperclip-CRZVp4z_.js +0 -1
  448. package/dist/elements/chunks/progress-bar-DNy66e_E.js +0 -1
  449. package/dist/elements/chunks/prompt-suggestion-Bn64Nl7L.js +0 -1
  450. package/dist/elements/chunks/reasoning-Emn9N4-T.js +0 -1
  451. package/dist/elements/chunks/rotate-cw-CPWe37D9.js +0 -1
  452. package/dist/elements/chunks/scroll-area-ClZkZxIk.js +0 -1
  453. package/dist/elements/chunks/scroll-button-DPE-t3x_.js +0 -1
  454. package/dist/elements/chunks/share-CyfnNEqp.js +0 -1
  455. package/dist/elements/chunks/source-UWCvmVak.js +0 -1
  456. package/dist/elements/chunks/star-DoHFAikQ.js +0 -1
  457. package/dist/elements/chunks/svelte--5p79yCD.js +0 -1
  458. package/dist/elements/chunks/tasks-card-CmZ5xh6v.js +0 -1
  459. package/dist/elements/chunks/text-shimmer-DYn09zrz.js +0 -1
  460. package/dist/elements/chunks/textarea-skiGHURM.js +0 -1
  461. package/dist/elements/chunks/toast-store-CFnQpThX.js +0 -1
  462. package/dist/elements/chunks/tsx-B8rCNbgL.js +0 -1
  463. package/dist/elements/chunks/typescript-RycA9KXf.js +0 -1
  464. package/dist/elements/chunks/use-card-resolution-DoA2f_EU.js +0 -1
  465. package/dist/elements/chunks/vue-BmIZj4XD.js +0 -1
  466. package/dist/elements/chunks/x-B6UnOiLw.js +0 -1
  467. package/dist/llms/llms-full.txt +0 -2949
  468. package/dist/llms/llms.txt +0 -156
  469. package/dist/register-impl-DpE7icIb.js +0 -293
  470. package/dist/ui/stat.d.ts +0 -16
  471. package/src/ui/stat.tsx +0 -41
  472. /package/dist/{elements/chunks/card-tags-D8lZ-C_U.js → card-tags-D8lZ-C_U.js} +0 -0
  473. /package/dist/{elements/chunks/link-preview-DNILK391.js → link-preview-DNILK391.js} +0 -0
  474. /package/dist/{elements/chunks/slots-DaNjX0rc.js → slots-DaNjX0rc.js} +0 -0
@@ -22,6 +22,8 @@ import {
22
22
  } from '../primitives/card-validate';
23
23
  import type { CardEnvelope, CardEvent, CardHost, CardResolution } from '../primitives/card-contract';
24
24
  import { emitCardEvent } from '../primitives/card-routing';
25
+ import { compileMask, formatForDisplay, formatRaw, normalizeToRaw } from '../primitives/field-mask';
26
+ import { FIELD_SEMANTIC_TYPES, fieldSemantics, type FieldSemanticType } from '../primitives/field-semantics';
25
27
  import { useCardHost } from '../primitives/card-host';
26
28
  import { useCardResolution } from './use-card-resolution';
27
29
  import { Check } from 'lucide-solid';
@@ -167,6 +169,233 @@ export function widgetFor(field: FormField, inlineMax: number): WidgetKind {
167
169
  }
168
170
  }
169
171
 
172
+ // ─────────────────────────────────────────────────────────────────────────────
173
+ // Field formats (spec §7.3) — the model-facing half of masked fields.
174
+ // ─────────────────────────────────────────────────────────────────────────────
175
+
176
+ /**
177
+ * What one field's `x-kai-*` format hints resolve to.
178
+ *
179
+ * The three props on the left are `Input`'s masking surface verbatim;
180
+ * `hint` is the TEXT statement of the expected format, which `FieldRow` renders and
181
+ * links through its EXISTING `aria-describedby` chain (no third channel).
182
+ * Every field is `undefined` when the field carries no hints, which is what "byte for
183
+ * byte the behavior of today" means here.
184
+ */
185
+ export interface FieldMaskHint {
186
+ /** The compiled-clean mask pattern handed to `Input`'s `format`. */
187
+ format?: string;
188
+ /** An explicit, aligned display guide. Absent = the pattern derives its own. */
189
+ guide?: string;
190
+ /** The tier-1 semantic type: `inputmode`, `autocomplete`, and the canonical form. */
191
+ semantic?: FieldSemanticType;
192
+ /** The expected format, stated in text for the description chain. */
193
+ hint?: string;
194
+ /**
195
+ * Everything that was ignored and why, as DATA rather than as a side effect.
196
+ *
197
+ * Resolution runs inside a memo on a render path, so it must never throw and must
198
+ * not warn twice for the same reason on a re-render. Returning the messages lets
199
+ * `FieldRow` own the once-per-row discipline and lets the pure tests assert the
200
+ * wording without a console spy.
201
+ */
202
+ warnings: string[];
203
+ }
204
+
205
+ /** The resolution of a field with no format hints: shared, frozen, and the default
206
+ * every non-text widget carries so the prop bag has one shape. */
207
+ export const EMPTY_MASK_HINT: FieldMaskHint = Object.freeze({ warnings: [] as string[] });
208
+
209
+ /**
210
+ * Messages already printed for a given field DEFINITION.
211
+ *
212
+ * Keyed on the field object, not on the row, and that is the fix rather than the
213
+ * convenience. A per-row `Set` looks right and does not work: `FieldRow`'s body runs
214
+ * TWICE for one visible row (measured — the parent rebuilds the `<For>` once at mount),
215
+ * so each build got its own empty Set and every bad hint printed twice. The reviewer
216
+ * caught that because the tests asserted `toHaveBeenCalled()` and never a count; they
217
+ * assert `toHaveBeenCalledTimes` now.
218
+ *
219
+ * A `WeakMap` because the key is model output: it must not pin a discarded card's field
220
+ * definitions in memory, and it must not dedupe across cards either. The repo's
221
+ * reactivity contract makes the identity exactly right — an edited field arrives as a
222
+ * NEW object (CLAUDE.md, the `kai-` contract), so a changed hint warns again, while the
223
+ * same definition rendered twice warns once.
224
+ */
225
+ const WARNED_FIELDS = new WeakMap<FormField, Set<string>>();
226
+
227
+ function warnOncePerField(field: FormField, message: string): void {
228
+ let seen = WARNED_FIELDS.get(field);
229
+ if (seen === undefined) {
230
+ seen = new Set<string>();
231
+ WARNED_FIELDS.set(field, seen);
232
+ }
233
+ if (seen.has(message)) return;
234
+ seen.add(message);
235
+ // eslint-disable-next-line no-console
236
+ console.warn(message);
237
+ }
238
+
239
+ /** Clip a model-supplied string before it reaches a console warning. Not a security
240
+ * boundary — the console is not a sink — but a 500-character pattern printing itself
241
+ * on every render is noise that hides the sentence that matters (M4 precedent). */
242
+ function clipForWarning(text: string): string {
243
+ return text.length <= 32 ? text : `${text.slice(0, 32)}… (${text.length} chars)`;
244
+ }
245
+
246
+ const SEMANTIC_TOKENS: readonly string[] = FIELD_SEMANTIC_TYPES;
247
+
248
+ /**
249
+ * Resolve a field's `x-kai-format` / `x-kai-mask` / `x-kai-mask-guide` into the
250
+ * masking props `Input` takes, or into nothing at all.
251
+ *
252
+ * EVERY INPUT HERE IS MODEL OUTPUT and therefore untrusted. The hints are
253
+ * display-only — they produce text and caret positions, never HTML, a URL or an
254
+ * attribute on a navigable element — so this is about denial of service and
255
+ * confusion, not injection: an unknown token, a pattern the engine refuses and a
256
+ * misaligned guide each degrade to the largest thing that still works, and say so.
257
+ * A THROW here would take out the whole card, including the fields that were fine.
258
+ */
259
+ export function resolveFieldMask(field: FormField, fieldKey = ''): FieldMaskHint {
260
+ const declared: unknown = field['x-kai-format'];
261
+ const mask: unknown = field['x-kai-mask'];
262
+ const guideIn: unknown = field['x-kai-mask-guide'];
263
+ const where = fieldKey ? ` on field "${clipForWarning(fieldKey)}"` : '';
264
+
265
+ if (declared === undefined) {
266
+ // A pattern with nothing to attach it to. Silence here would leave a model
267
+ // convinced it had asked for a mask and a developer looking at a plain field.
268
+ if (typeof mask === 'string' || typeof guideIn === 'string') {
269
+ return {
270
+ warnings: [
271
+ `kai-form: x-kai-mask${where} has no x-kai-format and is ignored. ` +
272
+ `Set "x-kai-format": "custom" to mask with it.`,
273
+ ],
274
+ };
275
+ }
276
+ return { warnings: [] };
277
+ }
278
+
279
+ if (typeof declared !== 'string' || !SEMANTIC_TOKENS.includes(declared)) {
280
+ return {
281
+ warnings: [
282
+ `kai-form: x-kai-format "${clipForWarning(String(declared))}"${where} is not one of ` +
283
+ `${SEMANTIC_TOKENS.join(', ')}. Rendering an unmasked text field.`,
284
+ ],
285
+ };
286
+ }
287
+
288
+ const semantic = declared as FieldSemanticType;
289
+ const warnings: string[] = [];
290
+ const custom = semantic === 'custom';
291
+
292
+ if (!custom && typeof mask === 'string') {
293
+ warnings.push(
294
+ `kai-form: x-kai-mask${where} is read only with x-kai-format "custom"; ` +
295
+ `"${semantic}" brings its own format. The mask is ignored.`,
296
+ );
297
+ }
298
+
299
+ const pattern = custom ? mask : fieldSemantics(semantic).defaultFormat;
300
+ if (typeof pattern !== 'string' || pattern === '') {
301
+ warnings.push(
302
+ `kai-form: x-kai-format "custom"${where} needs an x-kai-mask pattern. ` +
303
+ `Rendering an unmasked text field.`,
304
+ );
305
+ return { warnings };
306
+ }
307
+
308
+ // Compile the PATTERN first, alone. Compiling it with a bad guide would report the
309
+ // guide's failure and the pattern's as one event, and the two have different
310
+ // fallbacks: a refused pattern means no mask at all, a refused guide means this
311
+ // mask with its own derived guide.
312
+ let compiled;
313
+ try {
314
+ compiled = compileMask(pattern);
315
+ } catch (err) {
316
+ warnings.push(
317
+ `kai-form: x-kai-mask "${clipForWarning(pattern)}"${where} did not compile ` +
318
+ `(${err instanceof Error ? err.message : String(err)}). Rendering an unmasked text field.`,
319
+ );
320
+ return { warnings };
321
+ }
322
+
323
+ // A pattern with no `#`/`@`/`*` COMPILES — unknown characters are literals by
324
+ // position, which is the rule that makes `V-***` work and is not negotiable. But it
325
+ // compiles to capacity 0: a field that accepts nothing, shows nothing and submits
326
+ // nothing. Left alone that is the quietest failure in the whole feature, and the one
327
+ // a model is likeliest to reach — `mm/dd/yyyy` and `CHG-1234` are the strings this
328
+ // schema's own descriptions put in front of it, one free-form key away from where
329
+ // they belong. So it is refused here, out loud, on the same path as a pattern the
330
+ // engine rejects outright. `compileMask` cannot make this call for both callers: a
331
+ // zero-capacity pattern is a legitimate compile, it is only a nonsense FIELD.
332
+ if (compiled.capacity === 0) {
333
+ warnings.push(
334
+ `kai-form: x-kai-mask "${clipForWarning(pattern)}"${where} has no fill positions, so nothing ` +
335
+ `could ever be typed into it. Use # for a digit, @ for a letter or digit, * for an ` +
336
+ `obscurable one (e.g. "##/##/####", with "mm/dd/yyyy" as x-kai-mask-guide). ` +
337
+ `Rendering an unmasked text field.`,
338
+ );
339
+ return { warnings };
340
+ }
341
+
342
+ let guide = typeof guideIn === 'string' ? guideIn : undefined;
343
+ if (guide !== undefined) {
344
+ try {
345
+ compileMask(pattern, guide);
346
+ } catch (err) {
347
+ warnings.push(
348
+ `kai-form: x-kai-mask-guide "${clipForWarning(guide)}"${where} does not align with the ` +
349
+ `pattern (${err instanceof Error ? err.message : String(err)}). Using the pattern's own guide.`,
350
+ );
351
+ guide = undefined;
352
+ }
353
+ }
354
+
355
+ return {
356
+ format: pattern,
357
+ guide,
358
+ semantic,
359
+ // The guide when there is one (`mm/dd/yyyy` reads as a format to a person); the
360
+ // pattern otherwise, because a DERIVED guide is spaces at every fill position and
361
+ // says nothing out loud. A visual guide is not a description either way (spec §6).
362
+ hint: `Format: ${guide ?? pattern}`,
363
+ warnings,
364
+ };
365
+ }
366
+
367
+ /**
368
+ * The text a masked control SHOWS for a stored (canonical) value.
369
+ *
370
+ * The store holds one value per field and it is the canonical one: digits for
371
+ * `tel`/`ssn`/`credit-card`. That value is also what flows back into the control as its
372
+ * `value` prop — and writing `5550101234` over a field the masker just wrote
373
+ * `555-010-1234` into un-formats it one keystroke behind the user, with the caret
374
+ * jumping to the end. `<kai-input>` hit exactly this and split display from canonical;
375
+ * this is the same split, one layer up, for the Solid `Form`.
376
+ *
377
+ * Re-formatting through the SAME pure engine means the string handed back is the string
378
+ * already in the field, which the HTML value setter treats as a no-op (caret included).
379
+ *
380
+ * THE GUIDE BRANCH IS NOT A CHOICE MADE HERE — it mirrors `display()` in
381
+ * `primitives/input-mask.ts`: with an explicit guide the field is always the pattern's
382
+ * full length, without one it shows up to the last typed character. Disagreeing with the
383
+ * masker about that would put the fight straight back.
384
+ */
385
+ function maskedDisplay(hint: FieldMaskHint, value: unknown): unknown {
386
+ if (hint.format === undefined || typeof value !== 'string' || value === '') return value;
387
+ try {
388
+ const pattern = compileMask(hint.format, hint.guide);
389
+ const raw = normalizeToRaw(pattern, value);
390
+ return hint.guide === undefined ? formatRaw(pattern, raw) : formatForDisplay(pattern, raw);
391
+ } catch {
392
+ // Unreachable in practice — `resolveFieldMask` only reports a `format` that already
393
+ // compiled — but this runs on a render path, so it degrades to the stored text
394
+ // rather than taking the card down if that ever stops being true.
395
+ return value;
396
+ }
397
+ }
398
+
170
399
  /** Humanize a camelCase / snake_case property key into a label. */
171
400
  export function humanize(key: string): string {
172
401
  const spaced = key
@@ -626,6 +855,13 @@ export function Form(props: FormProps): JSX.Element {
626
855
  >
627
856
  <div class="flex w-full flex-wrap items-center justify-between gap-2">
628
857
  <Show when={dismissible()}>
858
+ {/* The contract `dismiss` verb — a footer ACTION (it emits
859
+ `{kind:'dismiss'}` and collapses the card to a re-openable
860
+ stub), not the chrome close in `Card`'s own `dismissible`
861
+ prop, which the contract cards deliberately never set.
862
+ confirm/choice/tasks were unlabelled ghost ✕ icons and now
863
+ match this one, so all four dismiss controls are the same
864
+ labelled ghost button. */}
629
865
  <Button
630
866
  type="button"
631
867
  variant="ghost"
@@ -738,27 +974,59 @@ interface FieldRowProps {
738
974
 
739
975
  function FieldRow(props: FieldRowProps): JSX.Element {
740
976
  const id = `f-${props.fieldKey}-${Math.random().toString(36).slice(2, 8)}`;
977
+ const labelId = `${id}-label`;
741
978
  const errorId = `${id}-err`;
742
979
  const descId = `${id}-desc`;
980
+ const formatId = `${id}-fmt`;
743
981
  const label = () => props.field.title ?? humanize(props.fieldKey);
744
982
  const widget = createMemo(() => widgetFor(props.field, props.inlineMax));
745
983
  const placeholder = () => props.field['x-kai-placeholder'];
984
+
985
+ // Field formats (spec §7.3). A memo, so a keystroke does not re-resolve: it reads
986
+ // `props.field` and nothing else, which is the subscription discipline the rest of
987
+ // this row is built on (K-D12b).
988
+ const maskHint = createMemo(() => {
989
+ const resolved = resolveFieldMask(props.field, props.fieldKey);
990
+ for (const message of resolved.warnings) warnOncePerField(props.field, message);
991
+ return resolved;
992
+ });
993
+
746
994
  const describedBy = () =>
747
- [props.field.description ? descId : '', props.error() ? errorId : '']
995
+ [
996
+ props.field.description ? descId : '',
997
+ maskHint().hint ? formatId : '',
998
+ props.error() ? errorId : '',
999
+ ]
748
1000
  .filter(Boolean)
749
1001
  .join(' ') || undefined;
750
1002
 
751
- const common = fieldCommon(props, id, placeholder, label, describedBy);
1003
+ // `fieldset` and `repeater` render a REAL <fieldset><legend> holding this same text,
1004
+ // so the row's own label would print it twice. They are the only two kinds that
1005
+ // supply their own VISIBLE grouping label.
1006
+ //
1007
+ // The other four kinds this list used to hold — radio, checkbox-group, multiselect,
1008
+ // taglist — supplied an `aria-label` and nothing else, which is a name only a screen
1009
+ // reader can reach: "Severity", "Environments" and "Tags" were announced and INVISIBLE
1010
+ // on screen. The row now always renders the text for them and hands `labelId` to the
1011
+ // widget, which points its group at it with `aria-labelledby`. One string, visible,
1012
+ // correctly associated.
1013
+ const ownLegend = () => ['fieldset', 'repeater'].includes(widget());
1014
+
1015
+ // Which kinds have no single labelable control for `<label for>` to name. Their label
1016
+ // is still an element with an id, which is what `aria-labelledby` needs; `taglist` is
1017
+ // deliberately NOT here, because its draft <input> carries the row id and a real
1018
+ // `for` association is strictly better than an ARIA one.
1019
+ const groupOnly = () => ['radio', 'checkbox-group', 'multiselect'].includes(widget());
1020
+
1021
+ const common = fieldCommon(props, id, placeholder, label, describedBy, maskHint, () =>
1022
+ ownLegend() ? undefined : labelId,
1023
+ );
752
1024
 
753
- // A nested fieldset / repeater / checkbox-group provide their own grouping
754
- // label, so the row's <label> is only rendered for simple single controls.
755
- const isGrouped = () =>
756
- ['fieldset', 'repeater', 'checkbox-group', 'multiselect', 'radio', 'taglist'].includes(widget());
757
1025
 
758
1026
  return (
759
1027
  <div class="flex flex-col gap-2 rounded-xl bg-surface p-3.5" data-field={props.fieldKey}>
760
- <Show when={!isGrouped()}>
761
- <label for={id} class="text-sm font-medium text-foreground">
1028
+ <Show when={!ownLegend()}>
1029
+ <label id={labelId} for={groupOnly() ? undefined : id} class="text-sm font-medium text-foreground">
762
1030
  {label()}
763
1031
  <Show when={props.required}>
764
1032
  <span class="text-destructive dark:text-red-400" aria-hidden="true">{' *'}</span>
@@ -772,6 +1040,15 @@ function FieldRow(props: FieldRowProps): JSX.Element {
772
1040
  </p>
773
1041
  </Show>
774
1042
 
1043
+ {/* The expected format, as TEXT and in the same describedby chain as the
1044
+ description and the error (spec §6, SC 3.3.2). The visual guide inside the
1045
+ control is not a description and does not replace this. */}
1046
+ <Show when={maskHint().hint}>
1047
+ <p id={formatId} class="text-xs text-muted-foreground">
1048
+ {maskHint().hint}
1049
+ </p>
1050
+ </Show>
1051
+
775
1052
  <WidgetSwitch widget={widget()} common={common} fieldKey={props.fieldKey} />
776
1053
 
777
1054
  <Show when={props.error()}>
@@ -806,10 +1083,17 @@ export function fieldCommon(
806
1083
  placeholder: () => string | undefined,
807
1084
  label: () => string,
808
1085
  describedBy: () => string | undefined = () => undefined,
1086
+ mask: () => FieldMaskHint = () => EMPTY_MASK_HINT,
1087
+ labelledBy: () => string | undefined = () => undefined,
809
1088
  ): ReturnType<FieldRowCommon> {
810
1089
  return {
811
1090
  id,
812
- get value() { return props.value(); },
1091
+ // The DISPLAY text for a masked field, the stored value verbatim for every other
1092
+ // one (`maskedDisplay` is the identity when nothing resolved). What the form
1093
+ // SUBMITS is untouched by this: that comes off the store, which the control fills
1094
+ // with the canonical value through `onInput`.
1095
+ get value() { return maskedDisplay(mask(), props.value()); },
1096
+ get mask() { return mask(); },
813
1097
  get field() { return props.field; },
814
1098
  get disabled() { return props.disabled || props.field.readOnly === true; },
815
1099
  get placeholder() { return placeholder(); },
@@ -817,6 +1101,7 @@ export function fieldCommon(
817
1101
  get invalid() { return Boolean(props.error()); },
818
1102
  get describedBy() { return describedBy(); },
819
1103
  get label() { return label(); },
1104
+ get labelledBy() { return labelledBy(); },
820
1105
  onInput: (v) => props.onInput(v),
821
1106
  onBlur: () => props.onBlur(),
822
1107
  };
@@ -830,6 +1115,7 @@ interface WidgetSwitchProps {
830
1115
  type FieldRowCommon = () => {
831
1116
  id: string;
832
1117
  value: unknown;
1118
+ mask: FieldMaskHint;
833
1119
  field: FormField;
834
1120
  disabled: boolean;
835
1121
  placeholder?: string;
@@ -837,6 +1123,8 @@ type FieldRowCommon = () => {
837
1123
  invalid: boolean;
838
1124
  describedBy?: string;
839
1125
  label: string;
1126
+ /** The id of the row's visible <label>, for widgets that name a GROUP. */
1127
+ labelledBy?: string;
840
1128
  onInput: (v: unknown) => void;
841
1129
  onBlur: () => void;
842
1130
  };
@@ -250,7 +250,11 @@ export function TerminalLoader(props: TerminalLoaderProps) {
250
250
  const containerSizes = { sm: 'h-4', md: 'h-5', lg: 'h-6' };
251
251
  return (
252
252
  <div class={cn('flex items-center space-x-1', containerSizes[size()], props.class)}>
253
- <span class={cn('text-primary font-mono', textSizes[size()])}>{'>'}</span>
253
+ {/* Decorative status chrome, not a control — was the BRAND token
254
+ (text-primary). The sibling TextShimmerLoader below already renders
255
+ its "Thinking" text purely in --color-foreground/--color-muted-foreground,
256
+ with no brand color at all; matches that precedent. */}
257
+ <span class={cn('text-foreground font-mono', textSizes[size()])}>{'>'}</span>
254
258
  <div class={cn('bg-primary animate-[blink_1s_step-end_infinite]', cursorSizes[size()])} />
255
259
  <span class="sr-only">Loading</span>
256
260
  </div>
@@ -302,13 +306,16 @@ export function TextDotsLoader(props: TextDotsLoaderProps) {
302
306
  const textSizes = { sm: 'text-xs', md: 'text-sm', lg: 'text-base' };
303
307
  return (
304
308
  <div class={cn('inline-flex items-center', props.class)}>
305
- <span class={cn('text-primary font-medium', textSizes[size()])}>
309
+ {/* Content/chrome, not a control — was `text-primary` on the label AND
310
+ the dots (both must match: one phrase, "Thinking..."). Matches
311
+ TextShimmerLoader's neutral precedent above. */}
312
+ <span class={cn('text-foreground font-medium', textSizes[size()])}>
306
313
  {props.text ?? 'Thinking'}
307
314
  </span>
308
315
  <span class="inline-flex">
309
- <span class="text-primary animate-[loading-dots_1.4s_infinite_0.2s]">.</span>
310
- <span class="text-primary animate-[loading-dots_1.4s_infinite_0.4s]">.</span>
311
- <span class="text-primary animate-[loading-dots_1.4s_infinite_0.6s]">.</span>
316
+ <span class="text-foreground animate-[loading-dots_1.4s_infinite_0.2s]">.</span>
317
+ <span class="text-foreground animate-[loading-dots_1.4s_infinite_0.4s]">.</span>
318
+ <span class="text-foreground animate-[loading-dots_1.4s_infinite_0.6s]">.</span>
312
319
  </span>
313
320
  </div>
314
321
  );
@@ -27,7 +27,7 @@ function MessageSkills(props: MessageSkillsProps) {
27
27
  <div class={cn("flex items-center gap-1 flex-wrap", props.class)}>
28
28
  <For each={props.skills}>
29
29
  {(skill) => (
30
- <span class="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-violet-400/10 text-violet-600 dark:text-violet-400">
30
+ <span class="inline-flex items-center px-1.5 py-0.5 rounded text-micro font-medium bg-violet-400/10 text-violet-600 dark:text-violet-400">
31
31
  {skill.name}
32
32
  </span>
33
33
  )}
@@ -8,6 +8,7 @@ import { actionIcon, BUILTIN_ACTION_LABEL } from "../ui/action-icons";
8
8
  import type { ChatMessageAction, CustomAction, FeedbackVote, MessagePart, MessageSource } from "../elements/chat-types";
9
9
  import { useChatConfig, textClass } from "../primitives/chat-config";
10
10
  import { Reasoning, ReasoningTrigger, ReasoningContent } from "./reasoning";
11
+ import { Loader } from "./loader";
11
12
  import { Tool } from "./tool";
12
13
  import {
13
14
  Attachments,
@@ -328,6 +329,12 @@ export interface MessageBodyProps {
328
329
  * the kit checks its own seven built-ins and leaves your own card type
329
330
  * unvalidated. A schema here WINS over a built-in of the same name. */
330
331
  cardSchemas?: CardSchemaMap;
332
+ /** The custom-element host node to emit card events off when no `CardProvider`
333
+ * is above this body, forwarded to `CardRenderer` as `hostElement`. The
334
+ * `<kai-chat>`/`<kai-message>`/`<kai-thread>` facades pass their own element,
335
+ * so a `card` part's events leave as the bubbling `kai-card` CustomEvent
336
+ * instead of being silently discarded. */
337
+ cardHostElement?: HTMLElement;
331
338
  /** Whether this is a user message (right-aligned bubble) vs an assistant
332
339
  * message (full-width transparent). */
333
340
  isUser: boolean;
@@ -348,11 +355,27 @@ export interface MessageBodyProps {
348
355
  copied?: boolean;
349
356
  /** Whether this message is the one currently streaming. Forwarded to each
350
357
  * reasoning part's `<Reasoning>` so the disclosure auto-opens while the
351
- * model is thinking and settles back once the stream ends (F-21: without it
352
- * the user watches a static collapsed "Reasoning" label for the whole
353
- * thinking window). The caller owns the definition of "streaming" — for
358
+ * model is thinking and settles back once the stream ends. Without it the
359
+ * user watches a static collapsed "Reasoning" label for the whole thinking
360
+ * window. The caller owns the definition of "streaming" — for
354
361
  * `ChatThread` that is `loading` + being the last assistant message. */
355
362
  isStreaming?: boolean;
363
+ /** How a `reasoning` part renders. `'full'` (default) is the current
364
+ * behavior: the collapsible `<Reasoning>` disclosure, with the "Thinking…"
365
+ * shimmer on its trigger while `isStreaming`. `'compact'` drops the
366
+ * disclosure entirely and shows only a shimmer loader while the part is
367
+ * streaming — nothing once it settles, so there is no expandable detail to
368
+ * open. `'off'` renders the part not at all, in either state. Kit-decides-
369
+ * HOW: this is a display-mode fact about the medium, not a per-message
370
+ * toggle, so it applies uniformly to every reasoning part in the body. */
371
+ reasoningMode?: 'full' | 'compact' | 'off';
372
+ /** Seeds the reasoning disclosure open AND keeps it tracking the stream
373
+ * (open while streaming, closes when it settles): the pre-Task-19f `full`
374
+ * behavior. Default false/absent: the panel starts closed (just the
375
+ * "Thinking" shimmer chip) and only opens on click, the current default
376
+ * (owner ruling, 2026-08-26). Meaningless when `reasoningMode` is
377
+ * `'compact'`/`'off'` (no disclosure exists to open). */
378
+ reasoningDefaultOpen?: boolean;
356
379
  /** INJECT slot projected at the TOP of the body — above the reasoning / tools /
357
380
  * content. A per-message header: a model-name label, a role + timestamp line.
358
381
  * In the `<kai-message>` shadow this is `<slot name="before-body" />`. */
@@ -613,7 +636,17 @@ function MessageBody(props: MessageBodyProps) {
613
636
  part="bubble content"
614
637
  markdown={props.markdown}
615
638
  class={props.isUser
616
- ? 'bg-muted text-primary max-w-[85%] rounded-2xl px-4 py-2'
639
+ // Content token, not the brand token: `--color-primary`
640
+ // is the documented consumer brand override
641
+ // (theme.css `--kai-color-primary`), so toking message
642
+ // TEXT to it means every consumer that brands primary
643
+ // gets brand-colored message text. `text-foreground` is
644
+ // already `MessageContent`'s base color (matches the
645
+ // assistant path's `bg-transparent p-0`, which carries
646
+ // no color override and falls through to the same
647
+ // base) — dropping `text-primary` here just lets that
648
+ // base apply on the user bubble too.
649
+ ? 'bg-muted max-w-[85%] rounded-2xl px-4 py-2'
617
650
  : 'bg-transparent p-0'}
618
651
  >
619
652
  {p().text}
@@ -622,18 +655,45 @@ function MessageBody(props: MessageBodyProps) {
622
655
  </Match>
623
656
  <Match when={carrierOnly()}>{null}</Match>
624
657
  <Match when={shownReasoning()}>
625
- {(p) => (
626
- <Reasoning class="mb-2 w-full" isStreaming={props.isStreaming}>
627
- <ReasoningTrigger>{p().label ?? 'Reasoning'}</ReasoningTrigger>
628
- <ReasoningContent markdown>{p().text}</ReasoningContent>
629
- </Reasoning>
630
- )}
658
+ {(p) => {
659
+ // Default 'full' is the pre-existing DISPLAY MODE byte
660
+ // for byte; the OPEN behavior changed under Task 19f —
661
+ // it no longer auto-opens while streaming by default
662
+ // (owner ruling 2026-08-26). `reasoningDefaultOpen`
663
+ // reproduces the old always-auto-opens behavior when set.
664
+ const mode = () => props.reasoningMode ?? 'full';
665
+ return (
666
+ <Switch fallback={null}>
667
+ <Match when={mode() === 'full'}>
668
+ <Reasoning
669
+ class="mb-2 w-full"
670
+ isStreaming={props.isStreaming}
671
+ defaultOpen={props.reasoningDefaultOpen}
672
+ openOnStream={props.reasoningDefaultOpen}
673
+ >
674
+ <ReasoningTrigger>{p().label ?? 'Reasoning'}</ReasoningTrigger>
675
+ <ReasoningContent markdown>{p().text}</ReasoningContent>
676
+ </Reasoning>
677
+ </Match>
678
+ {/* 'compact': the same "Thinking…" shimmer the full
679
+ disclosure's trigger shows while streaming — no
680
+ <Reasoning>/<ReasoningContent>, so there is no
681
+ expandable detail and nothing left once the part
682
+ settles (isStreaming false → this Match doesn't
683
+ fire, matching 'off'). 'off' never reaches here:
684
+ its Match doesn't fire either. */}
685
+ <Match when={mode() === 'compact' && props.isStreaming}>
686
+ <Loader variant="text-shimmer" text={p().label ?? 'Reasoning'} class="mb-2" />
687
+ </Match>
688
+ </Switch>
689
+ );
690
+ }}
631
691
  </Match>
632
692
  <Match when={partAs(part(), 'tool')}>
633
693
  {(p) => <Tool toolPart={p().tool} class="mb-2 w-full" />}
634
694
  </Match>
635
695
  <Match when={partAs(part(), 'card')}>
636
- {(p) => <CardRenderer envelope={p().envelope} types={props.cardTypes} schemas={props.cardSchemas} />}
696
+ {(p) => <CardRenderer envelope={p().envelope} types={props.cardTypes} schemas={props.cardSchemas} hostElement={props.cardHostElement} />}
637
697
  </Match>
638
698
  {/* No `source` match here on purpose: source parts never
639
699
  reach a 'single' group — they are collapsed into a
@@ -115,9 +115,15 @@ function PromptInputTextarea(props: PromptInputTextareaProps) {
115
115
  // the contenteditable composer is visually identical. The frame (PromptInput
116
116
  // root) still owns radius/bg/padding/focus-ring; auto-grow is native to a
117
117
  // contenteditable block (capped via max-height on the editable).
118
+ // `text-start` is a PIN, not a style choice: `text-align` inherits, so any
119
+ // centered ancestor (`Empty`'s root did exactly this) reached in and centered
120
+ // the placeholder AND the typed text. An input control's text alignment is a
121
+ // fact about the control, so it states it rather than inheriting it. LOGICAL
122
+ // (`start`), not `text-left` — in RTL the correct edge is the right one, and
123
+ // pinning the physical value would be a worse bug than the one it fixes.
118
124
  const editableClass = () =>
119
125
  cn(
120
- 'text-foreground min-h-[44px] w-full bg-transparent shadow-none outline-none focus-visible:ring-0 focus-visible:ring-offset-0 overflow-y-auto whitespace-pre-wrap break-words',
126
+ 'text-foreground min-h-[44px] w-full bg-transparent text-start shadow-none outline-none focus-visible:ring-0 focus-visible:ring-offset-0 overflow-y-auto whitespace-pre-wrap break-words',
121
127
  textClass(config.proseSize()),
122
128
  local.class,
123
129
  );
@@ -136,7 +136,10 @@ function renderHighlighted(text: string, highlight: string) {
136
136
  <Show when={before}>
137
137
  <span class="text-foreground/70 whitespace-pre-wrap">{before}</span>
138
138
  </Show>
139
- <span class="text-primary font-medium whitespace-pre-wrap">{matched}</span>
139
+ {/* Matched-substring highlight is CONTENT, not a control — was the BRAND
140
+ token. Full-strength `text-foreground` against the `/70`-opacity
141
+ siblings above/below is what actually carries the highlight. */}
142
+ <span class="text-foreground font-medium whitespace-pre-wrap">{matched}</span>
140
143
  <Show when={after}>
141
144
  <span class="text-foreground/70 whitespace-pre-wrap">{after}</span>
142
145
  </Show>