@kitnai/chat 0.6.0 → 0.8.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 (211) hide show
  1. package/README.md +9 -9
  2. package/dist/custom-elements.json +1676 -881
  3. package/dist/kitn-chat.es.js +36 -36
  4. package/dist/llms/llms-full.txt +316 -155
  5. package/dist/llms/llms.txt +18 -18
  6. package/dist/schemas/card-envelope.schema.json +14 -0
  7. package/dist/schemas/card-event.schema.json +12 -0
  8. package/dist/schemas/confirm.schema.json +65 -0
  9. package/dist/schemas/embed.schema.json +65 -0
  10. package/dist/schemas/form.result.schema.json +7 -0
  11. package/dist/schemas/form.schema.json +33 -0
  12. package/dist/schemas/link.schema.json +56 -0
  13. package/dist/schemas/task-list.result.schema.json +16 -0
  14. package/dist/schemas/task-list.schema.json +78 -0
  15. package/dist/theme.tokens.css +65 -65
  16. package/dist/tsx-B8rCNbgL.js +1 -0
  17. package/dist/typescript-RycA9KXf.js +1 -0
  18. package/frameworks/react/index.tsx +382 -193
  19. package/frameworks/react/runtime.tsx +2 -2
  20. package/llms-full.txt +316 -155
  21. package/llms.txt +18 -18
  22. package/package.json +5 -2
  23. package/src/components/artifact.stories.tsx +138 -0
  24. package/src/components/artifact.tsx +581 -0
  25. package/src/components/attachments.stories.tsx +7 -8
  26. package/src/components/attachments.tsx +2 -2
  27. package/src/components/card.tsx +110 -0
  28. package/src/components/chain-of-thought.stories.tsx +7 -8
  29. package/src/components/chat-container.stories.tsx +7 -8
  30. package/src/components/chat-container.tsx +4 -0
  31. package/src/components/checkpoint.stories.tsx +7 -8
  32. package/src/components/code-block.stories.tsx +8 -9
  33. package/src/components/component-meta.json +3411 -0
  34. package/src/components/confirm-card.stories.tsx +74 -0
  35. package/src/components/confirm-card.tsx +299 -0
  36. package/src/components/context.stories.tsx +7 -8
  37. package/src/components/conversation-item.stories.tsx +7 -8
  38. package/src/components/conversation-item.tsx +2 -2
  39. package/src/components/conversation-list.stories.tsx +7 -8
  40. package/src/components/conversation-list.tsx +1 -1
  41. package/src/components/embed.tsx +196 -0
  42. package/src/components/empty.stories.tsx +8 -9
  43. package/src/components/feedback-bar.stories.tsx +7 -8
  44. package/src/components/file-tree.stories.tsx +73 -0
  45. package/src/components/file-tree.tsx +383 -0
  46. package/src/components/file-upload.stories.tsx +7 -8
  47. package/src/components/form-widgets.tsx +461 -0
  48. package/src/components/form.tsx +796 -0
  49. package/src/components/image.stories.tsx +7 -8
  50. package/src/components/link-card.tsx +194 -0
  51. package/src/components/loader.stories.tsx +7 -8
  52. package/src/components/markdown.stories.tsx +7 -8
  53. package/src/components/message-narrow.stories.tsx +12 -13
  54. package/src/components/message-skills.stories.tsx +16 -17
  55. package/src/components/message.stories.tsx +17 -18
  56. package/src/components/model-switcher.stories.tsx +7 -8
  57. package/src/components/prompt-input.stories.tsx +8 -9
  58. package/src/components/prompt-suggestion.stories.tsx +7 -8
  59. package/src/components/prompt-suggestion.tsx +3 -3
  60. package/src/components/reasoning.stories.tsx +7 -8
  61. package/src/components/scroll-button.stories.tsx +7 -8
  62. package/src/components/slash-command.stories.tsx +8 -9
  63. package/src/components/slash-command.tsx +2 -2
  64. package/src/components/source.stories.tsx +7 -8
  65. package/src/components/source.tsx +1 -1
  66. package/src/components/task-list-card.stories.tsx +78 -0
  67. package/src/components/task-list-card.tsx +388 -0
  68. package/src/components/text-shimmer.stories.tsx +7 -8
  69. package/src/components/thinking-bar.stories.tsx +7 -8
  70. package/src/components/tool.stories.tsx +7 -8
  71. package/src/components/tool.tsx +2 -2
  72. package/src/components/voice-input.stories.tsx +7 -8
  73. package/src/elements/artifact.stories.tsx +291 -0
  74. package/src/elements/artifact.tsx +72 -0
  75. package/src/elements/{kitn-attachments.stories.tsx → attachments.stories.tsx} +11 -11
  76. package/src/elements/attachments.tsx +4 -4
  77. package/src/elements/card.stories.tsx +118 -0
  78. package/src/elements/card.tsx +40 -0
  79. package/src/elements/catalog.stories.tsx +491 -0
  80. package/src/elements/{kitn-chain-of-thought.stories.tsx → chain-of-thought.stories.tsx} +13 -13
  81. package/src/elements/chain-of-thought.tsx +3 -3
  82. package/src/elements/{kitn-chat-scope-picker.stories.tsx → chat-scope-picker.stories.tsx} +10 -10
  83. package/src/elements/chat-scope-picker.tsx +4 -4
  84. package/src/elements/{kitn-chat-workspace.stories.tsx → chat-workspace.stories.tsx} +71 -29
  85. package/src/elements/chat-workspace.tsx +29 -3
  86. package/src/elements/{kitn-chat.stories.tsx → chat.stories.tsx} +61 -16
  87. package/src/elements/chat.tsx +23 -2
  88. package/src/elements/{kitn-checkpoint.stories.tsx → checkpoint.stories.tsx} +11 -11
  89. package/src/elements/checkpoint.tsx +4 -4
  90. package/src/elements/{kitn-code-block.stories.tsx → code-block.stories.tsx} +10 -10
  91. package/src/elements/code-block.tsx +3 -3
  92. package/src/elements/compiled.css +1 -1
  93. package/src/elements/composed-shell.stories.tsx +316 -0
  94. package/src/elements/confirm-card.stories.tsx +186 -0
  95. package/src/elements/confirm-card.tsx +45 -0
  96. package/src/elements/{kitn-context-meter.stories.tsx → context-meter.stories.tsx} +10 -10
  97. package/src/elements/context-meter.tsx +3 -3
  98. package/src/elements/{kitn-conversation-list.stories.tsx → conversation-list.stories.tsx} +35 -22
  99. package/src/elements/conversation-list.tsx +11 -2
  100. package/src/elements/css.ts +1 -1
  101. package/src/elements/define.tsx +10 -10
  102. package/src/elements/element-meta.json +2649 -0
  103. package/src/elements/element-types.d.ts +251 -125
  104. package/src/elements/embed.stories.tsx +197 -0
  105. package/src/elements/embed.tsx +35 -0
  106. package/src/elements/{kitn-empty.stories.tsx → empty.stories.tsx} +12 -12
  107. package/src/elements/empty.tsx +3 -3
  108. package/src/elements/{kitn-feedback-bar.stories.tsx → feedback-bar.stories.tsx} +11 -11
  109. package/src/elements/feedback-bar.tsx +4 -4
  110. package/src/elements/file-tree.stories.tsx +133 -0
  111. package/src/elements/file-tree.tsx +52 -0
  112. package/src/elements/{kitn-file-upload.stories.tsx → file-upload.stories.tsx} +12 -12
  113. package/src/elements/file-upload.tsx +4 -4
  114. package/src/elements/form.stories.tsx +204 -0
  115. package/src/elements/form.tsx +37 -0
  116. package/src/elements/{kitn-image.stories.tsx → image.stories.tsx} +10 -10
  117. package/src/elements/image.tsx +3 -3
  118. package/src/elements/link-card.stories.tsx +193 -0
  119. package/src/elements/link-card.tsx +34 -0
  120. package/src/elements/{kitn-loader.stories.tsx → loader.stories.tsx} +11 -11
  121. package/src/elements/loader.tsx +3 -3
  122. package/src/elements/{kitn-markdown.stories.tsx → markdown.stories.tsx} +10 -10
  123. package/src/elements/markdown.tsx +3 -3
  124. package/src/elements/{kitn-message-skills.stories.tsx → message-skills.stories.tsx} +10 -10
  125. package/src/elements/message-skills.tsx +3 -3
  126. package/src/elements/{kitn-message.stories.tsx → message.stories.tsx} +12 -12
  127. package/src/elements/message.tsx +5 -5
  128. package/src/elements/{kitn-model-switcher.stories.tsx → model-switcher.stories.tsx} +10 -10
  129. package/src/elements/model-switcher.tsx +5 -5
  130. package/src/elements/{kitn-prompt-input.stories.tsx → prompt-input.stories.tsx} +41 -19
  131. package/src/elements/prompt-input.tsx +5 -5
  132. package/src/elements/{kitn-prompt-suggestions.stories.tsx → prompt-suggestions.stories.tsx} +13 -13
  133. package/src/elements/prompt-suggestions.tsx +4 -4
  134. package/src/elements/{kitn-reasoning.stories.tsx → reasoning.stories.tsx} +10 -10
  135. package/src/elements/reasoning.tsx +4 -4
  136. package/src/elements/register.ts +11 -1
  137. package/src/elements/resizable.stories.tsx +200 -0
  138. package/src/elements/resizable.tsx +264 -0
  139. package/src/elements/{kitn-response-stream.stories.tsx → response-stream.stories.tsx} +10 -10
  140. package/src/elements/response-stream.tsx +4 -4
  141. package/src/elements/{kitn-source-list.stories.tsx → source-list.stories.tsx} +11 -11
  142. package/src/elements/{kitn-source.stories.tsx → source.stories.tsx} +12 -12
  143. package/src/elements/source.tsx +5 -5
  144. package/src/elements/styles.css +140 -1
  145. package/src/elements/task-list-card.stories.tsx +194 -0
  146. package/src/elements/task-list-card.tsx +40 -0
  147. package/src/elements/{kitn-text-shimmer.stories.tsx → text-shimmer.stories.tsx} +10 -10
  148. package/src/elements/text-shimmer.tsx +3 -3
  149. package/src/elements/{kitn-thinking-bar.stories.tsx → thinking-bar.stories.tsx} +11 -11
  150. package/src/elements/thinking-bar.tsx +5 -5
  151. package/src/elements/{kitn-tool.stories.tsx → tool.stories.tsx} +10 -10
  152. package/src/elements/tool.tsx +3 -3
  153. package/src/elements/{kitn-voice-input.stories.tsx → voice-input.stories.tsx} +10 -10
  154. package/src/elements/voice-input.tsx +4 -4
  155. package/src/index.ts +94 -2
  156. package/src/primitives/card-contract.ts +60 -0
  157. package/src/primitives/card-host.tsx +35 -0
  158. package/src/primitives/card-routing.ts +79 -0
  159. package/src/primitives/card-schemas/card-envelope.schema.json +14 -0
  160. package/src/primitives/card-schemas/card-event.schema.json +12 -0
  161. package/src/primitives/card-schemas/confirm.schema.json +65 -0
  162. package/src/primitives/card-schemas/embed.schema.json +65 -0
  163. package/src/primitives/card-schemas/form.result.schema.json +7 -0
  164. package/src/primitives/card-schemas/form.schema.json +33 -0
  165. package/src/primitives/card-schemas/link.schema.json +56 -0
  166. package/src/primitives/card-schemas/task-list.result.schema.json +16 -0
  167. package/src/primitives/card-schemas/task-list.schema.json +78 -0
  168. package/src/primitives/card-validate.ts +95 -0
  169. package/src/primitives/embed-providers.ts +254 -0
  170. package/src/primitives/highlighter.ts +4 -0
  171. package/src/primitives/link-preview.ts +87 -0
  172. package/src/primitives/pdf-preview.ts +121 -0
  173. package/src/stories/chat-panel-layout.stories.tsx +2 -1
  174. package/src/stories/chat-scene.tsx +22 -21
  175. package/src/stories/checkpoint-restore.stories.tsx +10 -10
  176. package/src/stories/conversation-with-reasoning.stories.tsx +4 -4
  177. package/src/stories/conversation-with-sources.stories.tsx +7 -7
  178. package/src/stories/docs/Accessibility.mdx +2 -2
  179. package/src/stories/docs/ForAIAgents.mdx +3 -3
  180. package/src/stories/docs/GettingStarted.mdx +2 -2
  181. package/src/stories/docs/Installation.mdx +2 -2
  182. package/src/stories/docs/Integrations.mdx +29 -29
  183. package/src/stories/docs/Introduction.mdx +3 -3
  184. package/src/stories/docs/Theming.mdx +2 -2
  185. package/src/stories/docs/element-controls.ts +60 -0
  186. package/src/stories/docs/theme-editor/theme-editor.tsx +1 -0
  187. package/src/stories/examples/ChoosingComponents.mdx +94 -0
  188. package/src/stories/examples/sample-data.ts +79 -0
  189. package/src/stories/message-actions.stories.tsx +13 -13
  190. package/src/stories/pattern-centered-conversation.stories.tsx +3 -3
  191. package/src/stories/pattern-docked-widget.stories.tsx +1 -1
  192. package/src/stories/pattern-empty-state.stories.tsx +3 -3
  193. package/src/stories/prompt-input-variants.stories.tsx +13 -13
  194. package/src/stories/streaming-response.stories.tsx +3 -3
  195. package/src/stories/typography.stories.tsx +4 -4
  196. package/src/ui/avatar.stories.tsx +7 -8
  197. package/src/ui/badge.stories.tsx +7 -8
  198. package/src/ui/button.stories.tsx +8 -9
  199. package/src/ui/button.tsx +1 -0
  200. package/src/ui/collapsible.stories.tsx +6 -7
  201. package/src/ui/dropdown.stories.tsx +6 -7
  202. package/src/ui/hover-card.stories.tsx +6 -7
  203. package/src/ui/resizable.stories.tsx +74 -9
  204. package/src/ui/resizable.tsx +351 -71
  205. package/src/ui/scroll-area.stories.tsx +6 -7
  206. package/src/ui/scroll-area.tsx +3 -1
  207. package/src/ui/separator.stories.tsx +7 -8
  208. package/src/ui/skeleton.stories.tsx +7 -8
  209. package/src/ui/textarea.stories.tsx +6 -7
  210. package/src/ui/tooltip.stories.tsx +8 -9
  211. package/theme.css +65 -65
