@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
@@ -0,0 +1,80 @@
1
+ import { createSignal, onCleanup, onMount } from 'solid-js';
2
+ import { defineWebComponent } from './define';
3
+ import { readSlots, CONVERSATION_ITEM_SLOTS } from './slots';
4
+ import { SlottedConversationItem } from '../components/conversation-item';
5
+
6
+ interface Props extends Record<string, unknown> {
7
+ /** The row's identity, handed to the container's selection contract. In the
8
+ * element this is the `conversation-id` attribute (host `id` is the fallback). */
9
+ conversationId?: string;
10
+ /** Selected state. Reflected as `aria-current` on the row body and a
11
+ * `data-active` styling hook on the row; the container drives it from its
12
+ * `activeId`. */
13
+ active?: boolean;
14
+ /** Dense single-line row padding. */
15
+ compact?: boolean;
16
+ }
17
+
18
+ /**
19
+ * `<kai-conversation-item>` — one composed row of a consumer-owned conversation
20
+ * loop, slotted into `<kai-conversations>`' light DOM. The consumer owns the
21
+ * loop (framework-native `map`, `<For>`, `v-for`); the container detects these
22
+ * children, skips its data rendering, and runs the parent-item contract over
23
+ * them: selection state flowing container to item, roving tabindex, and the
24
+ * accessible list/row relationship (list rows with button bodies and
25
+ * `aria-current` marking the active one; ratified 2026-08-20 — axe's
26
+ * nested-interactive and aria-required-children rules are why this is not
27
+ * listbox/option).
28
+ *
29
+ * Slots: the default slot is the title; `leading`, `meta` and `menu` are the
30
+ * named regions. The `menu` slot takes your OWN popover (rename, fork, archive
31
+ * live there); the element provides only the region plus focus and ARIA
32
+ * plumbing, never a declarative actions prop, and a click inside it never
33
+ * selects the row.
34
+ *
35
+ * The item is presentational on its own: activation (click, Enter, Space)
36
+ * surfaces as `kai-conversation-select` on the surrounding `<kai-conversations>`,
37
+ * never as an event of this element.
38
+ */
39
+ defineWebComponent<Props>('kai-conversation-item', {
40
+ conversationId: undefined,
41
+ active: undefined,
42
+ compact: undefined,
43
+ }, (props, { element, flag, reflectFlag }) => {
44
+ // Which named regions the consumer has filled; drives the conditional
45
+ // wrappers so an empty region leaves no stray box behind.
46
+ const [slots, setSlots] = createSignal<Record<string, boolean>>({});
47
+ onMount(() => {
48
+ const read = () => setSlots(readSlots(element, CONVERSATION_ITEM_SLOTS));
49
+ read();
50
+ const observer = new MutationObserver(read);
51
+ observer.observe(element, { childList: true, attributes: true, subtree: true });
52
+ onCleanup(() => observer.disconnect());
53
+
54
+ // The HOST is the row LISTITEM (sibling restructure, ratified 2026-08-20):
55
+ // it wraps the activation body AND the consumer's tabbable menu, so it must
56
+ // never be the activation control itself — axe nested-interactive bans
57
+ // focusable descendants of a control. The button role, aria-current and the
58
+ // roving tabindex live on the shadow BODY (`data-kai-item-body`), which the
59
+ // container's controller targets. An authored role wins.
60
+ if (!element.hasAttribute('role')) element.setAttribute('role', 'listitem');
61
+ });
62
+
63
+ // Property and attribute stay in agreement for `active`; the body's
64
+ // aria-current follows it reactively (nothing is written to the host).
65
+ reflectFlag('active');
66
+
67
+ return (
68
+ <SlottedConversationItem
69
+ conversationId={props.conversationId as string | undefined}
70
+ active={flag('active')}
71
+ compact={flag('compact')}
72
+ hostSemantics
73
+ leading={slots().leading ? <slot name="leading" /> : undefined}
74
+ meta={slots().meta ? <slot name="meta" /> : undefined}
75
+ menu={slots().menu ? <slot name="menu" /> : undefined}
76
+ >
77
+ <slot />
78
+ </SlottedConversationItem>
79
+ );
80
+ });
@@ -1,9 +1,12 @@
1
- import { createSignal, onMount, onCleanup, Show } from 'solid-js';
1
+ import { createSignal, createEffect, onMount, onCleanup, Show } from 'solid-js';
2
2
  import { defineWebComponent } from './define';
