@kitn.ai/ui 0.25.1 → 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 +5 -7
  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 +27 -7
  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,231 @@
1
+ import { z } from 'zod';
2
+
3
+ /** Surface changes appearance; target changes delivery. Two axes, never one. */
4
+ export const SurfaceArchetype = z.enum(['full-screen', 'widget', 'docked', 'inline', 'platform-embed']);
5
+ export const DeliveryTarget = z.enum(['bundler', 'script-tag']);
6
+
7
+ /** The three readers `src/wire/read.ts` exports. The drift lint (Task 7) resolves them. */
8
+ export const WireReader = z.enum(['readModelStream', 'readOpenAIStream', 'readAnthropicStream']);
9
+
10
+ /** BYO key: the endpoint is always the consumer's own. One swappable field by design. */
11
+ export const Backend = z.object({
12
+ endpoint: z.literal('consumer-owned'),
13
+ reader: WireReader,
14
+ });
15
+
16
+ /**
17
+ * Tagged, because a bare path cannot honestly describe every invariant:
18
+ * `none` is a REPORTED coverage gap, never a failure and never a fake path.
19
+ */
20
+ export const EnforcedBy = z.discriminatedUnion('kind', [
21
+ z.object({ kind: z.literal('test'), paths: z.array(z.string()).min(1) }),
22
+ z.object({ kind: z.literal('lint'), script: z.string() }),
23
+ z.object({ kind: z.literal('structural'), path: z.string() }),
24
+ z.object({ kind: z.literal('none'), until: z.string().optional() }),
25
+ ]);
26
+
27
+ export const Diagnosis = z.object({ symptom: z.string(), cause: z.string() });
28
+
29
+ /**
30
+ * One wrong/right pair, as CODE rather than prose. A weak model applies a
31
+ * fragment it can pattern-match far more reliably than a sentence it has to
32
+ * reason from, and the pairs are also mechanically searchable: the self-audit
33
+ * checklist greps emitted code for the `wrong` form and expects zero hits. So
34
+ * keep `wrong` a compact, literal, greppable fragment — not a paraphrase.
35
+ */
36
+ export const InvariantExample = z.object({
37
+ wrong: z.string().min(1),
38
+ right: z.string().min(1),
39
+ note: z.string().optional(),
40
+ });
41
+
42
+ export const Invariant = z.object({
43
+ id: z.string().regex(/^[a-z0-9-]+$/),
44
+ statement: z.string().min(1),
45
+ appliesTo: z.object({
46
+ tags: z.array(z.string()).optional(),
47
+ parts: z.array(z.string()).optional(),
48
+ targets: z.array(DeliveryTarget).optional(),
49
+ }),
50
+ enforcedBy: EnforcedBy,
51
+ /**
52
+ * `enforced` — a guard covers what the statement prescribes.
53
+ * `partial` — a guard covers PART of it and the rest is uncovered; the
54
+ * statement must say which half is which. Added because two
55
+ * records were `enforced` while their headline consumer-facing
56
+ * prescription had no check at all, and `status` is what a
57
+ * downstream tool reads when it does not read the prose.
58
+ * `open` — nothing covers it. Travels with `enforcedBy.kind === 'none'`,
59
+ * in both directions, asserted in invariants.test.ts.
60
+ */
61
+ status: z.enum(['enforced', 'partial', 'open']),
62
+ diagnosis: z.array(Diagnosis).default([]),
63
+ // `.default([])` mirrors `diagnosis` above for consistency, and nothing more:
64
+ // it is NOT a compatibility affordance. `z.infer` makes `examples` required on
65
+ // `TInvariant`, and the only caller is `listInvariants()` over an in-repo
66
+ // literal — no external or serialized input reaches `Invariant.parse`, so
67
+ // there is no unmigrated record for the default to rescue. What actually makes
68
+ // the field mandatory is invariants.test.ts, which requires at least one pair
69
+ // on EVERY record and constrains the shape of each.
70
+ examples: z.array(InvariantExample).default([]),
71
+ });
72
+
73
+ /**
74
+ * WHERE one element goes: `child` is slotted into `parent`'s named `slot`.
75
+ *
76
+ * Structured rather than prose, for the reason the wiring edges are: an emitter
77
+ * has to be able to READ the composition. The scaffolder emitted the rail as a
78
+ * sibling below the chat while `<kai-chat>` documented a `sidebar` slot whose
79
+ * description is "left column (your nav / conversation list)", and an agent
80
+ * building from the MCP could not answer which was intended from anything the
81
+ * MCP served — because nothing served it. A sentence in `intent` would not have
82
+ * fixed that: `intent` already said "chat with a conversations sidebar" while
83
+ * the emitter did the other thing.
84
+ *
85
+ * NOT resolvable by `lint:catalog-drift`. That lint's ground truth is
86
+ * derived.json, which carries props, events, methods, parts, composedFrom and
87
+ * tokens — no slots — so `child`/`parent` resolve as elements and the SLOT NAME
88
+ * resolves against nothing. element-meta.json has the slots; wiring them into
89
+ * the derived layer is the way to close it.
90
+ */
91
+ export const CompositionPlacement = z.object({
92
+ /** the element that goes inside */
93
+ child: z.string(),
94
+ /** the element it goes inside of */
95
+ parent: z.string(),
96
+ /** the `slot` attribute the child carries */
97
+ slot: z.string(),
98
+ note: z.string().optional(),
99
+ });
100
+
101
+ /** One host-coordinates edge: this event on A sets this property on B. */
102
+ export const WiringEdge = z.object({
103
+ from: z.string(),
104
+ event: z.string(),
105
+ to: z.string(),
106
+ property: z.string(),
107
+ note: z.string().optional(),
108
+ });
109
+
110
+ export const SurfaceRecipe = z.object({
111
+ id: z.string().regex(/^[a-z0-9-]+$/),
112
+ intent: z.string().min(1),
113
+ archetypes: z.array(SurfaceArchetype).min(1),
114
+ targets: z.array(DeliveryTarget).min(1),
115
+ ingredients: z.array(z.string()).min(1),
116
+ backend: Backend,
117
+ // `.min(1)` like its siblings, and for a sharper reason than symmetry: an
118
+ // empty `wiring` is what a recipe looks like when it makes NO host-coordinates
119
+ // claim at all. Every wiring check in the drift lint is a loop over this
120
+ // array, so emptying it deleted the topology from both recipes while the lint
121
+ // went on printing "2 recipes ... resolved clean". The lint carries a readable
122
+ // duplicate of this check so the failure is a message rather than a ZodError.
123
+ wiring: z.array(WiringEdge).min(1),
124
+ /**
125
+ * Optional, and `.min(1)` when present: a recipe whose ingredients nest states
126
+ * where they nest, and a recipe of one element has nothing to say here. An
127
+ * empty array would be a third thing — a composition claim that claims
128
+ * nothing — so the schema refuses it.
129
+ */
130
+ composition: z.array(CompositionPlacement).min(1).optional(),
131
+ invariants: z.array(z.string()).min(1),
132
+ corpus: z.array(z.string()).min(1),
133
+ });
134
+
135
+ export const InventorySort = z.enum(['surface', 'ingredient', 'corpus']);
136
+ export const InventoryEntry = z.object({
137
+ title: z.string().min(1),
138
+ sort: InventorySort,
139
+ note: z.string().min(1),
140
+ });
141
+
142
+ export const ScenarioId = z.enum(['S1', 'S2', 'S3', 'S4', 'S5', 'S6', 'S7']);
143
+ export const Scenario = z.object({
144
+ id: ScenarioId,
145
+ prompt: z.string().min(1),
146
+ needs: z.array(z.string()).min(1),
147
+ depth: z.string().min(1),
148
+ scoring: z.array(z.string()).min(1),
149
+ });
150
+
151
+ /** The derived layer's committed artifact. Task 3's generator writes it; Task 3's test parses it. */
152
+ export const DerivedElement = z.object({
153
+ tag: z.string(),
154
+ props: z.array(
155
+ z.object({
156
+ name: z.string(),
157
+ scalar: z.boolean(),
158
+ optional: z.boolean(),
159
+ /**
160
+ * The function-valued-property contract, which `scalar` does NOT encode:
161
+ * `scalar: false` says "not an attribute", never "this is a callback you
162
+ * must supply". Derived by the generator from element-meta.json's prop
163
+ * type: strip a leading `undefined | `, then true iff the remainder
164
+ * starts with `(` and contains `=>`. Deliberately not a bare
165
+ * `includes('=>')`, which over-matches objects and arrays that merely
166
+ * CONTAIN callbacks (kai-cards.policy, kai-toast-region.toasts).
167
+ */
168
+ fn: z.boolean(),
169
+ }),
170
+ ),
171
+ events: z.array(z.string()),
172
+ methods: z.array(z.string()),
173
+ parts: z.array(z.string()),
174
+ /** Spec §3 names both; element-meta.json already carries them. */
175
+ composedFrom: z.array(z.string()),
176
+ tokens: z.array(z.string()),
177
+ });
178
+
179
+ /**
180
+ * Which MessagePart variants an element consumes. NOT derivable from any type
181
+ * today, so spec §3's registered-copy rule applies: this is an explicit copy,
182
+ * and Task 7's drift lint fails when the union gains a variant no record
183
+ * accounts for. Registered in "Copies this plan creates" at the end of the plan.
184
+ */
185
+ export const PartConsumption = z.object({
186
+ tag: z.string(),
187
+ consumes: z.array(z.string()).min(1),
188
+ });
189
+
190
+ export const EventException = z.object({
191
+ file: z.string(),
192
+ event: z.string(),
193
+ bubbles: z.boolean(),
194
+ composed: z.boolean(),
195
+ });
196
+
197
+ export const DerivedCatalog = z.object({
198
+ elements: z.array(DerivedElement).min(1),
199
+ // REGISTERED COPY: this floor restates MIN_VARIANTS, which lives in
200
+ // scripts/lib/message-part-variants.mjs (Task 2) and cannot be imported into a
201
+ // .ts module that also runs in the browser bundle. The generator asserts the
202
+ // real MIN_VARIANTS; this is the schema-side backstop. If MIN_VARIANTS moves,
203
+ // move this too — see "Copies this plan creates".
204
+ partVariants: z.array(z.string()).min(4),
205
+ integrations: z
206
+ .array(z.object({ id: z.string(), category: z.string(), streamFormat: z.string(), keyExposure: z.string() }))
207
+ .min(1),
208
+ capabilityGroups: z.array(z.object({ id: z.string(), components: z.array(z.string()) })).min(1),
209
+ themeTokens: z.array(z.string()).min(1),
210
+ // .min(1) because the tree HAS deliberate protocol exceptions — events that
211
+ // set bubbles or composed on purpose — so an empty array cannot be a true
212
+ // reading of it: it means the extractor broke, and a broken extractor that
213
+ // parses clean would silently gut spec §5's exception list.
214
+ //
215
+ // Deliberately no count here. The number is the extractor's to report, not
216
+ // this comment's to restate: `npm run build:api` (its gen-catalog.mjs step)
217
+ // prints the count as it writes, and the current set is the `eventExceptions`
218
+ // array in src/agent-tooling/catalog/derived.json. A hand-typed figure here
219
+ // would be stale the first time an element opts in or out, on the schema for
220
+ // the very field whose exception list was wrong before.
221
+ eventExceptions: z.array(EventException).min(1),
222
+ });
223
+
224
+ export type TCompositionPlacement = z.infer<typeof CompositionPlacement>;
225
+ export type TInvariant = z.infer<typeof Invariant>;
226
+ export type TInvariantExample = z.infer<typeof InvariantExample>;
227
+ export type TSurfaceRecipe = z.infer<typeof SurfaceRecipe>;
228
+ export type TScenario = z.infer<typeof Scenario>;
229
+ export type TInventoryEntry = z.infer<typeof InventoryEntry>;
230
+ export type TPartConsumption = z.infer<typeof PartConsumption>;
231
+ export type TDerivedCatalog = z.infer<typeof DerivedCatalog>;
@@ -0,0 +1,96 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * Tags agents invented while working from an acceptance pack, and what they
5
+ * should have used instead. The pack's `FABRICATED.md` is rendered from this.
6
+ *
7
+ * WHY THIS IS AN AUTHORED RECORD AND NOT A LOG THE EVALUATOR APPENDS TO.
8
+ * A row here is a claim about the tree — "the kit does not ship this tag" — and
9
+ * the claim rots in one direction that matters: the day the kit ships
10
+ * `kai-datagrid`, a row saying it does not exist becomes a lie told to every
11
+ * future agent, on the one page whose whole job is telling agents what is real.
12
+ * Living beside `invariants.ts` puts it where `fabrications.test.ts` can resolve
13
+ * it against `derived.json` in both directions on every CI run. A JSON log
14
+ * written by a judged run would have neither the review nor the check.
15
+ *
16
+ * The schema is declared here rather than in `catalog-types.ts` because this
17
+ * record arrived a task later than that file and nothing else consumes the
18
+ * shape; fold it in there if a second consumer appears.
19
+ *
20
+ * EVERY ROW COMES FROM AN OBSERVED RUN. Do not seed it with tags a model might
21
+ * plausibly invent — a fabricated fabrication is exactly the failure the page
22
+ * exists to prevent, and it would be indistinguishable from a real one.
23
+ */
24
+ export const Fabrication = z
25
+ .object({
26
+ /** The tag that was written and does not exist. Checked ABSENT from derived.json. */
27
+ invented: z.string().regex(/^kai-[a-z0-9-]+$/),
28
+ /** What the agent was trying to accomplish, in its own terms. */
29
+ wanted: z.string().min(1),
30
+ /** The real element to reach for. Checked PRESENT in derived.json when non-null. */
31
+ useInstead: z.string().regex(/^kai-[a-z0-9-]+$/).nullable(),
32
+ /**
33
+ * Required when `useInstead` is null. "There is nothing for this" is a real
34
+ * and useful answer — S6 exists to elicit exactly that — but it has to be
35
+ * said, because a blank cell reads as an unfinished row.
36
+ */
37
+ noReplacementReason: z.string().min(1).optional(),
38
+ /** ISO date of the run that first produced it. */
39
+ firstSeen: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
40
+ /** Provenance, so a row can be traced back to the run that observed it. */
41
+ scenario: z.string().min(1),
42
+ model: z.string().min(1),
43
+ })
44
+ .refine((f) => f.useInstead !== null || Boolean(f.noReplacementReason), {
45
+ message: 'a row with no `useInstead` must carry `noReplacementReason`',
46
+ path: ['noReplacementReason'],
47
+ });
48
+
49
+ export type TFabrication = z.infer<typeof Fabrication>;
50
+
51
+ /**
52
+ * EMPTY, and that is the honest state: the apparatus that would fill it (Task
53
+ * 8b's runner and evaluator) has just been built and no acceptance run has been
54
+ * performed. `FABRICATED.md` says the same thing in the same words to the agent.
55
+ */
56
+ export const fabrications: TFabrication[] = [];
57
+
58
+ export function listFabrications(): TFabrication[] {
59
+ return z.array(Fabrication).parse(fabrications);
60
+ }
61
+
62
+ /**
63
+ * Resolve rows against the tags the kit actually ships, in BOTH directions.
64
+ *
65
+ * This is a named function rather than a loop inside the test for one reason:
66
+ * the record is empty, so a loop over it iterates zero times and passes whatever
67
+ * the rule is. Deleting both assertions left the suite green. A rule that cannot
68
+ * be exercised is not a guard, and the direction it protects — `invented`, the
69
+ * one that rots when the kit later ships the tag — is the direction this file's
70
+ * own comment says matters most.
71
+ *
72
+ * Extracted so it can be run over FIXTURES that make it fire, and over the live
73
+ * record, from the same code path.
74
+ *
75
+ * @returns problems; empty means every row resolves
76
+ */
77
+ export function resolveFabrications(rows: TFabrication[], knownTags: Iterable<string>): string[] {
78
+ const known = new Set(knownTags);
79
+ if (known.size === 0) {
80
+ // Otherwise every row "resolves" in the direction that matters and none in
81
+ // the other, which is a broken input reported as a clean record.
82
+ return ['no known tags were supplied, so nothing could be resolved'];
83
+ }
84
+ const problems: string[] = [];
85
+ for (const row of rows) {
86
+ if (known.has(row.invented)) {
87
+ problems.push(
88
+ `\`${row.invented}\` is recorded as invented, but the kit SHIPS it now. Delete the row: it tells every future agent that a real element does not exist, on the one page whose job is saying what is real.`,
89
+ );
90
+ }
91
+ if (row.useInstead && !known.has(row.useInstead)) {
92
+ problems.push(`\`${row.invented}\` points at \`${row.useInstead}\` as the replacement, and the kit does not ship that either.`);
93
+ }
94
+ }
95
+ return problems;
96
+ }
@@ -0,0 +1,284 @@
1
+ import { z } from 'zod';
2
+ import { Invariant, type TInvariant } from './catalog-types';
3
+
4
+ /**
5
+ * Spec §5. Every one already known to break real consumers.
6
+ *
7
+ * The `statement` is the prose an agent applies; the `examples` are the part a
8
+ * weak model can pattern-match, so each `wrong` is a single-line literal the
9
+ * self-audit can grep for (enforced by invariants.test.ts, not by this comment).
10
+ *
11
+ * `enforcedBy` paths are REPO-relative; `lint` names a script in
12
+ * packages/ui/package.json. `kind: 'none'` is an honest coverage gap. Where a
13
+ * guard covers only part of a statement the record is `status: 'partial'` and
14
+ * the statement says which half is which — an invariant that overstates its own
15
+ * enforcement is worse than one that admits a gap, because a reader stops
16
+ * looking.
17
+ *
18
+ * EXAMPLES ARE CONSUMER CODE. Every `right` form must be runnable by someone who
19
+ * has only installed the package: no import that is not in the `exports` map.
20
+ * The kit's own guards (isSafeUrl, isRenderableLink) live in `src/primitives`,
21
+ * which ships compiled and unexported, so they are named as repo-internal
22
+ * guidance and never written as a consumer import.
23
+ */
24
+ export const invariants: TInvariant[] = [
25
+ {
26
+ id: 'reactivity-two-halves',
27
+ statement:
28
+ 'A new array reference NOTIFIES; a new object for each changed item makes the change VISIBLE. Editing an existing item needs both. Adds and removes need only the fresh array. Setting the same array back is a no-op even if an item inside it was swapped. The test pins how the KIT behaves — it will render stale unless both arrive — but nothing checks CONSUMER code, so this is a rule you apply, not a guarantee you will be warned about. Reorders follow the same rule as adds and removes; the test names reorders in its title but exercises only an add and a remove, so treat that half as reasoned rather than pinned.',
29
+ appliesTo: { tags: ['kai-chat', 'kai-conversations'] },
30
+ enforcedBy: { kind: 'test', paths: ['packages/ui/src/components/reactivity-contract.test.tsx'] },
31
+ status: 'enforced',
32
+ diagnosis: [
33
+ {
34
+ symptom: 'messages render once but never update while streaming',
35
+ cause: 'the same array reference is being set back; the element is never notified',
36
+ },
37
+ {
38
+ symptom: 'the list re-renders but an edited item shows stale content',
39
+ cause: 'the array is new but the item object identity is unchanged; the reference-keyed <For> keeps the old row',
40
+ },
41
+ ],
42
+ examples: [
43
+ {
44
+ wrong: "chat.messages.push({ id, role: 'user', parts: [{ type: 'text', text }] });",
45
+ right: "chat.messages = [...chat.messages, { id, role: 'user', parts: [{ type: 'text', text }] }];",
46
+ note: 'Mutating in place never notifies. Neither does assigning the same array reference back — the setter compares references.',
47
+ },
48
+ {
49
+ wrong: 'messages[last].parts.push(part);',
50
+ right: 'chat.messages = messages.map((m, i) => (i === last ? { ...m, parts: [...m.parts, part] } : m));',
51
+ note: 'This is the half that gets missed. A fresh array alone notifies, but the reference-keyed <For> keeps the old row until the EDITED ITEM is a new object too. createAssistantStream from @kitn.ai/ui/state already does both.',
52
+ },
53
+ ],
54
+ },
55
+ {
56
+ id: 'props-not-attributes',
57
+ statement:
58
+ "Set arrays, objects and functions as JS PROPERTIES on the element instance. Only scalars (strings, numbers, booleans) belong in attributes, and the derived layer's scalar flag records which prop is which. What actually goes wrong, because the mechanism is not the obvious one: a framework template binding or String() stringifies an array to '[object Object]', which is not JSON, and the attribute path falls back to handing the element that raw STRING — so the prop is silently a string and the list renders nothing. A function cannot survive JSON at all: JSON.stringify({ onSubmit }) is '{}', so every callback is dropped. And an attribute re-set is not how updates are delivered — see reactivity-two-halves. A hand-written, valid-JSON attribute does happen to parse today, because a transitive dependency JSON.parses attributes whose declared default is not a string, but that is that dependency's behaviour and not this kit's contract; do not build on it. NOTHING IN THIS REPO ENFORCES THIS — it is a consumer contract, and the scalar flag in the derived layer is how the catalog SERVES the fact, which is not the same as checking it.",
59
+ appliesTo: {},
60
+ // kind:'none' after measurement, replacing a `structural` pointer at
61
+ // define.tsx that did not contain the claimed mechanism. What define.tsx
62
+ // actually does is install non-reflecting accessors for the handful of props
63
+ // colliding with reflected global IDL attributes (role/hidden/autofocus), in
64
+ // the OPPOSITE direction: it stops property writes reflecting TO attributes.
65
+ // The attribute->property path lives in component-register, a transitive
66
+ // dependency, and measured in the real jsdom project against the real
67
+ // element it PARSES a JSON attribute onto the property rather than rejecting
68
+ // it. Nothing here checks consumer code for the contract; S1's scoring
69
+ // exercises a property update on kai-chat.messages, but no scenario names
70
+ // this invariant, so it is not claimed as a measurement either.
71
+ enforcedBy: { kind: 'none' },
72
+ status: 'open',
73
+ diagnosis: [
74
+ {
75
+ symptom: 'an element renders empty and the data looks right in devtools',
76
+ cause: "the property holds the STRING '[object Object]', not an array; it arrived through an attribute that was stringified rather than assigned",
77
+ },
78
+ {
79
+ symptom: 'everything renders but no callback ever fires',
80
+ cause: 'the object went through JSON into an attribute, and JSON drops functions silently',
81
+ },
82
+ ],
83
+ examples: [
84
+ {
85
+ wrong: "el.setAttribute('messages', String(messages));",
86
+ right: 'el.messages = messages;',
87
+ note: "Measured against the real element: the attribute becomes '[object Object]', JSON.parse fails, and the fallback leaves a STRING on the prop. messages is scalar:false in the derived layer; placeholder, loading and theme are scalar:true and do belong in attributes.",
88
+ },
89
+ {
90
+ wrong: "cards.setAttribute('policy', JSON.stringify({ onSubmit }));",
91
+ right: 'cards.policy = { onSubmit };',
92
+ note: "Measured: JSON.stringify({ onSubmit }) is '{}', so the handler is gone before the attribute is even set. No attribute can carry a function.",
93
+ },
94
+ ],
95
+ },
96
+ {
97
+ id: 'events-non-bubbling',
98
+ statement:
99
+ 'Non-bubbling is the default: public kai-* events are dispatched through the one helper that hard-codes bubbles:false and composed:false, so listen on the element itself, never on a parent or document. The protocol exceptions (kai-maximize-intent, kai-maximize-state and kai-card) bubble or compose deliberately and are listed in the derived layer under eventExceptions — do not generalise from them to the rest.',
100
+ appliesTo: {},
101
+ enforcedBy: { kind: 'structural', path: 'packages/ui/src/elements/define.tsx' },
102
+ status: 'enforced',
103
+ diagnosis: [
104
+ {
105
+ symptom: 'a delegated listener on document or a parent never fires',
106
+ cause: 'kai-* events do not bubble; attach the listener to the element that dispatches it',
107
+ },
108
+ {
109
+ symptom: 'a listener on the element works, but the same one on a wrapper div does not',
110
+ cause: 'same cause; only the three eventExceptions cross the element boundary',
111
+ },
112
+ ],
113
+ examples: [
114
+ {
115
+ wrong: "document.addEventListener('kai-submit', (e) => send(e.detail.value));",
116
+ right: "chat.addEventListener('kai-submit', (e) => send(e.detail.value));",
117
+ note: 'The dispatch helper in src/elements/define.tsx passes { bubbles: false, composed: false }, so nothing above the host ever sees the event.',
118
+ },
119
+ {
120
+ wrong: "wrapper.addEventListener('kai-message-action', handleAction);",
121
+ right: "chat.addEventListener('kai-message-action', handleAction);",
122
+ note: 'Delegating from a wrapper is the most common shape of this bug, because it is the habit every DOM framework teaches.',
123
+ },
124
+ ],
125
+ },
126
+ {
127
+ id: 'host-coordinates',
128
+ statement:
129
+ 'There is no store. Data flows in via properties, out via events, and the host wires element A to element B. Solid context does not cross element boundaries, so nothing coordinates elements except the host application, and no element owns another. Placing two elements in the same subtree wires nothing.',
130
+ appliesTo: {},
131
+ enforcedBy: { kind: 'none' },
132
+ status: 'open',
133
+ diagnosis: [
134
+ {
135
+ symptom: 'two elements are expected to sync but do not',
136
+ cause: 'nothing auto-coordinates; the host must listen on one element and set properties on the other',
137
+ },
138
+ {
139
+ symptom: 'a property assignment is silently ignored and the prop is not in the reference',
140
+ cause: 'the data was put on the element that displays the conversation rather than the one that owns the list',
141
+ },
142
+ ],
143
+ examples: [
144
+ {
145
+ wrong: 'chat.conversations = rows;',
146
+ right: 'conversations.conversations = rows;',
147
+ note: 'kai-chat has no conversations prop — the sidebar is its own element. One element never holds the whole app state.',
148
+ },
149
+ {
150
+ wrong: "chat.addEventListener('kai-conversation-select', (e) => load(e.detail.id));",
151
+ right:
152
+ "conversations.addEventListener('kai-conversation-select', (e) => { chat.messages = threadsById[e.detail.id]; });",
153
+ note: 'Event out of A, property into B, wired by the host. The event is dispatched by the element that owns the list, so that is where the listener goes.',
154
+ },
155
+ ],
156
+ },
157
+ {
158
+ id: 'untrusted-model-output',
159
+ statement:
160
+ "Everything the model produced is untrusted input: a MessagePart, card envelope or tool argument reaching innerHTML, an href or src, window.open or an iframe is a vulnerability. THE DEFECT IS NEVER A MISSING GUARD, IT IS WHICH PATH GOT IT — every one found so far sat on a path the CONSUMER controls while the model-controlled path beside it had none. So put a policy on the sink, and MATCH THE LIST TO THE SINK rather than reaching for one universal list: http:, https: and mailto: for anything navigable the user may click, resolved against the page so ordinary relative links still work; http: and https: ONLY for a model-supplied citation, which is a reference to a page on the public web and has no business being relative. Those are two lists because there are two sinks — it is the same split the kit makes internally between SAFE_SCHEMES and RENDERABLE_SCHEMES, not a variant invented here. Whichever you use, parse inside a try/catch and RETURN FALSE on an unparseable URL: new URL() throws, and a throw at a sink crashes the render. And render model text as TEXT. Escaping is the correct rendering: the source text must stay VISIBLE as well as inert, because a filter that deleted it would pass the security check and be a worse UI. COVERAGE, and read this before trusting CI here: the three XSS suites are tests and ONLY tests. They run in the required test job, so the vectors they pin cannot come back — but NOTHING structural stops a NEW sink landing unguarded. No lint script in the package is about sinks, and the coupling map's unenforced list has no entry for the class. A new sink is caught in review or not at all.",
161
+ appliesTo: {},
162
+ // WHAT THE THREE SUITES DO NOT CATCH: they pin the vectors that were FOUND
163
+ // (#246 markdown innerHTML, #247 the artifact's three URL sinks, and the
164
+ // hostile-stream path), so they are regression guards, not a guard over the
165
+ // CLASS. A newly written component that puts model text on a fresh
166
+ // unguarded sink adds no failing test anywhere. Verified against
167
+ // HANDOFF-2026-08-13 §13.2 ("They are tests and only tests... nothing
168
+ // structural stops a NEW sink landing unguarded"), and re-checked against
169
+ // the tree: none of packages/ui/scripts/lint-*.mjs concerns sinks, and
170
+ // docs/coupling-map.md has no row for the class. Kept as kind:'test'
171
+ // because the regression coverage is real; status is `partial` because the
172
+ // class is not covered, and the statement carries the gap.
173
+ enforcedBy: {
174
+ kind: 'test',
175
+ paths: [
176
+ 'packages/ui/tests/components/markdown-xss.test.tsx',
177
+ 'packages/ui/tests/components/artifact-url-xss.test.tsx',
178
+ 'packages/ui/tests/components/hostile-model-output.test.tsx',
179
+ ],
180
+ },
181
+ status: 'partial',
182
+ diagnosis: [
183
+ {
184
+ symptom: 'a custom renderer for a tool result or a card body executes markup the model emitted',
185
+ cause: 'model text reached innerHTML on a hand-written path; the guarded path is the one every real message flows through, and this one bypassed it',
186
+ },
187
+ {
188
+ symptom: 'a citation or card link navigates to javascript: or data:',
189
+ cause: 'a model-supplied URL reached an href, a src or window.open with no scheme check',
190
+ },
191
+ ],
192
+ examples: [
193
+ {
194
+ wrong: 'el.innerHTML = part.text;',
195
+ right: 'el.textContent = part.text;',
196
+ note: 'For rich text render the part through <kai-markdown>, which escapes rather than sanitizes (src/components/markdown.tsx) and filters link and image URLs. Never hand-roll a second markdown-to-innerHTML path.',
197
+ },
198
+ {
199
+ wrong: "window.open(card.url, '_blank');",
200
+ right:
201
+ "const isNavigable = (u) => { try { return ['http:', 'https:', 'mailto:'].includes(new URL(u, location.href).protocol); } catch { return false; } };\nif (isNavigable(card.url)) window.open(card.url, '_blank', 'noopener,noreferrer');",
202
+ note: "THE try/catch IS NOT OPTIONAL: new URL() THROWS on an unparseable input like 'http://[', and an uncaught throw here crashes the render instead of blocking the link — worse than the bug you are fixing. Returning false is the whole contract. Resolving against location.href is deliberate: a relative or fragment href inherits http: and is allowed, which is what makes ordinary links keep working. REPO-INTERNAL ONLY: contributors inside this package use isSafeUrl/SAFE_SCHEMES from src/primitives/card-routing.ts, which has this exact shape — neither is reachable from the published package, so never emit an import for them.",
203
+ },
204
+ {
205
+ wrong: '<a href={source.url}>{source.title}</a>',
206
+ right:
207
+ 'const isCitationUrl = (u) => { try { return [\'http:\', \'https:\'].includes(new URL(u).protocol); } catch { return false; } };\n{isCitationUrl(source.url) ? <a href={source.url} rel="noopener noreferrer">{source.title}</a> : <span>{source.title}</span>}',
208
+ note: "No base here, unlike the navigable case: a model-supplied citation is a reference to a page on the public web, so a relative path is not a citation and returns false. It must RETURN false, not throw — a throw escapes the ternary and the fallback never renders, which is exactly the deleted-text failure this invariant's own statement forbids. The <span> keeps the title VISIBLE. REPO-INTERNAL equivalent: isRenderableLink in src/primitives/link-preview.ts, also not exported.",
209
+ },
210
+ ],
211
+ },
212
+ {
213
+ id: 'kit-parses-consumer-fetches',
214
+ statement:
215
+ "The kit parses; the consumer fetches. Two halves, covered differently. KIT SIDE: every MessagePart variant the wire encodes must be accounted for, or a variant is gone once the request leaves — that half is enforced by lint:silent-drops in CI. CONSUMER SIDE: never hand-roll an SSE reader; import readOpenAIStream, readAnthropicStream or readModelStream from @kitn.ai/ui/wire, and fetch from your own endpoint, because there is no client, no key handling and no provider SDK below wire/. NO CI CHECK COVERS THAT SECOND HALF — no guard reads consumer or scaffolded code for a hand-rolled reader; it is measured by the acceptance deck instead, at scenario S2, whose scoring line is 'imports readOpenAIStream from @kitn.ai/ui/wire; no hand-rolled SSE reader anywhere in the output'.",
216
+ appliesTo: {},
217
+ // WHAT lint:silent-drops DOES NOT CATCH: it analyzes src/wire, so it covers
218
+ // the kit-side half only. A consumer or a scaffold that hand-rolls its own
219
+ // SSE loop never enters its scan, and the lint stays green. That gap is
220
+ // stated in the statement above so no reader concludes CI catches it, and
221
+ // scenario S2 is what actually measures it. Recorded rather than closed:
222
+ // flipping this to kind:'none' would discard a real guard over a real half,
223
+ // which is what `status: 'partial'` exists to express.
224
+ enforcedBy: { kind: 'lint', script: 'lint:silent-drops' },
225
+ status: 'partial',
226
+ diagnosis: [
227
+ {
228
+ symptom: 'streaming works for one provider and silently drops parts for another',
229
+ cause: 'a hand-rolled reader misses part variants the wire layer already handles; replace it with the wire import',
230
+ },
231
+ {
232
+ symptom: 'tokens arrive glued together, or a multibyte character renders as garbage mid-stream',
233
+ cause: 'a hand-rolled reader split on a data: prefix and assumed one frame per chunk; keep-alive comments, multi-line frames and codepoints split across a socket boundary are all real',
234
+ },
235
+ ],
236
+ examples: [
237
+ {
238
+ wrong: "text += JSON.parse(line.replace('data: ', '')).choices[0].delta.content;",
239
+ right: 'const turn = await readOpenAIStream(res, stream);',
240
+ note: "import { readOpenAIStream } from '@kitn.ai/ui/wire'; createAssistantStream from '@kitn.ai/ui/state' owns the message, the reader fills it. readAnthropicStream and readModelStream are the other two entry points.",
241
+ },
242
+ {
243
+ wrong: "await fetch('https://api.openai.com/v1/chat/completions', { headers: { Authorization: 'Bearer ' + apiKey } });",
244
+ right:
245
+ "await fetch('/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ messages: toOpenAIMessages(history) }) });",
246
+ note: 'The consumer fetches, from their own endpoint. A provider key in the browser is a leaked key; toOpenAIMessages/toAnthropicMessages encode the thread for the wire.',
247
+ },
248
+ ],
249
+ },
250
+ {
251
+ id: 'upgrade-race',
252
+ statement:
253
+ 'A property set before the element upgrades is lost. On script-tag targets, load order is not ours. Until issue #99 option B (upgrade-property preservation in defineWebComponent) lands, every script-tag recipe must state this race loudly and set properties only after registration — await customElements.whenDefined(tag), which is the guarantee. A timer, or DOMContentLoaded, is a guess about load order rather than a guarantee: registration can land later, from an async chunk or a dynamically inserted script.',
254
+ appliesTo: { targets: ['script-tag'] },
255
+ enforcedBy: { kind: 'none', until: 'issue #99 option B lands in defineWebComponent' },
256
+ status: 'open',
257
+ diagnosis: [
258
+ {
259
+ symptom: 'properties set in inline script are ignored on a CDN page',
260
+ cause: 'the element had not upgraded yet; the set landed on a plain HTMLElement and was lost',
261
+ },
262
+ {
263
+ symptom: 'the same code works under a bundler and not from a script tag',
264
+ cause: 'the bundler happened to order the registration first; a script tag gives no such guarantee',
265
+ },
266
+ ],
267
+ examples: [
268
+ {
269
+ wrong: 'setTimeout(() => { chat.messages = messages; }, 0);',
270
+ right: "customElements.whenDefined('kai-chat').then(() => { chat.messages = messages; });",
271
+ note: 'A timer bets on load order. whenDefined resolves when the registry actually has the tag, which is the thing you need to be true.',
272
+ },
273
+ {
274
+ wrong: "document.addEventListener('DOMContentLoaded', () => { chat.messages = messages; });",
275
+ right: "customElements.whenDefined('kai-chat').then(() => { chat.messages = messages; });",
276
+ note: 'DOMContentLoaded is about the parser, not the registry. It says the markup is there, never that the element behind the tag has been defined.',
277
+ },
278
+ ],
279
+ },
280
+ ];
281
+
282
+ export function listInvariants(): TInvariant[] {
283
+ return z.array(Invariant).parse(invariants);
284
+ }