@kitnai/chat 0.7.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 (212) hide show
  1. package/README.md +9 -9
  2. package/dist/custom-elements.json +1626 -883
  3. package/dist/kitn-chat.es.js +36 -36
  4. package/dist/llms/llms-full.txt +303 -142
  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 +356 -189
  19. package/frameworks/react/runtime.tsx +2 -2
  20. package/llms-full.txt +303 -142
  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 -20
  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 -22
  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 -19
  83. package/src/elements/chat-scope-picker.tsx +4 -4
  84. package/src/elements/{kitn-chat-workspace.stories.tsx → chat-workspace.stories.tsx} +15 -23
  85. package/src/elements/chat-workspace.tsx +2 -2
  86. package/src/elements/{kitn-chat.stories.tsx → chat.stories.tsx} +12 -20
  87. package/src/elements/chat.tsx +2 -2
  88. package/src/elements/{kitn-checkpoint.stories.tsx → checkpoint.stories.tsx} +11 -20
  89. package/src/elements/checkpoint.tsx +4 -4
  90. package/src/elements/{kitn-code-block.stories.tsx → code-block.stories.tsx} +10 -19
  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 -19
  97. package/src/elements/context-meter.tsx +3 -3
  98. package/src/elements/{kitn-conversation-list.stories.tsx → conversation-list.stories.tsx} +12 -20
  99. package/src/elements/conversation-list.tsx +2 -2
  100. package/src/elements/css.ts +1 -1
  101. package/src/elements/define.tsx +10 -10
  102. package/src/elements/element-meta.json +1379 -733
  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 -21
  107. package/src/elements/empty.tsx +3 -3
  108. package/src/elements/{kitn-feedback-bar.stories.tsx → feedback-bar.stories.tsx} +11 -20
  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 -21
  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 -19
  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 -20
  121. package/src/elements/loader.tsx +3 -3
  122. package/src/elements/{kitn-markdown.stories.tsx → markdown.stories.tsx} +10 -19
  123. package/src/elements/markdown.tsx +3 -3
  124. package/src/elements/{kitn-message-skills.stories.tsx → message-skills.stories.tsx} +10 -19
  125. package/src/elements/message-skills.tsx +3 -3
  126. package/src/elements/{kitn-message.stories.tsx → message.stories.tsx} +12 -21
  127. package/src/elements/message.tsx +5 -5
  128. package/src/elements/{kitn-model-switcher.stories.tsx → model-switcher.stories.tsx} +10 -19
  129. package/src/elements/model-switcher.tsx +5 -5
  130. package/src/elements/{kitn-prompt-input.stories.tsx → prompt-input.stories.tsx} +14 -22
  131. package/src/elements/prompt-input.tsx +3 -3
  132. package/src/elements/{kitn-prompt-suggestions.stories.tsx → prompt-suggestions.stories.tsx} +13 -22
  133. package/src/elements/prompt-suggestions.tsx +4 -4
  134. package/src/elements/{kitn-reasoning.stories.tsx → reasoning.stories.tsx} +10 -19
  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 -19
  140. package/src/elements/response-stream.tsx +4 -4
  141. package/src/elements/{kitn-source-list.stories.tsx → source-list.stories.tsx} +11 -20
  142. package/src/elements/{kitn-source.stories.tsx → source.stories.tsx} +12 -21
  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 -19
  148. package/src/elements/text-shimmer.tsx +3 -3
  149. package/src/elements/{kitn-thinking-bar.stories.tsx → thinking-bar.stories.tsx} +11 -20
  150. package/src/elements/thinking-bar.tsx +5 -5
  151. package/src/elements/{kitn-tool.stories.tsx → tool.stories.tsx} +10 -19
  152. package/src/elements/tool.tsx +3 -3
  153. package/src/elements/{kitn-voice-input.stories.tsx → voice-input.stories.tsx} +10 -19
  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 +32 -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
  212. package/src/stories/docs/element-spec.tsx +0 -86
@@ -10,9 +10,17 @@
10
10
  :host { display: block; }
11
11
  * { border-color: var(--color-border); }
12
12
 
