@kitn.ai/ui 0.14.1

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 (370) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +394 -0
  3. package/dist/bash-InADTalH.js +1 -0
  4. package/dist/core-AYMC6_lb.js +12 -0
  5. package/dist/css-M7EaDHN_.js +1 -0
  6. package/dist/custom-elements.json +4739 -0
  7. package/dist/engine-javascript-vq0WuIJl.js +141 -0
  8. package/dist/github-dark-dimmed-DUshB20C.js +1 -0
  9. package/dist/github-light-JYsPkUQd.js +1 -0
  10. package/dist/host-embed.d.ts +33 -0
  11. package/dist/html-CPZ3oZQ7.js +1 -0
  12. package/dist/index.d.ts +2 -0
  13. package/dist/javascript-C25yR2R2.js +1 -0
  14. package/dist/json-DxJze_jm.js +1 -0
  15. package/dist/kitn-chat-provider.d.ts +2 -0
  16. package/dist/kitn-chat-provider.es.js +240 -0
  17. package/dist/kitn-chat.es.js +77 -0
  18. package/dist/llms/llms-full.txt +1018 -0
  19. package/dist/llms/llms.txt +104 -0
  20. package/dist/origin.d.ts +5 -0
  21. package/dist/provider-runtime.d.ts +27 -0
  22. package/dist/schemas/card-envelope.schema.json +14 -0
  23. package/dist/schemas/card-event.schema.json +12 -0
  24. package/dist/schemas/choice.schema.json +70 -0
  25. package/dist/schemas/confirm.schema.json +65 -0
  26. package/dist/schemas/embed.schema.json +65 -0
  27. package/dist/schemas/form.result.schema.json +7 -0
  28. package/dist/schemas/form.schema.json +33 -0
  29. package/dist/schemas/link.schema.json +56 -0
  30. package/dist/schemas/tasks.result.schema.json +16 -0
  31. package/dist/schemas/tasks.schema.json +78 -0
  32. package/dist/svelte--5p79yCD.js +1 -0
  33. package/dist/theme.tokens.css +176 -0
  34. package/dist/tsx-B8rCNbgL.js +1 -0
  35. package/dist/typescript-RycA9KXf.js +1 -0
  36. package/dist/validate.d.ts +4 -0
  37. package/dist/version.d.ts +4 -0
  38. package/dist/vue-BmIZj4XD.js +1 -0
  39. package/dist/wire.d.ts +53 -0
  40. package/frameworks/react/index.tsx +948 -0
  41. package/frameworks/react/runtime.tsx +94 -0
  42. package/llms-full.txt +1018 -0
  43. package/llms.txt +104 -0
  44. package/package.json +141 -0
  45. package/src/components/artifact.stories.tsx +138 -0
  46. package/src/components/artifact.tsx +743 -0
  47. package/src/components/attachments.stories.tsx +303 -0
  48. package/src/components/attachments.test.tsx +64 -0
  49. package/src/components/attachments.tsx +399 -0
  50. package/src/components/card-fallback.tsx +28 -0
  51. package/src/components/card-renderer.tsx +52 -0
  52. package/src/components/card.tsx +110 -0
  53. package/src/components/chain-of-thought.stories.tsx +211 -0
  54. package/src/components/chain-of-thought.tsx +139 -0
  55. package/src/components/chat-container.stories.tsx +187 -0
  56. package/src/components/chat-container.tsx +82 -0
  57. package/src/components/chat-scope-picker.tsx +47 -0
  58. package/src/components/chat-thread.test.tsx +58 -0
  59. package/src/components/chat-thread.tsx +249 -0
  60. package/src/components/checkpoint.stories.tsx +102 -0
  61. package/src/components/checkpoint.tsx +88 -0
  62. package/src/components/choice-card.tsx +569 -0
  63. package/src/components/code-block.stories.tsx +150 -0
  64. package/src/components/code-block.tsx +105 -0
  65. package/src/components/component-meta.json +3735 -0
  66. package/src/components/confirm-card.stories.tsx +74 -0
  67. package/src/components/confirm-card.tsx +315 -0
  68. package/src/components/context.stories.tsx +229 -0
  69. package/src/components/context.test.tsx +133 -0
  70. package/src/components/context.tsx +363 -0
  71. package/src/components/conversation-item.stories.tsx +125 -0
  72. package/src/components/conversation-item.tsx +16 -0
  73. package/src/components/conversation-list.stories.tsx +133 -0
  74. package/src/components/conversation-list.tsx +101 -0
  75. package/src/components/default-input.test.tsx +111 -0
  76. package/src/components/embed.tsx +196 -0
  77. package/src/components/empty.stories.tsx +434 -0
  78. package/src/components/empty.tsx +166 -0
  79. package/src/components/feedback-bar.stories.tsx +138 -0
  80. package/src/components/feedback-bar.test.tsx +103 -0
  81. package/src/components/feedback-bar.tsx +166 -0
  82. package/src/components/file-tree.stories.tsx +73 -0
  83. package/src/components/file-tree.tsx +383 -0
  84. package/src/components/file-upload.stories.tsx +156 -0
  85. package/src/components/file-upload.tsx +161 -0
  86. package/src/components/form-widgets.tsx +461 -0
  87. package/src/components/form.tsx +873 -0
  88. package/src/components/image.stories.tsx +89 -0
  89. package/src/components/image.tsx +67 -0
  90. package/src/components/link-preview.tsx +194 -0
  91. package/src/components/loader.stories.tsx +181 -0
  92. package/src/components/loader.tsx +333 -0
  93. package/src/components/markdown.stories.tsx +180 -0
  94. package/src/components/markdown.tsx +81 -0
  95. package/src/components/message-action-bar.test.tsx +128 -0
  96. package/src/components/message-narrow.stories.tsx +329 -0
  97. package/src/components/message-skills.stories.tsx +211 -0
  98. package/src/components/message-skills.tsx +36 -0
  99. package/src/components/message.stories.tsx +281 -0
  100. package/src/components/message.tsx +227 -0
  101. package/src/components/model-switcher.stories.tsx +123 -0
  102. package/src/components/model-switcher.test.tsx +65 -0
  103. package/src/components/model-switcher.tsx +87 -0
  104. package/src/components/prompt-input.stories.tsx +222 -0
  105. package/src/components/prompt-input.tsx +211 -0
  106. package/src/components/prompt-suggestion.stories.tsx +142 -0
  107. package/src/components/prompt-suggestion.tsx +115 -0
  108. package/src/components/reasoning.stories.tsx +140 -0
  109. package/src/components/reasoning.tsx +156 -0
  110. package/src/components/resizable.test.tsx +226 -0
  111. package/src/components/response-stream.tsx +103 -0
  112. package/src/components/scroll-button.stories.tsx +100 -0
  113. package/src/components/scroll-button.tsx +34 -0
  114. package/src/components/slash-command.stories.tsx +163 -0
  115. package/src/components/slash-command.tsx +232 -0
  116. package/src/components/source-list.test.tsx +152 -0
  117. package/src/components/source.stories.tsx +124 -0
  118. package/src/components/source.tsx +129 -0
  119. package/src/components/tasks-card.stories.tsx +78 -0
  120. package/src/components/tasks-card.tsx +432 -0
  121. package/src/components/text-shimmer.stories.tsx +87 -0
  122. package/src/components/text-shimmer.tsx +37 -0
  123. package/src/components/thinking-bar.stories.tsx +87 -0
  124. package/src/components/thinking-bar.tsx +50 -0
  125. package/src/components/tool.stories.tsx +153 -0
  126. package/src/components/tool.tsx +184 -0
  127. package/src/components/use-card-resolution.ts +28 -0
  128. package/src/components/voice-input.stories.tsx +83 -0
  129. package/src/components/voice-input.tsx +107 -0
  130. package/src/elements/artifact.stories.tsx +506 -0
  131. package/src/elements/artifact.tsx +137 -0
  132. package/src/elements/attachments.stories.tsx +190 -0
  133. package/src/elements/attachments.tsx +136 -0
  134. package/src/elements/card.stories.tsx +113 -0
  135. package/src/elements/card.tsx +40 -0
  136. package/src/elements/cards.stories.tsx +59 -0
  137. package/src/elements/cards.tsx +97 -0
  138. package/src/elements/catalog.stories.tsx +491 -0
  139. package/src/elements/chain-of-thought.declarative.test.tsx +153 -0
  140. package/src/elements/chain-of-thought.stories.tsx +113 -0
  141. package/src/elements/chain-of-thought.tsx +92 -0
  142. package/src/elements/chat-scope-picker.stories.tsx +73 -0
  143. package/src/elements/chat-scope-picker.tsx +36 -0
  144. package/src/elements/chat-types.ts +42 -0
  145. package/src/elements/chat-workspace.stories.tsx +288 -0
  146. package/src/elements/chat-workspace.tsx +161 -0
  147. package/src/elements/chat.stories.tsx +151 -0
  148. package/src/elements/chat.tsx +80 -0
  149. package/src/elements/checkpoint.stories.tsx +72 -0
  150. package/src/elements/checkpoint.tsx +47 -0
  151. package/src/elements/choice.stories.tsx +240 -0
  152. package/src/elements/choice.tsx +48 -0
  153. package/src/elements/code-block.stories.tsx +83 -0
  154. package/src/elements/code-block.tsx +42 -0
  155. package/src/elements/compiled.css +2 -0
  156. package/src/elements/composed-shell.stories.tsx +316 -0
  157. package/src/elements/confirm-card.stories.tsx +227 -0
  158. package/src/elements/confirm-card.tsx +51 -0
  159. package/src/elements/context-meter.stories.tsx +151 -0
  160. package/src/elements/context-meter.tsx +121 -0
  161. package/src/elements/conversation-list.declarative.test.tsx +137 -0
  162. package/src/elements/conversation-list.stories.tsx +243 -0
  163. package/src/elements/conversation-list.tsx +80 -0
  164. package/src/elements/css.ts +5 -0
  165. package/src/elements/default-input.tsx +247 -0
  166. package/src/elements/define.tsx +171 -0
  167. package/src/elements/element-meta.json +3314 -0
  168. package/src/elements/element-types.d.ts +710 -0
  169. package/src/elements/embed.stories.tsx +213 -0
  170. package/src/elements/embed.tsx +35 -0
  171. package/src/elements/empty.stories.tsx +111 -0
  172. package/src/elements/empty.tsx +29 -0
  173. package/src/elements/feedback-bar.stories.tsx +114 -0
  174. package/src/elements/feedback-bar.tsx +61 -0
  175. package/src/elements/file-tree.stories.tsx +134 -0
  176. package/src/elements/file-tree.tsx +52 -0
  177. package/src/elements/file-upload.stories.tsx +82 -0
  178. package/src/elements/file-upload.tsx +44 -0
  179. package/src/elements/form.stories.tsx +248 -0
  180. package/src/elements/form.tsx +43 -0
  181. package/src/elements/framework-usage.json +563 -0
  182. package/src/elements/image.stories.tsx +71 -0
  183. package/src/elements/image.tsx +32 -0
  184. package/src/elements/link-preview.stories.tsx +209 -0
  185. package/src/elements/link-preview.tsx +34 -0
  186. package/src/elements/loader.stories.tsx +88 -0
  187. package/src/elements/loader.tsx +25 -0
  188. package/src/elements/markdown.stories.tsx +76 -0
  189. package/src/elements/markdown.tsx +38 -0
  190. package/src/elements/message-skills.declarative.test.tsx +104 -0
  191. package/src/elements/message-skills.stories.tsx +114 -0
  192. package/src/elements/message-skills.tsx +85 -0
  193. package/src/elements/message.stories.tsx +218 -0
  194. package/src/elements/message.tsx +162 -0
  195. package/src/elements/model-switcher.declarative.test.tsx +130 -0
  196. package/src/elements/model-switcher.stories.tsx +198 -0
  197. package/src/elements/model-switcher.tsx +93 -0
  198. package/src/elements/popover.stories.tsx +80 -0
  199. package/src/elements/popover.tsx +59 -0
  200. package/src/elements/prompt-input-slash-command.test.tsx +128 -0
  201. package/src/elements/prompt-input-stoppable.test.tsx +88 -0
  202. package/src/elements/prompt-input-toolbar-actions.test.tsx +120 -0
  203. package/src/elements/prompt-input.stories.tsx +331 -0
  204. package/src/elements/prompt-input.tsx +185 -0
  205. package/src/elements/prompt-suggestions.declarative.test.tsx +164 -0
  206. package/src/elements/prompt-suggestions.stories.tsx +213 -0
  207. package/src/elements/prompt-suggestions.tsx +94 -0
  208. package/src/elements/reasoning.stories.tsx +77 -0
  209. package/src/elements/reasoning.tsx +50 -0
  210. package/src/elements/register.ts +56 -0
  211. package/src/elements/remote.stories.tsx +314 -0
  212. package/src/elements/remote.tsx +221 -0
  213. package/src/elements/resizable.d.ts +27 -0
  214. package/src/elements/resizable.stories.tsx +426 -0
  215. package/src/elements/resizable.tsx +553 -0
  216. package/src/elements/response-stream.stories.tsx +79 -0
  217. package/src/elements/response-stream.tsx +40 -0
  218. package/src/elements/scroll-button.stories.tsx +202 -0
  219. package/src/elements/scroll-button.test.tsx +148 -0
  220. package/src/elements/scroll-button.tsx +126 -0
  221. package/src/elements/source-list.stories.tsx +143 -0
  222. package/src/elements/source.stories.tsx +88 -0
  223. package/src/elements/source.tsx +120 -0
  224. package/src/elements/styles.css +199 -0
  225. package/src/elements/switch.stories.tsx +56 -0
  226. package/src/elements/switch.tsx +43 -0
  227. package/src/elements/tasks.stories.tsx +237 -0
  228. package/src/elements/tasks.tsx +46 -0
  229. package/src/elements/text-shimmer.stories.tsx +64 -0
  230. package/src/elements/text-shimmer.tsx +28 -0
  231. package/src/elements/thinking-bar.stories.tsx +72 -0
  232. package/src/elements/thinking-bar.tsx +34 -0
  233. package/src/elements/tool.stories.tsx +89 -0
  234. package/src/elements/tool.tsx +23 -0
  235. package/src/elements/voice-input.stories.tsx +88 -0
  236. package/src/elements/voice-input.tsx +41 -0
  237. package/src/index.ts +253 -0
  238. package/src/primitives/card-contract.ts +71 -0
  239. package/src/primitives/card-host.tsx +35 -0
  240. package/src/primitives/card-registry.tsx +67 -0
  241. package/src/primitives/card-resolution.ts +36 -0
  242. package/src/primitives/card-routing.ts +79 -0
  243. package/src/primitives/card-schemas/card-envelope.schema.json +14 -0
  244. package/src/primitives/card-schemas/card-event.schema.json +12 -0
  245. package/src/primitives/card-schemas/choice.schema.json +70 -0
  246. package/src/primitives/card-schemas/confirm.schema.json +65 -0
  247. package/src/primitives/card-schemas/embed.schema.json +65 -0
  248. package/src/primitives/card-schemas/form.result.schema.json +7 -0
  249. package/src/primitives/card-schemas/form.schema.json +33 -0
  250. package/src/primitives/card-schemas/link.schema.json +56 -0
  251. package/src/primitives/card-schemas/tasks.result.schema.json +16 -0
  252. package/src/primitives/card-schemas/tasks.schema.json +78 -0
  253. package/src/primitives/card-validate.ts +95 -0
  254. package/src/primitives/chat-config.tsx +76 -0
  255. package/src/primitives/controllable.test.ts +47 -0
  256. package/src/primitives/controllable.ts +21 -0
  257. package/src/primitives/embed-providers.ts +254 -0
  258. package/src/primitives/highlighter.ts +157 -0
  259. package/src/primitives/link-preview.ts +87 -0
  260. package/src/primitives/pdf-preview.ts +121 -0
  261. package/src/primitives/use-auto-resize.ts +31 -0
  262. package/src/primitives/use-resize-observer.ts +12 -0
  263. package/src/primitives/use-stick-to-bottom.ts +43 -0
  264. package/src/primitives/use-text-stream.ts +112 -0
  265. package/src/primitives/use-voice-recorder.ts +50 -0
  266. package/src/remote/host-embed.ts +345 -0
  267. package/src/remote/index.ts +2 -0
  268. package/src/remote/origin.ts +30 -0
  269. package/src/remote/provider-runtime.ts +259 -0
  270. package/src/remote/provider.ts +2 -0
  271. package/src/remote/validate.ts +22 -0
  272. package/src/remote/version.ts +12 -0
  273. package/src/remote/wire.ts +48 -0
  274. package/src/stories/chat-panel-layout.stories.tsx +145 -0
  275. package/src/stories/chat-scene.tsx +571 -0
  276. package/src/stories/checkpoint-restore.stories.tsx +256 -0
  277. package/src/stories/context-usage.stories.tsx +212 -0
  278. package/src/stories/conversation-with-reasoning.stories.tsx +182 -0
  279. package/src/stories/conversation-with-sources.stories.tsx +471 -0
  280. package/src/stories/docs/Accessibility.mdx +119 -0
  281. package/src/stories/docs/ChildElements.mdx +269 -0
  282. package/src/stories/docs/ForAIAgents.mdx +162 -0
  283. package/src/stories/docs/GettingStarted.mdx +65 -0
  284. package/src/stories/docs/Installation.mdx +83 -0
  285. package/src/stories/docs/Introduction.mdx +50 -0
  286. package/src/stories/docs/SolidJsAdvanced.mdx +17 -0
  287. package/src/stories/docs/Theming.mdx +85 -0
  288. package/src/stories/docs/element-controls.ts +121 -0
  289. package/src/stories/docs/frameworks/Angular.mdx +245 -0
  290. package/src/stories/docs/frameworks/Html.mdx +227 -0
  291. package/src/stories/docs/frameworks/Overview.mdx +47 -0
  292. package/src/stories/docs/frameworks/React.mdx +227 -0
  293. package/src/stories/docs/frameworks/Solid.mdx +173 -0
  294. package/src/stories/docs/frameworks/Svelte.mdx +194 -0
  295. package/src/stories/docs/frameworks/Vue.mdx +238 -0
  296. package/src/stories/docs/generative-ui-overview.mdx +186 -0
  297. package/src/stories/docs/recipes/SpeechToText.mdx +7 -0
  298. package/src/stories/docs/recipes/Streaming.mdx +68 -0
  299. package/src/stories/docs/recipes/TextToSpeech.mdx +35 -0
  300. package/src/stories/docs/theme-editor/canvas.tsx +32 -0
  301. package/src/stories/docs/theme-editor/inspector.tsx +66 -0
  302. package/src/stories/docs/theme-editor/presets.test.ts +32 -0
  303. package/src/stories/docs/theme-editor/presets.ts +64 -0
  304. package/src/stories/docs/theme-editor/theme-css.test.ts +19 -0
  305. package/src/stories/docs/theme-editor/theme-css.ts +15 -0
  306. package/src/stories/docs/theme-editor/theme-editor.tsx +146 -0
  307. package/src/stories/docs/theme-tokens.tsx +174 -0
  308. package/src/stories/examples/ChoosingComponents.mdx +106 -0
  309. package/src/stories/examples/sample-data.ts +79 -0
  310. package/src/stories/examples/usage/checkpoint-restore.ts +156 -0
  311. package/src/stories/examples/usage/context-usage.ts +591 -0
  312. package/src/stories/examples/usage/conversation-with-reasoning.ts +224 -0
  313. package/src/stories/examples/usage/conversation-with-sources.ts +589 -0
  314. package/src/stories/examples/usage/empty-state.ts +144 -0
  315. package/src/stories/examples/usage/full-chat-app.ts +277 -0
  316. package/src/stories/examples/usage/index.ts +63 -0
  317. package/src/stories/examples/usage/message-actions.ts +704 -0
  318. package/src/stories/examples/usage/pattern-centered-conversation.ts +446 -0
  319. package/src/stories/examples/usage/pattern-chat-panel-layout.ts +442 -0
  320. package/src/stories/examples/usage/pattern-docked-widget.ts +479 -0
  321. package/src/stories/examples/usage/prompt-input-variants.ts +1356 -0
  322. package/src/stories/examples/usage/streaming-response.ts +687 -0
  323. package/src/stories/examples/usage/types.ts +33 -0
  324. package/src/stories/full-chat.stories.tsx +18 -0
  325. package/src/stories/message-actions.stories.tsx +230 -0
  326. package/src/stories/pattern-centered-conversation.stories.tsx +93 -0
  327. package/src/stories/pattern-docked-widget.stories.tsx +93 -0
  328. package/src/stories/pattern-empty-state.stories.tsx +76 -0
  329. package/src/stories/prompt-input-variants.stories.tsx +456 -0
  330. package/src/stories/streaming-response.stories.tsx +438 -0
  331. package/src/stories/theme-editor.stories.tsx +16 -0
  332. package/src/stories/token-reference.stories.tsx +18 -0
  333. package/src/stories/typography.stories.tsx +78 -0
  334. package/src/types.ts +47 -0
  335. package/src/ui/action-icons.ts +52 -0
  336. package/src/ui/avatar.stories.tsx +103 -0
  337. package/src/ui/avatar.tsx +23 -0
  338. package/src/ui/badge.stories.tsx +86 -0
  339. package/src/ui/badge.tsx +21 -0
  340. package/src/ui/button.stories.tsx +145 -0
  341. package/src/ui/button.tsx +38 -0
  342. package/src/ui/collapsible.stories.tsx +69 -0
  343. package/src/ui/collapsible.tsx +125 -0
  344. package/src/ui/dropdown.stories.tsx +59 -0
  345. package/src/ui/dropdown.tsx +196 -0
  346. package/src/ui/hover-card.stories.tsx +77 -0
  347. package/src/ui/hover-card.test.tsx +30 -0
  348. package/src/ui/hover-card.tsx +172 -0
  349. package/src/ui/overlay.stories.tsx +115 -0
  350. package/src/ui/overlay.tsx +158 -0
  351. package/src/ui/popover.stories.tsx +81 -0
  352. package/src/ui/popover.test.tsx +99 -0
  353. package/src/ui/popover.tsx +92 -0
  354. package/src/ui/resizable.stories.tsx +236 -0
  355. package/src/ui/resizable.tsx +576 -0
  356. package/src/ui/scroll-area.stories.tsx +50 -0
  357. package/src/ui/scroll-area.tsx +15 -0
  358. package/src/ui/separator.stories.tsx +81 -0
  359. package/src/ui/separator.tsx +10 -0
  360. package/src/ui/skeleton.stories.tsx +337 -0
  361. package/src/ui/skeleton.tsx +16 -0
  362. package/src/ui/switch.stories.tsx +51 -0
  363. package/src/ui/switch.test.tsx +59 -0
  364. package/src/ui/switch.tsx +62 -0
  365. package/src/ui/textarea.stories.tsx +76 -0
  366. package/src/ui/textarea.tsx +21 -0
  367. package/src/ui/tooltip.stories.tsx +74 -0
  368. package/src/ui/tooltip.tsx +68 -0
  369. package/src/utils/cn.ts +24 -0
  370. package/theme.css +187 -0
