@kitn.ai/ui 0.16.0 → 0.18.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 (493) hide show
  1. package/README.md +55 -17
  2. package/dist/components/artifact.d.ts +39 -1
  3. package/dist/components/attachment-types.d.ts +12 -0
  4. package/dist/components/attachments.d.ts +2 -10
  5. package/dist/components/card.d.ts +27 -0
  6. package/dist/components/chain-of-thought.d.ts +78 -2
  7. package/dist/components/chat-scope-picker.d.ts +7 -0
  8. package/dist/components/chat-thread.d.ts +39 -16
  9. package/dist/components/choice-card.d.ts +27 -0
  10. package/dist/components/coachmark.d.ts +49 -0
  11. package/dist/components/composer-dom.d.ts +35 -0
  12. package/dist/components/composer-highlight.d.ts +48 -0
  13. package/dist/components/composer-history.d.ts +34 -0
  14. package/dist/components/composer.d.ts +86 -0
  15. package/dist/components/confirm-card.d.ts +17 -0
  16. package/dist/components/conversation-item.d.ts +9 -0
  17. package/dist/components/conversation-list.d.ts +36 -1
  18. package/dist/components/dismissed-stub.d.ts +22 -0
  19. package/dist/components/file-tree.d.ts +15 -0
  20. package/dist/components/form.d.ts +34 -0
  21. package/dist/components/markdown.d.ts +3 -0
  22. package/dist/components/message.d.ts +71 -2
  23. package/dist/components/model-switcher.d.ts +7 -0
  24. package/dist/components/prompt-input.d.ts +13 -4
  25. package/dist/components/prompt-suggestion.d.ts +9 -1
  26. package/dist/components/reasoning.d.ts +14 -1
  27. package/dist/components/response-compare-types.d.ts +66 -0
  28. package/dist/components/response-compare.d.ts +65 -0
  29. package/dist/components/screen.d.ts +46 -0
  30. package/dist/components/tasks-card.d.ts +50 -1
  31. package/dist/components/toast.d.ts +58 -0
  32. package/dist/components/tool-types.d.ts +10 -0
  33. package/dist/components/tool.d.ts +8 -8
  34. package/dist/components/use-card-resolution.d.ts +10 -6
  35. package/dist/components/voice-input.d.ts +27 -0
  36. package/dist/components/voice-output.d.ts +33 -0
  37. package/dist/core-C8fzo39E.js +12 -0
  38. package/dist/custom-elements.json +8472 -1691
  39. package/dist/elements/agent-card.d.ts +1 -0
  40. package/dist/elements/agent-card.js +1 -0
  41. package/dist/elements/artifact.js +1 -0
  42. package/dist/elements/attachments.js +1 -0
  43. package/dist/elements/autoloader.d.ts +2 -0
  44. package/dist/elements/autoloader.js +1 -0
  45. package/dist/elements/avatar.d.ts +1 -0
  46. package/dist/elements/avatar.js +1 -0
  47. package/dist/elements/badge.d.ts +1 -0
  48. package/dist/elements/badge.js +1 -0
  49. package/dist/elements/button.d.ts +1 -0
  50. package/dist/elements/button.js +1 -0
  51. package/dist/elements/card.js +1 -0
  52. package/dist/elements/cards.d.ts +1 -0
  53. package/dist/elements/cards.js +1 -0
  54. package/dist/elements/chain-of-thought.d.ts +0 -12
  55. package/dist/elements/chain-of-thought.js +1 -0
  56. package/dist/elements/chat-scope-picker.js +1 -0
  57. package/dist/elements/chat-types.d.ts +8 -2
  58. package/dist/elements/chat-workspace.js +1 -0
  59. package/dist/elements/chat.js +1 -0
  60. package/dist/elements/checkpoint.js +1 -0
  61. package/dist/elements/choice.js +1 -0
  62. package/dist/elements/chunks/Icon-DLLBeLG5.js +1 -0
  63. package/dist/elements/chunks/action-icons-UB4V5FNq.js +1 -0
  64. package/dist/elements/chunks/arrow-left-C-gwlUmx.js +1 -0
  65. package/dist/elements/chunks/attachments-CgbErf1r.js +1 -0
  66. package/dist/elements/chunks/badge-DTQpgB6N.js +1 -0
  67. package/dist/elements/chunks/bash-InADTalH.js +1 -0
  68. package/dist/elements/chunks/button-XDUM6lXs.js +1 -0
  69. package/dist/elements/chunks/card-CBlFZ-7I.js +1 -0
  70. package/dist/elements/chunks/card-routing-C986KuUA.js +1 -0
  71. package/dist/elements/chunks/chat-thread-BIhmqwNL.js +1 -0
  72. package/dist/elements/chunks/check-hGUoVIKb.js +1 -0
  73. package/dist/elements/chunks/chevron-down-DDoaSUtO.js +1 -0
  74. package/dist/elements/chunks/chevron-right-CdGEUakB.js +1 -0
  75. package/dist/elements/chunks/circle-BKAZdmzb.js +1 -0
  76. package/dist/elements/chunks/circle-check-CmYG-y3I.js +1 -0
  77. package/dist/elements/chunks/circle-x-DewgAEym.js +1 -0
  78. package/dist/elements/chunks/cn-CU7UAWzk.js +1 -0
  79. package/dist/elements/chunks/code-block-DedwxwFI.js +1 -0
  80. package/dist/elements/chunks/collapsible-FfgmTHtM.js +1 -0
  81. package/dist/elements/chunks/composer-Cfbw5HKT.js +65 -0
  82. package/dist/elements/chunks/context-DwJmxlRE.js +1 -0
  83. package/dist/elements/chunks/conversation-list-DsCQMfPq.js +1 -0
  84. package/dist/elements/chunks/core-AYMC6_lb.js +12 -0
  85. package/dist/elements/chunks/css-M7EaDHN_.js +1 -0
  86. package/dist/elements/chunks/default-input-BuqsoENC.js +1 -0
  87. package/dist/elements/chunks/define-rGsaWsLL.js +1 -0
  88. package/dist/elements/chunks/disclosure-CwoAkpMH.js +1 -0
  89. package/dist/elements/chunks/download-DJ06G3Bz.js +1 -0
  90. package/dist/elements/chunks/dropdown-BFg8ri_R.js +1 -0
  91. package/dist/elements/chunks/ellipsis-dlN6YvGw.js +1 -0
  92. package/dist/elements/chunks/engine-javascript-vq0WuIJl.js +141 -0
  93. package/dist/elements/chunks/external-link-DBX2lc_R.js +1 -0
  94. package/dist/elements/chunks/file-text-HfwUx4F9.js +1 -0
  95. package/dist/elements/chunks/file-tree-C6Toj71u.js +1 -0
  96. package/dist/elements/chunks/folder-MYuqa9Xp.js +1 -0
  97. package/dist/elements/chunks/github-dark-dimmed-DUshB20C.js +1 -0
  98. package/dist/elements/chunks/github-light-JYsPkUQd.js +1 -0
  99. package/dist/elements/chunks/hover-card-gE7iyXdW.js +1 -0
  100. package/dist/elements/chunks/html-CPZ3oZQ7.js +1 -0
  101. package/dist/elements/chunks/icon-Vqz4ivrY.js +1 -0
  102. package/dist/elements/chunks/index-BCRhi84a.js +1 -0
  103. package/dist/elements/chunks/info-B_toFS0l.js +1 -0
  104. package/dist/elements/chunks/input-gzbHYZvQ.js +1 -0
  105. package/dist/elements/chunks/javascript-C25yR2R2.js +1 -0
  106. package/dist/elements/chunks/json-DxJze_jm.js +1 -0
  107. package/dist/elements/chunks/kbd-n49c4KZm.js +1 -0
  108. package/dist/elements/chunks/link-CKPs9yM0.js +1 -0
  109. package/dist/elements/chunks/loader-q6MamGus.js +1 -0
  110. package/dist/elements/chunks/markdown-DBrcqq4S.js +64 -0
  111. package/dist/elements/chunks/message-CTqaYI0s.js +1 -0
  112. package/dist/elements/chunks/message-feedback-Cvzm03w0.js +1 -0
  113. package/dist/elements/chunks/message-square-CUg9eRCi.js +1 -0
  114. package/dist/elements/chunks/minimize-2-l2klxP6K.js +1 -0
  115. package/dist/elements/chunks/model-switcher-CmyjiHO1.js +1 -0
  116. package/dist/elements/chunks/overlay-C8sOYqjT.js +1 -0
  117. package/dist/elements/chunks/paperclip-42Ia7xHD.js +1 -0
  118. package/dist/elements/chunks/progress-bar-tzKMWAwO.js +1 -0
  119. package/dist/elements/chunks/prompt-suggestion-D3F6vP_A.js +1 -0
  120. package/dist/elements/chunks/reasoning-BJNmmyov.js +1 -0
  121. package/dist/elements/chunks/resizable-BRsk19qI.js +1 -0
  122. package/dist/elements/chunks/rotate-cw-BgcggLHC.js +1 -0
  123. package/dist/elements/chunks/scroll-area-CgGbFFbT.js +1 -0
  124. package/dist/elements/chunks/separator-8hM4GdHE.js +1 -0
  125. package/dist/elements/chunks/settings-CaUIYOg-.js +1 -0
  126. package/dist/elements/chunks/settings-group-CYjOVIZQ.js +1 -0
  127. package/dist/elements/chunks/share-BTYM3Ddt.js +1 -0
  128. package/dist/elements/chunks/skeleton-DsTYrPUe.js +1 -0
  129. package/dist/elements/chunks/slots-DkZ-Cmha.js +1 -0
  130. package/dist/elements/chunks/star-9R3LBQ9n.js +1 -0
  131. package/dist/elements/chunks/store-CYl5Y0_u.js +1 -0
  132. package/dist/elements/chunks/svelte--5p79yCD.js +1 -0
  133. package/dist/elements/chunks/text-shimmer-CaYGv8Se.js +1 -0
  134. package/dist/elements/chunks/textarea-NEr2SLcD.js +1 -0
  135. package/dist/elements/chunks/thumbs-up-DZYZkFfc.js +1 -0
  136. package/dist/elements/chunks/toast-store-ClxVZRcc.js +1 -0
  137. package/dist/elements/chunks/tool-CHZOmoe9.js +1 -0
  138. package/dist/elements/chunks/tooltip-WpHElELC.js +1 -0
  139. package/dist/elements/chunks/triangle-alert-BlS80PUg.js +1 -0
  140. package/dist/elements/chunks/tsx-B8rCNbgL.js +1 -0
  141. package/dist/elements/chunks/typescript-RycA9KXf.js +1 -0
  142. package/dist/elements/chunks/use-card-resolution-VSVoxDeJ.js +1 -0
  143. package/dist/elements/chunks/video-DxTPZqT8.js +1 -0
  144. package/dist/elements/chunks/vue-BmIZj4XD.js +1 -0
  145. package/dist/elements/chunks/x-C1qtndxn.js +1 -0
  146. package/dist/elements/coachmark.d.ts +1 -0
  147. package/dist/elements/coachmark.js +1 -0
  148. package/dist/elements/code-block.js +1 -0
  149. package/dist/elements/command.d.ts +1 -0
  150. package/dist/elements/command.js +1 -0
  151. package/dist/elements/compare.d.ts +1 -0
  152. package/dist/elements/compare.js +1 -0
  153. package/dist/elements/composer.d.ts +1 -0
  154. package/dist/elements/composer.js +1 -0
  155. package/dist/elements/confirm-card.js +1 -0
  156. package/dist/elements/context-meter.js +1 -0
  157. package/dist/elements/conversation-list.d.ts +1 -11
  158. package/dist/elements/conversation-list.js +1 -0
  159. package/dist/elements/default-input.d.ts +20 -9
  160. package/dist/elements/define.d.ts +11 -0
  161. package/dist/elements/dialog.d.ts +1 -0
  162. package/dist/elements/dialog.js +1 -0
  163. package/dist/elements/disclosure.d.ts +37 -0
  164. package/dist/elements/editable-label.d.ts +1 -0
  165. package/dist/elements/editable-label.js +1 -0
  166. package/dist/elements/embed.js +1 -0
  167. package/dist/elements/empty.js +1 -0
  168. package/dist/elements/feedback-bar.js +1 -0
  169. package/dist/elements/file-tree.js +1 -0
  170. package/dist/elements/file-upload.js +1 -0
  171. package/dist/elements/form.js +1 -0
  172. package/dist/elements/hover-card.d.ts +1 -0
  173. package/dist/elements/hover-card.js +1 -0
  174. package/dist/elements/icon.d.ts +1 -0
  175. package/dist/elements/icon.js +1 -0
  176. package/dist/elements/image.js +1 -0
  177. package/dist/elements/input.d.ts +1 -0
  178. package/dist/elements/input.js +1 -0
  179. package/dist/elements/kbd.d.ts +1 -0
  180. package/dist/elements/kbd.js +1 -0
  181. package/dist/elements/link-preview.js +1 -0
  182. package/dist/elements/loader.js +1 -0
  183. package/dist/elements/markdown.js +1 -0
  184. package/dist/elements/menu.d.ts +1 -0
  185. package/dist/elements/menu.js +1 -0
  186. package/dist/elements/message-skills.d.ts +0 -16
  187. package/dist/elements/message-skills.js +1 -0
  188. package/dist/elements/message.js +1 -0
  189. package/dist/elements/model-switcher.d.ts +1 -11
  190. package/dist/elements/model-switcher.js +1 -0
  191. package/dist/elements/nav.d.ts +1 -0
  192. package/dist/elements/nav.js +1 -0
  193. package/dist/elements/notice.d.ts +1 -0
  194. package/dist/elements/notice.js +1 -0
  195. package/dist/elements/pane-group.d.ts +1 -0
  196. package/dist/elements/pane-group.js +1 -0
  197. package/dist/elements/pane.d.ts +1 -0
  198. package/dist/elements/pane.js +1 -0
  199. package/dist/elements/popover.js +1 -0
  200. package/dist/elements/progress-bar.d.ts +1 -0
  201. package/dist/elements/progress-bar.js +1 -0
  202. package/dist/elements/prompt-dock.d.ts +1 -0
  203. package/dist/elements/prompt-dock.js +1 -0
  204. package/dist/elements/prompt-input.d.ts +1 -9
  205. package/dist/elements/prompt-input.js +1 -0
  206. package/dist/elements/prompt-suggestions.d.ts +0 -6
  207. package/dist/elements/prompt-suggestions.js +1 -0
  208. package/dist/elements/reasoning.js +1 -0
  209. package/dist/elements/register.d.ts +10 -0
  210. package/dist/elements/remote.js +1 -0
  211. package/dist/elements/resizable.d.ts +1 -17
  212. package/dist/elements/resizable.js +1 -0
  213. package/dist/elements/response-stream.js +14 -0
  214. package/dist/elements/screen.d.ts +1 -0
  215. package/dist/elements/screen.js +1 -0
  216. package/dist/elements/scroll-area.d.ts +1 -0
  217. package/dist/elements/scroll-area.js +1 -0
  218. package/dist/elements/scroll-button.js +1 -0
  219. package/dist/elements/search.d.ts +1 -0
  220. package/dist/elements/search.js +1 -0
  221. package/dist/elements/segmented.d.ts +1 -0
  222. package/dist/elements/segmented.js +1 -0
  223. package/dist/elements/separator.d.ts +1 -0
  224. package/dist/elements/separator.js +1 -0
  225. package/dist/elements/setting-item.d.ts +1 -0
  226. package/dist/elements/setting-item.js +1 -0
  227. package/dist/elements/settings-group.d.ts +1 -0
  228. package/dist/elements/settings-group.js +1 -0
  229. package/dist/elements/skeleton.d.ts +1 -0
  230. package/dist/elements/skeleton.js +1 -0
  231. package/dist/elements/slots.d.ts +176 -0
  232. package/dist/elements/source.d.ts +0 -17
  233. package/dist/elements/source.js +1 -0
  234. package/dist/elements/status.d.ts +1 -0
  235. package/dist/elements/status.js +1 -0
  236. package/dist/elements/switch.js +1 -0
  237. package/dist/elements/tabs.d.ts +1 -0
  238. package/dist/elements/tabs.js +1 -0
  239. package/dist/elements/tasks.js +1 -0
  240. package/dist/elements/text-shimmer.js +1 -0
  241. package/dist/elements/thinking-bar.js +1 -0
  242. package/dist/elements/toast.d.ts +1 -0
  243. package/dist/elements/toast.js +1 -0
  244. package/dist/elements/tool.js +1 -0
  245. package/dist/elements/tooltip.d.ts +1 -0
  246. package/dist/elements/tooltip.js +1 -0
  247. package/dist/elements/voice-input.js +1 -0
  248. package/dist/elements/voice-output.d.ts +1 -0
  249. package/dist/elements/voice-output.js +1 -0
  250. package/dist/elements.d.ts +772 -61
  251. package/dist/engine-javascript-C1x7zo1_.js +141 -0
  252. package/dist/index.d.ts +15 -2
  253. package/dist/index.js +14405 -9810
  254. package/dist/kai.es.js +1 -0
  255. package/dist/llms/llms-full.txt +1234 -107
  256. package/dist/llms/llms.txt +4 -4
  257. package/dist/mcp.es.js +217 -6
  258. package/dist/primitives/card-contract.d.ts +20 -4
  259. package/dist/primitives/card-recovery.d.ts +55 -0
  260. package/dist/primitives/composer-model.d.ts +22 -0
  261. package/dist/primitives/composer-triggers.d.ts +19 -0
  262. package/dist/primitives/create-kai-chat.d.ts +38 -0
  263. package/dist/primitives/message-feedback.d.ts +35 -0
  264. package/dist/primitives/toast-store.d.ts +118 -0
  265. package/dist/primitives/use-speech-recognition.d.ts +26 -0
  266. package/dist/react/index.d.ts +1166 -98
  267. package/dist/react/runtime.d.ts +9 -2
  268. package/dist/react/use-kai-chat.d.ts +41 -0
  269. package/dist/react.js +575 -186
  270. package/dist/register-impl-CYWGkgbI.js +141 -0
  271. package/dist/schemas/choice.schema.json +5 -0
  272. package/dist/schemas/tasks.schema.json +7 -2
  273. package/dist/state/index.d.ts +4 -0
  274. package/dist/state/messages.d.ts +11 -0
  275. package/dist/state/stream.d.ts +22 -0
  276. package/dist/state/suggestions.d.ts +4 -0
  277. package/dist/state.js +117 -0
  278. package/dist/theme.tokens.css +87 -0
  279. package/dist/types.d.ts +4 -0
  280. package/dist/ui/agent-card.d.ts +61 -0
  281. package/dist/ui/button.d.ts +5 -2
  282. package/dist/ui/card.d.ts +61 -0
  283. package/dist/ui/collapsible.d.ts +15 -1
  284. package/dist/ui/command.d.ts +33 -0
  285. package/dist/ui/dialog.d.ts +42 -0
  286. package/dist/ui/dropdown.d.ts +95 -1
  287. package/dist/ui/editable-label.d.ts +26 -0
  288. package/dist/ui/hover-card.d.ts +13 -1
  289. package/dist/ui/icon.d.ts +18 -0
  290. package/dist/ui/input.d.ts +33 -0
  291. package/dist/ui/kbd.d.ts +21 -0
  292. package/dist/ui/nav.d.ts +62 -0
  293. package/dist/ui/notice.d.ts +31 -0
  294. package/dist/ui/overlay.d.ts +12 -0
  295. package/dist/ui/pane-grid.d.ts +58 -0
  296. package/dist/ui/pane-group.d.ts +75 -0
  297. package/dist/ui/pane.d.ts +78 -0
  298. package/dist/ui/popover.d.ts +11 -1
  299. package/dist/ui/progress-bar.d.ts +23 -0
  300. package/dist/ui/prompt-dock.d.ts +96 -0
  301. package/dist/ui/resizable.d.ts +12 -0
  302. package/dist/ui/scroll-area.d.ts +4 -0
  303. package/dist/ui/segmented.d.ts +28 -0
  304. package/dist/ui/settings-group.d.ts +32 -0
  305. package/dist/ui/skeleton.d.ts +12 -0
  306. package/dist/ui/stat.d.ts +16 -0
  307. package/dist/ui/status.d.ts +13 -0
  308. package/dist/ui/switch.d.ts +8 -1
  309. package/dist/ui/tabs.d.ts +35 -0
  310. package/dist/ui/tooltip.d.ts +20 -1
  311. package/frameworks/react/index.tsx +1100 -163
  312. package/frameworks/react/runtime.tsx +30 -1
  313. package/frameworks/react/use-kai-chat.tsx +87 -0
  314. package/llms-full.txt +1234 -107
  315. package/llms.txt +4 -4
  316. package/package.json +36 -12
  317. package/src/agent-tooling/mcp/manifest.ts +14 -0
  318. package/src/agent-tooling/mcp/tools/debug.ts +194 -6
  319. package/src/agent-tooling/mcp/tools/reference.ts +32 -0
  320. package/src/agent-tooling/mcp/tools/scaffold.ts +162 -1
  321. package/src/components/artifact.tsx +98 -13
  322. package/src/components/attachment-types.ts +13 -0
  323. package/src/components/attachments.tsx +4 -14
  324. package/src/components/card.tsx +109 -15
  325. package/src/components/chain-of-thought.tsx +210 -1
  326. package/src/components/chat-scope-picker.tsx +13 -3
  327. package/src/components/chat-thread.tsx +238 -137
  328. package/src/components/choice-card.tsx +136 -15
  329. package/src/components/coachmark.tsx +245 -0
  330. package/src/components/component-meta.json +921 -108
  331. package/src/components/composer-dom.ts +159 -0
  332. package/src/components/composer-highlight.ts +242 -0
  333. package/src/components/composer-history.ts +82 -0
  334. package/src/components/composer.tsx +979 -0
  335. package/src/components/confirm-card.tsx +69 -2
  336. package/src/components/conversation-item.tsx +69 -7
  337. package/src/components/conversation-list.tsx +134 -37
  338. package/src/components/dismissed-stub.tsx +80 -0
  339. package/src/components/file-tree.tsx +197 -47
  340. package/src/components/form-widgets.tsx +9 -7
  341. package/src/components/form.tsx +161 -20
  342. package/src/components/image.tsx +3 -5
  343. package/src/components/link-preview.tsx +5 -4
  344. package/src/components/markdown.tsx +5 -2
  345. package/src/components/message.tsx +174 -14
  346. package/src/components/model-switcher.tsx +19 -4
  347. package/src/components/prompt-input.tsx +46 -75
  348. package/src/components/prompt-suggestion.tsx +48 -16
  349. package/src/components/reasoning.tsx +29 -6
  350. package/src/components/response-compare-types.ts +127 -0
  351. package/src/components/response-compare.tsx +460 -0
  352. package/src/components/screen.tsx +176 -0
  353. package/src/components/tasks-card.tsx +374 -23
  354. package/src/components/toast.tsx +486 -0
  355. package/src/components/tool-types.ts +10 -0
  356. package/src/components/tool.tsx +22 -16
  357. package/src/components/use-card-resolution.ts +24 -8
  358. package/src/components/voice-input.tsx +102 -19
  359. package/src/components/voice-output.tsx +202 -0
  360. package/src/elements/agent-card.tsx +67 -0
  361. package/src/elements/artifact.tsx +53 -4
  362. package/src/elements/attachments.tsx +1 -0
  363. package/src/elements/autoloader.ts +89 -0
  364. package/src/elements/avatar.tsx +33 -0
  365. package/src/elements/badge.tsx +32 -0
  366. package/src/elements/button.tsx +126 -0
  367. package/src/elements/card-media.jpg +0 -0
  368. package/src/elements/card.tsx +124 -31
  369. package/src/elements/cards.tsx +81 -7
  370. package/src/elements/chain-of-thought.tsx +77 -32
  371. package/src/elements/chat-scope-picker.tsx +35 -8
  372. package/src/elements/chat-types.ts +9 -2
  373. package/src/elements/chat-workspace.tsx +157 -43
  374. package/src/elements/chat.tsx +49 -25
  375. package/src/elements/choice.tsx +71 -19
  376. package/src/elements/coachmark.tsx +90 -0
  377. package/src/elements/command.tsx +209 -0
  378. package/src/elements/compare.tsx +110 -0
  379. package/src/elements/compiled.css +1 -1
  380. package/src/elements/composer.tsx +151 -0
  381. package/src/elements/confirm-card.tsx +36 -11
  382. package/src/elements/conversation-list.tsx +80 -11
  383. package/src/elements/default-input.tsx +75 -53
  384. package/src/elements/define.tsx +47 -6
  385. package/src/elements/dialog.tsx +98 -0
  386. package/src/elements/disclosure.ts +73 -0
  387. package/src/elements/editable-label.tsx +125 -0
  388. package/src/elements/element-manifest.json +310 -0
  389. package/src/elements/element-meta.json +5954 -1605
  390. package/src/elements/element-types.d.ts +771 -60
  391. package/src/elements/file-tree.tsx +12 -2
  392. package/src/elements/file-upload.tsx +1 -1
  393. package/src/elements/form.tsx +54 -11
  394. package/src/elements/hover-card.tsx +80 -0
  395. package/src/elements/icon-names.json +48 -0
  396. package/src/elements/icon.tsx +41 -0
  397. package/src/elements/input.tsx +203 -0
  398. package/src/elements/kbd.tsx +45 -0
  399. package/src/elements/menu.tsx +217 -0
  400. package/src/elements/message.tsx +84 -55
  401. package/src/elements/model-switcher.tsx +27 -1
  402. package/src/elements/nav.tsx +87 -0
  403. package/src/elements/notice.tsx +53 -0
  404. package/src/elements/pane-group.tsx +119 -0
  405. package/src/elements/pane.tsx +129 -0
  406. package/src/elements/popover.tsx +39 -17
  407. package/src/elements/progress-bar.tsx +39 -0
  408. package/src/elements/prompt-dock.tsx +81 -0
  409. package/src/elements/prompt-input.tsx +112 -63
  410. package/src/elements/prompt-suggestions.tsx +25 -9
  411. package/src/elements/reasoning.tsx +30 -9
  412. package/src/elements/register-impl.ts +41 -3
  413. package/src/elements/register.ts +20 -3
  414. package/src/elements/remote.tsx +4 -0
  415. package/src/elements/resizable.tsx +48 -11
  416. package/src/elements/screen.tsx +90 -0
  417. package/src/elements/scroll-area.tsx +33 -0
  418. package/src/elements/search.tsx +180 -0
  419. package/src/elements/segmented.tsx +135 -0
  420. package/src/elements/separator.tsx +36 -0
  421. package/src/elements/setting-item.tsx +50 -0
  422. package/src/elements/settings-group.tsx +37 -0
  423. package/src/elements/skeleton.tsx +45 -0
  424. package/src/elements/slots.ts +607 -0
  425. package/src/elements/source.tsx +13 -6
  426. package/src/elements/status.tsx +42 -0
  427. package/src/elements/styles.css +19 -0
  428. package/src/elements/switch.tsx +89 -14
  429. package/src/elements/tabs.tsx +95 -0
  430. package/src/elements/tasks.tsx +70 -14
  431. package/src/elements/toast.tsx +89 -0
  432. package/src/elements/tool.tsx +43 -9
  433. package/src/elements/tooltip.tsx +85 -0
  434. package/src/elements/video-poster.jpg +0 -0
  435. package/src/elements/voice-input.tsx +66 -16
  436. package/src/elements/voice-output.tsx +69 -0
  437. package/src/index.ts +35 -2
  438. package/src/primitives/card-contract.ts +14 -5
  439. package/src/primitives/card-recovery.ts +149 -0
  440. package/src/primitives/card-routing.ts +3 -0
  441. package/src/primitives/card-schemas/choice.schema.json +5 -0
  442. package/src/primitives/card-schemas/tasks.schema.json +7 -2
  443. package/src/primitives/composer-model.ts +49 -0
  444. package/src/primitives/composer-triggers.ts +35 -0
  445. package/src/primitives/create-kai-chat.ts +66 -0
  446. package/src/primitives/message-feedback.ts +123 -0
  447. package/src/primitives/toast-store.ts +276 -0
  448. package/src/primitives/use-speech-recognition.ts +135 -0
  449. package/src/state/index.ts +6 -0
  450. package/src/state/messages.ts +36 -0
  451. package/src/state/stream.ts +92 -0
  452. package/src/state/suggestions.ts +9 -0
  453. package/src/types.ts +4 -0
  454. package/src/ui/agent-card.tsx +212 -0
  455. package/src/ui/button.tsx +16 -5
  456. package/src/ui/card.tsx +245 -0
  457. package/src/ui/collapsible.tsx +30 -4
  458. package/src/ui/command.tsx +99 -0
  459. package/src/ui/dialog.tsx +217 -0
  460. package/src/ui/dropdown.tsx +375 -13
  461. package/src/ui/editable-label.tsx +112 -0
  462. package/src/ui/hover-card.tsx +28 -5
  463. package/src/ui/icon.tsx +110 -0
  464. package/src/ui/input.tsx +132 -0
  465. package/src/ui/kbd.tsx +118 -0
  466. package/src/ui/nav.tsx +282 -0
  467. package/src/ui/notice.tsx +84 -0
  468. package/src/ui/overlay.tsx +108 -2
  469. package/src/ui/pane-grid.tsx +116 -0
  470. package/src/ui/pane-group.tsx +280 -0
  471. package/src/ui/pane.tsx +193 -0
  472. package/src/ui/popover.tsx +16 -2
  473. package/src/ui/progress-bar.tsx +83 -0
  474. package/src/ui/prompt-dock.tsx +145 -0
  475. package/src/ui/resizable.tsx +69 -1
  476. package/src/ui/scroll-area.tsx +14 -3
  477. package/src/ui/segmented.tsx +109 -0
  478. package/src/ui/settings-group.tsx +67 -0
  479. package/src/ui/skeleton.tsx +62 -5
  480. package/src/ui/stat.tsx +41 -0
  481. package/src/ui/status.tsx +48 -0
  482. package/src/ui/switch.tsx +49 -23
  483. package/src/ui/tabs.tsx +168 -0
  484. package/src/ui/tooltip.tsx +57 -7
  485. package/theme.css +113 -0
  486. package/dist/components/slash-command.d.ts +0 -15
  487. package/dist/kitn-chat.es.js +0 -1
  488. package/dist/register-impl-wCsFPoh9.js +0 -77
  489. package/dist/register-ujhr1nBS.js +0 -1
  490. package/src/components/slash-command.tsx +0 -232
  491. package/src/elements/framework-usage.json +0 -563
  492. /package/dist/{kitn-chat-provider.d.ts → kai-provider.d.ts} +0 -0
  493. /package/dist/{kitn-chat-provider.es.js → kai-provider.es.js} +0 -0
