@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
@@ -0,0 +1,180 @@
1
+ // src/state/persistence.ts — the mechanical halves of the persistence story.
2
+ //
3
+ // The boundary (spec §1, CLAUDE.md): persistence POLICY — what is stored,
4
+ // where, for how long, under what quota — is the app's. What lives here is
5
+ // only the mechanics every such app re-derives by hand: validating a stored
6
+ // thread back into `ChatMessage[]` (F-18), and the debounce/flush shape that
7
+ // keeps a per-token stream from hitting storage per token. No storage call,
8
+ // no fetch, no DOM; the save fn, the delay, and the reaction to a drop are
9
+ // all the consumer's.
10
+ import type { ChatMessage, FeedbackVote, MessagePart } from '../elements/chat-types';
11
+
12
+ /** One record the parse refused, and why. Deciding loudly is the caller's
13
+ * verb (warn, reject the whole record, count it); this is the information
14
+ * that a quiet version would withhold. */
15
+ export interface DroppedStored {
16
+ at: 'thread' | 'message' | 'part';
17
+ reason: string;
18
+ value: unknown;
19
+ }
20
+
21
+ export interface ParsedThread {
22
+ messages: ChatMessage[];
23
+ /** Empty on a clean parse. Never silently shorter output than input. */
24
+ dropped: DroppedStored[];
25
+ }
26
+
27
+ function isRecord(v: unknown): v is Record<string, unknown> {
28
+ return typeof v === 'object' && v !== null;
29
+ }
30
+
31
+ /**
32
+ * One payload check per `MessagePart` variant. The key set is DERIVED from the
33
+ * union in `src/elements/chat-types.ts` at compile time: `satisfies
34
+ * Record<MessagePart['type'], …>` makes a missing key (a 7th variant landed)
35
+ * and a stale key (a variant renamed away) each a type error in THIS file —
36
+ * the same union `lint:silent-drops` and `verify:scaffold` read, so the lists
37
+ * cannot disagree. The behavioral cross-check lives in ./persistence.test.ts,
38
+ * which re-reads the union by a deliberately different method.
39
+ *
40
+ * These are shape checks, not a trust boundary for rendering: a rehydrated
41
+ * part is model output twice over (it was model output before it was
42
+ * hand-editable storage), and it stays safe to draw because the elements are
43
+ * what draw it. What this buys is that a truncated or half-written record
44
+ * reaches the element as a well-formed thread minus the broken piece, instead
45
+ * of as `parts: undefined` taking the whole app down on first render.
46
+ */
47
+ const PART_VALIDATORS = {
48
+ text: (v) => typeof v.text === 'string',
49
+ reasoning: (v) => typeof v.text === 'string',
50
+ tool: (v) => isRecord(v.tool),
51
+ card: (v) => isRecord(v.envelope) && typeof v.envelope.type === 'string' && typeof v.envelope.id === 'string',
52
+ source: (v) => isRecord(v.source),
53
+ file: (v) => isRecord(v.attachment),
54
+ } satisfies Record<MessagePart['type'], (v: Record<string, unknown>) => boolean>;
55
+
56
+ function parsePart(v: unknown, dropped: DroppedStored[]): MessagePart | null {
57
+ if (!isRecord(v) || typeof v.type !== 'string') {
58
+ dropped.push({ at: 'part', reason: 'not a part record', value: v });
59
+ return null;
60
+ }
61
+ const validate = (PART_VALIDATORS as Record<string, (v: Record<string, unknown>) => boolean>)[v.type];
62
+ if (!validate) {
63
+ // An unknown `type` is dropped rather than passed on: every renderer and
64
+ // encoder switches on it and has no branch for a made-up one.
65
+ dropped.push({ at: 'part', reason: `unknown part type "${v.type}"`, value: v });
66
+ return null;
67
+ }
68
+ if (!validate(v)) {
69
+ dropped.push({ at: 'part', reason: `missing or invalid payload for "${v.type}" part`, value: v });
70
+ return null;
71
+ }
72
+ // Verbatim, not rebuilt: optional fields the variants carry (`raw`,
73
+ // `signature`, `index`, …) are load-bearing on the wire re-encode, so the
74
+ // validated record passes through whole.
75
+ return v as unknown as MessagePart;
76
+ }
77
+
78
+ function parseMessage(v: unknown, dropped: DroppedStored[]): ChatMessage | null {
79
+ if (!isRecord(v) || typeof v.id !== 'string' || (v.role !== 'user' && v.role !== 'assistant') || !Array.isArray(v.parts)) {
80
+ dropped.push({ at: 'message', reason: 'not a message record (id/role/parts)', value: v });
81
+ return null;
82
+ }
83
+ const parts = v.parts
84
+ .map((p) => parsePart(p, dropped))
85
+ .filter((p): p is MessagePart => p !== null);
86
+ if (parts.length === 0) {
87
+ // A message whose every part was unreadable is an empty bubble, and the
88
+ // per-part entries above already say what was lost.
89
+ dropped.push({ at: 'message', reason: 'no readable parts survived', value: v });
90
+ return null;
91
+ }
92
+ const message: ChatMessage = { id: v.id, role: v.role, parts };
93
+ if (v.feedback === 'like' || v.feedback === 'dislike') message.feedback = v.feedback as FeedbackVote;
94
+ if (isRecord(v.avatar)) message.avatar = v.avatar;
95
+ if (Array.isArray(v.actions)) message.actions = v.actions as ChatMessage['actions'];
96
+ return message;
97
+ }
98
+
99
+ /**
100
+ * Validate a stored thread — the JSON-parsed value of one thread's messages
101
+ * array — back into `ChatMessage[]`. Never throws: unreadable records are
102
+ * DROPPED and reported in `dropped`, so a truncated write loses one record,
103
+ * not every conversation. What to DO about a drop (warn, discard the whole
104
+ * record, telemetry) is the caller's policy.
105
+ */
106
+ export function parseStoredThread(value: unknown): ParsedThread {
107
+ const dropped: DroppedStored[] = [];
108
+ if (!Array.isArray(value)) {
109
+ return { messages: [], dropped: [{ at: 'thread', reason: 'stored thread is not an array', value }] };
110
+ }
111
+ const messages = value
112
+ .map((m) => parseMessage(m, dropped))
113
+ .filter((m): m is ChatMessage => m !== null);
114
+ return { messages, dropped };
115
+ }
116
+
117
+ export interface SaveSchedulerOptions {
118
+ /** The quiet period before a scheduled snapshot is written. This is policy
119
+ * and therefore REQUIRED — the kit ships no default debounce. */
120
+ delayMs: number;
121
+ /** The scheduler the seam runs on. Defaults to the global timers; inject
122
+ * your own for tests or a non-timer scheduler. */
123
+ setTimeout?: (fn: () => void, ms: number) => unknown;
124
+ clearTimeout?: (handle: unknown) => void;
125
+ }
126
+
127
+ export interface SaveScheduler<T> {
128
+ /** Remember `snapshot` as the latest state and (re)start the quiet-period
129
+ * timer. Call it on every state change; only the last snapshot inside the
130
+ * quiet period is written. */
131
+ schedule(snapshot: T): void;
132
+ /** Write the pending snapshot NOW and disarm the timer — the
133
+ * `beforeunload`/`visibilitychange` story, so a tab closed mid-stream still
134
+ * persists. No-op when nothing is pending: the last write already happened. */
135
+ flush(): void;
136
+ /** Drop the pending write unsaved (e.g. unmount-without-persist). */
137
+ cancel(): void;
138
+ pending(): boolean;
139
+ }
140
+
141
+ /**
142
+ * The debounce/flush seam: a streaming reply lands a state update per token,
143
+ * and without this shape so would the consumer's storage backend. The kit owns
144
+ * only the timer dance; `save` (WHAT and WHERE, including its own try/catch
145
+ * policy for quota errors) and `delayMs` (HOW LONG) are the consumer's.
146
+ */
147
+ export function createSaveScheduler<T>(save: (snapshot: T) => void, opts: SaveSchedulerOptions): SaveScheduler<T> {
148
+ const setTimer = opts.setTimeout ?? ((fn: () => void, ms: number) => setTimeout(fn, ms));
149
+ const clearTimer = opts.clearTimeout ?? ((handle: unknown) => clearTimeout(handle as ReturnType<typeof setTimeout>));
150
+
151
+ let latest: T | undefined;
152
+ let armed = false;
153
+ let handle: unknown;
154
+
155
+ const disarm = () => {
156
+ if (!armed) return;
157
+ clearTimer(handle);
158
+ armed = false;
159
+ };
160
+ const write = () => {
161
+ armed = false;
162
+ save(latest as T);
163
+ };
164
+
165
+ return {
166
+ schedule(snapshot) {
167
+ latest = snapshot;
168
+ disarm();
169
+ handle = setTimer(write, opts.delayMs);
170
+ armed = true;
171
+ },
172
+ flush() {
173
+ if (!armed) return;
174
+ disarm();
175
+ save(latest as T);
176
+ },
177
+ cancel: disarm,
178
+ pending: () => armed,
179
+ };
180
+ }
@@ -116,6 +116,56 @@ export interface AssistantStream {
116
116
  * assignability already holds. Measurements and the trade: boundary point 2. */
117
117
  addFile(attachment: AttachmentData): AssistantStream;
118
118
  done(): void;
119
+ /** Settles the turn as FAILED and puts `reason` where the reader can find it.
120
+ *
121
+ * WHERE THE REASON LANDS, in order:
122
+ * 1. every tool part that has NOT produced a result flips to `output-error`
123
+ * with `errorText: reason`, so no panel spins forever. A tool ALREADY in
124
+ * `output-error` with its own non-empty `errorText` is left exactly as it
125
+ * is: "search index offline" is the answer to what went wrong and
126
+ * "Connection lost." is the generic outer symptom, so overwriting the
127
+ * specific with the generic loses the only actionable thing on the
128
+ * message. It still counts as carrying the failure. A tool in
129
+ * `output-error` with NO text does get filled in — an error panel with
130
+ * nothing in it says no more than a blank bubble does.
131
+ * 2. if no part was able to carry it, the reason is APPENDED as its own text
132
+ * part.
133
+ *
134
+ * Never both — a turn does not report the same failure twice.
135
+ *
136
+ * "Where the reader can FIND it" is deliberate, and rule 1 and rule 2 are not
137
+ * equally loud. Rule 2 is text in the thread: read without any interaction.
138
+ * Rule 1 is a tool panel, which renders COLLAPSED — the header shows an error
139
+ * icon and a badge, so the failure is unmissable, but the `errorText` itself
140
+ * is one click away. That is the right default for a failed tool inside an
141
+ * otherwise readable answer, and it is why rule 2 exists rather than always
142
+ * stamping the panel and calling it reported.
143
+ *
144
+ * Rule 2 is the whole point and it used to be missing. `abort` only ever did
145
+ * rule 1, so a TEXT-ONLY turn — every turn of a text-only support widget —
146
+ * had nothing to stamp: the string was discarded and a failed request
147
+ * rendered an EMPTY assistant bubble, while the consumer that passed a
148
+ * perfectly good sentence believed it had reported the failure. That is the
149
+ * repo's "decide loudly" rule broken on the one path where being quiet is
150
+ * worst, and it shipped because the discard is invisible from the call site.
151
+ *
152
+ * It is a NEW part, never a merge onto the trailing text: gluing "Connection
153
+ * lost." onto the model's half-finished sentence reads as the model saying
154
+ * it. Text that already streamed stays exactly where it is.
155
+ *
156
+ * The reason is TRIMMED, and `abort()` with no reason — or one that is empty
157
+ * or all whitespace — appends nothing: there is no reason to discard, and the
158
+ * kit will not invent copy the consumer did not write. Rule 1 still runs,
159
+ * with `errorText: undefined`. Whitespace is not a pedantic case here: the
160
+ * scaffold hands over `err.message`, and an `Error` is free to carry `''`,
161
+ * which would otherwise render an INVISIBLE text part — a blank bubble that
162
+ * also claims to have said something.
163
+ *
164
+ * Settled is settled: after `done()` or a first `abort()`, this is a no-op
165
+ * like every other mutator, so the reason cannot be appended twice.
166
+ *
167
+ * The reason is CONSUMER-facing text and is rendered as markdown like any
168
+ * other text part. Pass a sentence a visitor can read, not a stack trace. */
119
169
  abort(reason?: string): void;
120
170
  }