13
+ /* Native controls (date/time pickers, <select>, number spinners, the
14
+ checkbox/radio fallbacks, scrollbars) must follow the kit's light/dark
15
+ mode — NOT the OS — so e.g. the native calendar icon never renders black
16
+ on a dark surface. `color-scheme` drives that native rendering; we set it
17
+ from the resolved mode (the `.dark` scope the element factory toggles). */
18
+ :host { color-scheme: light; }
19
+ .dark { color-scheme: dark; }
20
+
13
21
  /* One token-driven focus ring for EVERY focusable element in the shadow root.
14
22
  Blue by default (via --color-ring), consistent in light + dark, and
15
- overridable through --kitn-color-ring. Uses `outline` (not a box-shadow
23
+ overridable through --kc-color-ring. Uses `outline` (not a box-shadow
16
24
  ring) so it never clips on overflow and follows the element's radius;
17
25
  `:focus-visible` so it only appears for keyboard navigation, never on a
18
26
  mouse click. Components that render their own ring set
@@ -45,3 +53,134 @@
45
53
  *::-webkit-scrollbar-thumb:hover { background-color: var(--color-scrollbar-thumb-hover); }
46
54
  *::-webkit-scrollbar-corner { background: transparent; }
47
55
  }
56
+
57
+ @layer components {
58
+ /* Designed radio + checkbox (appearance-none) so the form controls read as
59
+ intentional, theme correctly in light/dark, and have comfortable targets —
60
+ replacing the weak native defaults. Used via `.kc-radio` / `.kc-checkbox`. */
61
+ .kc-radio,
62
+ .kc-checkbox {
63
+ appearance: none;
64
+ -webkit-appearance: none;
65
+ inline-size: 1.125rem;
66
+ block-size: 1.125rem;
67
+ margin: 0;
68
+ flex-shrink: 0;
69
+ cursor: pointer;
70
+ border: 1.5px solid var(--color-input);
71
+ background-color: var(--color-background);
72
+ display: inline-grid;
73
+ place-content: center;
74
+ box-shadow: 0 1px 2px rgb(0 0 0 / 0.04);
75
+ transition: border-color 0.18s ease, background-color 0.18s ease, box-shadow 0.18s ease,
76
+ transform 0.1s ease;
77
+ }
78
+ .kc-radio { border-radius: 9999px; }
79
+ .kc-checkbox { border-radius: 0.3rem; }
80
+ .kc-radio::after {
81
+ content: "";
82
+ inline-size: 0.5rem;
83
+ block-size: 0.5rem;
84
+ border-radius: 9999px;
85
+ background-color: var(--color-primary);
86
+ transform: scale(0);
87
+ transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
88
+ }
89
+ .kc-radio:checked { border-color: var(--color-primary); }
90
+ .kc-radio:checked::after { transform: scale(1); }
91
+ .kc-checkbox:checked {
92
+ background-color: var(--color-primary);
93
+ border-color: var(--color-primary);
94
+ }
95
+ .kc-checkbox::after {
96
+ content: "";
97
+ inline-size: 0.8rem;
98
+ block-size: 0.8rem;
99
+ background-color: var(--color-primary-foreground);
100
+ transform: scale(0);
101
+ transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
102
+ -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E") center / contain no-repeat;
103
+ mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E") center / contain no-repeat;
104
+ }
105
+ .kc-checkbox:checked::after { transform: scale(1); }
106
+ .kc-radio:hover:not(:disabled),
107
+ .kc-checkbox:hover:not(:disabled) {
108
+ border-color: var(--color-primary);
109
+ box-shadow: 0 0 0 4px color-mix(in srgb, var(--color-primary) 9%, transparent);
110
+ }
111
+ .kc-radio:active:not(:disabled),
112
+ .kc-checkbox:active:not(:disabled) { transform: scale(0.92); }
113
+ .kc-checkbox:checked {
114
+ box-shadow: 0 1px 4px color-mix(in srgb, var(--color-primary) 26%, transparent);
115
+ }
116
+ .kc-radio:disabled,
117
+ .kc-checkbox:disabled { opacity: 0.5; cursor: not-allowed; }
118
+
119
+ /* Custom range slider: a filled track (--kc-range-fill set by the widget) and a
120
+ refined thumb that scales on hover/press with a focus halo — no native chrome. */
121
+ .kc-range {
122
+ appearance: none;
123
+ -webkit-appearance: none;
124
+ inline-size: 100%;
125
+ block-size: 1.25rem;
126
+ background: transparent;
127
+ cursor: pointer;
128
+ margin: 0;
129
+ }
130
+ .kc-range::-webkit-slider-runnable-track {
131
+ block-size: 0.375rem;
132
+ border-radius: 9999px;
133
+ border: none;
134
+ box-shadow: none;
135
+ background: linear-gradient(
136
+ to right,
137
+ var(--color-primary) var(--kc-range-fill, 0%),
138
+ var(--color-muted) var(--kc-range-fill, 0%)
139
+ );
140
+ }
141
+ .kc-range::-moz-range-track {
142
+ block-size: 0.375rem;
143
+ border-radius: 9999px;
144
+ border: none;
145
+ box-shadow: none;
146
+ background: var(--color-muted);
147
+ }
148
+ .kc-range::-moz-range-progress {
149
+ block-size: 0.375rem;
150
+ border-radius: 9999px;
151
+ background: var(--color-primary);
152
+ }
153
+ .kc-range::-webkit-slider-thumb {
154
+ appearance: none;
155
+ -webkit-appearance: none;
156
+ inline-size: 1.125rem;
157
+ block-size: 1.125rem;
158
+ border-radius: 9999px;
159
+ background: var(--color-background);
160
+ border: 2px solid var(--color-primary);
161
+ box-shadow: 0 1px 3px rgb(0 0 0 / 0.18);
162
+ margin-block-start: -0.375rem;
163
+ transition: transform 0.12s ease, box-shadow 0.12s ease;
164
+ }
165
+ .kc-range::-moz-range-thumb {
166
+ inline-size: 1.125rem;
167
+ block-size: 1.125rem;
168
+ border-radius: 9999px;
169
+ background: var(--color-background);
170
+ border: 2px solid var(--color-primary);
171
+ box-shadow: 0 1px 3px rgb(0 0 0 / 0.18);
172
+ transition: transform 0.12s ease, box-shadow 0.12s ease;
173
+ }
174
+ .kc-range:hover::-webkit-slider-thumb { transform: scale(1.12); }
175
+ .kc-range:hover::-moz-range-thumb { transform: scale(1.12); }
176
+ .kc-range:focus-visible { outline: none; }
177
+ .kc-range:focus-visible::-webkit-slider-thumb,
178
+ .kc-range:active::-webkit-slider-thumb {
179
+ box-shadow: 0 0 0 6px color-mix(in srgb, var(--color-primary) 14%, transparent);
180
+ }
181
+ .kc-range:focus-visible::-moz-range-thumb,
182
+ .kc-range:active::-moz-range-thumb {
183
+ box-shadow: 0 0 0 6px color-mix(in srgb, var(--color-primary) 14%, transparent);
184
+ }
185
+ .kc-range:disabled { opacity: 0.5; cursor: not-allowed; }
186
+ }
@@ -0,0 +1,194 @@
1
+ import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
+ import { createSignal, onMount, type JSX } from 'solid-js';
3
+ import './task-list-card';
4
+ import { argTypesFor, specDescription } from '../stories/docs/element-controls';
5
+ import type { TaskListCardData } from '../components/task-list-card';
6
+ import type { CardEvent } from '../primitives/card-contract';
7
+
8
+ declare module 'solid-js' {
9
+ // eslint-disable-next-line @typescript-eslint/no-namespace
10
+ namespace JSX {
11
+ interface IntrinsicElements {
12
+ 'kc-task-list': JSX.HTMLAttributes<HTMLElement> & {
13
+ heading?: string;
14
+ 'card-id'?: string;
15
+ ref?: (el: HTMLElement) => void;
16
+ };
17
+ }
18
+ }
19
+ }
20
+
21
+ type TaskListEl = HTMLElement & { data?: TaskListCardData };
22
+
23
+ function Frame(props: { children: JSX.Element }) {
24
+ return <div style={{ 'max-width': '460px' }}>{props.children}</div>;
25
+ }
26
+
27
+ /** Mounts a <kc-task-list>, sets `.data`, logs the emitted CardEvent under the render. */
28
+ function TaskListDemo(props: { def: TaskListCardData; cardId: string; heading?: string }) {
29
+ const [log, setLog] = createSignal<CardEvent[]>([]);
30
+ let el: TaskListEl | undefined;
31
+ onMount(() => {
32
+ if (!el) return;
33
+ el.data = props.def;
34
+ el.addEventListener('kc-card', (e) => {
35
+ const detail = (e as CustomEvent<CardEvent>).detail;
36
+ setLog((prev) => [...prev, detail]);
37
+ });
38
+ });
39
+ return (
40
+ <Frame>
41
+ <div style={{ display: 'flex', 'flex-direction': 'column', gap: '12px' }}>
42
+ <kc-task-list ref={(e) => (el = e as TaskListEl)} card-id={props.cardId} heading={props.heading} />
43
+ <pre
44
+ style={{
45
+ margin: 0,
46
+ 'max-height': '180px',
47
+ overflow: 'auto',
48
+ background: 'var(--color-muted, #f4f4f5)',
49
+ 'border-radius': '8px',
50
+ padding: '8px',
51
+ 'font-size': '12px',
52
+ }}
53
+ >
54
+ {log().length === 0 ? '// emitted CardEvents appear here' : JSON.stringify(log(), null, 2)}
55
+ </pre>
56
+ </div>
57
+ </Frame>
58
+ );
59
+ }
60
+
61
+ const PLAN: TaskListCardData = {
62
+ mode: 'select',
63
+ selectAll: true,
64
+ confirmLabel: 'Run selected',
65
+ tasks: [
66
+ { id: 'lint', label: 'Run linter', checked: true },
67
+ { id: 'test', label: 'Run unit tests', checked: true },
68
+ { id: 'build', label: 'Build production bundle' },
69
+ { id: 'deploy', label: 'Deploy to staging', description: 'Reversible; staging only' },
70
+ ],
71
+ };
72
+
73
+ const REQUIRE_ONE: TaskListCardData = {
74
+ confirmLabel: 'Apply',
75
+ allowEmpty: false,
76
+ tasks: [
77
+ { id: 'cache', label: 'Clear the CDN cache' },
78
+ { id: 'reindex', label: 'Rebuild the search index' },
79
+ { id: 'restart', label: 'Restart the workers' },
80
+ ],
81
+ };
82
+
83
+ const BOUNDED: TaskListCardData = {
84
+ heading: 'Pick up to 2 reviewers',
85
+ confirmLabel: 'Request review',
86
+ min: 1,
87
+ max: 2,
88
+ tasks: [
89
+ { id: 'ana', label: 'Ana' },
90
+ { id: 'ben', label: 'Ben' },
91
+ { id: 'cat', label: 'Cat' },
92
+ { id: 'dan', label: 'Dan' },
93
+ ],
94
+ };
95
+
96
+ const WITH_DESCRIPTIONS: TaskListCardData = {
97
+ selectAll: true,
98
+ confirmLabel: 'Run cleanup',
99
+ tasks: [
100
+ { id: 'tmp', label: 'Delete temp files', description: 'Frees ~2.1 GB; safe to remove' },
101
+ { id: 'logs', label: 'Rotate logs', description: 'Archives logs older than 30 days' },
102
+ { id: 'orphans', label: 'Prune orphaned blobs', description: 'Unreferenced uploads only' },
103
+ ],
104
+ };
105
+
106
+ const HEADING_MAP: Record<string, string | undefined> = {
107
+ 'card-plan': 'Approve the plan steps',
108
+ 'card-require': 'Choose maintenance steps',
109
+ 'card-bounded': undefined,
110
+ 'card-desc': 'Storage cleanup',
111
+ };
112
+
113
+ const HTML_SNIPPET = (def: TaskListCardData, cardId: string) => {
114
+ const heading = HEADING_MAP[cardId];
115
+ return `<kc-task-list${heading ? ` heading="${heading}"` : ''}></kc-task-list>
116
+ <script type="module">
117
+ import '@kitnai/chat/elements'; // registers the custom elements
118
+
119
+ const el = document.querySelector('kc-task-list');
120
+ // \`data\` is the CardEnvelope.data (set as a property).
121
+ el.data = ${JSON.stringify(def, null, 2)};
122
+
123
+ // Toggling rows is local; only CONFIRM emits — a single bubbling \`kc-card\` event.
124
+ el.addEventListener('kc-card', (e) => {
125
+ const ev = e.detail; // { kind:'submit-data', cardId, data:{ selected } } | ...
126
+ if (ev.kind === 'submit-data') console.log('selected', ev.data.selected);
127
+ });
128
+ </script>`;
129
+ };
130
+
131
+ const meta = {
132
+ title: 'Generative UI/Cards/kc-task-list',
133
+ tags: ['autodocs'],
134
+ argTypes: argTypesFor('kc-task-list'),
135
+ parameters: {
136
+ layout: 'padded',
137
+ docs: {
138
+ description: specDescription('kc-task-list', [
139
+ "`<kc-task-list>` is a **selectable** task/plan list (set via the `data` **property**): checkbox rows + an optional select-all + a confirm button. The user picks a subset, confirms, and the card emits the Card contract's **`submit-data`** verb up a bubbling **`kc-card`** CustomEvent of `{ kind: 'submit-data', cardId, data: { selected } }` — the checked ids in **input order**. Toggling rows is local UI state; **only the final confirm emits** (the wire stays quiet, the result atomic).",
140
+ '**Gating:** confirm is enabled when `selectedCount >= (min ?? (allowEmpty ? 0 : 1))` and `<= (max ?? ∞)`. Select-all checks every toggleable (non-`disabled`) row and shows an **indeterminate** (`aria-checked="mixed"`) state when only some are checked. When `max` is reached, unchecked rows become non-toggleable. v1 is **select/approve only** (a future `mode:\'progress\'` is reserved in the schema).',
141
+ '**Events** (all frozen Card-contract verbs): `ready` on mount, `submit-data` on confirm, `error` for a malformed definition (renders the inline `kc-card` error). It **never invents events**. The same shapes flow over the remote iframe transport unchanged.',
142
+ ]),
143
+ },
144
+ },
145
+ } satisfies Meta;
146
+
147
+ export default meta;
148
+ type Story = StoryObj;
149
+
150
+ /** Select a plan (select-all on, a few pre-checked). */
151
+ export const SelectAPlan: Story = {
152
+ render: () => <TaskListDemo def={PLAN} cardId="card-plan" heading="Approve the plan steps" />,
153
+ parameters: { docs: { source: { code: HTML_SNIPPET(PLAN, 'card-plan'), language: 'html' } } },
154
+ };
155
+
156
+ /** Require at least one (`allowEmpty:false`, the default) — confirm stays disabled until a row is checked. */
157
+ export const RequireAtLeastOne: Story = {
158
+ render: () => <TaskListDemo def={REQUIRE_ONE} cardId="card-require" heading="Choose maintenance steps" />,
159
+ parameters: { docs: { source: { code: HTML_SNIPPET(REQUIRE_ONE, 'card-require'), language: 'html' } } },
160
+ };
161
+
162
+ /** Bounded (`min`/`max`) — confirm gating + max-reached row disabling. */
163
+ export const Bounded: Story = {
164
+ render: () => <TaskListDemo def={BOUNDED} cardId="card-bounded" />,
165
+ parameters: { docs: { source: { code: HTML_SNIPPET(BOUNDED, 'card-bounded'), language: 'html' } } },
166
+ };
167
+
168
+ /** Rows with secondary descriptions (linked via aria-describedby). */
169
+ export const WithDescriptions: Story = {
170
+ render: () => <TaskListDemo def={WITH_DESCRIPTIONS} cardId="card-desc" heading="Storage cleanup" />,
171
+ parameters: { docs: { source: { code: HTML_SNIPPET(WITH_DESCRIPTIONS, 'card-desc'), language: 'html' } } },
172
+ };
173
+
174
+ /** A malformed `data` (empty `tasks`) → the inline error state + an `error` event. */
175
+ export const ErrorState: Story = {
176
+ render: () => <TaskListDemo def={{ tasks: [] } as unknown as TaskListCardData} cardId="card-bad" />,
177
+ parameters: {
178
+ docs: {
179
+ source: {
180
+ code: `<kc-task-list></kc-task-list>
181
+ <script type="module">
182
+ import '@kitnai/chat/elements';
183
+ const el = document.querySelector('kc-task-list');
184
+ // No tasks → inline error state + an \`error\` event.
185
+ el.data = { tasks: [] };
186
+ el.addEventListener('kc-card', (e) => {
187
+ if (e.detail.kind === 'error') console.warn('task-list error:', e.detail.message);
188
+ });
189
+ </script>`,
190
+ language: 'html',
191
+ },
192
+ },
193
+ },
194
+ };
@@ -0,0 +1,40 @@
1
+ import { defineWebComponent } from './define';
2
+ import { TaskListCard, type TaskListCardData } from '../components/task-list-card';
3
+
4
+ interface Props extends Record<string, unknown> {
5
+ /** The task-list definition (the CardEnvelope.data). Set as a JS PROPERTY:
6
+ * `el.data = { tasks:[…], selectAll, confirmLabel, … }`. Import
7
+ * `TaskListCardData` from `@kitnai/chat` for the full shape. */
8
+ data?: Record<string, unknown>;
9
+ /** Stable card id correlating every emitted CardEvent. Attribute: `card-id`. */
10
+ cardId?: string;
11
+ /** Heading rendered in the card chrome (= CardEnvelope.title). Attribute: `heading`. */
12
+ heading?: string;
13
+ }
14
+
15
+ /**
16
+ * `<kc-task-list>` — a **selectable** task/plan list (set via the `data` property):
17
+ * checkbox rows + an optional select-all + a confirm button. Toggling rows is local
18
+ * UI state; only the final confirm emits the Card contract's **`submit-data`** verb
19
+ * up a bubbling **`kc-card`** CustomEvent (`{ kind:'submit-data', cardId,
20
+ * data:{ selected } }`) with the checked ids in input order. Also emits `ready` on
21
+ * mount and `error` for a malformed definition (inline error). v1 = select/approve
22
+ * mode only. Routes through a `CardProvider` when present, else the bubbling
23
+ * `kc-card` event. Isolated in Shadow DOM; theme-aware via the shared kit tokens.
24
+ */
25
+ defineWebComponent<Props>(
26
+ 'kc-task-list',
27
+ {
28
+ data: undefined,
29
+ cardId: undefined,
30
+ heading: undefined,
31
+ },
32
+ (props, { element }) => (
33
+ <TaskListCard
34
+ data={props.data as TaskListCardData | undefined}
35
+ cardId={props.cardId ?? (element.id || 'kc-task-list')}
36
+ heading={props.heading}
37
+ hostElement={element}
38
+ />
39
+ ),
40
+ );
@@ -1,14 +1,13 @@
1
1
  import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
