@kitn.ai/ui 0.25.2 → 0.26.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 (409) hide show
  1. package/README.md +2 -2
  2. package/dist/components/attachment-types.d.ts +46 -1
  3. package/dist/components/attachments.d.ts +31 -0
  4. package/dist/components/audio-visualizer/fit-scale.d.ts +50 -0
  5. package/dist/components/audio-visualizer/index.d.ts +15 -1
  6. package/dist/components/audio-visualizer/variant-bar.d.ts +24 -1
  7. package/dist/components/chat-thread.d.ts +3 -41
  8. package/dist/components/code-block.d.ts +22 -0
  9. package/dist/components/conversation-item.d.ts +54 -1
  10. package/dist/components/conversation-list.d.ts +65 -0
  11. package/dist/components/form.d.ts +42 -0
  12. package/dist/components/message.d.ts +7 -0
  13. package/dist/components/toast.d.ts +2 -1
  14. package/dist/components/voice-input.d.ts +8 -0
  15. package/dist/components/voice-output.d.ts +12 -1
  16. package/dist/components/workspace-shell.d.ts +87 -0
  17. package/dist/{create-tween-B5Y4Im8G.js → create-tween-BFT6c-Y8.js} +1 -1
  18. package/dist/{create-tween-4z1XYLkR.js → create-tween-CJNlzoaO.js} +1 -1
  19. package/dist/{create-tween-CUzOlaMS.js → create-tween-CQED4T0g.js} +1 -1
  20. package/dist/custom-elements.json +2931 -2572
  21. package/dist/diagnostics/hook.d.ts +81 -0
  22. package/dist/diagnostics/index.d.ts +7 -0
  23. package/dist/diagnostics/report-request.d.ts +61 -0
  24. package/dist/diagnostics.d.ts +5 -0
  25. package/dist/diagnostics.js +209 -0
  26. package/dist/elements/agent-card.js +1 -1
  27. package/dist/elements/artifact.js +1 -1
  28. package/dist/elements/attachments.js +1 -1
  29. package/dist/elements/audio-visualizer.js +1 -1
  30. package/dist/elements/autoloader.js +1 -1
  31. package/dist/elements/avatar.js +1 -1
  32. package/dist/elements/badge.js +1 -1
  33. package/dist/elements/button.js +1 -1
  34. package/dist/elements/card.js +1 -1
  35. package/dist/elements/cards.js +1 -1
  36. package/dist/elements/chain-of-thought.js +1 -1
  37. package/dist/elements/chat-scope-picker.js +1 -1
  38. package/dist/elements/chat-workspace.js +1 -1
  39. package/dist/elements/chat.js +1 -1
  40. package/dist/elements/checkpoint.js +1 -1
  41. package/dist/elements/choice.js +1 -1
  42. package/dist/elements/chunks/{Icon-b5A8hlHs.js → Icon--Y8vsFal.js} +1 -1
  43. package/dist/elements/chunks/action-icons-D9u3OzYW.js +1 -0
  44. package/dist/elements/chunks/arrow-left-CVh1TeN7.js +1 -0
  45. package/dist/elements/chunks/{artifact-BSuPYFHY.js → artifact-BkYcfIlZ.js} +1 -1
  46. package/dist/elements/chunks/attachments-DwfNlubq.js +1 -0
  47. package/dist/elements/chunks/audio-visualizer-D7lB6mvR.js +1 -0
  48. package/dist/elements/chunks/{badge-TIMiJFKc.js → badge-Q0-Cmwzz.js} +1 -1
  49. package/dist/elements/chunks/{button-B9okqG73.js → button-flUbeufF.js} +1 -1
  50. package/dist/elements/chunks/{card-renderer-CSoTL_e1.js → card-renderer-3Dyl-FpN.js} +1 -1
  51. package/dist/elements/chunks/check-B5kBPKeo.js +1 -0
  52. package/dist/elements/chunks/chevron-down-DZ1GS39h.js +1 -0
  53. package/dist/elements/chunks/chevron-right-CvP7OUd2.js +1 -0
  54. package/dist/elements/chunks/{choice-card-CGMc3kek.js → choice-card-D4QUIOWZ.js} +1 -1
  55. package/dist/elements/chunks/circle-CDf9g6MQ.js +1 -0
  56. package/dist/elements/chunks/circle-check-CA47Nqbz.js +1 -0
  57. package/dist/elements/chunks/{circle-x-mhH7pOK2.js → circle-x-CJ_HVbC8.js} +1 -1
  58. package/dist/elements/chunks/code-block-BdLiTitd.js +1 -0
  59. package/dist/elements/chunks/collapsible-BzbXpPzr.js +1 -0
  60. package/dist/elements/chunks/{composer-CZZmtYOT.js → composer-Byve-n8K.js} +2 -2
  61. package/dist/elements/chunks/{confirm-card-BWjf2-xo.js → confirm-card-B0flE3MN.js} +1 -1
  62. package/dist/elements/chunks/context-VLIL3sJ-.js +1 -0
  63. package/dist/elements/chunks/controllable-DTwTIlwp.js +1 -0
  64. package/dist/elements/chunks/conversation-item-BDztezOG.js +1 -0
  65. package/dist/elements/chunks/copy-Nf-kqxXH.js +1 -0
  66. package/dist/elements/chunks/{create-tween-CUrM6D_o.js → create-tween-c4Z8TDYz.js} +2 -2
  67. package/dist/elements/chunks/default-input-C2iLFq0b.js +1 -0
  68. package/dist/elements/chunks/define-wjHR1MkU.js +1 -0
  69. package/dist/elements/chunks/disclosure-CcNbyLy0.js +1 -0
  70. package/dist/elements/chunks/{download-DnCyWFJE.js → download-C4V7tBdb.js} +1 -1
  71. package/dist/elements/chunks/dropdown-BgWTthUx.js +1 -0
  72. package/dist/elements/chunks/{ellipsis-CVxqfsDZ.js → ellipsis-7gU33mL2.js} +1 -1
  73. package/dist/elements/chunks/embed-D3KewsIJ.js +1 -0
  74. package/dist/elements/chunks/{external-link-DG2sRdQL.js → external-link-CSOEsNlx.js} +1 -1
  75. package/dist/elements/chunks/{file-text-D0J9F6M7.js → file-text-D1G8khle.js} +1 -1
  76. package/dist/elements/chunks/{file-tree-yGWNK0Js.js → file-tree-zvB2i7P9.js} +1 -1
  77. package/dist/elements/chunks/{folder-NV1pi_ud.js → folder-WW2LGLid.js} +1 -1
  78. package/dist/elements/chunks/form-CQirdC4W.js +1 -0
  79. package/dist/elements/chunks/hover-card-CJJkfHTK.js +1 -0
  80. package/dist/elements/chunks/icon-Cfgmf4fa.js +1 -0
  81. package/dist/elements/chunks/{info-C8r5Q6cc.js → info-DX3YMOt6.js} +1 -1
  82. package/dist/elements/chunks/input-BtW9U6Sq.js +1 -0
  83. package/dist/elements/chunks/{kbd-OGJDUT-O.js → kbd-LGXwuT_V.js} +1 -1
  84. package/dist/elements/chunks/{link-BdsfLB7b.js → link-2My69BqT.js} +1 -1
  85. package/dist/elements/chunks/{link-preview-Cf5KOP-i.js → link-preview-OQ9IEq0f.js} +1 -1
  86. package/dist/elements/chunks/{loader-B5QzK_8_.js → loader-iOxc_Tu-.js} +1 -1
  87. package/dist/elements/chunks/{markdown-DbozyYqa.js → markdown-DgBFaGAz.js} +1 -1
  88. package/dist/elements/chunks/{message-DtXKPqiR.js → message-VMZohrXx.js} +1 -1
  89. package/dist/elements/chunks/message-circle-CpXMM-_n.js +1 -0
  90. package/dist/elements/chunks/message-square-DxcWtETQ.js +1 -0
  91. package/dist/elements/chunks/message-tZmV2A3p.js +1 -0
  92. package/dist/elements/chunks/{minimize-2-SegJ_oku.js → minimize-2-DPIUKTrT.js} +1 -1
  93. package/dist/elements/chunks/{model-switcher-D7JrFFHp.js → model-switcher-DsT8tFB0.js} +1 -1
  94. package/dist/elements/chunks/{overlay-CV7z2YuX.js → overlay-DuiwdXjm.js} +1 -1
  95. package/dist/elements/chunks/{paperclip-Bg0ITU3b.js → paperclip-CRZVp4z_.js} +1 -1
  96. package/dist/elements/chunks/{progress-bar-D1_rzt6N.js → progress-bar-DNy66e_E.js} +1 -1
  97. package/dist/elements/chunks/{prompt-suggestion-D5Sm8xF1.js → prompt-suggestion-Bn64Nl7L.js} +1 -1
  98. package/dist/elements/chunks/reasoning-Emn9N4-T.js +1 -0
  99. package/dist/elements/chunks/{resizable-Dpzjlvp4.js → resizable-D181b4fM.js} +1 -1
  100. package/dist/elements/chunks/{rotate-cw-DzVDJOA1.js → rotate-cw-CPWe37D9.js} +1 -1
  101. package/dist/elements/chunks/{scroll-area-93A1bbeG.js → scroll-area-ClZkZxIk.js} +1 -1
  102. package/dist/elements/chunks/scroll-button-DPE-t3x_.js +1 -0
  103. package/dist/elements/chunks/{separator-DH2r_ZSZ.js → separator-CeJHQqLW.js} +1 -1
  104. package/dist/elements/chunks/{settings-BFMOd6yB.js → settings-CqaHgJF9.js} +1 -1
  105. package/dist/elements/chunks/{settings-group-B3VZKIcz.js → settings-group-CjqjLCOn.js} +1 -1
  106. package/dist/elements/chunks/share-CyfnNEqp.js +1 -0
  107. package/dist/elements/chunks/{skeleton-N8BuWYmf.js → skeleton-D8akBnKS.js} +1 -1
  108. package/dist/elements/chunks/slots-DaNjX0rc.js +1 -0
  109. package/dist/elements/chunks/{source-w9V0NnBn.js → source-UWCvmVak.js} +1 -1
  110. package/dist/elements/chunks/{star-DHvACxtm.js → star-DoHFAikQ.js} +1 -1
  111. package/dist/elements/chunks/store-Cjq0qwYN.js +1 -0
  112. package/dist/elements/chunks/{tasks-card-lPxoodlA.js → tasks-card-CmZ5xh6v.js} +1 -1
  113. package/dist/elements/chunks/{text-shimmer-BY_rI3s5.js → text-shimmer-DYn09zrz.js} +1 -1
  114. package/dist/elements/chunks/textarea-skiGHURM.js +1 -0
  115. package/dist/elements/chunks/{thumbs-up-DQe0hvkZ.js → thumbs-up-DX2Ijk3H.js} +1 -1
  116. package/dist/elements/chunks/{toast-store-VUZZBhzH.js → toast-store-CFnQpThX.js} +1 -1
  117. package/dist/elements/chunks/{tool-C1YtxwPu.js → tool-DKQ2D1DP.js} +1 -1
  118. package/dist/elements/chunks/{tooltip-BqwnFWhn.js → tooltip-B0G99y8w.js} +1 -1
  119. package/dist/elements/chunks/{triangle-alert-B6or7F5u.js → triangle-alert-F9cyUEyy.js} +1 -1
  120. package/dist/elements/chunks/{use-card-resolution-DJg32jGH.js → use-card-resolution-DoA2f_EU.js} +1 -1
  121. package/dist/elements/chunks/{variant-aurora-QXTZPyST.js → variant-aurora-qt6Zct8M.js} +2 -2
  122. package/dist/elements/chunks/variant-custom-tJp22_BQ.js +1 -0
  123. package/dist/{variant-wave-BwzrHD1s.js → elements/chunks/variant-wave-CaQ5ti39.js} +2 -2
  124. package/dist/elements/chunks/x-B6UnOiLw.js +1 -0
  125. package/dist/elements/coachmark.js +1 -1
  126. package/dist/elements/code-block.js +1 -1
  127. package/dist/elements/command.js +1 -1
  128. package/dist/elements/compare.js +1 -1
  129. package/dist/elements/composer.js +1 -1
  130. package/dist/elements/confirm-card.js +1 -1
  131. package/dist/elements/context-meter.js +1 -1
  132. package/dist/elements/conversation-item.d.ts +1 -0
  133. package/dist/elements/conversation-item.js +1 -0
  134. package/dist/elements/conversation-list.js +1 -1
  135. package/dist/elements/default-input.d.ts +3 -3
  136. package/dist/elements/define.d.ts +27 -0
  137. package/dist/elements/diagnostic-events.d.ts +74 -0
  138. package/dist/elements/dialog.js +1 -1
  139. package/dist/elements/dock.d.ts +1 -0
  140. package/dist/elements/dock.js +149 -0
  141. package/dist/elements/dropdown.d.ts +1 -0
  142. package/dist/elements/dropdown.js +1 -0
  143. package/dist/elements/editable-label.js +1 -1
  144. package/dist/elements/element-diagnostics.d.ts +81 -0
  145. package/dist/elements/embed.js +1 -1
  146. package/dist/elements/empty.js +1 -1
  147. package/dist/elements/feedback-bar.js +1 -1
  148. package/dist/elements/file-tree.js +1 -1
  149. package/dist/elements/file-upload.js +1 -1
  150. package/dist/elements/form.js +1 -1
  151. package/dist/elements/hover-card.js +1 -1
  152. package/dist/elements/icon.js +1 -1
  153. package/dist/elements/image.js +1 -1
  154. package/dist/elements/input.js +1 -1
  155. package/dist/elements/kbd.js +1 -1
  156. package/dist/elements/link-preview.js +1 -1
  157. package/dist/elements/loader.js +1 -1
  158. package/dist/elements/markdown.js +1 -1
  159. package/dist/elements/menu.js +1 -1
  160. package/dist/elements/message-skills.js +1 -1
  161. package/dist/elements/message.js +1 -1
  162. package/dist/elements/model-switcher.js +1 -1
  163. package/dist/elements/nav.js +1 -1
  164. package/dist/elements/notice.js +1 -1
  165. package/dist/elements/pane-group.js +1 -1
  166. package/dist/elements/pane.js +1 -1
  167. package/dist/elements/popover.js +1 -1
  168. package/dist/elements/progress-bar.js +1 -1
  169. package/dist/elements/prompt-dock.js +1 -1
  170. package/dist/elements/prompt-input.js +1 -1
  171. package/dist/elements/prompt-suggestions.js +1 -1
  172. package/dist/elements/reasoning.js +1 -1
  173. package/dist/elements/register.d.ts +2 -0
  174. package/dist/elements/remote.js +1 -1
  175. package/dist/elements/resizable.js +1 -1
  176. package/dist/elements/response-stream.js +3 -3
  177. package/dist/elements/screen.js +1 -1
  178. package/dist/elements/scroll-area.js +1 -1
  179. package/dist/elements/scroll-button.js +1 -1
  180. package/dist/elements/search.js +1 -1
  181. package/dist/elements/segmented.js +1 -1
  182. package/dist/elements/separator.js +1 -1
  183. package/dist/elements/setting-item.js +1 -1
  184. package/dist/elements/settings-group.js +1 -1
  185. package/dist/elements/skeleton.js +1 -1
  186. package/dist/elements/slots.d.ts +35 -3
  187. package/dist/elements/source.js +1 -1
  188. package/dist/elements/status.js +1 -1
  189. package/dist/elements/switch.js +1 -1
  190. package/dist/elements/tabs.js +1 -1
  191. package/dist/elements/tasks.js +1 -1
  192. package/dist/elements/text-shimmer.js +1 -1
  193. package/dist/elements/thinking-bar.js +1 -1
  194. package/dist/elements/thread.js +1 -1
  195. package/dist/elements/toast.js +1 -1
  196. package/dist/elements/tool.js +1 -1
  197. package/dist/elements/tooltip.js +1 -1
  198. package/dist/elements/voice-input.js +1 -1
  199. package/dist/elements/voice-output.js +1 -1
  200. package/dist/elements.d.ts +232 -148
  201. package/dist/index.d.ts +2 -0
  202. package/dist/index.js +5456 -4931
  203. package/dist/index.server.js +4142 -3669
  204. package/dist/kai-provider.es.js +40 -40
  205. package/dist/kai.es.js +1 -1
  206. package/dist/llms/llms-full.txt +184 -69
  207. package/dist/llms/llms.txt +3 -3
  208. package/dist/mcp.es.js +2669 -546
  209. package/dist/primitives/use-resize-observer.d.ts +23 -0
  210. package/dist/primitives/use-speech-recognition.d.ts +8 -0
  211. package/dist/primitives/use-text-stream.d.ts +5 -0
  212. package/dist/react/index.d.ts +115 -266
  213. package/dist/react.js +247 -221
  214. package/dist/register-impl-DpE7icIb.js +293 -0
  215. package/dist/schemas/index.d.ts +1 -1
  216. package/dist/schemas/tool-defs.d.ts +35 -0
  217. package/dist/schemas.js +403 -310
  218. package/dist/{solid-BMuDmFo_.js → solid-BtdqNZI7.js} +8435 -7394
  219. package/dist/{solid-CGYNdLRR.js → solid-CnavwBye.js} +5987 -5035
  220. package/dist/solid.d.ts +5 -0
  221. package/dist/solid.js +210 -203
  222. package/dist/solid.server.js +210 -203
  223. package/dist/state/index.d.ts +4 -0
  224. package/dist/state/persistence.d.ts +51 -0
  225. package/dist/state/stream.d.ts +50 -0
  226. package/dist/state/threads.d.ts +71 -0
  227. package/dist/state.js +269 -169
  228. package/dist/types.d.ts +4 -2
  229. package/dist/ui/dock.d.ts +109 -0
  230. package/dist/ui/hover-card.d.ts +44 -0
  231. package/dist/{variant-aurora-DwSOfraQ.js → variant-aurora-B8II3-I1.js} +27 -27
  232. package/dist/{variant-aurora-wKvAQ5wp.js → variant-aurora-CMNWOSJR.js} +28 -28
  233. package/dist/{variant-aurora-XEkPT0_z.js → variant-aurora-CcuDMWwQ.js} +2 -2
  234. package/dist/{variant-custom-eFIg2XWQ.js → variant-custom-BdvYC7af.js} +40 -40
  235. package/dist/{variant-custom-BhS9wIpU.js → variant-custom-C714mqKl.js} +65 -65
  236. package/dist/variant-custom-L16JLLZy.js +1 -0
  237. package/dist/{elements/chunks/variant-wave-DDCwhLas.js → variant-wave-2S4La2Ka.js} +2 -2
  238. package/dist/{variant-wave-CbVsmgwT.js → variant-wave-D7ruiA1W.js} +25 -25
  239. package/dist/{variant-wave-CUWGx9Bn.js → variant-wave-ecwykeyL.js} +21 -21
  240. package/dist/wire/chunk.d.ts +47 -0
  241. package/dist/wire/diagnostics.d.ts +575 -0
  242. package/dist/wire/encode-probe.d.ts +70 -0
  243. package/dist/wire/encode.d.ts +24 -0
  244. package/dist/wire/index.d.ts +2 -0
  245. package/dist/wire/sse.d.ts +10 -2
  246. package/dist/wire.js +917 -480
  247. package/frameworks/react/index.tsx +132 -93
  248. package/llms-full.txt +184 -69
  249. package/llms.txt +3 -3
  250. package/package.json +26 -6
  251. package/src/agent-tooling/archetypes.ts +41 -9
  252. package/src/agent-tooling/catalog/README.md +676 -0
  253. package/src/agent-tooling/catalog/catalog-types.ts +231 -0
  254. package/src/agent-tooling/catalog/fabrications.ts +96 -0
  255. package/src/agent-tooling/catalog/invariants.ts +284 -0
  256. package/src/agent-tooling/catalog/labs-titles.ts +114 -0
  257. package/src/agent-tooling/catalog/scenarios.ts +87 -0
  258. package/src/agent-tooling/catalog/surfaces.ts +275 -0
  259. package/src/agent-tooling/integrations/anthropic.ts +7 -1
  260. package/src/agent-tooling/integrations/cloudflare.ts +43 -2
  261. package/src/agent-tooling/integrations/langgraph.ts +7 -1
  262. package/src/agent-tooling/integrations/mastra.ts +7 -1
  263. package/src/agent-tooling/integrations/mock.ts +92 -17
  264. package/src/agent-tooling/integrations/ollama.ts +7 -1
  265. package/src/agent-tooling/integrations/openai.ts +7 -1
  266. package/src/agent-tooling/integrations/openrouter.ts +7 -1
  267. package/src/agent-tooling/integrations/pi.ts +56 -1
  268. package/src/agent-tooling/integrations/vercel-ai-sdk.ts +7 -1
  269. package/src/agent-tooling/mcp/css-raw.d.ts +12 -0
  270. package/src/agent-tooling/mcp/manifest.ts +100 -1
  271. package/src/agent-tooling/mcp/server.ts +61 -5
  272. package/src/agent-tooling/mcp/tools/reference.ts +440 -4
  273. package/src/agent-tooling/mcp/tools/scaffold.ts +1834 -307
  274. package/src/agent-tooling/mcp/tools/theme.ts +146 -30
  275. package/src/agent-tooling/mcp/validate-args.ts +141 -0
  276. package/src/agent-tooling/registry.ts +19 -7
  277. package/src/agent-tooling/route-emit.ts +26 -2
  278. package/src/components/attachment-types.ts +47 -1
  279. package/src/components/attachments.tsx +169 -22
  280. package/src/components/audio-visualizer/fit-scale.ts +112 -0
  281. package/src/components/audio-visualizer/index.tsx +80 -3
  282. package/src/components/audio-visualizer/variant-aurora.tsx +10 -4
  283. package/src/components/audio-visualizer/variant-bar.tsx +38 -6
  284. package/src/components/audio-visualizer/variant-custom.tsx +8 -2
  285. package/src/components/audio-visualizer/variant-grid.tsx +16 -11
  286. package/src/components/audio-visualizer/variant-radial.tsx +15 -10
  287. package/src/components/audio-visualizer/variant-wave.tsx +7 -2
  288. package/src/components/chat-container.tsx +4 -1
  289. package/src/components/chat-thread.tsx +36 -7
  290. package/src/components/code-block.tsx +79 -3
  291. package/src/components/composer.tsx +8 -1
  292. package/src/components/conversation-item.tsx +114 -4
  293. package/src/components/conversation-list.tsx +212 -12
  294. package/src/components/embed.tsx +4 -0
  295. package/src/components/form.tsx +41 -14
  296. package/src/components/message.tsx +75 -5
  297. package/src/components/response-stream.tsx +13 -7
  298. package/src/components/screen.tsx +5 -0
  299. package/src/components/scroll-button.tsx +7 -0
  300. package/src/components/toast.tsx +11 -5
  301. package/src/components/voice-input.tsx +35 -3
  302. package/src/components/voice-output.tsx +46 -6
  303. package/src/components/workspace-shell.tsx +285 -0
  304. package/src/diagnostics/hook.ts +352 -0
  305. package/src/diagnostics/index.ts +82 -0
  306. package/src/diagnostics/report-request.ts +296 -0
  307. package/src/elements/attachments.tsx +6 -7
  308. package/src/elements/audio-visualizer.tsx +48 -4
  309. package/src/elements/chat-workspace.tsx +159 -287
  310. package/src/elements/chat.tsx +12 -9
  311. package/src/elements/code-block.tsx +19 -1
  312. package/src/elements/command.tsx +4 -1
  313. package/src/elements/compiled.css +1 -1
  314. package/src/elements/conversation-item.tsx +80 -0
  315. package/src/elements/conversation-list.tsx +60 -11
  316. package/src/elements/default-input.tsx +5 -5
  317. package/src/elements/define.tsx +168 -8
  318. package/src/elements/diagnostic-events.ts +114 -0
  319. package/src/elements/dialog.tsx +47 -0
  320. package/src/elements/disclosure.ts +34 -12
  321. package/src/elements/dock.tsx +204 -0
  322. package/src/elements/dropdown.tsx +123 -0
  323. package/src/elements/element-diagnostics.ts +392 -0
  324. package/src/elements/element-manifest.json +12 -0
  325. package/src/elements/element-meta.json +672 -439
  326. package/src/elements/element-nonscalar.json +170 -0
  327. package/src/elements/element-types.d.ts +232 -148
  328. package/src/elements/menu.tsx +13 -0
  329. package/src/elements/prompt-dock.tsx +2 -1
  330. package/src/elements/prompt-input.tsx +8 -8
  331. package/src/elements/register-impl.ts +46 -0
  332. package/src/elements/register.ts +28 -0
  333. package/src/elements/resizable.tsx +52 -25
  334. package/src/elements/scroll-button.tsx +5 -0
  335. package/src/elements/slots.ts +107 -12
  336. package/src/elements/styles.css +30 -6
  337. package/src/elements/thinking-bar.tsx +16 -7
  338. package/src/elements/toast.tsx +3 -0
  339. package/src/elements/tool.tsx +26 -5
  340. package/src/elements/voice-input.tsx +11 -2
  341. package/src/elements/voice-output.tsx +16 -3
  342. package/src/index.ts +2 -0
  343. package/src/primitives/use-resize-observer.ts +33 -0
  344. package/src/primitives/use-speech-recognition.ts +12 -1
  345. package/src/primitives/use-stick-to-bottom.ts +34 -2
  346. package/src/primitives/use-text-stream.ts +33 -2
  347. package/src/remote/provider-runtime.ts +6 -3
  348. package/src/schemas/index.ts +1 -0
  349. package/src/schemas/tool-defs.ts +297 -6
  350. package/src/solid.ts +5 -0
  351. package/src/state/index.ts +12 -0
  352. package/src/state/persistence.ts +180 -0
  353. package/src/state/stream.ts +71 -4
  354. package/src/state/threads.ts +142 -0
  355. package/src/types.ts +4 -2
  356. package/src/ui/dialog.tsx +6 -0
  357. package/src/ui/dock.tsx +445 -0
  358. package/src/ui/dropdown.tsx +67 -23
  359. package/src/ui/hover-card.tsx +156 -6
  360. package/src/ui/input.tsx +29 -10
  361. package/src/ui/resizable.tsx +11 -0
  362. package/src/wire/chunk.ts +47 -0
  363. package/src/wire/consume.ts +185 -26
  364. package/src/wire/diagnostics.ts +727 -0
  365. package/src/wire/encode-probe.ts +214 -0
  366. package/src/wire/encode.ts +267 -14
  367. package/src/wire/formats/anthropic.ts +8 -1
  368. package/src/wire/formats/openai.ts +5 -0
  369. package/src/wire/index.ts +22 -0
  370. package/src/wire/read.ts +250 -6
  371. package/src/wire/sse.ts +16 -3
  372. package/dist/elements/chunks/action-icons-DW9muWrY.js +0 -1
  373. package/dist/elements/chunks/arrow-left-iJVnQmxA.js +0 -1
  374. package/dist/elements/chunks/attachments-CL6TaqIb.js +0 -1
  375. package/dist/elements/chunks/audio-visualizer-BuUqQmaO.js +0 -1
  376. package/dist/elements/chunks/chat-thread-DxoKaStk.js +0 -1
  377. package/dist/elements/chunks/check-MdLzbxrm.js +0 -1
  378. package/dist/elements/chunks/chevron-down-MK7IzDqe.js +0 -1
  379. package/dist/elements/chunks/chevron-right-C5jXhmtV.js +0 -1
  380. package/dist/elements/chunks/circle-BG7VkPep.js +0 -1
  381. package/dist/elements/chunks/circle-check-GqvO2UBs.js +0 -1
  382. package/dist/elements/chunks/code-block-ClhQO9RW.js +0 -1
  383. package/dist/elements/chunks/collapsible-DBcBkFPh.js +0 -1
  384. package/dist/elements/chunks/context-hKjNqiyQ.js +0 -1
  385. package/dist/elements/chunks/conversation-list-CsGJjSKG.js +0 -1
  386. package/dist/elements/chunks/default-input-B1Mem5wu.js +0 -1
  387. package/dist/elements/chunks/define-B_tXMYRm.js +0 -1
  388. package/dist/elements/chunks/disclosure-DAoYOKew.js +0 -1
  389. package/dist/elements/chunks/dropdown-CmNna5lT.js +0 -1
  390. package/dist/elements/chunks/embed-CjAD2jSi.js +0 -1
  391. package/dist/elements/chunks/form-Cp8Y00Wn.js +0 -1
  392. package/dist/elements/chunks/hover-card-6KtXpLwT.js +0 -1
  393. package/dist/elements/chunks/icon-97R8SR2p.js +0 -1
  394. package/dist/elements/chunks/input-DrseEjhp.js +0 -1
  395. package/dist/elements/chunks/message-CE4k-qaj.js +0 -1
  396. package/dist/elements/chunks/message-square-C9ovmtOo.js +0 -1
  397. package/dist/elements/chunks/reasoning-BrxQfpfI.js +0 -1
  398. package/dist/elements/chunks/scroll-button-CrTo7wm4.js +0 -1
  399. package/dist/elements/chunks/share-DgNbkJKY.js +0 -1
  400. package/dist/elements/chunks/slots-CIw9RlAe.js +0 -1
  401. package/dist/elements/chunks/store-HIPGhNz9.js +0 -1
  402. package/dist/elements/chunks/textarea-DPfuhswM.js +0 -1
  403. package/dist/elements/chunks/variant-custom-D5ZlmziF.js +0 -1
  404. package/dist/elements/chunks/video-Bb86fVYI.js +0 -1
  405. package/dist/elements/chunks/x-BOhjBeOd.js +0 -1
  406. package/dist/primitives/card-validate-generator.testlib.d.ts +0 -20
  407. package/dist/register-impl-DGmGzrEM.js +0 -145
  408. package/dist/variant-custom-MkTNTycd.js +0 -1
  409. package/src/primitives/card-validate-generator.testlib.ts +0 -38