121
171
 
@@ -151,10 +201,27 @@ export function createAssistantStream(
151
201
  addFile(attachment) { mutate((p) => [...p, { type: 'file', attachment }]); return stream; },
152
202
  done() { settled = true; },
153
203
  abort(reason) {
154
- mutate((p) => p.map((part) =>
155
- part.type === 'tool' && part.tool.state !== 'output-available'
156
- ? { ...part, tool: { ...part.tool, state: 'output-error' as const, errorText: reason } }
157
- : part));
204
+ // Whitespace-only is the same nothing as empty. `err.message` on a thrown
205
+ // Error is free to be either, and appending it would render an INVISIBLE
206
+ // text part -- a blank bubble that also claims to have said something.
207
+ const text = reason?.trim() || undefined;
208
+ mutate((p) => {
209
+ let carried = false;
210
+ const next = p.map((part) => {
211
+ if (part.type !== 'tool' || part.tool.state === 'output-available') return part;
212
+ // Already showing its own, more specific failure: keep it, and count
213
+ // it as carrying. See the note above `abort`.
214
+ if (part.tool.state === 'output-error' && part.tool.errorText?.trim()) {
215
+ carried = true;
216
+ return part;
217
+ }
218
+ carried = true;
219
+ return { ...part, tool: { ...part.tool, state: 'output-error' as const, errorText: text } };
220
+ });
221
+ // The reason has to land SOMEWHERE. See the note above `abort`.
222
+ if (carried || !text) return next;
223
+ return [...next, { type: 'text', text }];
224
+ });
158
225
  settled = true;
159
226
  },
160
227
  };
