@kitn.ai/ui 0.18.1 → 0.19.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 (203) hide show
  1. package/dist/components/thread.d.ts +54 -0
  2. package/dist/custom-elements.json +195 -0
  3. package/dist/elements/agent-card.js +1 -1
  4. package/dist/elements/artifact.js +1 -1
  5. package/dist/elements/attachments.js +1 -1
  6. package/dist/elements/autoloader.js +1 -1
  7. package/dist/elements/avatar.js +1 -1
  8. package/dist/elements/badge.js +1 -1
  9. package/dist/elements/button.js +1 -1
  10. package/dist/elements/card.js +1 -1
  11. package/dist/elements/cards.js +1 -1
  12. package/dist/elements/chain-of-thought.js +1 -1
  13. package/dist/elements/chat-scope-picker.js +1 -1
  14. package/dist/elements/chat-workspace.js +1 -1
  15. package/dist/elements/chat.js +1 -1
  16. package/dist/elements/checkpoint.js +1 -1
  17. package/dist/elements/choice.js +1 -1
  18. package/dist/elements/chunks/{Icon-C2LTALdM.js → Icon-11SRdjJ4.js} +1 -1
  19. package/dist/elements/chunks/action-icons-CtRg17uX.js +1 -0
  20. package/dist/elements/chunks/arrow-left-CifsGq8x.js +1 -0
  21. package/dist/elements/chunks/{attachments-DLJcWCDV.js → attachments-CBqQRh60.js} +1 -1
  22. package/dist/elements/chunks/{badge-BTNZTODL.js → badge-CxklEXQm.js} +1 -1
  23. package/dist/elements/chunks/{button-DBDZebGr.js → button-BD7lUvVB.js} +1 -1
  24. package/dist/elements/chunks/{card-YfCyI4fH.js → card-B-PRGXdj.js} +1 -1
  25. package/dist/elements/chunks/chat-thread-DIZbRIKG.js +1 -0
  26. package/dist/elements/chunks/check-CUKS495w.js +1 -0
  27. package/dist/elements/chunks/chevron-down-AELmYT1j.js +1 -0
  28. package/dist/elements/chunks/chevron-right-C8g26oKh.js +1 -0
  29. package/dist/elements/chunks/circle-DGcI8Cft.js +1 -0
  30. package/dist/elements/chunks/circle-check-DuU0IcBQ.js +1 -0
  31. package/dist/elements/chunks/{circle-x-pDtzlghQ.js → circle-x-mtjCO_Ok.js} +1 -1
  32. package/dist/elements/chunks/{code-block-DZ6mz6vs.js → code-block-BeWVttmU.js} +1 -1
  33. package/dist/elements/chunks/{collapsible-CWUAShMC.js → collapsible-DGjADCN6.js} +1 -1
  34. package/dist/elements/chunks/{composer-BZyPU6gD.js → composer-CavJww3d.js} +7 -3
  35. package/dist/elements/chunks/{context-Cx6eQA_G.js → context-CnlsNJSJ.js} +1 -1
  36. package/dist/elements/chunks/{conversation-list-C4xTQSpT.js → conversation-list-5O_KdI_I.js} +1 -1
  37. package/dist/elements/chunks/{default-input-BZcwyKcP.js → default-input-CB46cJJ1.js} +1 -1
  38. package/dist/elements/chunks/{define-CNSKFZCd.js → define-DfTe4zwZ.js} +1 -1
  39. package/dist/elements/chunks/{disclosure-BcBs--Jf.js → disclosure-BtFK3a-M.js} +1 -1
  40. package/dist/elements/chunks/{download-BDQwcFav.js → download-qJv1dS3M.js} +1 -1
  41. package/dist/elements/chunks/{dropdown-wgs8xCJZ.js → dropdown-UhtAueYR.js} +1 -1
  42. package/dist/elements/chunks/{ellipsis-CUiEJ6R1.js → ellipsis-B1sOOqiJ.js} +1 -1
  43. package/dist/elements/chunks/{external-link-DJUmC7g2.js → external-link-DToP_VKE.js} +1 -1
  44. package/dist/elements/chunks/{file-text-DOYfYvrh.js → file-text-BwUs_Pj4.js} +1 -1
  45. package/dist/elements/chunks/{file-tree-DQzNQrbr.js → file-tree-C6RhmSnX.js} +1 -1
  46. package/dist/elements/chunks/{folder-DBDUBANz.js → folder-H_Xp0GYP.js} +1 -1
  47. package/dist/elements/chunks/hover-card-Dn3yrk1l.js +1 -0
  48. package/dist/elements/chunks/icon-BrxJQXzC.js +1 -0
  49. package/dist/elements/chunks/{info-DBQl-m6e.js → info-11eT2q5H.js} +1 -1
  50. package/dist/elements/chunks/{input-DZD67EvW.js → input-B9oZKDfb.js} +1 -1
  51. package/dist/elements/chunks/{kbd-DbyVrlnU.js → kbd-D2fx3rWh.js} +1 -1
  52. package/dist/elements/chunks/{link-vko7AttA.js → link-B-RNYWNw.js} +1 -1
  53. package/dist/elements/chunks/{loader-Cg6rO8tQ.js → loader-BJh_aRlp.js} +1 -1
  54. package/dist/elements/chunks/{markdown-n3Xbj3PR.js → markdown-BIpjrDFv.js} +1 -1
  55. package/dist/elements/chunks/{message-CgKytiSB.js → message-CPfqj8Gi.js} +1 -1
  56. package/dist/elements/chunks/{message-feedback-Ds3-US0e.js → message-feedback-8uj0aVYh.js} +1 -1
  57. package/dist/elements/chunks/message-square-CCSQ4M9g.js +1 -0
  58. package/dist/elements/chunks/{minimize-2-3kCdE0q3.js → minimize-2-C9qkiTJg.js} +1 -1
  59. package/dist/elements/chunks/{model-switcher-BMtXGwLU.js → model-switcher-DRrMcRXH.js} +1 -1
  60. package/dist/elements/chunks/{overlay-B1-MBI6h.js → overlay-C8_FUL8q.js} +1 -1
  61. package/dist/elements/chunks/{paperclip-DDEfmkHc.js → paperclip-MA9hxLhW.js} +1 -1
  62. package/dist/elements/chunks/{progress-bar-Dwbiyv3s.js → progress-bar-y5G6HDjg.js} +1 -1
  63. package/dist/elements/chunks/{prompt-suggestion-9lvgFcEa.js → prompt-suggestion-LalWvCJy.js} +1 -1
  64. package/dist/elements/chunks/reasoning-CW3SDdb4.js +1 -0
  65. package/dist/elements/chunks/resizable-xhYF5lCQ.js +1 -0
  66. package/dist/elements/chunks/{rotate-cw-jpisOdOV.js → rotate-cw-2ndEXjgS.js} +1 -1
  67. package/dist/elements/chunks/{scroll-area-g5Ze3K6f.js → scroll-area-BzOPDXrZ.js} +1 -1
  68. package/dist/elements/chunks/scroll-button-B1x4cYqM.js +1 -0
  69. package/dist/elements/chunks/{separator-Bmjc5XyX.js → separator-CPvL5Xdl.js} +1 -1
  70. package/dist/elements/chunks/{settings-group-9imY3kRA.js → settings-group-BiIym17O.js} +1 -1
  71. package/dist/elements/chunks/{settings-Cz1bMJP9.js → settings-hmzIuNXD.js} +1 -1
  72. package/dist/elements/chunks/{share-BdenF0wQ.js → share-BbziTAzn.js} +1 -1
  73. package/dist/elements/chunks/skeleton-Bxhs1thL.js +1 -0
  74. package/dist/elements/chunks/{slots-DkZ-Cmha.js → slots--V19HwUb.js} +1 -1
  75. package/dist/elements/chunks/{star-DY6z1UUH.js → star-BHL3Va5p.js} +1 -1
  76. package/dist/elements/chunks/{store-th4VyJXM.js → store-tpnnWlxY.js} +1 -1
  77. package/dist/elements/chunks/{text-shimmer-CEi-tlXr.js → text-shimmer-1DCKivKx.js} +1 -1
  78. package/dist/elements/chunks/textarea-Czaowo9F.js +1 -0
  79. package/dist/elements/chunks/{thumbs-up-BR33KLfA.js → thumbs-up-CdLqA13R.js} +1 -1
  80. package/dist/elements/chunks/{toast-store-BEMda0fv.js → toast-store-BvBquUFA.js} +1 -1
  81. package/dist/elements/chunks/{tool-TMued3WO.js → tool-4P_ne8Go.js} +1 -1
  82. package/dist/elements/chunks/{tooltip-Cft_Rxiw.js → tooltip-CBeFqJKk.js} +1 -1
  83. package/dist/elements/chunks/{triangle-alert-B07fX3Q8.js → triangle-alert-BllqxXq8.js} +1 -1
  84. package/dist/elements/chunks/{use-card-resolution-M36zVciB.js → use-card-resolution-73xiddIW.js} +1 -1
  85. package/dist/elements/chunks/{video-MKp3LmNT.js → video-O9rA6k7S.js} +1 -1
  86. package/dist/elements/chunks/x-DeS3rQhx.js +1 -0
  87. package/dist/elements/coachmark.js +1 -1
  88. package/dist/elements/code-block.js +1 -1
  89. package/dist/elements/command.js +1 -1
  90. package/dist/elements/compare.js +1 -1
  91. package/dist/elements/composer.js +1 -1
  92. package/dist/elements/confirm-card.js +1 -1
  93. package/dist/elements/context-meter.js +1 -1
  94. package/dist/elements/conversation-list.js +1 -1
  95. package/dist/elements/dialog.js +1 -1
  96. package/dist/elements/editable-label.js +1 -1
  97. package/dist/elements/embed.js +1 -1
  98. package/dist/elements/empty.js +1 -1
  99. package/dist/elements/feedback-bar.js +1 -1
  100. package/dist/elements/file-tree.js +1 -1
  101. package/dist/elements/file-upload.js +1 -1
  102. package/dist/elements/form.js +1 -1
  103. package/dist/elements/hover-card.js +1 -1
  104. package/dist/elements/icon.js +1 -1
  105. package/dist/elements/image.js +1 -1
  106. package/dist/elements/input.js +1 -1
  107. package/dist/elements/kbd.js +1 -1
  108. package/dist/elements/link-preview.js +1 -1
  109. package/dist/elements/loader.js +1 -1
  110. package/dist/elements/markdown.js +1 -1
  111. package/dist/elements/menu.js +1 -1
  112. package/dist/elements/message-skills.js +1 -1
  113. package/dist/elements/message.js +1 -1
  114. package/dist/elements/model-switcher.js +1 -1
  115. package/dist/elements/nav.js +1 -1
  116. package/dist/elements/notice.js +1 -1
  117. package/dist/elements/pane-group.js +1 -1
  118. package/dist/elements/pane.js +1 -1
  119. package/dist/elements/popover.js +1 -1
  120. package/dist/elements/progress-bar.js +1 -1
  121. package/dist/elements/prompt-dock.js +1 -1
  122. package/dist/elements/prompt-input.js +1 -1
  123. package/dist/elements/prompt-suggestions.js +1 -1
  124. package/dist/elements/reasoning.js +1 -1
  125. package/dist/elements/remote.js +1 -1
  126. package/dist/elements/resizable.js +1 -1
  127. package/dist/elements/response-stream.js +1 -1
  128. package/dist/elements/screen.js +1 -1
  129. package/dist/elements/scroll-area.js +1 -1
  130. package/dist/elements/scroll-button.js +1 -1
  131. package/dist/elements/search.js +1 -1
  132. package/dist/elements/segmented.js +1 -1
  133. package/dist/elements/separator.js +1 -1
  134. package/dist/elements/setting-item.js +1 -1
  135. package/dist/elements/settings-group.js +1 -1
  136. package/dist/elements/skeleton.js +1 -1
  137. package/dist/elements/slots.d.ts +3 -0
  138. package/dist/elements/source.js +1 -1
  139. package/dist/elements/status.js +1 -1
  140. package/dist/elements/switch.js +1 -1
  141. package/dist/elements/tabs.js +1 -1
  142. package/dist/elements/tasks.js +1 -1
  143. package/dist/elements/text-shimmer.js +1 -1
  144. package/dist/elements/thinking-bar.js +1 -1
  145. package/dist/elements/thread.d.ts +1 -0
  146. package/dist/elements/thread.js +1 -0
  147. package/dist/elements/toast.js +1 -1
  148. package/dist/elements/tool.js +1 -1
  149. package/dist/elements/tooltip.js +1 -1
  150. package/dist/elements/voice-input.js +1 -1
  151. package/dist/elements/voice-output.js +1 -1
  152. package/dist/elements.d.ts +24 -0
  153. package/dist/index.js +3393 -3311
  154. package/dist/kai.es.js +1 -1
  155. package/dist/llms/llms-full.txt +34 -4
  156. package/dist/llms/llms.txt +3 -3
  157. package/dist/react/index.d.ts +64 -0
  158. package/dist/react/use-voice-input.d.ts +15 -0
  159. package/dist/react.js +263 -240
  160. package/dist/register-impl-CaaHJcea.js +145 -0
  161. package/dist/ui/resizable.d.ts +9 -3
  162. package/frameworks/react/index.tsx +32 -1
  163. package/frameworks/react/use-voice-input.tsx +59 -0
  164. package/llms-full.txt +34 -4
  165. package/llms.txt +3 -3
  166. package/package.json +27 -5
  167. package/src/components/component-meta.json +30 -28
  168. package/src/components/composer.tsx +5 -0
  169. package/src/components/thread.tsx +177 -0
  170. package/src/elements/chat-workspace.tsx +1 -1
  171. package/src/elements/compiled.css +1 -1
  172. package/src/elements/element-manifest.json +4 -0
  173. package/src/elements/element-meta.json +117 -0
  174. package/src/elements/element-types.d.ts +24 -0
  175. package/src/elements/icon-names.json +2 -0
  176. package/src/elements/menu.tsx +7 -4
  177. package/src/elements/register-impl.ts +2 -0
  178. package/src/elements/resizable.tsx +78 -2
  179. package/src/elements/search.tsx +1 -1
  180. package/src/elements/slots.ts +7 -0
  181. package/src/elements/thread.tsx +105 -0
  182. package/src/ui/action-icons.ts +2 -1
  183. package/src/ui/command.tsx +5 -1
  184. package/src/ui/icon.tsx +4 -0
  185. package/src/ui/overlay.tsx +8 -1
  186. package/src/ui/resizable.tsx +47 -9
  187. package/dist/elements/chunks/action-icons-Dtl8Ox5D.js +0 -1
  188. package/dist/elements/chunks/arrow-left-GbBceGBE.js +0 -1
  189. package/dist/elements/chunks/chat-thread-Dz2qbzvI.js +0 -1
  190. package/dist/elements/chunks/check-fhmRksq_.js +0 -1
  191. package/dist/elements/chunks/chevron-down-DcRg1aRU.js +0 -1
  192. package/dist/elements/chunks/chevron-right-ChzBcNJ-.js +0 -1
  193. package/dist/elements/chunks/circle-B4fTe9v6.js +0 -1
  194. package/dist/elements/chunks/circle-check-CJV-3Rem.js +0 -1
  195. package/dist/elements/chunks/hover-card-BQGdGAQ3.js +0 -1
  196. package/dist/elements/chunks/icon-CAyRqVrg.js +0 -1
  197. package/dist/elements/chunks/message-square-MmRkvc-M.js +0 -1
  198. package/dist/elements/chunks/reasoning-DzHWsdAJ.js +0 -1
  199. package/dist/elements/chunks/resizable-Bq_eHxUm.js +0 -1
  200. package/dist/elements/chunks/skeleton-Pjc5jiaY.js +0 -1
  201. package/dist/elements/chunks/textarea-CboigarR.js +0 -1
  202. package/dist/elements/chunks/x-Ckwicf6g.js +0 -1
  203. package/dist/register-impl-cKscku5m.js +0 -141
