@kitn.ai/ui 0.30.0 → 0.31.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 (261) hide show
  1. package/README.md +1 -1
  2. package/dist/{Icon-BzBET3E-.js → Icon-DJDuE_-x.js} +1 -1
  3. package/dist/action-icons-xkBBVKkF.js +1 -0
  4. package/dist/agent-tooling/construct/schema-url.d.ts +9 -0
  5. package/dist/agent-tooling/construct/schema.d.ts +76 -1
  6. package/dist/agent-tooling/construct/templates.d.ts +45 -0
  7. package/dist/arrow-down-DU6wOl-h.js +1 -0
  8. package/dist/arrow-left-DD46kN17.js +1 -0
  9. package/dist/{artifact-DGjzHYUT.js → artifact-DoUAr1Fd.js} +1 -1
  10. package/dist/assets/dev-DwzHsqYD.js +553 -0
  11. package/dist/{attachments-YvXHlg88.js → attachments-BLWPDiLH.js} +1 -1
  12. package/dist/{audio-visualizer-9lz5IHsL.js → audio-visualizer-DhHicYvV.js} +1 -1
  13. package/dist/{badge-Dh_vp8A5.js → badge-BGm7pcwi.js} +1 -1
  14. package/dist/{bell-CT7_WUwS.js → bell-CMEdSo7A.js} +1 -1
  15. package/dist/builder-app/App.d.ts +1 -0
  16. package/dist/builder-app/edit-guard.d.ts +8 -0
  17. package/dist/builder-app/main.d.ts +0 -0
  18. package/dist/builder-page/assets/index-C10OF-ur.js +72 -0
  19. package/dist/builder-page/assets/index-CFyKLBlu.css +1 -0
  20. package/dist/builder-page/index.html +20 -0
  21. package/dist/button-BM0YTTDQ.js +1 -0
  22. package/dist/{card-renderer-DZVaGH2e.js → card-renderer-Ds-wXc6_.js} +1 -1
  23. package/dist/check-BG1VfYjm.js +1 -0
  24. package/dist/{checkbox-group-BiryEXPQ.js → checkbox-group-CqXj8ehz.js} +1 -1
  25. package/dist/{checkbox-Bu2T2e0U.js → checkbox-np_5mKxM.js} +1 -1
  26. package/dist/chevron-down-CZ4W0hy6.js +1 -0
  27. package/dist/chevron-right-5gdxkCTF.js +1 -0
  28. package/dist/{choice-card-CM6IjkmL.js → choice-card-BYBmPAT5.js} +1 -1
  29. package/dist/circle-C0lwjD2A.js +1 -0
  30. package/dist/circle-check-BfT1cxQU.js +1 -0
  31. package/dist/{circle-x-Bk-dA5Wf.js → circle-x-DzUfvRqr.js} +1 -1
  32. package/dist/{code-block-B26t-Q7q.js → code-block-H6E5Eurc.js} +1 -1
  33. package/dist/{collapsible-Bhc636Cf.js → collapsible-CBq1Tj6y.js} +1 -1
  34. package/dist/components/builder-message-actions.d.ts +9 -18
  35. package/dist/components/builder-panel-derived.d.ts +18 -0
  36. package/dist/components/builder-panel.d.ts +51 -0
  37. package/dist/components/builder-start.d.ts +14 -10
  38. package/dist/components/chat-thread.d.ts +27 -1
  39. package/dist/components/construct-form-paths.d.ts +54 -0
  40. package/dist/components/message.d.ts +5 -0
  41. package/dist/{composer-DMI85B6j.js → composer-B5IvgxAF.js} +1 -1
  42. package/dist/{confirm-card-B0YIAwXp.js → confirm-card-B63pZIfG.js} +1 -1
  43. package/dist/construct-cli.es.js +469 -125
  44. package/dist/construct-templates.d.ts +5 -0
  45. package/dist/construct-templates.js +266 -0
  46. package/dist/construct.js +269 -86
  47. package/dist/{context-DWgfhqWp.js → context-Byqor78j.js} +1 -1
  48. package/dist/controllable-C4BSt73s.js +1 -0
  49. package/dist/{conversation-item-BsOsVGmT.js → conversation-item-piXZFRWa.js} +1 -1
  50. package/dist/{conversation-list-aX3Vtc7L.js → conversation-list-B5yu9cPg.js} +1 -1
  51. package/dist/{copy-BmEwsNhJ.js → copy-DfI2NYEX.js} +1 -1
  52. package/dist/{create-tween-CNOAU3Fx.js → create-tween-C4j96_Aj.js} +1 -1
  53. package/dist/{create-tween-WfqiENls.js → create-tween-CHleHI8m.js} +1 -1
  54. package/dist/{create-tween-wlFtW9Kh.js → create-tween-DQ6lOhdl.js} +1 -1
  55. package/dist/{create-tween-zt79ba32.js → create-tween-DyjTgkJi.js} +1 -1
  56. package/dist/custom-elements.json +40 -5
  57. package/dist/{default-input-DMV5Eaze.js → default-input-Bwmt8Lai.js} +1 -1
  58. package/dist/define-B3OpsIDE.js +1 -0
  59. package/dist/define.js +2 -2
  60. package/dist/define.server.js +2 -2
  61. package/dist/{disclosure-NnACBxsC.js → disclosure-ZSAlD4wg.js} +1 -1
  62. package/dist/{dropdown-DT7G0RGR.js → dropdown-CSRhiMN2.js} +1 -1
  63. package/dist/elements/agent-card.js +1 -1
  64. package/dist/elements/artifact.js +1 -1
  65. package/dist/elements/attachments.js +1 -1
  66. package/dist/elements/audio-visualizer.js +1 -1
  67. package/dist/elements/avatar.js +1 -1
  68. package/dist/elements/badge.js +1 -1
  69. package/dist/elements/button.js +1 -1
  70. package/dist/elements/card.js +1 -1
  71. package/dist/elements/cards.js +1 -1
  72. package/dist/elements/chain-of-thought.js +1 -1
  73. package/dist/elements/chat-actions.d.ts +12 -0
  74. package/dist/elements/chat-scope-picker.js +1 -1
  75. package/dist/elements/chat-types.d.ts +7 -2
  76. package/dist/elements/chat-workspace.js +1 -1
  77. package/dist/elements/chat.js +1 -1
  78. package/dist/elements/checkbox-group.js +1 -1
  79. package/dist/elements/checkbox.js +1 -1
  80. package/dist/elements/checkpoint.js +1 -1
  81. package/dist/elements/choice.js +1 -1
  82. package/dist/elements/coachmark.js +1 -1
  83. package/dist/elements/code-block.js +1 -1
  84. package/dist/elements/command.js +1 -1
  85. package/dist/elements/compare.js +1 -1
  86. package/dist/elements/composer.js +1 -1
  87. package/dist/elements/confirm-card.js +1 -1
  88. package/dist/elements/context-meter.js +1 -1
  89. package/dist/elements/conversation-item.js +1 -1
  90. package/dist/elements/conversation-list.js +1 -1
  91. package/dist/elements/dialog.js +1 -1
  92. package/dist/elements/dock.js +1 -1
  93. package/dist/elements/dropdown.js +1 -1
  94. package/dist/elements/editable-label.js +1 -1
  95. package/dist/elements/embed.js +1 -1
  96. package/dist/elements/empty.js +1 -1
  97. package/dist/elements/feedback-bar.js +1 -1
  98. package/dist/elements/file-tree.js +1 -1
  99. package/dist/elements/file-upload.js +1 -1
  100. package/dist/elements/form.js +1 -1
  101. package/dist/elements/hover-card.js +1 -1
  102. package/dist/elements/icon.js +1 -1
  103. package/dist/elements/image.js +1 -1
  104. package/dist/elements/input.js +1 -1
  105. package/dist/elements/kbd.js +1 -1
  106. package/dist/elements/link-preview.js +1 -1
  107. package/dist/elements/loader.js +1 -1
  108. package/dist/elements/markdown.js +1 -1
  109. package/dist/elements/menu.js +1 -1
  110. package/dist/elements/message-skills.js +1 -1
  111. package/dist/elements/message.js +1 -1
  112. package/dist/elements/model-switcher.js +1 -1
  113. package/dist/elements/nav.js +1 -1
  114. package/dist/elements/notice.js +1 -1
  115. package/dist/elements/pane-grid.js +1 -1
  116. package/dist/elements/pane-group.js +1 -1
  117. package/dist/elements/pane.js +1 -1
  118. package/dist/elements/popover.js +1 -1
  119. package/dist/elements/progress-bar.js +1 -1
  120. package/dist/elements/prompt-dock.js +1 -1
  121. package/dist/elements/prompt-input.js +1 -1
  122. package/dist/elements/prompt-suggestions.js +1 -1
  123. package/dist/elements/radio-group.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/select.js +1 -1
  134. package/dist/elements/separator.js +1 -1
  135. package/dist/elements/setting-item.js +1 -1
  136. package/dist/elements/settings-group.js +1 -1
  137. package/dist/elements/skeleton.js +1 -1
  138. package/dist/elements/slider.js +1 -1
  139. package/dist/elements/source.js +1 -1
  140. package/dist/elements/status.js +1 -1
  141. package/dist/elements/switch.js +1 -1
  142. package/dist/elements/tabs.js +1 -1
  143. package/dist/elements/tasks.js +1 -1
  144. package/dist/elements/text-shimmer.js +1 -1
  145. package/dist/elements/thinking-bar.js +1 -1
  146. package/dist/elements/thread.js +1 -1
  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 +22 -10
  153. package/dist/{ellipsis-6pJ7SxYT.js → ellipsis-CJ5QIjWo.js} +1 -1
  154. package/dist/{embed-BMje-nxj.js → embed-6Vji0PfS.js} +1 -1
  155. package/dist/{external-link-Beh9DKeR.js → external-link-BPTtTdet.js} +1 -1
  156. package/dist/{file-text-D1GPfZ8Q.js → file-text-eVjcFDrK.js} +1 -1
  157. package/dist/{file-tree-Dwj2lqg-.js → file-tree-a9tfWYmg.js} +1 -1
  158. package/dist/{folder-b6D8x1DY.js → folder-DN6Ilq6d.js} +1 -1
  159. package/dist/{form-DWgdxDXu.js → form-U1cwhcQx.js} +1 -1
  160. package/dist/{hover-card-CWRkqm40.js → hover-card-C5ptzZOZ.js} +1 -1
  161. package/dist/{icon-CL0jdhsf.js → icon-BOMrFp0D.js} +1 -1
  162. package/dist/index.js +1991 -1989
  163. package/dist/index.server.js +1886 -1884
  164. package/dist/{info-0cOUherQ.js → info-DxzKsIHn.js} +1 -1
  165. package/dist/{input-BH6Ru91Q.js → input-aW72PACH.js} +1 -1
  166. package/dist/kai.es.js +1 -1
  167. package/dist/{kbd-DRAnVtNX.js → kbd-BP-6MwUs.js} +1 -1
  168. package/dist/{link-preview-DODgRyV9.js → link-preview-CZaDilMG.js} +1 -1
  169. package/dist/{link-Dlr0vssB.js → link-rlN11N0B.js} +1 -1
  170. package/dist/{loader-KvJB6NrT.js → loader-BdFK70fF.js} +1 -1
  171. package/dist/{markdown-DX9tuJVj.js → markdown-DjEaoNz-.js} +1 -1
  172. package/dist/mcp.es.js +579 -107
  173. package/dist/message--Nlbemtp.js +1 -0
  174. package/dist/message-C88yTu2A.js +1 -0
  175. package/dist/message-circle-CZeVDN1f.js +1 -0
  176. package/dist/message-square-BXNBKC6i.js +1 -0
  177. package/dist/{minimize-2-CyOHJwcN.js → minimize-2-BxLOOSEf.js} +1 -1
  178. package/dist/{model-switcher-0hrKDiK2.js → model-switcher-BxRvub2A.js} +1 -1
  179. package/dist/{overlay-DBMFXie6.js → overlay-CDwKJtHN.js} +1 -1
  180. package/dist/{panel-right-t8SWJL6e.js → panel-right-BovedCwW.js} +1 -1
  181. package/dist/{paperclip-CdzuOjbU.js → paperclip-D0Xyjja2.js} +1 -1
  182. package/dist/play-DGJ4MgB2.js +1 -0
  183. package/dist/primitives/speech.d.ts +35 -0
  184. package/dist/{progress-bar-CiBtxMWT.js → progress-bar-D-aQfy5h.js} +1 -1
  185. package/dist/{prompt-suggestion-I_DHP21i.js → prompt-suggestion-C6qLjMEQ.js} +1 -1
  186. package/dist/{radio-C_p3n39l.js → radio-D1CkXISc.js} +1 -1
  187. package/dist/react/index.d.ts +22 -6
  188. package/dist/react.js +13 -13
  189. package/dist/{reasoning-BDwy_tIe.js → reasoning-DARmbgmE.js} +1 -1
  190. package/dist/register-impl-WcrdIihK.js +424 -0
  191. package/dist/{resizable-SYC-fStM.js → resizable-Da6oSO2z.js} +1 -1
  192. package/dist/{rotate-ccw-B1nwmwuY.js → rotate-ccw-31ed1xK_.js} +1 -1
  193. package/dist/{rotate-cw-CLAnBcm1.js → rotate-cw-qkl39dw3.js} +1 -1
  194. package/dist/{scroll-area-zRyXoLhI.js → scroll-area-RrKnLYlA.js} +1 -1
  195. package/dist/{scroll-button-jzrbYCG9.js → scroll-button--QeNjobK.js} +1 -1
  196. package/dist/{select-DM-0nEe0.js → select-CS_0mWW3.js} +1 -1
  197. package/dist/{separator-BToOOoOt.js → separator-fT-zBypW.js} +1 -1
  198. package/dist/{settings-PaGo_S4U.js → settings-dfkV_WDm.js} +1 -1
  199. package/dist/{settings-group-BFXGIfXq.js → settings-group-CX694npG.js} +1 -1
  200. package/dist/{skeleton-DYpmFnnm.js → skeleton-BsqKD9TV.js} +1 -1
  201. package/dist/{slider-ebutAmkQ.js → slider-_EWfTH9H.js} +1 -1
  202. package/dist/{solid-15v_3WVL.js → solid-BITWG_VE.js} +3073 -3006
  203. package/dist/{solid-5P-otgdi.js → solid-CRFYWlBw.js} +3865 -3797
  204. package/dist/solid.js +1 -1
  205. package/dist/solid.server.js +1 -1
  206. package/dist/{source-c1gpRl9h.js → source-Da1TFiuj.js} +1 -1
  207. package/dist/speech-CjbWuqG2.js +1 -0
  208. package/dist/{star-pcV_3LqF.js → star-ZvSbbXS6.js} +1 -1
  209. package/dist/{store-CSgbFFCg.js → store-DE5lcr9q.js} +1 -1
  210. package/dist/{switch-Bh2OdLV2.js → switch-P_fsHqv3.js} +1 -1
  211. package/dist/{tasks-card-CbqQAn2S.js → tasks-card-B86qqyZm.js} +1 -1
  212. package/dist/{text-shimmer-BSzgEnDH.js → text-shimmer-BAkw_65G.js} +1 -1
  213. package/dist/{textarea-DrYNt_0z.js → textarea-Bkinatpi.js} +1 -1
  214. package/dist/{thumbs-up-BL3-NenK.js → thumbs-up-CDGW7a4o.js} +1 -1
  215. package/dist/{toast-store-DkOlYczt.js → toast-store-B94I50AO.js} +1 -1
  216. package/dist/{tool-CeIWUVUe.js → tool-CUiCeyi1.js} +1 -1
  217. package/dist/{tooltip-GA-nmg06.js → tooltip-B3wlQSPh.js} +1 -1
  218. package/dist/{trash-2-CT66QWlm.js → trash-2-Cu8yZmFQ.js} +1 -1
  219. package/dist/{triangle-alert-CeHLWxhD.js → triangle-alert-CCtDUUmn.js} +1 -1
  220. package/dist/ui/button-variant-names.d.ts +12 -0
  221. package/dist/ui/button.d.ts +6 -0
  222. package/dist/{upload-9Csb0F0J.js → upload-CxotJlLv.js} +1 -1
  223. package/dist/{use-card-resolution-Zg0a08Z-.js → use-card-resolution-DILUvNyj.js} +1 -1
  224. package/dist/{variant-aurora-4VBQHIKV.js → variant-aurora-BGaXBBGf.js} +1 -1
  225. package/dist/{variant-aurora-LE3fqxgw.js → variant-aurora-BLcQmvkd.js} +1 -1
  226. package/dist/{variant-aurora-DBeCAQNk.js → variant-aurora-DZIfAtx1.js} +2 -2
  227. package/dist/{variant-aurora-FzHVXHby.js → variant-aurora-DpoIW4ZX.js} +2 -2
  228. package/dist/{variant-custom-DI5R0rln.js → variant-custom-BhtWdAxE.js} +1 -1
  229. package/dist/{variant-custom-WWnrrRHS.js → variant-custom-BtApuSyr.js} +2 -2
  230. package/dist/{variant-custom-BQkNuPpE.js → variant-custom-C-zS4ZtE.js} +2 -2
  231. package/dist/{variant-custom-AFRGcOBS.js → variant-custom-swi9PXxU.js} +1 -1
  232. package/dist/{variant-wave-wTP5v_jU.js → variant-wave-7JCIwhpb.js} +1 -1
  233. package/dist/{variant-wave-nSI2sfII.js → variant-wave-BG4nGr6x.js} +1 -1
  234. package/dist/{variant-wave-DuQV7DhQ.js → variant-wave-C32lM9G2.js} +2 -2
  235. package/dist/{variant-wave-DUCbH5oI.js → variant-wave-Dl6jOiK3.js} +2 -2
  236. package/dist/x-C4niw05N.js +1 -0
  237. package/frameworks/react/index.tsx +12 -6
  238. package/llms-full.txt +9 -6
  239. package/llms.txt +1 -1
  240. package/package.json +7 -3
  241. package/solid.css +17 -2
  242. package/src/elements/element-meta.json +39 -14
  243. package/dist/action-icons-DaW2pU6m.js +0 -1
  244. package/dist/arrow-down-D783iMJj.js +0 -1
  245. package/dist/arrow-left-0VLM5kyg.js +0 -1
  246. package/dist/assets/dev-5XzXYR21.js +0 -91
  247. package/dist/button-C_3te6fm.js +0 -1
  248. package/dist/check-CiEOVnEN.js +0 -1
  249. package/dist/chevron-down-CSobuZRG.js +0 -1
  250. package/dist/chevron-right-DR9ZAP-2.js +0 -1
  251. package/dist/circle-BC66EYF7.js +0 -1
  252. package/dist/circle-check-BqmahW0O.js +0 -1
  253. package/dist/controllable-BagRpyHv.js +0 -1
  254. package/dist/define-CI0y3eE4.js +0 -1
  255. package/dist/message-BqQ7Ot5q.js +0 -1
  256. package/dist/message-DkaGfBd8.js +0 -1
  257. package/dist/message-circle-BPpp5QKv.js +0 -1
  258. package/dist/message-square-BLbsd9BJ.js +0 -1
  259. package/dist/play-BXy1u7rW.js +0 -1
  260. package/dist/register-impl-DvEnhIxx.js +0 -424
  261. package/dist/x-eiO9V6J6.js +0 -1