@@ -0,0 +1,142 @@
1
+ // src/state/threads.ts — the thread-switching fold.
2
+ //
3
+ // The identity category from the workspace glue inventory (spec §1/§3b): an
4
+ // id-bound `SetMessages`, per-thread loading state, the abort map, and
5
+ // delete-under-stream safety. Pure folds + callbacks per the state charter —
6
+ // no fetch, no DOM, no storage; the only state a helper here owns is the
7
+ // irreducible kind (which turns are in flight), never the consumer's records.
8
+ import type { ChatMessage } from '../elements/chat-types';
9
+ import type { SetMessages } from './stream';
10
+
11
+ /** The minimal thread record the fold needs. The consumer's own record type
12
+ * (title, timestamps, whatever its storage keeps) extends this — those fields
13
+ * are policy and stay outside the kit. */
14
+ export interface ThreadLike {
15
+ id: string;
16
+ messages: ChatMessage[];
17
+ }
18
+
19
+ /** The functional-updater setter over the whole thread LIST — the same
20
+ * React-setState shape `SetMessages` is, one level up. */
21
+ export type SetThreads<T extends ThreadLike> = (updater: (prev: T[]) => T[]) => void;
22
+
23
+ export interface BindThreadOptions<T extends ThreadLike> {
24
+ /** Stamp the changed record (e.g. `updatedAt`). Runs on a hit only. Kept a
25
+ * callback because WHAT gets stamped — and with whose clock — is the
26
+ * consumer's record shape, not ours. */
27
+ touch?: (thread: T) => T;
28
+ /** A delta arrived for a thread that is no longer in the list (deleted while
29
+ * its reply was streaming). The delta is dropped either way — dropping is
30
+ * the only correct fold — but the drop is reported, not silent. */
31
+ onDrop?: (threadId: string) => void;
32
+ }
33
+
34
+ /**
35
+ * Pure fold: apply `updater` to the messages of thread `id` within a list.
36
+ *
37
+ * reactivity-two-halves (CLAUDE.md): the hit gets a NEW containing array
38
+ * (which notifies) and a NEW record object (which makes the change visible);
39
+ * every other record keeps its reference. A MISS returns `threads` itself —
40
+ * the same reference — so a late delta for a deleted thread re-renders
41
+ * nothing and resurrects nothing.
42
+ */
43
+ export function updateThreadMessages<T extends ThreadLike>(
44
+ threads: T[],
45
+ id: string,
46
+ updater: (prev: ChatMessage[]) => ChatMessage[],
47
+ opts: BindThreadOptions<T> = {},
48
+ ): T[] {
49
+ let hit = false;
50
+ const next = threads.map((t) => {
51
+ if (t.id !== id) return t;
52
+ hit = true;
53
+ const updated = { ...t, messages: updater(t.messages) };
54
+ return opts.touch ? opts.touch(updated) : updated;
55
+ });
56
+ if (!hit) {
57
+ opts.onDrop?.(id);
58
+ return threads;
59
+ }
60
+ return next;
61
+ }
62
+
63
+ /**
64
+ * A `SetMessages` bound to ONE thread. Capture the id when the user hits send
65
+ * and hand the result to `createAssistantStream`: the reply then lands in the
66
+ * thread that was open at send time, never whichever one is open when the
67
+ * tokens arrive — which is what makes switching away mid-reply (and coming
68
+ * back to a finished answer) safe.
69
+ */
70
+ export function bindThreadMessages<T extends ThreadLike>(
71
+ setThreads: SetThreads<T>,
72
+ id: string,
73
+ opts: BindThreadOptions<T> = {},
74
+ ): SetMessages {
75
+ return (updater) => {
76
+ setThreads((prev) => updateThreadMessages(prev, id, updater, opts));
77
+ };
78
+ }
79
+
80
+ /** The in-flight-turn bookkeeping for a multi-thread app: which threads are
81
+ * streaming, and the AbortController for each. See `createThreadSessions`. */
82
+ export interface ThreadSessions {
83
+ /** Register a new in-flight turn. One turn per thread: a prior in-flight
84
+ * turn for the SAME thread is aborted first. Pass the returned controller's
85
+ * `signal` to your fetch — the fetch line itself stays yours. */
86
+ begin(threadId: string): AbortController;
87
+ /** The turn settled (done or failed) — call from your `finally`. `controller`
88
+ * is required so a slow old turn's cleanup cannot clear a newer turn that
89
+ * has since begun on the same thread: a stale controller is a no-op. */
90
+ end(threadId: string, controller: AbortController): void;
91
+ /** Cancel the in-flight turn (the delete-under-stream story): aborts the
92
+ * controller and clears the streaming state. Returns whether anything was
93
+ * actually in flight. Pair it with the id-bound sink above, which drops any
94
+ * deltas that were already past the abort. */
95
+ abort(threadId: string): boolean;
96
+ isStreaming(threadId: string): boolean;
97
+ /** A fresh array snapshot each call — safe to hand straight to a setState. */
98
+ streamingIds(): string[];
99
+ }
100
+
101
+ /**
102
+ * Create the session tracker. `onChange` fires with a fresh `streamingIds()`
103
+ * snapshot after every transition — mirror it into your framework's state to
104
+ * derive `loading` (`sessions.isStreaming(activeId)` is the per-thread flag a
105
+ * single boolean cannot be, because a stream keeps running when you switch
106
+ * away).
107
+ *
108
+ * This is the one stateful object in the module, because an abort map is
109
+ * irreducibly stateful; it still does no I/O and owns no timer.
110
+ */
111
+ export function createThreadSessions(onChange?: (streamingIds: string[]) => void): ThreadSessions {
112
+ const inFlight = new Map<string, AbortController>();
113
+ const snapshot = () => Array.from(inFlight.keys());
114
+ const notify = () => onChange?.(snapshot());
115
+
116
+ return {
117
+ begin(threadId) {
118
+ inFlight.get(threadId)?.abort();
119
+ const controller = new AbortController();
120
+ inFlight.set(threadId, controller);
121
+ notify();
122
+ return controller;
123
+ },
124
+ end(threadId, controller) {
125
+ if (inFlight.get(threadId) !== controller) return;
126
+ inFlight.delete(threadId);
127
+ notify();
128
+ },
129
+ abort(threadId) {
130
+ const controller = inFlight.get(threadId);
131
+ if (!controller) return false;
132
+ controller.abort();
133
+ inFlight.delete(threadId);
134
+ notify();
135
+ return true;
136
+ },
137
+ isStreaming(threadId) {
138
+ return inFlight.has(threadId);
139
+ },
140
+ streamingIds: snapshot,
141
+ };
142
+ }
package/src/types.ts CHANGED
@@ -31,9 +31,11 @@ export interface ConversationSummary {
31
31
  id: string;
32
32
  title: string;
33
33
  groupId?: string;
34
- scope: ConversationScope;
34
+ /** Only meaningful to scope-aware consumers; the list never reads it. */
35
+ scope?: ConversationScope;
35
36
  messageCount: number;
36
- lastMessageAt: string;
37
+ /** Fallback for the auto relative time when updatedAt is absent. */
38
+ lastMessageAt?: string;
37
39
  updatedAt: string;
38
40
  /** Right-aligned trailing text on the row — a count, status, or "days ago".
39
41
  * When omitted, a short relative time is auto-derived from `updatedAt`
@@ -156,7 +156,7 @@ export function AgentCard(props: AgentCardProps) {
156
156
  />
157
157
  </span>
158
158
  <Show when={local.status.label}>
159
- <span class={cn('text-[11px] font-medium leading-none', TONE_TEXT[tone()])}>
159
+ <span class={cn('text-caption font-medium leading-none', TONE_TEXT[tone()])}>
160
160
  {local.status.label}
161
161
  </span>
162
162
  </Show>
@@ -185,7 +185,7 @@ export function AgentCard(props: AgentCardProps) {
185
185
 
186
186
  <span class="flex shrink-0 items-center gap-2">
187
187
  <Show when={local.needsAttention}>
188
- <span class="inline-flex items-center gap-1 rounded-full bg-tool-amber/15 px-2 py-0.5 text-[11px] font-medium text-tool-amber">
188
+ <span class="inline-flex items-center gap-1 rounded-full bg-tool-amber/15 px-2 py-0.5 text-caption font-medium leading-none text-tool-amber">
189
189
  <Bell class="size-3" aria-hidden="true" />
190
190
  Needs you
191
191
  </span>
package/src/ui/avatar.tsx CHANGED
@@ -8,7 +8,7 @@ export interface AvatarProps extends JSX.HTMLAttributes<HTMLDivElement> {
8
8
  size?: 'sm' | 'md' | 'lg';
9
9
  }
10
10
 
11
- const sizeClasses = { sm: 'h-6 w-6 text-[10px]', md: 'h-8 w-8 text-xs', lg: 'h-10 w-10 text-sm' };
11
+ const sizeClasses = { sm: 'h-6 w-6 text-micro', md: 'h-8 w-8 text-xs', lg: 'h-10 w-10 text-sm' };
12
12
 
13
13
  export function Avatar(props: AvatarProps) {
14
14
  const [local, rest] = splitProps(props, ['src', 'alt', 'fallback', 'size', 'class']);
@@ -0,0 +1,153 @@
1
+ import { type JSX, For, Show, splitProps } from 'solid-js';
2
+ import { cn } from '../utils/cn';
3
+ import { Checkbox } from './checkbox';
4
+
5
+ /** One choice in a {@link CheckboxGroup}. */
6
+ export interface CheckboxOption<T = string> {
7
+ /** The value this row selects. Compared to the group's `value` by identity. */
8
+ value: T;
9
+ /** The row's visible label. */
10
+ label: JSX.Element;
11
+ /** Optional second line under the label. */
12
+ description?: JSX.Element;
13
+ /** Disable this row alone (the group's `disabled` disables all of them). */
14
+ disabled?: boolean;
15
+ }
16
+
17
+ export interface CheckboxGroupProps<T = string>
18
+ extends Omit<JSX.HTMLAttributes<HTMLDivElement>, 'onChange' | 'children'> {
19
+ /**
20
+ * The choices, in display order. Rendered in full: the kit never truncates,
21
+ * re-orders or de-duplicates a consumer's option list.
22
+ */
23
+ options: readonly CheckboxOption<T>[];
24
+ /** The selected values. Each option is checked when its `value` is in here (by identity). */
25
+ value?: readonly T[];
26
+ /**
27
+ * The shared form-control name every box in the group carries, so a native form
28
+ * submits the whole selection under one key and `FormData.getAll(name)` reads it back.
29
+ *
30
+ * UNLIKE {@link RadioGroup}, this has no default. A radio group needs a shared `name`
31
+ * for the browser to make the set exclusive and arrow-navigable, so one is generated
32
+ * when none is given; checkboxes are independent controls and need nothing to behave
33
+ * correctly. Generating a name here would submit the selection under a random key,
34
+ * which is worse than submitting nothing.
35
+ */
36
+ name?: string;
37
+ /** Disable every row. */
38
+ disabled?: boolean;
39
+ /** Accessible name for the group. Rendered as `aria-label` on the `group`. */
40
+ label?: string;
41
+ /**
42
+ * Fires on every toggle with the NEXT selection, plus the option that moved and
43
+ * which way it went. The next selection is a fresh array — the group holds no state
44
+ * of its own, so `value` is yours to own.
45
+ */
46
+ onChange?: (value: T[], option: CheckboxOption<T>, checked: boolean) => void;
47
+ /** Fires when a box loses focus — the commit point for a form field. */
48
+ onOptionBlur?: () => void;
49
+ /** Extra classes for each row. */
50
+ itemClass?: string;
51
+ /**
52
+ * Presentation slot. Replaces the default label/description column with whatever
53
+ * you return, so a row can carry media, a badge or a price without a second
54
+ * checkbox component existing. The control, the row chrome and the group semantics
55
+ * stay ours.
56
+ */
57
+ children?: (option: CheckboxOption<T>, state: { checked: boolean; disabled: boolean }) => JSX.Element;
58
+ }
59
+
60
+ /**
61
+ * A vertical set of checkbox rows in a bordered, divided list — the kit's standard
62
+ * "pick any number" control, and {@link RadioGroup}'s sibling: same options shape,
63
+ * same row chrome, same presentation slot, multi-value instead of single.
64
+ *
65
+ * Every row is a real `<input type="checkbox">` inside a `<label>`, so clicking
66
+ * anywhere on the row toggles it and the keyboard behaviour (a tab stop per box,
67
+ * Space to toggle) is the browser's rather than a reimplementation. The wrapper is
68
+ * `role="group"`, not `role="listbox"`: the boxes are independent controls and
69
+ * nothing here overrides what they already announce.
70
+ *
71
+ * Everything not listed in `CheckboxGroupProps` is forwarded to the group element, so
72
+ * `id`, `aria-labelledby`, any other `aria-*` and any `data-*` hook land where a form
73
+ * expects them. No validation is applied — "at least one" is your application's rule,
74
+ * not the kit's.
75
+ *
76
+ * ```tsx
77
+ * <CheckboxGroup
78
+ * label="Environments"
79
+ * name="env"
80
+ * options={[{ value: 'prod', label: 'Production' }, { value: 'staging', label: 'Staging' }]}
81
+ * value={envs()}
82
+ * onChange={setEnvs}
83
+ * />
84
+ * ```
85
+ */
86
+ export function CheckboxGroup<T = string>(props: CheckboxGroupProps<T>): JSX.Element {
87
+ const [local, rest] = splitProps(props, [
88
+ 'options', 'value', 'name', 'disabled', 'label', 'onChange', 'onOptionBlur', 'itemClass', 'children', 'class',
89
+ ]);
90
+ const selected = (): readonly T[] => local.value ?? [];
91
+ return (
92
+ <div
93
+ {...rest}
94
+ role="group"
95
+ aria-label={local.label}
96
+ class={cn('divide-y divide-border overflow-hidden rounded-lg border border-border', local.class)}
97
+ >
98
+ <For each={local.options}>
99
+ {(opt) => {
100
+ const checked = () => selected().includes(opt.value);
101
+ const rowDisabled = () => !!(local.disabled || opt.disabled);
102
+ return (
103
+ <label
104
+ class={cn(
105
+ 'flex cursor-pointer items-center gap-3 px-3 py-2.5 text-sm transition-colors',
106
+ opt.description !== undefined && 'items-start',
107
+ checked()
108
+ ? 'bg-accent font-medium text-accent-foreground'
109
+ : 'text-foreground hover:bg-muted/50',
110
+ local.itemClass,
111
+ )}
112
+ >
113
+ <Checkbox
114
+ name={local.name}
115
+ value={String(opt.value)}
116
+ checked={checked()}
117
+ disabled={rowDisabled()}
118
+ class={opt.description === undefined ? undefined : 'mt-0.5'}
119
+ onChange={(e) => {
120
+ const on = e.currentTarget.checked;
121
+ const now = selected();
122
+ // Filter-or-append rather than a toggle on the previous array, so the
123
+ // emitted selection follows the CONTROL's state. They diverge when the
124
+ // caller re-renders mid-interaction, and following the box is what a
125
+ // native form would report.
126
+ const next = on ? [...now.filter((v) => v !== opt.value), opt.value] : now.filter((v) => v !== opt.value);
127
+ local.onChange?.(next, opt, on);
128
+ }}
129
+ onBlur={() => local.onOptionBlur?.()}
130
+ />
131
+ <Show
132
+ when={local.children}
133
+ fallback={
134
+ // No description, a bare <span> — byte for byte what the form's
135
+ // hand-rolled checkbox list rendered before this component existed.
136
+ // The two-line column only appears when there is a second line.
137
+ <Show when={opt.description !== undefined} fallback={<span>{opt.label}</span>}>
138
+ <span class="flex flex-col gap-0.5">
139
+ <span>{opt.label}</span>
140
+ <span class="text-xs font-normal text-muted-foreground">{opt.description}</span>
141
+ </span>
142
+ </Show>
143
+ }
144
+ >
145
+ {(render) => render()(opt, { checked: checked(), disabled: rowDisabled() })}
146
+ </Show>
147
+ </label>
148
+ );
149
+ }}
150
+ </For>
151
+ </div>
152
+ );
153
+ }
@@ -0,0 +1,63 @@
1
+ import { type JSX, splitProps, createEffect } from 'solid-js';
2
+ import { cn } from '../utils/cn';
3
+
4
+ export interface CheckboxProps extends Omit<JSX.InputHTMLAttributes<HTMLInputElement>, 'type' | 'children'> {
5
+ /**
6
+ * The mixed / partially-checked state ("some of the things below are ticked").
7
+ *
8
+ * `indeterminate` is a DOM PROPERTY with no HTML attribute, so it cannot be set
9
+ * from markup — every caller that wanted it used to reach for a `ref` plus a
10
+ * `createEffect`. That effect now lives in here, once, and callers pass a prop.
11
+ *
12
+ * Note that the property is purely visual plus an accessibility hint: a checkbox
13
+ * whose `indeterminate` is true still reports `checked === false` and submits
14
+ * accordingly. A native indeterminate checkbox already announces as "mixed", so
15
+ * this component does not stamp `aria-checked="mixed"` on top of it — pass one
16
+ * yourself if you also want the attribute in the DOM.
17
+ */
18
+ indeterminate?: boolean;
19
+ }
20
+
21
+ /**
22
+ * A checkbox. A REAL `<input type="checkbox">` behind `appearance: none`, styled by
23
+ * the kit's `.kai-checkbox` rule, never a `<div>` wearing `role="checkbox"`.
24
+ *
25
+ * That is the whole design: the native control brings keyboard operation (Space),
26
+ * the `:focus-visible` ring, form participation via `name` / `value`, and correct
27
+ * screen-reader announcement — all of it for free and all of it correct. Every
28
+ * accessibility defect this kit's control audit found was in a control that had
29
+ * replaced the native element with something hand-rolled.
30
+ *
31
+ * Everything not listed in `CheckboxProps` is forwarded to the input, so `id`,
32
+ * `name`, `value`, `required`, `disabled`, `aria-*` and any `data-*` hook behave
33
+ * exactly as they do on a plain `<input>`.
34
+ *
35
+ * Validation is deliberately absent. `required` is passed through to the native
36
+ * attribute and nothing else — whether an unchecked box is an error is the
37
+ * consuming application's call, not the kit's.
38
+ *
39
+ * ```tsx
40
+ * <Checkbox name="notify" checked={on()} onChange={(e) => setOn(e.currentTarget.checked)} />
41
+ * ```
42
+ */
43
+ export function Checkbox(props: CheckboxProps): JSX.Element {
44
+ const [local, rest] = splitProps(props, ['class', 'indeterminate', 'ref']);
45
+ return (
46
+ <input
47
+ {...rest}
48
+ type="checkbox"
49
+ class={cn('kai-checkbox', local.class)}
50
+ ref={(el) => {
51
+ // `ref` is split out and re-applied by hand because this component needs the
52
+ // element for `indeterminate` as well. Solid always hands a component a ref
53
+ // FUNCTION (a variable ref compiles into one), so the typeof guard is for the
54
+ // hand-written object case only.
55
+ const forward = local.ref;
56
+ if (typeof forward === 'function') (forward as (el: HTMLInputElement) => void)(el);
57
+ createEffect(() => {
58
+ el.indeterminate = !!local.indeterminate;
59
+ });
60
+ }}
61
+ />
62
+ );
63
+ }