@kitn.ai/ui 0.27.0 → 0.28.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 (266) hide show
  1. package/README.md +10 -2
  2. package/dist/{Icon-BnFKqqtY.js → Icon-DObhsRtm.js} +1 -1
  3. package/dist/{action-icons-CBskfBMz.js → action-icons-CcgNN2oT.js} +1 -1
  4. package/dist/arrow-down-CjRU3JgV.js +1 -0
  5. package/dist/arrow-left-Bnpb1UEW.js +1 -0
  6. package/dist/{artifact-CvXEm1lS.js → artifact-Bwt7OgcR.js} +1 -1
  7. package/dist/{attachments-C43ZegEq.js → attachments-Qr4kg-2b.js} +1 -1
  8. package/dist/{audio-visualizer-B-79y1Tj.js → audio-visualizer-DToYdfSc.js} +1 -1
  9. package/dist/{badge-BpUbq8c0.js → badge-CdEu_jn2.js} +1 -1
  10. package/dist/{bell-sn17Ip9v.js → bell-DQAAOpDz.js} +1 -1
  11. package/dist/{button-BH6rN0U-.js → button-DkuOnGLy.js} +1 -1
  12. package/dist/{card-renderer-Cb1gQmth.js → card-renderer-Jx70m6_l.js} +1 -1
  13. package/dist/{card-routing-DifQgS7n.js → card-routing-0YidER_3.js} +1 -1
  14. package/dist/check-njkVdZu2.js +1 -0
  15. package/dist/{checkbox-Dc9Yov_B.js → checkbox-D7iRB3nL.js} +1 -1
  16. package/dist/{checkbox-group-B1kDLLYk.js → checkbox-group-B2_rpoPM.js} +1 -1
  17. package/dist/chevron-down-B72s6PXR.js +1 -0
  18. package/dist/chevron-right-BatuHzUm.js +1 -0
  19. package/dist/{choice-card-BT3c1S8g.js → choice-card-m0BYqqOK.js} +1 -1
  20. package/dist/circle-check-B62pODB3.js +1 -0
  21. package/dist/{circle-x-wobWkoSx.js → circle-x-BPG0iPst.js} +1 -1
  22. package/dist/circle-zx0ByzY9.js +1 -0
  23. package/dist/code-block-ClMLKb7C.js +1 -0
  24. package/dist/{collapsible-CPJbf6FM.js → collapsible-B4DlWicL.js} +1 -1
  25. package/dist/components/chat-thread.d.ts +89 -1
  26. package/dist/components/conversation-item.d.ts +16 -0
  27. package/dist/components/conversation-panel.d.ts +29 -0
  28. package/dist/components/home-panel.d.ts +34 -0
  29. package/dist/components/widget-tab-bar.d.ts +16 -0
  30. package/dist/{composer-CjngLzal.js → composer-sQ0HcvyG.js} +3 -3
  31. package/dist/{confirm-card-DCx_A4bU.js → confirm-card-rQWDQsnC.js} +1 -1
  32. package/dist/construct-cli.es.js +182 -14
  33. package/dist/{context-BY8V-Nlp.js → context-0kg4ZX1J.js} +1 -1
  34. package/dist/controllable-CnGpMF6r.js +1 -0
  35. package/dist/conversation-item-BxwQLaFJ.js +1 -0
  36. package/dist/conversation-list-BCfGN1JU.js +1 -0
  37. package/dist/{copy-B6DO4PqB.js → copy-n9u9rqpi.js} +1 -1
  38. package/dist/{create-tween-CnMHviKB.js → create-tween-C-q-JCM-.js} +1 -1
  39. package/dist/{create-tween-D1aiGZZh.js → create-tween-CRrJXxBd.js} +1 -1
  40. package/dist/{create-tween-BPzaefTp.js → create-tween-DZ3RRDHD.js} +1 -1
  41. package/dist/{create-tween-DUSQUKpr.js → create-tween-uPkArjIG.js} +1 -1
  42. package/dist/custom-elements.json +51 -2
  43. package/dist/{default-input-CucQEOqH.js → default-input-CaMrJkgH.js} +1 -1
  44. package/dist/define-By-uL588.js +1 -0
  45. package/dist/define.js +2 -2
  46. package/dist/define.server.js +2 -2
  47. package/dist/{disclosure-CbLtajUQ.js → disclosure-WUImBbDv.js} +1 -1
  48. package/dist/{dropdown-BwhViJFd.js → dropdown-wZVeJOMd.js} +1 -1
  49. package/dist/elements/agent-card.js +1 -1
  50. package/dist/elements/artifact.js +1 -1
  51. package/dist/elements/attachments.js +1 -1
  52. package/dist/elements/audio-visualizer.js +1 -1
  53. package/dist/elements/avatar.js +1 -1
  54. package/dist/elements/badge.js +1 -1
  55. package/dist/elements/button.js +1 -1
  56. package/dist/elements/card.js +1 -1
  57. package/dist/elements/cards.js +1 -1
  58. package/dist/elements/chain-of-thought.js +1 -1
  59. package/dist/elements/chat-scope-picker.js +1 -1
  60. package/dist/elements/chat-workspace.js +1 -1
  61. package/dist/elements/chat.js +1 -1
  62. package/dist/elements/checkbox-group.js +1 -1
  63. package/dist/elements/checkbox.js +1 -1
  64. package/dist/elements/checkpoint.js +1 -1
  65. package/dist/elements/choice.js +1 -1
  66. package/dist/elements/coachmark.js +1 -1
  67. package/dist/elements/code-block.js +1 -1
  68. package/dist/elements/command.js +1 -1
  69. package/dist/elements/compare.js +1 -1
  70. package/dist/elements/composer.js +1 -1
  71. package/dist/elements/confirm-card.js +1 -1
  72. package/dist/elements/context-meter.js +1 -1
  73. package/dist/elements/conversation-item.js +1 -1
  74. package/dist/elements/conversation-list.js +1 -1
  75. package/dist/elements/dialog.js +1 -1
  76. package/dist/elements/dock.js +3 -3
  77. package/dist/elements/dropdown.js +1 -1
  78. package/dist/elements/editable-label.js +1 -1
  79. package/dist/elements/embed.js +1 -1
  80. package/dist/elements/empty.js +1 -1
  81. package/dist/elements/feedback-bar.js +1 -1
  82. package/dist/elements/file-tree.js +1 -1
  83. package/dist/elements/file-upload.js +1 -1
  84. package/dist/elements/form.js +1 -1
  85. package/dist/elements/hover-card.js +1 -1
  86. package/dist/elements/icon.js +1 -1
  87. package/dist/elements/image.js +1 -1
  88. package/dist/elements/input.js +1 -1
  89. package/dist/elements/kbd.js +1 -1
  90. package/dist/elements/link-preview.js +1 -1
  91. package/dist/elements/loader.js +1 -1
  92. package/dist/elements/markdown.js +1 -1
  93. package/dist/elements/menu.js +1 -1
  94. package/dist/elements/message-skills.js +1 -1
  95. package/dist/elements/message.js +1 -1
  96. package/dist/elements/model-switcher.js +1 -1
  97. package/dist/elements/nav.js +1 -1
  98. package/dist/elements/notice.js +1 -1
  99. package/dist/elements/pane-grid.js +1 -1
  100. package/dist/elements/pane-group.js +1 -1
  101. package/dist/elements/pane.js +1 -1
  102. package/dist/elements/popover.js +1 -1
  103. package/dist/elements/progress-bar.js +1 -1
  104. package/dist/elements/prompt-dock.js +1 -1
  105. package/dist/elements/prompt-input.js +1 -1
  106. package/dist/elements/prompt-suggestions.js +1 -1
  107. package/dist/elements/radio-group.js +1 -1
  108. package/dist/elements/reasoning.js +1 -1
  109. package/dist/elements/remote.js +1 -1
  110. package/dist/elements/resizable.js +1 -1
  111. package/dist/elements/response-stream.js +1 -1
  112. package/dist/elements/screen.js +1 -1
  113. package/dist/elements/scroll-area.js +1 -1
  114. package/dist/elements/scroll-button.js +1 -1
  115. package/dist/elements/search.js +1 -1
  116. package/dist/elements/segmented.js +1 -1
  117. package/dist/elements/select.js +1 -1
  118. package/dist/elements/separator.js +1 -1
  119. package/dist/elements/setting-item.js +1 -1
  120. package/dist/elements/settings-group.js +1 -1
  121. package/dist/elements/skeleton.js +1 -1
  122. package/dist/elements/slider.js +1 -1
  123. package/dist/elements/source.js +1 -1
  124. package/dist/elements/status.js +1 -1
  125. package/dist/elements/switch.js +1 -1
  126. package/dist/elements/tabs.js +1 -1
  127. package/dist/elements/tasks.js +1 -1
  128. package/dist/elements/text-shimmer.js +1 -1
  129. package/dist/elements/thinking-bar.js +1 -1
  130. package/dist/elements/thread.js +1 -1
  131. package/dist/elements/toast.js +1 -1
  132. package/dist/elements/tool.js +1 -1
  133. package/dist/elements/tooltip.js +1 -1
  134. package/dist/elements/voice-input.js +1 -1
  135. package/dist/elements/voice-output.js +1 -1
  136. package/dist/elements.d.ts +20 -4
  137. package/dist/{ellipsis-DFr4Vqr3.js → ellipsis-CbOpJnU3.js} +1 -1
  138. package/dist/{embed-5s_vU4J0.js → embed-CrfjkbUG.js} +1 -1
  139. package/dist/{external-link-u9d85ORQ.js → external-link-ByXAwBBQ.js} +1 -1
  140. package/dist/{file-text-CX7_x42o.js → file-text-DMib2qwt.js} +1 -1
  141. package/dist/{file-tree-D3kBjXI5.js → file-tree-CJqM2aP7.js} +1 -1
  142. package/dist/{folder-V-YdIL8f.js → folder-_BAb929C.js} +1 -1
  143. package/dist/{form-BRNUMCN2.js → form-DSZgZQZk.js} +1 -1
  144. package/dist/{hover-card-DaeiYrTA.js → hover-card-BQbul9eq.js} +1 -1
  145. package/dist/{icon-BpixdZNq.js → icon-CdcfRhYI.js} +1 -1
  146. package/dist/index.d.ts +3 -1
  147. package/dist/index.js +2842 -2703
  148. package/dist/index.server.js +2820 -2681
  149. package/dist/{info-8mFsIjS3.js → info-DsFjGuI_.js} +1 -1
  150. package/dist/{input-d1Yuu-hz.js → input-DKbmkMos.js} +1 -1
  151. package/dist/kai.es.js +1 -1
  152. package/dist/{kbd-961zMPFB.js → kbd-PvdGhX4s.js} +1 -1
  153. package/dist/{link-preview-DIIbV2Sd.js → link-preview-DbM1866N.js} +1 -1
  154. package/dist/{link-DlZZBGaR.js → link-zF8PzWfF.js} +1 -1
  155. package/dist/{loader-YQszHFaW.js → loader-BM4CEz4G.js} +1 -1
  156. package/dist/{markdown-CVk1MPsj.js → markdown-LMvmAyBk.js} +7 -7
  157. package/dist/mcp.es.js +78 -4
  158. package/dist/{message-CwKVEdZg.js → message-BSsgvHvf.js} +1 -1
  159. package/dist/{message-DS8lzHc1.js → message-BYZECyek.js} +1 -1
  160. package/dist/message-circle-DruwObfX.js +1 -0
  161. package/dist/message-square-BG6q6mlm.js +1 -0
  162. package/dist/{minimize-2-UEZmP2QT.js → minimize-2-BQRoBV1V.js} +1 -1
  163. package/dist/{model-switcher-Kk1jEQ5t.js → model-switcher-CMpCOYcB.js} +1 -1
  164. package/dist/{overlay-C1od6LOz.js → overlay-Cb-MkyDa.js} +1 -1
  165. package/dist/{panel-right-ryET0yyn.js → panel-right-i8warFxT.js} +1 -1
  166. package/dist/{paperclip-BDNoZFJP.js → paperclip-ByY3UVO6.js} +1 -1
  167. package/dist/play-BvKKq3fX.js +1 -0
  168. package/dist/primitives/conversation-store.d.ts +54 -0
  169. package/dist/{progress-bar-BvYX0aoy.js → progress-bar-jVAHvMUf.js} +1 -1
  170. package/dist/{prompt-suggestion-BRFu2lcu.js → prompt-suggestion-CD-qtNgC.js} +1 -1
  171. package/dist/{radio-DcL23cNJ.js → radio-BsoBMhkF.js} +1 -1
  172. package/dist/react/index.d.ts +393 -0
  173. package/dist/react.js +20 -20
  174. package/dist/{reasoning-Cackq4Rh.js → reasoning-4kBPtlTx.js} +1 -1
  175. package/dist/register-impl-B1hLkrI8.js +424 -0
  176. package/dist/{resizable-Cp9HUYih.js → resizable-uPmY9VY-.js} +1 -1
  177. package/dist/{rotate-ccw-0DLRDHH5.js → rotate-ccw-B2xn93py.js} +1 -1
  178. package/dist/{rotate-cw-DOB9IGcU.js → rotate-cw-CMVLoCyH.js} +1 -1
  179. package/dist/{scroll-area-emVmWP4K.js → scroll-area-BqLUo8ZZ.js} +1 -1
  180. package/dist/{scroll-button-CeP_gv1x.js → scroll-button-DslXiJyL.js} +1 -1
  181. package/dist/{select-CWmu00tS.js → select-2cLRtPGT.js} +1 -1
  182. package/dist/{separator-CE6vly6z.js → separator-BWQtNJcM.js} +1 -1
  183. package/dist/{settings-CE-78SvZ.js → settings-BBR63jLg.js} +1 -1
  184. package/dist/{settings-group-ZFWNxFQR.js → settings-group-B2ciy-Iw.js} +1 -1
  185. package/dist/{skeleton-BebdP4mf.js → skeleton-CkHI-HgC.js} +1 -1
  186. package/dist/{slider-BAKRYUlz.js → slider-DDR9U-i0.js} +1 -1
  187. package/dist/{solid-BE5ui6sy.js → solid-BzCFdL25.js} +7720 -7058
  188. package/dist/{solid-DFhsce3O.js → solid-DruVOXnf.js} +5864 -5283
  189. package/dist/solid.d.ts +6 -2
  190. package/dist/solid.js +217 -210
  191. package/dist/solid.server.js +217 -210
  192. package/dist/{source-B0FlguSx.js → source-GEnFF528.js} +1 -1
  193. package/dist/{star-CBMZvLuN.js → star-CilHq8GP.js} +1 -1
  194. package/dist/store-Ciyk6eZG.js +1 -0
  195. package/dist/{switch-CViARZCy.js → switch-KkeQIbuH.js} +1 -1
  196. package/dist/{tasks-card-MjCcOhR_.js → tasks-card-BrN4bggJ.js} +1 -1
  197. package/dist/{text-shimmer-DKlwRF2G.js → text-shimmer-Dcj1MHuq.js} +1 -1
  198. package/dist/{textarea-3ay2Me2-.js → textarea-Dpvbngbg.js} +1 -1
  199. package/dist/theme.tokens.css +28 -0
  200. package/dist/{thumbs-up-DQcu2HMO.js → thumbs-up-CsT9Wzb9.js} +1 -1
  201. package/dist/{toast-store-C6yKOhbk.js → toast-store-tgK6MuMv.js} +1 -1
  202. package/dist/{tool-BCYNlfKy.js → tool-DzUjIBT8.js} +1 -1
  203. package/dist/{tooltip-05zI4hjs.js → tooltip-Bkkg_4t0.js} +1 -1
  204. package/dist/{trash-2-BR1RxGky.js → trash-2-jBKxjGGQ.js} +1 -1
  205. package/dist/{triangle-alert-DHohDOCy.js → triangle-alert-DlQ68ppf.js} +1 -1
  206. package/dist/types.d.ts +48 -3
  207. package/dist/ui/dock.d.ts +29 -0
  208. package/dist/{upload-BbGW6A_u.js → upload-TcAHXv9W.js} +1 -1
  209. package/dist/{url-scheme-policy-DHpoTJwB.js → url-scheme-policy-DR5DgvkM.js} +1 -1
  210. package/dist/{use-card-resolution-CJW4grFa.js → use-card-resolution-DaZqAdWf.js} +1 -1
  211. package/dist/{variant-aurora-xiMPty8f.js → variant-aurora-BhLuYxJR.js} +1 -1
  212. package/dist/{variant-aurora-BUOVgPOB.js → variant-aurora-CNmfHhcx.js} +2 -2
  213. package/dist/{variant-aurora-CUsUJvFI.js → variant-aurora-D6GsdTPA.js} +2 -2
  214. package/dist/{variant-aurora-CW2i4bfr.js → variant-aurora-DbW_OWL2.js} +1 -1
  215. package/dist/{variant-custom-D91HjAKz.js → variant-custom-BRvaNHSi.js} +1 -1
  216. package/dist/{variant-custom-BQtOnmeO.js → variant-custom-BvYHR62b.js} +1 -1
  217. package/dist/{variant-custom-CAnzzppW.js → variant-custom-DMTrEO5w.js} +2 -2
  218. package/dist/{variant-custom-CAm9L-E5.js → variant-custom-DV2x254s.js} +2 -2
  219. package/dist/{variant-wave-Ca1Ls4_y.js → variant-wave-BNMGrDfj.js} +2 -2
  220. package/dist/{variant-wave-uY5crU_K.js → variant-wave-BpDd8t3Z.js} +1 -1
  221. package/dist/{variant-wave-CxX39DKd.js → variant-wave-CyBueXj9.js} +2 -2
  222. package/dist/{variant-wave-DYyzIIZv.js → variant-wave-sp-qwsdI.js} +1 -1
  223. package/dist/x-Z5UntkIJ.js +1 -0
  224. package/frameworks/react/index.tsx +14 -4
  225. package/llms-full.txt +7 -2
  226. package/package.json +1 -1
  227. package/src/agent-tooling/README.md +192 -0
  228. package/src/agent-tooling/catalog/invariants.ts +2 -2
  229. package/src/agent-tooling/construct/cli.ts +16 -0
  230. package/src/agent-tooling/construct/codegen.ts +309 -27
  231. package/src/agent-tooling/construct/construct.v1.schema.json +69 -0
  232. package/src/agent-tooling/construct/fixtures/owner-widget.construct.json +11 -2
  233. package/src/agent-tooling/construct/schema.ts +81 -0
  234. package/src/components/chat-thread.tsx +532 -36
  235. package/src/components/conversation-item.tsx +23 -0
  236. package/src/components/conversation-panel.tsx +118 -0
  237. package/src/components/home-panel.tsx +159 -0
  238. package/src/components/widget-tab-bar.tsx +73 -0
  239. package/src/elements/chat.tsx +94 -2
  240. package/src/elements/compiled.css +1 -1
  241. package/src/elements/element-meta.json +49 -6
  242. package/src/elements/element-nonscalar.json +2 -0
  243. package/src/elements/element-types.d.ts +20 -4
  244. package/src/index.ts +3 -1
  245. package/src/primitives/conversation-store.ts +248 -0
  246. package/src/solid.ts +10 -2
  247. package/src/types.ts +45 -3
  248. package/src/ui/dock.tsx +47 -1
  249. package/theme.css +28 -0
  250. package/dist/arrow-down-Br0qtpsz.js +0 -1
  251. package/dist/arrow-left-Dj7xgE12.js +0 -1
  252. package/dist/check-DPeeMUYx.js +0 -1
  253. package/dist/chevron-down-CHSWk7ZT.js +0 -1
  254. package/dist/chevron-right-B7l_fMgx.js +0 -1
  255. package/dist/circle-Bb0yOpqM.js +0 -1
  256. package/dist/circle-check-BGZpCiz7.js +0 -1
  257. package/dist/code-block-CUQ5mtv0.js +0 -1
  258. package/dist/controllable-D43BKmtX.js +0 -1
  259. package/dist/conversation-list-D4lLU_yS.js +0 -1
  260. package/dist/define-CGGX-7Vg.js +0 -1
  261. package/dist/message-circle-Xf8Q-vZp.js +0 -1
  262. package/dist/message-square-Cxnwgkns.js +0 -1
  263. package/dist/play-OFIvYXUQ.js +0 -1
  264. package/dist/register-impl-C7rENvPK.js +0 -424
  265. package/dist/store-7eoadydQ.js +0 -1
  266. package/dist/x-C5m1JFgi.js +0 -1