@@ -4,6 +4,7 @@
4
4
  "kai-prompt-input": "prompt-input",
5
5
  "kai-chat": "chat",
6
6
  "kai-workspace": "chat-workspace",
7
+ "kai-thread": "thread",
7
8
  "kai-thinking-bar": "thinking-bar",
8
9
  "kai-model-switcher": "model-switcher",
9
10
  "kai-attachments": "attachments",
@@ -91,6 +92,9 @@
91
92
  "chat-workspace": [
92
93
  "kai-workspace"
93
94
  ],
95
+ "thread": [
96
+ "kai-thread"
97
+ ],
94
98
  "thinking-bar": [
95
99
  "kai-thinking-bar"
96
100
  ],
@@ -5200,6 +5200,15 @@
5200
5200
  "description": "Which item index is maximized (null = none). Declarative source of truth.",
5201
5201
  "default": "null",
5202
5202
  "displayType": "undefined | null | number"
5203
+ },
5204
+ {
5205
+ "name": "handle",
5206
+ "type": "undefined | \"none\" | \"line\" | \"grip\"",
5207
+ "optional": true,
5208
+ "scalar": true,
5209
+ "description": "Divider affordance drawn inside each draggable handle's 8px grab zone: - `line` (default) — a 1px hairline, transparent at rest, tinting on hover/drag. - `grip` — a dotted grip handle. - `none` — no visible divider, just the invisible hit-area. The full grab zone and keyboard/ARIA behavior are identical for all three.",
5210
+ "default": "'line'",
5211
+ "displayType": "undefined | \"none\" | \"line\" | \"grip\""
5203
5212
  }