@@ -0,0 +1,460 @@
1
+ import {
2
+ type JSX,
3
+ type Accessor,
4
+ Show,
5
+ Index,
6
+ splitProps,
7
+ mergeProps,
8
+ createSignal,
9
+ createMemo,
10
+ createEffect,
11
+ on,
12
+ ErrorBoundary,
13
+ createUniqueId,
14
+ } from 'solid-js';
15
+ import { cn } from '../utils/cn';
16
+ import { Button } from '../ui/button';
17
+ import { Card } from './card';
18
+ import { MessageBody } from './message';
19
+ import { TextShimmer } from './text-shimmer';
20
+ import { Check } from 'lucide-solid';
21
+ import {
22
+ type CompareCandidate,
23
+ type ComparePair,
24
+ type CompareSelection,
25
+ type ResponseCompareData,
26
+ buildSelection,
27
+ isAnyStreaming,
28
+ normalizeCandidates,
29
+ } from './response-compare-types';
30
+
31
+ // Re-export the data types + pure helpers so consumers (and the `<kai-compare>`
32
+ // facade) can import everything from a single `response-compare` module.
33
+ export type {
34
+ CompareCandidate,
35
+ ComparePair,
36
+ CompareCollapse,
37
+ ResponseCompareData,
38
+ CompareSelection,
39
+ } from './response-compare-types';
40
+ export { normalizeCandidates, buildSelection, isAnyStreaming } from './response-compare-types';
41
+
42
+ export type CompareLayout = 'auto' | 'columns' | 'tabs';
43
+
44
+ /** Imperative handle exposed via `controllerRef` — surfaces the compare's latent
45
+ * capabilities (commit a pick by candidate id, focus the roving tab stop) so the
46
+ * `<kai-compare>` facade can forward them as instance methods (Pattern C). */
47
+ export interface ResponseCompareController {
48
+ /** Commit a pick by candidate id — same path as the "Pick this" button: emits
49
+ * onSelect + optimistically collapses (single-shot; inert while streaming or
50
+ * already resolved). No-op for an unknown id. */
51
+ select(candidateId: string): void;
52
+ /** Focus the current roving tab stop (the focused candidate's "Pick this" radio). */
53
+ focus(options?: FocusOptions): void;
54
+ }
55
+
56
+ // ─────────────────────────────────────────────────────────────────────────────
57
+ // Resolution controller — a sibling of `useCardResolution` for `CompareSelection`,
58
+ // which has no `kind` field (so it can't satisfy `R extends CardResolution`). Same
59
+ // precedence: prop (host-driven / re-hydrated) > local optimistic flip > none. A
60
+ // fresh `data` identity clears the local flip, but an explicit prop keeps it
61
+ // resolved. `isOptimistic` is true only for a flip made THIS session (announced via
62
+ // role="status"; silent on re-hydrate).
63
+ // ─────────────────────────────────────────────────────────────────────────────
64
+
65
+ export interface ResolvedController<T> {
66
+ value: Accessor<T | undefined>;
67
+ isResolved: Accessor<boolean>;
68
+ isOptimistic: Accessor<boolean>;
69
+ setLocal: (v: T) => void;
70
+ }
71
+
72
+ export function useResolved<T>(opts: {
73
+ prop: Accessor<T | undefined>;
74
+ data: Accessor<unknown>;
75
+ }): ResolvedController<T> {
76
+ const [local, setLocal] = createSignal<T | undefined>(undefined);
77
+ // Clear the optimistic flip on a NEW data identity (deferred so mount doesn't
78
+ // clobber an initial prop). The prop still wins via the memo below.
79
+ createEffect(on(opts.data, () => setLocal(undefined), { defer: true }));
80
+ const value = createMemo(() => opts.prop() ?? local());
81
+ const isResolved = createMemo(() => value() !== undefined);
82
+ const isOptimistic = createMemo(() => opts.prop() === undefined && local() !== undefined);
83
+ return { value, isResolved, isOptimistic, setLocal };
84
+ }
85
+
86
+ // ─────────────────────────────────────────────────────────────────────────────
87
+ // The <ResponseCompare> component.
88
+ // ─────────────────────────────────────────────────────────────────────────────
89
+
90
+ export interface ResponseCompareProps {
91
+ /** The compare definition (prompt + the two candidates). */
92
+ data?: ResponseCompareData;
93
+ /** Stable id correlating every emitted event. */
94
+ compareId?: string;
95
+ /** Re-hydrate / control the selection. Renders the collapsed winner. Set as a
96
+ * JS property: `el.selection = { chosenId, rejectedIds }`. */
97
+ selection?: CompareSelection;
98
+ /** Layout. `'columns'` = side-by-side; `'tabs'` = one candidate at a time with
99
+ * pills to switch; `'auto'` (default) uses a CONTAINER query — columns when the
100
+ * component is ≥640px wide, tabs when narrower (e.g. on a phone). */
101
+ layout?: CompareLayout;
102
+ class?: string;
103
+ /** Receive the imperative controller once mounted. The `<kai-compare>` facade
104
+ * forwards these as element methods (select/focus). */
105
+ controllerRef?: (controller: ResponseCompareController) => void;
106
+ /** Fired when the user commits a pick. */
107
+ onSelect?: (sel: CompareSelection) => void;
108
+ /** Fired once both candidates have settled (stopped streaming) and a valid
109
+ * definition is shown. */
110
+ onReady?: () => void;
111
+ /** Fired for an unusable definition (inline error state). */
112
+ onError?: (message: string) => void;
113
+ }
114
+
115
+ /**
116
+ * `ResponseCompare` — a dual-response comparison. Two assistant candidates for the
117
+ * same prompt render side-by-side (or as tabs), each via `MessageBody` so a
118
+ * candidate reads exactly like an assistant message (reasoning + tools +
119
+ * attachments + markdown). The pick is a COMMIT, not a Submit: clicking "Pick this"
120
+ * (or Enter on the focused column) computes `{ chosenId, rejectedIds:[other], at }`,
121
+ * optimistically collapses to the chosen candidate's body, and calls `onSelect` for
122
+ * the consumer to send a `(prompt, chosen, rejected)` preference pair. Single-shot.
123
+ *
124
+ * While EITHER candidate is `streaming`, both pick controls are disabled and the
125
+ * streaming column shows a `TextShimmer`; `onReady` fires once both settle. The two
126
+ * columns are a WAI-ARIA radiogroup with roving tabindex (Arrow keys move A↔B,
127
+ * Enter/Space picks). Emits `onError` for a malformed definition.
128
+ */
129
+ export function ResponseCompare(props: ResponseCompareProps): JSX.Element {
130
+ const merged = mergeProps({ compareId: 'kai-compare', layout: 'auto' as CompareLayout }, props);
131
+ const [local] = splitProps(merged, [
132
+ 'data',
133
+ 'compareId',
134
+ 'selection',
135
+ 'layout',
136
+ 'class',
137
+ 'controllerRef',
138
+ 'onSelect',
139
+ 'onReady',
140
+ 'onError',
141
+ ]);
142
+
143
+ const uid = createUniqueId();
144
+
145
+ const normalized = createMemo(() => normalizeCandidates(local.data?.candidates));
146
+ const valid = createMemo(() => normalized().candidates !== null);
147
+ const errorMessage = createMemo(() => normalized().error ?? '');
148
+ const pair = createMemo<ComparePair | null>(() => normalized().candidates);
149
+
150
+ const res = useResolved<CompareSelection>({
151
+ prop: () => local.selection,
152
+ data: () => local.data,
153
+ });
154
+
155
+ // Both candidates settled → enable the pick + announce ready.
156
+ const streaming = createMemo(() => isAnyStreaming(pair()));
157
+
158
+ const [focusIndex, setFocusIndex] = createSignal(0);
159
+ // In tabs mode (and auto when the container is narrow) only one candidate shows
160
+ // at a time; `active` is which. Ignored in columns mode where both render.
161
+ const [active, setActive] = createSignal(0);
162
+ let groupRef: HTMLDivElement | undefined;
163
+
164
+ // Reset the roving tab stop + active tab whenever a NEW definition arrives.
165
+ createEffect(on(() => local.data, () => { setFocusIndex(0); setActive(0); }));
166
+
167
+ // ready / error lifecycle: error on an unusable definition, ready once both
168
+ // candidates have settled (so consumers know the pick is now live).
169
+ createEffect(
170
+ on([valid, streaming], ([ok, isStreaming]) => {
171
+ if (!ok) {
172
+ local.onError?.(errorMessage());
173
+ return;
174
+ }
175
+ if (!isStreaming) local.onReady?.();
176
+ }),
177
+ );
178
+
179
+ const chosenId = createMemo(() => res.value()?.chosenId);
180
+ const chosenCandidate = createMemo<CompareCandidate | undefined>(() => {
181
+ const id = chosenId();
182
+ return id === undefined ? undefined : pair()?.find((c) => c.id === id);
183
+ });
184
+
185
+ // Commit a pick: emit + optimistically flip (single-shot; inert once resolved
186
+ // or while still streaming).
187
+ const pick = (candidate: CompareCandidate): void => {
188
+ if (res.isResolved()) return;
189
+ if (streaming()) return;
190
+ const p = pair();
191
+ if (!p) return;
192
+ const sel = buildSelection(p, candidate.id);
193
+ local.onSelect?.(sel);
194
+ res.setLocal(sel);
195
+ };
196
+
197
+ const focusColumn = (index: number, options?: FocusOptions): void => {
198
+ const cols = groupRef?.querySelectorAll<HTMLElement>('[role="radio"]');
199
+ cols?.[index]?.focus(options);
200
+ };
201
+
202
+ // Imperative controller (Pattern C): hand the facade a handle over the latent
203
+ // pick + roving-focus capabilities. `pick`/`focusColumn` are already wired
204
+ // internally — this just surfaces them. `select` looks the candidate up by id
205
+ // (the public surface is id-based; `pick` is candidate-based internally) and
206
+ // is a no-op for an unknown id, while streaming, or once resolved (via pick()).
207
+ local.controllerRef?.({
208
+ select: (candidateId: string) => {
209
+ const cand = pair()?.find((c) => c.id === candidateId);
210
+ if (cand) pick(cand);
211
+ },
212
+ focus: (options?: FocusOptions) => focusColumn(focusIndex(), options),
213
+ });
214
+
215
+ // Two-item roving tabindex (A↔B). Arrows toggle the focused column; Enter/Space
216
+ // picks it. No-op once resolved.
217
+ const onGroupKeyDown = (e: KeyboardEvent): void => {
218
+ if (res.isResolved()) return;
219
+ const p = pair();
220
+ if (!p) return;
221
+ const move = (next: number) => { setFocusIndex(next); setActive(next); focusColumn(next); };
222
+ if (e.key === 'ArrowRight' || e.key === 'ArrowDown' || e.key === 'ArrowLeft' || e.key === 'ArrowUp') {
223
+ e.preventDefault();
224
+ move(focusIndex() === 0 ? 1 : 0);
225
+ } else if (e.key === 'Home') {
226
+ e.preventDefault();
227
+ move(0);
228
+ } else if (e.key === 'End') {
229
+ e.preventDefault();
230
+ move(1);
231
+ } else if (e.key === ' ' || e.key === 'Enter') {
232
+ e.preventDefault();
233
+ const cand = p[focusIndex()];
234
+ if (cand) pick(cand);
235
+ }
236
+ };
237
+
238
+ // `auto` switches by CONTAINER width (Tailwind v4 `@container` + `@[640px]`
239
+ // variants); `@container/compare` lives on the OUTER wrapper (below) so the
240
+ // pill bar + columns both respond to it.
241
+ const wrapperClass = createMemo(() => {
242
+ const l = local.layout;
243
+ if (l === 'columns') return 'grid grid-cols-2 gap-3';
244
+ if (l === 'tabs') return 'grid grid-cols-1 gap-3';
245
+ return 'grid grid-cols-1 gap-3 @[640px]/compare:grid-cols-2'; // auto
246
+ });
247
+ // The tab-pill bar shows in tabs mode, and in auto only while the container is narrow.
248
+ const showTabs = () => local.layout === 'tabs' || local.layout === 'auto';
249
+ const tabBarClass = () => (local.layout === 'auto' ? '@[640px]/compare:hidden' : '');
250
+ // A non-active candidate is hidden in tabs (always) and auto (only while narrow);
251
+ // the active one — and both in columns mode — always render.
252
+ const columnHiddenClass = (index: number): string => {
253
+ const l = local.layout;
254
+ if (l === 'columns' || index === active()) return '';
255
+ return l === 'tabs' ? 'hidden' : 'hidden @[640px]/compare:block';
256
+ };
257
+
258
+ const promptId = `kai-compare-prompt-${uid}`;
259
+ const groupLabel = (): string => local.data?.prompt ?? 'Compare two responses';
260
+
261
+ return (
262
+ <Show
263
+ when={valid()}
264
+ fallback={<Card errorMessage={errorMessage() || "This comparison couldn't be displayed."} />}
265
+ >
266
+ <ErrorBoundary
267
+ fallback={() => {
268
+ local.onError?.('The comparison failed to render.');
269
+ return <Card errorMessage="The comparison failed to render." />;
270
+ }}
271
+ >
272
+ <div class={cn('flex flex-col gap-3', local.layout === 'auto' && '@container/compare', local.class)}>
273
+ <Show when={local.data?.prompt}>
274
+ <p id={promptId} class="text-sm text-foreground">
275
+ {local.data?.prompt}
276
+ </p>
277
+ </Show>
278
+
279
+ <Show
280
+ when={!res.isResolved()}
281
+ fallback={
282
+ <CollapsedWinner
283
+ candidate={chosenCandidate()}
284
+ optimistic={res.isOptimistic()}
285
+ />
286
+ }
287
+ >
288
+ {/* Tab pills — switch the visible candidate in tabs / auto-narrow mode. */}
289
+ <Show when={showTabs()}>
290
+ <div role="tablist" aria-label="Switch response" class={cn('flex gap-1', tabBarClass())}>
291
+ <Index each={pair() ?? []}>
292
+ {(candidate, index) => (
293
+ <button
294
+ type="button"
295
+ role="tab"
296
+ aria-selected={active() === index}
297
+ class={cn(
298
+ 'rounded-md px-3 py-1 text-xs font-medium transition-colors',
299
+ active() === index
300
+ ? 'bg-muted text-foreground'
301
+ : 'text-muted-foreground hover:text-foreground',
302
+ )}
303
+ onClick={() => { setActive(index); setFocusIndex(index); }}
304
+ >
305
+ {candidate().label ?? `Response ${candidate().id}`}
306
+ </button>
307
+ )}
308
+ </Index>
309
+ </div>
310
+ </Show>
311
+
312
+ <div
313
+ ref={groupRef}
314
+ role="radiogroup"
315
+ aria-label={groupLabel()}
316
+ aria-describedby={local.data?.prompt ? promptId : undefined}
317
+ class={wrapperClass()}
318
+ onKeyDown={onGroupKeyDown}
319
+ >
320
+ <Index each={pair() ?? []}>
321
+ {(candidate, index) => (
322
+ <CompareColumn
323
+ candidate={candidate()}
324
+ tabStop={index === focusIndex()}
325
+ disabled={streaming()}
326
+ hiddenClass={() => columnHiddenClass(index)}
327
+ onFocus={() => setFocusIndex(index)}
328
+ onPick={() => {
329
+ setFocusIndex(index);
330
+ pick(candidate());
331
+ }}
332
+ />
333
+ )}
334
+ </Index>
335
+ </div>
336
+ </Show>
337
+ </div>
338
+ </ErrorBoundary>
339
+ </Show>
340
+ );
341
+ }
342
+
343
+ // ─────────────────────────────────────────────────────────────────────────────
344
+ // One candidate column — a plain container with the assistant-styled body and a
345
+ // "Pick this" button that IS the radio (the radiogroup option). Keeping the radio
346
+ // on the button, not the wrapping div, avoids nesting interactive controls (the
347
+ // body itself can contain interactive bits like reasoning/tool toggles).
348
+ // ─────────────────────────────────────────────────────────────────────────────
349
+
350
+ interface ColumnProps {
351
+ candidate: CompareCandidate;
352
+ tabStop: boolean;
353
+ disabled: boolean;
354
+ /** Extra (reactive) class controlling tabs/auto visibility — hidden when this
355
+ * is the non-active candidate in tabs / auto-narrow mode. */
356
+ hiddenClass?: () => string;
357
+ onFocus: () => void;
358
+ onPick: () => void;
359
+ }
360
+
361
+ function CompareColumn(props: ColumnProps): JSX.Element {
362
+ const labelText = (): string =>
363
+ props.candidate.label ?? props.candidate.model ?? props.candidate.id;
364
+ return (
365
+ <div
366
+ class={cn(
367
+ 'flex min-w-0 flex-col gap-3 rounded-lg border border-input bg-background/40 p-3',
368
+ props.hiddenClass?.(),
369
+ )}
370
+ >
371
+ <Show when={props.candidate.label || props.candidate.model}>
372
+ <div class="flex items-center gap-2">
373
+ <Show when={props.candidate.label}>
374
+ <span class="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
375
+ {props.candidate.label}
376
+ </span>
377
+ </Show>
378
+ <Show when={props.candidate.model}>
379
+ <span class="text-xs text-muted-foreground">{props.candidate.model}</span>
380
+ </Show>
381
+ </div>
382
+ </Show>
383
+
384
+ <div class="min-w-0 flex-1">
385
+ <Show
386
+ when={props.candidate.streaming}
387
+ fallback={
388
+ <MessageBody
389
+ content={props.candidate.content}
390
+ reasoning={props.candidate.reasoning}
391
+ tools={props.candidate.tools}
392
+ attachments={props.candidate.attachments}
393
+ isUser={false}
394
+ markdown={true}
395
+ />
396
+ }
397
+ >
398
+ <TextShimmer class="text-sm">Generating response…</TextShimmer>
399
+ </Show>
400
+ </div>
401
+
402
+ <Button
403
+ type="button"
404
+ role="radio"
405
+ aria-checked={false}
406
+ aria-label={`Pick ${labelText()}`}
407
+ data-candidate-id={props.candidate.id}
408
+ variant="outline"
409
+ size="sm"
410
+ class="mt-auto w-full"
411
+ disabled={props.disabled}
412
+ tabindex={props.tabStop ? 0 : -1}
413
+ onFocus={props.onFocus}
414
+ onClick={(e) => {
415
+ e.stopPropagation();
416
+ props.onPick();
417
+ }}
418
+ >
419
+ Pick this
420
+ </Button>
421
+ </div>
422
+ );
423
+ }
424
+
425
+ // ─────────────────────────────────────────────────────────────────────────────
426
+ // CollapsedWinner — the read-only view after a pick: the chosen candidate's body
427
+ // (reasoning + tools + attachments + markdown), with a "Selected" affordance.
428
+ // role="status" only for an optimistic flip made this session.
429
+ // ─────────────────────────────────────────────────────────────────────────────
430
+
431
+ function CollapsedWinner(props: {
432
+ candidate: CompareCandidate | undefined;
433
+ optimistic: boolean;
434
+ }): JSX.Element {
435
+ return (
436
+ <div
437
+ class="flex flex-col gap-2 rounded-lg border border-input bg-accent/40 p-3"
438
+ role={props.optimistic ? 'status' : undefined}
439
+ >
440
+ <div class="flex items-center gap-2 text-sm font-medium text-accent-foreground">
441
+ <Check size={16} aria-hidden="true" />
442
+ <span>Selected{props.candidate?.label ? `: ${props.candidate.label}` : ''}</span>
443
+ </div>
444
+ <Show when={props.candidate} fallback={<p class="text-sm text-muted-foreground">Response selected.</p>}>
445
+ {(c) => (
446
+ <div class="min-w-0">
447
+ <MessageBody
448
+ content={c().content}
449
+ reasoning={c().reasoning}
450
+ tools={c().tools}
451
+ attachments={c().attachments}
452
+ isUser={false}
453
+ markdown={true}
454
+ />
455
+ </div>
456
+ )}
457
+ </Show>
458
+ </div>
459
+ );
460
+ }
@@ -0,0 +1,176 @@
1
+ import { createEffect, createSignal, onCleanup, Show, type Accessor, type JSX } from 'solid-js';
2
+ import { ArrowLeft } from 'lucide-solid';
3
+ import { cn } from '../utils/cn';
4
+ import { createPresence } from '../ui/overlay';
5
+
6
+ /** Imperative open controller, handed to a parent (the kai-screen facade) via
7
+ * `controllerRef` so it can drive/observe open state with `wireDisclosure`. */
8
+ export interface ScreenController {
9
+ open: Accessor<boolean>;
10
+ setOpen: (v: boolean) => void;
11
+ }
12
+
13
+ export interface ScreenProps {
14
+ /** The screen body. */
15
+ children?: JSX.Element;
16
+ /** Header title text. A projected `title` slot overrides this. */
17
+ title?: JSX.Element;
18
+ /** Header title slot content (the facade's `<slot name="title" />`). Overrides `title`. */
19
+ titleSlot?: JSX.Element;
20
+ /** Header trailing cluster (the facade's `<slot name="actions" />`). */
21
+ actions?: JSX.Element;
22
+ /** Controlled open state. When set, the component never changes it itself;
23
+ * drive it from `onOpenChange`. Omit for uncontrolled (internal) state. */
24
+ open?: boolean;
25
+ /** Initial open state when uncontrolled. */
26
+ defaultOpen?: boolean;
27
+ /** Show the back button (default true). */
28
+ back?: boolean;
29
+ /** Skip marking sibling elements inert/aria-hidden while open. */
30
+ noInert?: boolean;
31
+ /** The custom-element host, whose siblings get inert-ed while open. */
32
+ host?: () => HTMLElement | undefined;
33
+ /** Fires whenever open wants to change (back button / method). */
34
+ onOpenChange?: (open: boolean) => void;
35
+ /** Back navigation intent: the back button or Escape. */
36
+ onBack?: () => void;
37
+ /** Receive the open controller once mounted. */
38
+ controllerRef?: (api: ScreenController) => void;
39
+ /** Receive the focusable surface node so the facade's `focus()` can target it. */
40
+ surfaceRef?: (el: HTMLElement) => void;
41
+ }
42
+
43
+ /** A stable id seed for labelling the dialog by its title. */
44
+ let idSeq = 0;
45
+
46
+ /**
47
+ * Screen is the presentational full-bleed overlay surface. It owns the hard parts
48
+ * of being a takeover: it fills its mount point while open, marks sibling
49
+ * elements `inert` + `aria-hidden` (the standard modal pattern), moves focus into
50
+ * the surface on open and restores it on close, fires `onBack` on Escape, and
51
+ * runs an enter/exit transition that honors `prefers-reduced-motion`. When closed
52
+ * it is removed from layout (no space, not focusable). The developer owns the swap
53
+ * (their own routing flips `open`); this owns being the overlay.
54
+ */
55
+ export function Screen(props: ScreenProps) {
56
+ const [internalOpen, setInternalOpen] = createSignal(props.defaultOpen ?? false);
57
+ const titleId = `kai-screen-title-${++idSeq}`;
58
+
59
+ const isControlled = () => props.open !== undefined;
60
+ const isOpen = () => (isControlled() ? !!props.open : internalOpen());
61
+ const setOpen = (v: boolean) => {
62
+ if (!isControlled()) setInternalOpen(v);
63
+ props.onOpenChange?.(v);
64
+ };
65
+
66
+ props.controllerRef?.({ open: isOpen, setOpen });
67
+
68
+ const presence = createPresence(isOpen);
69
+
70
+ let surface: HTMLElement | undefined;
71
+ // The element that had focus before we opened, restored on close.
72
+ let restoreFocus: HTMLElement | null = null;
73
+ // Siblings we inert-ed, so we can undo exactly what we did.
74
+ let inerted: { el: HTMLElement; hadInert: boolean; hadAriaHidden: string | null }[] = [];
75
+
76
+ const back = () => props.back !== false;
77
+
78
+ const requestBack = () => props.onBack?.();
79
+
80
+ // Inert + aria-hide the host's siblings while open (skip when noInert). This is
81
+ // the one light-DOM touch: the standard modal pattern that makes the background
82
+ // non-interactive and removes it from the a11y tree, which is what justifies the
83
+ // dialog/aria-modal semantics on the surface.
84
+ const applyInert = () => {
85
+ if (props.noInert) return;
86
+ const host = props.host?.();
87
+ const parent = host?.parentElement;
88
+ if (!host || !parent) return;
89
+ for (const node of Array.from(parent.children)) {
90
+ if (node === host) continue;
91
+ const el = node as HTMLElement;
92
+ inerted.push({ el, hadInert: el.hasAttribute('inert'), hadAriaHidden: el.getAttribute('aria-hidden') });
93
+ el.setAttribute('inert', '');
94
+ el.setAttribute('aria-hidden', 'true');
95
+ }
96
+ };
97
+ const releaseInert = () => {
98
+ for (const { el, hadInert, hadAriaHidden } of inerted) {
99
+ if (!hadInert) el.removeAttribute('inert');
100
+ if (hadAriaHidden === null) el.removeAttribute('aria-hidden');
101
+ else el.setAttribute('aria-hidden', hadAriaHidden);
102
+ }
103
+ inerted = [];
104
+ };
105
+
106
+ // Drive inert + focus on the open/closed transition.
107
+ createEffect((wasOpen: boolean) => {
108
+ const open = isOpen();
109
+ if (open && !wasOpen) {
110
+ restoreFocus = (document.activeElement as HTMLElement) ?? null;
111
+ applyInert();
112
+ // Move focus into the surface once it has mounted.
113
+ queueMicrotask(() => surface?.focus());
114
+ } else if (!open && wasOpen) {
115
+ releaseInert();
116
+ // Return focus to the invoker if it's still in the document.
117
+ const target = restoreFocus;
118
+ restoreFocus = null;
119
+ if (target && target.isConnected) queueMicrotask(() => target.focus());
120
+ }
121
+ return open;
122
+ // Seed prev=false so a `defaultOpen` (open-at-mount) still runs the open branch.
123
+ }, false);
124
+
125
+ onCleanup(releaseInert);
126
+
127
+ // Escape fires the back intent (navigation), matching the back button.
128
+ const onKeyDown = (e: KeyboardEvent) => {
129
+ if (e.key === 'Escape') {
130
+ e.stopPropagation();
131
+ requestBack();
132
+ }
133
+ };
134
+
135
+ return (
136
+ <Show when={presence.present()}>
137
+ <div
138
+ ref={(el) => { surface = el; presence.setRef(el); props.surfaceRef?.(el); }}
139
+ part="body"
140
+ role="dialog"
141
+ aria-modal="true"
142
+ aria-labelledby={titleId}
143
+ tabindex="-1"
144
+ data-expanded={presence.state() === 'open' ? '' : undefined}
145
+ data-closed={presence.state() === 'closed' ? '' : undefined}
146
+ onKeyDown={onKeyDown}
147
+ class={cn(
148
+ 'absolute inset-0 z-40 flex flex-col bg-background text-foreground outline-none',
149
+ 'animate-in fade-in-0 slide-in-from-bottom-2 motion-reduce:animate-none',
150
+ 'data-[closed]:animate-out data-[closed]:fade-out-0 data-[closed]:slide-out-to-bottom-2',
151
+ )}
152
+ >
153
+ <header part="header" class="flex shrink-0 items-center gap-2 border-b border-border px-3 py-2">
154
+ <Show when={back()}>
155
+ <button
156
+ part="back"
157
+ type="button"
158
+ aria-label="Back"
159
+ class="inline-flex size-8 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
160
+ onClick={requestBack}
161
+ >
162
+ <ArrowLeft size={16} aria-hidden="true" />
163
+ </button>
164
+ </Show>
165
+ <h2 part="title" id={titleId} class="min-w-0 flex-1 truncate text-sm font-semibold text-foreground">
166
+ {/* Native shadow-slot fallback: a projected `title` slot wins; otherwise
167
+ the `title` prop renders as the slot's default content. */}
168
+ {props.titleSlot ?? props.title}
169
+ </h2>
170
+ <div class="flex shrink-0 items-center gap-1">{props.actions}</div>
171
+ </header>
172
+ <div class="min-h-0 flex-1 overflow-auto">{props.children}</div>
173
+ </div>
174
+ </Show>
175
+ );
176
+ }