@kitn.ai/ui 0.25.2 → 0.27.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 (603) hide show
  1. package/README.md +17 -6
  2. package/bin/mcp.js +16 -3
  3. package/bin/route.js +23 -0
  4. package/bin/route.test.js +58 -0
  5. package/dist/{elements/chunks/Icon-b5A8hlHs.js → Icon-BnFKqqtY.js} +1 -1
  6. package/dist/action-icons-CBskfBMz.js +1 -0
  7. package/dist/arrow-down-Br0qtpsz.js +1 -0
  8. package/dist/arrow-left-Dj7xgE12.js +1 -0
  9. package/dist/artifact-CvXEm1lS.js +1 -0
  10. package/dist/assets/dev-5XzXYR21.js +91 -0
  11. package/dist/attachments-C43ZegEq.js +1 -0
  12. package/dist/audio-visualizer-B-79y1Tj.js +1 -0
  13. package/dist/badge-BpUbq8c0.js +1 -0
  14. package/dist/bash-InADTalH.js +1 -6
  15. package/dist/bell-sn17Ip9v.js +1 -0
  16. package/dist/{elements/chunks/button-B9okqG73.js → button-BH6rN0U-.js} +1 -1
  17. package/dist/card-renderer-Cb1gQmth.js +1 -0
  18. package/dist/card-routing-DifQgS7n.js +1 -0
  19. package/dist/check-DPeeMUYx.js +1 -0
  20. package/dist/checkbox-Dc9Yov_B.js +1 -0
  21. package/dist/checkbox-group-B1kDLLYk.js +1 -0
  22. package/dist/chevron-down-CHSWk7ZT.js +1 -0
  23. package/dist/chevron-right-B7l_fMgx.js +1 -0
  24. package/dist/choice-card-BT3c1S8g.js +1 -0
  25. package/dist/circle-Bb0yOpqM.js +1 -0
  26. package/dist/circle-check-BGZpCiz7.js +1 -0
  27. package/dist/circle-x-wobWkoSx.js +1 -0
  28. package/dist/{elements/chunks/cn-CU7UAWzk.js → cn-DN5AWfiS.js} +1 -1
  29. package/dist/code-block-CUQ5mtv0.js +1 -0
  30. package/dist/collapsible-CPJbf6FM.js +1 -0
  31. package/dist/components/attachment-types.d.ts +53 -1
  32. package/dist/components/attachments.d.ts +31 -0
  33. package/dist/components/audio-visualizer/fit-scale.d.ts +50 -0
  34. package/dist/components/audio-visualizer/index.d.ts +15 -1
  35. package/dist/components/audio-visualizer/variant-bar.d.ts +24 -1
  36. package/dist/components/card-renderer.d.ts +13 -0
  37. package/dist/components/card.d.ts +4 -0
  38. package/dist/components/chat-thread.d.ts +49 -42
  39. package/dist/components/code-block.d.ts +22 -0
  40. package/dist/components/conversation-item.d.ts +66 -1
  41. package/dist/components/conversation-list.d.ts +77 -0
  42. package/dist/components/form-widgets.d.ts +67 -2
  43. package/dist/components/form.d.ts +89 -0
  44. package/dist/components/message.d.ts +29 -0
  45. package/dist/components/reasoning.d.ts +6 -0
  46. package/dist/components/scroll-button.d.ts +11 -0
  47. package/dist/components/thread.d.ts +5 -0
  48. package/dist/components/toast.d.ts +2 -1
  49. package/dist/components/tool-types.d.ts +9 -0
  50. package/dist/components/voice-input.d.ts +8 -0
  51. package/dist/components/voice-output.d.ts +12 -1
  52. package/dist/components/workspace-shell.d.ts +87 -0
  53. package/dist/composer-CjngLzal.js +106 -0
  54. package/dist/confirm-card-DCx_A4bU.js +1 -0
  55. package/dist/construct-cli.es.js +1169 -0
  56. package/dist/context-BY8V-Nlp.js +1 -0
  57. package/dist/controllable-D43BKmtX.js +1 -0
  58. package/dist/conversation-list-D4lLU_yS.js +1 -0
  59. package/dist/copy-B6DO4PqB.js +1 -0
  60. package/dist/core-AYMC6_lb.js +12 -5874
  61. package/dist/{create-tween-CUzOlaMS.js → create-tween-BPzaefTp.js} +1 -1
  62. package/dist/{create-tween-B5Y4Im8G.js → create-tween-CnMHviKB.js} +1 -1
  63. package/dist/{create-tween-4z1XYLkR.js → create-tween-D1aiGZZh.js} +1 -1
  64. package/dist/{elements/chunks/create-tween-CUrM6D_o.js → create-tween-DUSQUKpr.js} +2 -2
  65. package/dist/css-M7EaDHN_.js +1 -6
  66. package/dist/custom-elements.json +3468 -1927
  67. package/dist/default-input-CucQEOqH.js +1 -0
  68. package/dist/define-CGGX-7Vg.js +1 -0
  69. package/dist/define.d.ts +4 -0
  70. package/dist/define.js +510 -0
  71. package/dist/define.server.d.ts +5 -0
  72. package/dist/define.server.js +506 -0
  73. package/dist/diagnostics/hook.d.ts +81 -0
  74. package/dist/diagnostics/index.d.ts +7 -0
  75. package/dist/diagnostics/report-request.d.ts +61 -0
  76. package/dist/diagnostics.d.ts +5 -0
  77. package/dist/diagnostics.js +209 -0
  78. package/dist/disclosure-CbLtajUQ.js +1 -0
  79. package/dist/dropdown-BwhViJFd.js +1 -0
  80. package/dist/elements/agent-card.js +1 -1
  81. package/dist/elements/artifact.js +1 -1
  82. package/dist/elements/attachments.js +1 -1
  83. package/dist/elements/audio-visualizer.js +1 -1
  84. package/dist/elements/autoloader.js +1 -1
  85. package/dist/elements/avatar.js +1 -1
  86. package/dist/elements/badge.js +1 -1
  87. package/dist/elements/button.js +1 -1
  88. package/dist/elements/card.js +1 -1
  89. package/dist/elements/cards.js +1 -1
  90. package/dist/elements/chain-of-thought.js +1 -1
  91. package/dist/elements/chat-scope-picker.js +1 -1
  92. package/dist/elements/chat-workspace.js +1 -1
  93. package/dist/elements/chat.js +1 -1
  94. package/dist/elements/checkbox-group.d.ts +1 -0
  95. package/dist/elements/checkbox-group.js +1 -0
  96. package/dist/elements/checkbox.d.ts +1 -0
  97. package/dist/elements/checkbox.js +1 -0
  98. package/dist/elements/checkpoint.js +1 -1
  99. package/dist/elements/choice.js +1 -1
  100. package/dist/elements/coachmark.js +1 -1
  101. package/dist/elements/code-block.js +1 -1
  102. package/dist/elements/command.js +1 -1
  103. package/dist/elements/compare.js +1 -1
  104. package/dist/elements/composer.js +1 -1
  105. package/dist/elements/confirm-card.js +1 -1
  106. package/dist/elements/context-meter.js +1 -1
  107. package/dist/elements/conversation-item.d.ts +1 -0
  108. package/dist/elements/conversation-item.js +1 -0
  109. package/dist/elements/conversation-list.js +1 -1
  110. package/dist/elements/default-input.d.ts +3 -3
  111. package/dist/elements/define-entry.d.ts +12 -0
  112. package/dist/elements/define.d.ts +27 -0
  113. package/dist/elements/diagnostic-events.d.ts +74 -0
  114. package/dist/elements/dialog.js +1 -1
  115. package/dist/elements/dock.d.ts +1 -0
  116. package/dist/elements/dock.js +243 -0
  117. package/dist/elements/dropdown.d.ts +1 -0
  118. package/dist/elements/dropdown.js +1 -0
  119. package/dist/elements/editable-label.js +1 -1
  120. package/dist/elements/element-data-types.d.ts +37 -0
  121. package/dist/elements/element-diagnostics.d.ts +81 -0
  122. package/dist/elements/embed.js +1 -1
  123. package/dist/elements/empty.js +1 -1
  124. package/dist/elements/feedback-bar.js +1 -1
  125. package/dist/elements/file-tree.js +1 -1
  126. package/dist/elements/file-upload.js +1 -1
  127. package/dist/elements/form.js +1 -1
  128. package/dist/elements/hover-card.js +1 -1
  129. package/dist/elements/icon.js +1 -1
  130. package/dist/elements/image.js +1 -1
  131. package/dist/elements/input.js +1 -1
  132. package/dist/elements/kbd.js +1 -1
  133. package/dist/elements/link-preview.js +1 -1
  134. package/dist/elements/loader.js +1 -1
  135. package/dist/elements/markdown.js +1 -1
  136. package/dist/elements/menu.js +1 -1
  137. package/dist/elements/message-skills.js +1 -1
  138. package/dist/elements/message.js +1 -1
  139. package/dist/elements/model-switcher.js +1 -1
  140. package/dist/elements/nav.js +1 -1
  141. package/dist/elements/notice.js +1 -1
  142. package/dist/elements/pane-grid.d.ts +1 -0
  143. package/dist/elements/pane-grid.js +1 -0
  144. package/dist/elements/pane-group.js +1 -1
  145. package/dist/elements/pane.js +1 -1
  146. package/dist/elements/popover.js +1 -1
  147. package/dist/elements/progress-bar.js +1 -1
  148. package/dist/elements/prompt-dock.js +1 -1
  149. package/dist/elements/prompt-input.js +1 -1
  150. package/dist/elements/prompt-suggestions.js +1 -1
  151. package/dist/elements/radio-group.d.ts +1 -0
  152. package/dist/elements/radio-group.js +1 -0
  153. package/dist/elements/reasoning.js +1 -1
  154. package/dist/elements/register.d.ts +2 -0
  155. package/dist/elements/remote.js +1 -1
  156. package/dist/elements/resizable.js +1 -1
  157. package/dist/elements/response-stream.js +3 -3
  158. package/dist/elements/screen.js +1 -1
  159. package/dist/elements/scroll-area.js +1 -1
  160. package/dist/elements/scroll-button.js +1 -1
  161. package/dist/elements/search.js +1 -1
  162. package/dist/elements/segmented.js +1 -1
  163. package/dist/elements/select.d.ts +1 -0
  164. package/dist/elements/select.js +1 -0
  165. package/dist/elements/separator.js +1 -1
  166. package/dist/elements/setting-item.js +1 -1
  167. package/dist/elements/settings-group.js +1 -1
  168. package/dist/elements/skeleton.js +1 -1
  169. package/dist/elements/slider.d.ts +1 -0
  170. package/dist/elements/slider.js +1 -0
  171. package/dist/elements/slots.d.ts +36 -4
  172. package/dist/elements/source.js +1 -1
  173. package/dist/elements/status.js +1 -1
  174. package/dist/elements/switch.js +1 -1
  175. package/dist/elements/tabs.js +1 -1
  176. package/dist/elements/tasks.js +1 -1
  177. package/dist/elements/text-shimmer.js +1 -1
  178. package/dist/elements/thinking-bar.js +1 -1
  179. package/dist/elements/thread.js +1 -1
  180. package/dist/elements/toast.js +1 -1
  181. package/dist/elements/tool.js +1 -1
  182. package/dist/elements/tooltip.js +1 -1
  183. package/dist/elements/voice-input.js +1 -1
  184. package/dist/elements/voice-output.js +1 -1
  185. package/dist/elements.d.ts +589 -167
  186. package/dist/{elements/chunks/ellipsis-CVxqfsDZ.js → ellipsis-DFr4Vqr3.js} +1 -1
  187. package/dist/embed-5s_vU4J0.js +1 -0
  188. package/dist/engine-javascript-vq0WuIJl.js +14 -2516
  189. package/dist/{elements/chunks/external-link-DG2sRdQL.js → external-link-u9d85ORQ.js} +1 -1
  190. package/dist/file-text-CX7_x42o.js +1 -0
  191. package/dist/{elements/chunks/file-tree-yGWNK0Js.js → file-tree-D3kBjXI5.js} +1 -1
  192. package/dist/folder-V-YdIL8f.js +1 -0
  193. package/dist/form-BRNUMCN2.js +1 -0
  194. package/dist/github-dark-dimmed-DUshB20C.js +1 -4
  195. package/dist/github-light-JYsPkUQd.js +1 -4
  196. package/dist/hover-card-DaeiYrTA.js +1 -0
  197. package/dist/html-CPZ3oZQ7.js +1 -10
  198. package/dist/icon-BpixdZNq.js +1 -0
  199. package/dist/{elements/chunks/index-BCRhi84a.js → index-xu_pdcPi.js} +1 -1
  200. package/dist/index.d.ts +3 -1
  201. package/dist/index.js +8473 -6470
  202. package/dist/index.server.js +6495 -4958
  203. package/dist/info-8mFsIjS3.js +1 -0
  204. package/dist/input-d1Yuu-hz.js +1 -0
  205. package/dist/javascript-C25yR2R2.js +1 -6
  206. package/dist/json-DxJze_jm.js +1 -6
  207. package/dist/kai-provider.es.js +40 -40
  208. package/dist/kai.es.js +1 -1
  209. package/dist/kbd-961zMPFB.js +1 -0
  210. package/dist/link-DlZZBGaR.js +1 -0
  211. package/dist/{elements/chunks/link-preview-Cf5KOP-i.js → link-preview-DIIbV2Sd.js} +1 -1
  212. package/dist/loader-YQszHFaW.js +1 -0
  213. package/dist/{elements/chunks/markdown-DbozyYqa.js → markdown-CVk1MPsj.js} +1 -1
  214. package/dist/mcp.es.js +3873 -544
  215. package/dist/message-CwKVEdZg.js +1 -0
  216. package/dist/message-DS8lzHc1.js +1 -0
  217. package/dist/message-circle-Xf8Q-vZp.js +1 -0
  218. package/dist/message-square-Cxnwgkns.js +1 -0
  219. package/dist/{elements/chunks/minimize-2-SegJ_oku.js → minimize-2-UEZmP2QT.js} +1 -1
  220. package/dist/{elements/chunks/model-switcher-D7JrFFHp.js → model-switcher-Kk1jEQ5t.js} +1 -1
  221. package/dist/{elements/chunks/overlay-CV7z2YuX.js → overlay-C1od6LOz.js} +1 -1
  222. package/dist/panel-right-ryET0yyn.js +1 -0
  223. package/dist/paperclip-BDNoZFJP.js +1 -0
  224. package/dist/play-OFIvYXUQ.js +1 -0
  225. package/dist/primitives/card-data-types.d.ts +49 -0
  226. package/dist/primitives/card-routing.d.ts +1 -33
  227. package/dist/primitives/card-validate.d.ts +2 -2
  228. package/dist/primitives/chat-config.d.ts +29 -2
  229. package/dist/primitives/field-mask.d.ts +96 -0
  230. package/dist/primitives/field-semantics.d.ts +36 -0
  231. package/dist/primitives/input-mask.d.ts +46 -0
  232. package/dist/primitives/url-scheme-policy.d.ts +34 -0
  233. package/dist/primitives/use-resize-observer.d.ts +23 -0
  234. package/dist/primitives/use-speech-recognition.d.ts +8 -0
  235. package/dist/primitives/use-text-stream.d.ts +5 -0
  236. package/dist/progress-bar-BvYX0aoy.js +1 -0
  237. package/dist/prompt-suggestion-BRFu2lcu.js +1 -0
  238. package/dist/radio-DcL23cNJ.js +1 -0
  239. package/dist/react/index.d.ts +306 -283
  240. package/dist/react.js +309 -247
  241. package/dist/reasoning-Cackq4Rh.js +1 -0
  242. package/dist/register-impl-C7rENvPK.js +424 -0
  243. package/dist/{elements/chunks/resizable-Dpzjlvp4.js → resizable-Cp9HUYih.js} +1 -1
  244. package/dist/rotate-ccw-0DLRDHH5.js +1 -0
  245. package/dist/rotate-cw-DOB9IGcU.js +1 -0
  246. package/dist/schemas/form.schema.json +21 -1
  247. package/dist/schemas/index.d.ts +1 -1
  248. package/dist/schemas/tool-defs.d.ts +45 -0
  249. package/dist/schemas.js +398 -305
  250. package/dist/scroll-area-emVmWP4K.js +1 -0
  251. package/dist/scroll-button-CeP_gv1x.js +1 -0
  252. package/dist/select-CWmu00tS.js +1 -0
  253. package/dist/{elements/chunks/separator-DH2r_ZSZ.js → separator-CE6vly6z.js} +1 -1
  254. package/dist/{elements/chunks/settings-BFMOd6yB.js → settings-CE-78SvZ.js} +1 -1
  255. package/dist/{elements/chunks/settings-group-B3VZKIcz.js → settings-group-ZFWNxFQR.js} +1 -1
  256. package/dist/{elements/chunks/skeleton-N8BuWYmf.js → skeleton-BebdP4mf.js} +1 -1
  257. package/dist/slider-BAKRYUlz.js +1 -0
  258. package/dist/slots-DaNjX0rc.js +1 -0
  259. package/dist/{solid-BMuDmFo_.js → solid-BE5ui6sy.js} +10672 -8116
  260. package/dist/{solid-CGYNdLRR.js → solid-DFhsce3O.js} +8137 -6056
  261. package/dist/solid.d.ts +15 -0
  262. package/dist/solid.js +248 -235
  263. package/dist/solid.server.js +248 -235
  264. package/dist/source-B0FlguSx.js +1 -0
  265. package/dist/star-CBMZvLuN.js +1 -0
  266. package/dist/state/index.d.ts +5 -1
  267. package/dist/state/mock.d.ts +32 -2
  268. package/dist/state/persistence.d.ts +51 -0
  269. package/dist/state/stream.d.ts +50 -0
  270. package/dist/state/threads.d.ts +71 -0
  271. package/dist/state.js +297 -172
  272. package/dist/store-7eoadydQ.js +1 -0
  273. package/dist/svelte--5p79yCD.js +1 -15
  274. package/dist/switch-CViARZCy.js +1 -0
  275. package/dist/tasks-card-MjCcOhR_.js +1 -0
  276. package/dist/text-shimmer-DKlwRF2G.js +1 -0
  277. package/dist/textarea-3ay2Me2-.js +1 -0
  278. package/dist/theme.tokens.css +82 -13
  279. package/dist/{elements/chunks/thumbs-up-DQe0hvkZ.js → thumbs-up-DQcu2HMO.js} +1 -1
  280. package/dist/toast-store-C6yKOhbk.js +1 -0
  281. package/dist/{elements/chunks/tool-C1YtxwPu.js → tool-BCYNlfKy.js} +1 -1
  282. package/dist/{elements/chunks/tooltip-BqwnFWhn.js → tooltip-05zI4hjs.js} +1 -1
  283. package/dist/trash-2-BR1RxGky.js +1 -0
  284. package/dist/{elements/chunks/triangle-alert-B6or7F5u.js → triangle-alert-DHohDOCy.js} +1 -1
  285. package/dist/tsx-B8rCNbgL.js +1 -6
  286. package/dist/types.d.ts +4 -2
  287. package/dist/typescript-RycA9KXf.js +1 -6
  288. package/dist/ui/checkbox-group.d.ts +83 -0
  289. package/dist/ui/checkbox.d.ts +40 -0
  290. package/dist/ui/dock.d.ts +131 -0
  291. package/dist/ui/hover-card.d.ts +44 -0
  292. package/dist/ui/icon.d.ts +3 -0
  293. package/dist/ui/input.d.ts +48 -2
  294. package/dist/ui/radio.d.ts +85 -0
  295. package/dist/ui/select.d.ts +71 -0
  296. package/dist/ui/slider.d.ts +65 -0
  297. package/dist/ui/switch.d.ts +9 -2
  298. package/dist/upload-BbGW6A_u.js +1 -0
  299. package/dist/url-scheme-policy-DHpoTJwB.js +1 -0
  300. package/dist/use-card-resolution-CJW4grFa.js +1 -0
  301. package/dist/{variant-aurora-DwSOfraQ.js → variant-aurora-BUOVgPOB.js} +27 -27
  302. package/dist/{variant-aurora-wKvAQ5wp.js → variant-aurora-CUsUJvFI.js} +28 -28
  303. package/dist/{variant-aurora-XEkPT0_z.js → variant-aurora-CW2i4bfr.js} +2 -2
  304. package/dist/{elements/chunks/variant-aurora-QXTZPyST.js → variant-aurora-xiMPty8f.js} +2 -2
  305. package/dist/variant-custom-BQtOnmeO.js +1 -0
  306. package/dist/{variant-custom-BhS9wIpU.js → variant-custom-CAm9L-E5.js} +65 -65
  307. package/dist/{variant-custom-eFIg2XWQ.js → variant-custom-CAnzzppW.js} +40 -40
  308. package/dist/variant-custom-D91HjAKz.js +1 -0
  309. package/dist/{variant-wave-CUWGx9Bn.js → variant-wave-Ca1Ls4_y.js} +21 -21
  310. package/dist/{variant-wave-CbVsmgwT.js → variant-wave-CxX39DKd.js} +25 -25
  311. package/dist/{variant-wave-BwzrHD1s.js → variant-wave-DYyzIIZv.js} +2 -2
  312. package/dist/{elements/chunks/variant-wave-DDCwhLas.js → variant-wave-uY5crU_K.js} +2 -2
  313. package/dist/vue-BmIZj4XD.js +1 -33
  314. package/dist/wire/chunk.d.ts +47 -0
  315. package/dist/wire/diagnostics.d.ts +575 -0
  316. package/dist/wire/encode-probe.d.ts +70 -0
  317. package/dist/wire/encode.d.ts +24 -0
  318. package/dist/wire/index.d.ts +2 -0
  319. package/dist/wire/sse.d.ts +10 -2
  320. package/dist/wire.js +917 -480
  321. package/dist/x-C5m1JFgi.js +1 -0
  322. package/frameworks/react/index.tsx +325 -108
  323. package/llms-full.txt +1354 -82
  324. package/llms.txt +6 -3
  325. package/package.json +39 -6
  326. package/src/agent-tooling/archetypes.ts +41 -9
  327. package/src/agent-tooling/catalog/README.md +676 -0
  328. package/src/agent-tooling/catalog/catalog-types.ts +231 -0
  329. package/src/agent-tooling/catalog/fabrications.ts +96 -0
  330. package/src/agent-tooling/catalog/invariants.ts +284 -0
  331. package/src/agent-tooling/catalog/labs-titles.ts +114 -0
  332. package/src/agent-tooling/catalog/scenarios.ts +87 -0
  333. package/src/agent-tooling/catalog/surfaces.ts +347 -0
  334. package/src/agent-tooling/construct/cli-entry.ts +8 -0
  335. package/src/agent-tooling/construct/cli.ts +131 -0
  336. package/src/agent-tooling/construct/codegen.ts +1353 -0
  337. package/src/agent-tooling/construct/construct.v1.schema.json +250 -0
  338. package/src/agent-tooling/construct/dev.ts +148 -0
  339. package/src/agent-tooling/construct/fixtures/demo-widget.construct.json +7 -0
  340. package/src/agent-tooling/construct/fixtures/ops-console.construct.json +44 -0
  341. package/src/agent-tooling/construct/fixtures/owner-widget.construct.json +23 -0
  342. package/src/agent-tooling/construct/schema.ts +371 -0
  343. package/src/agent-tooling/integrations/anthropic.ts +7 -1
  344. package/src/agent-tooling/integrations/cloudflare.ts +43 -2
  345. package/src/agent-tooling/integrations/langgraph.ts +7 -1
  346. package/src/agent-tooling/integrations/mastra.ts +7 -1
  347. package/src/agent-tooling/integrations/mock.ts +92 -17
  348. package/src/agent-tooling/integrations/ollama.ts +7 -1
  349. package/src/agent-tooling/integrations/openai.ts +7 -1
  350. package/src/agent-tooling/integrations/openrouter.ts +7 -1
  351. package/src/agent-tooling/integrations/pi.ts +56 -1
  352. package/src/agent-tooling/integrations/vercel-ai-sdk.ts +7 -1
  353. package/src/agent-tooling/mcp/css-raw.d.ts +12 -0
  354. package/src/agent-tooling/mcp/manifest.ts +100 -1
  355. package/src/agent-tooling/mcp/server.ts +63 -6
  356. package/src/agent-tooling/mcp/tools/construct.ts +130 -0
  357. package/src/agent-tooling/mcp/tools/debug.ts +126 -4
  358. package/src/agent-tooling/mcp/tools/reference.ts +542 -5
  359. package/src/agent-tooling/mcp/tools/scaffold.ts +1834 -307
  360. package/src/agent-tooling/mcp/tools/theme.ts +146 -30
  361. package/src/agent-tooling/mcp/validate-args.ts +141 -0
  362. package/src/agent-tooling/recipes/composed-thread.ts +714 -0
  363. package/src/agent-tooling/recipes/index.ts +22 -0
  364. package/src/agent-tooling/recipes/types.ts +32 -0
  365. package/src/agent-tooling/registry.ts +24 -7
  366. package/src/agent-tooling/route-emit.ts +26 -2
  367. package/src/components/attachment-types.ts +54 -1
  368. package/src/components/attachments.tsx +169 -22
  369. package/src/components/audio-visualizer/fit-scale.ts +112 -0
  370. package/src/components/audio-visualizer/index.tsx +80 -3
  371. package/src/components/audio-visualizer/variant-aurora.tsx +10 -4
  372. package/src/components/audio-visualizer/variant-bar.tsx +38 -6
  373. package/src/components/audio-visualizer/variant-custom.tsx +8 -2
  374. package/src/components/audio-visualizer/variant-grid.tsx +16 -11
  375. package/src/components/audio-visualizer/variant-radial.tsx +15 -10
  376. package/src/components/audio-visualizer/variant-wave.tsx +7 -2
  377. package/src/components/card-renderer.tsx +39 -8
  378. package/src/components/card.tsx +10 -2
  379. package/src/components/chat-container.tsx +4 -1
  380. package/src/components/chat-scope-picker.tsx +2 -2
  381. package/src/components/chat-thread.tsx +105 -13
  382. package/src/components/choice-card.tsx +102 -37
  383. package/src/components/coachmark.tsx +1 -1
  384. package/src/components/code-block.tsx +81 -5
  385. package/src/components/composer.tsx +89 -8
  386. package/src/components/confirm-card.tsx +20 -4
  387. package/src/components/conversation-item.tsx +138 -4
  388. package/src/components/conversation-list.tsx +230 -15
  389. package/src/components/embed.tsx +4 -0
  390. package/src/components/empty.tsx +14 -3
  391. package/src/components/file-tree.tsx +5 -1
  392. package/src/components/form-widgets.tsx +257 -195
  393. package/src/components/form.tsx +336 -21
  394. package/src/components/loader.tsx +12 -5
  395. package/src/components/message-skills.tsx +1 -1
  396. package/src/components/message.tsx +142 -12
  397. package/src/components/prompt-input.tsx +7 -1
  398. package/src/components/prompt-suggestion.tsx +4 -1
  399. package/src/components/reasoning.tsx +30 -3
  400. package/src/components/response-compare.tsx +2 -2
  401. package/src/components/response-stream.tsx +13 -7
  402. package/src/components/screen.tsx +5 -0
  403. package/src/components/scroll-button.tsx +63 -4
  404. package/src/components/source.tsx +6 -1
  405. package/src/components/tasks-card.tsx +58 -16
  406. package/src/components/thread.tsx +10 -1
  407. package/src/components/toast.tsx +11 -5
  408. package/src/components/tool-types.ts +9 -0
  409. package/src/components/voice-input.tsx +35 -3
  410. package/src/components/voice-output.tsx +46 -6
  411. package/src/components/workspace-shell.tsx +285 -0
  412. package/src/diagnostics/hook.ts +352 -0
  413. package/src/diagnostics/index.ts +82 -0
  414. package/src/diagnostics/report-request.ts +296 -0
  415. package/src/elements/attachments.tsx +9 -8
  416. package/src/elements/audio-visualizer.tsx +48 -4
  417. package/src/elements/chat-workspace.tsx +159 -287
  418. package/src/elements/chat.tsx +31 -10
  419. package/src/elements/checkbox-group.tsx +164 -0
  420. package/src/elements/checkbox.tsx +134 -0
  421. package/src/elements/code-block.tsx +19 -1
  422. package/src/elements/command.tsx +4 -1
  423. package/src/elements/compiled.css +1 -1
  424. package/src/elements/composer.tsx +12 -1
  425. package/src/elements/conversation-item.tsx +120 -0
  426. package/src/elements/conversation-list.tsx +60 -11
  427. package/src/elements/default-input.tsx +5 -5
  428. package/src/elements/define-entry.ts +12 -0
  429. package/src/elements/define.tsx +168 -8
  430. package/src/elements/diagnostic-events.ts +114 -0
  431. package/src/elements/dialog.tsx +47 -0
  432. package/src/elements/disclosure.ts +34 -12
  433. package/src/elements/dock.tsx +215 -0
  434. package/src/elements/dropdown.tsx +123 -0
  435. package/src/elements/element-data-types.ts +40 -0
  436. package/src/elements/element-diagnostics.ts +392 -0
  437. package/src/elements/element-manifest.json +36 -0
  438. package/src/elements/element-meta.json +1483 -560
  439. package/src/elements/element-nonscalar.json +185 -0
  440. package/src/elements/element-types.d.ts +589 -167
  441. package/src/elements/feedback-bar.tsx +5 -0
  442. package/src/elements/icon-names.json +29 -0
  443. package/src/elements/input.tsx +312 -23
  444. package/src/elements/menu.tsx +13 -0
  445. package/src/elements/message.tsx +2 -0
  446. package/src/elements/pane-grid.tsx +111 -0
  447. package/src/elements/prompt-dock.tsx +2 -1
  448. package/src/elements/prompt-input.tsx +16 -10
  449. package/src/elements/radio-group.tsx +129 -0
  450. package/src/elements/register-impl.ts +52 -0
  451. package/src/elements/register.ts +28 -0
  452. package/src/elements/resizable.tsx +52 -25
  453. package/src/elements/scroll-button.tsx +59 -6
  454. package/src/elements/select.tsx +165 -0
  455. package/src/elements/slider.tsx +171 -0
  456. package/src/elements/slots.ts +108 -13
  457. package/src/elements/styles.css +119 -9
  458. package/src/elements/thinking-bar.tsx +16 -7
  459. package/src/elements/thread.tsx +2 -0
  460. package/src/elements/toast.tsx +17 -1
  461. package/src/elements/tool.tsx +26 -5
  462. package/src/elements/voice-input.tsx +11 -2
  463. package/src/elements/voice-output.tsx +16 -3
  464. package/src/index.ts +4 -1
  465. package/src/primitives/card-data-types.ts +49 -0
  466. package/src/primitives/card-host.tsx +4 -2
  467. package/src/primitives/card-routing.ts +7 -56
  468. package/src/primitives/card-schemas/form.schema.json +21 -1
  469. package/src/primitives/card-validate.ts +10 -2
  470. package/src/primitives/chat-config.tsx +33 -6
  471. package/src/primitives/field-mask.ts +256 -0
  472. package/src/primitives/field-semantics.ts +115 -0
  473. package/src/primitives/input-mask.ts +853 -0
  474. package/src/primitives/toast-store.ts +66 -4
  475. package/src/primitives/url-scheme-policy.ts +70 -0
  476. package/src/primitives/use-resize-observer.ts +33 -0
  477. package/src/primitives/use-speech-recognition.ts +12 -1
  478. package/src/primitives/use-stick-to-bottom.ts +34 -2
  479. package/src/primitives/use-text-stream.ts +33 -2
  480. package/src/remote/provider-runtime.ts +6 -3
  481. package/src/schemas/index.ts +1 -0
  482. package/src/schemas/tool-defs.ts +307 -6
  483. package/src/solid.ts +15 -0
  484. package/src/state/index.ts +13 -1
  485. package/src/state/mock.ts +92 -6
  486. package/src/state/persistence.ts +180 -0
  487. package/src/state/stream.ts +71 -4
  488. package/src/state/threads.ts +142 -0
  489. package/src/types.ts +4 -2
  490. package/src/ui/agent-card.tsx +2 -2
  491. package/src/ui/avatar.tsx +1 -1
  492. package/src/ui/checkbox-group.tsx +153 -0
  493. package/src/ui/checkbox.tsx +63 -0
  494. package/src/ui/dialog.tsx +6 -0
  495. package/src/ui/dock.tsx +584 -0
  496. package/src/ui/dropdown.tsx +67 -23
  497. package/src/ui/hover-card.tsx +156 -6
  498. package/src/ui/icon.tsx +53 -0
  499. package/src/ui/input.tsx +275 -16
  500. package/src/ui/kbd.tsx +1 -1
  501. package/src/ui/nav.tsx +1 -1
  502. package/src/ui/pane-group.tsx +2 -2
  503. package/src/ui/radio.tsx +150 -0
  504. package/src/ui/resizable.tsx +11 -0
  505. package/src/ui/select.tsx +168 -0
  506. package/src/ui/slider.tsx +178 -0
  507. package/src/ui/switch.tsx +49 -18
  508. package/src/utils/cn.ts +11 -5
  509. package/src/wire/chunk.ts +47 -0
  510. package/src/wire/consume.ts +185 -26
  511. package/src/wire/diagnostics.ts +727 -0
  512. package/src/wire/encode-probe.ts +214 -0
  513. package/src/wire/encode.ts +267 -14
  514. package/src/wire/formats/anthropic.ts +8 -1
  515. package/src/wire/formats/openai.ts +5 -0
  516. package/src/wire/index.ts +22 -0
  517. package/src/wire/read.ts +250 -6
  518. package/src/wire/sse.ts +16 -3
  519. package/theme.css +84 -13
  520. package/dist/elements/chunks/action-icons-DW9muWrY.js +0 -1
  521. package/dist/elements/chunks/arrow-left-iJVnQmxA.js +0 -1
  522. package/dist/elements/chunks/artifact-BSuPYFHY.js +0 -1
  523. package/dist/elements/chunks/attachments-CL6TaqIb.js +0 -1
  524. package/dist/elements/chunks/audio-visualizer-BuUqQmaO.js +0 -1
  525. package/dist/elements/chunks/badge-TIMiJFKc.js +0 -1
  526. package/dist/elements/chunks/bash-InADTalH.js +0 -1
  527. package/dist/elements/chunks/card-renderer-CSoTL_e1.js +0 -1
  528. package/dist/elements/chunks/card-routing-BK09BdmL.js +0 -1
  529. package/dist/elements/chunks/chat-thread-DxoKaStk.js +0 -1
  530. package/dist/elements/chunks/check-MdLzbxrm.js +0 -1
  531. package/dist/elements/chunks/chevron-down-MK7IzDqe.js +0 -1
  532. package/dist/elements/chunks/chevron-right-C5jXhmtV.js +0 -1
  533. package/dist/elements/chunks/choice-card-CGMc3kek.js +0 -1
  534. package/dist/elements/chunks/circle-BG7VkPep.js +0 -1
  535. package/dist/elements/chunks/circle-check-GqvO2UBs.js +0 -1
  536. package/dist/elements/chunks/circle-x-mhH7pOK2.js +0 -1
  537. package/dist/elements/chunks/code-block-ClhQO9RW.js +0 -1
  538. package/dist/elements/chunks/collapsible-DBcBkFPh.js +0 -1
  539. package/dist/elements/chunks/composer-CZZmtYOT.js +0 -69
  540. package/dist/elements/chunks/confirm-card-BWjf2-xo.js +0 -1
  541. package/dist/elements/chunks/context-hKjNqiyQ.js +0 -1
  542. package/dist/elements/chunks/conversation-list-CsGJjSKG.js +0 -1
  543. package/dist/elements/chunks/core-AYMC6_lb.js +0 -12
  544. package/dist/elements/chunks/css-M7EaDHN_.js +0 -1
  545. package/dist/elements/chunks/default-input-B1Mem5wu.js +0 -1
  546. package/dist/elements/chunks/define-B_tXMYRm.js +0 -1
  547. package/dist/elements/chunks/disclosure-DAoYOKew.js +0 -1
  548. package/dist/elements/chunks/download-DnCyWFJE.js +0 -1
  549. package/dist/elements/chunks/dropdown-CmNna5lT.js +0 -1
  550. package/dist/elements/chunks/embed-CjAD2jSi.js +0 -1
  551. package/dist/elements/chunks/engine-javascript-vq0WuIJl.js +0 -141
  552. package/dist/elements/chunks/file-text-D0J9F6M7.js +0 -1
  553. package/dist/elements/chunks/folder-NV1pi_ud.js +0 -1
  554. package/dist/elements/chunks/form-Cp8Y00Wn.js +0 -1
  555. package/dist/elements/chunks/github-dark-dimmed-DUshB20C.js +0 -1
  556. package/dist/elements/chunks/github-light-JYsPkUQd.js +0 -1
  557. package/dist/elements/chunks/hover-card-6KtXpLwT.js +0 -1
  558. package/dist/elements/chunks/html-CPZ3oZQ7.js +0 -1
  559. package/dist/elements/chunks/icon-97R8SR2p.js +0 -1
  560. package/dist/elements/chunks/info-C8r5Q6cc.js +0 -1
  561. package/dist/elements/chunks/input-DrseEjhp.js +0 -1
  562. package/dist/elements/chunks/javascript-C25yR2R2.js +0 -1
  563. package/dist/elements/chunks/json-DxJze_jm.js +0 -1
  564. package/dist/elements/chunks/kbd-OGJDUT-O.js +0 -1
  565. package/dist/elements/chunks/link-BdsfLB7b.js +0 -1
  566. package/dist/elements/chunks/loader-B5QzK_8_.js +0 -1
  567. package/dist/elements/chunks/message-CE4k-qaj.js +0 -1
  568. package/dist/elements/chunks/message-DtXKPqiR.js +0 -1
  569. package/dist/elements/chunks/message-square-C9ovmtOo.js +0 -1
  570. package/dist/elements/chunks/paperclip-Bg0ITU3b.js +0 -1
  571. package/dist/elements/chunks/progress-bar-D1_rzt6N.js +0 -1
  572. package/dist/elements/chunks/prompt-suggestion-D5Sm8xF1.js +0 -1
  573. package/dist/elements/chunks/reasoning-BrxQfpfI.js +0 -1
  574. package/dist/elements/chunks/rotate-cw-DzVDJOA1.js +0 -1
  575. package/dist/elements/chunks/scroll-area-93A1bbeG.js +0 -1
  576. package/dist/elements/chunks/scroll-button-CrTo7wm4.js +0 -1
  577. package/dist/elements/chunks/share-DgNbkJKY.js +0 -1
  578. package/dist/elements/chunks/slots-CIw9RlAe.js +0 -1
  579. package/dist/elements/chunks/source-w9V0NnBn.js +0 -1
  580. package/dist/elements/chunks/star-DHvACxtm.js +0 -1
  581. package/dist/elements/chunks/store-HIPGhNz9.js +0 -1
  582. package/dist/elements/chunks/svelte--5p79yCD.js +0 -1
  583. package/dist/elements/chunks/tasks-card-lPxoodlA.js +0 -1
  584. package/dist/elements/chunks/text-shimmer-BY_rI3s5.js +0 -1
  585. package/dist/elements/chunks/textarea-DPfuhswM.js +0 -1
  586. package/dist/elements/chunks/toast-store-VUZZBhzH.js +0 -1
  587. package/dist/elements/chunks/tsx-B8rCNbgL.js +0 -1
  588. package/dist/elements/chunks/typescript-RycA9KXf.js +0 -1
  589. package/dist/elements/chunks/use-card-resolution-DJg32jGH.js +0 -1
  590. package/dist/elements/chunks/variant-custom-D5ZlmziF.js +0 -1
  591. package/dist/elements/chunks/video-Bb86fVYI.js +0 -1
  592. package/dist/elements/chunks/vue-BmIZj4XD.js +0 -1
  593. package/dist/elements/chunks/x-BOhjBeOd.js +0 -1
  594. package/dist/llms/llms-full.txt +0 -2834
  595. package/dist/llms/llms.txt +0 -156
  596. package/dist/primitives/card-validate-generator.testlib.d.ts +0 -20
  597. package/dist/register-impl-DGmGzrEM.js +0 -145
  598. package/dist/ui/stat.d.ts +0 -16
  599. package/dist/variant-custom-MkTNTycd.js +0 -1
  600. package/src/primitives/card-validate-generator.testlib.ts +0 -38
  601. package/src/ui/stat.tsx +0 -41
  602. /package/dist/{elements/chunks/card-tags-D8lZ-C_U.js → card-tags-D8lZ-C_U.js} +0 -0
  603. /package/dist/{elements/chunks/link-preview-DNILK391.js → link-preview-DNILK391.js} +0 -0