package/llms-full.txt CHANGED
@@ -1520,7 +1520,7 @@ _No events._
1520
1520
  | `cards` | — | `undefined \| { type: string; id: string; data: unknown; title?: undefined \| string; resolution?: undefined \| { kind: "action"; action: string; payload?: unknown; at?: undefined \| string } \| { kind: "submit"; data: unknown; at?: undefined \| string } \| { kind: "dismissed"; at?: undefined \| string } \| { kind: "expired"; reason?: undefined \| string; at?: undefined \| string } }[]` | The stream of card envelopes to render. Set as a JS PROPERTY: `el.cards = [...]`. |
1521
1521
  | `types` | — | `undefined \| Record<string, string>` | Optional type→tag overrides/additions (merged over the built-ins). Property: `el.types`. Typed as a plain string map (not the `CardTagMap` alias) so the generated React wrapper inlines it instead of emitting an unresolved named type. |
1522
1522
  | `schemas` | — | `undefined \| Record<string, object>` | JSON Schemas for the card types this app renders, keyed by envelope type. The companion of `types`, which says what DRAWS a card while this says what a VALID one looks like. An OBJECT, so it is a JS property only: `el.schemas = { 'pricing-table': pricingSchema }`, never an attribute. `createCardRegistry(...).validationSchemas` is exactly this shape. Without it the kit validates its own seven built-ins and leaves your own card type, the one your app actually cares about, as the only unchecked thing on screen. A schema here WINS over a built-in of the same name, matching `mergeCardTags`, where your entry is spread over ours. Typed `Record<string, object>` rather than `Record<string, JsonSchema>` deliberately: an imported `.json` schema widens `"type"` to `string`, and an authored one carries `$schema`/`title`/`description`/`additionalProperties`, so the tighter type would reject both of the normal ways to supply one. See `CardSchemaMap` in components/card-renderer.tsx. |