@@ -37,6 +37,63 @@ const useDropdown = () => {
37
37
  // a parent's querySelectorAll scoped to its own menu never reaches sub items.
38
38
  const ITEM_SELECTOR = '[role="menuitem"]:not([aria-disabled="true"]), [role="menuitemcheckbox"]:not([aria-disabled="true"]), [role="menuitemradio"]:not([aria-disabled="true"])';
39
39
 
40
+ /**
41
+ * The roving-focus set, in FLAT-TREE order.
42
+ *
43
+ * ★ NOT `querySelectorAll`, and the difference is the whole point: a `<slot>` in the
44
+ * shadow tree contains NONE of the light-DOM nodes it projects, so a plain query
45
+ * returns zero items for any facade whose rows are SLOTTED (`<kai-dropdown>`) and
46
+ * ArrowUp/Down, Home/End and typeahead all reach nothing. The items-tree facades
47
+ * (`kai-menu`, `kai-model-switcher`, `kai-scope-picker`) render their rows INTO the
48
+ * shadow tree, so both spellings agree for them — which is exactly why this went
49
+ * unnoticed until a slotted menu body existed.
50
+ *
51
+ * Same class as `hasFocusableChild` in ./hover-card.tsx, which asks the boolean
52
+ * version of this question. This one needs the ORDER as well, so it walks the tree
53
+ * and splices each slot's assigned elements in AT THE SLOT'S POSITION rather than
54
+ * appending them; a menu may mix rendered and slotted rows. `flatten: true` follows
55
+ * a slot assigned to another slot.
56
+ *
57
+ * Scope is unchanged: submenu content is portaled to a SIBLING node, so a parent's
58
+ * walk still never reaches sub items.
59
+ */
60
+ function menuItems(root: HTMLElement | undefined): HTMLElement[] {
61
+ if (!root) return [];
62
+ const out: HTMLElement[] = [];
63
+ const collect = (el: Element): void => {
64
+ if (el.matches(ITEM_SELECTOR)) out.push(el as HTMLElement);
65
+ for (const child of Array.from(el.children)) visit(child);
66
+ };
67
+ const visit = (el: Element): void => {
68
+ if (el.localName === 'slot') {
69
+ for (const assigned of (el as HTMLSlotElement).assignedElements({ flatten: true })) collect(assigned);
70
+ return;
71
+ }
72
+ collect(el);
73
+ };
74
+ for (const child of Array.from(root.children)) visit(child);
75
+ return out;
76
+ }
77
+
78
+ /** The first roving-focus item, in flat-tree order. Replaces
79
+ * `querySelector(ITEM_SELECTOR)` for the reason `menuItems` replaces the `All`. */
80
+ const firstMenuItem = (root: HTMLElement | undefined): HTMLElement | undefined => menuItems(root)[0];
81
+
82
+ /**
83
+ * The focused item, resolved across BOTH trees.
84
+ *
85
+ * Inside a Shadow DOM `document.activeElement` returns the HOST rather than the
86
+ * focused menu item, so the menu's own root node is asked first. But a SLOTTED row
87
+ * lives in the LIGHT DOM: focus is then outside the shadow tree, that root reports
88
+ * null, and the document's answer is the real one. Asking only the shadow root would
89
+ * leave `currentIndex()` at -1 for every slotted row, so ArrowDown would restart at
90
+ * the top instead of advancing.
91
+ */
92
+ function activeMenuItem(root: HTMLElement | undefined): Element | null {
93
+ const tree = root?.getRootNode() as Document | ShadowRoot | undefined;
94
+ return (tree?.activeElement ?? document.activeElement) as Element | null;
95
+ }
96
+
40
97
  /** Imperative open controller, handed to a parent (e.g. the kai-menu facade) via
41
98
  * `controllerRef` so it can drive/observe the Dropdown's open state. */
42
99
  export interface DropdownController { open: Accessor<boolean>; setOpen: (v: boolean) => void; }
@@ -103,8 +160,8 @@ export function Dropdown(props: DropdownProps) {
103
160
  // attempt focus now and re-assert in the menu ref's microtask so it lands
104
161
  // once the node exists. Skip disabled items (roving-focus contract).
105
162
  if (opts?.viaKeyboard) {
106
- queueMicrotask(() => menu()?.querySelector<HTMLElement>(ITEM_SELECTOR)?.focus());
107
- menu()?.querySelector<HTMLElement>(ITEM_SELECTOR)?.focus();
163
+ queueMicrotask(() => firstMenuItem(menu())?.focus());
164
+ firstMenuItem(menu())?.focus();
108
165
  }
109
166
  } else if (opts?.returnFocus !== false) {
110
167
  // Closing via keyboard/select: return focus to the trigger. The menu
@@ -179,23 +236,14 @@ export function DropdownContent(props: DropdownContentProps) {
179
236
  refs: () => [ctx.trigger(), ctx.menu(), ...ctx.subMenus()],
180
237
  });
181
238
 
182
- const items = () => Array.from(ctx.menu()?.querySelectorAll<HTMLElement>(ITEM_SELECTOR) ?? []);
239
+ const items = () => menuItems(ctx.menu());
183
240
  const focusIndex = (i: number) => {
184
241
  const list = items();
185
242
  if (!list.length) return;
186
243
  const idx = ((i % list.length) + list.length) % list.length;
187
244
  list[idx].focus();
188
245
  };
189
- // Resolve the focused item via the menu's own root node. Inside a Shadow DOM
190
- // (every kitn-* element), `document.activeElement` returns the host element,
191
- // not the focused menu item, which would break ArrowUp/Down roving focus.
192
- // `getRootNode().activeElement` correctly returns the active node within the
193
- // same tree (ShadowRoot or Document).
194
- const activeItem = () => {
195
- const root = ctx.menu()?.getRootNode() as Document | ShadowRoot | undefined;
196
- return (root?.activeElement ?? document.activeElement) as Element | null;
197
- };
198
- const currentIndex = () => items().findIndex((el) => el === activeItem());
246
+ const currentIndex = () => items().findIndex((el) => el === activeMenuItem(ctx.menu()));
199
247
 
200
248
  const onKeyDown = (e: KeyboardEvent) => {
201
249
  const list = items();
@@ -226,7 +274,7 @@ export function DropdownContent(props: DropdownContentProps) {
226
274
  // synchronously; this ref-time microtask re-asserts focus once the
227
275
  // menu node exists. Skip disabled items.
228
276
  if (ctx.openedViaKeyboard()) {
229
- queueMicrotask(() => el.querySelector<HTMLElement>(ITEM_SELECTOR)?.focus());
277
+ queueMicrotask(() => firstMenuItem(el)?.focus());
230
278
  }
231
279
  }}
232
280
  id={ctx.menuId}
@@ -424,8 +472,8 @@ export function DropdownSub(props: DropdownSubProps) {
424
472
  setOpenSig(v);
425
473
  if (v) {
426
474
  if (opts?.viaKeyboard) {
427
- queueMicrotask(() => menu()?.querySelector<HTMLElement>(ITEM_SELECTOR)?.focus());
428
- menu()?.querySelector<HTMLElement>(ITEM_SELECTOR)?.focus();
475
+ queueMicrotask(() => firstMenuItem(menu())?.focus());
476
+ firstMenuItem(menu())?.focus();
429
477
  }
430
478
  } else if (opts?.returnFocus !== false) {
431
479
  const el = trigger();
@@ -516,18 +564,14 @@ export function DropdownSubContent(props: DropdownSubContentProps) {
516
564
  // useDismiss here would double-fire Escape because document listeners run
517
565
  // after stopPropagation on the element, not on the document.
518
566
 
519
- const items = () => Array.from(sub.menu()?.querySelectorAll<HTMLElement>(ITEM_SELECTOR) ?? []);
567
+ const items = () => menuItems(sub.menu());
520
568
  const focusIndex = (i: number) => {
521
569
  const list = items();
522
570
  if (!list.length) return;
523
571
  const idx = ((i % list.length) + list.length) % list.length;
524
572
  list[idx].focus();
525
573
  };
526
- const activeItem = () => {
527
- const root = sub.menu()?.getRootNode() as Document | ShadowRoot | undefined;
528
- return (root?.activeElement ?? document.activeElement) as Element | null;
529
- };
530
- const currentIndex = () => items().findIndex((el) => el === activeItem());
574
+ const currentIndex = () => items().findIndex((el) => el === activeMenuItem(sub.menu()));
531
575
 
532
576
  const onKeyDown = (e: KeyboardEvent) => {
533
577
  const list = items();
@@ -559,7 +603,7 @@ export function DropdownSubContent(props: DropdownSubContentProps) {
559
603
  const unregister = parent.registerSubMenu(el);
560
604
  onCleanup(unregister);
561
605
  if (sub.openedViaKeyboard()) {
562
- queueMicrotask(() => el.querySelector<HTMLElement>(ITEM_SELECTOR)?.focus());
606
+ queueMicrotask(() => firstMenuItem(el)?.focus());
563
607
  }
564
608
  }}
565
609
  id={sub.menuId}
@@ -1,5 +1,5 @@
1
1
  import {
2
- createContext, useContext, createSignal, Show, splitProps, onCleanup,
2
+ createContext, useContext, createSignal, createUniqueId, Show, splitProps, onCleanup,
3
3
  type JSX, type Accessor,
4
4
  } from 'solid-js';
5
5
  import { Portal } from 'solid-js/web';
@@ -17,6 +17,12 @@ interface HoverCardCtx {
17
17
  setContent: (el: HTMLElement) => void;
18
18
  trigger: Accessor<HTMLElement | undefined>;
19
19
  content: Accessor<HTMLElement | undefined>;
20
+ /** Ties a focused trigger to the card it reveals. Without it a keyboard tab
21
+ * stop announces nothing, which is a focus stop that wastes the user's time. */
22
+ contentId: string;
23
+ /** Whether this card refuses to open. Read by the TRIGGER, not just by
24
+ * `enter()`: a card that cannot open must not be in the tab order either. */
25
+ disabled: Accessor<boolean>;
20
26
  }
21
27
  const Ctx = createContext<HoverCardCtx>();
22
28
  const useHoverCard = () => {
@@ -45,6 +51,7 @@ export function HoverCardRoot(props: HoverCardRootProps) {
45
51
  const [open, setOpen] = createSignal(props.defaultOpen ?? false);
46
52
  const [trigger, setTrigger] = createSignal<HTMLElement>();
47
53
  const [content, setContent] = createSignal<HTMLElement>();
54
+ const contentId = createUniqueId();
48
55
  let timer: number | undefined;
49
56
  props.controllerRef?.({ open, setOpen });
50
57
 
@@ -73,25 +80,167 @@ export function HoverCardRoot(props: HoverCardRootProps) {
73
80
  open, enter, leave, close,
74
81
  setTrigger, setContent,
75
82
  trigger, content,
83
+ contentId,
84
+ disabled: () => props.disabled ?? false,
76
85
  }}>
77
86
  {props.children}
78
87
  </Ctx.Provider>
79
88
  );
80
89
  }
81
90
 
82
- export interface HoverCardTriggerProps { children: JSX.Element; class?: string; }
91
+ export interface HoverCardTriggerProps {
92
+ children: JSX.Element;
93
+ class?: string;
94
+ /**
95
+ * Whether the trigger is its own tab stop. Omit for the automatic behaviour,
96
+ * which is right for every case in this repo: the trigger takes focus only
97
+ * when nothing inside it already can.
98
+ *
99
+ * Pass `false` to keep a trigger out of the tab order entirely, or `true` to
100
+ * force a stop. Both are escape hatches — reach for them when the automatic
101
+ * answer is wrong, not to restate it.
102
+ */
103
+ focusable?: boolean;
104
+ }
105
+
106
+ /** Anything the platform puts in the tab order by default, plus anything a
107
+ * consumer opted in with `tabindex`. `tabindex="-1"` is deliberately excluded:
108
+ * it is programmatically focusable but the Tab key skips it, so a trigger
109
+ * containing only such an element still needs a stop of its own. */
110
+ const FOCUSABLE_CHILD =
111
+ 'a[href],button,input,select,textarea,summary,[contenteditable=""],[contenteditable="true"],[tabindex]:not([tabindex^="-"])';
112
+
113
+ /** Does this subtree already offer the keyboard a way in? Slotted content is
114
+ * checked through `assignedElements()`, because a `<slot>` in the shadow tree
115
+ * contains none of the light-DOM nodes it projects — the `kai-hover-card`
116
+ * facade wraps a bare `<slot />`, so without this branch every use of that
117
+ * element would be told its children are inert.
118
+ *
119
+ * ★ WHEN this runs is as load-bearing as what it asks. At ref time the facade's
120
+ * trigger contains `<slot></slot>` with NOTHING assigned yet, so a single call
121
+ * from the ref answers "inert" for a slot that is about to receive a focusable
122
+ * `<a>` — and the trigger stamps a second tab stop on top of the child's. That
123
+ * is the very regression the `assignedElements()` branch was added to prevent,
124
+ * reintroduced by timing rather than by logic. Measured with a real Tab key:
125
+ * 4 stops across a 3-widget fixture. So the caller re-runs this on
126
+ * `slotchange`; see `HoverCardTrigger`. */
127
+ const hasFocusableChild = (root: HTMLElement): boolean => {
128
+ if (root.querySelector(FOCUSABLE_CHILD)) return true;
129
+ return Array.from(root.querySelectorAll('slot')).some((slot) =>
130
+ (slot as HTMLSlotElement).assignedElements().some(
131
+ (el) => el.matches(FOCUSABLE_CHILD) || el.querySelector(FOCUSABLE_CHILD),
132
+ ),
133
+ );
134
+ };
83
135
 
136
+ /**
137
+ * ★ THE TRIGGER IS A TAB STOP WHEN, AND ONLY WHEN, ITS CHILDREN ARE NOT.
138
+ *
139
+ * `onFocusIn`/`onFocusOut` were here from the start and the focus-open path was
140
+ * plainly intended, but the span never set `tabindex`, so the Tab key could not
141
+ * land on it and neither handler could fire on a keyboard. It went unnoticed for
142
+ * as long as it did because `focusin` BUBBLES and every consumer at the time put
143
+ * something focusable inside the trigger — `source.tsx` an `<a>`, `context.tsx`
144
+ * a `<Button>` — so the card opened via the child and the span's own inertness
145
+ * never showed. The first trigger with inert children (an attachment tile: a
146
+ * div, an img, an svg) had no tab stop anywhere in it.
147
+ *
148
+ * Hence delegation rather than an unconditional `tabindex`: adding one always
149
+ * would give every existing consumer TWO stops for one card, which is a worse
150
+ * bug than the one being fixed. The check runs in the ref AND again on every
151
+ * `slotchange`, because slot assignment happens after the ref and a one-shot
152
+ * check calls a not-yet-filled `<slot>` inert (see `hasFocusableChild`).
153
+ *
154
+ * ★ THE FOCUS LISTENERS ARE NATIVE, NOT SOLID'S DELEGATED `onFocusIn`. Solid
155
+ * delegates a fixed set of events from the document and retargets them into
156
+ * component trees; inside these shadow roots that path runs for a PROGRAMMATIC
157
+ * `.focus()` and, in the deeper trees, not for a real Tab. Measured: tabbing to
158
+ * an attachment tile in a mounted `<kai-chat>` left the card shut and
159
+ * `aria-describedby` null while `.focus()` on the same element opened it — so
160
+ * every keyboard user got a tab stop that announced nothing and showed nothing,
161
+ * which is worse than no stop at all. `addEventListener` in the ref does not
162
+ * care how focus arrived. Anything in this kit relying on delegated focus
163
+ * events inside a shadow root is suspect for the same reason.
164
+ *
165
+ * `aria-describedby` is what makes the stop worth arriving at: the card is
166
+ * DESCRIPTIVE, not an action, so the trigger gets no `role="button"` — that
167
+ * would promise an activation that does not exist — and instead points at the
168
+ * content it reveals so a screen reader reads it out.
169
+ */
84
170
  export function HoverCardTrigger(props: HoverCardTriggerProps) {
85
171
  const ctx = useHoverCard();
172
+ const [detectedInert, setDetectedInert] = createSignal(false);
173
+ // `&& !disabled` last, and it beats an explicit `focusable` — a disabled card
174
+ // early-returns out of `enter()`, so a stop here could never open anything.
175
+ // Landing on it would announce nothing and reveal nothing, which is the dead
176
+ // stop the note above argues against; forcing one is not an escape hatch
177
+ // anybody should get.
178
+ const isFocusable = () => (props.focusable ?? detectedInert()) && !ctx.disabled();
179
+
86
180
  return (
87
181
  <As
88
182
  as="span"
89
- class={cn('inline-block', props.class)}
90
- ref={ctx.setTrigger}
183
+ class={cn(
184
+ 'inline-block',
185
+ // A tab stop nobody can SEE is barely an improvement on no tab stop —
186
+ // WCAG 2.4.7. Only when this element is the stop; a delegating trigger
187
+ // must not draw a ring around its child's own focus state.
188
+ //
189
+ // AN OUTLINE, NOT A RING, and both halves of that were measured rather
190
+ // than assumed:
191
+ //
192
+ // 1. ★ A TAILWIND v4 UTILITY THAT ROUTES THROUGH `@property` IS INERT
193
+ // INSIDE THESE SHADOW ROOTS, and that is a trap far wider than this
194
+ // line. v4 gives `--tw-*` custom properties their defaults with
195
+ // `@property`, and an `@property` rule delivered through a shadow
196
+ // root's `adoptedStyleSheets` never registers — so the var resolves
197
+ // to nothing, the declaration is invalid, and the property falls
198
+ // back to its initial value. Measured in Chrome, twice:
199
+ // `ring-2 ring-offset-1` set `--tw-ring-shadow` correctly and
200
+ // computed `box-shadow: none`; `inset-ring-2` did the same; and
201
+ // `outline-2` alone computed `outline-style: none` while width,
202
+ // colour and offset all landed, because only the style goes
203
+ // through `var(--tw-outline-style)`. Hence the literal
204
+ // `[outline-style:solid]` — the one part that cannot be a var.
205
+ // 2. The offset is NEGATIVE so the outline lands inside the border
206
+ // box. A trigger that fills its container (`block size-full` inside
207
+ // the attachment tile's `overflow-hidden rounded-lg`) would
208
+ // otherwise have its focus indicator painted straight into the
209
+ // clip, and a trigger cannot know whether its container clips.
210
+ isFocusable() &&
211
+ 'rounded-sm focus-visible:[outline-style:solid] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring',
212
+ props.class,
213
+ )}
214
+ ref={(el: HTMLElement) => {
215
+ ctx.setTrigger(el);
216
+
217
+ // Native listeners, deliberately — see the note above. `focusin` /
218
+ // `focusout` rather than `focus` / `blur` because these have to fire
219
+ // for a focusable CHILD too, and only the -in/-out pair bubbles.
220
+ const enter = () => ctx.enter();
221
+ const leave = () => ctx.leave();
222
+ el.addEventListener('focusin', enter);
223
+ el.addEventListener('focusout', leave);
224
+ onCleanup(() => {
225
+ el.removeEventListener('focusin', enter);
226
+ el.removeEventListener('focusout', leave);
227
+ });
228
+
229
+ // Ask now for the direct-children case, and again whenever a slot is
230
+ // filled or emptied. Both are needed: `slotchange` never fires for a
231
+ // trigger with no slot in it, and the ref-time answer is wrong for one
232
+ // that has.
233
+ const evaluate = () => setDetectedInert(!hasFocusableChild(el));
234
+ evaluate();
235
+ for (const slot of Array.from(el.querySelectorAll('slot'))) {
236
+ slot.addEventListener('slotchange', evaluate);
237
+ onCleanup(() => slot.removeEventListener('slotchange', evaluate));
238
+ }
239
+ }}
240
+ tabIndex={isFocusable() ? 0 : undefined}
241
+ aria-describedby={isFocusable() && ctx.open() ? ctx.contentId : undefined}
91
242
  onPointerEnter={ctx.enter}
92
243
  onPointerLeave={ctx.leave}
93
- onFocusIn={ctx.enter}
94
- onFocusOut={ctx.leave}
95
244
  >
96
245
  {props.children}
97
246
  </As>
@@ -166,6 +315,7 @@ export function HoverCardContent(props: HoverCardContentProps) {
166
315
  >
167
316
  {/* Inner card: all visual + animation classes and the presence state. */}
168
317
  <div
318
+ id={ctx.contentId}
169
319
  data-expanded={presence.state() === 'open' ? '' : undefined}
170
320
  data-closed={presence.state() === 'closed' ? '' : undefined}
171
321
  class={cn(
package/src/ui/input.tsx CHANGED
@@ -69,7 +69,21 @@ export function Input(props: InputProps): JSX.Element {
69
69
  const hasHint = () => !!local.error || !!local.hint;
70
70
  const hasAffix = () => local.leading != null || local.trailing != null;
71
71
 
72
- const inputEl = (cls: string, part: string): JSX.Element => (
72
+ // The class arrives as an ACCESSOR, not a string, and this is load-bearing.
73
+ //
74
+ // Solid evaluates a `<Show>` `fallback` inside the Show's own memo. When the
75
+ // class was computed at the call site — `inputEl(cn(FIELD_BASE, …,
76
+ // isInvalid() && INVALID, local.class), …)` — those reads happened in that
77
+ // memo, so any of them changing re-ran the memo and BUILT A NEW `<input>`
78
+ // NODE: focus, caret and IME composition state all died with the old one. A
79
+ // consumer whose `invalid` is derived from the value (`kai-form` did exactly
80
+ // that) lost focus after every character.
81
+ //
82
+ // Passing a function moves every reactive read inside the element, where
83
+ // Solid compiles it into a nested effect that sets the attribute on the
84
+ // EXISTING node. Same reason the affix branch was always fine: it inserts the
85
+ // input through a function. Pinned by `tests/ui/input-node-identity.test.tsx`.
86
+ const inputEl = (cls: () => string, part: string): JSX.Element => (
73
87
  <input
74
88
  {...rest}
75
89
  id={id}
@@ -77,7 +91,7 @@ export function Input(props: InputProps): JSX.Element {
77
91
  disabled={local.disabled}
78
92
  aria-invalid={isInvalid() ? 'true' : undefined}
79
93
  aria-describedby={hasHint() ? hintId : undefined}
80
- class={cn(SEARCH_RESET, cls)}
94
+ class={cn(SEARCH_RESET, cls())}
81
95
  onInput={(e) => local.onValueInput?.(e.currentTarget.value)}
82
96
  onBlur={(e) => {
83
97
  const handler = local.onBlur;
@@ -87,6 +101,17 @@ export function Input(props: InputProps): JSX.Element {
87
101
  />
88
102
  );
89
103
 
104
+ const fieldClass = () =>
105
+ cn(FIELD_BASE, local.size === 'sm' && SIZE_SM, isInvalid() && INVALID, local.class);
106
+
107
+ // Created on first use and then REUSED, so toggling an affix on or off does
108
+ // not discard a focused input either. The lazy cache also keeps the unused
109
+ // branch's node (and its effects) from being built at all.
110
+ let plainNode: JSX.Element;
111
+ const plainInput = () => (plainNode ??= inputEl(fieldClass, 'field input'));
112
+ let rowNode: JSX.Element;
113
+ const rowInput = () => (rowNode ??= inputEl(() => ROW_INPUT, 'input'));
114
+
90
115
  return (
91
116
  <div class="flex w-full flex-col gap-1.5">
92
117
  <Show when={local.label}>
@@ -95,13 +120,7 @@ export function Input(props: InputProps): JSX.Element {
95
120
  </label>
96
121
  </Show>
97
122
 
98
- <Show
99
- when={hasAffix()}
100
- fallback={inputEl(
101
- cn(FIELD_BASE, local.size === 'sm' && SIZE_SM, isInvalid() && INVALID, local.class),
102
- 'field input',
103
- )}
104
- >
123
+ <Show when={hasAffix()} fallback={plainInput()}>
105
124
  <div
106
125
  part="field"
107
126
  class={cn(
@@ -115,7 +134,7 @@ export function Input(props: InputProps): JSX.Element {
115
134
  <Show when={local.leading}>
116
135
  <span class="flex shrink-0 items-center text-muted-foreground">{local.leading}</span>
117
136
  </Show>
118
- {inputEl(ROW_INPUT, 'input')}
137
+ {rowInput()}
119
138
  <Show when={local.trailing}>
120
139
  <span class="flex shrink-0 items-center text-muted-foreground">{local.trailing}</span>
121
140
  </Show>
@@ -509,6 +509,17 @@ function ResizableHandle(props: ResizableHandleProps) {
509
509
  style={{
510
510
  cursor: isStatic() ? 'default' : isHoriz() ? 'col-resize' : 'row-resize',
511
511
  [isHoriz() ? 'width' : 'height']: '8px',
512
+ // The panels on either side are given explicit `flex-shrink: 0` (see
513
+ // `applyDelta`/`resetPanelToDefault`/`ResizablePanel.sizeStyle`) so THEY
514
+ // never yield space back to the handle. Without an equally explicit
515
+ // shrink here the handle keeps the flexbox default (`flex-shrink: 1`),
516
+ // so the moment its neighbors' sizes sum to (or exceed) the container's
517
+ // main axis — e.g. two percentage sizes that add to 100% after a
518
+ // maximize/restore round trip — flexbox has nowhere else to reclaim the
519
+ // 8px shortfall from and squeezes the handle itself to a computed width
520
+ // of 0. That makes the divider invisible AND undraggable (no hit area)
521
+ // until reload. Pin it to never shrink, matching its neighbors.
522
+ 'flex-shrink': '0',
512
523
  'background': showStripHighlight() ? 'var(--color-muted-foreground, #98989f)' : 'transparent',
513
524
  'opacity': showStripHighlight() ? '0.3' : '1',
514
525
  }}
package/src/wire/chunk.ts CHANGED
@@ -42,6 +42,21 @@ export interface ModelUsage {
42
42
 
43
43
  export interface ModelStreamChunk {
44
44
  text?: string;
45
+ /**
46
+ * The model id the RESPONSE stated, verbatim; REPORT, NEVER INFER.
47
+ *
48
+ * Read from the response rather than the request, which is what makes it work
49
+ * at all when the app builds its own fetch and the kit never sees what was
50
+ * asked for. Providers commonly resolve an alias (ask for `gpt-4o`, get
51
+ * `gpt-4o-2024-08-06`); through a gateway the value is the gateway's own id.
52
+ * Both are reasons to pass the string through untouched.
53
+ *
54
+ * It is NOT guaranteed. A proxy can strip or rewrite it and a custom endpoint
55
+ * may omit it, so a consumer renders it as ABSENT when it is absent. Filling
56
+ * the gap with the requested id would lie in exactly the requested-vs-served
57
+ * mismatch this field exists to catch.
58
+ */
59
+ model?: string;
45
60
  /**
46
61
  * Reasoning delta. `''` is MEANINGFUL, not a no-op: a redacted block has no
47
62
  * readable text but still carries a payload that must round-trip, and a format
@@ -170,6 +185,38 @@ export interface ModelTurn {
170
185
  export interface ConsumeOptions {
171
186
  /** Label for the reasoning disclosure. Defaults to 'Thinking'. */
172
187
  reasoningLabel?: string;
188
+ /**
189
+ * Correlates diagnostics and namespaces reasoning parts for this consume call;
190
+ * assigned automatically when absent.
191
+ *
192
+ * Supply one only to tie a read to an id you already hold. Two reads into the
193
+ * SAME sink must not share a value: the id is what keeps a second round's
194
+ * block 0 from merging into the first round's reasoning part.
195
+ */
196
+ streamId?: string;
197
+ /**
198
+ * The app's own grouping of several reads into ONE logical turn, carried onto
199
+ * every diagnostic event this read emits.
200
+ *
201
+ * THE KIT REPORTS WHAT THE APP DECLARES AND GROUPS NOTHING ON ITS OWN. A chat
202
+ * app running a tool loop, or fanning out to sub-agents, makes several model
203
+ * calls that belong to one turn; the kit sees one Response at a time and has
204
+ * no way to know which ones those are. So it does not guess:
205
+ *
206
+ * readOpenAIStream(res, sink, { traceId: 'turn-42', label: 'planner' })
207
+ *
208
+ * Absent when not supplied -- the key is not present on the events at all,
209
+ * rather than present and undefined. Purely diagnostic: nothing in the parse
210
+ * branches on it and it never reaches a provider.
211
+ */
212
+ traceId?: string;
213
+ /** The app's name for THIS read inside its trace (`'planner'`,
214
+ * `'executor'`, `'retry-2'`). Carried onto every diagnostic event, and
215
+ * absent when not supplied. Never derived from the format or the model.
216
+ *
217
+ * Not to be confused with `reasoningLabel`, which is UI copy for the
218
+ * reasoning disclosure; this one is never rendered to an end user. */
219
+ label?: string;
173
220
  /** Fires once per tool call the moment its arguments parse cleanly. This is
174
221
  * the hook a host's tool loop waits on. There is deliberately no
175
222
  * per-fragment callback: `ToolPart.rawInput` is written on every fragment,