@@ -0,0 +1,88 @@
1
+ import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
+ import { onMount } from 'solid-js';
3
+ import './register'; // side effect: registers the custom elements
4
+ import { argTypesFor, specDescription } from '../stories/docs/element-controls';
5
+
6
+ // The web components are custom DOM elements, so declare the tags for JSX.
7
+ declare module 'solid-js' {
8
+ // eslint-disable-next-line @typescript-eslint/no-namespace
9
+ namespace JSX {
10
+ interface IntrinsicElements {
11
+ 'kai-voice-input': JSX.HTMLAttributes<HTMLElement> & {
12
+ disabled?: boolean | string;
13
+ };
14
+ }
15
+ }
16
+ }
17
+
18
+ /** Render `<kai-voice-input>` with a stub `transcribe` function-property. */
19
+ function VoiceElement(props: { disabled?: boolean }) {
20
+ let el: (HTMLElement & { transcribe?: (audio: Blob) => Promise<string> }) | undefined;
21
+ onMount(() => {
22
+ if (!el) return;
23
+ // transcribe MUST be set as a JS property — a value-returning callback
24
+ // can't be modelled as an attribute.
25
+ el.transcribe = async () => {
26
+ await new Promise((r) => setTimeout(r, 400));
27
+ return 'transcribed text';
28
+ };
29
+ el.addEventListener('kai-transcription', (e) => {
30
+ const ev = e as CustomEvent<{ text: string }>;
31
+ console.log('kai-transcription', ev.detail.text);
32
+ });
33
+ });
34
+ return (
35
+ <kai-voice-input
36
+ ref={(e) => (el = e as HTMLElement)}
37
+ disabled={props.disabled ? true : undefined}
38
+ style={{ display: 'inline-block', padding: '40px' }}
39
+ />
40
+ );
41
+ }
42
+
43
+ const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
44
+ <kai-voice-input id="voice"></kai-voice-input>
45
+
46
+ <script type="module">
47
+ import '@kitn.ai/ui/elements'; // registers the custom elements
48
+
49
+ const voice = document.getElementById('voice');
50
+ // transcribe is a FUNCTION property — your async transcriber
51
+ voice.transcribe = async (blob) => {
52
+ const text = await myTranscriptionApi(blob);
53
+ return text;
54
+ };
55
+ voice.addEventListener('kai-transcription', (e) => console.log(e.detail.text));
56
+ </script>`;
57
+
58
+ const meta = {
59
+ title: 'Components/VoiceInput',
60
+ tags: ['autodocs'],
61
+ argTypes: argTypesFor('kai-voice-input'),
62
+ parameters: {
63
+ layout: 'fullscreen',
64
+ docs: {
65
+ description: specDescription('kai-voice-input', [
66
+ '`<kai-voice-input>` is the framework-agnostic **web component** for a mic button that records and transcribes audio — isolated in **Shadow DOM**. It is the canonical **function-property** element.',
67
+ '**When to use:** adding voice dictation to an input in a non-Solid app. In SolidJS, use the `VoiceInput` primitive.',
68
+ '**Placement:** inline beside the prompt input field or as an icon button in the composer toolbar; renders as an inline-block sized to the mic button.',
69
+ "**How to use:** register once with `import '@kitn.ai/ui/elements'`, then set the `transcribe` **function property** (`el.transcribe = async blob => '...'`) — a value-returning callback can't be modelled as an event. It also emits `kai-audio-captured` (raw blob) and `kai-transcription` (text) **CustomEvents**.",
70
+ 'See the **Code** tab for HTML usage.',
71
+ ]),
72
+ },
73
+ },
74
+ } satisfies Meta;
75
+
76
+ export default meta;
77
+ type Story = StoryObj;
78
+
79
+ /** A working mic button wired to a stub transcriber. */
80
+ export const Default: Story = {
81
+ render: () => <VoiceElement />,
82
+ parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
83
+ };
84
+
85
+ /** A disabled mic button (non-interactive). */
86
+ export const Disabled: Story = {
87
+ render: () => <VoiceElement disabled />,
88
+ };
@@ -0,0 +1,41 @@
1
+ import { defineWebComponent } from './define';
2
+ import { VoiceInput } from '../components/voice-input';
3
+
4
+ interface Props extends Record<string, unknown> {
5
+ /**
6
+ * Transcriber the host supplies — records audio, returns the text. This is a
7
+ * **function-valued property** (`el.transcribe = async blob => '...'`) because
8
+ * a value-returning callback can't be modelled as a fire-and-forget event.
9
+ */
10
+ transcribe?: (audio: Blob) => Promise<string>;
11
+ /** Disable the mic button (non-interactive). */
12
+ disabled?: boolean;
13
+ }
14
+
15
+ /** Events fired by `<kai-voice-input>`. */
16
+ interface Events {
17
+ /** Raw audio captured (before transcription) — for hosts that prefer to
18
+ * handle transcription themselves instead of via the `transcribe` property. */
19
+ 'kai-audio-captured': { blob: Blob };
20
+ /** Transcription completed (the `transcribe` property resolved). */
21
+ 'kai-transcription': { text: string };
22
+ }
23
+
24
+ /**
25
+ * `<kai-voice-input>` — a mic button that records and transcribes. The
26
+ * canonical **function-property** element: set `el.transcribe` to your async
27
+ * transcriber. Also emits `kai-audio-captured` (raw blob) and `kai-transcription` (text).
28
+ */
29
+ defineWebComponent<Props, Events>('kai-voice-input', {
30
+ transcribe: undefined,
31
+ disabled: false,
32
+ }, (props, { dispatch, flag }) => (
33
+ <VoiceInput
34
+ disabled={flag('disabled')}
35
+ onTranscribe={async (blob) => {
36
+ dispatch('kai-audio-captured', { blob });
37
+ return props.transcribe ? props.transcribe(blob) : '';
38
+ }}
39
+ onTranscription={(text) => dispatch('kai-transcription', { text })}
40
+ />
41
+ ));
package/src/index.ts ADDED
@@ -0,0 +1,253 @@
1
+ // Shared types (folded in from @tab-zen/shared)
2
+ export type { ModelOption, SearchFilters, ConversationScope, ConversationSummary, ConversationGroup } from './types';
3
+
4
+ // Utilities
5
+ export { cn } from './utils/cn';
6
+
7
+ // Layer 1: Headless Primitives
8
+ export { useAutoResize } from './primitives/use-auto-resize';
9
+ export { useStickToBottom } from './primitives/use-stick-to-bottom';
10
+ export { useTextStream } from './primitives/use-text-stream';
11
+ export type { UseTextStreamOptions, TextStreamSegment } from './primitives/use-text-stream';
12
+ export { useVoiceRecorder } from './primitives/use-voice-recorder';
13
+ export type { UseVoiceRecorderOptions } from './primitives/use-voice-recorder';
14
+ export { ChatConfig, useChatConfig, proseClass, textClass } from './primitives/chat-config';
15
+ export type { ChatConfigValue, ProseSize, ChatConfigProps } from './primitives/chat-config';
16
+ export { configureCodeHighlighting, isCodeHighlightingEnabled } from './primitives/highlighter';
17
+ export type { CodeHighlightingOptions } from './primitives/highlighter';
18
+ export { configurePdfPreview, isPdfPreviewEnabled } from './primitives/pdf-preview';
19
+ export type { PdfPreviewOptions } from './primitives/pdf-preview';
20
+
21
+ // Card Contract (generative-UI foundation)
22
+ export { CARD_CONTRACT_VERSION } from './primitives/card-contract';
23
+ export type {
24
+ CardEnvelope, CardContext, CardEvent, CardEventKind, CardResolution, CardHost, CardPolicy,
25
+ } from './primitives/card-contract';
26
+ export { applyResolution, resolutionFromEvent } from './primitives/card-resolution';
27
+ export { CardProvider, useCardHost } from './primitives/card-host';
28
+ export type { CardProviderProps } from './primitives/card-host';
29
+ export { CARD_EVENT_NAME, emitCardEvent, routeCardEvent, listenForCardEvents } from './primitives/card-routing';
30
+ export { validateAgainstSchema } from './primitives/card-validate';
31
+ export type { JsonSchema, ValidationResult } from './primitives/card-validate';
32
+
33
+ // Remote host SDK (iframe transport — host side only; provider runtime ships via ./provider subpath)
34
+ export { mountRemoteCard } from './remote/host-embed';
35
+ export type { MountRemoteCardOptions, RemoteCardHandle } from './remote/host-embed';
36
+
37
+ // Card dispatcher (generative-UI host glue)
38
+ export { CardRenderer, renderCard } from './components/card-renderer';
39
+ export type { CardRendererProps } from './components/card-renderer';
40
+ export { CardFallback } from './components/card-fallback';
41
+ export type { CardFallbackProps } from './components/card-fallback';
42
+ export {
43
+ BUILTIN_CARD_TAGS, BUILTIN_CARD_COMPONENTS, mergeCardTags, mergeCardComponents,
44
+ } from './primitives/card-registry';
45
+ export type { CardComponent, CardComponentMap, CardTagMap } from './primitives/card-registry';
46
+
47
+ // Card: kai-card (base shell) + kai-form (JSON-Schema form renderer)
48
+ export { Card } from './components/card';
49
+ export type { CardProps } from './components/card';
50
+ export { Form, validateForm, buildResult, widgetFor, orderedKeys, coerceValue } from './components/form';
51
+ export type {
52
+ FormProps,
53
+ FormField,
54
+ FormDefinition,
55
+ FormCardEnvelope,
56
+ FormValidation,
57
+ WidgetKind,
58
+ } from './components/form';
59
+
60
+ // Card: kai-confirm (approval) + kai-tasks (selectable plan)
61
+ export {
62
+ ConfirmCard,
63
+ CONFIRM_CARD_TYPE,
64
+ buttonVariantForStyle,
65
+ normalizeActions,
66
+ defaultActionId,
67
+ } from './components/confirm-card';
68
+ export type {
69
+ ConfirmCardProps,
70
+ ConfirmAction,
71
+ ConfirmActionStyle,
72
+ ConfirmTone,
73
+ ConfirmCardData,
74
+ ConfirmCardEnvelope,
75
+ } from './components/confirm-card';
76
+ export {
77
+ TasksCard,
78
+ TASKS_CARD_TYPE,
79
+ normalizeTasks,
80
+ initialSelected,
81
+ selectedInOrder,
82
+ toggleableIds,
83
+ selectAllState,
84
+ showSelectAll,
85
+ canConfirm,
86
+ isMaxReached,
87
+ confirmReason,
88
+ } from './components/tasks-card';
89
+ export type {
90
+ TasksCardProps,
91
+ TasksTask,
92
+ TasksCardData,
93
+ TasksCardResult,
94
+ TasksCardEnvelope,
95
+ SelectAllState,
96
+ } from './components/tasks-card';
97
+
98
+ // Card: kai-choice (single-select option card)
99
+ export {
100
+ ChoiceCard,
101
+ CHOICE_CARD_TYPE,
102
+ OTHER_ACTION,
103
+ normalizeOptions,
104
+ resolveOtherConfig,
105
+ nextEnabledIndex,
106
+ firstEnabledIndex,
107
+ } from './components/choice-card';
108
+ export type {
109
+ ChoiceCardProps,
110
+ ChoiceOption,
111
+ ChoiceOptionMedia,
112
+ ChoiceAllowOther,
113
+ ChoiceCardData,
114
+ ChoiceCardEnvelope,
115
+ } from './components/choice-card';
116
+
117
+ // Card: kai-link-preview (OG/link preview) + kai-embed (lazy media embed)
118
+ export { LinkPreview } from './components/link-preview';
119
+ export type { LinkPreviewProps } from './components/link-preview';
120
+ export { Embed } from './components/embed';
121
+ export type { EmbedProps } from './components/embed';
122
+ export {
123
+ configureLinkPreview,
124
+ resolveLinkMetadata,
125
+ hasLinkPreviewFetcher,
126
+ LINK_PREVIEW_TYPE,
127
+ } from './primitives/link-preview';
128
+ export type { LinkPreviewData, LinkPreviewEnvelope, LinkMetadataFetcher } from './primitives/link-preview';
129
+ export {
130
+ resolveEmbed,
131
+ parseYouTubeId,
132
+ parseVimeoId,
133
+ configureEmbedAllowlist,
134
+ isGenericOriginAllowed,
135
+ EMBED_CARD_TYPE,
136
+ } from './primitives/embed-providers';
137
+ export type { EmbedCardData, EmbedCardEnvelope, EmbedProvider, ResolvedEmbed } from './primitives/embed-providers';
138
+
139
+ // Layer 2: UI Primitives
140
+ export { Button, buttonVariants } from './ui/button';
141
+ export type { ButtonProps } from './ui/button';
142
+ export { Avatar } from './ui/avatar';
143
+ export type { AvatarProps } from './ui/avatar';
144
+ export { Tooltip } from './ui/tooltip';
145
+ export { HoverCard } from './ui/hover-card';
146
+ export { Collapsible, CollapsibleTrigger, CollapsibleContent } from './ui/collapsible';
147
+ export { ScrollArea } from './ui/scroll-area';
148
+ export { Dropdown, DropdownTrigger, DropdownContent, DropdownItem } from './ui/dropdown';
149
+ export { Textarea } from './ui/textarea';
150
+ export type { TextareaProps } from './ui/textarea';
151
+ export { Badge } from './ui/badge';
152
+ export type { BadgeProps } from './ui/badge';
153
+ export { Separator } from './ui/separator';
154
+ export { ResizablePanelGroup, ResizablePanel, ResizableHandle, Resizable, normalizeSize, resolveToPx } from './ui/resizable';
155
+ export type { ResizablePanelGroupProps, ResizablePanelProps, ResizableHandleProps, ResizableProps, SizeValue } from './ui/resizable';
156
+ export { Skeleton } from './ui/skeleton';
157
+ export { FileTree, buildFileTree } from './components/file-tree';
158
+ export type {
159
+ FileTreeProps, FileTreeFile, FileTreeNode, FileTreeFolderNode, FileTreeFileNode,
160
+ } from './components/file-tree';
161
+ export { Artifact } from './components/artifact';
162
+ export type { ArtifactProps, ArtifactFile, ArtifactTab } from './components/artifact';
163
+
164
+ // Layer 3: AI/Feature Components
165
+ export {
166
+ ChatContainer, ChatContainerRoot, ChatContainerContent, ChatContainerScrollAnchor,
167
+ useChatContainer,
168
+ } from './components/chat-container';
169
+ export { Message, MessageAvatar, MessageContent, MessageActions, MessageAction, MessageCopyButton } from './components/message';
170
+ export type { MessageCopyButtonProps } from './components/message';
171
+ export { MessageSkills } from './components/message-skills';
172
+ export type { Skill as MessageSkill } from './components/message-skills';
173
+ export {
174
+ PromptInput, PromptInputTextarea, PromptInputActions, PromptInputAction,
175
+ usePromptInput,
176
+ } from './components/prompt-input';
177
+ export { SlashCommand } from './components/slash-command';
178
+ export type { SlashCommandItem, SlashCommandProps } from './components/slash-command';
179
+ export { ResponseStream } from './components/response-stream';
180
+ export { Markdown } from './components/markdown';
181
+ export { CodeBlock, CodeBlockCode, CodeBlockGroup } from './components/code-block';
182
+ export { Loader } from './components/loader';
183
+ export type { LoaderVariant, LoaderSize, LoaderProps } from './components/loader';
184
+ export {
185
+ CircularLoader, ClassicLoader, PulseLoader, PulseDotLoader,
186
+ DotsLoader, TypingLoader, WaveLoader, BarsLoader,
187
+ TerminalLoader, TextBlinkLoader, TextShimmerLoader, TextDotsLoader,
188
+ } from './components/loader';
189
+ export { FeedbackBar, type FeedbackValue, type FeedbackDetail } from './components/feedback-bar';
190
+ export {
191
+ ChainOfThought, ChainOfThoughtStep, ChainOfThoughtTrigger,
192
+ ChainOfThoughtContent, ChainOfThoughtItem,
193
+ } from './components/chain-of-thought';
194
+ export { Source, SourceTrigger, SourceContent, SourceList } from './components/source';
195
+ export { PromptSuggestion } from './components/prompt-suggestion';
196
+ export {
197
+ Empty, EmptyHeader, EmptyMedia, EmptyTitle, EmptyDescription, EmptyContent, emptyMediaVariants,
198
+ } from './components/empty';
199
+ export type {
200
+ EmptyProps, EmptyHeaderProps, EmptyMediaProps, EmptyTitleProps, EmptyDescriptionProps, EmptyContentProps,
201
+ } from './components/empty';
202
+ export { ScrollButton } from './components/scroll-button';
203
+ export { TextShimmer } from './components/text-shimmer';
204
+ export { Checkpoint, CheckpointIcon, CheckpointTrigger } from './components/checkpoint';
205
+ export type { CheckpointProps, CheckpointIconProps, CheckpointTriggerProps } from './components/checkpoint';
206
+ export {
207
+ Context,
208
+ ContextTrigger,
209
+ ContextContent,
210
+ ContextContentHeader,
211
+ ContextContentBody,
212
+ ContextContentFooter,
213
+ ContextInputUsage,
214
+ ContextOutputUsage,
215
+ ContextReasoningUsage,
216
+ ContextCacheUsage,
217
+ } from './components/context';
218
+ export type {
219
+ ContextProps,
220
+ ContextTriggerProps,
221
+ ContextContentProps,
222
+ ContextContentHeaderProps,
223
+ ContextContentBodyProps,
224
+ ContextContentFooterProps,
225
+ ContextUsageRowProps,
226
+ } from './components/context';
227
+ export { VoiceInput } from './components/voice-input';
228
+ export { ConversationList } from './components/conversation-list';
229
+ export { ConversationItem } from './components/conversation-item';
230
+ export { ModelSwitcher } from './components/model-switcher';
231
+ export { ChatScopePicker } from './components/chat-scope-picker';
232
+ export { Tool } from './components/tool';
233
+ export type { ToolPart, ToolProps } from './components/tool';
234
+ export { ThinkingBar } from './components/thinking-bar';
235
+ export type { ThinkingBarProps } from './components/thinking-bar';
236
+ export { Reasoning, ReasoningTrigger, ReasoningContent } from './components/reasoning';
237
+ export type { ReasoningProps, ReasoningTriggerProps, ReasoningContentProps } from './components/reasoning';
238
+ export { Image } from './components/image';
239
+ export type { ImageProps, GeneratedImageLike } from './components/image';
240
+ export { FileUpload, FileUploadTrigger, FileUploadContent } from './components/file-upload';
241
+ export type { FileUploadProps, FileUploadTriggerProps, FileUploadContentProps } from './components/file-upload';
242
+ export {
243
+ Attachments, Attachment, AttachmentPreview, AttachmentInfo, AttachmentRemove,
244
+ AttachmentHoverCard, AttachmentHoverCardTrigger, AttachmentHoverCardContent,
245
+ AttachmentEmpty, getMediaCategory, getAttachmentLabel,
246
+ useAttachmentsContext, useAttachmentContext,
247
+ } from './components/attachments';
248
+ export type {
249
+ AttachmentData, AttachmentMediaCategory, AttachmentVariant,
250
+ AttachmentsProps, AttachmentProps, AttachmentPreviewProps,
251
+ AttachmentInfoProps, AttachmentRemoveProps, AttachmentEmptyProps,
252
+ AttachmentHoverCardProps, AttachmentHoverCardTriggerProps, AttachmentHoverCardContentProps,
253
+ } from './components/attachments';
@@ -0,0 +1,71 @@
1
+ // src/primitives/card-contract.ts
2
+ // The frozen Card Contract: the one typed contract every card speaks across both
3
+ // transports (native <kai-*> + remote iframe). Pure types only — no runtime, no DOM.
4
+ // See docs/superpowers/specs/2026-06-13-card-contract-design.md.
5
+
6
+ /** Bumped on any BREAKING change to the shapes below. Additive/optional fields do not bump it. */
7
+ export const CARD_CONTRACT_VERSION = '1' as const;
8
+
9
+ /** A card the agent/server asks the chat to render. `data` conforms to the card
10
+ * type's own published JSON Schema (one schema per `type`). */
11
+ export interface CardEnvelope<TType extends string = string, TData = unknown> {
12
+ type: TType;
13
+ id: string;
14
+ data: TData;
15
+ title?: string;
16
+ /** Set when the user has resolved this card; renders the chromed read-only view. */
17
+ resolution?: CardResolution;
18
+ }
19
+
20
+ /** Context the host pushes to every card; updated when it changes (theme, etc.). */
21
+ export interface CardContext {
22
+ theme: { mode: 'light' | 'dark'; tokens?: Record<string, string> };
23
+ locale: string;
24
+ conversationId?: string;
25
+ /** Remote (iframe) cards only: short-lived signed token. Never long-lived. */
26
+ authToken?: string;
27
+ /** Host-resolved a11y prefs (e.g. reduced-motion, which doesn't cross the iframe). */
28
+ a11y?: { reducedMotion?: boolean };
29
+ }
30
+
31
+ /** Everything a card can ask the host to do. The host authorizes + routes each. */
32
+ export type CardEvent =
33
+ | { kind: 'ready'; cardId: string }
34
+ | { kind: 'submit'; cardId: string; data: unknown }
35
+ | { kind: 'action'; cardId: string; action: string; payload?: unknown }
36
+ | { kind: 'send-prompt'; cardId: string; text: string; mode?: 'compose' | 'send'; context?: unknown }
37
+ | { kind: 'open'; cardId: string; url: string; target?: 'tab' | 'artifact' }
38
+ | { kind: 'resize'; cardId: string; height: number }
39
+ | { kind: 'state'; cardId: string; patch: unknown }
40
+ | { kind: 'dismiss'; cardId: string }
41
+ | { kind: 'error'; cardId: string; message: string };
42
+
43
+ export type CardEventKind = CardEvent['kind'];
44
+
45
+ /** How a card was resolved by the user — the re-hydration channel for the chromed
46
+ * read-only state. Mirrors the two terminal CardEvents (minus `cardId`): the
47
+ * resolution is just the event that resolved the card. `at` is optional ISO-8601
48
+ * provenance (data only; never rendered). Additive — does not bump the contract
49
+ * version. */
50
+ export type CardResolution =
51
+ | { kind: 'action'; action: string; payload?: unknown; at?: string }
52
+ | { kind: 'submit'; data: unknown; at?: string };
53
+
54
+ /** What every card is handed (via native context or the iframe bridge). */
55
+ export interface CardHost {
56
+ context(): CardContext;
57
+ emit(event: CardEvent): void;
58
+ }
59
+
60
+ /** How the host routes each verb. Consumers supply handlers; defaults applied otherwise. */
61
+ export interface CardPolicy {
62
+ onSubmit?: (cardId: string, data: unknown) => void;
63
+ onAction?: (cardId: string, action: string, payload?: unknown) => void;
64
+ onSendPrompt?: (text: string, opts: { mode: 'compose' | 'send'; context?: unknown }) => void;
65
+ onOpen?: (url: string, target: 'tab' | 'artifact') => void;
66
+ onState?: (cardId: string, patch: unknown) => void;
67
+ onDismiss?: (cardId: string) => void;
68
+ onError?: (cardId: string, message: string) => void;
69
+ /** Cap on send-prompt: 'compose' (default) forbids silent sends. 'send' to allow. */
70
+ maxSendPromptMode?: 'compose' | 'send';
71
+ }
@@ -0,0 +1,35 @@
1
+ // src/primitives/card-host.tsx
2
+ // The native transport: a Solid context exposing a CardHost (context() + emit()).
3
+ // emit() routes through the contract policy via the shared routeCardEvent. Cards
4
+ // inside <kai-chat>/<CardProvider> use this; bare cards fall back to the bubbling
5
+ // kai-card event (see card-routing.listenForCardEvents).
6
+ import { createContext, useContext, type JSX } from 'solid-js';
7
+ import type { CardContext, CardEvent, CardHost, CardPolicy } from './card-contract';
8
+ import { routeCardEvent } from './card-routing';
9
+
10
+ const CardHostContext = createContext<CardHost>();
11
+
12
+ export interface CardProviderProps {
13
+ /** Ambient context, static or a reactive getter. */
14
+ context: CardContext | (() => CardContext);
15
+ /** Routing policy applied to every emitted event. */
16
+ policy?: CardPolicy;
17
+ children: JSX.Element;
18
+ }
19
+
20
+ export function CardProvider(props: CardProviderProps): JSX.Element {
21
+ // Never destructure props (Solid norm). Resolve context lazily so a getter stays reactive.
22
+ const host: CardHost = {
23
+ context: () =>
24
+ typeof props.context === 'function'
25
+ ? (props.context as () => CardContext)()
26
+ : props.context,
27
+ emit: (event: CardEvent) => routeCardEvent(props.policy, event),
28
+ };
29
+ return <CardHostContext.Provider value={host}>{props.children}</CardHostContext.Provider>;
30
+ }
31
+
32
+ /** Read the current CardHost. `undefined` when no provider is present (bare card). */
33
+ export function useCardHost(): CardHost | undefined {
34
+ return useContext(CardHostContext);
35
+ }
@@ -0,0 +1,67 @@
1
+ // src/primitives/card-registry.tsx
2
+ // One source of truth mapping a CardEnvelope.type to a renderer, for both layers:
3
+ // - CardComponentMap drives the Solid <CardRenderer>.
4
+ // - CardTagMap drives the <kai-cards> web component (child kai-* elements).
5
+ // Built-ins cover the 5 contract card types; consumers extend/override via a `types`
6
+ // prop (merged OVER the built-ins). kai-card (bare shell) is intentionally NOT a target.
7
+ import type { Component } from 'solid-js';
8
+ import type { CardEnvelope, CardHost } from './card-contract';
9
+ import { Form } from '../components/form';
10
+ import { ConfirmCard } from '../components/confirm-card';
11
+ import { TasksCard } from '../components/tasks-card';
12
+ import { ChoiceCard } from '../components/choice-card';
13
+ import { LinkPreview } from '../components/link-preview';
14
+ import { Embed } from '../components/embed';
15
+
16
+ /** Solid renderer for one envelope. `host` is the resolved CardHost so each wrapper
17
+ * can bridge its card's emit convention (form/confirm/tasks take `host`;
18
+ * link/embed take `onEmit`). */
19
+ export type CardComponent = Component<{ envelope: CardEnvelope; host?: CardHost }>;
20
+ export type CardComponentMap = Record<string, CardComponent>;
21
+
22
+ /** Web-component layer: envelope type → kai-* tag name. */
23
+ export type CardTagMap = Record<string, string>;
24
+
25
+ export const BUILTIN_CARD_TAGS: CardTagMap = {
26
+ form: 'kai-form',
27
+ confirm: 'kai-confirm',
28
+ 'tasks': 'kai-tasks',
29
+ choice: 'kai-choice',
30
+ link: 'kai-link-preview',
31
+ embed: 'kai-embed',
32
+ };
33
+
34
+ export const BUILTIN_CARD_COMPONENTS: CardComponentMap = {
35
+ form: (p) => (
36
+ <Form data={p.envelope.data as never} cardId={p.envelope.id} heading={p.envelope.title}
37
+ resolution={p.envelope.resolution} host={p.host} />
38
+ ),
39
+ confirm: (p) => (
40
+ <ConfirmCard data={p.envelope.data as never} cardId={p.envelope.id} heading={p.envelope.title}
41
+ resolution={p.envelope.resolution} host={p.host} />
42
+ ),
43
+ 'tasks': (p) => (
44
+ <TasksCard data={p.envelope.data as never} cardId={p.envelope.id} heading={p.envelope.title}
45
+ resolution={p.envelope.resolution} host={p.host} />
46
+ ),
47
+ choice: (p) => (
48
+ <ChoiceCard data={p.envelope.data as never} cardId={p.envelope.id} heading={p.envelope.title}
49
+ resolution={p.envelope.resolution} host={p.host} />
50
+ ),
51
+ // link/embed have no `heading` and emit via an onEmit callback (no context).
52
+ link: (p) => (
53
+ <LinkPreview data={p.envelope.data as never} cardId={p.envelope.id} onEmit={(e) => p.host?.emit(e)} />
54
+ ),
55
+ embed: (p) => (
56
+ <Embed data={p.envelope.data as never} cardId={p.envelope.id} onEmit={(e) => p.host?.emit(e)} />
57
+ ),
58
+ };
59
+
60
+ /** Built-ins with the consumer's overrides merged on top (consumer wins). */
61
+ export function mergeCardComponents(types?: CardComponentMap): CardComponentMap {
62
+ return types ? { ...BUILTIN_CARD_COMPONENTS, ...types } : { ...BUILTIN_CARD_COMPONENTS };
63
+ }
64
+
65
+ export function mergeCardTags(types?: CardTagMap): CardTagMap {
66
+ return types ? { ...BUILTIN_CARD_TAGS, ...types } : { ...BUILTIN_CARD_TAGS };
67
+ }
@@ -0,0 +1,36 @@
1
+ // Pure consumer-facing helpers for the re-hydration round-trip. Feed a terminal
2
+ // CardEvent back into the cards array so a resolved card survives reload:
3
+ // el.cards = applyResolution(el.cards, e.detail)
4
+ // Deterministic — no Date (the optional `at` provenance is left to the consumer).
5
+ import type { CardEnvelope, CardEvent, CardResolution } from './card-contract';
6
+
7
+ /** Map a terminal CardEvent (`action` | `submit`) to a CardResolution.
8
+ * Returns undefined for every non-terminal verb (ready/error/resize/state/…). */
9
+ export function resolutionFromEvent(event: CardEvent): CardResolution | undefined {
10
+ if (event.kind === 'action') {
11
+ return {
12
+ kind: 'action',
13
+ action: event.action,
14
+ ...(event.payload !== undefined ? { payload: event.payload } : {}),
15
+ };
16
+ }
17
+ if (event.kind === 'submit') {
18
+ return { kind: 'submit', data: event.data };
19
+ }
20
+ return undefined;
21
+ }
22
+
23
+ /** Return a new cards array with the envelope matching `event.cardId` stamped with its
24
+ * resolution. Non-terminal events or an unknown cardId return the SAME array reference
25
+ * (cheap no-op; safe to call on every event). Never mutates the input. */
26
+ export function applyResolution(cards: CardEnvelope[], event: CardEvent): CardEnvelope[] {
27
+ const resolution = resolutionFromEvent(event);
28
+ if (!resolution) return cards;
29
+ let changed = false;
30
+ const next = cards.map((c) => {
31
+ if (c.id !== event.cardId) return c;
32
+ changed = true;
33
+ return { ...c, resolution };
34
+ });
35
+ return changed ? next : cards;
36
+ }
@@ -0,0 +1,79 @@
1
+ // src/primitives/card-routing.ts
2
+ // Framework-agnostic card-event plumbing: the bubbling kai-card emitter, the single
3
+ // policy router (used by BOTH the native listener and the remote transport), and a
4
+ // host-side listener helper for the bare-element path.
5
+ import type { CardEvent, CardPolicy } from './card-contract';
6
+
7
+ /** The single contract event name. */
8
+ export const CARD_EVENT_NAME = 'kai-card';
9
+
10
+ /** Dispatch a CardEvent as the bubbling, composed `kai-card` event a host listener
11
+ * routes. NB: this is deliberately different from defineWebComponent's built-in
12
+ * non-bubbling dispatch. */
13
+ export function emitCardEvent(element: HTMLElement, event: CardEvent): void {
14
+ element.dispatchEvent(
15
+ new CustomEvent<CardEvent>(CARD_EVENT_NAME, { detail: event, bubbles: true, composed: true }),
16
+ );
17
+ }
18
+
19
+ const SAFE_SCHEMES = ['http:', 'https:', 'mailto:'];
20
+ function isSafeUrl(url: string): boolean {
21
+ try { return SAFE_SCHEMES.includes(new URL(url, 'http://_invalid_base').protocol); } catch { return false; }
22
+ }
23
+ function warnNoHandler(kind: string): void {
24
+ // eslint-disable-next-line no-console
25
+ console.warn(`[kai-card] no policy handler for "${kind}"`);
26
+ }
27
+
28
+ /** Apply the contract's policy to one event. The ONE place routing lives. */
29
+ export function routeCardEvent(policy: CardPolicy | undefined, event: CardEvent): void {
30
+ const p: CardPolicy = policy ?? {};
31
+ switch (event.kind) {
32
+ case 'ready':
33
+ break; // lifecycle; host may react via its own listener
34
+ case 'submit':
35
+ p.onSubmit ? p.onSubmit(event.cardId, event.data) : warnNoHandler('submit');
36
+ break;
37
+ case 'action':
38
+ p.onAction ? p.onAction(event.cardId, event.action, event.payload) : warnNoHandler('action');
39
+ break;
40
+ case 'send-prompt': {
41
+ const requested = event.mode ?? 'compose';
42
+ const mode = p.maxSendPromptMode === 'send' ? requested : 'compose';
43
+ p.onSendPrompt ? p.onSendPrompt(event.text, { mode, context: event.context }) : warnNoHandler('send-prompt');
44
+ break;
45
+ }
46
+ case 'open': {
47
+ if (!isSafeUrl(event.url)) {
48
+ p.onError ? p.onError(event.cardId, `Blocked unsafe url: ${event.url}`) : warnNoHandler('open(unsafe)');
49
+ break;
50
+ }
51
+ const target = event.target ?? 'tab';
52
+ if (p.onOpen) p.onOpen(event.url, target);
53
+ else if (typeof window !== 'undefined') window.open(event.url, '_blank', 'noopener,noreferrer');
54
+ break;
55
+ }
56
+ case 'state':
57
+ p.onState ? p.onState(event.cardId, event.patch) : warnNoHandler('state');
58
+ break;
59
+ case 'dismiss':
60
+ p.onDismiss ? p.onDismiss(event.cardId) : warnNoHandler('dismiss');
61
+ break;
62
+ case 'error':
63
+ p.onError ? p.onError(event.cardId, event.message) : warnNoHandler('error');
64
+ break;
65
+ case 'resize':
66
+ break; // transport plumbing (iframe height); not an app-policy concern natively
67
+ }
68
+ }
69
+
70
+ /** Attach a host-level `kai-card` listener that routes every bubbling card event
71
+ * through `policy`. Returns an unsubscribe fn. For the bare-element path. */
72
+ export function listenForCardEvents(
73
+ root: HTMLElement | Document,
74
+ policy: CardPolicy,
75
+ ): () => void {
76
+ const handler = (e: Event) => routeCardEvent(policy, (e as CustomEvent<CardEvent>).detail);
77
+ root.addEventListener(CARD_EVENT_NAME, handler as EventListener);
78
+ return () => root.removeEventListener(CARD_EVENT_NAME, handler as EventListener);
79
+ }