2
  import './register'; // side effect: registers the custom elements
3
- import { ElementSpec } from '../stories/docs/element-spec';
4
- import { argTypesFor } from '../stories/docs/element-controls';
3
+ import { argTypesFor, specDescription } from '../stories/docs/element-controls';
5
4
 
6
5
  // The web components are custom DOM elements, so declare the tags for JSX.
7
6
  declare module 'solid-js' {
8
7
  // eslint-disable-next-line @typescript-eslint/no-namespace
9
8
  namespace JSX {
10
9
  interface IntrinsicElements {
11
- 'kitn-text-shimmer': JSX.HTMLAttributes<HTMLElement> & {
10
+ 'kc-text-shimmer': JSX.HTMLAttributes<HTMLElement> & {
12
11
  text?: string;
13
12
  duration?: number;
14
13
  spread?: number;
@@ -18,27 +17,25 @@ declare module 'solid-js' {
18
17
  }
19
18
 
20
19
  const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
21
- <kitn-text-shimmer text="Thinking…" duration="3" spread="20"></kitn-text-shimmer>
20
+ <kc-text-shimmer text="Thinking…" duration="3" spread="20"></kc-text-shimmer>
22
21
 
23
22
  <script type="module">
24
23
  import '@kitnai/chat/elements'; // registers the custom elements
25
24
  </script>`;
26
25
 
27
26
  const meta = {
28
- title: 'Web Components/kitn-text-shimmer',
27
+ title: 'Web Components/kc-text-shimmer',
29
28
  tags: ['autodocs'],
30
- argTypes: argTypesFor('kitn-text-shimmer'),
29
+ argTypes: argTypesFor('kc-text-shimmer'),
31
30
  parameters: {
32
31
  layout: 'fullscreen',
33
32
  docs: {
34
- description: {
35
- component: [
36
- '`<kitn-text-shimmer>` is the framework-agnostic **web component** for animated shimmering text — a gradient sweep across a label, isolated in **Shadow DOM**.',
33
+ description: specDescription('kc-text-shimmer', [
34
+ '`<kc-text-shimmer>` is the framework-agnostic **web component** for animated shimmering text — a gradient sweep across a label, isolated in **Shadow DOM**.',
37
35
  '**When to use:** signalling a quiet, in-progress state ("Thinking…", "Generating…") inline. In SolidJS, use the `TextShimmer` primitive.',
38
36
  "**How to use:** register once with `import '@kitnai/chat/elements'`, set the `text` attribute, and tune `duration` (seconds) and `spread` (gradient width, 5–45).",
39
37
  'See the **Code** tab for HTML usage.',
40
- ].join('\n\n'),
41
- },
38
+ ]),
42
39
  },
43
40
  },
44
41
  } satisfies Meta;
@@ -46,17 +43,11 @@ const meta = {
46
43
  export default meta;
47
44
  type Story = StoryObj;
48
45
 
49
- /** Full generated API reference — properties, events, tokens, and composed-from. */
50
- export const API: Story = {
51
- render: () => <ElementSpec tag="kitn-text-shimmer" />,
52
- parameters: { layout: 'padded' },
53
- };
54
-
55
46
  /** Default shimmer. */
56
47
  export const Default: Story = {
57
48
  render: () => (
58
49
  <div style={{ padding: '24px', 'font-size': '18px' }}>
59
- <kitn-text-shimmer text="Thinking…" />
50
+ <kc-text-shimmer text="Thinking…" />
60
51
  </div>
61
52
  ),
62
53
  parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
@@ -66,7 +57,7 @@ export const Default: Story = {
66
57
  export const Tuned: Story = {
67
58
  render: () => (
68
59
  <div style={{ padding: '24px', 'font-size': '18px' }}>
69
- <kitn-text-shimmer text="Generating response…" duration={2} spread={35} />
60
+ <kc-text-shimmer text="Generating response…" duration={2} spread={35} />
70
61
  </div>
71
62
  ),
72
63
  };
@@ -1,4 +1,4 @@
1
- import { defineKitnElement } from './define';
1
+ import { defineWebComponent } from './define';
2
2
  import { TextShimmer } from '../components/text-shimmer';
3
3
 
4
4
  interface Props extends Record<string, unknown> {
@@ -13,10 +13,10 @@ interface Props extends Record<string, unknown> {
13
13
  }
14
14
 
15
15
  /**
16
- * `<kitn-text-shimmer>` — animated shimmering text. Text via the `text`
16
+ * `<kc-text-shimmer>` — animated shimmering text. Text via the `text`
17
17
  * attribute; `duration`/`spread` tune the effect.
18
18
  */
19
- defineKitnElement<Props>('kitn-text-shimmer', {
19
+ defineWebComponent<Props>('kc-text-shimmer', {
20
20
  text: '',
21
21
  as: 'span',
22
22
  duration: 4,
@@ -1,14 +1,13 @@
1
1
  import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
2
  import './register'; // side effect: registers the custom elements
3
- import { ElementSpec } from '../stories/docs/element-spec';
4
- import { argTypesFor } from '../stories/docs/element-controls';
3
+ import { argTypesFor, specDescription } from '../stories/docs/element-controls';
5
4
 
6
5
  // The web components are custom DOM elements, so declare the tags for JSX.
7
6
  declare module 'solid-js' {
8
7
  // eslint-disable-next-line @typescript-eslint/no-namespace
9
8
  namespace JSX {
10
9
  interface IntrinsicElements {
11
- 'kitn-thinking-bar': JSX.HTMLAttributes<HTMLElement> & {
10
+ 'kc-thinking-bar': JSX.HTMLAttributes<HTMLElement> & {
12
11
  text?: string;
13
12
  stoppable?: boolean | string;
14
13
  'stop-label'?: string;
@@ -19,30 +18,28 @@ declare module 'solid-js' {
19
18
  }
20
19
 
21
20
  const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
22
- <kitn-thinking-bar text="Thinking" stoppable stop-label="Answer now"></kitn-thinking-bar>
21
+ <kc-thinking-bar text="Thinking" stoppable stop-label="Answer now"></kc-thinking-bar>
23
22
 
24
23
  <script type="module">
25
24
  import '@kitnai/chat/elements'; // registers the custom elements
26
25
 
27
- document.querySelector('kitn-thinking-bar')
26
+ document.querySelector('kc-thinking-bar')
28
27
  .addEventListener('stop', () => console.log('user asked to stop'));
29
28
  </script>`;
30
29
 
31
30
  const meta = {
32
- title: 'Web Components/kitn-thinking-bar',
31
+ title: 'Web Components/kc-thinking-bar',
33
32
  tags: ['autodocs'],
34
- argTypes: argTypesFor('kitn-thinking-bar'),
33
+ argTypes: argTypesFor('kc-thinking-bar'),
35
34
  parameters: {
36
35
  layout: 'fullscreen',
37
36
  docs: {
38
- description: {
39
- component: [
40
- '`<kitn-thinking-bar>` is the framework-agnostic **web component** for an animated "thinking" indicator with an optional stop affordance — a pure leaf element isolated in **Shadow DOM**. (`<kitn-chat>` does not surface this; compose it yourself.)',
37
+ description: specDescription('kc-thinking-bar', [
38
+ '`<kc-thinking-bar>` is the framework-agnostic **web component** for an animated "thinking" indicator with an optional stop affordance — a pure leaf element isolated in **Shadow DOM**. (`<kc-chat>` does not surface this; compose it yourself.)',
41
39
  '**When to use:** showing that the assistant is reasoning, optionally letting the user interrupt with "Answer now". In SolidJS, use the `ThinkingBar` primitive.',
42
40
  "**How to use:** register once with `import '@kitnai/chat/elements'`, set the `text`/`stop-label` attributes, add the `stoppable` flag to show the stop button, and listen for the `stop` **CustomEvent**.",
43
41
  'See the **Code** tab for HTML usage.',
44
- ].join('\n\n'),
45
- },
42
+ ]),
46
43
  },
47
44
  },
48
45
  } satisfies Meta;
@@ -50,17 +47,11 @@ const meta = {
50
47
  export default meta;
51
48
  type Story = StoryObj;
52
49
 
53
- /** Full generated API reference — properties, events, tokens, and composed-from. */
54
- export const API: Story = {
55
- render: () => <ElementSpec tag="kitn-thinking-bar" />,
56
- parameters: { layout: 'padded' },
57
- };
58
-
59
50
  /** A plain thinking indicator. */
60
51
  export const Default: Story = {
61
52
  render: () => (
62
53
  <div style={{ padding: '24px' }}>
63
- <kitn-thinking-bar text="Thinking" />
54
+ <kc-thinking-bar text="Thinking" />
64
55
  </div>
65
56
  ),
66
57
  parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
@@ -70,7 +61,7 @@ export const Default: Story = {
70
61
  export const Stoppable: Story = {
71
62
  render: () => (
72
63
  <div style={{ padding: '24px' }}>
73
- <kitn-thinking-bar
64
+ <kc-thinking-bar
74
65
  text="Reasoning"
75
66
  stoppable
76
67
  stop-label="Answer now"
@@ -1,4 +1,4 @@
1
- import { defineKitnElement } from './define';
1
+ import { defineWebComponent } from './define';
2
2
  import { ThinkingBar } from '../components/thinking-bar';
3
3
 
4
4
  interface Props extends Record<string, unknown> {
@@ -10,18 +10,18 @@ interface Props extends Record<string, unknown> {
10
10
  stopLabel?: string;
11
11
  }
12
12
 
13
- /** Events fired by `<kitn-thinking-bar>`. */
13
+ /** Events fired by `<kc-thinking-bar>`. */
14
14
  interface Events {
15
15
  /** The "stop / answer now" affordance was clicked. */
16
16
  stop: void;
17
17
  }
18
18
 
19
19
  /**
20
- * `<kitn-thinking-bar>` — a pure leaf element: an animated "thinking" indicator
21
- * (one of the primitives the batteries-included `<kitn-chat>` does NOT surface).
20
+ * `<kc-thinking-bar>` — a pure leaf element: an animated "thinking" indicator
21
+ * (one of the primitives the batteries-included `<kc-chat>` does NOT surface).
22
22
  * Config via attributes, the only interaction (`stop`) comes back as an event.
23
23
  */
24
- defineKitnElement<Props, Events>('kitn-thinking-bar', {
24
+ defineWebComponent<Props, Events>('kc-thinking-bar', {
25
25
  text: 'Thinking',
26
26
  stoppable: false,
27
27
  stopLabel: 'Answer now',