1523
- | `policy` | — | `undefined \| { onSubmit?: undefined \| ((cardId: string, data: unknown) => void); onAction?: undefined \| ((cardId: string, action: string, payload?: unknown) => void); onSendPrompt?: undefined \| ((text: string, opts: { mode: "compose" \| "send"; context?: unknown; }) => void); onOpen?: undefined \| ((url: string, target: "tab" \| "artifact") => void); onState?: undefined \| ((cardId: string, patch: unknown) => void); onDismiss?: undefined \| ((cardId: string) => void); onReopen?: undefined \| ((cardId: string) => void); onError?: undefined \| ((cardId: string, message: string) => void); maxSendPromptMode?: undefined \| "compose" \| "send" }` | Optional CardPolicy handling child events. Property: `el.policy`. |
1523
+ | `policy` | — | `undefined \| { onSubmit?: undefined \| ((cardId: string, data: unknown) => void); onAction?: undefined \| ((cardId: string, action: string, payload?: unknown) => void); onSendPrompt?: undefined \| ((text: string, opts: { mode: "compose" \| "send"; context?: unknown }) => void); onOpen?: undefined \| ((url: string, target: "tab" \| "artifact") => void); onState?: undefined \| ((cardId: string, patch: unknown) => void); onDismiss?: undefined \| ((cardId: string) => void); onReopen?: undefined \| ((cardId: string) => void); onError?: undefined \| ((cardId: string, message: string) => void); maxSendPromptMode?: undefined \| "compose" \| "send" }` | Optional CardPolicy handling child events. Property: `el.policy`. |
1524
1524
  | `validateCards` | `validate-cards` | `undefined \| false \| true` | Validate each envelope's `data` against the schema for its type before rendering it, using a built-in's own schema or yours from `schemas`. Default `true`; set `validate-cards="false"` (or `el.validateCards = false`) to opt out. A hard failure (wrong type, a missing required field) renders a diagnostic naming the field instead of the card; a soft failure (bounds) renders the card unchanged. Both emit a contract `error` event. On in production too: a model emitting a bad shape is a production failure mode, so stripping the check there would hide it from exactly the person who needs to see it. |
