@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
@@ -0,0 +1,1169 @@
1
+ import { existsSync, readFileSync, rmSync, mkdirSync, writeFileSync, copyFileSync } from "node:fs";
2
+ import { spawn } from "node:child_process";
3
+ import { join, dirname, resolve } from "node:path";
4
+ import { z } from "zod";
5
+ import { createRequire } from "node:module";
6
+ const SAFE_SCHEMES = ["http:", "https:", "mailto:"];
7
+ function schemeOf(url) {
8
+ try {
9
+ return new URL(url, "http://_invalid_base").protocol;
10
+ } catch {
11
+ return void 0;
12
+ }
13
+ }
14
+ function isSafeUrl(url) {
15
+ const scheme = schemeOf(url);
16
+ return scheme !== void 0 && SAFE_SCHEMES.includes(scheme);
17
+ }
18
+ const TAG_RE = /^[a-z][a-z0-9]*-[a-z0-9-]+$/;
19
+ const ProviderSchema = z.discriminatedUnion("mode", [
20
+ z.object({ mode: z.literal("mock") }).strict(),
21
+ z.object({
22
+ mode: z.literal("endpoint"),
23
+ /** The CONSUMER's chat route. Kit parses, consumer fetches. */
24
+ url: z.string().min(1),
25
+ wire: z.enum(["openai", "anthropic"])
26
+ }).strict()
27
+ ]);
28
+ const ConstructSchema = z.object({
29
+ $schema: z.string().optional(),
30
+ /** The emitted tag: <acme-support>. Must satisfy customElements.define. */
31
+ name: z.string().regex(TAG_RE, 'must be a valid custom-element tag: lowercase, with a hyphen (e.g. "acme-support")'),
32
+ // Widened progressively: fullscreen/aside/split landed in Task 12, custom in Task 13.
33
+ layout: z.enum(["widget", "fullscreen", "aside", "split", "custom"]),
34
+ provider: ProviderSchema,
35
+ /** Plain (unsigned) identity passthrough — no signing/auth infra (owner
36
+ * ruling, 2026-08-26: signed JWT/HMAC identity is later additive
37
+ * vocabulary, not this field). TOP-LEVEL, not nested under `provider`:
38
+ * local history scoping (`capabilities.history.persistence: 'local'`)
39
+ * needs userId independent of provider mode — nesting it inside the
40
+ * provider union would make it silently inert for `mode: 'mock'` +
41
+ * local history, a real and common combination. Threaded as the
42
+ * `x-kai-user-id` header on every emitted fetch to the CONSUMER's own
43
+ * backend (the endpoint provider's chat POST, history's endpoint
44
+ * GET/PUT), and folded into the localStorage key for `local` history so
45
+ * different users on the same browser profile don't share one thread.
46
+ * Constrained to printable-ASCII (no CR/LF, no non-ISO-8859-1 code
47
+ * points) beyond the usual construct-authored-text escaping: this value
48
+ * reaches an HTTP HEADER VALUE, not just a JS string literal, and
49
+ * `fetch()` throws at RUNTIME on a header value containing CR/LF or
50
+ * outside ISO-8859-1 — a bad userId would surface as an opaque crash in
51
+ * the CONSUMER's generated app, not a construct-validation error. Reject
52
+ * loudly here instead, where the author gets a path + message. */
53
+ userId: z.string().min(1).regex(
54
+ /^[\x20-\x7E]+$/,
55
+ "must be printable ASCII with no line breaks (it becomes an HTTP header value)"
56
+ ).optional(),
57
+ theme: z.object({
58
+ /** Any CSS color; becomes --kai-color-primary on the host. */
59
+ accent: z.string().optional(),
60
+ mode: z.enum(["light", "dark", "system"]).default("system")
61
+ }).strict().optional(),
62
+ /** Construct-wide header chrome, valid on every layout (not layout-scoped
63
+ * like `widget`, and not a capability toggle — a header is a construct-
64
+ * wide fact, like `theme`). Rendered inside ChatThread's own built-in
65
+ * header bar; a logo/icon projects through the kit's EXISTING
66
+ * `header-start` named slot (`slots` vocabulary above), not a second
67
+ * image-prop convention here. */
68
+ header: z.object({
69
+ /** Rendered in ChatThread's built-in header bar (left side). Construct-
70
+ * authored/untrusted text, like theme.accent/provider.url — JSON.stringify'd
71
+ * at its one emit site, never a raw JSX attribute string. */
72
+ title: z.string().min(1).optional()
73
+ }).strict().optional(),
74
+ /** Greeting shown while the thread is empty (no messages yet) — the
75
+ * proven "welcome screen" pattern (Intercom-class): title + optional
76
+ * description + optional icon above, with `capabilities.starters`'
77
+ * chips and the composer still rendering below (ChatThread's `empty`
78
+ * REPLACE slot only stands in for the empty MESSAGE LIST — see
79
+ * chat-thread.tsx's own doc comment on `empty` — so the chips are
80
+ * never lost). Construct-wide like `header`, not a capability: it's a
81
+ * fact about the empty state, not a toggleable affordance.
82
+ * `title`/`description` are construct-authored/untrusted text, like
83
+ * `header.title`/`theme.accent`/`provider.url` — JSON.stringify'd at
84
+ * their one emit site, never a raw JSX attribute string. `icon` is a
85
+ * URL reaching an `<img src>` sink in emitted code, exactly like
86
+ * `widget.launcherIcon` — same `isSafeUrl` policy, same superRefine
87
+ * shape, below. */
88
+ empty: z.object({
89
+ title: z.string().min(1),
90
+ description: z.string().min(1).optional(),
91
+ icon: z.string().min(1).optional()
92
+ }).strict().optional(),
93
+ // Capability vocabulary, widened one field at a time by later tasks.
94
+ capabilities: z.object({
95
+ /** Starter prompts shown on the empty thread; clicking one sends it.
96
+ * 1-6 non-empty strings — construct-authored text, escaped like
97
+ * `theme.accent`/`provider.url` at every emit interpolation site. */
98
+ starters: z.array(z.string().min(1)).min(1).max(6).optional(),
99
+ /** Enables the paperclip attach affordance; accept is a non-empty
100
+ * list of media types/globs, e.g. ["image/*", "application/pdf"] —
101
+ * WHETHER stays with the construct author (this field), HOW stays
102
+ * with the kit (ChatThread's own attach/accept props, threaded
103
+ * through by codegen). */
104
+ attachments: z.object({
105
+ /** Accept-list of media types/globs, e.g. ["image/*", "application/pdf"]. */
106
+ accept: z.array(z.string().min(1)).min(1)
107
+ }).strict().optional(),
108
+ /** Conversation persistence. `none` (default, nothing emitted): the
109
+ * thread lives only in memory for the tab's lifetime. `local`:
110
+ * persisted to this browser's localStorage, keyed by the construct's
111
+ * tag — a mechanism decision (WHERE); what to retain and for how
112
+ * long stays an app decision (component-scope-boundary), so no
113
+ * retention count/quota lands here. `endpoint`: the CONSUMER's own
114
+ * thread route (GET returns ChatMessage[], PUT stores them) —
115
+ * requires `url`; `url` is rejected for any other persistence
116
+ * (superRefine below, both directions loud). */
117
+ history: z.object({
118
+ persistence: z.enum(["none", "local", "endpoint"]),
119
+ /** endpoint persistence only: the CONSUMER's thread routes (GET returns
120
+ * ChatMessage[], PUT stores them). Refined below. */
121
+ url: z.string().min(1).optional()
122
+ }).strict().optional(),
123
+ /** How the model's thinking (reasoning parts) renders. `'full'`
124
+ * (the default when omitted — see codegen.ts's emitReasoningProp)
125
+ * is the collapsible "Thinking" disclosure, shimmering while it
126
+ * streams. `'compact'` shows only a shimmer/typing loader while
127
+ * reasoning streams, with no expandable detail. `'off'` hides
128
+ * reasoning entirely. This is HOW an existing medium fact (the
129
+ * model's thinking) displays — the kit's call — so it maps straight
130
+ * onto ChatThread's own `reasoning` prop; there is no app-layer
131
+ * quota or retention decision hiding in it. Left `.optional()`
132
+ * rather than `.default('full')`, matching every sibling field in
133
+ * this object: a zod `.default()` here would make `reasoning`
134
+ * REQUIRED on the inferred input type (z.infer is the output type),
135
+ * breaking every `capabilities: {...}` object literal in this file
136
+ * and its tests that doesn't mention it. */
137
+ reasoning: z.enum(["full", "compact", "off"]).optional(),
138
+ /** Seeds the reasoning disclosure open AND keeps it tracking the
139
+ * stream (open while streaming, closes when it settles) — the
140
+ * pre-Task-19f `full` behavior. Default false/absent: the panel
141
+ * starts closed (just the "Thinking" shimmer chip) and only opens
142
+ * on click — the current default (owner ruling, 2026-08-26).
143
+ * Meaningless when `reasoning` is `'compact'` (no expandable
144
+ * content exists) or `'off'` (nothing renders); rejected on both,
145
+ * loudly, below. */
146
+ reasoningOpen: z.boolean().optional()
147
+ }).strict().optional(),
148
+ /** Named generative-UI card definitions the model can emit as tool calls,
149
+ * rendered in the thread. Top-level (not a capability): a card is
150
+ * something the construct's model can DO at any point once a card is
151
+ * declared, not a thread-affordance toggle like starters/attachments/
152
+ * reasoning. Reuses the kit's own card-tool projection end to end
153
+ * (`cardTools`/`toOpenAITools`/`toAnthropicTools`, `@kitn.ai/ui/schemas`)
154
+ * — never a second one authored here. `schema` is validated structurally
155
+ * only; deep card validation (incl. `x-kai-format` mask hints) is the
156
+ * kit's own card contract at render time. */
157
+ cards: z.array(
158
+ z.object({
159
+ /** Tool-facing card name, e.g. "refund_approval". */
160
+ name: z.string().regex(/^[a-z][a-z0-9_]*$/),
161
+ /** The kit's card schema JSON (incl. x-kai-format mask hints).
162
+ * Validated structurally here; deep card validation is the kit's
163
+ * own card contract at render time. */
164
+ schema: z.record(z.string(), z.unknown())
165
+ }).strict()
166
+ ).min(1).optional(),
167
+ /** Named `<slot>` projection points the emitted web component exposes —
168
+ * the format's ONLY escape hatch (named slots, no code-in-JSON). Each
169
+ * name must be a valid HTML slot-attribute value AND a legible
170
+ * identifier: kebab-case, starting with a letter (the same shape as a
171
+ * CSS custom-ident) — `slot name="Header!"` is rejected, not sanitized,
172
+ * matching the format's loud-rejection discipline everywhere else.
173
+ * 1-8 entries, no duplicates (superRefine below — a regex alone can't
174
+ * see across array entries). `layout: 'custom'` requires at least one
175
+ * declared slot (superRefine below): `custom` IS the slots grain — a
176
+ * `custom` layout with nothing to project into is not meaningfully
177
+ * different from `fullscreen`. */
178
+ slots: z.array(z.string().regex(/^[a-z][a-z0-9-]*$/, "slot names must be kebab-case, starting with a letter")).min(1).max(8).optional(),
179
+ /** Layout-scoped FAB chrome, `layout: 'widget'` only (superRefine below).
180
+ * Purely additive sibling to `layout` (widen-never-restructure) — mirrors
181
+ * Dock's own props verbatim, threaded through by codegen's emitLayoutOpen. */
182
+ widget: z.object({
183
+ /** Which corner. Mirrors Dock's own DockPosition enum verbatim — not a
184
+ * new left/right binary. Omitted keeps Dock's own default (bottom-end). */
185
+ position: z.enum(["bottom-end", "bottom-start", "top-end", "top-start"]).optional(),
186
+ /** An image URL for the closed-state launcher glyph, replacing Dock's
187
+ * built-in chat-bubble icon. Construct-authored/untrusted text, like
188
+ * theme.accent and provider.url — escaped the same way at its one emit site. */
189
+ launcherIcon: z.string().min(1).optional(),
190
+ /** Seed Dock's open state at mount. Uncontrolled — never steals focus (Dock's
191
+ * own focus contract, dock.tsx). Omitted keeps Dock's own default (closed). */
192
+ defaultOpen: z.boolean().optional()
193
+ }).strict().optional()
194
+ }).strict().superRefine((construct, ctx) => {
195
+ if (construct.slots) {
196
+ const seen = /* @__PURE__ */ new Set();
197
+ construct.slots.forEach((name, i) => {
198
+ if (seen.has(name)) {
199
+ ctx.addIssue({
200
+ code: z.ZodIssueCode.custom,
201
+ path: ["slots", i],
202
+ message: `duplicate slot name "${name}"`
203
+ });
204
+ }
205
+ seen.add(name);
206
+ });
207
+ }
208
+ if (construct.layout === "custom" && (!construct.slots || construct.slots.length === 0)) {
209
+ ctx.addIssue({
210
+ code: z.ZodIssueCode.custom,
211
+ path: ["slots"],
212
+ message: '"custom" layout requires at least one declared slot — custom IS the slots grain'
213
+ });
214
+ }
215
+ if (construct.layout === "split" && construct.slots) {
216
+ const i = construct.slots.indexOf("pane");
217
+ if (i !== -1) {
218
+ ctx.addIssue({
219
+ code: z.ZodIssueCode.custom,
220
+ path: ["slots", i],
221
+ message: `"pane" collides with the "split" layout's own fixed <slot name="pane"> — choose a different slot name`
222
+ });
223
+ }
224
+ }
225
+ if (construct.widget && construct.layout !== "widget") {
226
+ ctx.addIssue({
227
+ code: z.ZodIssueCode.custom,
228
+ path: ["widget"],
229
+ message: '"widget" is only valid on layout: "widget"'
230
+ });
231
+ }
232
+ if (construct.widget?.launcherIcon && !isSafeUrl(construct.widget.launcherIcon)) {
233
+ ctx.addIssue({
234
+ code: z.ZodIssueCode.custom,
235
+ path: ["widget", "launcherIcon"],
236
+ message: "launcherIcon must be an http(s)/mailto or relative URL — no javascript:/data: schemes"
237
+ });
238
+ }
239
+ if (construct.empty?.icon && !isSafeUrl(construct.empty.icon)) {
240
+ ctx.addIssue({
241
+ code: z.ZodIssueCode.custom,
242
+ path: ["empty", "icon"],
243
+ message: "icon must be an http(s)/mailto or relative URL — no javascript:/data: schemes"
244
+ });
245
+ }
246
+ const reasoning = construct.capabilities?.reasoning;
247
+ const reasoningOpen = construct.capabilities?.reasoningOpen;
248
+ if (reasoningOpen !== void 0 && (reasoning === "compact" || reasoning === "off")) {
249
+ ctx.addIssue({
250
+ code: z.ZodIssueCode.custom,
251
+ path: ["capabilities", "reasoningOpen"],
252
+ message: '"reasoningOpen" only applies when reasoning is "full" or omitted — "compact"/"off" have no disclosure to open'
253
+ });
254
+ }
255
+ const history = construct.capabilities?.history;
256
+ if (!history) return;
257
+ if (history.persistence === "endpoint" && !history.url) {
258
+ ctx.addIssue({
259
+ code: z.ZodIssueCode.custom,
260
+ path: ["capabilities", "history", "url"],
261
+ message: '"endpoint" persistence requires a url'
262
+ });
263
+ }
264
+ if (history.persistence !== "endpoint" && history.url !== void 0) {
265
+ ctx.addIssue({
266
+ code: z.ZodIssueCode.custom,
267
+ path: ["capabilities", "history", "url"],
268
+ message: 'url is only valid with "endpoint" persistence'
269
+ });
270
+ }
271
+ });
272
+ function validateConstruct(input) {
273
+ const parsed = ConstructSchema.safeParse(input);
274
+ if (parsed.success) return { ok: true, construct: parsed.data };
275
+ return {
276
+ ok: false,
277
+ problems: parsed.error.issues.flatMap((issue) => {
278
+ if (issue.code === "unrecognized_keys") {
279
+ return issue.keys.map((key) => ({
280
+ path: [...issue.path.map(String), key].join("."),
281
+ message: `"${key}" is not construct vocabulary`
282
+ }));
283
+ }
284
+ return [
285
+ {
286
+ path: issue.path.map(String).join("."),
287
+ message: issue.message
288
+ }
289
+ ];
290
+ })
291
+ };
292
+ }
293
+ function kitVersion() {
294
+ const require2 = createRequire(import.meta.url);
295
+ const pkg = require2("@kitn.ai/ui/package.json");
296
+ return pkg.version;
297
+ }
298
+ const themeMode = (c) => c.theme?.mode === "light" ? "light" : c.theme?.mode === "dark" ? "dark" : "auto";
299
+ function srgbChannelToLinear(c) {
300
+ const cs = c / 255;
301
+ return cs <= 0.03928 ? cs / 12.92 : Math.pow((cs + 0.055) / 1.055, 2.4);
302
+ }
303
+ function relativeLuminance(r, g, b) {
304
+ return 0.2126 * srgbChannelToLinear(r) + 0.7152 * srgbChannelToLinear(g) + 0.0722 * srgbChannelToLinear(b);
305
+ }
306
+ function hslToRgb(h, s, l) {
307
+ const hue = (h % 360 + 360) % 360;
308
+ const chroma = (1 - Math.abs(2 * l - 1)) * s;
309
+ const x = chroma * (1 - Math.abs(hue / 60 % 2 - 1));
310
+ const m = l - chroma / 2;
311
+ const [r1, g1, b1] = hue < 60 ? [chroma, x, 0] : hue < 120 ? [x, chroma, 0] : hue < 180 ? [0, chroma, x] : hue < 240 ? [0, x, chroma] : hue < 300 ? [x, 0, chroma] : [chroma, 0, x];
312
+ return [Math.round((r1 + m) * 255), Math.round((g1 + m) * 255), Math.round((b1 + m) * 255)];
313
+ }
314
+ function parseAccentRgb(accent) {
315
+ const s = accent.trim();
316
+ const hex3 = /^#([0-9a-fA-F]{3})$/.exec(s);
317
+ if (hex3) {
318
+ const [r, g, b] = hex3[1].split("").map((ch) => parseInt(ch + ch, 16));
319
+ return [r, g, b];
320
+ }
321
+ const hex6 = /^#([0-9a-fA-F]{6})([0-9a-fA-F]{2})?$/.exec(s);
322
+ if (hex6) {
323
+ const hex = hex6[1];
324
+ return [parseInt(hex.slice(0, 2), 16), parseInt(hex.slice(2, 4), 16), parseInt(hex.slice(4, 6), 16)];
325
+ }
326
+ const rgbFn = /^rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*[\d.]+\s*)?\)$/.exec(s);
327
+ if (rgbFn) {
328
+ const [r, g, b] = [rgbFn[1], rgbFn[2], rgbFn[3]].map(Number);
329
+ return [r, g, b].every((v) => v >= 0 && v <= 255) ? [r, g, b] : null;
330
+ }
331
+ const hslFn = /^hsla?\(\s*([\d.]+)\s*,\s*([\d.]+)%\s*,\s*([\d.]+)%\s*(?:,\s*[\d.]+\s*)?\)$/.exec(s);
332
+ if (hslFn) {
333
+ return hslToRgb(Number(hslFn[1]), Number(hslFn[2]) / 100, Number(hslFn[3]) / 100);
334
+ }
335
+ return null;
336
+ }
337
+ function resolveContrastForeground(accent) {
338
+ const rgb = parseAccentRgb(accent);
339
+ if (!rgb) return null;
340
+ const luminance = relativeLuminance(...rgb);
341
+ return luminance <= 0.5 ? "#ffffff" : "#000000";
342
+ }
343
+ function commentSafe(text) {
344
+ return text.replace(/[\r\n]/g, " ").replace(/\*\//g, "* /");
345
+ }
346
+ const CONTRAST_COLOR_SUPPORTS = "@supports (color: contrast-color(red))";
347
+ function accentContrastNotice(construct) {
348
+ const accent = construct.theme?.accent;
349
+ if (!accent || resolveContrastForeground(accent) !== null) return null;
350
+ return `accent '${commentSafe(accent)}' not parseable for contrast; foreground left at theme default in browsers without CSS contrast-color() support`;
351
+ }
352
+ function generateProject(construct, opts = {}) {
353
+ const uiSpec = opts.uiSpec ?? `^${kitVersion()}`;
354
+ const files = [
355
+ { path: "package.json", code: emitPackageJson(construct, uiSpec) },
356
+ { path: "tsconfig.json", code: emitTsconfig() },
357
+ { path: "vite.config.ts", code: emitViteDev() },
358
+ { path: "vite.config.lib.ts", code: emitViteLib(construct) },
359
+ { path: "index.html", code: emitIndexHtml(construct) },
360
+ { path: "src/element.tsx", code: emitElement(construct) },
361
+ { path: "src/App.tsx", code: emitApp(construct) }
362
+ ];
363
+ if (construct.cards) files.push({ path: "src/cards.ts", code: emitCardsRegistry(construct.cards) });
364
+ return files;
365
+ }
366
+ function emitCardsRegistry(cards) {
367
+ const entries = cards.map((card) => ` ${card.name}: ${JSON.stringify(card.schema, null, 2).split("\n").join("\n ")},`).join("\n");
368
+ return `// src/cards.ts — the construct's card registry, verbatim from the construct.
369
+ // Tool definitions for YOUR backend derive from this same object via
370
+ // @kitn.ai/ui/schemas (cardTools / toOpenAITools / toAnthropicTools) — one
371
+ // projection, shared with the kit.
372
+ export const cards = {
373
+ ${entries}
374
+ } as const;
375
+ `;
376
+ }
377
+ function emitCardComponentImport(c) {
378
+ return c.cards ? ", BUILTIN_CARD_COMPONENTS" : "";
379
+ }
380
+ function emitCardsImport(c) {
381
+ if (!c.cards) return "";
382
+ const toolsImport = c.provider.mode === "endpoint" ? c.provider.wire === "openai" ? ", toOpenAITools" : ", toAnthropicTools" : "";
383
+ return `import { cards } from './cards';
384
+ // Generative-UI cards, v1: every declared card renders as the kit's own
385
+ // schema-driven FORM (BUILTIN_CARD_COMPONENTS.form, components/form.tsx) — it
386
+ // walks the card's JSON Schema into real input fields, honoring
387
+ // x-kai-format/x-kai-mask/x-kai-mask-guide hints itself. ChatThread's own
388
+ // MessageBody already matches \`part.type === 'card'\` in its part rendering and
389
+ // draws it with the kit's own \`CardRenderer\` (components/card-renderer.tsx),
390
+ // which picks the component from \`cardTypes\` (below) by envelope.type — so
391
+ // there is nothing to hand-compose beyond that one map. Turning a model's tool
392
+ // call into that renderable part is \`cardFromToolCall\` (the inverse of
393
+ // \`cardTools\`), applied once per settled turn below; its data is then replaced
394
+ // with the DECLARED card schema (not the model's call arguments) — the fields
395
+ // on screen are the construct's own vocabulary, not whatever shape a model
396
+ // happened to send.
397
+ //
398
+ // UPDATE (CD-1, owner ruling 2026-08-26, Task 19g): the field SHAPE (title/
399
+ // type/widget/validation) still comes from the construct's own declared
400
+ // schema, never the model's — that part is unchanged. But discarding the
401
+ // model's call arguments wholesale also threw away any VALUE it wanted to
402
+ // pre-fill, breaking "model proposes, user confirms" (kai_refund_approval
403
+ // {amount:50} rendered an empty form). So the model's args are now
404
+ // shallow-merged onto the declared schema's field \`default\`s below
405
+ // (mergeToolArgsIntoFormDefaults) before the card is added — see
406
+ // emitApplyCardTools.
407
+ import { cardFromToolCall${toolsImport} } from '@kitn.ai/ui/schemas';
408
+
409
+ // Every declared card name routes to the SAME form renderer — cardFromToolCall
410
+ // makes envelope.type equal the card's own name (kai_refund_approval ->
411
+ // 'refund_approval'), and CardRenderer resolves a type's component from this
412
+ // map.
413
+ //
414
+ // Deliberately NOT also wiring ChatThread's \`cardSchemas\` prop to this
415
+ // registry below. That prop validates envelope.data AGAINST the named schema,
416
+ // and this card's data IS \`cards[name]\` itself (see emitApplyCardTools) — the
417
+ // construct's declared field schema, not values shaped like it. Wiring it as
418
+ // its own validator asks "does this FormDefinition itself have an \`amount\`
419
+ // key" and a well-formed FormDefinition never does, so every card would
420
+ // render the HARD validation-failure fallback instead of the form (caught
421
+ // live: eject + kai dev showed exactly that "(root).amount: required"
422
+ // failure before this comment existed). The construct's own schema.ts
423
+ // already checks \`cards\` structurally at validate time; there is nothing
424
+ // left for a second, self-referential check here to catch.
425
+ const cardTypes = Object.fromEntries(Object.keys(cards).map((name) => [name, BUILTIN_CARD_COMPONENTS.form] as const));
426
+
427
+ // CD-1 (owner ruling 2026-08-26, Task 19g): shallow-merge a model's card
428
+ // tool-call args onto a DECLARED form schema's field defaults — "model
429
+ // proposes, user confirms". Only top-level keys the args AND the schema both
430
+ // name are touched; the schema's own field shape (title/type/widget/
431
+ // validation) is never altered, and a key the model sent that isn't a
432
+ // declared field is ignored (the construct's vocabulary wins, not the
433
+ // model's). One level deep only — a nested object field's own defaults are
434
+ // not recursed into; no evidence of need yet (vocabulary-on-evidence).
435
+ function mergeToolArgsIntoFormDefaults(
436
+ schema: Record<string, unknown> & { properties?: Record<string, unknown> },
437
+ args: Record<string, unknown>,
438
+ ): Record<string, unknown> {
439
+ const declared = schema.properties;
440
+ if (!declared || typeof declared !== 'object') return schema;
441
+ const patched: Record<string, unknown> = {};
442
+ for (const [key, value] of Object.entries(args)) {
443
+ if (!(key in declared)) continue;
444
+ patched[key] = { ...(declared[key] as Record<string, unknown>), default: value };
445
+ }
446
+ return { ...schema, properties: { ...schema.properties, ...patched } };
447
+ }`;
448
+ }
449
+ function emitCardTypesProp(c) {
450
+ return c.cards ? " cardTypes={cardTypes}" : "";
451
+ }
452
+ function emitApplyCardTools(c) {
453
+ if (!c.cards) return "";
454
+ return `
455
+ for (const part of chat.messages().find((m) => m.id === stream.id)?.parts ?? []) {
456
+ if (part.type !== 'tool' || part.tool.state !== 'input-available') continue;
457
+ const card = cardFromToolCall(part.tool.type, part.tool.input, { id: part.tool.toolCallId ?? crypto.randomUUID() });
458
+ if (card && card.type in cards) {
459
+ const declared = cards[card.type as keyof typeof cards];
460
+ const args = card.data as Record<string, unknown> | undefined;
461
+ const merged = args && typeof args === 'object'
462
+ ? mergeToolArgsIntoFormDefaults(declared, args)
463
+ : declared;
464
+ stream.addCard({ ...card, data: merged });
465
+ }
466
+ }`;
467
+ }
468
+ function emitToolsField(c) {
469
+ if (!c.cards || c.provider.mode !== "endpoint") return "";
470
+ const toolsFn = c.provider.wire === "openai" ? "toOpenAITools" : "toAnthropicTools";
471
+ return `, tools: ${toolsFn}(cards)`;
472
+ }
473
+ function emitPackageJson(c, uiSpec) {
474
+ return `${JSON.stringify(
475
+ {
476
+ name: c.name,
477
+ private: true,
478
+ type: "module",
479
+ scripts: {
480
+ dev: "vite",
481
+ build: "vite build --config vite.config.lib.ts",
482
+ typecheck: "tsc --noEmit"
483
+ },
484
+ dependencies: {
485
+ "@kitn.ai/ui": uiSpec,
486
+ "solid-js": "^1.9.0"
487
+ },
488
+ devDependencies: {
489
+ typescript: "^5.6.0",
490
+ vite: "^6.0.0",
491
+ "vite-plugin-solid": "^2.11.0"
492
+ }
493
+ },
494
+ null,
495
+ 2
496
+ )}
497
+ `;
498
+ }
499
+ function emitTsconfig() {
500
+ return `${JSON.stringify(
501
+ {
502
+ compilerOptions: {
503
+ target: "ES2022",
504
+ module: "ESNext",
505
+ moduleResolution: "bundler",
506
+ jsx: "preserve",
507
+ jsxImportSource: "solid-js",
508
+ strict: true,
509
+ noUnusedLocals: true,
510
+ skipLibCheck: true,
511
+ types: ["vite/client"]
512
+ },
513
+ include: ["src"]
514
+ },
515
+ null,
516
+ 2
517
+ )}
518
+ `;
519
+ }
520
+ function emitViteDev() {
521
+ return `import { defineConfig } from 'vite';
522
+ import solid from 'vite-plugin-solid';
523
+
524
+ export default defineConfig({ plugins: [solid()] });
525
+ `;
526
+ }
527
+ function emitViteLib(c) {
528
+ return `import { defineConfig } from 'vite';
529
+ import solid from 'vite-plugin-solid';
530
+
531
+ // kai compile: ONE self-registering .js. Everything is inlined (no externals):
532
+ // the consumer installs nothing but this output.
533
+ export default defineConfig({
534
+ plugins: [solid()],
535
+ build: {
536
+ lib: { entry: 'src/element.tsx', formats: ['es'], fileName: () => '${c.name}.js' },
537
+ },
538
+ });
539
+ `;
540
+ }
541
+ function emitIndexHtml(c) {
542
+ const hint = c.layout === "widget" ? `
543
+ <p style="position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); margin: 0; color: #94a3b8; font: 14px system-ui, sans-serif; text-align: center; max-width: 28rem; padding: 0 1rem;">This blank page stands in for your site. The chat widget is in the bottom-right corner.</p>` : "";
544
+ const slotDemo = (c.slots ?? []).map(
545
+ (name) => `
546
+ <div slot="${name}" style="padding: 0.5rem 1rem; font: 13px system-ui, sans-serif; color: #64748b;">Projected into slot "${name}" — replace with your own markup.</div>`
547
+ ).join("");
548
+ const body = slotDemo ? `
549
+ <${c.name}>${slotDemo}
550
+ </${c.name}>` : `
551
+ <${c.name}></${c.name}>`;
552
+ return `<!doctype html>
553
+ <html lang="en">
554
+ <head>
555
+ <meta charset="utf-8" />
556
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
557
+ <title>${c.name} — construct preview</title>
558
+ </head>
559
+ <body style="margin: 0;">${hint}${body}
560
+ <script type="module" src="/src/element.tsx"><\/script>
561
+ </body>
562
+ </html>
563
+ `;
564
+ }
565
+ function emitElement(c) {
566
+ const accent = c.theme?.accent;
567
+ if (!accent) {
568
+ const facade2 = "() => <App />";
569
+ return `import { defineWebComponent } from '@kitn.ai/ui/define';
570
+ import { App } from './App';
571
+
572
+ // The one facade. Interior stays pure Solid (no nested element registrations);
573
+ // the kit CSS is injected into the shadow root by defineWebComponent itself.
574
+ defineWebComponent('${c.name}', { theme: '${themeMode(c)}' as 'light' | 'dark' | 'auto' }, ${facade2});
575
+ `;
576
+ }
577
+ const foreground = resolveContrastForeground(accent);
578
+ const foregroundCss = foreground !== null ? `:host { --kai-color-primary-foreground: ${foreground}; }
579
+ ` : (
580
+ // Not guessed at: an unparseable accent (var(), a named color, a
581
+ // color-mix()/oklch() call, …) leaves NO base declaration, so the
582
+ // kit's own theme default stands — except in a browser new enough to
583
+ // resolve contrast-color() itself, where the @supports block below
584
+ // still gets it right natively.
585
+ `/* NOTICE: accent '${commentSafe(accent)}' not parseable for contrast at generation time; the paired foreground falls back to the theme default in browsers without CSS contrast-color() support. */
586
+ `
587
+ );
588
+ const styleText = foregroundCss + `${CONTRAST_COLOR_SUPPORTS} {
589
+ :host { --kai-color-primary-foreground: contrast-color(var(--kai-color-primary)); }
590
+ }`;
591
+ const facade = `(_props, ctx) => {
592
+ ctx.element.style.setProperty('--kai-color-primary', ${JSON.stringify(accent)});
593
+ return (
594
+ <>
595
+ <style>{${JSON.stringify(styleText)}}</style>
596
+ <App />
597
+ </>
598
+ );
599
+ }`;
600
+ return `import { defineWebComponent } from '@kitn.ai/ui/define';
601
+ import { App } from './App';
602
+
603
+ // The one facade. Interior stays pure Solid (no nested element registrations);
604
+ // the kit CSS is injected into the shadow root by defineWebComponent itself.
605
+ defineWebComponent('${c.name}', { theme: '${themeMode(c)}' as 'light' | 'dark' | 'auto' }, ${facade});
606
+ `;
607
+ }
608
+ function emitApp(c) {
609
+ if (c.layout === "custom") return emitCustomApp(c);
610
+ return `${emitSolidJsImport(c)}import { ChatThread, createKaiChat${emitLayoutImport(c)}${emitCardComponentImport(c)}${emitEmptyComponentImport(c)}${emitHeaderCloseImport(c)} } from '@kitn.ai/ui/solid';
611
+ import type { AttachmentData${emitHistoryTypeImport(c)} } from '@kitn.ai/ui/solid';
612
+ ${emitProviderImports(c)}
613
+ ${emitCardsImport(c)}
614
+
615
+ ${emitProviderSetup(c)}
616
+ ${emitHistorySetup(c)}
617
+
618
+ // ChatThread is the kit's own MOST-INTEGRATED chat surface — the same
619
+ // composition <kai-chat>'s facade renders (src/elements/chat.tsx). It owns
620
+ // the message list, the composer (padding, focus ring, the send button) and
621
+ // their layout AS ONE UNIT, so nothing here re-derives spacing, alignment or
622
+ // focus styling by hand: every prior version of this file that hand-composed
623
+ // Thread + PromptInput + Button was restating layout the kit already owns,
624
+ // and every visual defect the owner hit (flush composer, a clipped focus
625
+ // ring) traced back to that restatement. Composing ChatThread directly
626
+ // leaves NOTHING here to restate it with.
627
+ //
628
+ // Capability gating (format rule: an undeclared capability's affordance must
629
+ // be OFF). The construct schema carries ONE capability field so far
630
+ // (capabilities.starters, Task 8) — every other affordance below is gated to
631
+ // "off" unconditionally, not per-construct, until there's a field to gate ON.
632
+ // - webSearch / voice: real ChatThreadProps booleans, default OFF when
633
+ // omitted — set to \`false\` explicitly rather than left implicit, so the
634
+ // gating decision is visible in the emitted source, not just inferred
635
+ // from an absent prop.
636
+ // - suggestions: ChatThread ALREADY owns starter prompts end to end — its
637
+ // own \`suggestions\` prop renders the chips, hides them once
638
+ // \`messages\` is non-empty, and (default \`suggestionMode="submit"\`)
639
+ // calls \`onSubmit\` with the clicked text exactly like a typed submit.
640
+ // So capabilities.starters threads straight into that prop; there is
641
+ // nothing to hand-compose. Omitted (undefined) when no starters are
642
+ // declared, same off-by-default effect as the booleans above.
643
+ // - models: omitted (undefined) — no model switcher; no capabilities field yet.
644
+ // - attachments (the paperclip): gated via ChatThread's \`attach\`/\`accept\`
645
+ // props (kit gap closed — ChatThread forwards both to DefaultPromptInput,
646
+ // mirroring webSearch/voice). ChatThread ALREADY owns the whole
647
+ // round-trip end to end — the paperclip button, staged previews, staging
648
+ // each file as a data URI (never a blob object URL; see
649
+ // AttachmentData.url's doc in components/attachment-types.ts), and
650
+ // handing the staged list back via onSubmit's \`attachments\` — and its
651
+ // Message component ALREADY groups consecutive file parts into one
652
+ // attachment row (message.tsx). So there is nothing to hand-compose
653
+ // here, same lesson as suggestions above: hand-rolling a second picker or
654
+ // a second file-part renderer would restate what ChatThread/Message
655
+ // already own. capabilities.attachments threads straight into
656
+ // attach/accept; the only App.tsx-owned piece is folding the picked
657
+ // attachments into the outgoing message's parts at the submit site
658
+ // (see emitProviderSetup) since createKaiChat's own append/streamAssistant
659
+ // ops don't do that folding themselves.
660
+ // - reasoning: gated via ChatThread's own \`reasoning\` prop (kit gap closed
661
+ // — ChatThread forwards it to every MessageBody as \`reasoningMode\`,
662
+ // mirroring attach/accept). \`'full'\` is both the schema default and
663
+ // ChatThread's own default, so it and an absent field emit no prop at
664
+ // all — the SAME off-by-default convention as every other capability
665
+ // here, just anchored on the medium's existing default instead of an
666
+ // "off" value, since a reasoning disclosure is normal chat behavior, not
667
+ // an opt-in affordance like the paperclip or a starter chip.
668
+ // - empty (the welcome-screen greeting, Task 14): gated via ChatThread's
669
+ // own \`emptyContent\` prop, plain JSX rendered in the SAME shadow tree
670
+ // this file's App already composes ChatThread inside of (see
671
+ // emitEmptyContentProp's own doc for why that boundary needs no Portal
672
+ // at all). \`capabilities.starters\`' chips and the composer still render
673
+ // underneath it: ChatThread's own doc comment on \`emptyContent\` is
674
+ // explicit that it replaces only the empty MESSAGE LIST.
675
+ // - the widget close control (owner feedback on the live demo): a declared
676
+ // \`header.title\` on a \`widget\` layout gets its close button threaded
677
+ // into ChatThread's own header row via \`headerEndContent\`, wired back to
678
+ // Dock's \`controllerRef\` seam through a local closure — see
679
+ // emitDockCloseVar/emitHeaderEndContentProp's docs. No header means no
680
+ // row for it to sit in, so that case is untouched and Dock's own built-in
681
+ // mobile X keeps covering it.
682
+ export function App() {
683
+ ${emitDockCloseVar(c, " ")} return (
684
+ ${emitLayoutOpen(c)}${emitSlots(c.slots, " ")} <ChatThread messages={chat.messages()} loading={chat.loading()} placeholder="Ask anything" onSubmit={submit} webSearch={false} voice={false}${emitHeaderProp(c)}${emitHeaderEndContentProp(c)}${emitAttachProps(c)}${emitStartersProp(c)}${emitReasoningProp(c)}${emitReasoningOpenProp(c)}${emitEmptyContentProp(c)}${emitCardTypesProp(c)} />
685
+ ${emitLayoutClose(c)} );
686
+ }
687
+ `;
688
+ }
689
+ function emitCustomApp(c) {
690
+ const slots = c.slots ?? [];
691
+ const [headerSlot, ...restSlots] = slots;
692
+ const history = c.capabilities?.history;
693
+ const solidJsNames = ["createSignal", ...history && history.persistence !== "none" ? ["createEffect"] : []];
694
+ return `import { ${solidJsNames.join(", ")} } from 'solid-js';
695
+ import { Thread, PromptInput, PromptInputTextarea, PromptInputActions, Button, createKaiChat${emitCardComponentImport(c)} } from '@kitn.ai/ui/solid';
696
+ import type { AttachmentData${emitHistoryTypeImport(c)} } from '@kitn.ai/ui/solid';
697
+ ${emitProviderImports(c)}
698
+ ${emitCardsImport(c)}
699
+
700
+ ${emitProviderSetup(c)}
701
+ ${emitHistorySetup(c)}
702
+
703
+ // layout: custom — minimal chrome, no ChatThread/Dock/PaneGroup. The bare
704
+ // spine (Thread + PromptInput) plus the declared slots, positioned by hand so
705
+ // YOU own the surrounding DOM. Capabilities beyond the spine (starters,
706
+ // attachments, reasoning display-mode, reasoningOpen, header.title, empty)
707
+ // are NOT wired here in v1 — this file is the eject artifact; add them the
708
+ // way ChatThread composes them (components/chat-thread.tsx in the kit's own
709
+ // source) if this construct needs them on a custom layout.
710
+ export function App() {
711
+ const [value, setValue] = createSignal('');
712
+
713
+ const handleSubmit = () => {
714
+ const text = value();
715
+ if (!text.trim() || chat.loading()) return;
716
+ setValue('');
717
+ void submit({ value: text, attachments: [] });
718
+ };
719
+
720
+ return (
721
+ <div style={{ height: '100dvh', display: 'flex', 'flex-direction': 'column' }}>
722
+ ${emitSlots(headerSlot ? [headerSlot] : void 0, " ")} <Thread messages={chat.messages()} loading={chat.loading()} class="min-h-0 flex-1"${emitCardTypesProp(c)} />
723
+ {/* Slot placement rule: first declared slot above the thread, every other
724
+ declared slot below the composer, in declaration order. Reorder by
725
+ ejecting — this is the whole grain dimmer. */}
726
+ <PromptInput value={value()} onValueChange={setValue} isLoading={chat.loading()} onSubmit={handleSubmit}>
727
+ <PromptInputTextarea placeholder="Ask anything" />
728
+ <PromptInputActions>
729
+ <Button onClick={handleSubmit}>Send</Button>
730
+ </PromptInputActions>
731
+ </PromptInput>
732
+ ${emitSlots(restSlots, " ")} </div>
733
+ );
734
+ }
735
+ `;
736
+ }
737
+ function emitHeaderProp(c) {
738
+ const title = c.header?.title;
739
+ if (!title) return "";
740
+ return ` chatTitle={${JSON.stringify(title)}}`;
741
+ }
742
+ function emitEmptyContentProp(c) {
743
+ const empty = c.empty;
744
+ if (!empty) return "";
745
+ const title = `<EmptyTitle>{${JSON.stringify(empty.title)}}</EmptyTitle>`;
746
+ const icon = empty.icon ? `<EmptyMedia><img src={${JSON.stringify(empty.icon)}} alt="" style={{ width: '40px', height: '40px', 'border-radius': '9999px' }} /></EmptyMedia>` : "";
747
+ const description = empty.description ? `<EmptyDescription>{${JSON.stringify(empty.description)}}</EmptyDescription>` : "";
748
+ return ` emptyContent={<Empty><EmptyHeader>${icon}${title}${description}</EmptyHeader></Empty>}`;
749
+ }
750
+ function emitEmptyComponentImport(c) {
751
+ if (!c.empty) return "";
752
+ let names = ", Empty, EmptyHeader, EmptyTitle";
753
+ if (c.empty.icon) names += ", EmptyMedia";
754
+ if (c.empty.description) names += ", EmptyDescription";
755
+ return names;
756
+ }
757
+ function widgetHasHeaderClose(c) {
758
+ return c.layout === "widget" && !!c.header?.title;
759
+ }
760
+ function emitDockCloseVar(c, indent) {
761
+ return widgetHasHeaderClose(c) ? `${indent}let dockClose: (() => void) | undefined;
762
+ ` : "";
763
+ }
764
+ function emitDockControllerRef(c) {
765
+ return widgetHasHeaderClose(c) ? " controllerRef={(api) => (dockClose = () => api.setOpen(false))}" : "";
766
+ }
767
+ function emitDockHideClose(c) {
768
+ return widgetHasHeaderClose(c) ? " hideClose={true}" : "";
769
+ }
770
+ function emitHeaderEndContentProp(c) {
771
+ if (!widgetHasHeaderClose(c)) return "";
772
+ return ` headerEndContent={<Button variant="ghost" size="icon-sm" aria-label="Close ${c.name}" onClick={() => dockClose?.()}><DockCloseGlyph /></Button>}`;
773
+ }
774
+ function emitHeaderCloseImport(c) {
775
+ return widgetHasHeaderClose(c) ? ", Button, DockCloseGlyph" : "";
776
+ }
777
+ function emitAttachProps(c) {
778
+ const attachments = c.capabilities?.attachments;
779
+ if (!attachments) return " attach={false}";
780
+ return ` attach={true} accept={${JSON.stringify(attachments.accept.join(","))}}`;
781
+ }
782
+ function emitStartersProp(c) {
783
+ const starters = c.capabilities?.starters;
784
+ if (!starters || starters.length === 0) return "";
785
+ return ` suggestions={${JSON.stringify(starters)}}`;
786
+ }
787
+ function emitReasoningProp(c) {
788
+ const reasoning = c.capabilities?.reasoning;
789
+ if (!reasoning || reasoning === "full") return "";
790
+ return ` reasoning="${reasoning}"`;
791
+ }
792
+ function emitReasoningOpenProp(c) {
793
+ return c.capabilities?.reasoningOpen === true ? " reasoningOpen={true}" : "";
794
+ }
795
+ function emitSolidJsImport(c) {
796
+ const history = c.capabilities?.history;
797
+ if (!history || history.persistence === "none") return "";
798
+ return `import { createEffect } from 'solid-js';
799
+ `;
800
+ }
801
+ function emitHistoryTypeImport(c) {
802
+ const history = c.capabilities?.history;
803
+ if (!history || history.persistence === "none") return "";
804
+ return ", ChatMessage";
805
+ }
806
+ function emitUserIdHeaderEntry(c) {
807
+ return c.userId ? `, 'x-kai-user-id': ${JSON.stringify(c.userId)}` : "";
808
+ }
809
+ function emitHistorySetup(c) {
810
+ const history = c.capabilities?.history;
811
+ if (!history || history.persistence === "none") return "";
812
+ if (history.persistence === "local") {
813
+ const key = JSON.stringify(c.userId ? `kai:${c.name}:${c.userId}:thread` : `kai:${c.name}:thread`);
814
+ return `
815
+ // History: persisted locally in this browser, keyed by the element tag. What to
816
+ // retain and for how long is an app decision — clear the key to reset.
817
+ const THREAD_KEY = ${key};
818
+ try {
819
+ const saved = localStorage.getItem(THREAD_KEY);
820
+ if (saved) {
821
+ const parsed: unknown = JSON.parse(saved);
822
+ if (Array.isArray(parsed)) {
823
+ chat.setMessages(() => parsed as ChatMessage[]);
824
+ } else {
825
+ console.warn(\`[\${THREAD_KEY}] stored history was not an array; ignoring and starting fresh\`);
826
+ }
827
+ }
828
+ } catch { /* storage unavailable or corrupt: run in-memory */ }
829
+ createEffect(() => {
830
+ try {
831
+ localStorage.setItem(THREAD_KEY, JSON.stringify(chat.messages()));
832
+ } catch { /* storage unavailable: run in-memory */ }
833
+ });
834
+ `;
835
+ }
836
+ const url = JSON.stringify(history.url);
837
+ return `
838
+ // History: persisted to your endpoint (GET on mount, PUT on every change) —
839
+ // the kit PARSES, this app FETCHES; your route owns the storage and what to
840
+ // retain and for how long. \`hydrated\` guards the mount-load from immediately
841
+ // PUTting back what it just loaded, but still flips on a FAILED load — one
842
+ // offline/CORS/DNS blip degrades to "start fresh, keep saving", not
843
+ // "never save again".
844
+ let hydrated = false;
845
+ (async () => {
846
+ try {
847
+ const r = await fetch(${url}${c.userId ? `, { headers: { 'x-kai-user-id': ${JSON.stringify(c.userId)} } }` : ""});
848
+ const saved: unknown = r.ok ? await r.json() : [];
849
+ if (Array.isArray(saved)) {
850
+ chat.setMessages(() => saved as ChatMessage[]);
851
+ } else {
852
+ console.warn('history endpoint returned a non-array body; ignoring and starting fresh');
853
+ }
854
+ } catch (err) {
855
+ console.error('history endpoint GET failed; starting fresh (will keep saving)', err);
856
+ } finally {
857
+ hydrated = true;
858
+ }
859
+ })();
860
+ createEffect(() => {
861
+ const snapshot = chat.messages();
862
+ if (!hydrated) return;
863
+ fetch(${url}, {
864
+ method: 'PUT',
865
+ headers: { 'content-type': 'application/json'${emitUserIdHeaderEntry(c)} },
866
+ body: JSON.stringify(snapshot),
867
+ }).catch((err) => {
868
+ console.error('history endpoint PUT failed; this change was not persisted', err);
869
+ });
870
+ });
871
+ `;
872
+ }
873
+ function emitProviderImports(c) {
874
+ if (c.provider.mode === "mock") {
875
+ return `import { createMockResponder } from '@kitn.ai/ui/state';
876
+ import { readOpenAIStream } from '@kitn.ai/ui/wire';`;
877
+ }
878
+ const read = c.provider.wire === "openai" ? "readOpenAIStream" : "readAnthropicStream";
879
+ const encode = c.provider.wire === "openai" ? "toOpenAIMessages" : "toAnthropicMessages";
880
+ return `import { ${read}, ${encode} } from '@kitn.ai/ui/wire';`;
881
+ }
882
+ function emitProviderSetup(c) {
883
+ if (c.provider.mode === "mock") {
884
+ const cardsNote = c.cards ? `
885
+ // Cards demo keylessly: createMockResponder() can already SCRIPT a tool call
886
+ // (\`replies: [{ toolCalls: [...] }]\`, F-35), so a scripted turn calling
887
+ // \`kai_<card name>\` renders exactly like a live model's would, below.` : "";
888
+ return `// Provider seam: mock — keyless, streams locally, announces itself once.
889
+ // Swap for provider.mode "endpoint" in the construct and re-run kai dev; the
890
+ // generated fetch keeps this exact shape (the seam is the point).${cardsNote}
891
+ const respond = createMockResponder();
892
+ const chat = createKaiChat();
893
+
894
+ async function submit(detail: { value: string; attachments: AttachmentData[] }) {
895
+ if (!detail.value.trim() || chat.loading()) return;
896
+ chat.append({
897
+ id: crypto.randomUUID(),
898
+ role: 'user',
899
+ parts: [
900
+ { type: 'text', text: detail.value },
901
+ ...detail.attachments.map((attachment) => ({ type: 'file' as const, attachment })),
902
+ ],
903
+ });
904
+ const stream = chat.streamAssistant();
905
+ try {
906
+ await readOpenAIStream(respond(detail.value), stream);${emitApplyCardTools(c)}
907
+ stream.done();
908
+ } catch (err) {
909
+ stream.abort(err instanceof Error ? err.message : String(err));
910
+ }
911
+ }`;
912
+ }
913
+ const { url, wire } = c.provider;
914
+ const read = wire === "openai" ? "readOpenAIStream" : "readAnthropicStream";
915
+ const encode = wire === "openai" ? "toOpenAIMessages" : "toAnthropicMessages";
916
+ return `// Provider seam: YOUR endpoint (${wire} wire, see the fetch call below
917
+ // for the URL). The kit PARSES, this component FETCHES — no key, no
918
+ // provider SDK, no client in here. Your route holds the key and re-frames to
919
+ // the provider; the kai MCP scaffold tool emits one for your framework.
920
+ const chat = createKaiChat();
921
+
922
+ async function submit(detail: { value: string; attachments: AttachmentData[] }) {
923
+ if (!detail.value.trim() || chat.loading()) return;
924
+ chat.append({
925
+ id: crypto.randomUUID(),
926
+ role: 'user',
927
+ parts: [
928
+ { type: 'text', text: detail.value },
929
+ ...detail.attachments.map((attachment) => ({ type: 'file' as const, attachment })),
930
+ ],
931
+ });
932
+ const stream = chat.streamAssistant();
933
+ try {
934
+ const response = await fetch(${JSON.stringify(url)}, {
935
+ method: 'POST',
936
+ headers: { 'content-type': 'application/json'${emitUserIdHeaderEntry(c)} },
937
+ body: JSON.stringify({ messages: ${encode}(chat.messages())${emitToolsField(c)} }),
938
+ });
939
+ if (!response.ok) throw new Error(\`endpoint responded \${response.status}\`);
940
+ await ${read}(response, stream);${emitApplyCardTools(c)}
941
+ stream.done();
942
+ } catch (err) {
943
+ stream.abort(err instanceof Error ? err.message : String(err));
944
+ }
945
+ }`;
946
+ }
947
+ function emitLayoutImport(c) {
948
+ switch (c.layout) {
949
+ case "widget":
950
+ return ", Dock";
951
+ case "split":
952
+ return ", WorkspaceShell";
953
+ case "fullscreen":
954
+ case "aside":
955
+ return "";
956
+ case "custom":
957
+ return "";
958
+ }
959
+ }
960
+ function emitSlots(slots, indent) {
961
+ if (!slots || slots.length === 0) return "";
962
+ return slots.map((name) => `${indent}<slot name="${name}" />
963
+ `).join("");
964
+ }
965
+ function emitDockPosition(c) {
966
+ const w = c.layout === "widget" ? c.widget : void 0;
967
+ return w?.position ? ` position="${w.position}"` : "";
968
+ }
969
+ function emitDockLauncher(c) {
970
+ const w = c.layout === "widget" ? c.widget : void 0;
971
+ if (!w?.launcherIcon) return "";
972
+ return ` launcher={<img src={${JSON.stringify(w.launcherIcon)}} alt="" style={{ width: '24px', height: '24px', 'border-radius': '9999px' }} />}`;
973
+ }
974
+ function emitDockDefaultOpen(c) {
975
+ const w = c.layout === "widget" ? c.widget : void 0;
976
+ return w?.defaultOpen === true ? " defaultOpen={true}" : "";
977
+ }
978
+ function emitLayoutOpen(c) {
979
+ switch (c.layout) {
980
+ case "widget":
981
+ return ` <Dock label="${c.name}"${emitDockPosition(c)}${emitDockLauncher(c)}${emitDockDefaultOpen(c)}${emitDockHideClose(c)}${emitDockControllerRef(c)}>
982
+ `;
983
+ case "fullscreen":
984
+ return ` <div style={{ height: '100dvh', display: 'flex', 'flex-direction': 'column' }}>
985
+ `;
986
+ case "aside":
987
+ return ` <aside data-kai-layout="aside" style={{ position: 'fixed', 'inset-block': '0', 'inset-inline-end': '0', width: '380px', display: 'flex', 'flex-direction': 'column', 'border-inline-start': '1px solid var(--kai-color-border)' }}>
988
+ {/* Mirrors Dock's own narrow-viewport full-bleed rule (ui/dock.tsx:229-240)
989
+ — aside has no dedicated kit component (see the emitLayoutOpen doc
990
+ comment above), so this is the honest hand-rolled equivalent, not a
991
+ new responsive strategy. */}
992
+ <style>{\`@media (max-width: 480px) { [data-kai-layout="aside"] { inset: 0; width: auto; height: auto; border-inline-start: 0; } }\`}</style>
993
+ `;
994
+ case "split":
995
+ return ` <div style={{ height: '100dvh' }}>
996
+ <WorkspaceShell class="h-full" drawerBelow={480} end={
997
+ <div style={{ height: '100%', overflow: 'auto' }}>
998
+ <slot name="pane" />
999
+ </div>
1000
+ }>
1001
+ `;
1002
+ case "custom":
1003
+ return "";
1004
+ }
1005
+ }
1006
+ function emitLayoutClose(c) {
1007
+ switch (c.layout) {
1008
+ case "widget":
1009
+ return ` </Dock>
1010
+ `;
1011
+ case "fullscreen":
1012
+ return ` </div>
1013
+ `;
1014
+ case "aside":
1015
+ return ` </aside>
1016
+ `;
1017
+ case "split":
1018
+ return ` </WorkspaceShell>
1019
+ </div>
1020
+ `;
1021
+ case "custom":
1022
+ return "";
1023
+ }
1024
+ }
1025
+ function emitTypes(c) {
1026
+ return `declare global {
1027
+ interface HTMLElementTagNameMap {
1028
+ '${c.name}': HTMLElement & { theme: 'light' | 'dark' | 'auto' };
1029
+ }
1030
+ }
1031
+ export {};
1032
+ `;
1033
+ }
1034
+ const MANIFEST = ".kai-manifest.json";
1035
+ function writeProject(files, dir) {
1036
+ const manifestPath = join(dir, MANIFEST);
1037
+ const previous = existsSync(manifestPath) ? JSON.parse(readFileSync(manifestPath, "utf8")) : [];
1038
+ const current = new Set(files.map((f) => f.path));
1039
+ for (const stale of previous) {
1040
+ if (!current.has(stale)) rmSync(join(dir, stale), { force: true });
1041
+ }
1042
+ const overwritten = [];
1043
+ for (const f of files) {
1044
+ const abs = join(dir, f.path);
1045
+ if (existsSync(abs)) overwritten.push(f.path);
1046
+ mkdirSync(dirname(abs), { recursive: true });
1047
+ writeFileSync(abs, f.code);
1048
+ }
1049
+ writeFileSync(manifestPath, `${JSON.stringify([...current].sort(), null, 2)}
1050
+ `);
1051
+ return overwritten;
1052
+ }
1053
+ const defaultIo = { log: (s) => console.log(s), error: (s) => console.error(s) };
1054
+ const USAGE = `usage: kai <command>
1055
+
1056
+ kai validate <construct.json> check a construct, print problems with paths
1057
+ kai eject <construct.json> <outDir> write the generated Solid project (it's yours)
1058
+ kai dev <construct.json> live preview with reload-on-edit (Task 5)
1059
+ kai compile <construct.json> [outDir] one self-registering .js (Task 6)
1060
+ `;
1061
+ function loadConstruct(path, io) {
1062
+ const abs = resolve(path);
1063
+ let raw;
1064
+ try {
1065
+ raw = readFileSync(abs, "utf8");
1066
+ } catch {
1067
+ io.error(`cannot read ${abs}`);
1068
+ return null;
1069
+ }
1070
+ let json;
1071
+ try {
1072
+ json = JSON.parse(raw);
1073
+ } catch (err) {
1074
+ io.error(`${abs} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
1075
+ return null;
1076
+ }
1077
+ const out = validateConstruct(json);
1078
+ if (!out.ok) {
1079
+ io.error(`${abs} is not a valid construct:`);
1080
+ for (const p of out.problems) io.error(` ${p.path || "(root)"}: ${p.message}`);
1081
+ return null;
1082
+ }
1083
+ return out.construct;
1084
+ }
1085
+ function parseUiFlag(rest) {
1086
+ const uiFlag = rest.indexOf("--ui");
1087
+ const uiSpec = uiFlag >= 0 ? rest[uiFlag + 1] : void 0;
1088
+ const positional = uiFlag >= 0 ? rest.filter((_, i) => i !== uiFlag && i !== uiFlag + 1) : rest;
1089
+ return { uiSpec, positional };
1090
+ }
1091
+ async function runCli(argv, io = defaultIo) {
1092
+ const [command, ...rest] = argv;
1093
+ switch (command) {
1094
+ case "validate": {
1095
+ const construct = loadConstruct(rest[0] ?? "", io);
1096
+ if (!construct) return 1;
1097
+ io.log(`valid construct: <${construct.name}> (layout: ${construct.layout}, provider: ${construct.provider.mode})`);
1098
+ return 0;
1099
+ }
1100
+ case "eject": {
1101
+ const { uiSpec, positional } = parseUiFlag(rest);
1102
+ const [path, outDir] = positional;
1103
+ if (!path || !outDir) {
1104
+ io.error(USAGE);
1105
+ return 2;
1106
+ }
1107
+ const construct = loadConstruct(path, io);
1108
+ if (!construct) return 1;
1109
+ const overwritten = writeProject(generateProject(construct, { uiSpec }), resolve(outDir));
1110
+ if (overwritten.length > 0) {
1111
+ io.log(`overwriting ${overwritten.length} existing file(s)`);
1112
+ }
1113
+ const notice = accentContrastNotice(construct);
1114
+ if (notice) io.log(notice);
1115
+ io.log(`ejected <${construct.name}> to ${resolve(outDir)} — npm install && npm run dev. The source is yours.`);
1116
+ return 0;
1117
+ }
1118
+ case "dev": {
1119
+ const { uiSpec, positional } = parseUiFlag(rest);
1120
+ const path = positional[0];
1121
+ if (!path) {
1122
+ io.error(USAGE);
1123
+ return 2;
1124
+ }
1125
+ const { dev } = await import("./assets/dev-5XzXYR21.js");
1126
+ await dev(path, { io, uiSpec });
1127
+ return 0;
1128
+ }
1129
+ case "compile": {
1130
+ const { uiSpec, positional } = parseUiFlag(rest);
1131
+ const [path, outArg] = positional;
1132
+ if (!path) {
1133
+ io.error(USAGE);
1134
+ return 2;
1135
+ }
1136
+ const construct = loadConstruct(path, io);
1137
+ if (!construct) return 1;
1138
+ const outDir = resolve(outArg ?? "dist-construct");
1139
+ const { workDirFor, ensureInstalled } = await import("./assets/dev-5XzXYR21.js");
1140
+ const dir = workDirFor(construct.name, process.cwd());
1141
+ const files = generateProject(construct, { uiSpec });
1142
+ writeProject(files, dir);
1143
+ await ensureInstalled(dir, files, io);
1144
+ await new Promise((done, fail) => {
1145
+ const child = spawn("npm", ["run", "build"], { cwd: dir, stdio: "inherit" });
1146
+ child.on("exit", (code) => code === 0 ? done() : fail(new Error(`vite build exited ${code}`)));
1147
+ });
1148
+ mkdirSync(outDir, { recursive: true });
1149
+ copyFileSync(join(dir, "dist", `${construct.name}.js`), join(outDir, `${construct.name}.js`));
1150
+ writeFileSync(join(outDir, `${construct.name}.d.ts`), emitTypes(construct));
1151
+ writeProject(files, join(outDir, "source"));
1152
+ io.log(`compiled <${construct.name}> → ${outDir}/${construct.name}.js (source beside it in source/).`);
1153
+ io.log(`endpoint backends: the kai MCP scaffold tool emits a matching route — see its output for your framework.`);
1154
+ return 0;
1155
+ }
1156
+ default:
1157
+ io.error(USAGE);
1158
+ return 2;
1159
+ }
1160
+ }
1161
+ runCli(process.argv.slice(2)).then((code) => {
1162
+ process.exitCode = code;
1163
+ });
1164
+ export {
1165
+ accentContrastNotice as a,
1166
+ generateProject as g,
1167
+ validateConstruct as v,
1168
+ writeProject as w
1169
+ };