@kitn.ai/ui 0.25.2 → 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 (603) hide show
  1. package/README.md +17 -6
  2. package/bin/mcp.js +16 -3
  3. package/bin/route.js +23 -0
  4. package/bin/route.test.js +58 -0
  5. package/dist/{elements/chunks/Icon-b5A8hlHs.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/audio-visualizer-B-79y1Tj.js +1 -0
  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-B9okqG73.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/code-block-CUQ5mtv0.js +1 -0
  30. package/dist/collapsible-CPJbf6FM.js +1 -0
  31. package/dist/components/attachment-types.d.ts +53 -1
  32. package/dist/components/attachments.d.ts +31 -0
  33. package/dist/components/audio-visualizer/fit-scale.d.ts +50 -0
  34. package/dist/components/audio-visualizer/index.d.ts +15 -1
  35. package/dist/components/audio-visualizer/variant-bar.d.ts +24 -1
  36. package/dist/components/card-renderer.d.ts +13 -0
  37. package/dist/components/card.d.ts +4 -0
  38. package/dist/components/chat-thread.d.ts +49 -42
  39. package/dist/components/code-block.d.ts +22 -0
  40. package/dist/components/conversation-item.d.ts +66 -1
  41. package/dist/components/conversation-list.d.ts +77 -0
  42. package/dist/components/form-widgets.d.ts +67 -2
  43. package/dist/components/form.d.ts +89 -0
  44. package/dist/components/message.d.ts +29 -0
  45. package/dist/components/reasoning.d.ts +6 -0
  46. package/dist/components/scroll-button.d.ts +11 -0
  47. package/dist/components/thread.d.ts +5 -0
  48. package/dist/components/toast.d.ts +2 -1
  49. package/dist/components/tool-types.d.ts +9 -0
  50. package/dist/components/voice-input.d.ts +8 -0
  51. package/dist/components/voice-output.d.ts +12 -1
  52. package/dist/components/workspace-shell.d.ts +87 -0
  53. package/dist/composer-CjngLzal.js +106 -0
  54. package/dist/confirm-card-DCx_A4bU.js +1 -0
  55. package/dist/construct-cli.es.js +1169 -0
  56. package/dist/context-BY8V-Nlp.js +1 -0
  57. package/dist/controllable-D43BKmtX.js +1 -0
  58. package/dist/conversation-list-D4lLU_yS.js +1 -0
  59. package/dist/copy-B6DO4PqB.js +1 -0
  60. package/dist/core-AYMC6_lb.js +12 -5874
  61. package/dist/{create-tween-CUzOlaMS.js → create-tween-BPzaefTp.js} +1 -1
  62. package/dist/{create-tween-B5Y4Im8G.js → create-tween-CnMHviKB.js} +1 -1
  63. package/dist/{create-tween-4z1XYLkR.js → create-tween-D1aiGZZh.js} +1 -1
  64. package/dist/{elements/chunks/create-tween-CUrM6D_o.js → create-tween-DUSQUKpr.js} +2 -2
  65. package/dist/css-M7EaDHN_.js +1 -6
  66. package/dist/custom-elements.json +3468 -1927
  67. package/dist/default-input-CucQEOqH.js +1 -0
  68. package/dist/define-CGGX-7Vg.js +1 -0
  69. package/dist/define.d.ts +4 -0
  70. package/dist/define.js +510 -0
  71. package/dist/define.server.d.ts +5 -0
  72. package/dist/define.server.js +506 -0
  73. package/dist/diagnostics/hook.d.ts +81 -0
  74. package/dist/diagnostics/index.d.ts +7 -0
  75. package/dist/diagnostics/report-request.d.ts +61 -0
  76. package/dist/diagnostics.d.ts +5 -0
  77. package/dist/diagnostics.js +209 -0
  78. package/dist/disclosure-CbLtajUQ.js +1 -0
  79. package/dist/dropdown-BwhViJFd.js +1 -0
  80. package/dist/elements/agent-card.js +1 -1
  81. package/dist/elements/artifact.js +1 -1
  82. package/dist/elements/attachments.js +1 -1
  83. package/dist/elements/audio-visualizer.js +1 -1
  84. package/dist/elements/autoloader.js +1 -1
  85. package/dist/elements/avatar.js +1 -1
  86. package/dist/elements/badge.js +1 -1
  87. package/dist/elements/button.js +1 -1
  88. package/dist/elements/card.js +1 -1
  89. package/dist/elements/cards.js +1 -1
  90. package/dist/elements/chain-of-thought.js +1 -1
  91. package/dist/elements/chat-scope-picker.js +1 -1
  92. package/dist/elements/chat-workspace.js +1 -1
  93. package/dist/elements/chat.js +1 -1
  94. package/dist/elements/checkbox-group.d.ts +1 -0
  95. package/dist/elements/checkbox-group.js +1 -0
  96. package/dist/elements/checkbox.d.ts +1 -0
  97. package/dist/elements/checkbox.js +1 -0
  98. package/dist/elements/checkpoint.js +1 -1
  99. package/dist/elements/choice.js +1 -1
  100. package/dist/elements/coachmark.js +1 -1
  101. package/dist/elements/code-block.js +1 -1
  102. package/dist/elements/command.js +1 -1
  103. package/dist/elements/compare.js +1 -1
  104. package/dist/elements/composer.js +1 -1
  105. package/dist/elements/confirm-card.js +1 -1
  106. package/dist/elements/context-meter.js +1 -1
  107. package/dist/elements/conversation-item.d.ts +1 -0
  108. package/dist/elements/conversation-item.js +1 -0
  109. package/dist/elements/conversation-list.js +1 -1
  110. package/dist/elements/default-input.d.ts +3 -3
  111. package/dist/elements/define-entry.d.ts +12 -0
  112. package/dist/elements/define.d.ts +27 -0
  113. package/dist/elements/diagnostic-events.d.ts +74 -0
  114. package/dist/elements/dialog.js +1 -1
  115. package/dist/elements/dock.d.ts +1 -0
  116. package/dist/elements/dock.js +243 -0
  117. package/dist/elements/dropdown.d.ts +1 -0
  118. package/dist/elements/dropdown.js +1 -0
  119. package/dist/elements/editable-label.js +1 -1
  120. package/dist/elements/element-data-types.d.ts +37 -0
  121. package/dist/elements/element-diagnostics.d.ts +81 -0
  122. package/dist/elements/embed.js +1 -1
  123. package/dist/elements/empty.js +1 -1
  124. package/dist/elements/feedback-bar.js +1 -1
  125. package/dist/elements/file-tree.js +1 -1
  126. package/dist/elements/file-upload.js +1 -1
  127. package/dist/elements/form.js +1 -1
  128. package/dist/elements/hover-card.js +1 -1
  129. package/dist/elements/icon.js +1 -1
  130. package/dist/elements/image.js +1 -1
  131. package/dist/elements/input.js +1 -1
  132. package/dist/elements/kbd.js +1 -1
  133. package/dist/elements/link-preview.js +1 -1
  134. package/dist/elements/loader.js +1 -1
  135. package/dist/elements/markdown.js +1 -1
  136. package/dist/elements/menu.js +1 -1
  137. package/dist/elements/message-skills.js +1 -1
  138. package/dist/elements/message.js +1 -1
  139. package/dist/elements/model-switcher.js +1 -1
  140. package/dist/elements/nav.js +1 -1
  141. package/dist/elements/notice.js +1 -1
  142. package/dist/elements/pane-grid.d.ts +1 -0
  143. package/dist/elements/pane-grid.js +1 -0
  144. package/dist/elements/pane-group.js +1 -1
  145. package/dist/elements/pane.js +1 -1
  146. package/dist/elements/popover.js +1 -1
  147. package/dist/elements/progress-bar.js +1 -1
  148. package/dist/elements/prompt-dock.js +1 -1
  149. package/dist/elements/prompt-input.js +1 -1
  150. package/dist/elements/prompt-suggestions.js +1 -1
  151. package/dist/elements/radio-group.d.ts +1 -0
  152. package/dist/elements/radio-group.js +1 -0
  153. package/dist/elements/reasoning.js +1 -1
  154. package/dist/elements/register.d.ts +2 -0
  155. package/dist/elements/remote.js +1 -1
  156. package/dist/elements/resizable.js +1 -1
  157. package/dist/elements/response-stream.js +3 -3
  158. package/dist/elements/screen.js +1 -1
  159. package/dist/elements/scroll-area.js +1 -1
  160. package/dist/elements/scroll-button.js +1 -1
  161. package/dist/elements/search.js +1 -1
  162. package/dist/elements/segmented.js +1 -1
  163. package/dist/elements/select.d.ts +1 -0
  164. package/dist/elements/select.js +1 -0
  165. package/dist/elements/separator.js +1 -1
  166. package/dist/elements/setting-item.js +1 -1
  167. package/dist/elements/settings-group.js +1 -1
  168. package/dist/elements/skeleton.js +1 -1
  169. package/dist/elements/slider.d.ts +1 -0
  170. package/dist/elements/slider.js +1 -0
  171. package/dist/elements/slots.d.ts +36 -4
  172. package/dist/elements/source.js +1 -1
  173. package/dist/elements/status.js +1 -1
  174. package/dist/elements/switch.js +1 -1
  175. package/dist/elements/tabs.js +1 -1
  176. package/dist/elements/tasks.js +1 -1
  177. package/dist/elements/text-shimmer.js +1 -1
  178. package/dist/elements/thinking-bar.js +1 -1
  179. package/dist/elements/thread.js +1 -1
  180. package/dist/elements/toast.js +1 -1
  181. package/dist/elements/tool.js +1 -1
  182. package/dist/elements/tooltip.js +1 -1
  183. package/dist/elements/voice-input.js +1 -1
  184. package/dist/elements/voice-output.js +1 -1
  185. package/dist/elements.d.ts +589 -167
  186. package/dist/{elements/chunks/ellipsis-CVxqfsDZ.js → ellipsis-DFr4Vqr3.js} +1 -1
  187. package/dist/embed-5s_vU4J0.js +1 -0
  188. package/dist/engine-javascript-vq0WuIJl.js +14 -2516
  189. package/dist/{elements/chunks/external-link-DG2sRdQL.js → external-link-u9d85ORQ.js} +1 -1
  190. package/dist/file-text-CX7_x42o.js +1 -0
  191. package/dist/{elements/chunks/file-tree-yGWNK0Js.js → file-tree-D3kBjXI5.js} +1 -1
  192. package/dist/folder-V-YdIL8f.js +1 -0
  193. package/dist/form-BRNUMCN2.js +1 -0
  194. package/dist/github-dark-dimmed-DUshB20C.js +1 -4
  195. package/dist/github-light-JYsPkUQd.js +1 -4
  196. package/dist/hover-card-DaeiYrTA.js +1 -0
  197. package/dist/html-CPZ3oZQ7.js +1 -10
  198. package/dist/icon-BpixdZNq.js +1 -0
  199. package/dist/{elements/chunks/index-BCRhi84a.js → index-xu_pdcPi.js} +1 -1
  200. package/dist/index.d.ts +3 -1
  201. package/dist/index.js +8473 -6470
  202. package/dist/index.server.js +6495 -4958
  203. package/dist/info-8mFsIjS3.js +1 -0
  204. package/dist/input-d1Yuu-hz.js +1 -0
  205. package/dist/javascript-C25yR2R2.js +1 -6
  206. package/dist/json-DxJze_jm.js +1 -6
  207. package/dist/kai-provider.es.js +40 -40
  208. package/dist/kai.es.js +1 -1
  209. package/dist/kbd-961zMPFB.js +1 -0
  210. package/dist/link-DlZZBGaR.js +1 -0
  211. package/dist/{elements/chunks/link-preview-Cf5KOP-i.js → link-preview-DIIbV2Sd.js} +1 -1
  212. package/dist/loader-YQszHFaW.js +1 -0
  213. package/dist/{elements/chunks/markdown-DbozyYqa.js → markdown-CVk1MPsj.js} +1 -1
  214. package/dist/mcp.es.js +3873 -544
  215. package/dist/message-CwKVEdZg.js +1 -0
  216. package/dist/message-DS8lzHc1.js +1 -0
  217. package/dist/message-circle-Xf8Q-vZp.js +1 -0
  218. package/dist/message-square-Cxnwgkns.js +1 -0
  219. package/dist/{elements/chunks/minimize-2-SegJ_oku.js → minimize-2-UEZmP2QT.js} +1 -1
  220. package/dist/{elements/chunks/model-switcher-D7JrFFHp.js → model-switcher-Kk1jEQ5t.js} +1 -1
  221. package/dist/{elements/chunks/overlay-CV7z2YuX.js → overlay-C1od6LOz.js} +1 -1
  222. package/dist/panel-right-ryET0yyn.js +1 -0
  223. package/dist/paperclip-BDNoZFJP.js +1 -0
  224. package/dist/play-OFIvYXUQ.js +1 -0
  225. package/dist/primitives/card-data-types.d.ts +49 -0
  226. package/dist/primitives/card-routing.d.ts +1 -33
  227. package/dist/primitives/card-validate.d.ts +2 -2
  228. package/dist/primitives/chat-config.d.ts +29 -2
  229. package/dist/primitives/field-mask.d.ts +96 -0
  230. package/dist/primitives/field-semantics.d.ts +36 -0
  231. package/dist/primitives/input-mask.d.ts +46 -0
  232. package/dist/primitives/url-scheme-policy.d.ts +34 -0
  233. package/dist/primitives/use-resize-observer.d.ts +23 -0
  234. package/dist/primitives/use-speech-recognition.d.ts +8 -0
  235. package/dist/primitives/use-text-stream.d.ts +5 -0
  236. package/dist/progress-bar-BvYX0aoy.js +1 -0
  237. package/dist/prompt-suggestion-BRFu2lcu.js +1 -0
  238. package/dist/radio-DcL23cNJ.js +1 -0
  239. package/dist/react/index.d.ts +306 -283
  240. package/dist/react.js +309 -247
  241. package/dist/reasoning-Cackq4Rh.js +1 -0
  242. package/dist/register-impl-C7rENvPK.js +424 -0
  243. package/dist/{elements/chunks/resizable-Dpzjlvp4.js → resizable-Cp9HUYih.js} +1 -1
  244. package/dist/rotate-ccw-0DLRDHH5.js +1 -0
  245. package/dist/rotate-cw-DOB9IGcU.js +1 -0
  246. package/dist/schemas/form.schema.json +21 -1
  247. package/dist/schemas/index.d.ts +1 -1
  248. package/dist/schemas/tool-defs.d.ts +45 -0
  249. package/dist/schemas.js +398 -305
  250. package/dist/scroll-area-emVmWP4K.js +1 -0
  251. package/dist/scroll-button-CeP_gv1x.js +1 -0
  252. package/dist/select-CWmu00tS.js +1 -0
  253. package/dist/{elements/chunks/separator-DH2r_ZSZ.js → separator-CE6vly6z.js} +1 -1
  254. package/dist/{elements/chunks/settings-BFMOd6yB.js → settings-CE-78SvZ.js} +1 -1
  255. package/dist/{elements/chunks/settings-group-B3VZKIcz.js → settings-group-ZFWNxFQR.js} +1 -1
  256. package/dist/{elements/chunks/skeleton-N8BuWYmf.js → skeleton-BebdP4mf.js} +1 -1
  257. package/dist/slider-BAKRYUlz.js +1 -0
  258. package/dist/slots-DaNjX0rc.js +1 -0
  259. package/dist/{solid-BMuDmFo_.js → solid-BE5ui6sy.js} +10672 -8116
  260. package/dist/{solid-CGYNdLRR.js → solid-DFhsce3O.js} +8137 -6056
  261. package/dist/solid.d.ts +15 -0
  262. package/dist/solid.js +248 -235
  263. package/dist/solid.server.js +248 -235
  264. package/dist/source-B0FlguSx.js +1 -0
  265. package/dist/star-CBMZvLuN.js +1 -0
  266. package/dist/state/index.d.ts +5 -1
  267. package/dist/state/mock.d.ts +32 -2
  268. package/dist/state/persistence.d.ts +51 -0
  269. package/dist/state/stream.d.ts +50 -0
  270. package/dist/state/threads.d.ts +71 -0
  271. package/dist/state.js +297 -172
  272. package/dist/store-7eoadydQ.js +1 -0
  273. package/dist/svelte--5p79yCD.js +1 -15
  274. package/dist/switch-CViARZCy.js +1 -0
  275. package/dist/tasks-card-MjCcOhR_.js +1 -0
  276. package/dist/text-shimmer-DKlwRF2G.js +1 -0
  277. package/dist/textarea-3ay2Me2-.js +1 -0
  278. package/dist/theme.tokens.css +82 -13
  279. package/dist/{elements/chunks/thumbs-up-DQe0hvkZ.js → thumbs-up-DQcu2HMO.js} +1 -1
  280. package/dist/toast-store-C6yKOhbk.js +1 -0
  281. package/dist/{elements/chunks/tool-C1YtxwPu.js → tool-BCYNlfKy.js} +1 -1
  282. package/dist/{elements/chunks/tooltip-BqwnFWhn.js → tooltip-05zI4hjs.js} +1 -1
  283. package/dist/trash-2-BR1RxGky.js +1 -0
  284. package/dist/{elements/chunks/triangle-alert-B6or7F5u.js → triangle-alert-DHohDOCy.js} +1 -1
  285. package/dist/tsx-B8rCNbgL.js +1 -6
  286. package/dist/types.d.ts +4 -2
  287. package/dist/typescript-RycA9KXf.js +1 -6
  288. package/dist/ui/checkbox-group.d.ts +83 -0
  289. package/dist/ui/checkbox.d.ts +40 -0
  290. package/dist/ui/dock.d.ts +131 -0
  291. package/dist/ui/hover-card.d.ts +44 -0
  292. package/dist/ui/icon.d.ts +3 -0
  293. package/dist/ui/input.d.ts +48 -2
  294. package/dist/ui/radio.d.ts +85 -0
  295. package/dist/ui/select.d.ts +71 -0
  296. package/dist/ui/slider.d.ts +65 -0
  297. package/dist/ui/switch.d.ts +9 -2
  298. package/dist/upload-BbGW6A_u.js +1 -0
  299. package/dist/url-scheme-policy-DHpoTJwB.js +1 -0
  300. package/dist/use-card-resolution-CJW4grFa.js +1 -0
  301. package/dist/{variant-aurora-DwSOfraQ.js → variant-aurora-BUOVgPOB.js} +27 -27
  302. package/dist/{variant-aurora-wKvAQ5wp.js → variant-aurora-CUsUJvFI.js} +28 -28
  303. package/dist/{variant-aurora-XEkPT0_z.js → variant-aurora-CW2i4bfr.js} +2 -2
  304. package/dist/{elements/chunks/variant-aurora-QXTZPyST.js → variant-aurora-xiMPty8f.js} +2 -2
  305. package/dist/variant-custom-BQtOnmeO.js +1 -0
  306. package/dist/{variant-custom-BhS9wIpU.js → variant-custom-CAm9L-E5.js} +65 -65
  307. package/dist/{variant-custom-eFIg2XWQ.js → variant-custom-CAnzzppW.js} +40 -40
  308. package/dist/variant-custom-D91HjAKz.js +1 -0
  309. package/dist/{variant-wave-CUWGx9Bn.js → variant-wave-Ca1Ls4_y.js} +21 -21
  310. package/dist/{variant-wave-CbVsmgwT.js → variant-wave-CxX39DKd.js} +25 -25
  311. package/dist/{variant-wave-BwzrHD1s.js → variant-wave-DYyzIIZv.js} +2 -2
  312. package/dist/{elements/chunks/variant-wave-DDCwhLas.js → variant-wave-uY5crU_K.js} +2 -2
  313. package/dist/vue-BmIZj4XD.js +1 -33
  314. package/dist/wire/chunk.d.ts +47 -0
  315. package/dist/wire/diagnostics.d.ts +575 -0
  316. package/dist/wire/encode-probe.d.ts +70 -0
  317. package/dist/wire/encode.d.ts +24 -0
  318. package/dist/wire/index.d.ts +2 -0
  319. package/dist/wire/sse.d.ts +10 -2
  320. package/dist/wire.js +917 -480
  321. package/dist/x-C5m1JFgi.js +1 -0
  322. package/frameworks/react/index.tsx +325 -108
  323. package/llms-full.txt +1354 -82
  324. package/llms.txt +6 -3
  325. package/package.json +39 -6
  326. package/src/agent-tooling/archetypes.ts +41 -9
  327. package/src/agent-tooling/catalog/README.md +676 -0
  328. package/src/agent-tooling/catalog/catalog-types.ts +231 -0
  329. package/src/agent-tooling/catalog/fabrications.ts +96 -0
  330. package/src/agent-tooling/catalog/invariants.ts +284 -0
  331. package/src/agent-tooling/catalog/labs-titles.ts +114 -0
  332. package/src/agent-tooling/catalog/scenarios.ts +87 -0
  333. package/src/agent-tooling/catalog/surfaces.ts +347 -0
  334. package/src/agent-tooling/construct/cli-entry.ts +8 -0
  335. package/src/agent-tooling/construct/cli.ts +131 -0
  336. package/src/agent-tooling/construct/codegen.ts +1353 -0
  337. package/src/agent-tooling/construct/construct.v1.schema.json +250 -0
  338. package/src/agent-tooling/construct/dev.ts +148 -0
  339. package/src/agent-tooling/construct/fixtures/demo-widget.construct.json +7 -0
  340. package/src/agent-tooling/construct/fixtures/ops-console.construct.json +44 -0
  341. package/src/agent-tooling/construct/fixtures/owner-widget.construct.json +23 -0
  342. package/src/agent-tooling/construct/schema.ts +371 -0
  343. package/src/agent-tooling/integrations/anthropic.ts +7 -1
  344. package/src/agent-tooling/integrations/cloudflare.ts +43 -2
  345. package/src/agent-tooling/integrations/langgraph.ts +7 -1
  346. package/src/agent-tooling/integrations/mastra.ts +7 -1
  347. package/src/agent-tooling/integrations/mock.ts +92 -17
  348. package/src/agent-tooling/integrations/ollama.ts +7 -1
  349. package/src/agent-tooling/integrations/openai.ts +7 -1
  350. package/src/agent-tooling/integrations/openrouter.ts +7 -1
  351. package/src/agent-tooling/integrations/pi.ts +56 -1
  352. package/src/agent-tooling/integrations/vercel-ai-sdk.ts +7 -1
  353. package/src/agent-tooling/mcp/css-raw.d.ts +12 -0
  354. package/src/agent-tooling/mcp/manifest.ts +100 -1
  355. package/src/agent-tooling/mcp/server.ts +63 -6
  356. package/src/agent-tooling/mcp/tools/construct.ts +130 -0
  357. package/src/agent-tooling/mcp/tools/debug.ts +126 -4
  358. package/src/agent-tooling/mcp/tools/reference.ts +542 -5
  359. package/src/agent-tooling/mcp/tools/scaffold.ts +1834 -307
  360. package/src/agent-tooling/mcp/tools/theme.ts +146 -30
  361. package/src/agent-tooling/mcp/validate-args.ts +141 -0
  362. package/src/agent-tooling/recipes/composed-thread.ts +714 -0
  363. package/src/agent-tooling/recipes/index.ts +22 -0
  364. package/src/agent-tooling/recipes/types.ts +32 -0
  365. package/src/agent-tooling/registry.ts +24 -7
  366. package/src/agent-tooling/route-emit.ts +26 -2
  367. package/src/components/attachment-types.ts +54 -1
  368. package/src/components/attachments.tsx +169 -22
  369. package/src/components/audio-visualizer/fit-scale.ts +112 -0
  370. package/src/components/audio-visualizer/index.tsx +80 -3
  371. package/src/components/audio-visualizer/variant-aurora.tsx +10 -4
  372. package/src/components/audio-visualizer/variant-bar.tsx +38 -6
  373. package/src/components/audio-visualizer/variant-custom.tsx +8 -2
  374. package/src/components/audio-visualizer/variant-grid.tsx +16 -11
  375. package/src/components/audio-visualizer/variant-radial.tsx +15 -10
  376. package/src/components/audio-visualizer/variant-wave.tsx +7 -2
  377. package/src/components/card-renderer.tsx +39 -8
  378. package/src/components/card.tsx +10 -2
  379. package/src/components/chat-container.tsx +4 -1
  380. package/src/components/chat-scope-picker.tsx +2 -2
  381. package/src/components/chat-thread.tsx +105 -13
  382. package/src/components/choice-card.tsx +102 -37
  383. package/src/components/coachmark.tsx +1 -1
  384. package/src/components/code-block.tsx +81 -5
  385. package/src/components/composer.tsx +89 -8
  386. package/src/components/confirm-card.tsx +20 -4
  387. package/src/components/conversation-item.tsx +138 -4
  388. package/src/components/conversation-list.tsx +230 -15
  389. package/src/components/embed.tsx +4 -0
  390. package/src/components/empty.tsx +14 -3
  391. package/src/components/file-tree.tsx +5 -1
  392. package/src/components/form-widgets.tsx +257 -195
  393. package/src/components/form.tsx +336 -21
  394. package/src/components/loader.tsx +12 -5
  395. package/src/components/message-skills.tsx +1 -1
  396. package/src/components/message.tsx +142 -12
  397. package/src/components/prompt-input.tsx +7 -1
  398. package/src/components/prompt-suggestion.tsx +4 -1
  399. package/src/components/reasoning.tsx +30 -3
  400. package/src/components/response-compare.tsx +2 -2
  401. package/src/components/response-stream.tsx +13 -7
  402. package/src/components/screen.tsx +5 -0
  403. package/src/components/scroll-button.tsx +63 -4
  404. package/src/components/source.tsx +6 -1
  405. package/src/components/tasks-card.tsx +58 -16
  406. package/src/components/thread.tsx +10 -1
  407. package/src/components/toast.tsx +11 -5
  408. package/src/components/tool-types.ts +9 -0
  409. package/src/components/voice-input.tsx +35 -3
  410. package/src/components/voice-output.tsx +46 -6
  411. package/src/components/workspace-shell.tsx +285 -0
  412. package/src/diagnostics/hook.ts +352 -0
  413. package/src/diagnostics/index.ts +82 -0
  414. package/src/diagnostics/report-request.ts +296 -0
  415. package/src/elements/attachments.tsx +9 -8
  416. package/src/elements/audio-visualizer.tsx +48 -4
  417. package/src/elements/chat-workspace.tsx +159 -287
  418. package/src/elements/chat.tsx +31 -10
  419. package/src/elements/checkbox-group.tsx +164 -0
  420. package/src/elements/checkbox.tsx +134 -0
  421. package/src/elements/code-block.tsx +19 -1
  422. package/src/elements/command.tsx +4 -1
  423. package/src/elements/compiled.css +1 -1
  424. package/src/elements/composer.tsx +12 -1
  425. package/src/elements/conversation-item.tsx +120 -0
  426. package/src/elements/conversation-list.tsx +60 -11
  427. package/src/elements/default-input.tsx +5 -5
  428. package/src/elements/define-entry.ts +12 -0
  429. package/src/elements/define.tsx +168 -8
  430. package/src/elements/diagnostic-events.ts +114 -0
  431. package/src/elements/dialog.tsx +47 -0
  432. package/src/elements/disclosure.ts +34 -12
  433. package/src/elements/dock.tsx +215 -0
  434. package/src/elements/dropdown.tsx +123 -0
  435. package/src/elements/element-data-types.ts +40 -0
  436. package/src/elements/element-diagnostics.ts +392 -0
  437. package/src/elements/element-manifest.json +36 -0
  438. package/src/elements/element-meta.json +1483 -560
  439. package/src/elements/element-nonscalar.json +185 -0
  440. package/src/elements/element-types.d.ts +589 -167
  441. package/src/elements/feedback-bar.tsx +5 -0
  442. package/src/elements/icon-names.json +29 -0
  443. package/src/elements/input.tsx +312 -23
  444. package/src/elements/menu.tsx +13 -0
  445. package/src/elements/message.tsx +2 -0
  446. package/src/elements/pane-grid.tsx +111 -0
  447. package/src/elements/prompt-dock.tsx +2 -1
  448. package/src/elements/prompt-input.tsx +16 -10
  449. package/src/elements/radio-group.tsx +129 -0
  450. package/src/elements/register-impl.ts +52 -0
  451. package/src/elements/register.ts +28 -0
  452. package/src/elements/resizable.tsx +52 -25
  453. package/src/elements/scroll-button.tsx +59 -6
  454. package/src/elements/select.tsx +165 -0
  455. package/src/elements/slider.tsx +171 -0
  456. package/src/elements/slots.ts +108 -13
  457. package/src/elements/styles.css +119 -9
  458. package/src/elements/thinking-bar.tsx +16 -7
  459. package/src/elements/thread.tsx +2 -0
  460. package/src/elements/toast.tsx +17 -1
  461. package/src/elements/tool.tsx +26 -5
  462. package/src/elements/voice-input.tsx +11 -2
  463. package/src/elements/voice-output.tsx +16 -3
  464. package/src/index.ts +4 -1
  465. package/src/primitives/card-data-types.ts +49 -0
  466. package/src/primitives/card-host.tsx +4 -2
  467. package/src/primitives/card-routing.ts +7 -56
  468. package/src/primitives/card-schemas/form.schema.json +21 -1
  469. package/src/primitives/card-validate.ts +10 -2
  470. package/src/primitives/chat-config.tsx +33 -6
  471. package/src/primitives/field-mask.ts +256 -0
  472. package/src/primitives/field-semantics.ts +115 -0
  473. package/src/primitives/input-mask.ts +853 -0
  474. package/src/primitives/toast-store.ts +66 -4
  475. package/src/primitives/url-scheme-policy.ts +70 -0
  476. package/src/primitives/use-resize-observer.ts +33 -0
  477. package/src/primitives/use-speech-recognition.ts +12 -1
  478. package/src/primitives/use-stick-to-bottom.ts +34 -2
  479. package/src/primitives/use-text-stream.ts +33 -2
  480. package/src/remote/provider-runtime.ts +6 -3
  481. package/src/schemas/index.ts +1 -0
  482. package/src/schemas/tool-defs.ts +307 -6
  483. package/src/solid.ts +15 -0
  484. package/src/state/index.ts +13 -1
  485. package/src/state/mock.ts +92 -6
  486. package/src/state/persistence.ts +180 -0
  487. package/src/state/stream.ts +71 -4
  488. package/src/state/threads.ts +142 -0
  489. package/src/types.ts +4 -2
  490. package/src/ui/agent-card.tsx +2 -2
  491. package/src/ui/avatar.tsx +1 -1
  492. package/src/ui/checkbox-group.tsx +153 -0
  493. package/src/ui/checkbox.tsx +63 -0
  494. package/src/ui/dialog.tsx +6 -0
  495. package/src/ui/dock.tsx +584 -0
  496. package/src/ui/dropdown.tsx +67 -23
  497. package/src/ui/hover-card.tsx +156 -6
  498. package/src/ui/icon.tsx +53 -0
  499. package/src/ui/input.tsx +275 -16
  500. package/src/ui/kbd.tsx +1 -1
  501. package/src/ui/nav.tsx +1 -1
  502. package/src/ui/pane-group.tsx +2 -2
  503. package/src/ui/radio.tsx +150 -0
  504. package/src/ui/resizable.tsx +11 -0
  505. package/src/ui/select.tsx +168 -0
  506. package/src/ui/slider.tsx +178 -0
  507. package/src/ui/switch.tsx +49 -18
  508. package/src/utils/cn.ts +11 -5
  509. package/src/wire/chunk.ts +47 -0
  510. package/src/wire/consume.ts +185 -26
  511. package/src/wire/diagnostics.ts +727 -0
  512. package/src/wire/encode-probe.ts +214 -0
  513. package/src/wire/encode.ts +267 -14
  514. package/src/wire/formats/anthropic.ts +8 -1
  515. package/src/wire/formats/openai.ts +5 -0
  516. package/src/wire/index.ts +22 -0
  517. package/src/wire/read.ts +250 -6
  518. package/src/wire/sse.ts +16 -3
  519. package/theme.css +84 -13
  520. package/dist/elements/chunks/action-icons-DW9muWrY.js +0 -1
  521. package/dist/elements/chunks/arrow-left-iJVnQmxA.js +0 -1
  522. package/dist/elements/chunks/artifact-BSuPYFHY.js +0 -1
  523. package/dist/elements/chunks/attachments-CL6TaqIb.js +0 -1
  524. package/dist/elements/chunks/audio-visualizer-BuUqQmaO.js +0 -1
  525. package/dist/elements/chunks/badge-TIMiJFKc.js +0 -1
  526. package/dist/elements/chunks/bash-InADTalH.js +0 -1
  527. package/dist/elements/chunks/card-renderer-CSoTL_e1.js +0 -1
  528. package/dist/elements/chunks/card-routing-BK09BdmL.js +0 -1
  529. package/dist/elements/chunks/chat-thread-DxoKaStk.js +0 -1
  530. package/dist/elements/chunks/check-MdLzbxrm.js +0 -1
  531. package/dist/elements/chunks/chevron-down-MK7IzDqe.js +0 -1
  532. package/dist/elements/chunks/chevron-right-C5jXhmtV.js +0 -1
  533. package/dist/elements/chunks/choice-card-CGMc3kek.js +0 -1
  534. package/dist/elements/chunks/circle-BG7VkPep.js +0 -1
  535. package/dist/elements/chunks/circle-check-GqvO2UBs.js +0 -1
  536. package/dist/elements/chunks/circle-x-mhH7pOK2.js +0 -1
  537. package/dist/elements/chunks/code-block-ClhQO9RW.js +0 -1
  538. package/dist/elements/chunks/collapsible-DBcBkFPh.js +0 -1
  539. package/dist/elements/chunks/composer-CZZmtYOT.js +0 -69
  540. package/dist/elements/chunks/confirm-card-BWjf2-xo.js +0 -1
  541. package/dist/elements/chunks/context-hKjNqiyQ.js +0 -1
  542. package/dist/elements/chunks/conversation-list-CsGJjSKG.js +0 -1
  543. package/dist/elements/chunks/core-AYMC6_lb.js +0 -12
  544. package/dist/elements/chunks/css-M7EaDHN_.js +0 -1
  545. package/dist/elements/chunks/default-input-B1Mem5wu.js +0 -1
  546. package/dist/elements/chunks/define-B_tXMYRm.js +0 -1
  547. package/dist/elements/chunks/disclosure-DAoYOKew.js +0 -1
  548. package/dist/elements/chunks/download-DnCyWFJE.js +0 -1
  549. package/dist/elements/chunks/dropdown-CmNna5lT.js +0 -1
  550. package/dist/elements/chunks/embed-CjAD2jSi.js +0 -1
  551. package/dist/elements/chunks/engine-javascript-vq0WuIJl.js +0 -141
  552. package/dist/elements/chunks/file-text-D0J9F6M7.js +0 -1
  553. package/dist/elements/chunks/folder-NV1pi_ud.js +0 -1
  554. package/dist/elements/chunks/form-Cp8Y00Wn.js +0 -1
  555. package/dist/elements/chunks/github-dark-dimmed-DUshB20C.js +0 -1
  556. package/dist/elements/chunks/github-light-JYsPkUQd.js +0 -1
  557. package/dist/elements/chunks/hover-card-6KtXpLwT.js +0 -1
  558. package/dist/elements/chunks/html-CPZ3oZQ7.js +0 -1
  559. package/dist/elements/chunks/icon-97R8SR2p.js +0 -1
  560. package/dist/elements/chunks/info-C8r5Q6cc.js +0 -1
  561. package/dist/elements/chunks/input-DrseEjhp.js +0 -1
  562. package/dist/elements/chunks/javascript-C25yR2R2.js +0 -1
  563. package/dist/elements/chunks/json-DxJze_jm.js +0 -1
  564. package/dist/elements/chunks/kbd-OGJDUT-O.js +0 -1
  565. package/dist/elements/chunks/link-BdsfLB7b.js +0 -1
  566. package/dist/elements/chunks/loader-B5QzK_8_.js +0 -1
  567. package/dist/elements/chunks/message-CE4k-qaj.js +0 -1
  568. package/dist/elements/chunks/message-DtXKPqiR.js +0 -1
  569. package/dist/elements/chunks/message-square-C9ovmtOo.js +0 -1
  570. package/dist/elements/chunks/paperclip-Bg0ITU3b.js +0 -1
  571. package/dist/elements/chunks/progress-bar-D1_rzt6N.js +0 -1
  572. package/dist/elements/chunks/prompt-suggestion-D5Sm8xF1.js +0 -1
  573. package/dist/elements/chunks/reasoning-BrxQfpfI.js +0 -1
  574. package/dist/elements/chunks/rotate-cw-DzVDJOA1.js +0 -1
  575. package/dist/elements/chunks/scroll-area-93A1bbeG.js +0 -1
  576. package/dist/elements/chunks/scroll-button-CrTo7wm4.js +0 -1
  577. package/dist/elements/chunks/share-DgNbkJKY.js +0 -1
  578. package/dist/elements/chunks/slots-CIw9RlAe.js +0 -1
  579. package/dist/elements/chunks/source-w9V0NnBn.js +0 -1
  580. package/dist/elements/chunks/star-DHvACxtm.js +0 -1
  581. package/dist/elements/chunks/store-HIPGhNz9.js +0 -1
  582. package/dist/elements/chunks/svelte--5p79yCD.js +0 -1
  583. package/dist/elements/chunks/tasks-card-lPxoodlA.js +0 -1
  584. package/dist/elements/chunks/text-shimmer-BY_rI3s5.js +0 -1
  585. package/dist/elements/chunks/textarea-DPfuhswM.js +0 -1
  586. package/dist/elements/chunks/toast-store-VUZZBhzH.js +0 -1
  587. package/dist/elements/chunks/tsx-B8rCNbgL.js +0 -1
  588. package/dist/elements/chunks/typescript-RycA9KXf.js +0 -1
  589. package/dist/elements/chunks/use-card-resolution-DJg32jGH.js +0 -1
  590. package/dist/elements/chunks/variant-custom-D5ZlmziF.js +0 -1
  591. package/dist/elements/chunks/video-Bb86fVYI.js +0 -1
  592. package/dist/elements/chunks/vue-BmIZj4XD.js +0 -1
  593. package/dist/elements/chunks/x-BOhjBeOd.js +0 -1
  594. package/dist/llms/llms-full.txt +0 -2834
  595. package/dist/llms/llms.txt +0 -156
  596. package/dist/primitives/card-validate-generator.testlib.d.ts +0 -20
  597. package/dist/register-impl-DGmGzrEM.js +0 -145
  598. package/dist/ui/stat.d.ts +0 -16
  599. package/dist/variant-custom-MkTNTycd.js +0 -1
  600. package/src/primitives/card-validate-generator.testlib.ts +0 -38
  601. package/src/ui/stat.tsx +0 -41
  602. /package/dist/{elements/chunks/card-tags-D8lZ-C_U.js → card-tags-D8lZ-C_U.js} +0 -0
  603. /package/dist/{elements/chunks/link-preview-DNILK391.js → link-preview-DNILK391.js} +0 -0
package/src/ui/dialog.tsx CHANGED
@@ -190,6 +190,12 @@ export function Dialog(props: DialogProps) {
190
190
  tabindex={-1}
191
191
  data-expanded={presence.state() === 'open' ? '' : undefined}
192
192
  data-closed={presence.state() === 'closed' ? '' : undefined}
193
+ // `outline-none` is DELIBERATE here and must stay. This is a
194
+ // `tabindex={-1}` focus-trap container: it is focused
195
+ // programmatically on open to move keyboard and screen-reader
196
+ // context into the dialog, and is never a tab stop. Drawing a ring
197
+ // would put a blue box around the entire dialog every time it
198
+ // opened. The controls inside it carry their own focus rings.
193
199
  class={cn(
194
200
  'flex max-h-[85vh] w-full max-w-lg flex-col overflow-hidden rounded-xl border border-border bg-background text-foreground shadow-xl outline-none',
195
201
  'animate-in fade-in-0 zoom-in-95 data-[closed]:animate-out data-[closed]:fade-out-0 data-[closed]:zoom-out-95 motion-reduce:animate-none',
@@ -0,0 +1,584 @@
1
+ import {
2
+ createEffect, createSignal, createUniqueId, Show, untrack, type Accessor, type JSX,
3
+ } from 'solid-js';
4
+ import { MessageCircle, X } from 'lucide-solid';
5
+
6
+ /** Which corner the dock sits in. LOGICAL, so `-end` follows the writing direction
7
+ * and an RTL page docks on the left with no extra work (both reference
8
+ * implementations reached for `inset-inline-end` independently). */
9
+ export type DockPosition = 'bottom-end' | 'bottom-start' | 'top-end' | 'top-start';
10
+
11
+ /** Where focus goes when the panel opens.
12
+ * - `content` (default) — `focus()` the first element assigned to the panel, falling
13
+ * back to the panel itself when that element cannot take focus. Content-AGNOSTIC:
14
+ * the dock never learns what it is holding.
15
+ * - `panel` — focus the `tabindex="-1"` panel container (what `kai-dialog` does).
16
+ * - `none` — never move focus. */
17
+ export type DockFocusOnOpen = 'content' | 'panel' | 'none';
18
+
19
+ /** Imperative open controller, handed to a parent (the `kai-dock` facade) via
20
+ * `controllerRef` so it can drive/observe open state with `wireDisclosure`. */
21
+ export interface DockController { open: Accessor<boolean>; setOpen: (v: boolean) => void }
22
+
23
+ export interface DockProps {
24
+ /** The panel body. ANY content — the dock never reads or types it. */
25
+ children?: JSX.Element;
26
+ /** Content inside the built-in button while CLOSED. Defaults to a chat glyph. */
27
+ launcher?: JSX.Element;
28
+ /** Content inside the button while OPEN. Defaults to a ✕; when only `launcher` is
29
+ * given, that content stays rather than morphing into a clashing built-in. */
30
+ launcherOpen?: JSX.Element;
31
+ /** The widget's NAME. Derives the panel's accessible name and both launcher names
32
+ * (`Open ${label}` / `Close ${label}`) — one string instead of three. */
33
+ label?: string;
34
+ /** i18n override for the launcher's name while closed. */
35
+ openLabel?: string;
36
+ /** i18n override for the launcher's name while open. */
37
+ closeLabel?: string;
38
+ /** Which corner. Logical, RTL-correct. Defaults to `bottom-end`. */
39
+ position?: DockPosition;
40
+ /** Render the unread dot. CONSUMER-OWNED: shown only while closed, and never
41
+ * written back by the dock (see the component doc). */
42
+ unread?: boolean;
43
+ /** Disable the launcher. */
44
+ disabled?: boolean;
45
+ /** Suppress the dock's own built-in mobile `[part="close"]` X. Off (rendered) by
46
+ * default — the built-in X is the FALLBACK close route for panel content with no
47
+ * header of its own, which is the general case the dock has to cover since it
48
+ * never reads what it holds. Set `true` when the slotted/composed content
49
+ * supplies its own close affordance in its own header row (e.g. `ChatThread`'s
50
+ * `headerEndContent`) — with both present the two X's stack, one floating over
51
+ * the other's row, which is exactly the "doesn't look intentional" feedback this
52
+ * prop exists to let a caller avoid. TRADEOFF: the mobile panel reserves a
53
+ * padding band above its content so the built-in X never paints over it; that
54
+ * band stays reserved unless you set `hideClose` true, so only set it once your
55
+ * own control is actually in place — otherwise you get the band with no X to
56
+ * justify it. */
57
+ hideClose?: boolean;
58
+ /** Where focus lands on open. Defaults to `content`. */
59
+ focusOnOpen?: DockFocusOnOpen;
60
+ /** Controlled open state. When set, the component never changes it itself — drive it
61
+ * from `onOpenChange`. Omit for uncontrolled (internal) state. */
62
+ open?: boolean;
63
+ /** Initial open state when uncontrolled. Does NOT move focus (see the focus contract). */
64
+ defaultOpen?: boolean;
65
+ /** Fires whenever the dock wants to open or close (launcher / Escape / a method). */
66
+ onOpenChange?: (open: boolean) => void;
67
+ /** Receive the open controller once mounted. */
68
+ controllerRef?: (api: DockController) => void;
69
+ /** Receive the focusable panel node so a facade's `focus()` can target it. */
70
+ panelRef?: (el: HTMLElement) => void;
71
+ /** Receive the launcher button so a facade's `focus()` can target it. */
72
+ launcherRef?: (el: HTMLButtonElement) => void;
73
+ /**
74
+ * Resolve the element `focusOnOpen: 'content'` should focus.
75
+ *
76
+ * A PROP, because the answer is "the first element assigned to the panel slot" and
77
+ * only the web-component facade can see slot assignments — a Solid caller passes
78
+ * its own ref instead. Returning nothing falls back to focusing the panel.
79
+ */
80
+ contentTarget?: () => HTMLElement | null | undefined;
81
+ }
82
+
83
+ /** The built-in closed glyph. Exported so the facade can use it as its `launcher`
84
+ * slot's FALLBACK content — one definition, two users, rather than a second copy
85
+ * of the same icon choice living in the element layer. */
86
+ export function DockLauncherGlyph(): JSX.Element {
87
+ return <MessageCircle size={24} />;
88
+ }
89
+
90
+ /** The built-in open glyph. Exported for the same reason as {@link DockLauncherGlyph}. */
91
+ export function DockCloseGlyph(): JSX.Element {
92
+ return <X size={24} />;
93
+ }
94
+
95
+ /**
96
+ * The dock's own stylesheet, scoped to its shadow root.
97
+ *
98
+ * IN A `<style>` RATHER THAN INLINE, and that is a decision. `kai-prompt-dock` puts
99
+ * its tokenized chrome in an inline `style={{}}`, which resolves `var()` fallbacks
100
+ * and lets an outside override win — but an inline declaration also beats every rule
101
+ * in the cascade, and this element ships TWO rules that have to be able to win: the
102
+ * narrow-viewport full-bleed default and the reduced-motion rule. Both are media
103
+ * queries, which cannot be expressed inline at all. So the geometry lives here and
104
+ * only the open/closed state (visibility / opacity / pointer-events) is inline, where
105
+ * it belongs: that one must beat any consumer rule, and it is also the half a jsdom
106
+ * test can actually read.
107
+ *
108
+ * EVERY RULE IS SCOPED UNDER `[data-kai-dock]`, including the `[part=...]` ones. Inside
109
+ * a shadow root that scoping is redundant; in a plain Solid page it is the difference
110
+ * between styling this dock and restyling every `[part="panel"]` on the document.
111
+ *
112
+ * DOCUMENTED CONSTRAINT, which the element cannot fix from inside: put `<kai-dock>`
113
+ * as a body-level child. A `transform` / `filter` / `contain` ancestor makes
114
+ * `position: fixed` resolve against that ancestor instead of the viewport.
115
+ */
116
+ const DOCK_CSS = `
117
+ :host { display: contents; }
118
+
119
+ [data-kai-dock] {
120
+ position: fixed;
121
+ z-index: var(--kai-dock-z, 1000);
122
+ display: flex;
123
+ gap: var(--kai-dock-gap, 0.85rem);
124
+ /* Only a positioning frame: it must not swallow clicks on the page it floats
125
+ over. The launcher and the open panel opt back in. */
126
+ pointer-events: none;
127
+ }
128
+
129
+ /* Logical insets throughout, so an RTL page docks on the other side for free.
130
+ column-reverse for the bottom corners keeps the LAUNCHER FIRST IN DOM ORDER --
131
+ Tab reaches the control before the panel content -- while painting it below. */
132
+ [data-kai-dock][data-position^="bottom"] {
133
+ flex-direction: column-reverse;
134
+ inset-block-end: var(--kai-dock-inset, 1.5rem);
135
+ }
136
+ [data-kai-dock][data-position^="top"] {
137
+ flex-direction: column;
138
+ inset-block-start: var(--kai-dock-inset, 1.5rem);
139
+ }
140
+ [data-kai-dock][data-position$="end"] {
141
+ inset-inline-end: var(--kai-dock-inset, 1.5rem);
142
+ align-items: flex-end;
143
+ }
144
+ [data-kai-dock][data-position$="start"] {
145
+ inset-inline-start: var(--kai-dock-inset, 1.5rem);
146
+ align-items: flex-start;
147
+ }
148
+
149
+ [data-kai-dock] [part="launcher"] {
150
+ pointer-events: auto;
151
+ position: relative;
152
+ /* ABOVE the panel, always. At <=480px the panel goes fixed/inset:0 and it comes
153
+ after the launcher in DOM order, so at z-index:auto it painted OVER the launcher
154
+ and a touch user lost every pointer route to closing. Both reference
155
+ implementations stacked the launcher over the panel for the same reason. The
156
+ launcher itself is now HIDDEN while the panel is open at that width (see the
157
+ <=480px block below) in favour of the in-panel [part="close"] X, but this
158
+ z-index still matters for the moment between "open" landing and the panel's
159
+ own transition finishing, and for any consumer CSS that reintroduces the
160
+ launcher there. */
161
+ z-index: 1;
162
+ flex: none;
163
+ display: inline-flex;
164
+ align-items: center;
165
+ justify-content: center;
166
+ gap: 0.5rem;
167
+ /* The size token is the BLOCK size and the inline MINIMUM, not a fixed square. An
168
+ icon-only launcher is still a perfect disc (the icon plus padding is narrower
169
+ than the minimum), but a slotted TEXT label — the reference app's "Support"
170
+ pill — widens the button instead of overflowing it. */
171
+ inline-size: auto;
172
+ min-inline-size: var(--kai-dock-launcher-size, 56px);
173
+ block-size: var(--kai-dock-launcher-size, 56px);
174
+ padding-block: 0;
175
+ padding-inline: 0.9rem;
176
+ white-space: nowrap;
177
+ border: 0;
178
+ border-radius: 9999px;
179
+ background: var(--color-primary);
180
+ color: var(--color-primary-foreground);
181
+ box-shadow: 0 10px 25px -6px rgb(0 0 0 / 0.35);
182
+ cursor: pointer;
183
+ transition: transform 150ms ease, box-shadow 150ms ease;
184
+ }
185
+ [data-kai-dock] [part="launcher"]:hover:not(:disabled) { transform: translateY(-1px); }
186
+ [data-kai-dock] [part="launcher"]:focus-visible { outline: 2px solid var(--color-ring); outline-offset: 2px; }
187
+ [data-kai-dock] [part="launcher"]:disabled { opacity: 0.55; cursor: not-allowed; }
188
+
189
+ /* The mobile-only close affordance rendered INSIDE the panel. Hidden by default and
190
+ only switched on inside the <=480px media block below, alongside the rule that
191
+ hides the launcher while the panel is open at that width — same query drives both,
192
+ CSS-only, no JS viewport logic. Absolutely positioned against the panel, which is
193
+ the panel's own containing block once the narrow-viewport rule below sets it to
194
+ position: fixed.
195
+
196
+ The inset is a CONSUMER-OVERRIDABLE TOKEN, same idiom as every other geometry
197
+ value in this file (--kai-dock-width and friends). The dock is content-agnostic —
198
+ it never reads what is slotted into the panel — so it cannot know whether that
199
+ content renders its own trailing controls (a ChatThread header-end slot with
200
+ share/settings icons, say) in the same top-right corner.
201
+
202
+ The mobile block below reserves a BAND of the panel's own padding above whatever
203
+ is slotted, sized to this button's own footprint, so nothing paints under this X
204
+ by default -- verified fix (round 1) against a real ChatThread header-end slot
205
+ colliding with it at 375px. Owner feedback on the real widget (round 2): for a
206
+ construct with only a title in the header, that band read as a dead empty strip
207
+ with the X floating alone in it -- "that doesn't look like the empty component
208
+ either, lets do better." The actual fix composes instead of reserving space:
209
+ ChatThread now has its own headerEndContent escape hatch (see its prop doc) so a
210
+ caller puts the close control INSIDE the header row, sharing it with the title --
211
+ zero collision by construction, no band needed. hideClose on this component (see
212
+ its prop doc) then suppresses this built-in X for exactly that case, and the
213
+ band above is scoped to :not([data-hide-close]) so it comes off ONLY there --
214
+ every other consumer (anyone who hasn't opted into hideClose, including a
215
+ hand-authored dock with real header-end content) keeps the band, same as round 1
216
+ shipped, so the collision it fixed stays fixed for them. What remains here for
217
+ that default case is a plain absolute overlay, top-right of the panel, with
218
+ these consumer-overridable inset tokens for the rare case it should deliberately
219
+ sit in front of something (e.g. a single small badge). */
220
+ [data-kai-dock] [part="close"] {
221
+ display: none;
222
+ position: absolute;
223
+ inset-block-start: var(--kai-dock-close-inset-block, 0.75rem);
224
+ inset-inline-end: var(--kai-dock-close-inset-inline, 0.75rem);
225
+ z-index: 1;
226
+ align-items: center;
227
+ justify-content: center;
228
+ inline-size: 2.25rem;
229
+ block-size: 2.25rem;
230
+ padding: 0;
231
+ border: 0;
232
+ border-radius: 9999px;
233
+ background: var(--color-background);
234
+ color: var(--color-foreground);
235
+ box-shadow: 0 2px 8px -2px rgb(0 0 0 / 0.3);
236
+ cursor: pointer;
237
+ }
238
+ [data-kai-dock] [part="close"]:hover { background: var(--color-muted, var(--color-background)); }
239
+ [data-kai-dock] [part="close"]:focus-visible { outline: 2px solid var(--color-ring); outline-offset: 2px; }
240
+
241
+ [data-kai-dock] [part="badge"] {
242
+ position: absolute;
243
+ inset-block-start: 3px;
244
+ inset-inline-end: 3px;
245
+ inline-size: 0.75rem;
246
+ block-size: 0.75rem;
247
+ border-radius: 9999px;
248
+ background: var(--color-destructive);
249
+ box-shadow: 0 0 0 2px var(--color-primary);
250
+ }
251
+
252
+ [data-kai-dock] [part="panel"] {
253
+ pointer-events: auto;
254
+ display: flex;
255
+ flex-direction: column;
256
+ overflow: hidden;
257
+ inline-size: var(--kai-dock-width, 380px);
258
+ block-size: var(--kai-dock-height, 600px);
259
+ max-inline-size: calc(100vw - 2 * var(--kai-dock-inset, 1.5rem));
260
+ max-block-size: calc(
261
+ 100dvh - 2 * var(--kai-dock-inset, 1.5rem)
262
+ - var(--kai-dock-launcher-size, 56px) - var(--kai-dock-gap, 0.85rem)
263
+ );
264
+ border: 1px solid var(--color-border);
265
+ border-radius: var(--kai-dock-radius, 16px);
266
+ background: var(--color-background);
267
+ color: var(--color-foreground);
268
+ box-shadow: 0 20px 45px -12px rgb(0 0 0 / 0.35);
269
+ /* VISIBILITY IS SWITCHED, NEVER ANIMATED, and the zero duration is the whole point.
270
+ visibility 180ms here meant the COMPUTED value stayed hidden for about two
271
+ frames after opening while the inline style already said visible — and a
272
+ focus() into a hidden subtree is dropped silently, so focusOnOpen never landed
273
+ in a real browser (it worked under reduced motion, which is what made it look like
274
+ a focus bug rather than a paint one).
275
+ This rule is the CLOSING direction: hold visibility for the length of the fade so
276
+ the panel does not vanish mid-transition, then switch. The opening direction sets
277
+ the delay to 0s below, so opening is synchronous and focus has somewhere to go. */
278
+ transition: opacity 180ms ease, transform 180ms ease, visibility 0s linear 180ms;
279
+ }
280
+ [data-kai-dock] [part="panel"][data-expanded] {
281
+ transition: opacity 180ms ease, transform 180ms ease, visibility 0s linear 0s;
282
+ }
283
+ /* The panel is a flex column and the slotted surface fills it -- the one layout
284
+ fact both reference implementations wrote by hand (flex:1; min-height:0). Both
285
+ spellings are here on purpose: ::slotted covers the shadow-DOM facade, > *
286
+ covers a plain Solid caller, and each is inert in the other's world (a <slot>
287
+ is display:contents, so flex properties do nothing to it). */
288
+ [data-kai-dock] [part="panel"] ::slotted(*) { flex: 1 1 auto; min-block-size: 0; }
289
+ [data-kai-dock] [part="panel"] > * { flex: 1 1 auto; min-block-size: 0; }
290
+ [data-kai-dock] [part="panel"]:focus-visible { outline: 2px solid var(--color-ring); outline-offset: 2px; }
291
+ [data-kai-dock][data-position^="bottom"] [part="panel"][data-closed] { transform: translateY(0.5rem); }
292
+ [data-kai-dock][data-position^="top"] [part="panel"][data-closed] { transform: translateY(-0.5rem); }
293
+
294
+ /* Narrow viewports take the panel full-bleed. A SHIPPED DEFAULT, not a prop: both
295
+ references wrote this rule independently, which makes it a HOW. While the panel is
296
+ OPEN at this width the launcher hides and the panel's own [part="close"] X becomes
297
+ the pointer close route instead — a FAB floating over a full-bleed panel read as
298
+ wrong (owner feedback) and Escape alone strands a touch user with no visible
299
+ affordance at all. Closed, the launcher is unaffected: it is the panel's own
300
+ [data-expanded] state that gates both rules below, not the media query alone. */
301
+ @media (max-width: 480px) {
302
+ [data-kai-dock] [part="panel"] {
303
+ position: fixed;
304
+ inset: 0;
305
+ inline-size: auto;
306
+ block-size: auto;
307
+ max-inline-size: none;
308
+ max-block-size: none;
309
+ border: 0;
310
+ border-radius: 0;
311
+ }
312
+
313
+ /* Reserve a band for the built-in [part="close"] X ABOVE whatever is slotted,
314
+ rather than overlaying it on top of the panel's own content — the fix round 1
315
+ collision protection (verified against a real ChatThread header-end slot at
316
+ 375px: without this, the X painted directly over trailing icon buttons).
317
+ DERIVED from the button's own inset + size tokens, not a second hand-typed
318
+ number.
319
+
320
+ SCOPED to :not([data-hide-close]) — round 2 (owner feedback: the band read as
321
+ a dead strip when nothing but the X occupied it) removed this UNCONDITIONALLY
322
+ and reintroduced the very collision round 1 fixed, for every consumer who
323
+ never opted into hideClose. hideClose is only true for a caller that supplies
324
+ its OWN close control in its own header row (ChatThread's headerEndContent is
325
+ the one that does today) — the built-in X and this band both come off
326
+ together for that case, since there is nothing left for the band to protect.
327
+ Every other consumer -- including a hand-authored kai-dock with real
328
+ slot="header-end" content and no opinion on hideClose -- keeps the band, the
329
+ same as round 1 shipped. */
330
+ [data-kai-dock]:not([data-hide-close]) [part="panel"] {
331
+ padding-block-start: calc(2 * var(--kai-dock-close-inset-block, 0.75rem) + 2.25rem);
332
+ }
333
+
334
+ /* The full-bleed panel gets its own close affordance instead of relying on the
335
+ launcher, which is hidden below while the panel is open at this width — a FAB
336
+ floating over a full-bleed panel with no other visible close route is exactly
337
+ what the owner flagged. Escape and the X both still work; the launcher comes
338
+ back the instant the panel closes because data-expanded is gone by then.
339
+ Absolutely positioned, so the padding-block-start above (a content-box
340
+ concern) does not shift IT — only the in-flow slotted content. Content that
341
+ supplies its own close control passes hideClose, which drops the button from
342
+ the render tree entirely (see the component body) rather than being gated
343
+ here — so this rule stays a plain, unconditional width-query switch; the band
344
+ above is what actually varies with hideClose. */
345
+ [data-kai-dock] [part="close"] { display: inline-flex; }
346
+ [data-kai-dock]:has([part="panel"][data-expanded]) [part="launcher"] { display: none; }
347
+ }
348
+
349
+ @media (prefers-reduced-motion: reduce) {
350
+ /* [data-expanded] is listed explicitly because the open-direction rule above is
351
+ MORE SPECIFIC than a bare [part="panel"], so without it an opening panel kept
352
+ animating opacity and transform for exactly the users who asked it not to. */
353
+ [data-kai-dock] [part="launcher"],
354
+ [data-kai-dock] [part="panel"],
355
+ [data-kai-dock] [part="panel"][data-expanded] { transition: none; }
356
+ [data-kai-dock] [part="panel"][data-closed] { transform: none; }
357
+ }
358
+ `;
359
+
360
+ /**
361
+ * The deepest active element, drilling through nested shadow roots.
362
+ *
363
+ * A local copy of the helper `ui/dialog.tsx` already carries. Deliberately not
364
+ * refactored into a shared module by this change: dialog is not this task's file, and
365
+ * a four-line tree walk duplicated once is cheaper than a cross-file move nobody
366
+ * asked for. If a third caller appears, hoist it then.
367
+ */
368
+ function deepActiveElement(): HTMLElement | null {
369
+ let el = document.activeElement as HTMLElement | null;
370
+ while (el?.shadowRoot?.activeElement) el = el.shadowRoot.activeElement as HTMLElement;
371
+ return el;
372
+ }
373
+
374
+ /**
375
+ * Is `node` inside `root` in the FLATTENED tree?
376
+ *
377
+ * `root.contains(node)` is the wrong question here and would answer `false` for every
378
+ * real case: the panel's content is light-DOM nodes ASSIGNED to a slot, so it lives in
379
+ * the host's tree, not the panel's. This walks up through `assignedSlot` at each slot
380
+ * and through `host` at each shadow boundary, which is the tree focus actually moves in.
381
+ */
382
+ function isWithin(root: Node, node: Node | null): boolean {
383
+ let cur: Node | null = node;
384
+ while (cur) {
385
+ if (cur === root) return true;
386
+ const slot = cur instanceof Element ? cur.assignedSlot : null;
387
+ if (slot) { cur = slot; continue; }
388
+ if (cur.parentNode) { cur = cur.parentNode; continue; }
389
+ cur = cur instanceof ShadowRoot ? cur.host : null;
390
+ }
391
+ return false;
392
+ }
393
+
394
+ /**
395
+ * Dock — a corner-docked launcher button with a panel that opens above it.
396
+ *
397
+ * The affordance behind a support widget: a circular button pinned to a corner of the
398
+ * viewport, and a floating panel holding whatever you slot into it. The panel is
399
+ * content-agnostic on purpose — a chat, a form, your own component — and the dock
400
+ * never reads, types or reaches into it.
401
+ *
402
+ * WHAT IT OWNS (all of it non-configurable, because these are facts about the medium
403
+ * rather than preferences):
404
+ *
405
+ * - **Hide semantics.** Closed means `visibility: hidden` + `opacity: 0` + `inert`,
406
+ * and the panel is NEVER unmounted. `display: none` would leave the panel with no
407
+ * layout box while closed, so anything inside that measures itself — a thread
408
+ * scroller, a `ResizeObserver` — measures zero and re-measures on every open. The
409
+ * cost of keeping the box is that a stream keeps rendering into a closed panel,
410
+ * which is precisely what makes an unread dot honest.
411
+ * - **Focus.** On open, focus moves per `focusOnOpen`; on close it returns to the
412
+ * launcher, always. `inert` is cleared and visibility restored BEFORE `focus()` runs
413
+ * — a `focus()` into an inert or hidden subtree is silently dropped, no error — via
414
+ * a `queueMicrotask`, the same shape `ui/dialog.tsx` uses. Mount moves no focus.
415
+ * - **Escape.** Closes only while the dock CONTAINS focus, and never stops the event.
416
+ * A background widget that ate every Escape on the page would break the host page's
417
+ * own menus; swallowing a key from a page you are a guest on is a decide-quietly move.
418
+ * - **No focus trap**, deliberately, unlike `kai-dialog`. The page staying usable is
419
+ * the whole point of "docked", which is also why the panel is `aria-modal="false"`.
420
+ * - **Mobile close route.** At <=480px the panel goes full-bleed and the launcher
421
+ * hides while it is open — a floating launcher over a full-bleed panel is not an
422
+ * obvious close affordance. The panel gets its own `[part="close"]` X, top-right,
423
+ * CSS-gated by the SAME media query and `[data-expanded]`, so it needs no viewport
424
+ * JS. Desktop is unaffected: the launcher keeps toggling and no X ever renders.
425
+ * This is the FALLBACK route, for panel content with no header of its own to hold
426
+ * a close control — content that does (a `ChatThread` using `headerEndContent`,
427
+ * say) passes `hideClose` to drop this X instead of stacking a second one over
428
+ * its own header row.
429
+ *
430
+ * WHAT IT REFUSES: it never aborts a request, never persists its own open state, never
431
+ * decides what "unread" means, and never clears `unread` on open — writing over a
432
+ * consumer's prop is the trap `kai-chat.loading` fell into. Those are the app's calls;
433
+ * `open` + `onOpenChange` is the seam.
434
+ *
435
+ * Geometry is CSS custom properties rather than props: `--kai-dock-width` ·
436
+ * `--kai-dock-height` · `--kai-dock-inset` · `--kai-dock-gap` · `--kai-dock-radius` ·
437
+ * `--kai-dock-z` · `--kai-dock-launcher-size`.
438
+ *
439
+ * @see `elements/dock.tsx` for the `<kai-dock>` web-component facade.
440
+ */
441
+ export function Dock(props: DockProps) {
442
+ const panelId = `kai-dock-panel-${createUniqueId()}`;
443
+ const [internalOpen, setInternalOpen] = createSignal(props.defaultOpen ?? false);
444
+
445
+ const isControlled = () => props.open !== undefined;
446
+ const isOpen = () => (isControlled() ? !!props.open : internalOpen());
447
+ const setOpen = (v: boolean) => {
448
+ if (!isControlled()) setInternalOpen(v);
449
+ props.onOpenChange?.(v);
450
+ };
451
+
452
+ props.controllerRef?.({ open: isOpen, setOpen });
453
+
454
+ let panel: HTMLElement | undefined;
455
+ let launcher: HTMLButtonElement | undefined;
456
+
457
+ const label = () => props.label ?? 'Chat';
458
+ const launcherName = () => (isOpen()
459
+ ? props.closeLabel ?? `Close ${label()}`
460
+ : props.openLabel ?? `Open ${label()}`);
461
+
462
+ /** The focus half of an open, deferred so the state flip above has already landed. */
463
+ const focusOnOpen = () => {
464
+ const mode = props.focusOnOpen ?? 'content';
465
+ if (mode === 'none') return;
466
+ if (mode === 'content') {
467
+ const target = props.contentTarget?.();
468
+ target?.focus();
469
+ // Content-agnostic fallback: a plain <div> is not focusable, so `focus()` was a
470
+ // silent no-op and the panel takes it instead. Asking where focus ACTUALLY
471
+ // landed is the only way to tell, since the dock is not allowed to inspect what
472
+ // it is holding.
473
+ if (target && isWithin(panel!, deepActiveElement())) return;
474
+ }
475
+ panel?.focus();
476
+ };
477
+
478
+ // The focus choreography, and the ONE ordering that is a hard invariant.
479
+ //
480
+ // SEEDED WITH THE INITIAL OPEN VALUE, not `false`. `kai-dialog` seeds `false` on
481
+ // purpose so a `defaultOpen` modal DOES take focus at mount; a docked widget is the
482
+ // opposite — it is a guest on someone's page and must not steal focus on load, from
483
+ // `default-open` OR from `open`. Same mechanism, opposite default, and the
484
+ // difference is exactly the modal/non-modal split.
485
+ createEffect((wasOpen: boolean) => {
486
+ const open = isOpen();
487
+ if (open !== wasOpen) {
488
+ // The attributes and inline styles below are written by render effects, which
489
+ // Solid runs before this one; the microtask is belt-and-braces for a state
490
+ // change driven from outside a Solid batch, and gives a freshly-slotted element
491
+ // a turn to upgrade before we call focus() on it.
492
+ if (open) queueMicrotask(focusOnOpen);
493
+ else queueMicrotask(() => launcher?.focus());
494
+ }
495
+ return open;
496
+ }, untrack(() => props.open ?? props.defaultOpen ?? false));
497
+
498
+ const onKeyDown = (e: KeyboardEvent) => {
499
+ if (e.key !== 'Escape' || !isOpen()) return;
500
+ // D-C. This listener is ON the dock rather than on `document`, so the only
501
+ // keydowns it can ever see are ones whose target is inside it — which IS the
502
+ // condition "the dock contains focus", with no ambiguity and no second check. It
503
+ // also composes: an overlay inside the panel that handles its own Escape and
504
+ // stops the event correctly prevents the dock from closing underneath it.
505
+ // No stopPropagation, no preventDefault: the host page keeps its key.
506
+ setOpen(false);
507
+ };
508
+
509
+ return (
510
+ <>
511
+ <style>{DOCK_CSS}</style>
512
+ <div
513
+ data-kai-dock
514
+ data-position={props.position ?? 'bottom-end'}
515
+ // Drives the mobile panel's reserved-band rule below: present only when the
516
+ // built-in [part="close"] X is ACTUALLY suppressed (hideClose true), so the
517
+ // band stays on for every consumer who hasn't opted into their own close
518
+ // control — see the [part="close"]/reserved-band comments for why removing
519
+ // it unconditionally regressed the collision it existed to prevent.
520
+ bool:data-hide-close={!!props.hideClose}
521
+ onKeyDown={onKeyDown}
522
+ >
523
+ <button
524
+ ref={(el) => { launcher = el; props.launcherRef?.(el); }}
525
+ part="launcher"
526
+ type="button"
527
+ aria-expanded={isOpen() ? 'true' : 'false'}
528
+ aria-controls={panelId}
529
+ // The NAME is what changes for assistive tech; the glyph morph is decorative.
530
+ aria-label={launcherName()}
531
+ disabled={props.disabled || undefined}
532
+ onClick={() => setOpen(!isOpen())}
533
+ >
534
+ <Show when={props.unread && !isOpen()}>
535
+ <span part="badge" aria-hidden="true" />
536
+ </Show>
537
+ <span aria-hidden="true" style={{ display: 'inline-flex' }}>
538
+ <Show when={isOpen()} fallback={props.launcher ?? <DockLauncherGlyph />}>
539
+ {props.launcherOpen ?? props.launcher ?? <DockCloseGlyph />}
540
+ </Show>
541
+ </span>
542
+ </button>
543
+ <div
544
+ ref={(el) => { panel = el; props.panelRef?.(el); }}
545
+ part="panel"
546
+ id={panelId}
547
+ role="dialog"
548
+ // Non-modal, and saying so is load-bearing given there is no focus trap:
549
+ // claiming modality without trapping is worse than claiming neither.
550
+ aria-modal="false"
551
+ aria-label={label()}
552
+ tabindex={-1}
553
+ data-expanded={isOpen() ? '' : undefined}
554
+ data-closed={isOpen() ? undefined : ''}
555
+ // `inert` is the half that actually stops Tab: a visibility:hidden panel is
556
+ // already skipped by most AT, but a focusable control inside it is not.
557
+ bool:inert={!isOpen()}
558
+ // INLINE, unlike the geometry: the closed state must beat any consumer rule,
559
+ // and it is the half a test can read without a layout engine.
560
+ style={{
561
+ visibility: isOpen() ? 'visible' : 'hidden',
562
+ opacity: isOpen() ? '1' : '0',
563
+ 'pointer-events': isOpen() ? 'auto' : 'none',
564
+ }}
565
+ >
566
+ {/* Mobile-only (see the <=480px rule in DOCK_CSS): the full-bleed panel's
567
+ own close affordance, since the launcher is hidden by the same query
568
+ while the panel is open. Reuses the launcher's derived close name
569
+ rather than a new prop — same string, same i18n override. Dropped
570
+ from the render tree entirely (not just hidden) when `hideClose` is
571
+ set — the panel content supplies its own, in its own header, and a
572
+ second X floating over that row is the exact "doesn't look
573
+ intentional" feedback `hideClose` exists to avoid. */}
574
+ <Show when={!props.hideClose}>
575
+ <button type="button" part="close" aria-label={launcherName()} onClick={() => setOpen(false)}>
576
+ <DockCloseGlyph />
577
+ </button>
578
+ </Show>
579
+ {props.children}
580
+ </div>
581
+ </div>
582
+ </>
583
+ );
584
+ }