1525
1525
 
1526
1526
  **Events** (non-bubbling `CustomEvent`s — listen directly on the element):
@@ -1614,6 +1614,9 @@ _No events._
1614
1614
  | `messages` | — | `undefined \| { id: string; role: "user" \| "assistant"; parts: ({ type: "text"; text: string; raw?: undefined \| { source: string; payload: unknown } } \| { type: "reasoning"; text: string; label?: undefined \| string; index?: undefined \| number; streamId?: undefined \| string; signature?: undefined \| string; raw?: undefined \| { source: string; payload: unknown } } \| { type: "tool"; tool: { type: string; kind?: undefined \| "command" \| "file-change" \| "search" \| "fetch" \| "mcp" \| "image" \| "generic"; state: "input-streaming" \| "input-available" \| "output-available" \| "output-error"; input?: undefined \| Record<string, unknown>; rawInput?: undefined \| string; output?: undefined \| Record<string, unknown>; toolCallId?: undefined \| string; errorText?: undefined \| string; raw?: undefined \| { source: string; payload: unknown } }; raw?: undefined \| { source: string; payload: unknown } } \| { type: "card"; envelope: { type: string; id: string; data: unknown; title?: undefined \| string; resolution?: undefined \| { kind: "action"; action: string; payload?: unknown; at?: undefined \| string } \| { kind: "submit"; data: unknown; at?: undefined \| string } \| { kind: "dismissed"; at?: undefined \| string } \| { kind: "expired"; reason?: undefined \| string; at?: undefined \| string } }; raw?: undefined \| { source: string; payload: unknown } } \| { type: "source"; source: { id?: undefined \| string; url?: undefined \| string; title?: undefined \| string; snippet?: undefined \| string; index?: undefined \| number }; raw?: undefined \| { source: string; payload: unknown } } \| { type: "file"; attachment: { id: string; type: "file" \| "source-document"; filename?: undefined \| string; mediaType?: undefined \| string; url?: undefined \| string; title?: undefined \| string }; raw?: undefined \| { source: string; payload: unknown } })[]; 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" }[]` | The full message thread to render, newest last. Each entry carries its role, ordered `parts`, and optional actions/avatar/feedback. Set as a JS property (`el.messages = [...]`); a NEW array reference per streaming chunk re-renders (mutating in place does not). Omit for an empty thread. Re-declared here (rather than inherited from `ChatThreadProps`) because the ELEMENT registers a `[]` default and renders the empty state without it, while the SolidJS `<ChatThread>` component still requires it. The facade hands it a validated array either way. Matches `<kai-thread>`. |
1615
1615
  | `cardTypes` | — | `undefined \| Record<string, string>` | Optional card type -> custom-element tag overrides/additions for `card` parts (merged over the built-ins). Property: `el.cardTypes`. Typed as a plain string map (not the `CardTagMap` alias) so the generated React wrapper inlines it instead of emitting an unresolved named type. |
1616
1616
  | `cardSchemas` | — | `undefined \| Record<string, object>` | JSON Schemas for the card types this app renders, keyed by envelope type. The companion of `cardTypes`, which says what DRAWS a card while this says what a VALID one looks like. An OBJECT, so it is a JS property only: `el.cardSchemas = { 'pricing-table': pricingSchema }`, never an attribute. `createCardRegistry(...).validationSchemas` is exactly this shape. Without it the kit validates its own seven built-ins and leaves your own card type, the one your app actually cares about, as the only unchecked thing on screen. A schema here WINS over a built-in of the same name. Typed `Record<string, object>` rather than `Record<string, JsonSchema>` deliberately: an imported `.json` schema widens `"type"` to `string`, and an authored one carries `$schema`/`title`/`description`/`additionalProperties`, so the tighter type would reject both of the normal ways to supply one. |
