@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
@@ -36,6 +36,11 @@ interface Events {
36
36
  * vote (`collect-detail`), then confirms with a thank-you — all in place. Emits
37
37
  * `kai-feedback` (`{ value }`), `kai-feedback-detail` (`{ value, category?, comment? }`),
38
38
  * and `kai-close`.
39
+ *
40
+ * Placement and lifecycle are the app's call: the component is designed as a
41
+ * THREAD-level bar (its events carry no message id), and when to show it —
42
+ * e.g. revealed after a settled turn, hidden again on submit — is product
43
+ * behavior the host decides.
39
44
  */
40
45
  defineWebComponent<Props, Events>('kai-feedback-bar', {
41
46
  barTitle: 'Was this helpful?',
@@ -1,20 +1,33 @@
1
1
  [
2
2
  "archive",
3
+ "arrow-down",
3
4
  "arrow-left",
5
+ "arrow-right",
4
6
  "arrow-up",
5
7
  "audio-lines",
8
+ "bell",
6
9
  "book-open",
7
10
  "bookmark",
8
11
  "box",
9
12
  "briefcase",
13
+ "check",
10
14
  "chevron-down",
11
15
  "chevron-left",
16
+ "chevron-right",
17
+ "chevron-up",
12
18
  "circle",
19
+ "circle-alert",
20
+ "circle-check",
21
+ "circle-x",
13
22
  "clock",
14
23
  "code",
15
24
  "copy",
25
+ "desktop",
26
+ "download",
16
27
  "ellipsis",
17
28
  "external-link",
29
+ "eye",
30
+ "eye-off",
18
31
  "file-text",
19
32
  "flag",
20
33
  "folder",
@@ -24,27 +37,43 @@
24
37
  "globe",
25
38
  "home",
26
39
  "image",
40
+ "info",
41
+ "laptop",
42
+ "link",
27
43
  "list-filter",
28
44
  "lock",
45
+ "maximize-2",
29
46
  "message-circle",
30
47
  "message-square",
31
48
  "mic",
49
+ "minimize-2",
50
+ "minus",
51
+ "mobile",
32
52
  "monitor",
33
53
  "moon",
34
54
  "more-horizontal",
35
55
  "panel-left",
56
+ "panel-right",
36
57
  "paperclip",
37
58
  "pencil",
59
+ "play",
38
60
  "plus",
61
+ "rotate-ccw",
39
62
  "rotate-cw",
40
63
  "search",
41
64
  "settings",
42
65
  "share",
43
66
  "sliders-horizontal",
67
+ "smartphone",
44
68
  "smile",
45
69
  "sparkles",
70
+ "square",
46
71
  "square-pen",
47
72
  "sun",
73
+ "tablet",
74
+ "trash",
75
+ "triangle-alert",
76
+ "upload",
48
77
  "workflow",
49
78
  "x"
50
79
  ]
@@ -1,14 +1,26 @@
1
1
  import { createEffect, createSignal, onCleanup, onMount, untrack } from 'solid-js';
2
2
  import { defineWebComponent } from './define';
3
- import { Input, type InputProps } from '../ui/input';
3
+ import { DEFAULT_FORMAT, Input, type InputProps } from '../ui/input';
4
+ import {
5
+ compileMask,
6
+ formatForDisplay,
7
+ formatRaw,
8
+ normalizeToRaw,
9
+ type CaseMode,
10
+ } from '../primitives/field-mask';
11
+ import { canonicalize, fieldSemantics, type FieldSemanticType } from '../primitives/field-semantics';
12
+ import type { CopyPolicy, InputMaskRejectReason } from '../primitives/input-mask';
4
13
 
5
14
  interface Props extends Record<string, unknown> {
6
15
  /** Native input type: `text` (default) · `email` · `url` · `search` · `tel` ·
7
16
  * `password` · `number`. Single-line only. */
8
17
  type?: string;
9
- /** Controlled value. Settable and reflected to the `value` attribute. `el.value
10
- * = 'hi'` drives it (no event); typing updates it and fires `kai-input`. Read
11
- * `el.value` for live state. */
18
+ /** Controlled value, and always the CANONICAL one when a mask is active: digits
19
+ * for `tel` / `ssn` / `credit-card`, the formatted text for `custom`.
20
+ * Settable and reflected to the `value` attribute. `el.value = '5551234567'`
21
+ * drives it (no event) and is re-fitted to the mask on the way in, so the field
22
+ * shows `555-123-4567`. Read `el.value` for live state; the formatted text rides
23
+ * along on every `kai-input` / `kai-change` detail as `formattedValue`. */
12
24
  value?: string;
13
25
  /** Placeholder shown when empty. */
14
26
  placeholder?: string;
@@ -34,6 +46,35 @@ interface Props extends Record<string, unknown> {
34
46
  autocomplete?: string;
35
47
  /** Virtual-keyboard hint forwarded to the inner input (e.g. `numeric`, `email`). */
36
48
  inputmode?: string;
49
+
50
+ // --- Form-field formats (spec §7.2). Five SCALARS, which is exactly why they work as
51
+ // HTML attributes under the kai- contract. With `format` AND `semantic` both absent the
52
+ // element behaves as it always did: no mask, no extra attributes, nothing.
53
+
54
+ /** Mask pattern: `#` a digit, `@` a letter or digit, `*` an obscurable letter or
55
+ * digit, and every other character a positional literal (`@@@-####` → `CHG-4821`).
56
+ *
57
+ * The literal `default` is the opt-in sentinel: it resolves to the default format of
58
+ * `semantic` (`tel` → `###-###-####`). A bare `semantic` never starts masking on its
59
+ * own, so an opt-in token is what turns tier 2 on. */
60
+ format?: string;
61
+ /** Placeholder guide shown at unfilled positions, aligned position for position with
62
+ * `format`: `mm/dd/yyyy` against `##/##/####`. Spaces are a valid guide character, so
63
+ * a guide of blanks and separators is how a phone field shows its shape without
64
+ * showing letters. Without a guide the field shows only up to the last typed
65
+ * character. A guide is a visual aid, never an accessible name: keep the `hint` text
66
+ * as well. */
67
+ guide?: string;
68
+ /** Semantic field type: `tel` · `ssn` · `credit-card` · `custom`. On its own it sets
69
+ * `inputmode` / `autocomplete` / `spellcheck` / `autocorrect` / `autocapitalize` and
70
+ * decides the canonical value; it never starts masking by itself. */
71
+ semantic?: FieldSemanticType;
72
+ /** Case folding applied to typed and pasted text: `preserve` (default) · `upper` ·
73
+ * `lower`. Attribute: `case-mode`. */
74
+ caseMode?: CaseMode;
75
+ /** What a copy or cut of a masked field puts on the clipboard: `canonical` (default)
76
+ * · `formatted` · `obscured` · `blocked`. Attribute: `copy-policy`. */
77
+ copyPolicy?: CopyPolicy;
37
78
  // NOTE: `autocapitalize` is forwarded too, but NOT as a declared prop — see the
38
79
  // attribute read in the facade body (it is a global reflected HTMLElement IDL
39
80
  // attribute and would break the element constructor as a component-register prop).
@@ -41,10 +82,22 @@ interface Props extends Record<string, unknown> {
41
82
 
42
83
  /** Events fired by `<kai-input>`. */
43
84
  interface Events {
44
- /** The value changed per keystroke. */
45
- 'kai-input': { value: string };
46
- /** The value was committed (blur). */
47
- 'kai-change': { value: string };
85
+ /** The value changed per keystroke. `value` is the canonical value (what a backend
86
+ * wants); `formattedValue` is the text on screen. With no mask the two are equal. */
87
+ 'kai-input': { value: string; formattedValue: string };
88
+ /** The value was committed (blur). Same detail shape as `kai-input`. */
89
+ 'kai-change': { value: string; formattedValue: string };
90
+ /** A mask refused, or partly refused, some content. The reasons are `full` (no free
91
+ * position left), `wrong-class` (a letter into a digit position), `over-capacity` (a
92
+ * paste longer than the mask holds; what fits was kept), and `format-change-clipped`
93
+ * (the `format` changed under a value that no longer fits). `data` is the content
94
+ * that was refused.
95
+ *
96
+ * The first three are USER-INPUT errors, and are the ones worth announcing in a
97
+ * polite live region. `format-change-clipped` is not one: it follows the app changing
98
+ * its own configuration, so it reports and nothing more. None of the four touches
99
+ * validity, so `invalid` and `error` stay the consumer decision. */
100
+ 'kai-input-rejected': { reason: InputMaskRejectReason; data: string };
48
101
  }
49
102
 
50
103
  /** Named slots whose occupancy gates an affix. An empty `<slot>` is always a
@@ -72,8 +125,52 @@ type SlotName = (typeof SLOT_NAMES)[number];
72
125
  * </script>
73
126
  * ```
74
127
  *
75
- * Methods: `focus()`, `select()`, `clear()`. Restyle via `::part(field)`,
128
+ * Methods: `focus()`, `select()`, `clear()`, `getRawValue()`, `getFormattedValue()`.
129
+ * Restyle via `::part(field)`,
76
130
  * `::part(input)`, `::part(label)`, `::part(hint)`.
131
+ *
132
+ * ## Masked and formatted fields
133
+ *
134
+ * `format` turns the field into a mask; `guide`, `semantic`, `case-mode` and
135
+ * `copy-policy` tune it. All five are scalars, so all five are plain attributes.
136
+ *
137
+ * ```html
138
+ * <kai-input label="Ticket" format="@@@-####" case-mode="upper"
139
+ * hint="Three letters, a dash, four digits."></kai-input>
140
+ * <kai-input label="Phone" semantic="tel" format="default"
141
+ * guide=" - - "></kai-input>
142
+ * ```
143
+ *
144
+ * `el.value` is the CANONICAL value — digits for `tel` / `ssn` / `credit-card`, the
145
+ * formatted text for `custom`. The text on screen rides along as
146
+ * `formattedValue` on the `kai-input` / `kai-change` details. Refusals report on
147
+ * `kai-input-rejected` and never touch validity.
148
+ *
149
+ * TWO THINGS THIS DELIBERATELY DOES NOT DO, both recorded rather than smuggled:
150
+ *
151
+ * 1. **Not form-associated.** There is no `ElementInternals` and no
152
+ * `setFormValue()`, so a `<kai-input>` inside a `<form>` contributes nothing to
153
+ * `FormData` and takes no part in native validation, with or without a mask.
154
+ * That is a KNOWN GAP: form association lands later, with its own migration.
155
+ * Read the value off the element.
156
+ * 2. **No `value-type` prop.** Canonical-form-per-semantic-type is the whole
157
+ * contract; a per-field raw-or-formatted switch would make
158
+ * the same field round-trip differently depending on who set it.
159
+ *
160
+ * ### The controlled-value rule, stated
161
+ *
162
+ * A write to `el.value` after mount is re-fitted through the masker, so it lands
163
+ * FORMATTED on screen and canonical on the property, and fires no event. What that
164
+ * covers is any write that reaches this element: `el.value = x`, the `value`
165
+ * attribute, and a React/Vue/Svelte wrapper prop, since every wrapper drives this
166
+ * element by writing the property. A value present at first render is masked by the
167
+ * same rule, and so is a change to `format` / `guide` / `semantic` / `case-mode`
168
+ * AFTER mount: the canonical value is re-derived, so `el.value` never serves a
169
+ * reading that disagrees with the configuration in force. An over-long write is
170
+ * clipped to what the mask holds and says so on `kai-input-rejected`.
171
+ *
172
+ * The one thing none of that reaches is a consumer that writes the inner `<input>`
173
+ * inside the shadow root directly, behind both this element and its mask.
77
174
  */
78
175
  defineWebComponent<Props, Events>('kai-input', {
79
176
  type: 'text',
@@ -90,15 +187,76 @@ defineWebComponent<Props, Events>('kai-input', {
90
187
  name: undefined,
91
188
  autocomplete: undefined,
92
189
  inputmode: undefined,
190
+ format: undefined,
191
+ guide: undefined,
192
+ semantic: undefined,
193
+ caseMode: undefined,
194
+ copyPolicy: undefined,
93
195
  }, (props, ctx) => {
94
196
  const { element, dispatch, flag, expose } = ctx;
95
197
 
96
- // Lift the value into the facade and drive Input CONTROLLED so the host can read
97
- // it (`el.value` / `:host([value])`) and set it after mount. Seed from the
98
- // `value` property/attribute present on mount.
99
- const [value, setValue] = createSignal(
198
+ /** Is a mask configured at all? An empty `format` means no, same as an absent one. */
199
+ const masked = (): boolean => {
200
+ const format = props.format as string | undefined;
201
+ return format !== undefined && format !== '';
202
+ };
203
+
204
+ /**
205
+ * Resolve a seed value through the SAME pure engine the masker runs on, so the two
206
+ * signals below start out agreeing with what the field will actually show.
207
+ *
208
+ * WHY THE FACADE DERIVES THIS INSTEAD OF ASKING THE MASKER. A seeded value is the one
209
+ * case the masker cannot report: it normalizes the text at attach time WITHOUT
210
+ * notifying, because attaching is not an edit the consumer made. Nor can the facade
211
+ * provoke it afterwards -- an edit whose raw value comes out unchanged commits nothing
212
+ * and notifies nobody (`applyEdit` returns early on `nextRaw === raw`), which is
213
+ * exactly what re-writing the seed is. So without this, a
214
+ * `<kai-input format="@@@-####" value="chg4821">` would SHOW `CHG-4821` while
215
+ * `el.value` still read `chg4821`.
216
+ *
217
+ * This is a derivation, not a second policy -- `field-mask`, `field-semantics` and the
218
+ * `DEFAULT_FORMAT` sentinel are all read from where `ui/input.tsx` reads them, so
219
+ * nothing about the resolution is restated here. A bad format or an unresolvable
220
+ * `default` falls through unmasked exactly as it does there (the widget is the one
221
+ * that warns; warning twice for one misconfiguration would be noise).
222
+ */
223
+ const throughMask = (text: string): { canonical: string; formatted: string } => {
224
+ const plain = { canonical: text, formatted: text };
225
+ if (text === '' || !masked()) return plain;
226
+ const semantic = (props.semantic as FieldSemanticType | undefined) ?? 'custom';
227
+ const format = props.format as string;
228
+ const resolved = format === DEFAULT_FORMAT ? fieldSemantics(semantic).defaultFormat : format;
229
+ if (resolved === undefined) return plain;
230
+ try {
231
+ const pattern = compileMask(resolved, props.guide as string | undefined);
232
+ const raw = normalizeToRaw(pattern, text, (props.caseMode as CaseMode | undefined) ?? 'preserve');
233
+ return {
234
+ canonical: canonicalize(pattern, formatRaw(pattern, raw), semantic),
235
+ formatted: formatForDisplay(pattern, raw),
236
+ };
237
+ } catch {
238
+ return plain;
239
+ }
240
+ };
241
+
242
+ // TWO signals, and the split is the whole controlled-value story.
243
+ //
244
+ // `value` is the CANONICAL value: what `el.value` reads, what reflects to the `[value]`
245
+ // attribute, what every event detail carries. `display` is the text the inner input
246
+ // shows, which under a mask is the FORMATTED text and is not the same string.
247
+ //
248
+ // Before the split there was one signal driving both, and it fought the masker: the
249
+ // masker writes `555-123-4567` into the input, the facade stores the canonical
250
+ // `5551234567`, and the controlled `value` prop then writes THAT back over the input on
251
+ // the next flush -- the field un-formats itself one keystroke behind the user. Feeding
252
+ // Input the display text means the value it writes is the text already there, which the
253
+ // HTML value setter treats as a no-op (caret included).
254
+
255
+ const seed = throughMask(
100
256
  (props.value as string | undefined) ?? element.getAttribute('value') ?? '',
101
257
  );
258
+ const [value, setValue] = createSignal(seed.canonical);
259
+ const [display, setDisplay] = createSignal(seed.formatted);
102
260
 
103
261
  // Coerce any value coming through the `value` setter (a JS assignment, or the
104
262
  // write-back from `attributeChangedCallback`) to a string — falling back to the
@@ -107,18 +265,115 @@ defineWebComponent<Props, Events>('kai-input', {
107
265
  const coerce = (v: unknown): string =>
108
266
  v == null ? (element.getAttribute('value') ?? '') : String(v);
109
267
 
110
- // Set the value WITHOUT firing an event (programmatic / reflect write-back).
111
- const setQuiet = (next: string) => { if (untrack(value) !== next) setValue(next); };
268
+ // Set both WITHOUT firing an event (the unmasked path: canonical IS the display text).
269
+ const setBothQuiet = (next: string) => {
270
+ if (untrack(value) !== next) setValue(next);
271
+ if (untrack(display) !== next) setDisplay(next);
272
+ };
273
+
274
+ const innerInput = () => element.shadowRoot?.querySelector<HTMLInputElement>('input') ?? null;
275
+
276
+ /** Set while the facade is driving the field itself, so the masker callback it
277
+ * provokes updates state without firing `kai-input`. A programmatic write is not a
278
+ * keystroke, and the host that performed it already knows what it wrote. */
279
+ let programmatic = false;
280
+
281
+ /**
282
+ * The single external-write path: `el.value = …`, the `value` attribute, and `clear()`
283
+ * all land here.
284
+ *
285
+ * WITH A MASK it goes THROUGH the masker rather than around it. The masker owns
286
+ * `input.value`, its own stored value, its undo stack and its caret; writing the input
287
+ * behind its back leaves it holding a stale string, and the next keystroke reconciles
288
+ * against that stale string rather than against what is on screen. So: put the text in
289
+ * the field, then fire the `input` event the masker already listens for, which is its
290
+ * documented reconcile path (spec §5.1) and ends in the same normalize-format-commit
291
+ * every edit takes. The masker calls back with the canonical value and the formatted
292
+ * text is then in the DOM, so both signals come from IT, not from the caller.
293
+ *
294
+ * A refusal (wrong characters, over capacity) reports on `kai-input-rejected` and leaves
295
+ * the field as it was, which is the same contract typing gets.
296
+ */
297
+ const writeValue = (next: string, force = false): void => {
298
+ // The reflect effect below writes `[value]`, which component-register writes back
299
+ // through this setter. Without this guard that write-back would re-enter the masker on
300
+ // every value change. `force` is for the one caller that must re-run an equal value.
301
+ if (!force && untrack(value) === next) return;
302
+
303
+ const input = innerInput();
304
+ if (input === null || !masked()) {
305
+ setBothQuiet(next);
306
+ return;
307
+ }
308
+
309
+ programmatic = true;
310
+ try {
311
+ input.value = next;
312
+ input.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
313
+ } finally {
314
+ programmatic = false;
315
+ }
316
+ };
112
317
 
113
318
  // Override `value` so reads return LIVE state and host writes (`el.value = …`)
114
319
  // drive it WITHOUT firing events — distinct from the typing path.
115
320
  Object.defineProperty(element, 'value', {
116
321
  get: () => value(),
117
- set: (v: unknown) => setQuiet(coerce(v)),
322
+ set: (v: unknown) => writeValue(coerce(v)),
118
323
  configurable: true,
119
324
  enumerable: true,
120
325
  });
121
326
 
327
+ // A CONFIGURATION CHANGE AFTER MOUNT re-derives the canonical value.
328
+ //
329
+ // The masker cannot report this one, in either of the two shapes it takes:
330
+ //
331
+ // - `format` ARRIVING on a live field attaches a fresh masker, and attaching seeds
332
+ // with `notify: false` on purpose. The display re-formats, `el.value` does not.
333
+ // - `update()` notifies only when the FORMATTED text changed, so a reconfiguration
334
+ // that moves the canonical value WITHOUT moving the display is silent by design:
335
+ // flipping `semantic` from `custom` to `tel` under `###-###-####` turns
336
+ // `555-123-4567` into `5551234567` on the wire and changes nothing on screen.
337
+ //
338
+ // Either way `el.value` would go on serving a stale canonical value indefinitely -- and
339
+ // an external write does NOT repair it, because an edit whose raw value comes out
340
+ // unchanged commits nothing and notifies nobody. Only a keystroke did. So the facade
341
+ // re-derives, from the text the masker has settled on.
342
+ //
343
+ // WHAT IT RE-FITS, and why not simply the text on screen. While a mask is on, the text
344
+ // on screen can carry GUIDE characters at the unfilled positions, and a guide character
345
+ // that happens to fit the position class would be absorbed as content on the way back
346
+ // in. The previous CANONICAL value has no guide in it and is the faithful record of what
347
+ // the user actually entered, so that is the input -- the same thing `update()` re-fits
348
+ // from. With no mask there is no canonical form to speak of, so the field text is it.
349
+ //
350
+ // WHY A MICROTASK. This effect is created before the JSX below, so on any flush it runs
351
+ // BEFORE the mask effect inside `Input`: reading or writing here would race the masker
352
+ // it is trying to agree with. One microtask puts the whole thing after the flush. Where
353
+ // the masker DID notify (a clipping format change), it has already set the right value
354
+ // by then and this recomputes the same answer.
355
+ {
356
+ let first = true;
357
+ createEffect(() => {
358
+ // Tracked deliberately, and these four only: the inputs `throughMask` reads.
359
+ void props.format;
360
+ void props.guide;
361
+ void props.semantic;
362
+ void props.caseMode;
363
+ if (first) {
364
+ // The seed above already did this, synchronously and with no DOM to wait for.
365
+ first = false;
366
+ return;
367
+ }
368
+ queueMicrotask(() => {
369
+ const source = masked() ? untrack(value) : (innerInput()?.value ?? untrack(display));
370
+ const next = throughMask(source);
371
+ if (untrack(value) !== next.canonical) setValue(next.canonical);
372
+ if (untrack(display) !== next.formatted) setDisplay(next.formatted);
373
+ });
374
+ });
375
+ }
376
+
122
377
  // Reflect internal value → the `[value]` host attribute (for `:host([value])`).
123
378
  // The guard against the live attribute keeps the write-back the reflect triggers
124
379
  // (attributeChangedCallback → setter) from looping.
@@ -163,15 +418,30 @@ defineWebComponent<Props, Events>('kai-input', {
163
418
  });
164
419
  const region = (name: SlotName) => (filled()[name] ? <slot name={name} /> : undefined);
165
420
 
166
- const innerInput = () => element.shadowRoot?.querySelector<HTMLInputElement>('input') ?? null;
167
-
168
421
  expose({
169
422
  /** Focus the inner input (the host can't reach into the shadow root). */
170
423
  focus: (options?: FocusOptions) => innerInput()?.focus(options),
171
424
  /** Select the inner input's text. */
172
425
  select: () => innerInput()?.select(),
173
- /** Empty the value and fire `kai-change` with `''`. */
174
- clear: () => { setValue(''); dispatch('kai-change', { value: '' }); },
426
+ /** The canonical value: digits for `tel` / `ssn` / `credit-card`, the formatted text
427
+ * for `custom`, and the field text when no mask is on. Identical to reading
428
+ * `el.value`, under the name backends use for the submitted form of a masked
429
+ * field. The mask engine has a third,
430
+ * narrower notion of raw (the fill characters with no literals at all) and that one
431
+ * is internal: it is not what any backend wants and it is not exposed here. */
432
+ getRawValue: (): string => untrack(value),
433
+ /** The text on screen, literals and guide included. The counterpart to
434
+ * `formattedValue` on the `kai-input` / `kai-change` details, for a consumer that
435
+ * needs it outside an event. */
436
+ getFormattedValue: (): string => innerInput()?.value ?? untrack(display),
437
+ /** Empty the value and fire `kai-change` with `''`. On a masked field this resets the
438
+ * mask itself, not just the text on screen, so the next character starts over. */
439
+ clear: () => {
440
+ writeValue('', true);
441
+ // From the signals, not from the DOM: unmasked, the input has not been re-rendered
442
+ // yet at this point, so the DOM still holds the text that was just cleared.
443
+ dispatch('kai-change', { value: untrack(value), formattedValue: untrack(display) });
444
+ },
175
445
  });
176
446
 
177
447
  return (
@@ -179,7 +449,7 @@ defineWebComponent<Props, Events>('kai-input', {
179
449
  <style>{':host{display:block}'}</style>
180
450
  <Input
181
451
  type={(props.type as string) ?? 'text'}
182
- value={value()}
452
+ value={display()}
183
453
  placeholder={props.placeholder as string | undefined}
184
454
  label={props.label as string | undefined}
185
455
  hint={props.hint as string | undefined}
@@ -193,10 +463,29 @@ defineWebComponent<Props, Events>('kai-input', {
193
463
  autocomplete={props.autocomplete as InputProps['autocomplete']}
194
464
  inputmode={props.inputmode as InputProps['inputmode']}
195
465
  autocapitalize={autocapitalize() as InputProps['autocapitalize']}
466
+ format={props.format as string | undefined}
467
+ guide={props.guide as string | undefined}
468
+ semantic={props.semantic as FieldSemanticType | undefined}
469
+ caseMode={props.caseMode as CaseMode | undefined}
470
+ copyPolicy={props.copyPolicy as CopyPolicy | undefined}
196
471
  leading={region('leading')}
197
472
  trailing={region('trailing')}
198
- onValueInput={(v) => { setValue(v); dispatch('kai-input', { value: v }); }}
199
- onValueChange={(v) => { setQuiet(v); dispatch('kai-change', { value: v }); }}
473
+ onValueInput={(v) => {
474
+ // `v` is already canonical (the widget reads it off the masker). The formatted
475
+ // text is whatever the masker just put in the DOM, so read it from there rather
476
+ // than re-deriving it -- there is only one thing that knows it.
477
+ setValue(v);
478
+ setDisplay(innerInput()?.value ?? v);
479
+ if (!programmatic) {
480
+ dispatch('kai-input', { value: v, formattedValue: untrack(display) });
481
+ }
482
+ }}
483
+ onValueChange={(v) => {
484
+ setValue(v);
485
+ if (!masked()) setDisplay(v);
486
+ dispatch('kai-change', { value: v, formattedValue: innerInput()?.value ?? v });
487
+ }}
488
+ onMaskReject={(detail) => dispatch('kai-input-rejected', detail)}
200
489
  />
201
490
  </>
202
491
  );
@@ -285,6 +285,8 @@ defineWebComponent<Props, Events>('kai-message', {
285
285
  parts={msg().parts}
286
286
  cardTypes={cardComponentsFromTags(props.cardTypes, (props as { theme?: string }).theme)}
287
287
  cardSchemas={props.cardSchemas}
288
+ /* F-26: card parts emit off THIS element as the bubbling `kai-card` event. */
289
+ cardHostElement={element}
288
290
  isUser={isUser()}
289
291
  markdown={useMarkdown()}
290
292
  actions={mergedActions()}
@@ -0,0 +1,111 @@
1
+ import { createSignal, onMount, onCleanup, Index } from 'solid-js';
2
+ import { defineWebComponent } from './define';
3
+ import { PaneGrid } from '../ui/pane-grid';
4
+
5
+ interface Props extends Record<string, unknown> {
6
+ /** Minimum width of every pane, in px, before columns drop / the grid scrolls.
7
+ * Defaults to `280`. Attribute: `min-pane-width`. */
8
+ minPaneWidth?: number;
9
+ /** Minimum height of every pane, in px, before the grid scrolls vertically.
10
+ * Defaults to `200`. Attribute: `min-pane-height`. */
11
+ minPaneHeight?: number;
12
+ /** Column cap when the container is wide (default `3`). Attribute: `max-columns`. */
13
+ maxColumns?: number;
14
+ /** Gap between panes, any CSS length. Defaults to the kit gap
15
+ * (`var(--kai-pane-grid-gap, 0.5rem)`). Attribute: `gap`. */
16
+ gap?: string;
17
+ /** When set to a valid child index, render ONLY that pane full-bleed: a simple
18
+ * maximize hook the consumer drives (pair it with `<kai-pane>`'s `kai-maximize`
19
+ * event). Clear it (or point out of range) for the full tiled grid.
20
+ * Attribute: `maximized-index`. */
21
+ maximizedIndex?: number | null;
22
+ }
23
+
24
+ /** Attribute values arrive as strings; blank/invalid → undefined (the default). */
25
+ function num(value: unknown): number | undefined {
26
+ if (value == null || value === '') return undefined;
27
+ const n = Number(value);
28
+ return Number.isFinite(n) ? n : undefined;
29
+ }
30
+
31
+ /**
32
+ * `<kai-pane-grid>` — an N-pane responsive tiling grid with a min-size + scroll
33
+ * floor. Each direct light child is one tile: the grid fills up to `max-columns`
34
+ * columns when wide, DROPS columns as the container narrows so panes never squish
35
+ * below `min-pane-width`, then SCROLLS once even one column can't fit. Rows keep
36
+ * at least `min-pane-height` the same way. The natural children are `<kai-pane>` /
37
+ * `<kai-pane-group>`, but any elements tile.
38
+ *
39
+ * ```html
40
+ * <kai-pane-grid max-columns="2" min-pane-width="320" style="height: 480px">
41
+ * <kai-pane headline="Atlas">…</kai-pane>
42
+ * <kai-pane headline="Otto">…</kai-pane>
43
+ * <kai-pane headline="Nova">…</kai-pane>
44
+ * </kai-pane-grid>
45
+ * ```
46
+ *
47
+ * Maximize: set `maximized-index` to a child index to show only that pane
48
+ * full-bleed; clear it to restore. The grid never decides this itself — drive it
49
+ * from your own control (e.g. `<kai-pane>`'s `kai-maximize` event). Scalar
50
+ * attributes only; no events.
51
+ *
52
+ * Under the shadow root each pane child is projected through its own named slot
53
+ * (assigned automatically — an author-set `slot` attribute on a direct child is
54
+ * respected and left alone), so the light children become the grid tiles directly.
55
+ */
56
+ defineWebComponent<Props>('kai-pane-grid', {
57
+ minPaneWidth: undefined,
58
+ minPaneHeight: undefined,
59
+ maxColumns: undefined,
60
+ gap: undefined,
61
+ maximizedIndex: undefined,
62
+ }, (props, { element }) => {
63
+ // One named slot per direct light child, in child order. The slot names are
64
+ // written onto the children (`pane-<i>`), the same per-child-slot pattern
65
+ // <kai-resizable> uses (`p0`/`p1`/…): PaneGrid tiles its JSX children, and a
66
+ // single default <slot> would make ALL panes one tile. A child that already
67
+ // carries an author-set slot attribute outside our namespace is left alone
68
+ // (and therefore not tiled) — authoring escape hatch, same as unslotted text.
69
+ const [slotNames, setSlotNames] = createSignal<string[]>([]);
70
+
71
+ onMount(() => {
72
+ const read = () => {
73
+ const names: string[] = [];
74
+ let i = 0;
75
+ for (const child of Array.from(element.children)) {
76
+ const existing = child.getAttribute('slot');
77
+ if (existing !== null && !/^pane-\d+$/.test(existing)) continue; // author-owned
78
+ const name = `pane-${i++}`;
79
+ if (existing !== name) child.setAttribute('slot', name);
80
+ names.push(name);
81
+ }
82
+ // Structural equality: our own setAttribute('slot') writes re-fire the
83
+ // observer; an unchanged name list means an unchanged layout, so skip the
84
+ // re-render (mirrors <kai-resizable>'s itemsChanged guard).
85
+ if (names.length !== slotNames().length || names.some((n, j) => n !== slotNames()[j])) {
86
+ setSlotNames(names);
87
+ }
88
+ };
89
+ read();
90
+ const observer = new MutationObserver(read);
91
+ observer.observe(element, { childList: true });
92
+ onCleanup(() => observer.disconnect());
93
+ });
94
+
95
+ return (
96
+ <>
97
+ {/* A tiling grid fills its container — default the host to a stretching
98
+ block, same as <kai-resizable>. Give the element (or a parent) a height. */}
99
+ <style>{':host{display:block;height:100%;min-height:0;min-width:0}'}</style>
100
+ <PaneGrid
101
+ minPaneWidth={num(props.minPaneWidth)}
102
+ minPaneHeight={num(props.minPaneHeight)}
103
+ maxColumns={num(props.maxColumns)}
104
+ gap={props.gap as string | undefined}
105
+ maximizedIndex={num(props.maximizedIndex) ?? null}
106
+ >
107
+ <Index each={slotNames()}>{(name) => <slot name={name()} />}</Index>
108
+ </PaneGrid>
109
+ </>
110
+ );
111
+ });
@@ -49,7 +49,11 @@ interface Props extends Record<string, unknown> {
49
49
  attach?: boolean;
50
50
  /** Attachments to seed the input with (so a consumer can pre-populate staged
51
51
  * files without an upload). Set as a JS property; the element then manages its
52
- * own attachment state from there (add via the paperclip, remove per chip). */
52
+ * own attachment state from there (add via the paperclip, remove per chip).
53
+ * Each item's `url` must be a `data:` URI or an https URL, never
54
+ * `URL.createObjectURL`: a `blob:` URL previews perfectly and is meaningless
55
+ * outside this tab, so `toOpenAIMessages`/`toAnthropicMessages` refuse it.
56
+ * (The built-in paperclip already stages files as `data:` URIs.) */
53
57
  attachments?: AttachmentData[];
54
58
  /** Rich entity triggers. Each `{ char, kind, items }` opens a caret-anchored menu
55
59
  * that inserts an atomic pill. Convention: `/` → skills, `@` → agents (plugins
@@ -64,7 +68,9 @@ interface Props extends Record<string, unknown> {
64
68
  interface Events {
65
69
  /** The user submitted the prompt (Enter or send button). `value` is the
66
70
  * flattened text (back-compat); `doc` is the structured document and
67
- * `entities` the inserted pills (skills/agents) for downstream expansion. */
71
+ * `entities` the inserted pills (skills/agents) for downstream expansion.
72
+ * `<kai-prompt-input>` is the batteries-included composer row (send button,
73
+ * toolbar, attachment staging) built on `<kai-composer>`, the bare editor. */
68
74
  'kai-submit': { value: string; doc: ComposerDoc; entities: EntityRef[]; attachments: AttachmentData[] };
69
75
  /** The input changed (fires on every edit). Carries the flattened `value`
70
76
  * plus the structured `doc` + `entities`. */