5204
5213
  ],
5205
5214
  "events": [
@@ -6768,6 +6777,114 @@
6768
6777
  ],
6769
6778
  "tokens": []
6770
6779
  },
6780
+ {
6781
+ "tag": "kai-thread",
6782
+ "className": "KaiThreadElement",
6783
+ "displayName": "Thread",
6784
+ "props": [
6785
+ {
6786
+ "name": "messages",
6787
+ "type": "undefined | { id: string; role: \"user\" | \"assistant\"; content: string; reasoning?: undefined | { text: string; label?: undefined | string }; tools?: undefined | { type: string; state: \"input-streaming\" | \"input-available\" | \"output-available\" | \"output-error\"; input?: undefined | Record<string, unknown>; output?: undefined | Record<string, unknown>; toolCallId?: undefined | string; errorText?: undefined | string }[]; attachments?: undefined | { id: string; type: \"file\" | \"source-document\"; filename?: undefined | string; mediaType?: undefined | string; url?: undefined | string; title?: undefined | string }[]; actions?: undefined | (\"copy\" | \"like\" | \"dislike\" | \"regenerate\" | \"edit\" | { id: string; label: string; icon?: undefined | string; tooltip?: undefined | string })[]; avatar?: undefined | { src?: undefined | string; fallback?: undefined | string; alt?: undefined | string }; feedback?: undefined | \"like\" | \"dislike\" }[]",
6788
+ "optional": true,
6789
+ "scalar": false,
6790
+ "description": "The full message thread to render, newest last. Each entry carries its role, content, and optional reasoning/tools/attachments/actions/avatar. Set as a JS property (`el.messages = [...]`); a NEW array reference per streaming chunk re-renders (mutating in place does not).",
6791
+ "displayType": "undefined | { id: string; role: \"user\" | \"assistant\"; content: string; reasoning?: undefined | { text: string; label?: undefined | string }; tools?: undefined | { type: string; state: \"input-streaming\" | \"input-available\" | \"output-available\" | \"output-error\"; input?: undefined | Record<string, unknown>; output?: undefined | Record<string, unknown>; toolCallId?: undefined | string; errorText?: undefined | string }[]; attachments?: undefined | { id: string; type: \"file\" | \"source-document\"; filename?: undefined | string; mediaType?: undefined | string; url?: undefined | string; title?: undefined | string }[]; actions?: undefined | (\"copy\" | \"like\" | \"dislike\" | \"regenerate\" | \"edit\" | { id: string; label: string; icon?: undefined | string; tooltip?: undefined | string })[]; avatar?: undefined | { src?: undefined | string; fallback?: undefined | string; alt?: undefined | string }; feedback?: undefined | \"like\" | \"dislike\" }[]"
6792
+ },
6793
+ {
6794
+ "name": "loading",
6795
+ "type": "undefined | false | true",
6796
+ "optional": true,
6797
+ "scalar": true,
6798
+ "description": "Show a typing indicator on the pending assistant turn — set while awaiting the assistant's reply.",
6799
+ "default": "false",
6800
+ "displayType": "undefined | false | true"
6801
+ },
6802
+ {
6803
+ "name": "proseSize",
6804
+ "type": "undefined | \"sm\" | \"lg\" | \"xs\" | \"base\"",
6805
+ "optional": true,
6806
+ "scalar": true,
6807
+ "description": "Body/prose font scale for rendered markdown (`'xs' | 'sm' | 'base' | 'lg'`). Defaults to `'sm'`.",
6808
+ "default": "'sm'",
6809
+ "displayType": "undefined | \"sm\" | \"lg\" | \"xs\" | \"base\""
6810
+ },
6811
+ {
6812
+ "name": "codeTheme",
6813
+ "type": "undefined | string",
6814
+ "optional": true,
6815
+ "scalar": true,
6816
+ "description": "Shiki theme name for syntax-highlighted code blocks (e.g. `'github-dark-dimmed'`).",
6817
+ "default": "'github-dark-dimmed'",
6818
+ "displayType": "undefined | string"
6819
+ },
6820
+ {
6821
+ "name": "codeHighlight",
6822
+ "type": "undefined | false | true",
6823
+ "optional": true,
6824
+ "scalar": true,
6825
+ "description": "Enable Shiki syntax highlighting in code blocks. Turn off to render plain `<pre>` blocks (lighter, no highlighter load). Default true.",
6826
+ "default": "true",
6827
+ "displayType": "undefined | false | true"
6828
+ },
6829
+ {
6830
+ "name": "actionsReveal",
6831
+ "type": "undefined | \"always\" | \"hover\"",
6832
+ "optional": true,
6833
+ "scalar": true,
6834
+ "description": "Whether each message's action bar is always visible (`'always'`, default) or only revealed on hover of that message row (`'hover'`).",
6835
+ "default": "'always'",
6836
+ "displayType": "undefined | \"always\" | \"hover\""
6837
+ },
6838
+ {
6839
+ "name": "scrollButton",
6840
+ "type": "undefined | false | true",
6841
+ "optional": true,
6842
+ "scalar": true,
6843
+ "description": "Show the scroll-to-bottom button inside the scroll area. Default true.",
6844
+ "default": "true",
6845
+ "displayType": "undefined | false | true"
6846
+ },
6847
+ {
6848
+ "name": "class",
6849
+ "type": "undefined | string",
6850
+ "optional": true,
6851
+ "scalar": true,
6852
+ "description": "Extra classes applied to the thread's inner root.",
6853
+ "displayType": "undefined | string"
6854
+ }
6855
+ ],
6856
+ "events": [
6857
+ {
6858
+ "name": "kai-message-action",
6859
+ "detail": "{ messageId: string; action: string; state?: undefined | \"on\" | \"off\" }",
6860
+ "description": "A message's action button was clicked. `action` is the built-in name (`copy` / `like` / `dislike` / `regenerate` / `edit`) or a custom id. `state` is present only for the toggleable feedback votes: `'on'` when a like/dislike is set, `'off'` when re-tapped to clear.",
6861
+ "displayDetail": "{ messageId: string; action: string; state?: undefined | \"on\" | \"off\" }"
6862
+ }
6863
+ ],
6864
+ "methods": [
6865
+ {
6866
+ "name": "scrollToBottom",
6867
+ "params": "behavior?: ScrollBehavior",
6868
+ "returns": "void",
6869
+ "description": "Scroll the message list to the bottom (default `'smooth'`)."
6870
+ }
6871
+ ],
6872
+ "composedFrom": [
6873
+ {
6874
+ "name": "Thread",
6875
+ "group": "Components",
6876
+ "storyId": "solid-advanced-elements-thread--docs"
6877
+ }
6878
+ ],
6879
+ "tokens": [],
6880
+ "slots": [
6881
+ {
6882
+ "name": "empty",
6883
+ "mode": "replace",
6884
+ "doc": "Custom zero-state rendered in the message area while the thread is empty; replaces the built-in default."
6885
+ }
6886
+ ]
6887
+ },
6771
6888
  {
6772
6889
  "tag": "kai-toast-region",
6773
6890
  "className": "KaiToastRegionElement",
@@ -923,6 +923,8 @@ export interface KaiResizableElement extends HTMLElement {
923
923
  orientation?: "vertical" | "horizontal";
924
924
  /** Which item index is maximized (null = none). Declarative source of truth. */
925
925
  maximizedIndex?: null | number;
926
+ /** Divider affordance drawn inside each draggable handle's 8px grab zone: - `line` (default) — a 1px hairline, transparent at rest, tinting on hover/drag. - `grip` — a dotted grip handle. - `none` — no visible divider, just the invisible hit-area. The full grab zone and keyboard/ARIA behavior are identical for all three. */
927
+ handle?: "none" | "line" | "grip";
926
928
  }
927
929
 
928
930
  export interface KaiResizableItemElement extends HTMLElement {
@@ -1213,6 +1215,27 @@ export interface KaiThinkingBarElement extends HTMLElement {
1213
1215
  stopLabel?: string;
1214
1216
  }
1215
1217
 
1218
+ export interface KaiThreadElement extends HTMLElement {
1219
+ /** Color mode (`auto` follows prefers-color-scheme). */
1220
+ theme?: 'light' | 'dark' | 'auto';
1221
+ /** The full message thread to render, newest last. Each entry carries its role, content, and optional reasoning/tools/attachments/actions/avatar. Set as a JS property (`el.messages = [...]`); a NEW array reference per streaming chunk re-renders (mutating in place does not). */
1222
+ messages?: { id: string; role: "user" | "assistant"; content: string; reasoning?: { text: string; label?: string }; tools?: { type: string; state: "input-streaming" | "input-available" | "output-available" | "output-error"; input?: Record<string, unknown>; output?: Record<string, unknown>; toolCallId?: string; errorText?: string }[]; attachments?: { id: string; type: "file" | "source-document"; filename?: string; mediaType?: string; url?: string; title?: string }[]; actions?: ("copy" | "like" | "dislike" | "regenerate" | "edit" | { id: string; label: string; icon?: string; tooltip?: string })[]; avatar?: { src?: string; fallback?: string; alt?: string }; feedback?: "like" | "dislike" }[];
1223
+ /** Show a typing indicator on the pending assistant turn — set while awaiting the assistant's reply. */
1224
+ loading?: boolean;
1225
+ /** Body/prose font scale for rendered markdown (`'xs' | 'sm' | 'base' | 'lg'`). Defaults to `'sm'`. */
1226
+ proseSize?: "sm" | "lg" | "xs" | "base";
1227
+ /** Shiki theme name for syntax-highlighted code blocks (e.g. `'github-dark-dimmed'`). */
1228
+ codeTheme?: string;
1229
+ /** Enable Shiki syntax highlighting in code blocks. Turn off to render plain `<pre>` blocks (lighter, no highlighter load). Default true. */
1230
+ codeHighlight?: boolean;
1231
+ /** Whether each message's action bar is always visible (`'always'`, default) or only revealed on hover of that message row (`'hover'`). */
1232
+ actionsReveal?: "always" | "hover";
1233
+ /** Show the scroll-to-bottom button inside the scroll area. Default true. */
1234
+ scrollButton?: boolean;
1235
+ /** Extra classes applied to the thread's inner root. */
1236
+ class?: string;
1237
+ }
1238
+
1216
1239
  export interface KaiToastRegionElement extends HTMLElement {
1217
1240
  /** Color mode (`auto` follows prefers-color-scheme). */
1218
1241
  theme?: 'light' | 'dark' | 'auto';
@@ -1412,6 +1435,7 @@ declare global {
1412
1435
  'kai-tasks': KaiTasksElement;
1413
1436
  'kai-text-shimmer': KaiTextShimmerElement;
1414
1437
  'kai-thinking-bar': KaiThinkingBarElement;
1438
+ 'kai-thread': KaiThreadElement;
1415
1439
  'kai-toast-region': KaiToastRegionElement;
1416
1440
  'kai-tool': KaiToolElement;
1417
1441
  'kai-tooltip': KaiTooltipElement;
@@ -30,6 +30,7 @@
30
30
  "message-square",
31
31
  "mic",
32
32
  "monitor",
33
+ "moon",
33
34
  "more-horizontal",
34
35
  "panel-left",
35
36
  "paperclip",
@@ -43,6 +44,7 @@
43
44
  "smile",
44
45
  "sparkles",
45
46
  "square-pen",
47
+ "sun",
46
48
  "workflow",
47
49
  "x"
48
50
  ]
@@ -79,12 +79,15 @@ interface Events {
79
79
  /**
80
80
  * `<kai-menu>` — a cascading action menu driven by a JSON items-tree.
81
81
  *
82
- * Set the `items` property in JavaScript (array, not attribute). The trigger
83
- * is slotted put your icon/button content in `slot="trigger"`:
82
+ * Set the `items` property in JavaScript (array, not attribute). The menu
83
+ * provides its own trigger button (accessible + keyboard-wired); slot only the
84
+ * VISUAL content into `slot="trigger"` (an icon, text, an `<svg>`) — never your
85
+ * own `<button>`/`<a>`, which would nest interactive elements. Set the
86
+ * accessible name with the `label` attribute (for an icon-only trigger):
84
87
  *
85
88
  * ```html
86
- * <kai-menu>
87
- * <button slot="trigger" aria-label="Open menu">+</button>
89
+ * <kai-menu label="Open menu">
90
+ * <span slot="trigger">+</span>
88
91
  * </kai-menu>
89
92
  * <script type="module">
90
93
  * import '@kitn.ai/ui/elements';
@@ -7,6 +7,8 @@ import './conversation-list';
7
7
  import './prompt-input';
8
8
  import './chat';
9
9
  import './chat-workspace';
10
+ // Message-list composable (the scrolling thread slice, sans composer/header)
11
+ import './thread';
10
12
  // Composable leaf elements (spike — see docs/handoff + examples/composable)
11
13
  import './thinking-bar';
12
14
  import './model-switcher';
@@ -55,6 +55,14 @@ interface GroupProps extends Record<string, unknown> {
55
55
  orientation?: Orientation;
56
56
  /** Which item index is maximized (null = none). Declarative source of truth. */
57
57
  maximizedIndex?: number | null;
58
+ /**
59
+ * Divider affordance drawn inside each draggable handle's 8px grab zone:
60
+ * - `line` (default) — a 1px hairline, transparent at rest, tinting on hover/drag.
61
+ * - `grip` — a dotted grip handle.
62
+ * - `none` — no visible divider, just the invisible hit-area.
63
+ * The full grab zone and keyboard/ARIA behavior are identical for all three.
64
+ */
65
+ handle?: 'line' | 'grip' | 'none';
58
66
  }
59
67
 
60
68
  interface GroupEvents extends Record<string, unknown> {
@@ -75,6 +83,7 @@ interface GroupEvents extends Record<string, unknown> {
75
83
  defineWebComponent<GroupProps, GroupEvents>('kai-resizable', {
76
84
  orientation: 'horizontal',
77
85
  maximizedIndex: null,
86
+ handle: 'line',
78
87
  }, (props, { element, dispatch }) => {
79
88
  const [items, setItems] = createSignal<ItemInfo[]>([]);
80
89
  const orientation = (): Orientation => (props.orientation === 'vertical' ? 'vertical' : 'horizontal');
@@ -452,7 +461,7 @@ defineWebComponent<GroupProps, GroupEvents>('kai-resizable', {
452
461
  <>
453
462
  <Show when={i() > 0}>
454
463
  <ResizableHandle
455
- withHandle
464
+ handle={props.handle ?? 'line'}
456
465
  orientation={orientation()}
457
466
  static={isStatic()}
458
467
  onPanelResize={() => {
@@ -516,6 +525,68 @@ defineWebComponent<GroupProps, GroupEvents>('kai-resizable', {
516
525
  );
517
526
  });
518
527
 
528
+ /**
529
+ * Reflect a `<kai-resizable-item>`'s reactive CONFIG props to the ATTRIBUTES the
530
+ * parent `<kai-resizable>` reads (`readItems()` + its `size`/`min`/`max`/`locked`
531
+ * MutationObserver). Framework runtimes (React et al.) assign these as DOM
532
+ * *properties* — `el.size = "280px"` — but component-register only mirrors
533
+ * attribute→property, never the reverse, so a property-set size never becomes an
534
+ * attribute and the parent silently falls back to a flexible split. This is the
535
+ * exact class of bug the `collapsed` reflection already fixed; extended here to
536
+ * `size`/`min`/`max` (strings) and `locked` (boolean). `hidden` is deliberately
537
+ * NOT reflected — native `el.hidden` reflects to the attribute on its own and the
538
+ * parent honours the `hidden` property directly (and maximize drives `hidden`).
539
+ *
540
+ * Durability (don't fight drags): the parent WRITES `size` back to the live percent
541
+ * during a drag (`persistSizes` → `setAttribute('size', …)`), and that attribute
542
+ * write back-propagates into this prop's signal (component-register's
543
+ * attributeChangedCallback → property → the solid-element prop signal). A naive
544
+ * "reflect on every signal change" effect would therefore re-stomp the dragged size
545
+ * the next time a framework re-render re-asserts the ORIGINAL declared `size`
546
+ * (e.g. React re-runs `el.size = "280px"` on every render). So each string reflect
547
+ * only writes when the value the CONSUMER declared actually changes: a signal value
548
+ * that already equals the current attribute came FROM an attribute write (a drag),
549
+ * not the consumer, so it is left untouched and is not adopted as the new baseline.
550
+ * Net effect: a dragged size survives re-renders, while an explicit `size` change
551
+ * still applies. `size` prop signals use Solid's DEFAULT (same-value-skipping)
552
+ * equality — this guard is needed on top of that only because of the drag-write
553
+ * back-propagation, which same-value-skip alone can't see.
554
+ *
555
+ * Exported for unit testing (the live Shadow-DOM element is not jsdom-friendly).
556
+ */
557
+ export function reflectItemConfig(
558
+ element: HTMLElement,
559
+ props: { readonly size?: string; readonly min?: string; readonly max?: string },
560
+ flag: (name: string) => boolean,
561
+ ) {
562
+ const norm = (v: unknown): string | null => (v == null || v === '' ? null : String(v));
563
+ const reflectString = (name: 'size' | 'min' | 'max') => {
564
+ // Last value the CONSUMER declared (distinct from a value the parent wrote to
565
+ // the attribute mid-drag). Starts `undefined`, which `norm` never returns, so
566
+ // the first genuine value always reflects.
567
+ let lastConsumer: string | null | undefined;
568
+ createEffect(() => {
569
+ const desired = norm(props[name]); // reactive read
570
+ if (desired === lastConsumer) return; // consumer intent unchanged → leave the attr (the drag owns it)
571
+ const current = element.getAttribute(name);
572
+ // Already matches — either untouched or the parent wrote it (drag). Don't
573
+ // adopt it as the consumer baseline and don't fight it.
574
+ if (desired === current) return;
575
+ lastConsumer = desired;
576
+ if (desired === null) element.removeAttribute(name);
577
+ else element.setAttribute(name, desired);
578
+ });
579
+ };
580
+ reflectString('size');
581
+ reflectString('min');
582
+ reflectString('max');
583
+ // Boolean: mirror the `collapsed`/`loading` precedent — a bare `locked` attribute
584
+ // and the IDL property are both honoured via `flag`. The parent also writes
585
+ // `locked` during maximize/restore; because `flag` re-derives from that same
586
+ // attribute state, the reflection stays self-consistent (no fight).
587
+ createEffect(() => { element.toggleAttribute('locked', flag('locked')); });
588
+ }
589
+
519
590
  interface ItemProps extends Record<string, unknown> {
520
591
  /** Initial main-axis size: `"280px"` (fixed) or `"25%"`/`25` (percent). Omitted → flexible. */
521
592
  size?: string;
@@ -551,7 +622,7 @@ defineWebComponent<ItemProps>('kai-resizable-item', {
551
622
  locked: false,
552
623
  hidden: false,
553
624
  collapsed: false,
554
- }, (_props, { element, flag }) => {
625
+ }, (props, { element, flag }) => {
555
626
  // Reflect the reactive `collapsed` prop to the `collapsed` ATTRIBUTE the parent
556
627
  // <kai-resizable> reads (and observes). This is what makes a bare JSX boolean
557
628
  // collapse the panel at INITIAL render from any framework: component-register
@@ -563,6 +634,11 @@ defineWebComponent<ItemProps>('kai-resizable-item', {
563
634
  // `hidden`, so the back-compat hidden path and maximize (which drive `hidden`)
564
635
  // are untouched.
565
636
  createEffect(() => { element.toggleAttribute('collapsed', flag('collapsed')); });
637
+ // Reflect the other config props (`size`/`min`/`max`/`locked`) property→attribute
638
+ // for the SAME reason — a framework that sets them as DOM properties (React et al.)
639
+ // would otherwise be invisible to the parent. See reflectItemConfig for the
640
+ // drag-durability guard on `size`.
641
+ reflectItemConfig(element, props, flag);
566
642
  return (
567
643
  <>
568
644
  {/* The item host fills the panel's single grid cell (the panel stretches it on
@@ -157,7 +157,7 @@ defineWebComponent<Props, Events>('kai-search', {
157
157
  </button>
158
158
  </Show>
159
159
  <Show when={!value() && props.shortcut}>
160
- <Kbd keys={props.shortcut as string} size="sm" class="opacity-80" />
160
+ <Kbd keys={props.shortcut as string} size="sm" />
161
161
  </Show>
162
162
  </>
163
163
  );
@@ -552,11 +552,18 @@ export const MENU_PARTS: PartDef[] = [
552
552
  { name: 'shortcut', doc: 'The right-aligned per-item keyboard shortcut, rendered as kai-kbd key caps. Shown only when an item carries a `shortcut`.', recipe: 'kai-menu::part(shortcut) { opacity: 0.8 }' },
553
553
  ];
554
554
 
555
+ /** Slots of `<kai-thread>` (the standalone scrolling message list; messages come
556
+ * from the `messages` prop). */
557
+ export const THREAD_SLOTS: SlotDef[] = [
558
+ { name: 'empty', mode: 'replace', doc: 'Custom zero-state rendered in the message area while the thread is empty; replaces the built-in default.' },
559
+ ];
560
+
555
561
  export const ELEMENT_COMPOSITION: Record<string, ElementComposition> = {
556
562
  'kai-chat': { slots: CHAT_SLOTS, parts: CHAT_PARTS },
557
563
  'kai-command': { parts: COMMAND_PARTS },
558
564
  'kai-conversations': { slots: CONVERSATIONS_SLOTS, parts: CONVERSATIONS_PARTS },
559
565
  'kai-message': { slots: MESSAGE_SLOTS, parts: MESSAGE_PARTS },
566
+ 'kai-thread': { slots: THREAD_SLOTS },
560
567
  'kai-prompt-input': { slots: PROMPT_INPUT_SLOTS, parts: PROMPT_INPUT_PARTS },
561
568
  'kai-button': { slots: BUTTON_SLOTS, parts: BUTTON_PARTS },
562
569
  'kai-badge': { parts: BADGE_PARTS },
@@ -0,0 +1,105 @@
1
+ import { createSignal, onMount, onCleanup } from 'solid-js';
2
+ import { defineWebComponent } from './define';
3
+ import { readSlots, THREAD_SLOTS } from './slots';
4
+ import { Thread, type ThreadController } from '../components/thread';
5
+ import type { ChatMessage } from './chat-types';
6
+ import type { ProseSize } from '../primitives/chat-config';
7
+
8
+ interface Props extends Record<string, unknown> {
9
+ /** The full message thread to render, newest last. Each entry carries its role,
10
+ * content, and optional reasoning/tools/attachments/actions/avatar. Set as a JS
11
+ * property (`el.messages = [...]`); a NEW array reference per streaming chunk
12
+ * re-renders (mutating in place does not). */
13
+ messages?: ChatMessage[];
14
+ /** Show a typing indicator on the pending assistant turn — set while awaiting
15
+ * the assistant's reply. */
16
+ loading?: boolean;
17
+ /** Body/prose font scale for rendered markdown (`'xs' | 'sm' | 'base' | 'lg'`).
18
+ * Defaults to `'sm'`. */
19
+ proseSize?: ProseSize;
20
+ /** Shiki theme name for syntax-highlighted code blocks (e.g.
21
+ * `'github-dark-dimmed'`). */
22
+ codeTheme?: string;
23
+ /** Enable Shiki syntax highlighting in code blocks. Turn off to render plain
24
+ * `<pre>` blocks (lighter, no highlighter load). Default true. */
25
+ codeHighlight?: boolean;
26
+ /** Whether each message's action bar is always visible (`'always'`, default) or
27
+ * only revealed on hover of that message row (`'hover'`). */
28
+ actionsReveal?: 'always' | 'hover';
29
+ /** Show the scroll-to-bottom button inside the scroll area. Default true. */
30
+ scrollButton?: boolean;
31
+ /** Extra classes applied to the thread's inner root. */
32
+ class?: string;
33
+ }
34
+
35
+ /** Events fired by `<kai-thread>`. */
36
+ interface Events extends Record<string, unknown> {
37
+ /** A message's action button was clicked. `action` is the built-in name (`copy`
38
+ * / `like` / `dislike` / `regenerate` / `edit`) or a custom id. `state` is
39
+ * present only for the toggleable feedback votes: `'on'` when a like/dislike is
40
+ * set, `'off'` when re-tapped to clear. */
41
+ 'kai-message-action': { messageId: string; action: string; state?: 'on' | 'off' };
42
+ }
43
+
44
+ /**
45
+ * `<kai-thread>` — the scrolling message list of a chat, as a standalone
46
+ * composable element: one message row per `messages` entry with markdown / code
47
+ * highlight / reasoning + tool panels / avatars / action row, stick-to-bottom
48
+ * scroll with a scroll-to-bottom button, an optional typing indicator, and an
49
+ * `empty` slot. It fills the height its parent gives it and scrolls internally
50
+ * (`:host { display:block; height:100% }`). No composer, header, suggestions, or
51
+ * sidebar — pair it with `<kai-prompt-input>` and your own layout, or reach for
52
+ * the batteries-included `<kai-chat>`. Emits `kai-message-action`; exposes
53
+ * `scrollToBottom()`.
54
+ */
55
+ defineWebComponent<Props, Events>('kai-thread', {
56
+ messages: undefined,
57
+ loading: false,
58
+ proseSize: 'sm',
59
+ codeTheme: 'github-dark-dimmed',
60
+ codeHighlight: true,
61
+ actionsReveal: 'always',
62
+ scrollButton: true,
63
+ class: undefined,
64
+ }, (props, { element, dispatch, flag, expose }) => {
65
+ let controller: ThreadController | undefined;
66
+
67
+ // Detect whether the consumer projected `slot="empty"` content, so the built-in
68
+ // default only renders when they did NOT.
69
+ const [slots, setSlots] = createSignal<Record<string, boolean>>({});
70
+ onMount(() => {
71
+ const read = () => setSlots(readSlots(element, THREAD_SLOTS));
72
+ read();
73
+ const observer = new MutationObserver(read);
74
+ observer.observe(element, { childList: true });
75
+ onCleanup(() => observer.disconnect());
76
+ });
77
+
78
+ // Imperative method API — forward the thread's scroll control onto the host.
79
+ expose({
80
+ /** Scroll the message list to the bottom (default `'smooth'`). */
81
+ scrollToBottom: (behavior?: ScrollBehavior) => controller?.scrollToBottom(behavior),
82
+ });
83
+
84
+ return (
85
+ <>
86
+ {/* Fill the height the parent gives us and scroll internally, like
87
+ <kai-resizable>. Consumers only need to give a parent (or the element) a
88
+ height. */}
89
+ <style>{':host{display:block;height:100%}'}</style>
90
+ <Thread
91
+ class={props.class as string | undefined}
92
+ messages={(props.messages as ChatMessage[] | undefined) ?? []}
93
+ loading={flag('loading')}
94
+ proseSize={props.proseSize as ProseSize}
95
+ codeTheme={props.codeTheme as string}
96
+ codeHighlight={flag('codeHighlight')}
97
+ actionsReveal={props.actionsReveal as 'always' | 'hover'}
98
+ scrollButton={props.scrollButton !== false}
99
+ empty={slots()['empty'] ? <slot name="empty" /> : undefined}
100
+ onMessageAction={(detail) => dispatch('kai-message-action', detail)}
101
+ controllerRef={(c) => (controller = c)}
102
+ />
103
+ </>
104
+ );
105
+ });
@@ -1,7 +1,7 @@
1
1
  import type { Component } from 'solid-js';
2
2
  import {
3
3
  Copy, ThumbsUp, ThumbsDown, RefreshCw, Pencil,
4
- Share, Bookmark, Download, Link, Trash2, Check, X, Star, Flag, Reply, MoreHorizontal,
4
+ Share, Bookmark, Download, Link, Trash2, Check, X, Star, Flag, Reply, MoreHorizontal, Volume2,
5
5
  } from 'lucide-solid';
6
6
  import type { ChatMessageAction } from '../elements/chat-types';
7
7
 
@@ -33,6 +33,7 @@ const ICONS: Record<string, IconComponent> = {
33
33
  flag: Flag,
34
34
  reply: Reply,
35
35
  more: MoreHorizontal,
36
+ 'volume-2': Volume2,
36
37
  };
37
38
 
38
39
  /** Resolve a curated icon name to its component, or `undefined` when unknown
@@ -41,7 +41,11 @@ export function CommandList(props: CommandListProps): JSX.Element {
41
41
  const hasItems = () => props.groups.some((g) => g.items.length > 0);
42
42
 
43
43
  return (
44
- <div role="listbox" aria-label={props.ariaLabel ?? 'Command palette'} id={props.id}>
44
+ <div
45
+ role={hasItems() ? 'listbox' : undefined}
46
+ aria-label={hasItems() ? (props.ariaLabel ?? 'Command palette') : undefined}
47
+ id={props.id}
48
+ >
45
49
  <Show
46
50
  when={hasItems()}
47
51
  fallback={
package/src/ui/icon.tsx CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  House, Clock, Lock, Box, Briefcase, PanelLeft, Circle, SlidersHorizontal, Workflow, SquarePen,
9
9
  Archive, ArrowUp, Bookmark, Copy, Ellipsis, ExternalLink, Flag,
10
10
  GitBranch, GitPullRequest, ListFilter, RotateCw,
11
+ Moon, Sun,
11
12
  } from 'lucide-solid';
12
13
 
13
14
  type IconComponent = Component<{ class?: string }>;
@@ -64,6 +65,9 @@ const NAMED_ICONS: Record<string, IconComponent> = {
64
65
  'git-pull-request': GitPullRequest,
65
66
  'list-filter': ListFilter,
66
67
  'rotate-cw': RotateCw,
68
+ // Theme / appearance glyphs.
69
+ moon: Moon,
70
+ sun: Sun,
67
71
  };
68
72
 
69
73
  /** Render an item icon.
@@ -206,7 +206,14 @@ export function usePosition(
206
206
  // on unmount via onCleanup.
207
207
  const cleanup = autoUpdate(ref, float, update);
208
208
  let ro: ResizeObserver | undefined;
209
- if (typeof ResizeObserver === 'function') {
209
+ // Only a REAL element can be observed. The composer's trigger menu anchors to a
210
+ // VIRTUAL reference (a caret-rect object exposing just getBoundingClientRect, no
211
+ // DOM node); ResizeObserver.observe() throws "parameter 1 is not of type 'Element'"
212
+ // on it, and that uncaught throw aborts this effect before onCleanup registers,
213
+ // which corrupted the menu's reactive scope so it never re-opened after the first
214
+ // time. autoUpdate already tracks the virtual anchor; this ResizeObserver is a
215
+ // real-element-only add-on for ancestor/element resizes.
216
+ if (typeof ResizeObserver === 'function' && ref instanceof Element) {
210
217
  ro = new ResizeObserver(update);
211
218
  ro.observe(ref);
212
219
  for (const ancestor of getOverflowAncestors(ref)) {