@@ -256,6 +256,7 @@ export function ChatScene(props: { class?: string }) {
256
256
  </div>
257
257
  <div class="flex items-center gap-2">
258
258
  <select
259
+ aria-label="Reading size"
259
260
  class="bg-muted/40 text-xs text-muted-foreground rounded-lg px-2 py-1.5 outline-none hover:bg-muted/60 transition-colors cursor-pointer"
260
261
  value={proseSize()}
261
262
  onChange={(e) => setProseSize(e.currentTarget.value as ProseSize)}
@@ -305,10 +306,10 @@ export function ChatScene(props: { class?: string }) {
305
306
  understand why.
306
307
  </MessageContent>
307
308
  <MessageActions class="flex gap-0 opacity-0 transition-opacity duration-150 group-hover:opacity-100">
308
- <Button variant="ghost" size="icon-sm" class="rounded-full">
309
+ <Button variant="ghost" size="icon-sm" class="rounded-full" aria-label="Edit message">
309
310
  <Pencil class="size-3.5" />
310
311
  </Button>
311
- <Button variant="ghost" size="icon-sm" class="rounded-full">
312
+ <Button variant="ghost" size="icon-sm" class="rounded-full" aria-label="Copy message">
312
313
  <Copy class="size-3.5" />
313
314
  </Button>
314
315
  </MessageActions>
@@ -325,16 +326,16 @@ export function ChatScene(props: { class?: string }) {
325
326
  {assistantResponse1}
326
327
  </MessageContent>
327
328
  <MessageActions class="-ml-2.5 flex gap-0 opacity-0 transition-opacity duration-150 group-hover:opacity-100">
328
- <Button variant="ghost" size="icon-sm" class="rounded-full">
329
+ <Button variant="ghost" size="icon-sm" class="rounded-full" aria-label="Copy message">
329
330
  <Copy class="size-3.5" />
330
331
  </Button>
331
- <Button variant="ghost" size="icon-sm" class="rounded-full">
332
+ <Button variant="ghost" size="icon-sm" class="rounded-full" aria-label="Good response">
332
333
  <ThumbsUp class="size-3.5" />
333
334
  </Button>
334
- <Button variant="ghost" size="icon-sm" class="rounded-full">
335
+ <Button variant="ghost" size="icon-sm" class="rounded-full" aria-label="Bad response">
335
336
  <ThumbsDown class="size-3.5" />
336
337
  </Button>
337
- <Button variant="ghost" size="icon-sm" class="rounded-full">
338
+ <Button variant="ghost" size="icon-sm" class="rounded-full" aria-label="Regenerate response">
338
339
  <RefreshCw class="size-3.5" />
339
340
  </Button>
340
341
  </MessageActions>
@@ -349,10 +350,10 @@ export function ChatScene(props: { class?: string }) {
349
350
  useEffect?
350
351
  </MessageContent>
351
352
  <MessageActions class="flex gap-0 opacity-0 transition-opacity duration-150 group-hover:opacity-100">
352
- <Button variant="ghost" size="icon-sm" class="rounded-full">
353
+ <Button variant="ghost" size="icon-sm" class="rounded-full" aria-label="Edit message">
353
354
  <Pencil class="size-3.5" />
354
355
  </Button>
355
- <Button variant="ghost" size="icon-sm" class="rounded-full">
356
+ <Button variant="ghost" size="icon-sm" class="rounded-full" aria-label="Copy message">
356
357
  <Copy class="size-3.5" />
357
358
  </Button>
358
359
  </MessageActions>
@@ -369,16 +370,16 @@ export function ChatScene(props: { class?: string }) {
369
370
  {assistantResponse2}
370
371
  </MessageContent>
371
372
  <MessageActions class="-ml-2.5 flex gap-0">
372
- <Button variant="ghost" size="icon-sm" class="rounded-full">
373
+ <Button variant="ghost" size="icon-sm" class="rounded-full" aria-label="Copy message">
373
374
  <Copy class="size-3.5" />
374
375
  </Button>
375
- <Button variant="ghost" size="icon-sm" class="rounded-full">
376
+ <Button variant="ghost" size="icon-sm" class="rounded-full" aria-label="Good response">
376
377
  <ThumbsUp class="size-3.5" />
377
378
  </Button>
378
- <Button variant="ghost" size="icon-sm" class="rounded-full">
379
+ <Button variant="ghost" size="icon-sm" class="rounded-full" aria-label="Bad response">
379
380
  <ThumbsDown class="size-3.5" />
380
381
  </Button>
381
- <Button variant="ghost" size="icon-sm" class="rounded-full">
382
+ <Button variant="ghost" size="icon-sm" class="rounded-full" aria-label="Regenerate response">
382
383
  <RefreshCw class="size-3.5" />
383
384
  </Button>
384
385
  </MessageActions>
@@ -445,16 +446,16 @@ export function ChatScene(props: { class?: string }) {
445
446
  SolidJS's fine-grained reactivity really shines here — updating a single item in React triggers a full virtual DOM diff of all 10,000 items, while SolidJS surgically updates only the changed DOM node.`}
446
447
  </MessageContent>
447
448
  <MessageActions class="-ml-2.5 flex gap-0">
448
- <Button variant="ghost" size="icon-sm" class="rounded-full">
449
+ <Button variant="ghost" size="icon-sm" class="rounded-full" aria-label="Copy message">
449
450
  <Copy class="size-3.5" />
450
451
  </Button>
451
- <Button variant="ghost" size="icon-sm" class="rounded-full">
452
+ <Button variant="ghost" size="icon-sm" class="rounded-full" aria-label="Good response">
452
453
  <ThumbsUp class="size-3.5" />
453
454
  </Button>
454
- <Button variant="ghost" size="icon-sm" class="rounded-full">
455
+ <Button variant="ghost" size="icon-sm" class="rounded-full" aria-label="Bad response">
455
456
  <ThumbsDown class="size-3.5" />
456
457
  </Button>
457
- <Button variant="ghost" size="icon-sm" class="rounded-full">
458
+ <Button variant="ghost" size="icon-sm" class="rounded-full" aria-label="Regenerate response">
458
459
  <RefreshCw class="size-3.5" />
459
460
  </Button>
460
461
  </MessageActions>
@@ -533,26 +534,26 @@ SolidJS's fine-grained reactivity really shines here — updating a single item
533
534
  />
534
535
  <PromptInputActions class="mt-2 flex w-full items-center justify-between gap-2 px-3 pb-3">
535
536
  <div class="flex items-center gap-2">
536
- <Button variant="outline" size="icon-sm" class="rounded-full">
537
+ <Button variant="outline" size="icon-sm" class="rounded-full" aria-label="Add">
537
538
  <Plus class="size-4" />
538
539
  </Button>
539
- <Button variant="outline" size="sm" class="rounded-full gap-1">
540
+ <Button variant="outline" size="sm" class="rounded-full gap-1" aria-label="Search the web">
540
541
  <Globe class="size-4" />
541
542
  Search
542
543
  </Button>
543
- <Button variant="outline" size="icon-sm" class="rounded-full">
544
+ <Button variant="outline" size="icon-sm" class="rounded-full" aria-label="More options">
544
545
  <MoreHorizontal class="size-4" />
545
546
  </Button>
546
547
  </div>
547
548
  <div class="flex items-center gap-2">
548
- <Button variant="outline" size="icon-sm" class="rounded-full">
549
+ <Button variant="outline" size="icon-sm" class="rounded-full" aria-label="Voice input">
549
550
  <Mic class="size-4" />
550
551
  </Button>
551
552
  <Button
552
553
  size="icon-sm"
553
554
  class="rounded-full"
554
555
  disabled={!inputValue().trim()}
555
- >
556
+ aria-label="Send message">
556
557
  <ArrowUp class="size-4" />
557
558
  </Button>
558
559
  </div>
@@ -67,9 +67,9 @@ Key design decisions:
67
67
  - Support filtering via query params: \`?status=active&assignee=me\``}
68
68
  </MessageContent>
69
69
  <MessageActions>
70
- <Button variant="ghost" size="icon-sm"><Copy class="size-3.5" /></Button>
71
- <Button variant="ghost" size="icon-sm"><ThumbsUp class="size-3.5" /></Button>
72
- <Button variant="ghost" size="icon-sm"><ThumbsDown class="size-3.5" /></Button>
70
+ <Button variant="ghost" size="icon-sm" aria-label="Copy message"><Copy class="size-3.5" /></Button>
71
+ <Button variant="ghost" size="icon-sm" aria-label="Good response"><ThumbsUp class="size-3.5" /></Button>
72
+ <Button variant="ghost" size="icon-sm" aria-label="Bad response"><ThumbsDown class="size-3.5" /></Button>
73
73
  </MessageActions>
74
74
  </div>
75
75
  </Message>
@@ -128,9 +128,9 @@ function authMiddleware(req, res, next) {
128
128
  **The tradeoff:** you can't instantly revoke JWTs. Mitigate this with short expiry + a blocklist for critical cases (password change, account compromise).`}
129
129
  </MessageContent>
130
130
  <MessageActions>
131
- <Button variant="ghost" size="icon-sm"><Copy class="size-3.5" /></Button>
132
- <Button variant="ghost" size="icon-sm"><ThumbsUp class="size-3.5" /></Button>
133
- <Button variant="ghost" size="icon-sm"><ThumbsDown class="size-3.5" /></Button>
131
+ <Button variant="ghost" size="icon-sm" aria-label="Copy message"><Copy class="size-3.5" /></Button>
132
+ <Button variant="ghost" size="icon-sm" aria-label="Good response"><ThumbsUp class="size-3.5" /></Button>
133
+ <Button variant="ghost" size="icon-sm" aria-label="Bad response"><ThumbsDown class="size-3.5" /></Button>
134
134
  </MessageActions>
135
135
  </div>
136
136
  </Message>
@@ -197,9 +197,9 @@ interface ApiError {
197
197
  Always include the \`requestId\` -- it's invaluable for correlating client errors with server logs.`}
198
198
  </MessageContent>
199
199
  <MessageActions>
200
- <Button variant="ghost" size="icon-sm"><Copy class="size-3.5" /></Button>
201
- <Button variant="ghost" size="icon-sm"><ThumbsUp class="size-3.5" /></Button>
202
- <Button variant="ghost" size="icon-sm"><ThumbsDown class="size-3.5" /></Button>
200
+ <Button variant="ghost" size="icon-sm" aria-label="Copy message"><Copy class="size-3.5" /></Button>
201
+ <Button variant="ghost" size="icon-sm" aria-label="Good response"><ThumbsUp class="size-3.5" /></Button>
202
+ <Button variant="ghost" size="icon-sm" aria-label="Bad response"><ThumbsDown class="size-3.5" /></Button>
203
203
  </MessageActions>
204
204
  </div>
205
205
  </Message>
@@ -212,7 +212,7 @@ Always include the \`requestId\` -- it's invaluable for correlating client error
212
212
  <PromptInput>
213
213
  <PromptInputTextarea placeholder="Continue the API design..." />
214
214
  <PromptInputActions class="justify-end">
215
- <Button variant="default" size="icon-sm" class="rounded-full">
215
+ <Button variant="default" size="icon-sm" class="rounded-full" aria-label="Send message">
216
216
  <ArrowUp class="size-4" />
217
217
  </Button>
218
218
  </PromptInputActions>
@@ -125,9 +125,9 @@ Move to a custom sync layer if:
125
125
  </MessageContent>
126
126
 
127
127
  <MessageActions>
128
- <Button variant="ghost" size="icon-sm"><Copy class="size-3.5" /></Button>
129
- <Button variant="ghost" size="icon-sm"><ThumbsUp class="size-3.5" /></Button>
130
- <Button variant="ghost" size="icon-sm"><ThumbsDown class="size-3.5" /></Button>
128
+ <Button variant="ghost" size="icon-sm" aria-label="Copy message"><Copy class="size-3.5" /></Button>
129
+ <Button variant="ghost" size="icon-sm" aria-label="Good response"><ThumbsUp class="size-3.5" /></Button>
130
+ <Button variant="ghost" size="icon-sm" aria-label="Bad response"><ThumbsDown class="size-3.5" /></Button>
131
131
  </MessageActions>
132
132
  </div>
133
133
  </Message>
@@ -140,7 +140,7 @@ Move to a custom sync layer if:
140
140
  <PromptInput>
141
141
  <PromptInputTextarea placeholder="Ask a follow-up..." />
142
142
  <PromptInputActions class="justify-end">
143
- <Button variant="default" size="icon-sm" class="rounded-full">
143
+ <Button variant="default" size="icon-sm" class="rounded-full" aria-label="Send message">
144
144
  <ArrowUp class="size-4" />
145
145
  </Button>
146
146
  </PromptInputActions>
@@ -75,9 +75,9 @@ The pattern is consistent: CPU-intensive tasks like **image processing, cryptogr
75
75
  </SourceList>
76
76
 
77
77
  <MessageActions>
78
- <Button variant="ghost" size="icon-sm"><Copy class="size-3.5" /></Button>
79
- <Button variant="ghost" size="icon-sm"><ThumbsUp class="size-3.5" /></Button>
80
- <Button variant="ghost" size="icon-sm"><ThumbsDown class="size-3.5" /></Button>
78
+ <Button variant="ghost" size="icon-sm" aria-label="Copy message"><Copy class="size-3.5" /></Button>
79
+ <Button variant="ghost" size="icon-sm" aria-label="Good response"><ThumbsUp class="size-3.5" /></Button>
80
+ <Button variant="ghost" size="icon-sm" aria-label="Bad response"><ThumbsDown class="size-3.5" /></Button>
81
81
  </MessageActions>
82
82
  </div>
83
83
  </Message>
@@ -139,9 +139,9 @@ The consensus from the Chrome team's analysis is: **use Wasm for compute-heavy i
139
139
  </SourceList>
140
140
 
141
141
  <MessageActions>
142
- <Button variant="ghost" size="icon-sm"><Copy class="size-3.5" /></Button>
143
- <Button variant="ghost" size="icon-sm"><ThumbsUp class="size-3.5" /></Button>
144
- <Button variant="ghost" size="icon-sm"><ThumbsDown class="size-3.5" /></Button>
142
+ <Button variant="ghost" size="icon-sm" aria-label="Copy message"><Copy class="size-3.5" /></Button>
143
+ <Button variant="ghost" size="icon-sm" aria-label="Good response"><ThumbsUp class="size-3.5" /></Button>
144
+ <Button variant="ghost" size="icon-sm" aria-label="Bad response"><ThumbsDown class="size-3.5" /></Button>
145
145
  </MessageActions>
146
146
  </div>
147
147
  </Message>
@@ -154,7 +154,7 @@ The consensus from the Chrome team's analysis is: **use Wasm for compute-heavy i
154
154
  <PromptInput>
155
155
  <PromptInputTextarea placeholder="Ask about WebAssembly..." />
156
156
  <PromptInputActions class="justify-end">
157
- <Button variant="default" size="icon-sm" class="rounded-full">
157
+ <Button variant="default" size="icon-sm" class="rounded-full" aria-label="Send message">
158
158
  <ArrowUp class="size-4" />
159
159
  </Button>
160
160
  </PromptInputActions>
@@ -14,7 +14,7 @@ This page describes the accessibility posture, the keyboard model, known limitat
14
14
 
15
15
  All text and UI chrome meet WCAG 2.1 AA contrast requirements (4.5:1 for body text, 3:1 for large text and UI components) in the default light and dark palettes.
16
16
 
17
- The `theme` attribute on every element (`light | dark | auto`) controls which token set loads. `auto` (the default) follows the OS `prefers-color-scheme` media query, so users who configure dark mode at the OS level get it automatically. Override either set with `--kitn-color-*` tokens — if you do, verify contrast yourself; the kit cannot audit tokens it does not control.
17
+ The `theme` attribute on every element (`light | dark | auto`) controls which token set loads. `auto` (the default) follows the OS `prefers-color-scheme` media query, so users who configure dark mode at the OS level get it automatically. Override either set with `--kc-color-*` tokens — if you do, verify contrast yourself; the kit cannot audit tokens it does not control.
18
18
 
19
19
  ---
20
20
 
@@ -113,7 +113,7 @@ The kit does not carry a "WCAG AA certified" certification — no automated tool
113
113
  ## What integrators need to do
114
114
 
115
115
  1. **Do not hide focus rings.** The kit renders visible focus rings via `:focus-visible`. Do not add `outline: none` or `outline: 0` in your host-page CSS targeting `*` or the element host.
116
- 2. **Verify contrast if you override tokens.** Use a contrast checker after overriding any `--kitn-color-*` token.
116
+ 2. **Verify contrast if you override tokens.** Use a contrast checker after overriding any `--kc-color-*` token.
117
117
  3. **Provide meaningful conversation titles.** The conversation list announces the `title` field to screen readers. "Chat 1" is less useful than "React integration help".
118
118
  4. **Set a meaningful `placeholder`.** The textarea placeholder doubles as an accessible name when no label is associated.
119
119
  5. **Check your theme choice.** Using `theme="light"` in a page the user has set to dark mode (or vice versa) can create contrast mismatches between kit components and host-page content. `theme="auto"` (the default) avoids this.
@@ -35,12 +35,12 @@ An HTML attribute is always a string, so passing `messages`, `models`, `context`
35
35
  `suggestions`, or `slashCommands` as an attribute silently fails.
36
36
 
37
37
  ```js
38
- const chat = document.querySelector('kitn-chat');
38
+ const chat = document.querySelector('kc-chat');
39
39
  chat.messages = [{ id: '1', role: 'assistant', content: 'Hi!' }]; // ✅ property
40
40
  ```
41
41
 
42
42
  ```html
43
- <kitn-chat messages="[...]"></kitn-chat> <!-- ❌ never works -->
43
+ <kc-chat messages="[...]"></kc-chat> <!-- ❌ never works -->
44
44
  ```
45
45
 
46
46
  Only scalar props (string / number / boolean) work as attributes — for example
@@ -60,7 +60,7 @@ Only scalar props (string / number / boolean) work as attributes — for example
60
60
 
61
61
  ```js
62
62
  import '@kitnai/chat/elements';
63
- const chat = document.querySelector('kitn-chat');
63
+ const chat = document.querySelector('kc-chat');
64
64
  chat.messages = [];
65
65
 
66
66
  chat.addEventListener('submit', async (e) => {
@@ -5,14 +5,14 @@ import * as FullChat from '../full-chat.stories';
5
5
 
6
6
  # Getting Started
7
7
 
8
- `<kitn-chat>` is **transport-agnostic**: you give it a `messages` array, it renders the conversation, and it emits a `submit` event when the user sends. You own the request and the streaming; the component owns the UI.
8
+ `<kc-chat>` is **transport-agnostic**: you give it a `messages` array, it renders the conversation, and it emits a `submit` event when the user sends. You own the request and the streaming; the component owns the UI.
9
9
 
10
10
  ## A web component in ~10 lines
11
11
 
12
12
  Set rich data as JS **properties** and listen for **events** on the element:
13
13
 
14
14
  ```html
15
- <kitn-chat id="chat" style="display:block; height:100vh;"></kitn-chat>
15
+ <kc-chat id="chat" style="display:block; height:100vh;"></kc-chat>
16
16
 
17
17
  <script type="module">
18
18
  import '@kitnai/chat/elements';
@@ -29,7 +29,7 @@ The `@kitnai/chat` entry is shipped as source, so your bundler tree-shakes it do
29
29
 
30
30
  ## Web components (React, Vue, plain HTML, …)
31
31
 
32
- Build the element bundle, then import it as a **side effect** — that registers `<kitn-chat>`, `<kitn-conversation-list>`, and `<kitn-prompt-input>`:
32
+ Build the element bundle, then import it as a **side effect** — that registers `<kc-chat>`, `<kc-conversations>`, and `<kc-prompt-input>`:
33
33
 
34
34
  ```bash
35
35
  npm run build # emits dist/kitn-chat.es.js
@@ -60,7 +60,7 @@ The element bundle is a self-contained ES module, so you can load it straight fr
60
60
  import 'https://unpkg.com/@kitnai/chat/dist/kitn-chat.es.js';
61
61
  </script>
62
62
 
63
- <kitn-chat></kitn-chat>
63
+ <kc-chat></kc-chat>
64
64
  ```
65
65
 
66
66
  - The URLs above track the **latest** release (no version) — they never go stale, so they're ideal for demos and quick starts.
@@ -13,13 +13,13 @@ import { Meta } from '@storybook/addon-docs/blocks';
13
13
  Arrays and objects **must** be set as JavaScript **properties** on the DOM element — never as HTML attributes. An HTML attribute is always a string, so passing `messages`, `models`, `context`, `suggestions`, or `slashCommands` as an attribute silently fails or is ignored.
14
14
 
15
15
  ```js
16
- const chat = document.querySelector('kitn-chat');
16
+ const chat = document.querySelector('kc-chat');
17
17
 
18
18
  // ✅ correct — set as a JS property
19
19
  chat.messages = [{ id: '1', role: 'assistant', content: 'Hello!' }];
20
20
 
21
21
  // ❌ wrong — HTML attribute, always a string, never works
22
- // <kitn-chat messages="[...]"></kitn-chat>
22
+ // <kc-chat messages="[...]"></kc-chat>
23
23
  ```
24
24
 
25
25
  Scalar values (strings, numbers, booleans) work as attributes: `placeholder`, `loading`, `theme`, `prose-size`, and so on.
@@ -30,7 +30,7 @@ Scalar values (strings, numbers, booleans) work as attributes: `placeholder`, `l
30
30
 
31
31
  Import the element bundle once as a side-effect (it registers all `kitn-*` custom elements), then set properties and listen for events in a `<script type="module">` block.
32
32
 
33
- > **Want the whole shell in one tag?** `<kitn-chat-workspace>` bundles the conversation-list sidebar, the drag-to-resize handle, and the full chat thread together. Set `conversations`, `messages`, and optionally `models` as properties; listen for `conversationselect` and `submit`. See the <a href="?path=/docs/web-components-kitn-chat-workspace--docs">kitn-chat-workspace story</a> and the <a href="https://github.com/kitn-ai/kitn-chat/blob/main/docs/web-components.md#kitn-chat-workspace--kitnchatworkspace">web-components.md reference</a> for the full API.
33
+ > **Want the whole shell in one tag?** `<kc-workspace>` bundles the conversation-list sidebar, the drag-to-resize handle, and the full chat thread together. Set `conversations`, `messages`, and optionally `models` as properties; listen for `conversationselect` and `submit`. See the <a href="?path=/docs/web-components-kc-workspace--docs">kc-workspace story</a> and the <a href="https://github.com/kitn-ai/chat/blob/main/docs/web-components.md#kc-workspace--kcworkspace">web-components.md reference</a> for the full API.
34
34
 
35
35
  ```html
36
36
  <!DOCTYPE html>
@@ -40,7 +40,7 @@ Import the element bundle once as a side-effect (it registers all `kitn-*` custo
40
40
  <link rel="stylesheet" href="./node_modules/@kitnai/chat/dist/theme.tokens.css" />
41
41
  </head>
42
42
  <body style="height: 100vh; margin: 0;">
43
- <kitn-chat id="chat" style="display: block; height: 100%;"></kitn-chat>
43
+ <kc-chat id="chat" style="display: block; height: 100%;"></kc-chat>
44
44
 
45
45
  <script type="module">
46
46
  import '@kitnai/chat/elements';
@@ -89,10 +89,10 @@ Scalars can go directly in the HTML as attributes:
89
89
 
90
90
  ```html
91
91
  <!-- theme, placeholder, and loading are scalar → safe as attributes -->
92
- <kitn-chat
92
+ <kc-chat
93
93
  theme="dark"
94
94
  placeholder="Ask anything…"
95
- ></kitn-chat>
95
+ ></kc-chat>
96
96
  ```
97
97
 
98
98
  ---
@@ -102,7 +102,7 @@ Scalars can go directly in the HTML as attributes:
102
102
  The kit ships auto-generated, typed React wrappers under `@kitnai/chat/react`. They handle the ref plumbing internally — rich props are set as DOM **properties** and CustomEvents are exposed as `on<Event>` handlers, so you write idiomatic JSX without touching refs yourself.
103
103
 
104
104
  ```tsx
105
- import { KitnChat, KitnConversationList } from '@kitnai/chat/react';
105
+ import { KcChat, KcConversations } from '@kitnai/chat/react';
106
106
  import { useState } from 'react';
107
107
 
108
108
  type Message = {
@@ -137,7 +137,7 @@ export function App() {
137
137
  };
138
138
 
139
139
  return (
140
- <KitnChat
140
+ <KcChat
141
141
  messages={messages}
142
142
  suggestions={['Summarize the chat', 'Start fresh']}
143
143
  onSubmit={handleSubmit}
@@ -169,18 +169,18 @@ Component names are the PascalCase of the tag name:
169
169
 
170
170
  ```tsx
171
171
  import {
172
- KitnChat,
173
- KitnConversationList,
174
- KitnPromptInput,
175
- KitnMessage,
176
- KitnMarkdown,
177
- KitnCodeBlock,
178
- KitnReasoning,
179
- KitnTool,
180
- KitnContextMeter,
181
- KitnModelSwitcher,
182
- KitnAttachments,
183
- KitnLoader,
172
+ KcChat,
173
+ KcConversations,
174
+ KcPromptInput,
175
+ KcMessage,
176
+ KcMarkdown,
177
+ KcCodeBlock,
178
+ KcReasoning,
179
+ KcTool,
180
+ KcContext,
181
+ KcModelSwitcher,
182
+ KcAttachments,
183
+ KcLoader,
184
184
  // …all 27 elements
185
185
  } from '@kitnai/chat/react';
186
186
  ```
@@ -223,7 +223,7 @@ export function Chat() {
223
223
  return () => el.removeEventListener('submit', onSubmit);
224
224
  }, []);
225
225
 
226
- return <kitn-chat ref={chatRef} style={{ display: 'block', height: '100vh' }} />;
226
+ return <kc-chat ref={chatRef} style={{ display: 'block', height: '100vh' }} />;
227
227
  }
228
228
  ```
229
229
 
@@ -254,7 +254,7 @@ const handleSubmit = (e: CustomEvent<{ value: string }>) => {
254
254
 
255
255
  <template>
256
256
  <!-- Arrays/objects → .prop modifier; scalars → plain attributes -->
257
- <kitn-chat
257
+ <kc-chat
258
258
  :messages.prop="messages"
259
259
  placeholder="Ask anything…"
260
260
  theme="auto"
@@ -275,7 +275,7 @@ Add `@kitnai/chat/elements` to your `vite.config.ts` / `env.d.ts` once so Vue's
275
275
 
276
276
  ### Sidebar + chat together (Vue)
277
277
 
278
- Cross-element coordination goes through the host component — the `<kitn-conversation-list>` fires `select`, you update a reactive ref, and pass it into `<kitn-chat>`:
278
+ Cross-element coordination goes through the host component — the `<kc-conversations>` fires `select`, you update a reactive ref, and pass it into `<kc-chat>`:
279
279
 
280
280
  ```html
281
281
  <script setup lang="ts">
@@ -297,13 +297,13 @@ const onSelect = (e: CustomEvent<{ id: string }>) => {
297
297
 
298
298
  <template>
299
299
  <div style="display: flex; height: 100vh;">
300
- <kitn-conversation-list
300
+ <kc-conversations
301
301
  :conversations.prop="conversations"
302
302
  :active-id="activeId"
303
303
  style="width: 260px"
304
304
  @select="onSelect"
305
305
  />
306
- <kitn-chat
306
+ <kc-chat
307
307
  :messages.prop="messages"
308
308
  style="flex: 1"
309
309
  />
@@ -333,7 +333,7 @@ Svelte's template compiler sets DOM **properties** when you bind with `bind:` or
333
333
  </script>
334
334
 
335
335
  <!-- use:action pattern to set properties -->
336
- <kitn-chat
336
+ <kc-chat
337
337
  use:setProps={{ messages }}
338
338
  style="display: block; height: 100vh"
339
339
  on:submit={handleSubmit}
@@ -393,7 +393,7 @@ export class AppComponent {
393
393
 
394
394
  ```html
395
395
  <!-- app.component.html -->
396
- <kitn-chat
396
+ <kc-chat
397
397
  [messages]="messages"
398
398
  [models]="models"
399
399
  [loading]="loading"
@@ -401,7 +401,7 @@ export class AppComponent {
401
401
  style="display: block; height: 100vh"
402
402
  (submit)="onSubmit($event)"
403
403
  (modelchange)="onModelChange($event)"
404
- ></kitn-chat>
404
+ ></kc-chat>
405
405
  ```
406
406
 
407
407
  > **Why `[messages]` works:** Angular's `[prop]` binding writes to the element's DOM property (not an attribute), so the property-vs-attribute rule is satisfied automatically. Scalars like `theme` can be plain attributes. `CUSTOM_ELEMENTS_SCHEMA` is required so Angular doesn't error on the unknown `kitn-*` tags.
@@ -509,4 +509,4 @@ async function speakCloud(text) {
509
509
 
510
510
  ## Speech-to-text
511
511
 
512
- The reverse direction is built in — the kit ships `<kitn-voice-input>` (and a `VoiceInput` SolidJS component). Find it in the sidebar under the component stories.
512
+ The reverse direction is built in — the kit ships `<kc-voice-input>` (and a `VoiceInput` SolidJS component). Find it in the sidebar under the component stories.
@@ -13,9 +13,9 @@ Message threads, prompt inputs, streaming responses, markdown + code rendering,
13
13
 
14
14
  ## Why @kitnai/chat
15
15
 
16
- - **Works in any framework** — drop in the framework-agnostic **web components** (`<kitn-chat>`) and they just work in React, Vue, Angular, Svelte, or plain HTML. Authored in SolidJS, so SolidJS apps can also import the components natively for full compositional control.
16
+ - **Works in any framework** — drop in the framework-agnostic **web components** (`<kc-chat>`) and they just work in React, Vue, Angular, Svelte, or plain HTML. Authored in SolidJS, so SolidJS apps can also import the components natively for full compositional control.
17
17
  - **Zero style conflicts** — the web components render in **Shadow DOM**, so the host page's CSS can't leak in and the kit's Tailwind can't leak out.
18
- - **Lightweight** — a markdown-only `<kitn-chat>` is **~61 KB gzip**, a single file. Syntax highlighting loads **on demand, per language, with no WASM** — and never loads at all if you don't render code.
18
+ - **Lightweight** — a markdown-only `<kc-chat>` is **~61 KB gzip**, a single file. Syntax highlighting loads **on demand, per language, with no WASM** — and never loads at all if you don't render code.
19
19
  - **~50 composable components** across three layers: headless primitives → accessible UI primitives (built in-house, WCAG 2.1 AA — no third-party UI dependency) → AI feature components.
20
20
  - **Themeable** — restyle everything by overriding a handful of `--color-*` design tokens.
21
21
 
@@ -26,7 +26,7 @@ The kit ships at two layers, and the sidebar reflects that. **Use the right one
26
26
  - **Web Components** — the framework-agnostic `<kitn-*>` custom elements. **This is what to copy into a React, Vue, Angular, Svelte, or plain-HTML app.** Data goes on JS properties, interactions come back as events.
27
27
  - **Components · SolidJS** and **UI · SolidJS** — the **native SolidJS** components (feature components) and primitives (Button, Dropdown, HoverCard, …) that the web components are *built from*. Their snippets are SolidJS JSX, so **only copy these into a SolidJS app**.
28
28
 
29
- In other words: the `<kitn-chat>` web component is a thin facade over the SolidJS `ChatContainer`/`Message`/… components — same UI, different consumption model. When in doubt, reach for **Web Components**.
29
+ In other words: the `<kc-chat>` web component is a thin facade over the SolidJS `ChatContainer`/`Message`/… components — same UI, different consumption model. When in doubt, reach for **Web Components**.
30
30
 
31
31
  ## Where to next
32
32
 
@@ -64,7 +64,7 @@ document.documentElement.classList.toggle('dark');
64
64
 
65
65
  ## Appearance settings
66
66
 
67
- `ChatConfig` (SolidJS) and matching properties on `<kitn-chat>` control non-color appearance:
67
+ `ChatConfig` (SolidJS) and matching properties on `<kc-chat>` control non-color appearance:
68
68
 
69
69
  | Setting | Values | Purpose |
70
70
  | --- | --- | --- |
@@ -79,7 +79,7 @@ document.documentElement.classList.toggle('dark');
79
79
  ```
80
80
 
81
81
  ```html
82
- <kitn-chat prose-size="base" code-theme="github-dark-dimmed"></kitn-chat>
82
+ <kc-chat prose-size="base" code-theme="github-dark-dimmed"></kc-chat>
83
83
  ```
84
84
 
85
85
  That's the whole theming surface — tokens for color, `ChatConfig` for sizing and code. Everything else is encapsulated.
@@ -0,0 +1,60 @@
1
+ import meta from '../../elements/element-meta.json';
2
+
3
+ type Prop = { name: string; type: string; default?: string; scalar: boolean };
4
+ type ElementMeta = { tag: string; props: Prop[] };
5
+ const all = meta as unknown as ElementMeta[];
6
+
7
+ // A small, consistent pointer at the top of every Web Component's Docs tab,
8
+ // directing readers to the dedicated API tab (which holds the full generated
9
+ // spec). Keeps the Docs tab focused on examples while keeping the spec one click
10
+ // away — and discoverable, since everyone reads the description first.
11
+ const API_POINTER =
12
+ '> **Full API reference** — every property, event, default and token is on the **API** tab above.';
13
+
14
+ /**
15
+ * Build a Storybook `docs.description` from an element's intro paragraphs, with
16
+ * the API-tab pointer prepended. The generated spec itself lives in the **API**
17
+ * tab (see `.storybook/api-tab.tsx`), not inline here.
18
+ * (`tag` is kept in the signature so callers don't churn if we re-inline later.)
19
+ */
20
+ export function specDescription(_tag: string, paragraphs: string[]): { component: string } {
21
+ return { component: [API_POINTER, ...paragraphs].join('\n\n') };
22
+ }
23
+
24
+ // The Components/UI sibling of API_POINTER — same idea, component vocabulary
25
+ // (props/callbacks/slots rather than properties/events/attributes).
26
+ const COMPONENT_POINTER =
27
+ '> **Full API reference** — every prop, callback, slot and token is on the **API** tab above.';
28
+
29
+ /**
30
+ * Build a Storybook `docs.description` for a SolidJS/UI component story, with the
31
+ * API-tab pointer prepended. The generated spec (props/callbacks/slots/tokens)
32
+ * lives on the **API** tab (see `.storybook/api-tab.tsx`), from
33
+ * `src/components/component-meta.json`.
34
+ */
35
+ export function componentDescription(paragraphs: string[]): { component: string } {
36
+ return { component: [COMPONENT_POINTER, ...paragraphs].join('\n\n') };
37
+ }
38
+
39
+ const enumValues = (type: string): string[] | null => {
40
+ // string-literal unions like "'light' | 'dark' | 'auto'"
41
+ const parts = type.split('|').map((s) => s.trim());
42
+ if (parts.length > 1 && parts.every((p) => /^'[^']*'$/.test(p))) return parts.map((p) => p.slice(1, -1));
43
+ return null;
44
+ };
45
+
46
+ /** Storybook argTypes for an element's scalar props (theme select, booleans, text, number). */
47
+ export function argTypesFor(tag: string): Record<string, unknown> {
48
+ const el = all.find((e) => e.tag === tag);
49
+ if (!el) return {};
50
+ const out: Record<string, unknown> = {};
51
+ for (const p of el.props) {
52
+ if (!p.scalar) continue;
53
+ const values = enumValues(p.type);
54
+ if (values) out[p.name] = { control: 'select', options: values };
55
+ else if (/boolean/.test(p.type)) out[p.name] = { control: 'boolean' };
56
+ else if (/number/.test(p.type)) out[p.name] = { control: 'number' };
57
+ else out[p.name] = { control: 'text' };
58
+ }
59
+ return out;
60
+ }
@@ -110,6 +110,7 @@ export function ThemeEditor() {
110
110
  </div>
111
111
  <div class="flex items-center gap-2">
112
112
  <select
113
+ aria-label="Theme preset"
113
114
  class="bg-input border border-border rounded-md text-xs px-2 h-8"
114
115
  value={presetName()}
115
116
  onChange={(e) => loadPreset(e.currentTarget.value)}