@@ -15,7 +15,14 @@ function isSafeUrl(url) {
15
15
  const scheme = schemeOf(url);
16
16
  return scheme !== void 0 && SAFE_SCHEMES.includes(scheme);
17
17
  }
18
+ const CHAT_MESSAGE_ACTIONS = ["copy", "like", "dislike", "regenerate", "edit", "speak"];
19
+ const BUTTON_VARIANT_NAMES = ["default", "ghost", "subtle", "outline", "destructive"];
18
20
  const TAG_RE = /^[a-z][a-z0-9]*-[a-z0-9-]+$/;
21
+ const TriggerEntrySchema = z.object({
22
+ id: z.string().min(1),
23
+ label: z.string().min(1),
24
+ description: z.string().min(1).optional()
25
+ }).strict();
19
26
  const ProviderSchema = z.discriminatedUnion("mode", [
20
27
  z.object({ mode: z.literal("mock") }).strict(),
21
28
  z.object({
@@ -84,7 +91,26 @@ const ConstructSchema = z.object({
84
91
  /** Rendered in ChatThread's built-in header bar (left side). Construct-
85
92
  * authored/untrusted text, like theme.accent/provider.url — JSON.stringify'd
86
93
  * at its one emit site, never a raw JSX attribute string. */
87
- title: z.string().min(1).optional()
94
+ title: z.string().min(1).optional(),
95
+ /** Renders a theme-toggle Button in ChatThread's header-end region,
96
+ * flipping the host's `theme` attribute (the attribute
97
+ * defineWebComponent already owns) via the facade's ctx.element —
98
+ * codegen work, no new kit surface (B-10). */
99
+ themeToggle: z.boolean().optional(),
100
+ /** Header action buttons (label + kit Button variant), rendered in
101
+ * the header-end region. Vocabulary-never-logic: the construct
102
+ * cannot say what an action DOES, so each click dispatches a
103
+ * non-bubbling `kai-header-action` CustomEvent on the host with
104
+ * `detail: { label }` — the consumer's listening seam (B-10).
105
+ * `variant` derives from the kit Button's own list
106
+ * (BUTTON_VARIANT_NAMES — B-6a), never restated. `label` is
107
+ * construct-authored untrusted text, JSON.stringify'd at emit. */
108
+ actions: z.array(
109
+ z.object({
110
+ label: z.string().min(1),
111
+ variant: z.enum(BUTTON_VARIANT_NAMES).optional()
112
+ }).strict()
113
+ ).min(1).optional()
88
114
  }).strict().optional(),
89
115
  /** Greeting shown while the thread is empty (no messages yet) — the
90
116
  * proven "welcome screen" pattern (Intercom-class): title + optional
@@ -193,7 +219,28 @@ const ConstructSchema = z.object({
193
219
  * vocabulary; HOW it persists (the ConversationStore adapter,
194
220
  * localStorage vs. a fetch endpoint) is codegen's call, never
195
221
  * vocabulary here (C-3 — no transport-layer vocabulary). */
196
- conversations: z.literal(true).optional()
222
+ conversations: z.literal(true).optional(),
223
+ /** Role-scoped default action bars, threaded onto ChatThread's
224
+ * `userActions`/`assistantActions` props (B-3/B-7b). Ordered
225
+ * arrays; enum ids ONLY, read off the ONE const
226
+ * (CHAT_MESSAGE_ACTIONS — B-6): a CustomAction is an id the APP
227
+ * must handle, a construct has no app code, so emitting one is a
228
+ * dead affordance. Duplicates within one array rejected below
229
+ * (superRefine, the slots pattern). min(1): an empty list IS the
230
+ * absent key. */
231
+ messageActions: z.object({
232
+ user: z.array(z.enum(CHAT_MESSAGE_ACTIONS)).min(1).optional(),
233
+ assistant: z.array(z.enum(CHAT_MESSAGE_ACTIONS)).min(1).optional()
234
+ }).strict().optional(),
235
+ /** The citations STRIP (the `part="citations"` row consecutive
236
+ * `source` parts already collapse into — message.tsx). `strip:
237
+ * false` hides it (emits ChatThread's `hideSources`); `strip:
238
+ * true` or the key absent emits NOTHING — the row already renders,
239
+ * the kit default IS the on state, the same anchored-on-the-
240
+ * default convention as `reasoning: 'full'` (B-4). */
241
+ sources: z.object({
242
+ strip: z.boolean().optional()
243
+ }).strict().optional()
197
244
  }).strict().optional(),
198
245
  /** Named generative-UI card definitions the model can emit as tool calls,
199
246
  * rendered in the thread. Top-level (not a capability): a card is
@@ -240,109 +287,245 @@ const ConstructSchema = z.object({
240
287
  /** Seed Dock's open state at mount. Uncontrolled — never steals focus (Dock's
241
288
  * own focus contract, dock.tsx). Omitted keeps Dock's own default (closed). */
242
289
  defaultOpen: z.boolean().optional()
290
+ }).strict().optional(),
291
+ /** Layout-scoped aside geometry, `layout: 'aside'` only (superRefine
292
+ * below, mirroring `widget`'s scoping exactly). `position` picks the
293
+ * docked inline edge (default 'end', today's hardcoded behavior);
294
+ * `width` overrides codegen's 380px default. `width` is construct-
295
+ * authored untrusted text: it lands as a JSON.stringify'd VALUE inside
296
+ * the emitted Solid `style={{ }}` object — property assignment, the
297
+ * same no-CSS-text-interpolation guarantee as setProperty — never
298
+ * concatenated into a CSS string (B-2). */
299
+ aside: z.object({
300
+ position: z.enum(["start", "end"]).optional(),
301
+ width: z.string().min(1).optional()
302
+ }).strict().optional(),
303
+ /** Construct-wide shell chrome (10a): both members reuse REAL kit
304
+ * pieces — command.tsx's CommandList behind a codegen-emitted overlay
305
+ * (opened on Mod+K; entries DERIVE from what this construct enables —
306
+ * menu-honesty against dead entries), and the documented Dropdown+
307
+ * Avatar user-menu recipe. `name`/`plan` are construct-authored
308
+ * untrusted text, JSON.stringify'd at emit like every sibling.
309
+ * `commandPalette` is presence-only `z.literal(true)`, matching
310
+ * `conversations`' pattern. */
311
+ shell: z.object({
312
+ commandPalette: z.literal(true).optional(),
313
+ userMenu: z.object({
314
+ name: z.string().min(1),
315
+ plan: z.string().min(1).optional()
316
+ }).strict().optional()
317
+ }).strict().optional(),
318
+ /** Composer chrome — NOT a capability: chrome on the medium, like
319
+ * `header` (B-5). `triggers` maps onto ChatThread's real, shipped
320
+ * `triggers` prop: `slash` → `{ char: '/', kind: 'command', items }`,
321
+ * `mention` → `{ char: '@', kind: 'mention', items }` at emit. */
322
+ composer: z.object({
323
+ triggers: z.object({
324
+ slash: z.array(TriggerEntrySchema).min(1).optional(),
325
+ mention: z.array(TriggerEntrySchema).min(1).optional()
326
+ }).strict().optional()
243
327
  }).strict().optional()