3
3
  import { createControllableSignal } from '../primitives/controllable';
4
4
  import { readSlots, CONVERSATIONS_SLOTS } from './slots';
5
- import { ConversationList, CollapsedRail, type ConversationListController } from '../components/conversation-list';
6
- import type { ConversationGroup, ConversationSummary, ConversationScope } from '../types';
5
+ import {
6
+ ConversationList, CollapsedRail, createConversationItemsController,
7
+ type ConversationListController,
8
+ } from '../components/conversation-list';
9
+ import type { ConversationGroup, ConversationSummary } from '../types';
7
10
 
8
11
  interface Props extends Record<string, unknown> {
9
12
  /** The list's section headers (`{ id, name, sortOrder, createdAt }`), rendered
@@ -16,7 +19,11 @@ interface Props extends Record<string, unknown> {
16
19
  * matching no entry in `groups`, falls into a trailing "Ungrouped" section, so
17
20
  * nothing you pass in is ever dropped. There is no recency bucketing. Set as a
18
21
  * JS property. Omit to supply them as `<kai-conversation>` light-DOM children
19
- * instead, or for the empty state. */
22
+ * instead, or for the empty state. A search query that matches nothing shows a
23
+ * visible "No conversations match your search" state, distinct from the
24
+ * zero-conversations empty state. Slotted `<kai-conversation-item>` children
25
+ * switch the list into item mode instead: your own rows win and this array is
26
+ * not rendered. */
20
27
  conversations?: ConversationSummary[];
21
28
  /** The id of the currently-open conversation, highlighted in the list. */
22
29
  activeId?: string;
@@ -28,10 +35,14 @@ interface Props extends Record<string, unknown> {
28
35
  /** Initial collapsed state when uncontrolled (default false). Use the
29
36
  * `default-collapsed` attribute to start collapsed in plain HTML. */
30
37
  defaultCollapsed?: boolean;
38
+ /** Dense single-line rows (a leading dot + title, no message count). */
39
+ compact?: boolean;
31
40
  }
32
41
 
33
42
  interface Events {
34
- /** A conversation was selected. */
43
+ /** A conversation was selected. The selection event in BOTH modes: a
44
+ * batteries data row, or an activated `<kai-conversation-item>` child
45
+ * (click, Enter or Space). */
35
46
  'kai-conversation-select': { id: string };
36
47
  /** The "New chat" button was clicked. */
37
48
  'kai-new-chat': Record<string, never>;
@@ -50,19 +61,19 @@ interface Events {
50
61
  * - `id` → ConversationSummary.id
51
62
  * - `group-id` → ConversationSummary.groupId (optional)
52
63
  * - textContent → ConversationSummary.title
53
- * Required fields not expressible as HTML attributes (`scope`, `messageCount`,
54
- * `lastMessageAt`, `updatedAt`) receive safe defaults so the rendered list item
55
- * is fully functional with just `id` + title text.
64
+ * Fields not expressible as HTML attributes are NOT fabricated (F-10): the
65
+ * optional `scope` and `lastMessageAt` stay absent, and the required
66
+ * `messageCount`/`updatedAt` get honest defaults zero messages, and an empty
67
+ * `updatedAt` from which no trailing relative time is derived (the epoch it
68
+ * used to fabricate rendered a bogus "many days ago" on every declarative row).
56
69
  */
57
70
  export function parseKaiConversationElement(n: Element): ConversationSummary {
58
71
  return {
59
72
  id: n.getAttribute('id') ?? '',
60
73
  title: n.textContent?.trim() ?? '',
61
74
  groupId: n.getAttribute('group-id') ?? undefined,
62
- scope: { type: 'collection' } as ConversationScope,
63
75
  messageCount: 0,
64
- lastMessageAt: new Date(0).toISOString(),
65
- updatedAt: new Date(0).toISOString(),
76
+ updatedAt: '',
66
77
  };
67
78
  }
68
79
 
@@ -72,17 +83,34 @@ defineWebComponent<Props, Events>('kai-conversations', {
72
83
  activeId: undefined,
73
84
  collapsed: undefined,
74
85
  defaultCollapsed: undefined,
86
+ compact: undefined,
75
87
  }, (props, { dispatch, element, expose, flag }) => {
76
88
  // Read declarative <kai-conversation> children from light DOM.
77
89
  // Shadow DOM with no <slot> suppresses them visually — they're invisible data carriers.
78
90
  const [slottedConversations, setSlottedConversations] = createSignal<ConversationSummary[]>([]);
91
+ // Item mode (spec 2026-08-20 § 2a): light-DOM <kai-conversation-item> children
92
+ // mean the CONSUMER owns the loop — the container skips its data rendering and
93
+ // runs the parent-item contract over the hosts instead.
94
+ const [itemHosts, setItemHosts] = createSignal<HTMLElement[]>([]);
79
95
  // Which composition slots (header/empty/footer) the consumer has filled.
80
96
  const [slots, setSlots] = createSignal<Record<string, boolean>>({});
81
97
  onMount(() => {
82
98
  const read = () => {
83
99
  const nodes = [...element.querySelectorAll('kai-conversation')];
84
100
  setSlottedConversations(nodes.map(parseKaiConversationElement));
101
+ // Reference-stable: a fresh array every observer tick would re-run the
102
+ // sync effect (whose writes the observer sees) in a feedback loop.
103
+ const hosts = [...element.querySelectorAll<HTMLElement>(':scope > kai-conversation-item')];
104
+ setItemHosts((prev) =>
105
+ prev.length === hosts.length && hosts.every((h, i) => h === prev[i]) ? prev : hosts,
106
+ );
85
107
  setSlots(readSlots(element, CONVERSATIONS_SLOTS));
108
+ // Re-sync on EVERY mutation, not only membership changes: an item host
109
+ // that upgrades after the first sync mutates its own attributes (the
110
+ // facade's mount), which lands here — and its shadow body needs stamping
111
+ // even though the hosts array is reference-stable. sync() writes
112
+ // on-change only, so this cannot feed the observer a loop.
113
+ if (hosts.length) itemsController.sync();
86
114
  };
87
115
  read();
88
116
  const observer = new MutationObserver(read);
@@ -90,9 +118,26 @@ defineWebComponent<Props, Events>('kai-conversations', {
90
118
  onCleanup(() => observer.disconnect());
91
119
  });
92
120
 
121
+ const itemMode = () => itemHosts().length > 0;
122
+
93
123
  // Prop conversations take precedence; slotted children are appended after.
94
124
  const allConversations = () => [...(props.conversations ?? []), ...slottedConversations()];
95
125
 
126
+ // The parent-item contract: selection flowing container→item, roving tabindex,
127
+ // ARIA list-row bookkeeping — pure DOM, host-agnostic (see the controller's JSDoc in
128
+ // components/conversation-list.tsx). Solid context cannot cross the element
129
+ // boundary, so the channel is DOM traversal by construction.
130
+ const itemsController = createConversationItemsController({
131
+ getItems: itemHosts,
132
+ getActiveId: () => props.activeId as string | undefined,
133
+ onSelect: (id) => dispatch('kai-conversation-select', { id }),
134
+ });
135
+ // Re-derive the bookkeeping whenever the children change (the MutationObserver
136
+ // and the shadow slot's slotchange both funnel into itemHosts) or activeId moves.
137
+ createEffect(() => {
138
+ if (itemMode()) itemsController.sync();
139
+ });
140
+
96
141
  // ── Rail collapse (controlled/uncontrolled, same pattern as kai-workspace) ──
97
142
  // `collapsed` (when set) wins; otherwise the element manages its own state,
98
143
  // seeded from `defaultCollapsed`. `setCollapsedTo` always writes the internal
@@ -143,8 +188,12 @@ defineWebComponent<Props, Events>('kai-conversations', {
143
188
  onSelect={(id) => dispatch('kai-conversation-select', { id })}
144
189
  onNewChat={() => dispatch('kai-new-chat')}
145
190
  onToggleSidebar={() => { dispatch('kai-toggle-sidebar'); setCollapsedTo(true); }}
191
+ compact={flag('compact')}
146
192
  onSearchChange={(query) => dispatch('kai-search', { query })}
147
193
  controllerRef={(c) => (controller = c)}
194
+ items={itemMode() ? <slot /> : undefined}
195
+ itemsKeyDown={itemsController.handleKeyDown}
196
+ itemsClick={itemsController.handleClick}
148
197
  header={slots().header ? <slot name="header" /> : undefined}
149
198
  footer={slots().footer ? <slot name="footer" /> : undefined}
150
199
  empty={slots().empty ? <slot name="empty" /> : undefined}
@@ -70,8 +70,8 @@ export interface DefaultPromptInputProps {
70
70
  * (name, media type, reason) and renders nothing itself: what the user should
71
71
  * see is the application's call, not the kit's. */
72
72
  onAttachmentsRejected?: (rejected: RejectedAttachment[]) => void;
73
- /** Show a Search (Globe) button in the left toolbar; calls `onSearch`. */
74
- search?: boolean;
73
+ /** Show a web-search (Globe) button in the left toolbar; calls `onWebSearch`. */
74
+ webSearch?: boolean;
75
75
  /** Show a Voice (Mic) button in the left toolbar; calls `onVoice`. */
76
76
  voice?: boolean;
77
77
  /** Send-button visibility. `'always'` (default) always shows it; `'auto'` shows
@@ -84,7 +84,7 @@ export interface DefaultPromptInputProps {
84
84
  onSubmit: () => void;
85
85
  onSuggestionClick: (v: string) => void;
86
86
  onAttachmentsChange?: (attachments: AttachmentData[]) => void;
87
- onSearch?: () => void;
87
+ onWebSearch?: () => void;
88
88
  onVoice?: () => void;
89
89
  /** When `true` and `loading` is also `true`, the send button is replaced by
90
90
  * a Stop button that calls `onStop`. */
@@ -296,7 +296,7 @@ export function DefaultPromptInput(props: DefaultPromptInputProps) {
296
296
  <Paperclip class="size-4" />
297
297
  </Button>
298
298
  </Show>
299
- <Show when={props.search}>
299
+ <Show when={props.webSearch}>
300
300
  <Button
301
301
  type="button"
302
302
  variant="outline"
@@ -304,7 +304,7 @@ export function DefaultPromptInput(props: DefaultPromptInputProps) {
304
304
  class="rounded-full gap-1"
305
305
  aria-label="Search the web"
306
306
  disabled={props.disabled}
307
- onClick={() => props.onSearch?.()}
307
+ onClick={() => props.onWebSearch?.()}
308
308
  >
309
309
  <Globe class="size-4" />
310
310
  Search
@@ -1,7 +1,8 @@
1
1
  import { customElement } from 'solid-element';
2
2
  import { ChatConfig } from '../primitives/chat-config';
3
3
  import { ELEMENT_CSS } from './css';
4
- import { createSignal, onCleanup, Show, type JSX } from 'solid-js';
4
+ import { elementDiagnosticsWanted, installElementDiagnostics } from './element-diagnostics';
5
+ import { createEffect, createSignal, onCleanup, Show, type JSX } from 'solid-js';
5
6
 
6
7
  /**
7
8
  * Shared constructable stylesheet, built once and adopted by every element's
@@ -78,6 +79,33 @@ export interface WebComponentContext<E = Record<string, unknown>> {
78
79
  * convention). Methods are attached when the facade renders (on element upgrade).
79
80
  */
80
81
  expose: (methods: Record<string, (...args: never[]) => unknown>) => void;
82
+ /**
83
+ * Reflect a boolean prop to its host attribute AND make that prop read back what
84
+ * was set. Use this instead of hand-rolling
85
+ * `createEffect(() => element.toggleAttribute('x', flag('x')))`.
86
+ *
87
+ * WHY IT IS ONE CALL AND NOT TWO. The reflection is what BREAKS the read-back, so
88
+ * the fix has to be attached to it or it gets forgotten — and it was, three times.
89
+ * `toggleAttribute(name, true)` sets the attribute to the EMPTY STRING;
90
+ * component-register's `attributeChangedCallback` then writes the prop back as
91
+ * `this[name] = parseAttributeValue("")`, and `parseAttributeValue` returns
92
+ * `undefined` for the empty string. So a reflected flag became write-only:
93
+ * `el.loading = true` left `el.loading === undefined` while `[loading]` was on the
94
+ * host. The element kept BEHAVING correctly, because `flag()` reads the attribute —
95
+ * which is precisely why nobody noticed until a consumer tried to read the property
96
+ * back (findings G-05).
97
+ *
98
+ * What this installs is an instance-level wrapper around the accessor
99
+ * component-register already created, coercing every incoming value through the
100
+ * same `flag()` policy (`resolveFlag`) — so the `undefined` write-back resolves to
101
+ * the attribute that caused it, and the property and the attribute can no longer
102
+ * disagree. It delegates to the underlying setter, so reactivity is untouched.
103
+ *
104
+ * `source` overrides where the ON/OFF value comes from, for an element whose truth
105
+ * is an internal controller rather than the prop (see `wireDisclosure`). Returning
106
+ * `undefined` from it means "not ready, leave the attribute alone".
107
+ */
108
+ reflectFlag: (name: string, source?: () => boolean | undefined) => void;
81
109
  /**
82
110
  * Whether the element should currently render its dark-mode look --
83
111
  * exactly the SAME resolved value that already drives the `.dark` class
@@ -197,10 +225,22 @@ function defineWithNonReflectingProps<T>(
197
225
  tag: string,
198
226
  keys: readonly string[],
199
227
  define: () => T,
228
+ /**
229
+ * Anything else that must be on the prototype BEFORE the registry sees the
230
+ * class. Today that is the element-layer diagnostics, and it needs this seam
231
+ * for a reason that is adjacent to the one above but not the same: `define()`
232
+ * SNAPSHOTS the lifecycle callbacks (`connectedCallback`,
233
+ * `attributeChangedCallback`, …) off the prototype into the custom-element
234
+ * definition, and the browser invokes the snapshot. A wrapper installed
235
+ * afterwards is present on the prototype, passes `hasOwnProperty`, and is
236
+ * never called. See `installElementDiagnostics`.
237
+ */
238
+ alsoPatch?: (proto: object) => void,
200
239
  ): { result: T; handled: boolean } {
201
- // `handled` = nothing more for the caller to do. With no colliding props there is
202
- // nothing to install at all, so that case is trivially handled.
203
- if (keys.length === 0) return { result: define(), handled: true };
240
+ // `handled` = nothing more for the caller to do. With no colliding props and
241
+ // nothing else to patch there is nothing to install at all, so that case is
242
+ // trivially handled.
243
+ if (keys.length === 0 && !alsoPatch) return { result: define(), handled: true };
204
244
  const registry = customElements;
205
245
  const hadOwnDefine = Object.prototype.hasOwnProperty.call(registry, 'define');
206
246
  const inner = registry.define;
@@ -214,7 +254,8 @@ function defineWithNonReflectingProps<T>(
214
254
  options?: ElementDefinitionOptions,
215
255
  ) {
216
256
  if (name === tag) {
217
- installNonReflectingProps(constructor.prototype, keys);
257
+ if (keys.length) installNonReflectingProps(constructor.prototype, keys);
258
+ alsoPatch?.(constructor.prototype);
218
259
  handled = true;
219
260
  }
220
261
  return inner.call(this, name, constructor, options);
@@ -237,6 +278,76 @@ function defineWithNonReflectingProps<T>(
237
278
  }
238
279
  }
239
280
 
281
+ /**
282
+ * Marks a setter as one of ours, so the wrap is idempotent WITHOUT being permanent.
283
+ *
284
+ * The mark goes on the setter function rather than on the element, and that is the
285
+ * whole subtlety: component-register installs a FRESH instance accessor from
286
+ * `initializeProps` on every (re)connect after a release, which throws our wrapper
287
+ * away. An element-level "already done" flag would then skip re-wrapping and the
288
+ * property would quietly go write-only again on the second mount. An unmarked setter
289
+ * is by definition a new one, so it gets wrapped again.
290
+ */
291
+ const FLAG_READ_BACK = Symbol('kai-flag-read-back');
292
+
293
+ /**
294
+ * Wrap the instance accessor component-register created for `name` so every value
295
+ * written to it — a consumer's assignment, or the `undefined` write-back that
296
+ * `attributeChangedCallback` performs after a `toggleAttribute` — is resolved through
297
+ * the same policy `flag()` uses.
298
+ *
299
+ * TIMING. This must run from the facade body, i.e. during `connectedCallback` AFTER
300
+ * `initializeProps`, and that is not a detail: `initializeProps` harvests any value
301
+ * set on the element BEFORE upgrade (`value = element[key]`) and only then installs
302
+ * the accessor. Wrapping earlier (on the prototype, the way `installNonReflectingProps`
303
+ * does) would be shadowed by that accessor and would also sit in front of the harvest —
304
+ * the same class of defect `define-upgrade-ordering.test.tsx` pins for attributes.
305
+ */
306
+ function installFlagReadBack(element: HTMLElement, name: string, attribute: string): void {
307
+ const inner = Object.getOwnPropertyDescriptor(element, name);
308
+ if (!inner?.get || !inner.set) {
309
+ // Reached only if `name` is not a declared prop of this element — a typo, or a
310
+ // facade reflecting something it never declared. Warn rather than no-op silently;
311
+ // a silent skip here would look exactly like a working read-back.
312
+ console.warn(
313
+ `reflectFlag("${name}"): no such declared prop on <${element.localName}> — ` +
314
+ `the attribute will still reflect, but the property will not read back.`,
315
+ );
316
+ return;
317
+ }
318
+ const { get, set } = inner;
319
+ if ((set as { [FLAG_READ_BACK]?: true })[FLAG_READ_BACK]) return;
320
+
321
+ const wrapped = function (this: HTMLElement, value: unknown) {
322
+ set.call(this, resolveFlag(this, value, attribute));
323
+ } as ((value: unknown) => void) & { [FLAG_READ_BACK]?: true };
324
+ wrapped[FLAG_READ_BACK] = true;
325
+
326
+ Object.defineProperty(element, name, {
327
+ get,
328
+ set: wrapped,
329
+ enumerable: inner.enumerable ?? true,
330
+ configurable: true,
331
+ });
332
+
333
+ // Seed, but ONLY upwards. The value sitting in the prop store right now may already
334
+ // be the broken one: `<el loading>` parses to `undefined` in `initializeProps`
335
+ // exactly as it does in the write-back, so without this the bare-attribute case
336
+ // would still read back `undefined` until something else wrote to the prop.
337
+ //
338
+ // WHY NOT SEED `false` TOO. Writing `false` over an `undefined` would look tidier
339
+ // and is wrong: for props declared with an `undefined` default, `undefined` MEANS
340
+ // "the consumer has not set this", and at least one caller depends on telling that
341
+ // apart from an explicit `false` — `wireDisclosure` seeds from `defaultOpen` only
342
+ // while `open` is unset, so a blanket `false` seed would clobber
343
+ // `<el default-open>` on every mount. Promoting only a present attribute to `true`
344
+ // fixes the read-back without inventing an opinion the author never expressed.
345
+ const current = get.call(element);
346
+ if (typeof current !== 'boolean' && resolveFlag(element, current, attribute)) {
347
+ wrapped.call(element, true);
348
+ }
349
+ }
350
+
240
351
  /** Underlying flag resolution; see `WebComponentContext.flag`. */
241
352
  function resolveFlag(element: HTMLElement, value: unknown, attribute: string): boolean {
242
353
  if (value === true) return true;
@@ -329,6 +440,21 @@ export function defineWebComponent<P extends Record<string, unknown>, E = Record
329
440
  const flag = (name: string) =>
330
441
  resolveFlag(element, (props as Record<string, unknown>)[name], toAttr(name));
331
442
 
443
+ // Reflect a boolean prop to its attribute and keep the property readable. See
444
+ // WebComponentContext.reflectFlag for the defect this exists to close.
445
+ const reflectFlag: WebComponentContext<E>['reflectFlag'] = (name, source) => {
446
+ const attribute = toAttr(name);
447
+ installFlagReadBack(element, name, attribute);
448
+ createEffect(() => {
449
+ const on = source ? source() : flag(name);
450
+ // `undefined` means the caller's source is not ready yet (a controller a
451
+ // primitive has not handed up). Leave the author's attribute alone rather
452
+ // than writing a guessed OFF over it.
453
+ if (on === undefined) return;
454
+ element.toggleAttribute(attribute, on);
455
+ });
456
+ };
457
+
332
458
  // Attach imperative methods onto the host instance. See WebComponentContext.expose.
333
459
  // Uses defineProperty so a method can shadow an inherited getter-only accessor
334
460
  // (e.g. `focus`/`blur`) which a plain assignment would throw on. A method name
@@ -381,15 +507,39 @@ export function defineWebComponent<P extends Record<string, unknown>, E = Record
381
507
  <div classList={{ dark: isDark() }} style={{ display: 'contents', color: 'var(--color-foreground)' }}>
382
508
  <div ref={portalNode} />
383
509
  <ChatConfig portalMount={portalNode}>
384
- {Facade(props as unknown as P, { element, dispatch, flag, expose, dark: isDark })}
510
+ {Facade(props as unknown as P, {
511
+ element,
512
+ dispatch,
513
+ flag,
514
+ reflectFlag,
515
+ expose,
516
+ dark: isDark,
517
+ })}
385
518
  </ChatConfig>
386
519
  </div>
387
520
  </>
388
521
  );
389
522
  };
390
523
 
391
- const { result: Ctor, handled } = defineWithNonReflectingProps(tag, shadowedProps, () =>
392
- customElement(tag, defaults, renderFacade),
524
+ // Element-layer diagnostics: report the three `kai-` contract violations that
525
+ // are otherwise silent (an array or object prop set as an HTML attribute, the
526
+ // same array reference handed back, a new array of the same item objects).
527
+ //
528
+ // HERE because this function is the single choke point — every kai-* element
529
+ // registers through it, which `elements/slots.test.ts` already enforces — so
530
+ // one call covers every element with no per-element work and no second list.
531
+ //
532
+ // Through the pre-define seam, not after the fact, because the registry
533
+ // snapshots lifecycle callbacks at definition time; see `alsoPatch` above.
534
+ // `elementDiagnosticsWanted` is asked first so a tag with no non-scalar prop
535
+ // (37 of the 80) does not even pay for the registry interception.
536
+ const wantsDiagnostics = elementDiagnosticsWanted(tag);
537
+
538
+ const { result: Ctor, handled } = defineWithNonReflectingProps(
539
+ tag,
540
+ shadowedProps,
541
+ () => customElement(tag, defaults, renderFacade),
542
+ wantsDiagnostics ? (proto) => installElementDiagnostics(tag, proto) : undefined,
393
543
  );
394
544
 
395
545
  // Belt and braces for the one path the wrap cannot see: if `customElements.define`
@@ -400,4 +550,14 @@ export function defineWebComponent<P extends Record<string, unknown>, E = Record
400
550
  // in the normal case, where the accessors are already on this same prototype.
401
551
  const proto = (Ctor as unknown as { prototype?: object } | undefined)?.prototype;
402
552
  if (!handled && proto) installNonReflectingProps(proto, shadowedProps);
553
+
554
+ // NOTE the diagnostics have no equivalent late fallback, and must not get one.
555
+ // The non-reflecting props still work when installed late (they only have to
556
+ // beat the NEXT constructor); the diagnostics do not, because the registry has
557
+ // already snapshotted the lifecycle callbacks it will invoke. Installing them
558
+ // here would produce a wrapper that is visibly present and never called —
559
+ // which is worse than not installing it, since it would read as coverage. So
560
+ // in the one environment the pre-define seam cannot reach (a frozen registry:
561
+ // SES lockdown, a sealed polyfill), element diagnostics are simply absent, and
562
+ // every other element behaviour is unaffected.
403
563
  }
@@ -0,0 +1,114 @@
1
+ // The ELEMENT-layer diagnostic events: what the `kai-*` custom elements SAW a
2
+ // consumer do to them.
3
+ //
4
+ // The wire events answer "did the data arrive". These answer the other half —
5
+ // "the data is right and the UI still is not updating" — which is a different
6
+ // failure with a different cause and, until now, no evidence at all. All three
7
+ // causes are documented in root CLAUDE.md's `kai-` contract section, and every
8
+ // one of them is silent by construction:
9
+ //
10
+ // 1. An array or object prop set as an HTML ATTRIBUTE. `component-register`
11
+ // parses attributes with a `JSON.parse` whose `catch` returns the raw
12
+ // string, so `el.setAttribute('messages', arr)` assigns the literal text
13
+ // `"[object Object],[object Object]"` to the property and pushes THAT into
14
+ // the Solid signal. Nothing throws, nothing logs, nothing renders.
15
+ // 2. The SAME array reference handed back. solid-element creates one signal
16
+ // per prop and sets it with `set(() => v)`; Solid's default equality is
17
+ // `===`, so re-assigning the identical reference notifies nothing.
18
+ // 3. A NEW array whose items are all the previous item objects. The array
19
+ // notifies, but the lists render through reference-keyed `<For>`s, so no
20
+ // row is re-invoked and nothing on screen changes.
21
+ //
22
+ // ZERO IMPORTS, DELIBERATELY. These are type declarations only, so this file is
23
+ // erased at build. That is what lets `wire/diagnostics.ts` name them in its
24
+ // union without `wire/` acquiring a runtime dependency on `elements/` — which
25
+ // would be a real layering inversion, and would drag the element bundle into
26
+ // every consumer that only parses streams.
27
+ //
28
+ // METADATA ONLY, by the same rule the wire events follow: if a value comes from
29
+ // the model, the end user, or the app's data it is PAYLOAD; if it describes the
30
+ // shape, size, timing or identity of that value it is METADATA. A tag name, a
31
+ // prop name, a kind, a count and a length are all identity or shape. A prop's
32
+ // VALUE never appears in any field below — `valuePreview` is a shape
33
+ // description built from a closed vocabulary, never the text itself. Pinned by
34
+ // `element-diagnostics-payload.test.ts`, which plants a sentinel inside every
35
+ // prop value it sets and asserts it never reaches a serialized event.
36
+
37
+ /** The envelope, structurally identical to `WireDiagnosticBase`. Restated here
38
+ * rather than imported so this file keeps its zero-import property; the two are
39
+ * kept in step by `element-diagnostics.test.ts`, which asserts every emitted
40
+ * element event carries a `type` and a numeric `t`. */
41
+ export interface ElementDiagnosticBase {
42
+ type: string;
43
+ /** `Date.now()` at emission. */
44
+ t: number;
45
+ }
46
+
47
+ /** Which contract was broken. A closed vocabulary, so a panel keys its own
48
+ * explanation off the kind and never needs prose from the kit. */
49
+ export type ElementViolationKind =
50
+ /** A non-scalar prop received a value through the ATTRIBUTE channel. The
51
+ * attribute cannot carry an array or an object, so the property now holds a
52
+ * string and the element renders nothing. */
53
+ | 'array-prop-as-attribute'
54
+ /** A list prop was set to the array reference it already held. Solid compares
55
+ * with `===`, so this notified nothing — a no-op write. */
56
+ | 'same-array-reference'
57
+ /** A list prop received a NEW array of the SAME length whose every item is
58
+ * reference-identical to the previous array's. The array notified, but every
59
+ * row's identity is unchanged, so a reference-keyed `<For>` re-invokes none
60
+ * of them. The classic "I mutated the item and copied the array" case.
61
+ *
62
+ * NOTE what this deliberately does NOT claim: the kit cannot know an item
63
+ * "changed" without comparing its CONTENT, and content is payload. So this
64
+ * reports the structurally knowable fact — an update that cannot render —
65
+ * and leaves the question of whether the consumer meant anything by it to
66
+ * the consumer, who is the only one who can answer it. */
67
+ | 'mutated-in-place';
68
+
69
+ /** A consumer broke one of the three `kai-` contract rules on a live element. */
70
+ export interface ElementViolationEvent extends ElementDiagnosticBase {
71
+ type: 'element.violation';
72
+ kind: ElementViolationKind;
73
+ /** The custom-element tag, e.g. `kai-chat`. */
74
+ tag: string;
75
+ /** The camelCase prop name, e.g. `messages`. */
76
+ prop: string;
77
+ /**
78
+ * SHAPE of the offending value, never its content. `array-prop-as-attribute`
79
+ * only. A closed vocabulary:
80
+ *
81
+ * `[object Object]` one object stringified into the attribute
82
+ * `[object Object] x N` an array of N objects stringified — the common case
83
+ * `json:number` the attribute held valid JSON, but a scalar
84
+ * `json:boolean` "
85
+ * `json:null` "
86
+ * `string(len=N)` anything else: the LENGTH of the text, never the text
87
+ * `empty-attribute` a bare attribute on a prop that needs a value
88
+ */
89
+ valuePreview?: string;
90
+ /** Element count of the array involved. `same-array-reference` and
91
+ * `mutated-in-place` only. A length is shape, not content. */
92
+ length?: number;
93
+ }
94
+
95
+ /** Which `kai-*` elements are DEFINED in this realm right now.
96
+ *
97
+ * The question behind it is hydration: an SSR page whose markup contains
98
+ * `<kai-chat>` but whose element bundle never loaded shows empty boxes and no
99
+ * error, because an undefined custom element is a valid, inert HTMLElement.
100
+ * The SSR starters already answer this by hand for a hard-coded handful
101
+ * (`HydrationBadge.tsx`); this is the same answer over the whole manifest. */
102
+ export interface ElementRegistryEvent extends ElementDiagnosticBase {
103
+ type: 'element.registry';
104
+ /** Tags `customElements.get()` resolved. */
105
+ defined: string[];
106
+ /** Tags in the manifest that are NOT defined in this realm. Named for what it
107
+ * is rather than `undefined`, which is a miserable key to read in a panel. */
108
+ notDefined: string[];
109
+ /** `defined.length + notDefined.length`. Present so a panel can show "62 of
110
+ * 79" without trusting itself to add up two arrays it may have truncated. */
111
+ total: number;
112
+ }
113
+
114
+ export type ElementDiagnosticEvent = ElementViolationEvent | ElementRegistryEvent;