@@ -11,6 +11,7 @@ import {
11
11
  For,
12
12
  } from 'solid-js';
13
13
  import { cn } from '../utils/cn';
14
+ import { useChatConfig, textClass } from '../primitives/chat-config';
14
15
  import {
15
16
  type ComposerDoc,
16
17
  type EntityRef,
@@ -242,6 +243,7 @@ function setCaretToOffset(root: HTMLElement, offset: number): void {
242
243
  }
243
244
 
244
245
  export function Composer(props: ComposerProps): JSX.Element {
246
+ const config = useChatConfig();
245
247
  let editable!: HTMLDivElement;
246
248
  // A SIGNAL (not a plain `let`): usePosition's floating ref must be reactive so
247
249
  // Floating UI recomputes once the menu mounts. A non-reactive ref left the menu
@@ -799,16 +801,78 @@ export function Composer(props: ComposerProps): JSX.Element {
799
801
  // This is covered by updateTriggerState() in handleInput.
800
802
 
801
803
  const maxH = () => props.maxHeight ?? 240;
804
+ // This element is `contenteditable` with `role="textbox"` — a real tab stop,
805
+ // and the primary one in the kit — so it must never be left with NO focus
806
+ // affordance. WHICH element draws it depends on who owns the frame:
807
+ //
808
+ // - Standalone (the `else` branch below renders the rounded shell): the FRAME
809
+ // owns the ring via `focus-within:ring-*`, exactly like `PromptInput`, so
810
+ // the editable neutralizes its own. The editable is a square, full-bleed
811
+ // box inside a `rounded-xl` shell, so an outline on IT painted a sharp
812
+ // rectangle hugging the text region inside the rounded border — the bug.
813
+ // - Bare: the host owns radius/bg/padding and may draw nothing at all
814
+ // (`PromptInput` passes its own `editableClass` and rings its frame), so
815
+ // the editable keeps `kai-focus-inset` — a ring inside the border box,
816
+ // since an outset one would be clipped by a rounded host shell.
817
+ //
818
+ // A consumer passing `editableClass` still replaces this wholesale — including
819
+ // the `text-start` pin below, so a replacement string owes its own (`PromptInput`,
820
+ // the one in-repo caller, carries it).
802
821
  const editableCls = () =>
803
822
  props.editableClass ??
804
- 'text-foreground min-h-[44px] w-full overflow-y-auto outline-none whitespace-pre-wrap break-words';
823
+ cn(
824
+ // `text-start` is a PIN, not a style choice: `text-align` inherits, so any
825
+ // centered ancestor (`Empty`'s root did exactly this) reached in and centered
826
+ // the placeholder AND the typed text. An input control's text alignment is a
827
+ // fact about the control, so it states it rather than inheriting it. LOGICAL
828
+ // (`start`), not `text-left` — in RTL the correct edge is the right one, and
829
+ // pinning the physical value would be a worse bug than the one it fixes.
830
+ 'text-foreground min-h-[44px] w-full text-start overflow-y-auto whitespace-pre-wrap break-words',
831
+ // Size from the SAME token every other text surface reads (`ChatConfig`'s
832
+ // proseSize, default `sm` = the 14px the rest of the kit sets explicitly) —
833
+ // `Markdown`, `MessageContent` and `PromptInputTextarea` all go through
834
+ // `textClass`. Without it the editable set no size at all and just inherited
835
+ // whatever the shadow root's base was (16px in a stock page), so the composer
836
+ // read a step larger than every component beside it. Going through the token
837
+ // also means `proseSize` moves the composer like it moves `PromptInput`,
838
+ // rather than leaving one input deaf to it.
839
+ textClass(config.proseSize()),
840
+ props.bare
841
+ ? 'kai-focus-inset'
842
+ : 'outline-none focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0',
843
+ );
805
844
 
806
845
  const inner = (
807
846
  <>
808
847
  {/* Static style for CSS Custom Highlight API decoration.
809
848
  No-op in browsers that don't support ::highlight(); the selector
810
849
  is simply unrecognized and dropped. */}
811
- <style>{`::highlight(${highlightName}) { background-color: color-mix(in srgb, var(--color-primary, #6366f1) 22%, transparent); }`}</style>
850
+ <style>{`
851
+ /* MARKED text, not selected text. Reads --color-highlight (overridable via
852
+ --kai-color-highlight), a warm <mark> yellow. It used to be a 22% tint of
853
+ --color-primary with a #6366f1 indigo fallback — but the kit's
854
+ --color-primary is a NEUTRAL (near-black in light, near-white in dark),
855
+ so the indigo the fallback implied never appeared and the highlight
856
+ rendered plain grey in both themes.
857
+
858
+ NO \`prefers-color-scheme\` here, deliberately — the same trap that put
859
+ the pill hues below at 2.33:1. The OS scheme and the kit's RESOLVED
860
+ theme are different questions, and \`theme="light"\` on a dark-OS machine
861
+ is an ordinary configuration; a scheme-keyed value would paint the dark
862
+ amber on the light field. The pills answer this by scoping to \`.dark\`,
863
+ which is NOT available here: \`::highlight()\` names a document-registered
864
+ highlight, so there is no wrapper to hang a descendant selector on.
865
+
866
+ It does not need one. \`--color-highlight\` reaches this pseudo through
867
+ highlight inheritance from the ORIGINATING element — the text inside the
868
+ \`.dark\` wrapper — so the token already answers the resolved theme,
869
+ verified by rendering the dark amber under \`theme="dark"\` with only the
870
+ LIGHT literal below as a fallback. The literal is therefore reachable
871
+ only when no kit stylesheet is loaded at all, and it is deliberately the
872
+ light one: an unstyled page is a white page, and guessing from the OS is
873
+ exactly the failure above. */
874
+ ::highlight(${highlightName}) { background-color: var(--color-highlight, hsl(45 96% 78%)); }
875
+ `}</style>
812
876
  {/* Atomic entity pill styling. Self-contained (currentColor-based) so it
813
877
  renders correctly without depending on a Tailwind rebuild for the
814
878
  custom .kai-composer-pill class. */}
@@ -839,12 +903,24 @@ export function Composer(props: ComposerProps): JSX.Element {
839
903
  .kai-composer-pill[data-kind="agent"] { color: var(--kai-pill-agent, #7c3aed); }
840
904
  /* Brighter hues on dark so they don't muddy against a dark field. The
841
905
  consumer's --kai-pill-* override still wins (it's the var value); only
842
- the FALLBACK shifts per scheme. */
843
- @media (prefers-color-scheme: dark) {
844
- .kai-composer-pill[data-kind="skill"] { color: var(--kai-pill-skill, #6ea8fe); }
845
- .kai-composer-pill[data-kind="agent"] { color: var(--kai-pill-agent, #c4a7fc); }
846
- }
847
- .kai-composer-pill-sigil { opacity: 0.6; font-weight: 600; }
906
+ the FALLBACK shifts per scheme.
907
+
908
+ Keyed on the kit's RESOLVED theme (the \`.dark\` wrapper an element
909
+ facade puts inside its shadow root, and the \`.dark\` class the docs /
910
+ Storybook put on <html>) — NOT \`prefers-color-scheme\`. Those are
911
+ different questions: \`theme="light"\` on a machine whose OS is dark is
912
+ an ordinary, common configuration, and the media query answered the OS
913
+ while the surface under the pill answered the attribute. That painted
914
+ the DARK hue (#6ea8fe) on the LIGHT field: 2.33:1, under both the
915
+ 4.5:1 text floor (SC 1.4.3) and even 3:1. Matched pairs looked fine,
916
+ which is why it survived — never scheme-match a colour whose backdrop
917
+ is chosen by the theme attribute. */
918
+ .dark .kai-composer-pill[data-kind="skill"] { color: var(--kai-pill-skill, #6ea8fe); }
919
+ .dark .kai-composer-pill[data-kind="agent"] { color: var(--kai-pill-agent, #c4a7fc); }
920
+ /* Dimmer than the label, but the sigil is what makes a pill read as
921
+ \`/skill\` vs \`@agent\`, so it is a meaningful glyph and owes 3:1
922
+ (SC 1.4.11). At 0.6 the light-mode composite was 2.52:1. */
923
+ .kai-composer-pill-sigil { opacity: 0.8; font-weight: 600; }
848
924
  /* Plugins (and any other/unknown kind): the richer CHIP — background +
849
925
  icon — so a composite bundle reads differently at a glance. */
850
926
  .kai-composer-pill:not([data-kind="skill"]):not([data-kind="agent"]) {
@@ -984,7 +1060,12 @@ export function Composer(props: ComposerProps): JSX.Element {
984
1060
  ) : (
985
1061
  <div
986
1062
  class={cn(
1063
+ // Same frame-owns-the-ring pattern as `PromptInput`: the editable inside
1064
+ // neutralizes its own outline (see `editableCls`), so this rounded shell
1065
+ // draws the focus affordance and the ring follows `rounded-xl` instead of
1066
+ // painting a square box around the inner text region.
987
1067
  'kai-composer relative rounded-xl bg-surface p-2',
1068
+ 'focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-0',
988
1069
  props.disabled && 'cursor-not-allowed opacity-60',
989
1070
  )}
990
1071
  >
@@ -18,7 +18,7 @@ import type { CardEnvelope, CardEvent, CardHost, CardResolution } from '../primi
18
18
  import { useCardResolution } from './use-card-resolution';
19
19
  import { emitCardEvent } from '../primitives/card-routing';
20
20
  import { useCardHost } from '../primitives/card-host';
21
- import { AlertTriangle, Check, X } from 'lucide-solid';
21
+ import { AlertTriangle, Check } from 'lucide-solid';
22
22
 
23
23
  // ─────────────────────────────────────────────────────────────────────────────
24
24
  // Types (confirm.schema.json) — AUTHORED IN ../primitives/card-data-types.ts.
@@ -316,14 +316,23 @@ export function ConfirmCard(props: ConfirmCardProps): JSX.Element {
316
316
  >
317
317
  <div class="flex w-full flex-wrap items-center justify-between gap-2">
318
318
  <Show when={local.data?.dismissible === true}>
319
+ {/* The contract `dismiss` verb — a footer ACTION (it emits
320
+ `{kind:'dismiss'}` and collapses the card to a re-openable
321
+ stub), not the chrome close in `Card`'s own `dismissible`
322
+ prop, which the contract cards deliberately never set.
323
+ Labelled rather than a bare ghost ✕: every other control in
324
+ this row says what it does, and an unlabelled 28px glyph
325
+ 300px from the primary button reads as stray chrome. Same
326
+ shape in all four cards — `form` already looked like this.
327
+ `aria-label` is kept (redundant with the text) so the
328
+ element tests that select on it keep working. */}
319
329
  <Button
320
330
  type="button"
321
331
  variant="ghost"
322
- size="icon-sm"
323
332
  aria-label="Dismiss"
324
333
  onClick={onDismiss}
325
334
  >
326
- <X size={16} aria-hidden="true" />
335
+ Dismiss
327
336
  </Button>
328
337
  </Show>
329
338
  <div ref={actionsRef} class="ml-auto flex flex-wrap items-center gap-2">
@@ -375,7 +384,14 @@ export function ConfirmCard(props: ConfirmCardProps): JSX.Element {
375
384
  <p class="text-sm font-semibold text-foreground">{local.data?.heading}</p>
376
385
  </Show>
377
386
  <Show when={local.data?.body}>
378
- <p class="text-sm text-foreground">{local.data?.body}</p>
387
+ {/* The supporting sentence sits directly under the heading it
388
+ supports, muted — the same contrast step the shell header makes
389
+ between `heading` and `description`. It stays in the BODY (not
390
+ the shell's `description` slot, where `form`'s and `choice`'s
391
+ live) because `data.heading` is a documented IN-BODY heading
392
+ that this sentence has to stay beneath, and because this div is
393
+ the Enter-to-confirm tab stop. */}
394
+ <p class="text-sm text-muted-foreground">{local.data?.body}</p>
379
395
  </Show>
380
396
  </div>
381
397
  </Card>
@@ -1,4 +1,4 @@
1
- import { Show, splitProps, createMemo } from 'solid-js';
1
+ import { Show, splitProps, createMemo, type JSX } from 'solid-js';
2
2
  import { MessageSquare } from 'lucide-solid';
3
3
  import { cn } from '../utils/cn';
4
4
  import type { ConversationSummary } from '../types';
@@ -31,12 +31,146 @@ export function relativeTimeShort(iso?: string, now: number = Date.now()): strin
31
31
  return `${Math.floor(hours / 24)}d ago`;
32
32
  }
33
33
 
34
+ /**
35
+ * The slotted-item shape rendered by `<kai-conversation-item>` — the composed
36
+ * row of the consumer-owned loop. Distinct from the
37
+ * data-mode `ConversationItem` above, which batteries mode keeps rendering
38
+ * unchanged: this one takes REGIONS, not a `ConversationSummary`.
39
+ *
40
+ * Regions map 1:1 to the element's slots: `children` = the default slot (the
41
+ * title), plus `leading`, `meta`, and `menu`. The `menu` region takes the
42
+ * consumer's OWN popover (rename / fork / archive live there); the component
43
+ * provides only the region plus focus and ARIA plumbing, never a declarative
44
+ * actions prop. Activation has two modes: inside `<kai-conversations>` it
45
+ * lives in the CONTAINER (`createConversationItemsController` in
46
+ * conversation-list.tsx — this row renders no handler and the
47
+ * `data-kai-item-menu` marker on the menu region is what the container's
48
+ * activation guard keys off so a click in the consumer's menu never also
49
+ * selects the row); STANDALONE, `onActivate` makes the row body
50
+ * its own tabbable button-role control — click / Enter / Space — with the menu
51
+ * still outside the control as the body's sibling.
52
+ *
53
+ * ARIA contract for direct Solid use: the row renders `role="listitem"` holding
54
+ * a `role="button"` body (`aria-current` marks the active row, the same dialect
55
+ * as the batteries-mode row above) with the menu as the body's tabbable SIBLING
56
+ * (axe `nested-interactive` bans focusable descendants of
57
+ * a control, and `aria-required-children` outlaws non-option content anywhere
58
+ * inside a listbox, which is why the vocabulary is not listbox/option). Render
59
+ * your loop's rows inside a `role="list"` ancestor; element mode has no such
60
+ * burden: `<kai-conversations>`' items region provides it for slotted items.
61
+ */
62
+ export interface SlottedConversationItemProps {
63
+ /** The row's identity, handed to the container's selection contract. In the
64
+ * element this is the `conversation-id` attribute (host `id` is the fallback). */
65
+ conversationId?: string;
66
+ /** Selected state. Reflected as `aria-current` on the row body and a
67
+ * `data-active` styling hook on the row; the container drives it from its
68
+ * `activeId`. */
69
+ active?: boolean;
70
+ /** Dense single-line row padding. */
71
+ compact?: boolean;
72
+ /** Leading region before the title (an icon or avatar). */
73
+ leading?: JSX.Element;
74
+ /** Meta region under the title (a timestamp or status line). */
75
+ meta?: JSX.Element;
76
+ /** Your own row menu (a popover trigger). Never selects the row. */
77
+ menu?: JSX.Element;
78
+ /** The title (the element's default slot). */
79
+ children?: JSX.Element;
80
+ /** Set when the element HOST carries the row-group semantics: the inner row
81
+ * then renders `role="presentation"` so the accessibility tree sees one
82
+ * group (the host), never two. The `kai-conversation-item` facade sets it;
83
+ * Solid consumers rendering the component directly leave it off and the row
84
+ * itself is the group. */
85
+ hostSemantics?: boolean;
86
+ /** STANDALONE activation: when set, the row body is itself the
87
+ * activation control — `tabindex="0"` on the `role="button"` body, and
88
+ * click / Enter / Space call this. The menu region never triggers it (it is
89
+ * the body's sibling, outside the control). Inside `<kai-conversations>`
90
+ * leave it UNSET: the container's controller owns activation (its delegated
91
+ * click/keydown → `kai-conversation-select`) plus roving tabindex, and a
92
+ * handler here would double-fire. The `kai-conversation-item` facade passes
93
+ * it only when the item is standalone. */
94
+ onActivate?: () => void;
95
+ class?: string;
96
+ }
97
+
98
+ export function SlottedConversationItem(props: SlottedConversationItemProps) {
99
+ const [local] = splitProps(props, ['conversationId', 'active', 'compact', 'leading', 'meta', 'menu', 'children', 'hostSemantics', 'onActivate', 'class']);
100
+ return (
101
+ // The sibling restructure: axe nested-interactive
102
+ // bans focusable descendants of an activation control, so the control role
103
+ // sits on the row BODY and the consumer's menu is its tabbable SIBLING
104
+ // (the nav.tsx TrailingActions precedent). The vocabulary is
105
+ // list/listitem/button + aria-current — NOT listbox/option, because axe
106
+ // aria-required-children lets a listbox subtree own nothing but options
107
+ // (and groups of options), which outlaws a sibling menu ANYWHERE inside
108
+ // it; button + aria-current is also exactly what the batteries-mode
109
+ // ConversationItem row above renders, so the two modes speak one dialect.
110
+ <div
111
+ part="row"
112
+ role={local.hostSemantics ? 'presentation' : 'listitem'}
113
+ data-active={local.active ? '' : undefined}
114
+ data-conversation-id={local.conversationId}
115
+ class={cn(
116
+ 'flex w-full items-center gap-2.5 rounded-lg text-left transition-colors',
117
+ local.compact ? 'px-2.5 py-1.5' : 'px-2.5 py-2',
118
+ local.active ? 'bg-muted' : 'hover:bg-muted/50',
119
+ local.class,
120
+ )}
121
+ >
122
+ <div
123
+ part="body"
124
+ role="button"
125
+ data-kai-item-body
126
+ aria-current={local.active ? 'true' : 'false'}
127
+ // Standalone activation only: with `onActivate` unset —
128
+ // the inside-a-container case — no tabindex and no handlers render, so
129
+ // the container's delegated activation stays the single path.
130
+ tabindex={local.onActivate ? 0 : undefined}
131
+ onClick={() => local.onActivate?.()}
132
+ onKeyDown={(e: KeyboardEvent) => {
133
+ if (!local.onActivate) return;
134
+ if (e.key === 'Enter' || e.key === ' ') {
135
+ e.preventDefault(); // Space must not scroll the page
136
+ local.onActivate();
137
+ }
138
+ }}
139
+ class="flex min-w-0 flex-1 cursor-pointer items-center gap-2.5 rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
140
+ >
141
+ <Show when={local.leading}>
142
+ <span part="leading" class="flex shrink-0 items-center text-muted-foreground">{local.leading}</span>
143
+ </Show>
144
+ <div class="min-w-0 flex-1">
145
+ <div part="title" class={cn('truncate text-sm', local.active ? 'font-medium text-foreground' : 'text-foreground/80')}>
146
+ {local.children}
147
+ </div>
148
+ <Show when={local.meta}>
149
+ <div part="meta" class="mt-0.5 truncate text-xs text-muted-foreground">{local.meta}</div>
150
+ </Show>
151
+ </div>
152
+ </div>
153
+ <Show when={local.menu}>
154
+ <span part="menu" data-kai-item-menu class="ml-auto flex shrink-0 items-center">{local.menu}</span>
155
+ </Show>
156
+ </div>
157
+ );
158
+ }
159
+
34
160
  export function ConversationItem(props: ConversationItemProps) {
35
161
  const [local] = splitProps(props, ['conversation', 'isActive', 'onSelect', 'compact', 'class']);
36
162
  // The trailing text: the consumer's own `trailing` field, else an auto relative
37
- // time from updatedAt (fallback lastMessageAt). REACTIVITY: this (and the auto
38
- // time) update when the consumer re-assigns the `conversations` array with a new
39
- // reference — the kit's standard reactivity model — not via an internal clock.
163
+ // time from updatedAt (fallback lastMessageAt). Never an internal clock — it is a
164
+ // render-time snapshot.
165
+ //
166
+ // REACTIVITY, and the weaker version of this note is what #224 was filed against:
167
+ // a new `conversations` array reference is NOT sufficient. `ConversationList` renders
168
+ // these rows through a reference-keyed `<For>` that captures `conv` as a VALUE, so a
169
+ // row whose item object is unchanged is never re-invoked and never re-reads anything
170
+ // below. The consumer needs BOTH — a new array (which is what notifies at all) and a
171
+ // new object for the item that changed (which is what this row can see). Adds,
172
+ // removes and reorders are fine on a fresh array alone, since those rows' identities
173
+ // already differ. Pinned by `src/components/reactivity-contract.test.tsx`.
40
174
  const trailing = createMemo(
41
175
  () => local.conversation.trailing ?? relativeTimeShort(local.conversation.updatedAt ?? local.conversation.lastMessageAt),
42
176
  );
@@ -30,6 +30,175 @@ export function CollapsedRail(props: CollapsedRailProps) {
30
30
  );
31
31
  }
32
32
 
33
+ /** Read a conversation item's identity the way the container does: the
34
+ * `conversationId` property, else the `conversation-id` attribute, else the
35
+ * host `id`. The property is what a framework loop sets; the attributes are
36
+ * the plain-HTML spellings (host `id` matches the `<kai-conversation>`
37
+ * data-carrier precedent). */
38
+ export function readConversationItemId(el: Element): string {
39
+ const prop = (el as Element & { conversationId?: unknown }).conversationId;
40
+ if (typeof prop === 'string' && prop) return prop;
41
+ return el.getAttribute('conversation-id') ?? el.id;
42
+ }
43
+
44
+ /** Whether a `<kai-conversation-item>` is STANDALONE — outside the management
45
+ * of a `<kai-conversations>` container — and therefore activates ITSELF:
46
+ * the facade makes its row body a
47
+ * tabbable button and fires `kai-select` on click / Enter / Space. Derived
48
+ * from the container's own membership rule, not from mere ancestry: item mode
49
+ * queries `:scope > kai-conversation-item` (direct children only), so an item
50
+ * wrapped in another element inside a container is standalone too — the
51
+ * container's controller never stamps or activates it. Inside a container
52
+ * (a direct child), the parent-item contract is the ONLY activation
53
+ * path: the container dispatches `kai-conversation-select` and owns roving
54
+ * tabindex, and the item fires nothing of its own. */
55
+ export function isStandaloneConversationItem(el: Element): boolean {
56
+ return el.parentElement?.localName !== 'kai-conversations';
57
+ }
58
+
59
+ export interface ConversationItemsControllerOptions {
60
+ /** The current slotted item hosts, in DOM order. */
61
+ getItems: () => HTMLElement[];
62
+ /** The container's active conversation id. */
63
+ getActiveId: () => string | undefined;
64
+ /** Item activation (click / Enter / Space). Surfaces as
65
+ * `kai-conversation-select` on the element. */
66
+ onSelect: (id: string) => void;
67
+ }
68
+
69
+ export interface ConversationItemsController {
70
+ /** Re-derive the ARIA/roving-tabindex bookkeeping over the current items.
71
+ * Call on slotchange / child mutation and whenever `activeId` changes. */
72
+ sync(): void;
73
+ /** The container's items-region click handler (composed-path aware). */
74
+ handleClick(e: MouseEvent): void;
75
+ /** The container's items-region keydown handler (arrows, Home/End,
76
+ * Enter/Space). */
77
+ handleKeyDown(e: KeyboardEvent): void;
78
+ }
79
+
80
+ /**
81
+ * The parent-item contract of item mode, as a pure-DOM
82
+ * controller so it is host-agnostic: the `kai-conversations` facade wires it over
83
+ * its slotted `kai-conversation-item` children, and the jsdom contract tests
84
+ * drive it over plain nodes. Solid context cannot cross the element boundary
85
+ * (each facade is its own Solid root), so the channel is DOM traversal by
86
+ * construction:
87
+ *
88
+ * - selection flows container to item — exactly one item's BODY node (the
89
+ * shadow body of a `kai-conversation-item`, else the node itself; see
90
+ * `bodyOf`) is `aria-current="true"`, plus the `active` property on the
91
+ * host for the item's own styling hook;
92
+ * - `role="button"` is ensured on each item's body node (an authored role is
93
+ * left alone);
94
+ * - roving tabindex — exactly one body node `tabindex="0"` (the active
95
+ * item's, else the first's), the rest `-1`, re-derived on every `sync()`;
96
+ * menu content keeps its natural tab order (it is the body's SIBLING, not a
97
+ * descendant);
98
+ * - activation (click / Enter / Space) calls `onSelect` with the item's id, and
99
+ * is SUPPRESSED when the composed path crosses the item's `menu` region
100
+ * (light-DOM `slot="menu"` content or the shadow `data-kai-item-menu`
101
+ * wrapper), so the consumer's own popover never also selects the row;
102
+ * - ArrowUp/ArrowDown/Home/End move focus item-to-item, tabindex following the
103
+ * focused item.
104
+ */
105
+ export function createConversationItemsController(
106
+ opts: ConversationItemsControllerOptions,
107
+ ): ConversationItemsController {
108
+ const itemFromEvent = (e: Event): HTMLElement | undefined => {
109
+ const items = opts.getItems();
110
+ return e.composedPath().find((n): n is HTMLElement => items.includes(n as HTMLElement));
111
+ };
112
+ /** The item's ACTIVATION node — the target of role/aria-current/tabindex/
113
+ * focus. For a `kai-conversation-item` host that is its shadow body (the
114
+ * sibling restructure: the host is the row listitem
115
+ * wrapping the body AND the consumer's tabbable menu, so the control
116
+ * semantics must sit below it). A bare node with no such body is its own
117
+ * control. */
118
+ const bodyOf = (item: HTMLElement): HTMLElement =>
119
+ (item.shadowRoot?.querySelector('[data-kai-item-body]') as HTMLElement | null) ?? item;
120
+ const menuInPath = (e: Event): boolean =>
121
+ e.composedPath().some(
122
+ (n) =>
123
+ n instanceof Element &&
124
+ (n.hasAttribute('data-kai-item-menu') || n.getAttribute('slot') === 'menu'),
125
+ );
126
+ // Write-on-change only. `setAttribute` records a mutation even when the value
127
+ // is identical, and the facade re-syncs from a MutationObserver over these very
128
+ // nodes — unconditional writes would feed the observer forever.
129
+ const setAttr = (el: Element, name: string, value: string) => {
130
+ if (el.getAttribute(name) !== value) el.setAttribute(name, value);
131
+ };
132
+ /** An item whose shadow body has not RENDERED yet must not be stamped: the
133
+ * fallback would write control semantics onto the HOST, they would stick
134
+ * (the item facade defers to an authored role), and axe then sees exactly
135
+ * the role="button"-host-with-focusable-menu shape the restructure removed.
136
+ * Measured in the focus-order probe: the container's first sync can run
137
+ * before the item elements upgrade. A later sync catches them — the
138
+ * facade's own mount mutates host attributes, which re-runs sync through
139
+ * the container's MutationObserver read(). Bare nodes (no dash: the jsdom
140
+ * stand-ins) are always ready. */
141
+ const readyBodies = (items: HTMLElement[]) => {
142
+ const out = new Map<HTMLElement, HTMLElement>();
143
+ for (const item of items) {
144
+ const body = bodyOf(item);
145
+ if (body === item && item.localName.includes('-')) continue;
146
+ out.set(item, body);
147
+ }
148
+ return out;
149
+ };
150
+
151
+ const rove = (items: HTMLElement[], target: HTMLElement | undefined) => {
152
+ for (const [item, body] of readyBodies(items)) setAttr(body, 'tabindex', item === target ? '0' : '-1');
153
+ };
154
+
155
+ const sync = () => {
156
+ const items = opts.getItems();
157
+ const activeId = opts.getActiveId();
158
+ const bodies = readyBodies(items);
159
+ let anchor: HTMLElement | undefined;
160
+ for (const [item, body] of bodies) {
161
+ const isActive = activeId !== undefined && readConversationItemId(item) === activeId;
162
+ if (!body.hasAttribute('role')) body.setAttribute('role', 'button');
163
+ setAttr(body, 'aria-current', isActive ? 'true' : 'false');
164
+ const host = item as HTMLElement & { active?: boolean };
165
+ if (host.active !== isActive) host.active = isActive;
166
+ if (isActive) anchor = item;
167
+ }
168
+ rove(items, anchor ?? (bodies.keys().next().value as HTMLElement | undefined));
169
+ };
170
+
171
+ return {
172
+ sync,
173
+ handleClick(e) {
174
+ const item = itemFromEvent(e);
175
+ if (!item || menuInPath(e)) return;
176
+ opts.onSelect(readConversationItemId(item));
177
+ },
178
+ handleKeyDown(e) {
179
+ const items = opts.getItems();
180
+ if (items.length === 0) return;
181
+ const item = itemFromEvent(e);
182
+ if (e.key === 'Enter' || e.key === ' ') {
183
+ if (!item || menuInPath(e)) return;
184
+ e.preventDefault();
185
+ opts.onSelect(readConversationItemId(item));
186
+ return;
187
+ }
188
+ let next: HTMLElement | undefined;
189
+ const idx = item ? items.indexOf(item) : items.findIndex((i) => bodyOf(i).getAttribute('tabindex') === '0');
190
+ if (e.key === 'ArrowDown') next = items[Math.min(idx + 1, items.length - 1)];
191
+ else if (e.key === 'ArrowUp') next = items[Math.max(idx - 1, 0)];
192
+ else if (e.key === 'Home') next = items[0];
193
+ else if (e.key === 'End') next = items[items.length - 1];
194
+ if (!next) return;
195
+ e.preventDefault();
196
+ rove(items, next);
197
+ bodyOf(next).focus();
198
+ },
199
+ };
200
+ }
201
+
33
202
  export interface ConversationListProps {
34
203
  groups: ConversationGroup[];
35
204
  conversations: ConversationSummary[];
@@ -51,6 +220,20 @@ export interface ConversationListProps {
51
220
  /** Receive the imperative controller once mounted. The `kai-conversations`
52
221
  * facade uses it to focus / clear the internal search input. */
53
222
  controllerRef?: (controller: ConversationListController) => void;
223
+ /** Item mode: your OWN rows, rendered inside a list region in place of the
224
+ * data rows. When set, the built-in search
225
+ * filter, grouping and empty/no-match states do not apply — the consumer's
226
+ * loop owns them — while the chrome (header, search box, new-chat, footer)
227
+ * still renders and `onSearchChange` still reports queries. The
228
+ * `kai-conversations` facade passes its default `<slot>` here when it detects
229
+ * `kai-conversation-item` children. */
230
+ items?: JSX.Element;
231
+ /** Keydown handler for the item-mode list region (the facade wires
232
+ * `createConversationItemsController.handleKeyDown`). */
233
+ itemsKeyDown?: (e: KeyboardEvent) => void;
234
+ /** Click handler for the item-mode list region (the facade wires
235
+ * `createConversationItemsController.handleClick`). */
236
+ itemsClick?: (e: MouseEvent) => void;
54
237
  class?: string;
55
238
  }
56
239
 
@@ -64,8 +247,10 @@ export interface ConversationListController {
64
247
  }
65
248
 
66
249
  export function ConversationList(props: ConversationListProps) {
67
- const [local] = splitProps(props, ['groups', 'conversations', 'activeId', 'onSelect', 'onNewChat', 'onToggleSidebar', 'header', 'footer', 'empty', 'compact', 'onSearchChange', 'controllerRef', 'class']);
250
+ const [local] = splitProps(props, ['groups', 'conversations', 'activeId', 'onSelect', 'onNewChat', 'onToggleSidebar', 'header', 'footer', 'empty', 'compact', 'onSearchChange', 'controllerRef', 'items', 'itemsKeyDown', 'itemsClick', 'class']);
68
251
  const [searchQuery, setSearchQuery] = createSignal('');
252
+ // Item mode: the consumer's own rows replace the data rendering wholesale.
253
+ const itemMode = createMemo(() => local.items != null);
69
254
  const isEmpty = createMemo(() => local.conversations.length === 0);
70
255
  // The search query is owned here; setQuery is the single mutation point so both
71
256
  // typing and the imperative clearSearch() notify the facade (→ kai-search).
@@ -133,32 +318,61 @@ export function ConversationList(props: ConversationListProps) {
133
318
  >
134
319
  {local.header}
135
320
  </Show>
136
- <Show when={!isEmpty()}>
321
+ <Show when={itemMode() || !isEmpty()}>
137
322
  <div class="px-3 pb-2">
138
323
  <div class="flex items-center gap-2 rounded-md bg-surface-strong px-2.5 py-1.5">
139
324
  <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="text-muted-foreground"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
140
325
  <input ref={searchInput} type="text" value={searchQuery()} onInput={(e) => setQuery(e.currentTarget.value)} placeholder="Search chats..."
141
326
  aria-label="Search chats"
142
- class="bg-transparent text-[13px] text-foreground placeholder:text-muted-foreground rounded-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring w-full" />
327
+ class="bg-transparent text-compact text-foreground placeholder:text-muted-foreground rounded-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring w-full" />
143
328
  </div>
144
329
  </div>
145
330
  </Show>
331
+ {/* Item mode: the consumer's own rows in a list region. The container's
332
+ filter/grouping/empty states do not apply — the consumer's loop owns
333
+ them. */}
334
+ <Show when={itemMode()}>
335
+ <ScrollArea class="flex-1 px-2">
336
+ <div
337
+ role="list"
338
+ aria-label="Conversations"
339
+ part="items"
340
+ class="space-y-0.5 py-1"
341
+ onKeyDown={(e) => local.itemsKeyDown?.(e)}
342
+ onClick={(e) => local.itemsClick?.(e)}
343
+ >
344
+ {local.items}
345
+ </div>
346
+ </ScrollArea>
347
+ </Show>
146
348
  {/* list, or the empty state (replace) when there are no conversations. */}
147
349
  <Show
148
- when={!isEmpty()}
350
+ when={!itemMode() && !isEmpty()}
149
351
  fallback={
150
- <Show
151
- when={local.empty}
152
- fallback={
153
- <div class="flex flex-1 flex-col items-center justify-center gap-1 p-6 text-center text-sm text-muted-foreground">
154
- No conversations yet
155
- </div>
156
- }
157
- >
158
- {local.empty}
352
+ <Show when={!itemMode()}>
353
+ <Show
354
+ when={local.empty}
355
+ fallback={
356
+ <div class="flex flex-1 flex-col items-center justify-center gap-1 p-6 text-center text-sm text-muted-foreground">
357
+ No conversations yet
358
+ </div>
359
+ }
360
+ >
361
+ {local.empty}
362
+ </Show>
159
363
  </Show>
160
364
  }
161
365
  >
366
+ {/* F-04, decide loudly: a query matching nothing renders a VISIBLE
367
+ no-match state, keyed off the FILTERED count — distinct from the
368
+ zero-conversations empty state above, which keys off the unfiltered
369
+ list (and still owns the `empty` override). */}
370
+ <Show when={filteredConversations().length === 0}>
371
+ <div class="flex flex-1 flex-col items-center justify-center gap-1 p-6 text-center text-sm text-muted-foreground">
372
+ No conversations match your search
373
+ </div>
374
+ </Show>
375
+ <Show when={filteredConversations().length > 0}>
162
376
  <ScrollArea class="flex-1 px-2">
163
377
  <For each={local.groups}>
164
378
  {(group) => {
@@ -183,6 +397,7 @@ export function ConversationList(props: ConversationListProps) {
183
397
  </Show>
184
398
  </Show>
185
399
  </ScrollArea>
400
+ </Show>
186
401
  </Show>
187
402
  {/* footer (inject): a row below the list (account / settings / …). */}
188
403
  <Show when={local.footer}>
@@ -196,11 +411,11 @@ function GroupSection(props: { name: string; count: number; conversations: Conve
196
411
  const [open, setOpen] = createSignal(true);
197
412
  return (
198
413
  <Collapsible open={open()} onOpenChange={setOpen}>
199
- <CollapsibleTrigger class="flex items-center gap-1.5 w-full px-1.5 py-1 rounded-md text-[13px] text-muted-foreground font-medium hover:bg-muted/30 transition-colors cursor-pointer mt-1.5">
414
+ <CollapsibleTrigger class="flex items-center gap-1.5 w-full px-1.5 py-1 rounded-md text-compact text-muted-foreground font-medium hover:bg-muted/30 transition-colors cursor-pointer mt-1.5">
200
415
  <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
201
416
  class={cn('transition-transform', !open() && '-rotate-90')}><polyline points="6 9 12 15 18 9"/></svg>
202
417
  <span>{props.name}</span>
203
- <Badge variant="count" class="ml-auto text-[11px]">{props.count}</Badge>
418
+ <Badge variant="count" class="ml-auto text-caption">{props.count}</Badge>
204
419
  </CollapsibleTrigger>
205
420
  <CollapsibleContent>
206
421
  <div class="pl-2 mt-0.5 space-y-0.5">