244
328
  }).strict().superRefine((construct, ctx) => {
245
- if (construct.slots) {
246
- const seen = /* @__PURE__ */ new Set();
247
- construct.slots.forEach((name, i) => {
248
- if (seen.has(name)) {
329
+ for (const rule of CROSS_FIELD_RULES) rule.check(construct, ctx);
330
+ });
331
+ const CROSS_FIELD_RULES = [
332
+ {
333
+ id: "slots-unique",
334
+ paths: ["slots"],
335
+ check: (construct, ctx) => {
336
+ if (!construct.slots) return;
337
+ const seen = /* @__PURE__ */ new Set();
338
+ construct.slots.forEach((name, i) => {
339
+ if (seen.has(name)) {
340
+ ctx.addIssue({
341
+ code: z.ZodIssueCode.custom,
342
+ path: ["slots", i],
343
+ message: `duplicate slot name "${name}"`
344
+ });
345
+ }
346
+ seen.add(name);
347
+ });
348
+ }
349
+ },
350
+ {
351
+ id: "custom-layout-needs-slots",
352
+ paths: ["layout", "slots"],
353
+ check: (construct, ctx) => {
354
+ if (construct.layout === "custom" && (!construct.slots || construct.slots.length === 0)) {
249
355
  ctx.addIssue({
250
356
  code: z.ZodIssueCode.custom,
251
- path: ["slots", i],
252
- message: `duplicate slot name "${name}"`
357
+ path: ["slots"],
358
+ message: '"custom" layout requires at least one declared slot custom IS the slots grain'
253
359
  });
254
360
  }
255
- seen.add(name);
256
- });
257
- }
258
- if (construct.layout === "custom" && (!construct.slots || construct.slots.length === 0)) {
259
- ctx.addIssue({
260
- code: z.ZodIssueCode.custom,
261
- path: ["slots"],
262
- message: '"custom" layout requires at least one declared slot — custom IS the slots grain'
263
- });
264
- }
265
- if (construct.layout === "split" && construct.slots) {
266
- const i = construct.slots.indexOf("pane");
267
- if (i !== -1) {
268
- ctx.addIssue({
269
- code: z.ZodIssueCode.custom,
270
- path: ["slots", i],
271
- message: `"pane" collides with the "split" layout's own fixed <slot name="pane"> — choose a different slot name`
272
- });
273
361
  }
274
- }
275
- if (construct.widget && construct.layout !== "widget") {
276
- ctx.addIssue({
277
- code: z.ZodIssueCode.custom,
278
- path: ["widget"],
279
- message: '"widget" is only valid on layout: "widget"'
280
- });
281
- }
282
- if (construct.widget?.launcherIcon && !isSafeUrl(construct.widget.launcherIcon)) {
283
- ctx.addIssue({
284
- code: z.ZodIssueCode.custom,
285
- path: ["widget", "launcherIcon"],
286
- message: "launcherIcon must be an http(s)/mailto or relative URL — no javascript:/data: schemes"
287
- });
288
- }
289
- if (construct.empty?.icon && !isSafeUrl(construct.empty.icon)) {
290
- ctx.addIssue({
291
- code: z.ZodIssueCode.custom,
292
- path: ["empty", "icon"],
293
- message: "icon must be an http(s)/mailto or relative URL — no javascript:/data: schemes"
294
- });
295
- }
296
- const reasoning = construct.capabilities?.reasoning;
297
- const reasoningOpen = construct.capabilities?.reasoningOpen;
298
- if (reasoningOpen !== void 0 && (reasoning === "compact" || reasoning === "off")) {
299
- ctx.addIssue({
300
- code: z.ZodIssueCode.custom,
301
- path: ["capabilities", "reasoningOpen"],
302
- message: '"reasoningOpen" only applies when reasoning is "full" or omitted — "compact"/"off" have no disclosure to open'
303
- });
304
- }
305
- const history = construct.capabilities?.history;
306
- if (construct.capabilities?.conversations && (!history || history.persistence === "none")) {
307
- ctx.addIssue({
308
- code: z.ZodIssueCode.custom,
309
- path: ["capabilities", "conversations"],
310
- message: '"conversations" requires capabilities.history.persistence to be "local" or "endpoint" — a conversation list needs somewhere to persist conversations'
311
- });
312
- }
313
- const URL_SHAPED = /^[a-zA-Z][a-zA-Z0-9+.-]*:|^\/\//;
314
- for (const [i, link] of (construct.home?.links ?? []).entries()) {
315
- if (link.href && !isSafeUrl(link.href)) {
316
- ctx.addIssue({
317
- code: z.ZodIssueCode.custom,
318
- path: ["home", "links", i, "href"],
319
- message: "href must be an http(s)/mailto or relative URL — no javascript:/data: schemes"
320
- });
362
+ },
363
+ {
364
+ id: "split-pane-slot-collision",
365
+ paths: ["layout", "slots"],
366
+ check: (construct, ctx) => {
367
+ if (construct.layout === "split" && construct.slots) {
368
+ const i = construct.slots.indexOf("pane");
369
+ if (i !== -1) {
370
+ ctx.addIssue({
371
+ code: z.ZodIssueCode.custom,
372
+ path: ["slots", i],
373
+ message: `"pane" collides with the "split" layout's own fixed <slot name="pane"> — choose a different slot name`
374
+ });
375
+ }
376
+ }
321
377
  }
322
- if (link.icon && URL_SHAPED.test(link.icon) && !isSafeUrl(link.icon)) {
323
- ctx.addIssue({
324
- code: z.ZodIssueCode.custom,
325
- path: ["home", "links", i, "icon"],
326
- message: "icon must be a kit icon name or an http(s)/relative URL — no javascript:/data: schemes"
327
- });
378
+ },
379
+ {
380
+ id: "widget-layout-scope",
381
+ paths: ["layout", "widget"],
382
+ check: (construct, ctx) => {
383
+ if (construct.widget && construct.layout !== "widget") {
384
+ ctx.addIssue({
385
+ code: z.ZodIssueCode.custom,
386
+ path: ["widget"],
387
+ message: '"widget" is only valid on layout: "widget"'
388
+ });
389
+ }
390
+ }
391
+ },
392
+ {
393
+ id: "aside-layout-scope",
394
+ paths: ["layout", "aside"],
395
+ check: (construct, ctx) => {
396
+ if (construct.aside && construct.layout !== "aside") {
397
+ ctx.addIssue({
398
+ code: z.ZodIssueCode.custom,
399
+ path: ["aside"],
400
+ message: '"aside" is only valid on layout: "aside"'
401
+ });
402
+ }
403
+ }
404
+ },
405
+ {
406
+ id: "message-actions-unique",
407
+ paths: ["capabilities.messageActions.user", "capabilities.messageActions.assistant"],
408
+ check: (construct, ctx) => {
409
+ const messageActions = construct.capabilities?.messageActions;
410
+ if (!messageActions) return;
411
+ for (const role of ["user", "assistant"]) {
412
+ const list = messageActions[role];
413
+ if (!list) continue;
414
+ const seen = /* @__PURE__ */ new Set();
415
+ list.forEach((id, i) => {
416
+ if (seen.has(id)) {
417
+ ctx.addIssue({
418
+ code: z.ZodIssueCode.custom,
419
+ path: ["capabilities", "messageActions", role, i],
420
+ message: `duplicate action id "${id}"`
421
+ });
422
+ }
423
+ seen.add(id);
424
+ });
425
+ }
426
+ }
427
+ },
428
+ {
429
+ id: "launcher-icon-url",
430
+ paths: ["widget.launcherIcon"],
431
+ check: (construct, ctx) => {
432
+ if (construct.widget?.launcherIcon && !isSafeUrl(construct.widget.launcherIcon)) {
433
+ ctx.addIssue({
434
+ code: z.ZodIssueCode.custom,
435
+ path: ["widget", "launcherIcon"],
436
+ message: "launcherIcon must be an http(s)/mailto or relative URL — no javascript:/data: schemes"
437
+ });
438
+ }
439
+ }
440
+ },
441
+ {
442
+ id: "empty-icon-url",
443
+ paths: ["empty.icon"],
444
+ check: (construct, ctx) => {
445
+ if (construct.empty?.icon && !isSafeUrl(construct.empty.icon)) {
446
+ ctx.addIssue({
447
+ code: z.ZodIssueCode.custom,
448
+ path: ["empty", "icon"],
449
+ message: "icon must be an http(s)/mailto or relative URL — no javascript:/data: schemes"
450
+ });
451
+ }
452
+ }
453
+ },
454
+ {
455
+ id: "reasoning-open-scope",
456
+ paths: ["capabilities.reasoning", "capabilities.reasoningOpen"],
457
+ check: (construct, ctx) => {
458
+ const reasoning = construct.capabilities?.reasoning;
459
+ const reasoningOpen = construct.capabilities?.reasoningOpen;
460
+ if (reasoningOpen !== void 0 && (reasoning === "compact" || reasoning === "off")) {
461
+ ctx.addIssue({
462
+ code: z.ZodIssueCode.custom,
463
+ path: ["capabilities", "reasoningOpen"],
464
+ message: '"reasoningOpen" only applies when reasoning is "full" or omitted — "compact"/"off" have no disclosure to open'
465
+ });
466
+ }
467
+ }
468
+ },
469
+ {
470
+ id: "conversations-need-history",
471
+ paths: ["capabilities.conversations", "capabilities.history"],
472
+ check: (construct, ctx) => {
473
+ const history = construct.capabilities?.history;
474
+ if (construct.capabilities?.conversations && (!history || history.persistence === "none")) {
475
+ ctx.addIssue({
476
+ code: z.ZodIssueCode.custom,
477
+ path: ["capabilities", "conversations"],
478
+ message: '"conversations" requires capabilities.history.persistence to be "local" or "endpoint" — a conversation list needs somewhere to persist conversations'
479
+ });
480
+ }
481
+ }
482
+ },
483
+ {
484
+ id: "home-link-urls",
485
+ paths: ["home.links"],
486
+ check: (construct, ctx) => {
487
+ const URL_SHAPED = /^[a-zA-Z][a-zA-Z0-9+.-]*:|^\/\//;
488
+ for (const [i, link] of (construct.home?.links ?? []).entries()) {
489
+ if (link.href && !isSafeUrl(link.href)) {
490
+ ctx.addIssue({
491
+ code: z.ZodIssueCode.custom,
492
+ path: ["home", "links", i, "href"],
493
+ message: "href must be an http(s)/mailto or relative URL — no javascript:/data: schemes"
494
+ });
495
+ }
496
+ if (link.icon && URL_SHAPED.test(link.icon) && !isSafeUrl(link.icon)) {
497
+ ctx.addIssue({
498
+ code: z.ZodIssueCode.custom,
499
+ path: ["home", "links", i, "icon"],
500
+ message: "icon must be a kit icon name or an http(s)/relative URL — no javascript:/data: schemes"
501
+ });
502
+ }
503
+ }
504
+ }
505
+ },
506
+ {
507
+ id: "history-endpoint-url",
508
+ paths: ["capabilities.history.persistence", "capabilities.history.url"],
509
+ check: (construct, ctx) => {
510
+ const history = construct.capabilities?.history;
511
+ if (!history) return;
512
+ if (history.persistence === "endpoint" && !history.url) {
513
+ ctx.addIssue({
514
+ code: z.ZodIssueCode.custom,
515
+ path: ["capabilities", "history", "url"],
516
+ message: '"endpoint" persistence requires a url'
517
+ });
518
+ }
519
+ if (history.persistence !== "endpoint" && history.url !== void 0) {
520
+ ctx.addIssue({
521
+ code: z.ZodIssueCode.custom,
522
+ path: ["capabilities", "history", "url"],
523
+ message: 'url is only valid with "endpoint" persistence'
524
+ });
525
+ }
328
526
  }
329
527
  }
330
- if (!history) return;
331
- if (history.persistence === "endpoint" && !history.url) {
332
- ctx.addIssue({
333
- code: z.ZodIssueCode.custom,
334
- path: ["capabilities", "history", "url"],
335
- message: '"endpoint" persistence requires a url'
336
- });
337
- }
338
- if (history.persistence !== "endpoint" && history.url !== void 0) {
339
- ctx.addIssue({
340
- code: z.ZodIssueCode.custom,
341
- path: ["capabilities", "history", "url"],
342
- message: 'url is only valid with "endpoint" persistence'
343
- });
344
- }
345
- });
528
+ ];
346
529
  function validateConstruct(input) {
347
530
  const parsed = ConstructSchema.safeParse(input);
348
531
  if (parsed.success) return { ok: true, construct: parsed.data };
@@ -639,8 +822,12 @@ function emitIndexHtml(c) {
639
822
  function emitElement(c) {
640
823
  const accent = c.theme?.accent;
641
824
  const unreadColor = c.theme?.unreadColor;
825
+ const usesCtx = !!accent || !!unreadColor || needsHost(c);
826
+ const appJsx = needsHost(c) ? "<App host={ctx.element} />" : "<App />";
642
827
  if (!accent && !unreadColor) {
643
- const facade2 = "() => <App />";
828
+ const facade2 = usesCtx ? `(_props, ctx) => {
829
+ return ${appJsx};
830
+ }` : "() => <App />";
644
831
  return `import { defineWebComponent } from '@kitn.ai/ui/define';
645
832
  import { App } from './App';
646
833
 
@@ -653,7 +840,7 @@ defineWebComponent('${c.name}', { theme: '${themeMode(c)}' as 'light' | 'dark' |
653
840
  ctx.element.style.setProperty('--kai-color-unread', ${JSON.stringify(unreadColor)});` : "";
654
841
  if (!accent) {
655
842
  const facade2 = `(_props, ctx) => {${unreadColorSetProperty}
656
- return <App />;
843
+ return ${appJsx};
657
844
  }`;
658
845
  return `import { defineWebComponent } from '@kitn.ai/ui/define';
659
846
  import { App } from './App';
@@ -682,7 +869,7 @@ defineWebComponent('${c.name}', { theme: '${themeMode(c)}' as 'light' | 'dark' |
682
869
  return (
683
870
  <>
684
871
  <style>{${JSON.stringify(styleText)}}</style>
685
- <App />
872
+ ${appJsx}
686
873
  </>
687
874
  );
688
875
  }`;
@@ -696,7 +883,7 @@ defineWebComponent('${c.name}', { theme: '${themeMode(c)}' as 'light' | 'dark' |
696
883
  }
697
884
  function emitApp(c) {
698
885
  if (c.layout === "custom") return emitCustomApp(c);
699
- return `${emitSolidJsImport(c)}${emitConversationsSignalsImport(c)}import { ChatThread, createKaiChat${emitLayoutImport(c)}${emitCardComponentImport(c)}${emitEmptyComponentImport(c)}${emitHeaderCloseImport(c)} } from '@kitn.ai/ui/solid';
886
+ return `${emitSolidJsImports(c)}import { ChatThread, createKaiChat${emitLayoutImport(c)}${emitCardComponentImport(c)}${emitEmptyComponentImport(c)}${emitChromeImports(c)} } from '@kitn.ai/ui/solid';
700
887
  import type { AttachmentData${emitHistoryTypeImport(c)}${emitConversationsResetTypeImport(c)} } from '@kitn.ai/ui/solid';
701
888
  ${emitProviderImports(c)}
702
889
  ${emitCardsImport(c)}
@@ -786,10 +973,10 @@ ${emitHistorySetup(c)}
786
973
  // list view is open must not leave it there for the next open — see
787
974
  // widgetHasConversationsChrome/emitDockOnOpenChangeProp's docs. Wired only
788
975
  // for \`widget\`, the one layout with something that closes/reopens at all.
789
- export function App() {
790
- ${emitDockCloseVar(c, " ")}${emitChatControllerVar(c, " ")}${emitConversationsSignalsVar(c, " ")} return (
791
- ${emitLayoutOpen(c)}${emitSlots(c.slots, " ")} <ChatThread messages={chat.messages()} loading={chat.loading()} placeholder="Ask anything" onSubmit={submit} webSearch={false} voice={false}${emitHeaderProp(c)}${emitHeaderEndContentProp(c)}${emitAttachProps(c)}${emitStartersProp(c)}${emitReasoningProp(c)}${emitReasoningOpenProp(c)}${emitEmptyContentProp(c)}${emitCardTypesProp(c)}${emitHomeProp(c)}${emitConversationsProps(c)}${emitChatControllerRefProp(c)}${emitChatThreadUnreadProps(c)} />
792
- ${emitLayoutClose(c)} );
976
+ ${emitChromeComment(c)}export function App(${needsHost(c) ? "props: { host: HTMLElement }" : ""}) {
977
+ ${emitToggleThemeVar(c, " ")}${emitDockCloseVar(c, " ")}${emitChatControllerVar(c, " ")}${emitConversationsSignalsVar(c, " ")}${emitShellPaletteVars(c, " ")} return (
978
+ ${hasShellPalette(c) ? " <>\n" : ""}${emitLayoutOpen(c)}${emitSlots(c.slots, " ")} <ChatThread messages={chat.messages()} loading={chat.loading()} placeholder="Ask anything" onSubmit={submit} webSearch={false} voice={false}${emitHeaderProp(c)}${emitHeaderEndContentProp(c)}${emitAttachProps(c)}${emitStartersProp(c)}${emitReasoningProp(c)}${emitReasoningOpenProp(c)}${emitMessageActionsProps(c)}${emitHideSourcesProp(c)}${emitTriggersProp(c)}${emitEmptyContentProp(c)}${emitCardTypesProp(c)}${emitHomeProp(c)}${emitConversationsProps(c)}${emitChatControllerRefProp(c)}${emitChatThreadUnreadProps(c)} />
979
+ ${emitLayoutClose(c)}${emitShellPaletteOverlay(c)}${hasShellPalette(c) ? " </>\n" : ""} );
793
980
  }
794
981
  `;
795
982
  }
@@ -811,7 +998,8 @@ ${emitHistorySetup(c)}
811
998
  // spine (Thread + PromptInput) plus the declared slots, positioned by hand so
812
999
  // YOU own the surrounding DOM. Capabilities beyond the spine (starters,
813
1000
  // attachments, reasoning display-mode, reasoningOpen, header.title, empty,
814
- // conversations) are NOT wired here in v1 — this file is the eject artifact; add them the
1001
+ // conversations, header.themeToggle/actions, composer.triggers, shell) are
1002
+ // NOT wired here in v1 — this file is the eject artifact; add them the
815
1003
  // way ChatThread composes them (components/chat-thread.tsx in the kit's own
816
1004
  // source) if this construct needs them on a custom layout.
817
1005
  export function App() {
@@ -867,22 +1055,68 @@ function widgetHasHeaderClose(c) {
867
1055
  function widgetHasConversationsChrome(c) {
868
1056
  return c.layout === "widget" && (!!c.capabilities?.conversations || !!c.home);
869
1057
  }
1058
+ function hasShellPalette(c) {
1059
+ return c.layout !== "custom" && c.shell?.commandPalette === true;
1060
+ }
1061
+ function emitShellPaletteVars(c, indent) {
1062
+ if (!hasShellPalette(c)) return "";
1063
+ const entries = [
1064
+ `{ id: 'focus-composer', label: 'Focus composer' }`,
1065
+ ...c.capabilities?.conversations ? [`{ id: 'new-conversation', label: 'New conversation' }`] : [],
1066
+ ...c.header?.themeToggle ? [`{ id: 'toggle-theme', label: 'Toggle theme' }`] : []
1067
+ ];
1068
+ const newConversationLine = c.capabilities?.conversations ? `
1069
+ ${indent} if (id === 'new-conversation') chatController?.startNewConversation();` : "";
1070
+ const toggleThemeLine = c.header?.themeToggle ? `
1071
+ ${indent} if (id === 'toggle-theme') toggleTheme();` : "";
1072
+ return `${indent}// Command palette (shell.commandPalette): Mod+K toggles; Escape/backdrop
1073
+ ${indent}// close; entries DERIVE from what this construct enables — no dead rows.
1074
+ ${indent}const [paletteOpen, setPaletteOpen] = createSignal(false);
1075
+ ${indent}const [paletteQuery, setPaletteQuery] = createSignal('');
1076
+ ${indent}const PALETTE_COMMANDS = [${entries.join(", ")}];
1077
+ ${indent}const paletteGroups = () => {
1078
+ ${indent} const q = paletteQuery().trim().toLowerCase();
1079
+ ${indent} const items = q ? PALETTE_COMMANDS.filter((i) => i.label.toLowerCase().includes(q)) : PALETTE_COMMANDS;
1080
+ ${indent} return [{ items }];
1081
+ ${indent}};
1082
+ ${indent}const runPaletteCommand = (id: string) => {
1083
+ ${indent} setPaletteOpen(false);
1084
+ ${indent} if (id === 'focus-composer') chatController?.focus();${newConversationLine}${toggleThemeLine}
1085
+ ${indent}};
1086
+ ${indent}onMount(() => {
1087
+ ${indent} const onKey = (e: KeyboardEvent) => {
1088
+ ${indent} if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') { e.preventDefault(); setPaletteOpen((v) => !v); }
1089
+ ${indent} if (e.key === 'Escape') setPaletteOpen(false);
1090
+ ${indent} };
1091
+ ${indent} window.addEventListener('keydown', onKey);
1092
+ ${indent} onCleanup(() => window.removeEventListener('keydown', onKey));
1093
+ ${indent}});
1094
+ `;
1095
+ }
1096
+ function emitShellPaletteOverlay(c) {
1097
+ if (!hasShellPalette(c)) return "";
1098
+ return ` <Show when={paletteOpen()}>
1099
+ <div style={{ position: 'fixed', inset: '0', 'z-index': '50', display: 'flex', 'align-items': 'flex-start', 'justify-content': 'center', 'padding-block-start': '14vh', background: 'rgb(0 0 0 / 0.5)' }} onClick={() => setPaletteOpen(false)}>
1100
+ <div style={{ width: '100%', 'max-width': '32rem', overflow: 'hidden', 'border-radius': '0.75rem', border: '1px solid var(--color-border)', background: 'var(--color-popover)' }} onClick={(e) => e.stopPropagation()}>
1101
+ <Input value={paletteQuery()} onValueInput={setPaletteQuery} placeholder="Search commands..." autofocus />
1102
+ <CommandList groups={paletteGroups()} onSelect={runPaletteCommand} />
1103
+ </div>
1104
+ </div>
1105
+ </Show>
1106
+ `;
1107
+ }
870
1108
  function emitChatControllerVar(c, indent) {
871
- return widgetHasConversationsChrome(c) ? `${indent}let chatController: ChatThreadController | undefined;
1109
+ return widgetHasConversationsChrome(c) || hasShellPalette(c) ? `${indent}let chatController: ChatThreadController | undefined;
872
1110
  ` : "";
873
1111
  }
874
1112
  function emitChatControllerRefProp(c) {
875
- return widgetHasConversationsChrome(c) ? " controllerRef={(api) => (chatController = api)}" : "";
1113
+ return widgetHasConversationsChrome(c) || hasShellPalette(c) ? " controllerRef={(api) => (chatController = api)}" : "";
876
1114
  }
877
1115
  function emitDockOnOpenChangeProp(c) {
878
1116
  return widgetHasConversationsChrome(c) ? " onOpenChange={(open) => { setDockOpen(open); if (!open) chatController?.closeConversationsList(); }}" : "";
879
1117
  }
880
1118
  function emitConversationsResetTypeImport(c) {
881
- return widgetHasConversationsChrome(c) ? ", ChatThreadController" : "";
882
- }
883
- function emitConversationsSignalsImport(c) {
884
- return widgetHasConversationsChrome(c) ? `import { createSignal } from 'solid-js';
885
- ` : "";
1119
+ return widgetHasConversationsChrome(c) || hasShellPalette(c) ? ", ChatThreadController" : "";
886
1120
  }
887
1121
  function emitConversationsSignalsVar(c, indent) {
888
1122
  if (!widgetHasConversationsChrome(c)) return "";
@@ -908,12 +1142,96 @@ function emitDockControllerRef(c) {
908
1142
  function emitDockHideClose(c) {
909
1143
  return widgetHasHeaderClose(c) ? " hideClose={true}" : "";
910
1144
  }
1145
+ function emitToggleThemeVar(c, indent) {
1146
+ if (c.layout === "custom" || !c.header?.themeToggle) return "";
1147
+ return `${indent}const toggleTheme = () => props.host.setAttribute('theme', props.host.getAttribute('theme') === 'dark' ? 'light' : 'dark');
1148
+ `;
1149
+ }
1150
+ function hasHeaderActionsChrome(c) {
1151
+ return c.layout !== "custom" && !!c.header?.actions?.length;
1152
+ }
1153
+ function hasThemeToggleChrome(c) {
1154
+ return c.layout !== "custom" && c.header?.themeToggle === true;
1155
+ }
1156
+ function hasUserMenuChrome(c) {
1157
+ return c.layout !== "custom" && !!c.shell?.userMenu;
1158
+ }
911
1159
  function emitHeaderEndContentProp(c) {
912
- if (!widgetHasHeaderClose(c)) return "";
913
- return ` headerEndContent={<Button variant="ghost" size="icon-sm" aria-label="Close ${c.name}" onClick={() => dockClose?.()}><DockCloseGlyph /></Button>}`;
1160
+ const pieces = [];
1161
+ if (hasHeaderActionsChrome(c)) {
1162
+ for (const a of c.header.actions) {
1163
+ const variant = a.variant ? ` variant="${a.variant}"` : "";
1164
+ pieces.push(
1165
+ `<Button${variant} size="sm" onClick={() => props.host.dispatchEvent(new CustomEvent('kai-header-action', { detail: { label: ${JSON.stringify(a.label)} } }))}>{${JSON.stringify(a.label)}}</Button>`
1166
+ );
1167
+ }
1168
+ }
1169
+ if (hasThemeToggleChrome(c)) {
1170
+ pieces.push(`<Button variant="ghost" size="sm" aria-label="Toggle theme" onClick={toggleTheme}>Theme</Button>`);
1171
+ }
1172
+ if (hasUserMenuChrome(c)) {
1173
+ const m = c.shell.userMenu;
1174
+ const menuLabel = JSON.stringify(`${m.name}${m.plan ? ` — ${m.plan}` : ""} account menu`);
1175
+ const initials = JSON.stringify(m.name.slice(0, 2).toUpperCase());
1176
+ const item = (id, label) => `<DropdownItem onSelect={() => props.host.dispatchEvent(new CustomEvent('kai-user-menu', { detail: { item: '${id}' } }))}>${label}</DropdownItem>`;
1177
+ pieces.push(
1178
+ `<Dropdown><DropdownTrigger aria-label={${menuLabel}}><Avatar fallback={${initials}} size="sm" /></DropdownTrigger><DropdownContent>${item("settings", "Settings")}${item("help", "Get help")}<DropdownSeparator />${item("log-out", "Log out")}</DropdownContent></Dropdown>`
1179
+ );
1180
+ }
1181
+ if (widgetHasHeaderClose(c)) {
1182
+ pieces.push(
1183
+ `<Button variant="ghost" size="icon-sm" aria-label="Close ${c.name}" onClick={() => dockClose?.()}><DockCloseGlyph /></Button>`
1184
+ );
1185
+ }
1186
+ if (pieces.length === 0) return "";
1187
+ const content = pieces.length > 1 ? `<>${pieces.join("")}</>` : pieces[0];
1188
+ return ` headerEndContent={${content}}`;
1189
+ }
1190
+ function emitChromeImports(c) {
1191
+ let names = "";
1192
+ if (hasHeaderActionsChrome(c) || hasThemeToggleChrome(c) || widgetHasHeaderClose(c)) names += ", Button";
1193
+ if (hasUserMenuChrome(c)) {
1194
+ names += ", Dropdown, DropdownTrigger, DropdownContent, DropdownItem, DropdownSeparator, Avatar";
1195
+ }
1196
+ if (hasShellPalette(c)) names += ", CommandList, Input";
1197
+ if (widgetHasHeaderClose(c)) names += ", DockCloseGlyph";
1198
+ return names;
1199
+ }
1200
+ function emitChromeComment(c) {
1201
+ const lines = [];
1202
+ if (hasHeaderActionsChrome(c)) {
1203
+ lines.push("// header.actions dispatch 'kai-header-action' on the host, detail: { label }.");
1204
+ }
1205
+ if (hasUserMenuChrome(c)) {
1206
+ lines.push("// shell.userMenu dispatches 'kai-user-menu' on the host, detail: { item }.");
1207
+ }
1208
+ if (hasShellPalette(c)) {
1209
+ lines.push("// shell.commandPalette: Mod+K opens the command palette; entries derive from");
1210
+ lines.push("// what this construct enables (menu-honesty — no dead rows).");
1211
+ }
1212
+ return lines.length ? `${lines.join("\n")}
1213
+ ` : "";
914
1214
  }
915
- function emitHeaderCloseImport(c) {
916
- return widgetHasHeaderClose(c) ? ", Button, DockCloseGlyph" : "";
1215
+ function emitMessageActionsProps(c) {
1216
+ const actions = c.capabilities?.messageActions;
1217
+ if (!actions) return "";
1218
+ let out = "";
1219
+ if (actions.user) out += ` userActions={${JSON.stringify(actions.user)}}`;
1220
+ if (actions.assistant) out += ` assistantActions={${JSON.stringify(actions.assistant)}}`;
1221
+ return out;
1222
+ }
1223
+ function emitHideSourcesProp(c) {
1224
+ return c.capabilities?.sources?.strip === false ? " hideSources={true}" : "";
1225
+ }
1226
+ function emitTriggersProp(c) {
1227
+ const triggers = c.composer?.triggers;
1228
+ if (!triggers) return "";
1229
+ const defs = [
1230
+ ...triggers.slash ? [{ char: "/", kind: "command", items: triggers.slash }] : [],
1231
+ ...triggers.mention ? [{ char: "@", kind: "mention", items: triggers.mention }] : []
1232
+ ];
1233
+ if (defs.length === 0) return "";
1234
+ return ` triggers={${JSON.stringify(defs)}}`;
917
1235
  }
918
1236
  function emitAttachProps(c) {
919
1237
  const attachments = c.capabilities?.attachments;
@@ -948,10 +1266,17 @@ function emitConversationsImport(c) {
948
1266
  const name = c.capabilities.history?.persistence === "endpoint" ? "fetchStore" : "localStorageStore";
949
1267
  return `import { ${name} } from '@kitn.ai/ui/solid';`;
950
1268
  }
951
- function emitSolidJsImport(c) {
1269
+ function needsCreateEffect(c) {
952
1270
  const history = c.capabilities?.history;
953
- if (!history || history.persistence === "none" || c.layout !== "custom" && c.capabilities?.conversations) return "";
954
- return `import { createEffect } from 'solid-js';
1271
+ return !!history && history.persistence !== "none" && !(c.layout !== "custom" && c.capabilities?.conversations);
1272
+ }
1273
+ function emitSolidJsImports(c) {
1274
+ const names = [];
1275
+ if (needsCreateEffect(c)) names.push("createEffect");
1276
+ if (widgetHasConversationsChrome(c) || hasShellPalette(c)) names.push("createSignal");
1277
+ if (hasShellPalette(c)) names.push("Show", "onMount", "onCleanup");
1278
+ if (names.length === 0) return "";
1279
+ return `import { ${names.join(", ")} } from 'solid-js';
955
1280
  `;
956
1281
  }
957
1282
  function emitHistoryTypeImport(c) {
@@ -1119,6 +1444,9 @@ function emitSlots(slots, indent) {
1119
1444
  return slots.map((name) => `${indent}<slot name="${name}" />
1120
1445
  `).join("");
1121
1446
  }
1447
+ function needsHost(c) {
1448
+ return hasThemeToggleChrome(c) || hasHeaderActionsChrome(c) || hasUserMenuChrome(c);
1449
+ }
1122
1450
  function emitDockPosition(c) {
1123
1451
  const w = c.layout === "widget" ? c.widget : void 0;
1124
1452
  return w?.position ? ` position="${w.position}"` : "";
@@ -1143,14 +1471,19 @@ function emitLayoutOpen(c) {
1143
1471
  case "fullscreen":
1144
1472
  return ` <div style={{ height: '100dvh', display: 'flex', 'flex-direction': 'column' }}>
1145
1473
  `;
1146
- case "aside":
1147
- return ` <aside data-kai-layout="aside" style={{ position: 'fixed', 'inset-block': '0', 'inset-inline-end': '0', width: '380px', display: 'flex', 'flex-direction': 'column', 'border-inline-start': '1px solid var(--kai-color-border)' }}>
1474
+ case "aside": {
1475
+ const position = c.aside?.position ?? "end";
1476
+ const width = JSON.stringify(c.aside?.width ?? "380px");
1477
+ const inset = position === "start" ? "'inset-inline-start': '0'" : "'inset-inline-end': '0'";
1478
+ const borderSide = position === "start" ? "border-inline-end" : "border-inline-start";
1479
+ return ` <aside data-kai-layout="aside" style={{ position: 'fixed', 'inset-block': '0', ${inset}, width: ${width}, display: 'flex', 'flex-direction': 'column', '${borderSide}': '1px solid var(--kai-color-border)' }}>
1148
1480
  {/* Mirrors Dock's own narrow-viewport full-bleed rule (ui/dock.tsx:229-240)
1149
1481
  — aside has no dedicated kit component (see the emitLayoutOpen doc
1150
1482
  comment above), so this is the honest hand-rolled equivalent, not a
1151
1483
  new responsive strategy. */}
1152
- <style>{\`@media (max-width: 480px) { [data-kai-layout="aside"] { inset: 0; width: auto; height: auto; border-inline-start: 0; } }\`}</style>
1484
+ <style>{\`@media (max-width: 480px) { [data-kai-layout="aside"] { inset: 0; width: auto; height: auto; ${borderSide}: 0; } }\`}</style>
1153
1485
  `;
1486
+ }
1154
1487
  case "split":
1155
1488
  return ` <div style={{ height: '100dvh' }}>
1156
1489
  <WorkspaceShell class="h-full" drawerBelow={480} end={
@@ -1216,6 +1549,7 @@ const USAGE = `usage: npx @kitn.ai/ui <command> (or \`kai <command>\` once @ki
1216
1549
  kai validate <construct.json> check a construct, print problems with paths
1217
1550
  kai eject <construct.json> <outDir> write the generated Solid project (it's yours)
1218
1551
  kai dev <construct.json> live preview with reload-on-edit
1552
+ kai dev --builder [construct.json] visual builder + live preview (no file = start from a template)
1219
1553
  kai compile <construct.json> [outDir] one self-registering .js
1220
1554
  `;
1221
1555
  function homeRecentConversationWarning(construct) {
@@ -1254,6 +1588,12 @@ function parseUiFlag(rest) {
1254
1588
  const positional = uiFlag >= 0 ? rest.filter((_, i) => i !== uiFlag && i !== uiFlag + 1) : rest;
1255
1589
  return { uiSpec, positional };
1256
1590
  }
1591
+ function parseDevArgs(rest) {
1592
+ const { uiSpec, positional } = parseUiFlag(rest);
1593
+ const builder = positional.includes("--builder");
1594
+ const path = positional.filter((a) => a !== "--builder")[0];
1595
+ return { uiSpec, builder, path };
1596
+ }
1257
1597
  async function runCli(argv, io = defaultIo) {
1258
1598
  const [command, ...rest] = argv;
1259
1599
  switch (command) {
@@ -1284,13 +1624,17 @@ async function runCli(argv, io = defaultIo) {
1284
1624
  return 0;
1285
1625
  }
1286
1626
  case "dev": {
1287
- const { uiSpec, positional } = parseUiFlag(rest);
1288
- const path = positional[0];
1627
+ const { uiSpec, builder, path } = parseDevArgs(rest);
1628
+ if (builder) {
1629
+ const { devBuilder } = await import("./assets/dev-DwzHsqYD.js");
1630
+ await devBuilder(path, { io, uiSpec });
1631
+ return 0;
1632
+ }
1289
1633
  if (!path) {
1290
1634
  io.error(USAGE);
1291
1635
  return 2;
1292
1636
  }
1293
- const { dev } = await import("./assets/dev-5XzXYR21.js");
1637
+ const { dev } = await import("./assets/dev-DwzHsqYD.js");
1294
1638
  await dev(path, { io, uiSpec });
1295
1639
  return 0;
1296
1640
  }
@@ -1304,7 +1648,7 @@ async function runCli(argv, io = defaultIo) {
1304
1648
  const construct = loadConstruct(path, io);
1305
1649
  if (!construct) return 1;
1306
1650
  const outDir = resolve(outArg ?? "dist-construct");
1307
- const { workDirFor, ensureInstalled } = await import("./assets/dev-5XzXYR21.js");
1651
+ const { workDirFor, ensureInstalled } = await import("./assets/dev-DwzHsqYD.js");
1308
1652
  const dir = workDirFor(construct.name, process.cwd());
1309
1653
  const files = generateProject(construct, { uiSpec });
1310
1654
  writeProject(files, dir);