1617
+ | `conversations` | `conversations` | `undefined \| false \| true` | Turns on the prior-conversations list (a list-toggle button in the header, plus a second list view sharing the panel, C-1). Attribute- settable like every other boolean flag on this element: `<kai-chat conversations>`. Requires `store`. A row select, "new conversation," or the visitor's mount-time auto-restore all deliver their messages the same way: listen for `kai-conversation-load` and set `el.messages` from `event.detail.messages` (a fresh array): this element does not update `messages` for you. Set with no `store`, the underlying `ChatThread` decides loudly (one console.error) and stays visually off; this facade always supplies its own internal load handler (the `kai-conversation-load` dispatch below), so the second ChatThread guard, missing `onConversationLoad`, never trips here, even for a consumer who never listens for the event. Default false. |
1618
+ | `store` | — | `undefined \| { list: () => Promise<{ id: string; title: string; groupId?: undefined \| string; scope?: undefined \| { type: "document" \| "collection"; documentId?: undefined \| string; filters?: undefined \| { tags?: undefined \| string[]; authors?: undefined \| string[]; contentType?: undefined \| "transcript" \| "markdown"; dateRange?: undefined \| { from: string; to: string } } }; messageCount: number; lastMessageAt?: undefined \| string; updatedAt: string; trailing?: undefined \| string; lastReadAt?: undefined \| string }[]>; load: (id: string) => Promise<{ id: string; role: "user" \| "assistant"; parts: ({ type: "text"; text: string; raw?: undefined \| { source: string; payload: unknown } } \| { type: "reasoning"; text: string; label?: undefined \| string; index?: undefined \| number; streamId?: undefined \| string; signature?: undefined \| string; raw?: undefined \| { source: string; payload: unknown } } \| { type: "tool"; tool: { type: string; kind?: undefined \| "command" \| "file-change" \| "search" \| "fetch" \| "mcp" \| "image" \| "generic"; state: "input-streaming" \| "input-available" \| "output-available" \| "output-error"; input?: undefined \| Record<string, unknown>; rawInput?: undefined \| string; output?: undefined \| Record<string, unknown>; toolCallId?: undefined \| string; errorText?: undefined \| string; raw?: undefined \| { source: string; payload: unknown } }; raw?: undefined \| { source: string; payload: unknown } } \| { type: "card"; envelope: { type: string; id: string; data: unknown; title?: undefined \| string; resolution?: undefined \| { kind: "action"; action: string; payload?: unknown; at?: undefined \| string } \| { kind: "submit"; data: unknown; at?: undefined \| string } \| { kind: "dismissed"; at?: undefined \| string } \| { kind: "expired"; reason?: undefined \| string; at?: undefined \| string } }; raw?: undefined \| { source: string; payload: unknown } } \| { type: "source"; source: { id?: undefined \| string; url?: undefined \| string; title?: undefined \| string; snippet?: undefined \| string; index?: undefined \| number }; raw?: undefined \| { source: string; payload: unknown } } \| { type: "file"; attachment: { id: string; type: "file" \| "source-document"; filename?: undefined \| string; mediaType?: undefined \| string; url?: undefined \| string; title?: undefined \| string }; raw?: undefined \| { source: string; payload: unknown } })[]; 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" }[]>; save: (id: string, messages: { id: string; role: "user" \| "assistant"; parts: ({ type: "text"; text: string; raw?: undefined \| { source: string; payload: unknown } } \| { type: "reasoning"; text: string; label?: undefined \| string; index?: undefined \| number; streamId?: undefined \| string; signature?: undefined \| string; raw?: undefined \| { source: string; payload: unknown } } \| { type: "tool"; tool: { type: string; kind?: undefined \| "command" \| "file-change" \| "search" \| "fetch" \| "mcp" \| "image" \| "generic"; state: "input-streaming" \| "input-available" \| "output-available" \| "output-error"; input?: undefined \| Record<string, unknown>; rawInput?: undefined \| string; output?: undefined \| Record<string, unknown>; toolCallId?: undefined \| string; errorText?: undefined \| string; raw?: undefined \| { source: string; payload: unknown } }; raw?: undefined \| { source: string; payload: unknown } } \| { type: "card"; envelope: { type: string; id: string; data: unknown; title?: undefined \| string; resolution?: undefined \| { kind: "action"; action: string; payload?: unknown; at?: undefined \| string } \| { kind: "submit"; data: unknown; at?: undefined \| string } \| { kind: "dismissed"; at?: undefined \| string } \| { kind: "expired"; reason?: undefined \| string; at?: undefined \| string } }; raw?: undefined \| { source: string; payload: unknown } } \| { type: "source"; source: { id?: undefined \| string; url?: undefined \| string; title?: undefined \| string; snippet?: undefined \| string; index?: undefined \| number }; raw?: undefined \| { source: string; payload: unknown } } \| { type: "file"; attachment: { id: string; type: "file" \| "source-document"; filename?: undefined \| string; mediaType?: undefined \| string; url?: undefined \| string; title?: undefined \| string }; raw?: undefined \| { source: string; payload: unknown } })[]; 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" }[]) => Promise<void>; markRead?: undefined \| ((id: string) => Promise<void>) }` | The adapter this thread persists conversations through: an object of three functions (`list`/`load`/`save`; `ConversationStore`, exported from `@kitn.ai/ui`'s `primitives/conversation-store`). A JS PROPERTY ONLY: `el.store = myAdapter`. It can never be an attribute, since a function-bearing object has no HTML string form, the same reasoning that keeps `messages`/`cardSchemas` property-only (the kai- contract: array/object props are JS properties, never attributes). Two built-ins ship: `localStorageStore(name, userId?)` and `fetchStore(url, userId?)`. |
1619
+ | `home` | — | `undefined \| { greeting?: undefined \| { title?: undefined \| string; subtitle?: undefined \| string }; recentConversation?: undefined \| false \| true; newConversation?: undefined \| { label?: undefined \| string }; links?: undefined \| { label: string; href?: undefined \| string; description?: undefined \| string; icon?: undefined \| string }[] }` | Turns on the widget home screen (Intercom-pattern): the panel boots into a `home` view, with a greeting, most-recent-conversation card, a "new conversation" CTA, and host-defined links, plus a Home/Messages tab bar for switching back to the thread. An OBJECT, so it is a JS property only: `el.home = { greeting: { title: 'Hey' }, links: [...] }`, never an attribute (the kai- contract: array/object props are JS properties). A `links` entry with no `href` fires `kai-home-link` with that entry when tapped, rather than navigating; one WITH `href` opens it directly (only when the URL passes the kit's own scheme allowlist). Omit for the no-home widget (chat view only, unchanged). |
1617
1620
 
1618
1621
  **Events** (non-bubbling `CustomEvent`s — listen directly on the element):
1619
1622
 
@@ -1621,6 +1624,8 @@ _No events._
1621
1624
  |---|---|---|
1622
1625
  | `kai-attachments-change` | `CustomEvent<{ attachments: { id: string; type: "file" \| "source-document"; filename?: undefined \| string; mediaType?: undefined \| string; url?: undefined \| string; title?: undefined \| string }[] }>` | The staged attachments changed (file added or removed). Carries the full current list so a consumer can react in real time. |
1623
1626
  | `kai-attachments-rejected` | `CustomEvent<{ rejected: { filename: string; mediaType: string; reason: "filtered" \| "unsupported" }[] }>` | One or more picked files were refused because `accept` excluded them. The element renders NO message of its own: it reports the facts (name, media type, whether the kit could have sent it) and what the user should see is the application's call. Only ever fires when `accept` is set. |
1627
+ | `kai-conversation-load` | `CustomEvent<{ id: string \| undefined; messages: { id: string; role: "user" \| "assistant"; parts: ({ type: "text"; text: string; raw?: undefined \| { source: string; payload: unknown } } \| { type: "reasoning"; text: string; label?: undefined \| string; index?: undefined \| number; streamId?: undefined \| string; signature?: undefined \| string; raw?: undefined \| { source: string; payload: unknown } } \| { type: "tool"; tool: { type: string; kind?: undefined \| "command" \| "file-change" \| "search" \| "fetch" \| "mcp" \| "image" \| "generic"; state: "input-streaming" \| "input-available" \| "output-available" \| "output-error"; input?: undefined \| Record<string, unknown>; rawInput?: undefined \| string; output?: undefined \| Record<string, unknown>; toolCallId?: undefined \| string; errorText?: undefined \| string; raw?: undefined \| { source: string; payload: unknown } }; raw?: undefined \| { source: string; payload: unknown } } \| { type: "card"; envelope: { type: string; id: string; data: unknown; title?: undefined \| string; resolution?: undefined \| { kind: "action"; action: string; payload?: unknown; at?: undefined \| string } \| { kind: "submit"; data: unknown; at?: undefined \| string } \| { kind: "dismissed"; at?: undefined \| string } \| { kind: "expired"; reason?: undefined \| string; at?: undefined \| string } }; raw?: undefined \| { source: string; payload: unknown } } \| { type: "source"; source: { id?: undefined \| string; url?: undefined \| string; title?: undefined \| string; snippet?: undefined \| string; index?: undefined \| number }; raw?: undefined \| { source: string; payload: unknown } } \| { type: "file"; attachment: { id: string; type: "file" \| "source-document"; filename?: undefined \| string; mediaType?: undefined \| string; url?: undefined \| string; title?: undefined \| string }; raw?: undefined \| { source: string; payload: unknown } })[]; 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" }[] }>` | A conversation's history loaded: a row tap in the list, "new conversation," or the visitor's own mount-time auto-restore of their most recent thread (only fires when `conversations` is on and a `store` is set). `detail.id` is that conversation's id, `undefined` for the "new conversation" case (no id exists until the first message mints one, C-6). Set `el.messages = event.detail.messages` (already a fresh array) to actually render it, since this element does not do that for you; `messages` stays your own state like everywhere else on this element. |
1628
+ | `kai-home-link` | `CustomEvent<{ entry: { label: string; href?: undefined \| string; description?: undefined \| string; icon?: undefined \| string } }>` | A `home.links` entry with no `href` was activated (tapped/clicked/Enter). Meaningful only when `home` is set. |
1624
1629
  | `kai-message-action` | `CustomEvent<{ messageId: string; action: string; state?: undefined \| "on" \| "off" }>` | An action button on a message was clicked. `action` is the built-in name or custom id. `state` is present only for the toggleable feedback votes: `'on'` when a like/dislike is set, `'off'` when re-tapped to clear. |
1625
1630
  | `kai-model-change` | `CustomEvent<{ modelId: string }>` | The header model switcher changed. |
1626
1631
  | `kai-submit` | `CustomEvent<{ value: string; attachments: { id: string; type: "file" \| "source-document"; filename?: undefined \| string; mediaType?: undefined \| string; url?: undefined \| string; title?: undefined \| string }[] }>` | User submitted a message. |
@@ -2055,7 +2060,7 @@ _No events._
2055
2060
  |---|---|---|---|
2056
2061
  | `theme` | `theme` | `"light" \| "dark" \| "auto"` | Color mode (`auto` follows prefers-color-scheme). |
2057
2062
  | `groups` | — | `undefined \| { id: string; userId?: undefined \| string; teamId?: undefined \| string; name: string; sortOrder: number; createdAt: string }[]` | The list's section headers (`{ id, name, sortOrder, createdAt }`), rendered in array order. A group carries no conversations of its own; it is matched against `conversations` by id, so the two props are complementary rather than alternatives. Omit for an ungrouped list. Set as a JS property. |
2058
- | `conversations` | — | `undefined \| { id: string; title: string; groupId?: undefined \| string; scope?: undefined \| { type: "document" \| "collection"; documentId?: undefined \| string; filters?: undefined \| { tags?: undefined \| string[]; authors?: undefined \| string[]; contentType?: undefined \| "transcript" \| "markdown"; dateRange?: undefined \| { from: string; to: string } } }; messageCount: number; lastMessageAt?: undefined \| string; updatedAt: string; trailing?: undefined \| string }[]` | Every conversation the list renders, flat. Each one is filed under the group whose `id` equals its `groupId`; one with no `groupId`, or with a `groupId` matching no entry in `groups`, falls into a trailing "Ungrouped" section, so nothing you pass in is ever dropped. There is no recency bucketing. Set as a JS property. Omit to supply them as `<kai-conversation>` light-DOM children instead, or for the empty state. A search query that matches nothing shows a visible "No conversations match your search" state, distinct from the zero-conversations empty state. Slotted `<kai-conversation-item>` children switch the list into item mode instead: your own rows win and this array is not rendered. |
2063
+ | `conversations` | — | `undefined \| { id: string; title: string; groupId?: undefined \| string; scope?: undefined \| { type: "document" \| "collection"; documentId?: undefined \| string; filters?: undefined \| { tags?: undefined \| string[]; authors?: undefined \| string[]; contentType?: undefined \| "transcript" \| "markdown"; dateRange?: undefined \| { from: string; to: string } } }; messageCount: number; lastMessageAt?: undefined \| string; updatedAt: string; trailing?: undefined \| string; lastReadAt?: undefined \| string }[]` | Every conversation the list renders, flat. Each one is filed under the group whose `id` equals its `groupId`; one with no `groupId`, or with a `groupId` matching no entry in `groups`, falls into a trailing "Ungrouped" section, so nothing you pass in is ever dropped. There is no recency bucketing. Set as a JS property. Omit to supply them as `<kai-conversation>` light-DOM children instead, or for the empty state. A search query that matches nothing shows a visible "No conversations match your search" state, distinct from the zero-conversations empty state. Slotted `<kai-conversation-item>` children switch the list into item mode instead: your own rows win and this array is not rendered. |
2059
2064
  | `activeId` | `active-id` | `undefined \| string` | The id of the currently-open conversation, highlighted in the list. |
2060
2065
  | `collapsed` | `collapsed` | `undefined \| false \| true` | Controlled collapsed state. Set as a JS property (`el.collapsed = true`) to drive the rail from your app, updating it in response to `kai-collapse-toggle`. Omit for uncontrolled (the element manages it). Collapsed shrinks the rail to a floating reopen button. |
2061
2066
  | `defaultCollapsed` | `default-collapsed` | `undefined \| false \| true` | Initial collapsed state when uncontrolled (default false). Use the `default-collapsed` attribute to start collapsed in plain HTML. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kitn.ai/ui",
3
- "version": "0.27.0",
3
+ "version": "0.28.0",
4
4
  "nx": {
5
5
  "name": "ui",
6
6
  "targets": {
@@ -0,0 +1,192 @@
1
+ # `agent-tooling/`
2
+
3
+ The kit's machine-facing front door: everything that makes AI coding agents
4
+ and CLIs fluent building with `@kitn.ai/ui`. It is packaging and tooling on
5
+ top of the library, not part of it — `grep -rn "agent-tooling"
6
+ packages/ui/src/components packages/ui/src/elements` returns no imports (only
7
+ a code comment and a test's `UNSCANNED_DIRS` entry mention the directory by
8
+ name). Nothing a consumer's chat UI renders depends on anything in here.
9
+
10
+ ## The four parts
11
+
12
+ ```
13
+ ┌─────────────────────────┐
14
+ │ kai MCP server (mcp/) │
15
+ │ construct · scaffold · │
16
+ │ reference · theme · │
17
+ │ debug │
18
+ └───────┬─────────┬────────┘
19
+ consumes consumes
20
+ │ │
21
+ ▼ ▼
22
+ ┌────────────────┐ ┌──────────────────┐
23
+ │ construct/ │ │ catalog/ │
24
+ │ (schema→codegen │ │ (invariants, │
25
+ │ →cli engine) │ │ surfaces, │
26
+ └────────▲────────┘ │ fabrications, …) │
27
+ │ └───────────────────┘
28
+ standalone,
29
+ no MCP involved
30
+
31
+ ┌────────┴────────┐
32
+ │ bin/mcp.js CLI │
33
+ │ validate/dev/ │
34
+ │ compile/eject │
35
+ └──────────────────┘
36
+ ```
37
+
38
+ - **`mcp/`** — the `kai` MCP server. The tools in `mcp/tools/`:
39
+ - `construct.ts` — turn-by-turn authoring of a construct JSON file (one
40
+ JSON file → one web component); stateless, the harness owns the file.
41
+ - `scaffold.ts` — generates a full chat integration (framework + backend
42
+ route + integration wiring) from `agent-tooling/registry.ts` and
43
+ `agent-tooling/archetypes.ts` (backed by `agent-tooling/integrations/`).
44
+ - `reference.ts` — component/element/card lookup for an agent deciding what
45
+ to use.
46
+ - `theme.ts` — produces a `--kai-*` CSS token override block from a brand
47
+ color or description, resolved against the kit's own `theme.css`.
48
+ - `debug.ts` — diagnoses common `kai-*` integration failures from a
49
+ symptom/snippet, sourced from `apps/docs/src/content/docs/guides/for-ai-agents.mdx`.
50
+ - **`construct/`** — the construct engine: a declarative JSON format compiled
51
+ to a self-registering web component. Pipeline is `schema.ts` (Zod, single
52
+ source of truth) → `codegen.ts` (the one generation path — `kai dev`,
53
+ `kai compile`, and `kai eject` all call the same `generateProject`) →
54
+ `cli.ts` (the `validate | eject | dev | compile` subcommands). Fixtures
55
+ live in `construct/fixtures/` (`demo-widget`, `ops-console`,
56
+ `owner-widget`). The published JSON Schema artifact is
57
+ `construct/construct.v1.schema.json`, mirrored byte-identical at
58
+ `apps/docs/public/schemas/construct/v1.json`; both are guarded against
59
+ drift from the Zod source by `construct/schema-artifact.test.ts` (asserts
60
+ the checked-in artifact equals `z.toJSONSchema(ConstructSchema)` right now)
61
+ and regenerated by `scripts/gen-construct-schema.mjs` via `build:api`.
62
+ - **`catalog/`** — Zod-typed reference data, mostly independent of the
63
+ construct engine, each file feeding a specific consumer:
64
+ - `catalog-types.ts` — the shared Zod types (`SurfaceArchetype`,
65
+ `DeliveryTarget`, `WireReader`, `Backend`, …) the rest of the catalog is
66
+ built from.
67
+ - `invariants.ts` — the composition invariants an agent must hold, each
68
+ with an `enforcedBy` path and a `lint` script name; feeds
69
+ `invariants.test.ts` and the acceptance harness.
70
+ - `surfaces.ts` — the sorted inventory of product-shaped surfaces vs.
71
+ ingredients vs. corpus; feeds `surfaces.test.ts` and `derived.json`.
72
+ - `fabrications.ts` — tags agents have invented while working from an
73
+ acceptance pack (and what they should have used instead); renders the
74
+ pack's `FABRICATED.md`, checked against `derived.json` in both directions
75
+ by `fabrications.test.ts`.
76
+ - `scenarios.ts` — the acceptance deck (spec §6): prompts + what each
77
+ needs, written before the catalog it measures.
78
+ - `labs-titles.ts` — parses the real `Labs/…` story titles registered by
79
+ story files (TypeScript AST, not a regex), used to keep the catalog's
80
+ story inventory honest.
81
+ - `derived.json` — generated inventory of elements/props read by
82
+ `catalog-types.ts`, `fabrications.ts`, and `surfaces.ts` (and their
83
+ tests) rather than by anything outside the catalog.
84
+ - `catalog/README.md` — the catalog's own deeper documentation.
85
+
86
+ How they relate: the MCP's `construct` and `scaffold` tools **consume** the
87
+ construct engine and the catalogs — they don't reimplement authoring logic,
88
+ they call into `construct/schema.ts` / `construct/codegen.ts` and read the
89
+ registry/catalog data. The construct CLI (`bin/mcp.js validate|dev|compile|eject`,
90
+ built from `construct/cli-entry.ts` → `dist/construct-cli.es.js`) runs the
91
+ engine standalone, with no MCP server involved at all.
92
+
93
+ ## Usage
94
+
95
+ - **`npx @kitn.ai/ui mcp`** starts the MCP server (the historical default
96
+ subcommand). For wiring it into a multi-harness setup, see the docs site's
97
+ agents guide: `apps/docs/src/content/docs/guides/for-ai-agents.mdx`.
98
+ - **CLI form**, no MCP: `node bin/mcp.js validate|dev|compile|eject <construct.json>`
99
+ (`bin/mcp.js` dispatches `dev`/`compile`/`eject`/`validate` to the construct
100
+ CLI and anything else, including `mcp` or no argument, to the MCP server;
101
+ an unrecognized subcommand is a loud error, not a silent fallthrough).
102
+ `eject` writes a real Solid project you own; `dev` gives a live-reload
103
+ preview; `compile` emits one self-registering `.js`.
104
+ - **Acceptance harness** (`packages/ui/scripts/acceptance-*.mjs`:
105
+ `acceptance-pack.mjs`, `acceptance-run.mjs`, `acceptance-eval.mjs`,
106
+ `acceptance-gate-compiles.mjs`) — builds and grades a coding agent's
107
+ from-scratch attempt at using the catalog with no kit source, per the
108
+ scenarios in `catalog/scenarios.ts`; this is how the catalog's own claims
109
+ get checked against a real agent rather than asserted.
110
+
111
+ ## Worked example
112
+
113
+ `construct/fixtures/owner-widget.construct.json`, field by field:
114
+
115
+ - `"$schema"` — points at the published schema URL for editor validation.
116
+ - `"name": "acme-support"` — the construct's name; also the emitted custom
117
+ element's tag basis.
118
+ - `"layout": "widget"` — a floating launcher widget, not full-screen or docked.
119
+ - `"provider": { "mode": "mock" }` — no backend; canned responses (vs.
120
+ `mode: "endpoint"`, the consumer's own chat route).
121
+ - `"userId": "user_123"` — the identity value threaded through for
122
+ history/persistence.
123
+ - `"header": { "title": "Acme Support" }` — the chat header's title text.
124
+ - `"theme": { "unreadColor": "#38BDF8" }` — one branded token override.
125
+ - `"empty": { "title": ..., "description": ... }` — the empty-state copy
126
+ shown before the first message.
127
+ - `"widget": { "position": "top-start", "defaultOpen": true }` — widget-layout
128
+ placement and initial open state.
129
+ - `"capabilities.attachments.accept"` — allowed MIME patterns for uploads.
130
+ - `"capabilities.history.persistence": "local"` — history persisted in the
131
+ browser, not the consumer's backend.
132
+ - `"capabilities.starters"` — suggested first prompts.
133
+ - `"capabilities.reasoningOpen": true` — reasoning panels start expanded.
134
+ - `"capabilities.conversations": true` — enables the conversations sidebar.
135
+
136
+ `codegen.ts` turns this into a small, deterministic (no dates, no randomness,
137
+ fixed key order) Solid project: pure Solid interior composing
138
+ `@kitn.ai/ui/solid` components, provider glue built on `@kitn.ai/ui/state` +
139
+ `@kitn.ai/ui/wire` (never a hand-rolled SSE reader), and one
140
+ `defineWebComponent` facade carrying the tag, theme default, and slots. The
141
+ facade injects the kit's compiled CSS into the shadow root, so the generated
142
+ project needs no Tailwind and no CSS build step. `kai dev`, `kai compile`,
143
+ and `kai eject` all go through this same generation path — the preview is
144
+ the artifact, there is no separate interpreter to drift from it.
145
+
146
+ ## Rules that bind changes here
147
+
148
+ - **Vocabulary never logic** — the construct format has no handlers and no
149
+ expressions; it describes, it doesn't script. `schema.ts` is `.strict()`
150
+ everywhere, so an unknown key is a loud rejection.
151
+ - **Widen, never restructure `schema.ts`** — it's the single source of truth;
152
+ never hand-edit the generated JSON Schema artifacts or restate an enum
153
+ elsewhere, read it off `ConstructSchema.shape` or the generated artifact.
154
+ - **Derive, don't type** — the published schema artifact is generated from
155
+ Zod (`scripts/gen-construct-schema.mjs`, run by `build:api`) and pinned by
156
+ `construct/schema-artifact.test.ts`; `verify:construct`'s layout and
157
+ capability axes are read off that same drift-guarded artifact rather than
158
+ hand-listed, so adding a layout or capability to the Zod schema moves what
159
+ the gate covers on its own.
160
+ - **`verify:construct`** (`scripts/verify-construct.mjs`) drives the real CLI
161
+ end to end against this checkout's own packed tarball — eject, install,
162
+ compile under the emitted tsconfig, build, and bundle inside a real
163
+ consumer app — for every layout/capability combination the schema
164
+ currently declares. No unit test in this tree leaves the process; this is
165
+ the one that does.
166
+ - **`verify:scaffold`** (`scripts/verify-scaffold-compiles.mjs`) compiles the
167
+ scaffolder's emitted front ends and backend routes under `tsc --strict`
168
+ across the integration × surface × framework axes derived from the
169
+ registry, plus structural checks (e.g. the Solid emit branches on every
170
+ `MessagePart` variant).
171
+ - **`verify:generated`** (`scripts/verify-generated-sync.mjs`) re-runs
172
+ `build:api` and fails if any derived artifact in the tree (including
173
+ catalog/manifest files this directory reads) differs from what the
174
+ generator produces right now — catches drift `nx build ui`'s NX cache can
175
+ hide.
176
+ - **The schema-artifact test pair** (`construct/schema-artifact.test.ts`)
177
+ catches a `schema.ts` edit that shipped without a regen, in the unit suite
178
+ itself rather than only in CI's `verify:generated`.
179
+
180
+ ## Where deeper docs live
181
+
182
+ - Repo root [`CLAUDE.md`](../../../../CLAUDE.md)'s **Map** section for how
183
+ this directory sits relative to `primitives/`, `ui/`, `components/`, and
184
+ `elements/`.
185
+ - [`.claude/README.md`](../../../../.claude/README.md) for the project-local
186
+ skills/agents that exercise this tooling (notably `/consumer-regression`).
187
+ - The docs site's agents guide,
188
+ `apps/docs/src/content/docs/guides/for-ai-agents.mdx` — the rules
189
+ `debug.ts` and the MCP's contract enforcement are sourced from.
190
+ - `docs/superpowers/specs/2026-08-25-construct-engine-design.md` — the
191
+ construct engine's design spec (decisions ledger, format rules, success
192
+ criteria).
@@ -142,9 +142,9 @@ export const invariants: TInvariant[] = [
142
142
  ],
143
143
  examples: [
144
144
  {
145
- wrong: 'chat.conversations = rows;',
145
+ wrong: 'chat.conversationRows = rows;',
146
146
  right: 'conversations.conversations = rows;',
147
- note: 'kai-chat has no conversations prop the sidebar is its own element. One element never holds the whole app state.',
147
+ note: "kai-chat's own `conversations` prop is a boolean flag (turns the built-in list panel on); it has no `conversationRows`-shaped data prop, and the sidebar is its own element. One element never holds the whole app state.",
148
148
  },
149
149
  {
150
150
  wrong: "chat.addEventListener('kai-conversation-select', (e) => load(e.detail.id));",
@@ -25,6 +25,20 @@ const USAGE = `usage: kai <command>
25
25
  kai compile <construct.json> [outDir] one self-registering .js (Task 6)
26
26
  `;
27
27
 
28
+ /** H-3, decide loudly: `home.recentConversation` renders nothing without
29
+ * `capabilities.conversations` to draw the card from — never fatal (the
30
+ * schema explicitly does NOT require conversations for `home`, see
31
+ * schema.ts's own doc on `home`), just a non-blocking heads-up at validate
32
+ * time, the same idiom `validate`'s success line already uses (io.log, not
33
+ * a bare console call). Extracted so it can be unit-tested without going
34
+ * through the CLI's stdout wiring. */
35
+ export function homeRecentConversationWarning(construct: Construct): string | null {
36
+ if (construct.home?.recentConversation && !construct.capabilities?.conversations) {
37
+ return 'warning: home.recentConversation is set but capabilities.conversations is not — the recent-conversation card will render nothing without it.';
38
+ }
39
+ return null;
40
+ }
41
+
28
42
  export function loadConstruct(path: string, io: CliIo): Construct | null {
29
43
  const abs = resolve(path);
30
44
  let raw: string;
@@ -66,6 +80,8 @@ export async function runCli(argv: string[], io: CliIo = defaultIo): Promise<num
66
80
  const construct = loadConstruct(rest[0] ?? '', io);
67
81
  if (!construct) return 1;
68
82
  io.log(`valid construct: <${construct.name}> (layout: ${construct.layout}, provider: ${construct.provider.mode})`);
83
+ const warning = homeRecentConversationWarning(construct);
84
+ if (warning) io.log(warning);
69
85
  return 0;
70
86
  }
71
87
  case 'eject': {