@kitn.ai/ui 0.14.1

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 (370) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +394 -0
  3. package/dist/bash-InADTalH.js +1 -0
  4. package/dist/core-AYMC6_lb.js +12 -0
  5. package/dist/css-M7EaDHN_.js +1 -0
  6. package/dist/custom-elements.json +4739 -0
  7. package/dist/engine-javascript-vq0WuIJl.js +141 -0
  8. package/dist/github-dark-dimmed-DUshB20C.js +1 -0
  9. package/dist/github-light-JYsPkUQd.js +1 -0
  10. package/dist/host-embed.d.ts +33 -0
  11. package/dist/html-CPZ3oZQ7.js +1 -0
  12. package/dist/index.d.ts +2 -0
  13. package/dist/javascript-C25yR2R2.js +1 -0
  14. package/dist/json-DxJze_jm.js +1 -0
  15. package/dist/kitn-chat-provider.d.ts +2 -0
  16. package/dist/kitn-chat-provider.es.js +240 -0
  17. package/dist/kitn-chat.es.js +77 -0
  18. package/dist/llms/llms-full.txt +1018 -0
  19. package/dist/llms/llms.txt +104 -0
  20. package/dist/origin.d.ts +5 -0
  21. package/dist/provider-runtime.d.ts +27 -0
  22. package/dist/schemas/card-envelope.schema.json +14 -0
  23. package/dist/schemas/card-event.schema.json +12 -0
  24. package/dist/schemas/choice.schema.json +70 -0
  25. package/dist/schemas/confirm.schema.json +65 -0
  26. package/dist/schemas/embed.schema.json +65 -0
  27. package/dist/schemas/form.result.schema.json +7 -0
  28. package/dist/schemas/form.schema.json +33 -0
  29. package/dist/schemas/link.schema.json +56 -0
  30. package/dist/schemas/tasks.result.schema.json +16 -0
  31. package/dist/schemas/tasks.schema.json +78 -0
  32. package/dist/svelte--5p79yCD.js +1 -0
  33. package/dist/theme.tokens.css +176 -0
  34. package/dist/tsx-B8rCNbgL.js +1 -0
  35. package/dist/typescript-RycA9KXf.js +1 -0
  36. package/dist/validate.d.ts +4 -0
  37. package/dist/version.d.ts +4 -0
  38. package/dist/vue-BmIZj4XD.js +1 -0
  39. package/dist/wire.d.ts +53 -0
  40. package/frameworks/react/index.tsx +948 -0
  41. package/frameworks/react/runtime.tsx +94 -0
  42. package/llms-full.txt +1018 -0
  43. package/llms.txt +104 -0
  44. package/package.json +141 -0
  45. package/src/components/artifact.stories.tsx +138 -0
  46. package/src/components/artifact.tsx +743 -0
  47. package/src/components/attachments.stories.tsx +303 -0
  48. package/src/components/attachments.test.tsx +64 -0
  49. package/src/components/attachments.tsx +399 -0
  50. package/src/components/card-fallback.tsx +28 -0
  51. package/src/components/card-renderer.tsx +52 -0
  52. package/src/components/card.tsx +110 -0
  53. package/src/components/chain-of-thought.stories.tsx +211 -0
  54. package/src/components/chain-of-thought.tsx +139 -0
  55. package/src/components/chat-container.stories.tsx +187 -0
  56. package/src/components/chat-container.tsx +82 -0
  57. package/src/components/chat-scope-picker.tsx +47 -0
  58. package/src/components/chat-thread.test.tsx +58 -0
  59. package/src/components/chat-thread.tsx +249 -0
  60. package/src/components/checkpoint.stories.tsx +102 -0
  61. package/src/components/checkpoint.tsx +88 -0
  62. package/src/components/choice-card.tsx +569 -0
  63. package/src/components/code-block.stories.tsx +150 -0
  64. package/src/components/code-block.tsx +105 -0
  65. package/src/components/component-meta.json +3735 -0
  66. package/src/components/confirm-card.stories.tsx +74 -0
  67. package/src/components/confirm-card.tsx +315 -0
  68. package/src/components/context.stories.tsx +229 -0
  69. package/src/components/context.test.tsx +133 -0
  70. package/src/components/context.tsx +363 -0
  71. package/src/components/conversation-item.stories.tsx +125 -0
  72. package/src/components/conversation-item.tsx +16 -0
  73. package/src/components/conversation-list.stories.tsx +133 -0
  74. package/src/components/conversation-list.tsx +101 -0
  75. package/src/components/default-input.test.tsx +111 -0
  76. package/src/components/embed.tsx +196 -0
  77. package/src/components/empty.stories.tsx +434 -0
  78. package/src/components/empty.tsx +166 -0
  79. package/src/components/feedback-bar.stories.tsx +138 -0
  80. package/src/components/feedback-bar.test.tsx +103 -0
  81. package/src/components/feedback-bar.tsx +166 -0
  82. package/src/components/file-tree.stories.tsx +73 -0
  83. package/src/components/file-tree.tsx +383 -0
  84. package/src/components/file-upload.stories.tsx +156 -0
  85. package/src/components/file-upload.tsx +161 -0
  86. package/src/components/form-widgets.tsx +461 -0
  87. package/src/components/form.tsx +873 -0
  88. package/src/components/image.stories.tsx +89 -0
  89. package/src/components/image.tsx +67 -0
  90. package/src/components/link-preview.tsx +194 -0
  91. package/src/components/loader.stories.tsx +181 -0
  92. package/src/components/loader.tsx +333 -0
  93. package/src/components/markdown.stories.tsx +180 -0
  94. package/src/components/markdown.tsx +81 -0
  95. package/src/components/message-action-bar.test.tsx +128 -0
  96. package/src/components/message-narrow.stories.tsx +329 -0
  97. package/src/components/message-skills.stories.tsx +211 -0
  98. package/src/components/message-skills.tsx +36 -0
  99. package/src/components/message.stories.tsx +281 -0
  100. package/src/components/message.tsx +227 -0
  101. package/src/components/model-switcher.stories.tsx +123 -0
  102. package/src/components/model-switcher.test.tsx +65 -0
  103. package/src/components/model-switcher.tsx +87 -0
  104. package/src/components/prompt-input.stories.tsx +222 -0
  105. package/src/components/prompt-input.tsx +211 -0
  106. package/src/components/prompt-suggestion.stories.tsx +142 -0
  107. package/src/components/prompt-suggestion.tsx +115 -0
  108. package/src/components/reasoning.stories.tsx +140 -0
  109. package/src/components/reasoning.tsx +156 -0
  110. package/src/components/resizable.test.tsx +226 -0
  111. package/src/components/response-stream.tsx +103 -0
  112. package/src/components/scroll-button.stories.tsx +100 -0
  113. package/src/components/scroll-button.tsx +34 -0
  114. package/src/components/slash-command.stories.tsx +163 -0
  115. package/src/components/slash-command.tsx +232 -0
  116. package/src/components/source-list.test.tsx +152 -0
  117. package/src/components/source.stories.tsx +124 -0
  118. package/src/components/source.tsx +129 -0
  119. package/src/components/tasks-card.stories.tsx +78 -0
  120. package/src/components/tasks-card.tsx +432 -0
  121. package/src/components/text-shimmer.stories.tsx +87 -0
  122. package/src/components/text-shimmer.tsx +37 -0
  123. package/src/components/thinking-bar.stories.tsx +87 -0
  124. package/src/components/thinking-bar.tsx +50 -0
  125. package/src/components/tool.stories.tsx +153 -0
  126. package/src/components/tool.tsx +184 -0
  127. package/src/components/use-card-resolution.ts +28 -0
  128. package/src/components/voice-input.stories.tsx +83 -0
  129. package/src/components/voice-input.tsx +107 -0
  130. package/src/elements/artifact.stories.tsx +506 -0
  131. package/src/elements/artifact.tsx +137 -0
  132. package/src/elements/attachments.stories.tsx +190 -0
  133. package/src/elements/attachments.tsx +136 -0
  134. package/src/elements/card.stories.tsx +113 -0
  135. package/src/elements/card.tsx +40 -0
  136. package/src/elements/cards.stories.tsx +59 -0
  137. package/src/elements/cards.tsx +97 -0
  138. package/src/elements/catalog.stories.tsx +491 -0
  139. package/src/elements/chain-of-thought.declarative.test.tsx +153 -0
  140. package/src/elements/chain-of-thought.stories.tsx +113 -0
  141. package/src/elements/chain-of-thought.tsx +92 -0
  142. package/src/elements/chat-scope-picker.stories.tsx +73 -0
  143. package/src/elements/chat-scope-picker.tsx +36 -0
  144. package/src/elements/chat-types.ts +42 -0
  145. package/src/elements/chat-workspace.stories.tsx +288 -0
  146. package/src/elements/chat-workspace.tsx +161 -0
  147. package/src/elements/chat.stories.tsx +151 -0
  148. package/src/elements/chat.tsx +80 -0
  149. package/src/elements/checkpoint.stories.tsx +72 -0
  150. package/src/elements/checkpoint.tsx +47 -0
  151. package/src/elements/choice.stories.tsx +240 -0
  152. package/src/elements/choice.tsx +48 -0
  153. package/src/elements/code-block.stories.tsx +83 -0
  154. package/src/elements/code-block.tsx +42 -0
  155. package/src/elements/compiled.css +2 -0
  156. package/src/elements/composed-shell.stories.tsx +316 -0
  157. package/src/elements/confirm-card.stories.tsx +227 -0
  158. package/src/elements/confirm-card.tsx +51 -0
  159. package/src/elements/context-meter.stories.tsx +151 -0
  160. package/src/elements/context-meter.tsx +121 -0
  161. package/src/elements/conversation-list.declarative.test.tsx +137 -0
  162. package/src/elements/conversation-list.stories.tsx +243 -0
  163. package/src/elements/conversation-list.tsx +80 -0
  164. package/src/elements/css.ts +5 -0
  165. package/src/elements/default-input.tsx +247 -0
  166. package/src/elements/define.tsx +171 -0
  167. package/src/elements/element-meta.json +3314 -0
  168. package/src/elements/element-types.d.ts +710 -0
  169. package/src/elements/embed.stories.tsx +213 -0
  170. package/src/elements/embed.tsx +35 -0
  171. package/src/elements/empty.stories.tsx +111 -0
  172. package/src/elements/empty.tsx +29 -0
  173. package/src/elements/feedback-bar.stories.tsx +114 -0
  174. package/src/elements/feedback-bar.tsx +61 -0
  175. package/src/elements/file-tree.stories.tsx +134 -0
  176. package/src/elements/file-tree.tsx +52 -0
  177. package/src/elements/file-upload.stories.tsx +82 -0
  178. package/src/elements/file-upload.tsx +44 -0
  179. package/src/elements/form.stories.tsx +248 -0
  180. package/src/elements/form.tsx +43 -0
  181. package/src/elements/framework-usage.json +563 -0
  182. package/src/elements/image.stories.tsx +71 -0
  183. package/src/elements/image.tsx +32 -0
  184. package/src/elements/link-preview.stories.tsx +209 -0
  185. package/src/elements/link-preview.tsx +34 -0
  186. package/src/elements/loader.stories.tsx +88 -0
  187. package/src/elements/loader.tsx +25 -0
  188. package/src/elements/markdown.stories.tsx +76 -0
  189. package/src/elements/markdown.tsx +38 -0
  190. package/src/elements/message-skills.declarative.test.tsx +104 -0
  191. package/src/elements/message-skills.stories.tsx +114 -0
  192. package/src/elements/message-skills.tsx +85 -0
  193. package/src/elements/message.stories.tsx +218 -0
  194. package/src/elements/message.tsx +162 -0
  195. package/src/elements/model-switcher.declarative.test.tsx +130 -0
  196. package/src/elements/model-switcher.stories.tsx +198 -0
  197. package/src/elements/model-switcher.tsx +93 -0
  198. package/src/elements/popover.stories.tsx +80 -0
  199. package/src/elements/popover.tsx +59 -0
  200. package/src/elements/prompt-input-slash-command.test.tsx +128 -0
  201. package/src/elements/prompt-input-stoppable.test.tsx +88 -0
  202. package/src/elements/prompt-input-toolbar-actions.test.tsx +120 -0
  203. package/src/elements/prompt-input.stories.tsx +331 -0
  204. package/src/elements/prompt-input.tsx +185 -0
  205. package/src/elements/prompt-suggestions.declarative.test.tsx +164 -0
  206. package/src/elements/prompt-suggestions.stories.tsx +213 -0
  207. package/src/elements/prompt-suggestions.tsx +94 -0
  208. package/src/elements/reasoning.stories.tsx +77 -0
  209. package/src/elements/reasoning.tsx +50 -0
  210. package/src/elements/register.ts +56 -0
  211. package/src/elements/remote.stories.tsx +314 -0
  212. package/src/elements/remote.tsx +221 -0
  213. package/src/elements/resizable.d.ts +27 -0
  214. package/src/elements/resizable.stories.tsx +426 -0
  215. package/src/elements/resizable.tsx +553 -0
  216. package/src/elements/response-stream.stories.tsx +79 -0
  217. package/src/elements/response-stream.tsx +40 -0
  218. package/src/elements/scroll-button.stories.tsx +202 -0
  219. package/src/elements/scroll-button.test.tsx +148 -0
  220. package/src/elements/scroll-button.tsx +126 -0
  221. package/src/elements/source-list.stories.tsx +143 -0
  222. package/src/elements/source.stories.tsx +88 -0
  223. package/src/elements/source.tsx +120 -0
  224. package/src/elements/styles.css +199 -0
  225. package/src/elements/switch.stories.tsx +56 -0
  226. package/src/elements/switch.tsx +43 -0
  227. package/src/elements/tasks.stories.tsx +237 -0
  228. package/src/elements/tasks.tsx +46 -0
  229. package/src/elements/text-shimmer.stories.tsx +64 -0
  230. package/src/elements/text-shimmer.tsx +28 -0
  231. package/src/elements/thinking-bar.stories.tsx +72 -0
  232. package/src/elements/thinking-bar.tsx +34 -0
  233. package/src/elements/tool.stories.tsx +89 -0
  234. package/src/elements/tool.tsx +23 -0
  235. package/src/elements/voice-input.stories.tsx +88 -0
  236. package/src/elements/voice-input.tsx +41 -0
  237. package/src/index.ts +253 -0
  238. package/src/primitives/card-contract.ts +71 -0
  239. package/src/primitives/card-host.tsx +35 -0
  240. package/src/primitives/card-registry.tsx +67 -0
  241. package/src/primitives/card-resolution.ts +36 -0
  242. package/src/primitives/card-routing.ts +79 -0
  243. package/src/primitives/card-schemas/card-envelope.schema.json +14 -0
  244. package/src/primitives/card-schemas/card-event.schema.json +12 -0
  245. package/src/primitives/card-schemas/choice.schema.json +70 -0
  246. package/src/primitives/card-schemas/confirm.schema.json +65 -0
  247. package/src/primitives/card-schemas/embed.schema.json +65 -0
  248. package/src/primitives/card-schemas/form.result.schema.json +7 -0
  249. package/src/primitives/card-schemas/form.schema.json +33 -0
  250. package/src/primitives/card-schemas/link.schema.json +56 -0
  251. package/src/primitives/card-schemas/tasks.result.schema.json +16 -0
  252. package/src/primitives/card-schemas/tasks.schema.json +78 -0
  253. package/src/primitives/card-validate.ts +95 -0
  254. package/src/primitives/chat-config.tsx +76 -0
  255. package/src/primitives/controllable.test.ts +47 -0
  256. package/src/primitives/controllable.ts +21 -0
  257. package/src/primitives/embed-providers.ts +254 -0
  258. package/src/primitives/highlighter.ts +157 -0
  259. package/src/primitives/link-preview.ts +87 -0
  260. package/src/primitives/pdf-preview.ts +121 -0
  261. package/src/primitives/use-auto-resize.ts +31 -0
  262. package/src/primitives/use-resize-observer.ts +12 -0
  263. package/src/primitives/use-stick-to-bottom.ts +43 -0
  264. package/src/primitives/use-text-stream.ts +112 -0
  265. package/src/primitives/use-voice-recorder.ts +50 -0
  266. package/src/remote/host-embed.ts +345 -0
  267. package/src/remote/index.ts +2 -0
  268. package/src/remote/origin.ts +30 -0
  269. package/src/remote/provider-runtime.ts +259 -0
  270. package/src/remote/provider.ts +2 -0
  271. package/src/remote/validate.ts +22 -0
  272. package/src/remote/version.ts +12 -0
  273. package/src/remote/wire.ts +48 -0
  274. package/src/stories/chat-panel-layout.stories.tsx +145 -0
  275. package/src/stories/chat-scene.tsx +571 -0
  276. package/src/stories/checkpoint-restore.stories.tsx +256 -0
  277. package/src/stories/context-usage.stories.tsx +212 -0
  278. package/src/stories/conversation-with-reasoning.stories.tsx +182 -0
  279. package/src/stories/conversation-with-sources.stories.tsx +471 -0
  280. package/src/stories/docs/Accessibility.mdx +119 -0
  281. package/src/stories/docs/ChildElements.mdx +269 -0
  282. package/src/stories/docs/ForAIAgents.mdx +162 -0
  283. package/src/stories/docs/GettingStarted.mdx +65 -0
  284. package/src/stories/docs/Installation.mdx +83 -0
  285. package/src/stories/docs/Introduction.mdx +50 -0
  286. package/src/stories/docs/SolidJsAdvanced.mdx +17 -0
  287. package/src/stories/docs/Theming.mdx +85 -0
  288. package/src/stories/docs/element-controls.ts +121 -0
  289. package/src/stories/docs/frameworks/Angular.mdx +245 -0
  290. package/src/stories/docs/frameworks/Html.mdx +227 -0
  291. package/src/stories/docs/frameworks/Overview.mdx +47 -0
  292. package/src/stories/docs/frameworks/React.mdx +227 -0
  293. package/src/stories/docs/frameworks/Solid.mdx +173 -0
  294. package/src/stories/docs/frameworks/Svelte.mdx +194 -0
  295. package/src/stories/docs/frameworks/Vue.mdx +238 -0
  296. package/src/stories/docs/generative-ui-overview.mdx +186 -0
  297. package/src/stories/docs/recipes/SpeechToText.mdx +7 -0
  298. package/src/stories/docs/recipes/Streaming.mdx +68 -0
  299. package/src/stories/docs/recipes/TextToSpeech.mdx +35 -0
  300. package/src/stories/docs/theme-editor/canvas.tsx +32 -0
  301. package/src/stories/docs/theme-editor/inspector.tsx +66 -0
  302. package/src/stories/docs/theme-editor/presets.test.ts +32 -0
  303. package/src/stories/docs/theme-editor/presets.ts +64 -0
  304. package/src/stories/docs/theme-editor/theme-css.test.ts +19 -0
  305. package/src/stories/docs/theme-editor/theme-css.ts +15 -0
  306. package/src/stories/docs/theme-editor/theme-editor.tsx +146 -0
  307. package/src/stories/docs/theme-tokens.tsx +174 -0
  308. package/src/stories/examples/ChoosingComponents.mdx +106 -0
  309. package/src/stories/examples/sample-data.ts +79 -0
  310. package/src/stories/examples/usage/checkpoint-restore.ts +156 -0
  311. package/src/stories/examples/usage/context-usage.ts +591 -0
  312. package/src/stories/examples/usage/conversation-with-reasoning.ts +224 -0
  313. package/src/stories/examples/usage/conversation-with-sources.ts +589 -0
  314. package/src/stories/examples/usage/empty-state.ts +144 -0
  315. package/src/stories/examples/usage/full-chat-app.ts +277 -0
  316. package/src/stories/examples/usage/index.ts +63 -0
  317. package/src/stories/examples/usage/message-actions.ts +704 -0
  318. package/src/stories/examples/usage/pattern-centered-conversation.ts +446 -0
  319. package/src/stories/examples/usage/pattern-chat-panel-layout.ts +442 -0
  320. package/src/stories/examples/usage/pattern-docked-widget.ts +479 -0
  321. package/src/stories/examples/usage/prompt-input-variants.ts +1356 -0
  322. package/src/stories/examples/usage/streaming-response.ts +687 -0
  323. package/src/stories/examples/usage/types.ts +33 -0
  324. package/src/stories/full-chat.stories.tsx +18 -0
  325. package/src/stories/message-actions.stories.tsx +230 -0
  326. package/src/stories/pattern-centered-conversation.stories.tsx +93 -0
  327. package/src/stories/pattern-docked-widget.stories.tsx +93 -0
  328. package/src/stories/pattern-empty-state.stories.tsx +76 -0
  329. package/src/stories/prompt-input-variants.stories.tsx +456 -0
  330. package/src/stories/streaming-response.stories.tsx +438 -0
  331. package/src/stories/theme-editor.stories.tsx +16 -0
  332. package/src/stories/token-reference.stories.tsx +18 -0
  333. package/src/stories/typography.stories.tsx +78 -0
  334. package/src/types.ts +47 -0
  335. package/src/ui/action-icons.ts +52 -0
  336. package/src/ui/avatar.stories.tsx +103 -0
  337. package/src/ui/avatar.tsx +23 -0
  338. package/src/ui/badge.stories.tsx +86 -0
  339. package/src/ui/badge.tsx +21 -0
  340. package/src/ui/button.stories.tsx +145 -0
  341. package/src/ui/button.tsx +38 -0
  342. package/src/ui/collapsible.stories.tsx +69 -0
  343. package/src/ui/collapsible.tsx +125 -0
  344. package/src/ui/dropdown.stories.tsx +59 -0
  345. package/src/ui/dropdown.tsx +196 -0
  346. package/src/ui/hover-card.stories.tsx +77 -0
  347. package/src/ui/hover-card.test.tsx +30 -0
  348. package/src/ui/hover-card.tsx +172 -0
  349. package/src/ui/overlay.stories.tsx +115 -0
  350. package/src/ui/overlay.tsx +158 -0
  351. package/src/ui/popover.stories.tsx +81 -0
  352. package/src/ui/popover.test.tsx +99 -0
  353. package/src/ui/popover.tsx +92 -0
  354. package/src/ui/resizable.stories.tsx +236 -0
  355. package/src/ui/resizable.tsx +576 -0
  356. package/src/ui/scroll-area.stories.tsx +50 -0
  357. package/src/ui/scroll-area.tsx +15 -0
  358. package/src/ui/separator.stories.tsx +81 -0
  359. package/src/ui/separator.tsx +10 -0
  360. package/src/ui/skeleton.stories.tsx +337 -0
  361. package/src/ui/skeleton.tsx +16 -0
  362. package/src/ui/switch.stories.tsx +51 -0
  363. package/src/ui/switch.test.tsx +59 -0
  364. package/src/ui/switch.tsx +62 -0
  365. package/src/ui/textarea.stories.tsx +76 -0
  366. package/src/ui/textarea.tsx +21 -0
  367. package/src/ui/tooltip.stories.tsx +74 -0
  368. package/src/ui/tooltip.tsx +68 -0
  369. package/src/utils/cn.ts +24 -0
  370. package/theme.css +187 -0
@@ -0,0 +1,256 @@
1
+ import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
+ import { createSignal } from 'solid-js';
3
+ import {
4
+ ChatContainer, ChatContainerContent, ChatContainerScrollAnchor,
5
+ Message, MessageAvatar, MessageContent, MessageActions,
6
+ PromptInput, PromptInputTextarea, PromptInputActions,
7
+ Checkpoint, CheckpointIcon, CheckpointTrigger,
8
+ Button, Separator,
9
+ } from '../index';
10
+ import { Copy, ThumbsUp, ThumbsDown, ArrowUp, RotateCcw } from 'lucide-solid';
11
+
12
+ const meta: Meta = {
13
+ title: 'Examples/Checkpoint & Restore',
14
+ parameters: {
15
+ layout: 'padded',
16
+ docs: {
17
+ description: {
18
+ component: [
19
+ 'Mark named restore points in a conversation. `<kai-checkpoint>` (or the SolidJS `Checkpoint` primitive) renders a bookmark button that fires `kai-select` (no detail payload) when clicked.',
20
+ '',
21
+ '**"Restoring" is your responsibility — the element just emits an event.** When `kai-select` fires, slice your message array back to the messages that existed at that checkpoint. The element has no internal state management.',
22
+ '',
23
+ '**Where checkpoints come from:** create a checkpoint entry in your message list after each AI response you want to mark as a save point. The `label` / `tooltip` props are plain strings passed to the element.',
24
+ '',
25
+ '**`<kai-checkpoint>` WC props:** `label` (optional visible text), `tooltip` (hover text), `variant` (`ghost` | `default` | `outline`, default `ghost`), `size` (`sm` | `md` | `lg` | `icon` | `icon-sm`, default `sm`). The icon is always the built-in `CheckpointIcon` — there is no `icon` prop on the WC (planned gap in the capability-gaps spec).',
26
+ '',
27
+ '**Custom icon in the trigger:** compose the SolidJS `Checkpoint` + `CheckpointIcon` + `CheckpointTrigger` primitives. The demo places a `<RotateCcw>` icon inside `CheckpointTrigger` — that works because `CheckpointTrigger` accepts arbitrary children.',
28
+ ].join('\n'),
29
+ },
30
+ },
31
+ },
32
+ };
33
+
34
+ export default meta;
35
+ type Story = StoryObj;
36
+
37
+ export const Default: Story = {
38
+ name: 'API Design Session with Restore Points',
39
+ parameters: {
40
+ docs: {
41
+ description: {
42
+ story: [
43
+ 'Three-exchange API design session with two checkpoints. Clicking a checkpoint updates the header label — in a real app you would also slice your message array back to the messages that existed at that point.',
44
+ '',
45
+ '**`kai-select` carries no detail payload** — you identify which checkpoint was clicked by which handler you wired up, not from the event itself. Confirmed in `src/elements/checkpoint.tsx`.',
46
+ '',
47
+ '**The `<RotateCcw>` icon in the trigger is a SolidJS-only touch** — the `<kai-checkpoint>` WC always uses the built-in `CheckpointIcon` (no `icon` prop). Compose `CheckpointTrigger` with custom children only when using the SolidJS primitives.',
48
+ ].join('\n'),
49
+ },
50
+ },
51
+ },
52
+ render: () => {
53
+ const [restoredTo, setRestoredTo] = createSignal<string | null>(null);
54
+
55
+ return (
56
+ <div class="flex flex-col h-[700px] w-full max-w-2xl bg-background rounded-xl shadow-lg overflow-hidden">
57
+ <div class="flex items-center justify-between px-4 py-3">
58
+ <h2 class="text-sm font-semibold text-foreground">API Design Session</h2>
59
+ {restoredTo() && (
60
+ <span class="text-xs text-muted-foreground bg-muted px-2 py-1 rounded-md">
61
+ Restored to checkpoint: {restoredTo()}
62
+ </span>
63
+ )}
64
+ </div>
65
+ <Separator />
66
+
67
+ <ChatContainer class="flex-1 p-4">
68
+ <ChatContainerContent class="space-y-6 py-4">
69
+
70
+ {/* Initial exchange */}
71
+ <Message>
72
+ <MessageAvatar src="" fallback="U" alt="User" />
73
+ <MessageContent>
74
+ I'm designing a REST API for a task management app. What should the core endpoints look like?
75
+ </MessageContent>
76
+ </Message>
77
+
78
+ <Message>
79
+ <MessageAvatar src="" fallback="AI" alt="Assistant" />
80
+ <div class="flex-1 space-y-2">
81
+ <MessageContent markdown>
82
+ {`Here's a clean REST resource design for task management:
83
+
84
+ \`\`\`
85
+ GET /api/tasks # List tasks (with filters)
86
+ POST /api/tasks # Create task
87
+ GET /api/tasks/:id # Get single task
88
+ PATCH /api/tasks/:id # Update task
89
+ DELETE /api/tasks/:id # Delete task
90
+
91
+ GET /api/projects # List projects
92
+ POST /api/projects # Create project
93
+ GET /api/projects/:id/tasks # Tasks within a project
94
+ \`\`\`
95
+
96
+ Key design decisions:
97
+ - Use **PATCH** over PUT for partial updates
98
+ - Nest tasks under projects for scoped queries
99
+ - Support filtering via query params: \`?status=active&assignee=me\``}
100
+ </MessageContent>
101
+ <MessageActions>
102
+ <Button variant="ghost" size="icon-sm" aria-label="Copy message"><Copy class="size-3.5" /></Button>
103
+ <Button variant="ghost" size="icon-sm" aria-label="Good response"><ThumbsUp class="size-3.5" /></Button>
104
+ <Button variant="ghost" size="icon-sm" aria-label="Bad response"><ThumbsDown class="size-3.5" /></Button>
105
+ </MessageActions>
106
+ </div>
107
+ </Message>
108
+
109
+ {/* First checkpoint */}
110
+ <Checkpoint>
111
+ <CheckpointIcon />
112
+ <CheckpointTrigger
113
+ tooltip="Restore to this point"
114
+ onClick={() => setRestoredTo('API structure defined')}
115
+ >
116
+ <div class="flex items-center gap-1.5 text-xs">
117
+ <RotateCcw class="size-3" />
118
+ <span>API structure defined</span>
119
+ </div>
120
+ </CheckpointTrigger>
121
+ </Checkpoint>
122
+
123
+ {/* Second exchange */}
124
+ <Message>
125
+ <MessageAvatar src="" fallback="U" alt="User" />
126
+ <MessageContent>
127
+ What about authentication? Should I use JWT or session cookies?
128
+ </MessageContent>
129
+ </Message>
130
+
131
+ <Message>
132
+ <MessageAvatar src="" fallback="AI" alt="Assistant" />
133
+ <div class="flex-1 space-y-2">
134
+ <MessageContent markdown>
135
+ {`For a task management API, I'd recommend **JWT with refresh tokens**:
136
+
137
+ \`\`\`typescript
138
+ // Auth endpoints
139
+ POST /api/auth/login // Returns access + refresh tokens
140
+ POST /api/auth/refresh // Exchanges refresh for new access token
141
+ POST /api/auth/logout // Invalidates refresh token
142
+
143
+ // Middleware
144
+ function authMiddleware(req, res, next) {
145
+ const token = req.headers.authorization?.split(' ')[1];
146
+ try {
147
+ req.user = jwt.verify(token, SECRET);
148
+ next();
149
+ } catch {
150
+ res.status(401).json({ error: 'Invalid token' });
151
+ }
152
+ }
153
+ \`\`\`
154
+
155
+ **Why JWT over sessions:**
156
+ - Stateless -- no server-side session store needed
157
+ - Works across subdomains and mobile apps
158
+ - Access tokens expire in 15min, limiting damage from theft
159
+
160
+ **The tradeoff:** you can't instantly revoke JWTs. Mitigate this with short expiry + a blocklist for critical cases (password change, account compromise).`}
161
+ </MessageContent>
162
+ <MessageActions>
163
+ <Button variant="ghost" size="icon-sm" aria-label="Copy message"><Copy class="size-3.5" /></Button>
164
+ <Button variant="ghost" size="icon-sm" aria-label="Good response"><ThumbsUp class="size-3.5" /></Button>
165
+ <Button variant="ghost" size="icon-sm" aria-label="Bad response"><ThumbsDown class="size-3.5" /></Button>
166
+ </MessageActions>
167
+ </div>
168
+ </Message>
169
+
170
+ {/* Second checkpoint */}
171
+ <Checkpoint>
172
+ <CheckpointIcon />
173
+ <CheckpointTrigger
174
+ tooltip="Restore to this point"
175
+ onClick={() => setRestoredTo('Auth strategy chosen')}
176
+ >
177
+ <div class="flex items-center gap-1.5 text-xs">
178
+ <RotateCcw class="size-3" />
179
+ <span>Auth strategy chosen</span>
180
+ </div>
181
+ </CheckpointTrigger>
182
+ </Checkpoint>
183
+
184
+ {/* Third exchange after checkpoint */}
185
+ <Message>
186
+ <MessageAvatar src="" fallback="U" alt="User" />
187
+ <MessageContent>
188
+ Now help me think about error handling. What's a good error response format?
189
+ </MessageContent>
190
+ </Message>
191
+
192
+ <Message>
193
+ <MessageAvatar src="" fallback="AI" alt="Assistant" />
194
+ <div class="flex-1 space-y-2">
195
+ <MessageContent markdown>
196
+ {`Use a consistent error envelope inspired by the **RFC 7807 Problem Details** spec:
197
+
198
+ \`\`\`typescript
199
+ interface ApiError {
200
+ status: number; // HTTP status code
201
+ code: string; // Machine-readable error code
202
+ message: string; // Human-readable description
203
+ details?: unknown[]; // Validation errors, field-level info
204
+ requestId: string; // For debugging/support
205
+ }
206
+
207
+ // Example responses
208
+ // 400 Bad Request
209
+ {
210
+ "status": 400,
211
+ "code": "VALIDATION_ERROR",
212
+ "message": "Invalid task data",
213
+ "details": [
214
+ { "field": "title", "error": "Required field missing" },
215
+ { "field": "dueDate", "error": "Must be a future date" }
216
+ ],
217
+ "requestId": "req_abc123"
218
+ }
219
+
220
+ // 404 Not Found
221
+ {
222
+ "status": 404,
223
+ "code": "RESOURCE_NOT_FOUND",
224
+ "message": "Task not found",
225
+ "requestId": "req_def456"
226
+ }
227
+ \`\`\`
228
+
229
+ Always include the \`requestId\` -- it's invaluable for correlating client errors with server logs.`}
230
+ </MessageContent>
231
+ <MessageActions>
232
+ <Button variant="ghost" size="icon-sm" aria-label="Copy message"><Copy class="size-3.5" /></Button>
233
+ <Button variant="ghost" size="icon-sm" aria-label="Good response"><ThumbsUp class="size-3.5" /></Button>
234
+ <Button variant="ghost" size="icon-sm" aria-label="Bad response"><ThumbsDown class="size-3.5" /></Button>
235
+ </MessageActions>
236
+ </div>
237
+ </Message>
238
+
239
+ <ChatContainerScrollAnchor />
240
+ </ChatContainerContent>
241
+ </ChatContainer>
242
+
243
+ <div class="px-4 pb-4">
244
+ <PromptInput>
245
+ <PromptInputTextarea placeholder="Continue the API design..." />
246
+ <PromptInputActions class="justify-end">
247
+ <Button variant="default" size="icon-sm" class="rounded-full" aria-label="Send message">
248
+ <ArrowUp class="size-4" />
249
+ </Button>
250
+ </PromptInputActions>
251
+ </PromptInput>
252
+ </div>
253
+ </div>
254
+ );
255
+ },
256
+ };
@@ -0,0 +1,212 @@
1
+ import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
+ import {
3
+ Context, ContextTrigger, ContextContent,
4
+ ContextContentHeader, ContextContentBody, ContextContentFooter,
5
+ ContextInputUsage, ContextOutputUsage, ContextReasoningUsage, ContextCacheUsage,
6
+ ModelSwitcher, Button, Separator,
7
+ } from '../index';
8
+ import type { ModelOption } from '../types';
9
+ import { createSignal } from 'solid-js';
10
+
11
+ const meta: Meta = {
12
+ title: 'Examples/Context & Token Usage',
13
+ parameters: {
14
+ layout: 'padded',
15
+ docs: {
16
+ description: {
17
+ component: [
18
+ 'Show how much of the model\'s context window is consumed. `<kai-context>` (or its SolidJS `Context` primitive) takes token counts and renders a circular meter trigger + hover-card breakdown.',
19
+ '',
20
+ '**Color thresholds** are configurable via `warnThreshold` (default `0.7`) and `dangerThreshold` (default `0.9`) props on both `<Context>` and `<kai-context>`:',
21
+ '- Green (`bg-primary`): `usedTokens / maxTokens` ≤ `warnThreshold`',
22
+ '- Yellow (`bg-yellow-400`): above `warnThreshold`',
23
+ '- Red (`bg-red-400`): above `dangerThreshold`',
24
+ '',
25
+ 'When the computed severity level changes, `<kai-context>` fires a **`kai-threshold-change`** CustomEvent with `detail.level` set to `\'ok\'`, `\'warn\'`, or `\'danger\'`.',
26
+ '',
27
+ '**Where token counts come from:** read them from the API response `usage` object (`input_tokens`, `output_tokens`, `cache_read_input_tokens`, etc.) after each turn and pass them as props. The element fires no data-fetching events.',
28
+ '',
29
+ '**Live-update pattern:** for SolidJS, drive the `Context` props from reactive signals that you update after each streaming response completes. The element re-renders automatically when props change.',
30
+ '',
31
+ '**`<kai-context>` web-component route:** pass a single `context` object as a **property** (not an attribute — attributes only accept strings). Set `warnThreshold` / `dangerThreshold` as numeric properties to override defaults. The WC renders the full trigger + popover for you with no composition needed.',
32
+ ].join('\n'),
33
+ },
34
+ },
35
+ },
36
+ };
37
+
38
+ export default meta;
39
+ type Story = StoryObj;
40
+
41
+ export const LowUsage: Story = {
42
+ name: 'Low Usage (Green)',
43
+ parameters: {
44
+ docs: {
45
+ description: {
46
+ story: 'Early in a conversation. `usedTokens / maxTokens` ≤ `warnThreshold` (default 70%) → green meter. Only input + output rows shown; omit `reasoningTokens` / `cacheTokens` to hide those rows.',
47
+ },
48
+ },
49
+ },
50
+ render: () => (
51
+ <div class="p-8">
52
+ <p class="text-sm text-muted-foreground mb-4">Early in a conversation -- minimal token usage.</p>
53
+ <Context usedTokens={4200} maxTokens={200000} inputTokens={2800} outputTokens={1400} estimatedCost={0.012}>
54
+ <ContextTrigger />
55
+ <ContextContent>
56
+ <ContextContentHeader />
57
+ <ContextContentBody>
58
+ <div class="space-y-1.5">
59
+ <ContextInputUsage />
60
+ <ContextOutputUsage />
61
+ </div>
62
+ </ContextContentBody>
63
+ <ContextContentFooter />
64
+ </ContextContent>
65
+ </Context>
66
+ </div>
67
+ ),
68
+ };
69
+
70
+ export const MediumUsage: Story = {
71
+ name: 'Medium Usage (Yellow)',
72
+ parameters: {
73
+ docs: {
74
+ description: {
75
+ story: '`usedTokens / maxTokens` > `warnThreshold` (default 70%) → yellow meter. Add `reasoningTokens` to surface a reasoning row in the breakdown. Pass `warnThreshold` to `<Context>` / `<kai-context>` to override the 70% default.',
76
+ },
77
+ },
78
+ },
79
+ render: () => (
80
+ <div class="p-8">
81
+ <p class="text-sm text-muted-foreground mb-4">Extended conversation with reasoning -- approaching 75% usage.</p>
82
+ <Context usedTokens={150000} maxTokens={200000} inputTokens={85000} outputTokens={42000} reasoningTokens={23000} estimatedCost={0.89}>
83
+ <ContextTrigger />
84
+ <ContextContent>
85
+ <ContextContentHeader />
86
+ <ContextContentBody>
87
+ <div class="space-y-1.5">
88
+ <ContextInputUsage />
89
+ <ContextOutputUsage />
90
+ <ContextReasoningUsage />
91
+ </div>
92
+ </ContextContentBody>
93
+ <ContextContentFooter />
94
+ </ContextContent>
95
+ </Context>
96
+ </div>
97
+ ),
98
+ };
99
+
100
+ export const HighUsage: Story = {
101
+ name: 'High Usage (Red)',
102
+ parameters: {
103
+ docs: {
104
+ description: {
105
+ story: '`usedTokens / maxTokens` > `dangerThreshold` (default 90%) → red meter. Pass `dangerThreshold` to `<Context>` / `<kai-context>` to override the 90% default. Signal to the user to start a new conversation.',
106
+ },
107
+ },
108
+ },
109
+ render: () => (
110
+ <div class="p-8">
111
+ <p class="text-sm text-muted-foreground mb-4">Near the context limit -- user should consider starting a new conversation.</p>
112
+ <Context usedTokens={189000} maxTokens={200000} inputTokens={110000} outputTokens={54000} reasoningTokens={25000} estimatedCost={1.42}>
113
+ <ContextTrigger />
114
+ <ContextContent>
115
+ <ContextContentHeader />
116
+ <ContextContentBody>
117
+ <div class="space-y-1.5">
118
+ <ContextInputUsage />
119
+ <ContextOutputUsage />
120
+ <ContextReasoningUsage />
121
+ </div>
122
+ </ContextContentBody>
123
+ <ContextContentFooter />
124
+ </ContextContent>
125
+ </Context>
126
+ </div>
127
+ ),
128
+ };
129
+
130
+ export const WithCacheBreakdown: Story = {
131
+ name: 'Full Breakdown with Cache',
132
+ parameters: {
133
+ docs: {
134
+ description: {
135
+ story: 'Add `cacheTokens` to expose the cache row. From the API: cache-hit tokens come from `usage.cache_read_input_tokens` in the response; cache-write tokens from `usage.cache_creation_input_tokens`. Both count toward `usedTokens`.',
136
+ },
137
+ },
138
+ },
139
+ render: () => (
140
+ <div class="p-8">
141
+ <p class="text-sm text-muted-foreground mb-4">Detailed usage including cache hit tokens.</p>
142
+ <Context usedTokens={82000} maxTokens={200000} inputTokens={45000} outputTokens={22000} reasoningTokens={15000} cacheTokens={32000} estimatedCost={0.38}>
143
+ <ContextTrigger />
144
+ <ContextContent>
145
+ <ContextContentHeader />
146
+ <ContextContentBody>
147
+ <div class="space-y-1.5">
148
+ <ContextInputUsage />
149
+ <ContextOutputUsage />
150
+ <ContextReasoningUsage />
151
+ <ContextCacheUsage />
152
+ </div>
153
+ </ContextContentBody>
154
+ <ContextContentFooter />
155
+ </ContextContent>
156
+ </Context>
157
+ </div>
158
+ ),
159
+ };
160
+
161
+ export const InHeaderBar: Story = {
162
+ name: 'In a Header Bar',
163
+ parameters: {
164
+ docs: {
165
+ description: {
166
+ story: '`Context` (or `<kai-context>`) is a plain inline element — place it anywhere in your header. Here it sits next to a `ModelSwitcher`. Drive both from reactive signals that you update after each API response.',
167
+ },
168
+ },
169
+ },
170
+ render: () => {
171
+ const [modelId, setModelId] = createSignal('claude-4');
172
+ const models: ModelOption[] = [
173
+ { id: 'claude-4', name: 'Claude 4 Opus', provider: 'Anthropic' },
174
+ { id: 'claude-4-sonnet', name: 'Claude 4 Sonnet', provider: 'Anthropic' },
175
+ ];
176
+
177
+ return (
178
+ <div class="w-full max-w-3xl">
179
+ <p class="text-sm text-muted-foreground mb-4 px-4">Context usage as it appears in the app header, alongside model switcher.</p>
180
+
181
+ <div class="bg-background rounded-xl shadow-lg overflow-hidden">
182
+ <div class="flex items-center justify-between px-4 py-2.5">
183
+ <div class="flex items-center gap-3">
184
+ <h2 class="text-sm font-semibold text-foreground">Database indexing strategies</h2>
185
+ <span class="text-xs text-muted-foreground">24 messages</span>
186
+ </div>
187
+ <div class="flex items-center gap-2">
188
+ <ModelSwitcher models={models} currentModelId={modelId()} onModelChange={setModelId} />
189
+ <Context usedTokens={67000} maxTokens={200000} inputTokens={38000} outputTokens={29000} estimatedCost={0.31}>
190
+ <ContextTrigger />
191
+ <ContextContent>
192
+ <ContextContentHeader />
193
+ <ContextContentBody>
194
+ <div class="space-y-1.5">
195
+ <ContextInputUsage />
196
+ <ContextOutputUsage />
197
+ </div>
198
+ </ContextContentBody>
199
+ <ContextContentFooter />
200
+ </ContextContent>
201
+ </Context>
202
+ </div>
203
+ </div>
204
+ <Separator />
205
+ <div class="h-48 flex items-center justify-center text-muted-foreground text-sm">
206
+ Chat content area
207
+ </div>
208
+ </div>
209
+ </div>
210
+ );
211
+ },
212
+ };
@@ -0,0 +1,182 @@
1
+ import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
+ import {
3
+ ChatContainer, ChatContainerContent, ChatContainerScrollAnchor,
4
+ Message, MessageAvatar, MessageContent, MessageActions,
5
+ PromptInput, PromptInputTextarea, PromptInputActions,
6
+ ChainOfThought, ChainOfThoughtStep, ChainOfThoughtTrigger, ChainOfThoughtContent, ChainOfThoughtItem,
7
+ Button, Separator,
8
+ } from '../index';
9
+ import { Copy, ThumbsUp, ThumbsDown, ArrowUp, Lightbulb, Search, Calculator } from 'lucide-solid';
10
+
11
+ const meta: Meta = {
12
+ title: 'Examples/Conversation with Reasoning',
13
+ parameters: {
14
+ layout: 'padded',
15
+ docs: {
16
+ description: {
17
+ component: [
18
+ 'Show a model\'s chain-of-thought trace above its answer. Two elements cover this:',
19
+ '',
20
+ '**`<kai-chain-of-thought>`** — the flat, data-driven route. Pass a `steps` array of `{ label, content }` objects as a **property** (not an attribute — attributes only accept strings). The element renders collapsible steps with connector lines automatically.',
21
+ '',
22
+ '**Gotcha — no per-step icons in the element:** the `steps` shape has no `icon` field. The Search/Calculator/Lightbulb icons in the live demo are `ChainOfThoughtTrigger leftIcon` props, a SolidJS-only touch. If you need branded step icons, compose the SolidJS primitives directly (see the Solid tab).',
23
+ '',
24
+ '**`<kai-reasoning>`** — a single collapsible reasoning block (text + label). Better for raw LLM thinking output: pass `text` as a property, add `streaming` while the stream is live (auto-expands then re-collapses), and `markdown` (defaults to `true`). It fires `kai-open-change: { open }` when toggled.',
25
+ '',
26
+ 'The live demo composes the SolidJS `ChainOfThought` + step primitives for full icon control.',
27
+ ].join('\n'),
28
+ },
29
+ },
30
+ },
31
+ };
32
+
33
+ export default meta;
34
+ type Story = StoryObj;
35
+
36
+ export const Default: Story = {
37
+ name: 'Reasoning + Answer',
38
+ parameters: {
39
+ docs: {
40
+ description: {
41
+ story: [
42
+ 'Four-step chain-of-thought trace above a technical answer. Key points:',
43
+ '',
44
+ '- The `ChainOfThought` steps here use `leftIcon` (Search/Calculator/Lightbulb) — a **SolidJS-only** prop on `ChainOfThoughtTrigger`. The `<kai-chain-of-thought>` element\'s `steps` array has no icon field.',
45
+ '- Each step is independently collapsible — `ChainOfThoughtStep isLast` removes the trailing connector line.',
46
+ '- The copy/like/dislike bar is a separate `MessageActions` composition; it is not part of the chain-of-thought model.',
47
+ ].join('\n'),
48
+ },
49
+ },
50
+ },
51
+ render: () => (
52
+ <div class="flex flex-col h-[700px] w-full max-w-2xl bg-background rounded-xl shadow-lg overflow-hidden">
53
+ <div class="flex items-center px-4 py-3">
54
+ <h2 class="text-sm font-semibold text-foreground">Architecture Decision</h2>
55
+ </div>
56
+ <Separator />
57
+
58
+ <ChatContainer class="flex-1 p-4">
59
+ <ChatContainerContent class="space-y-6 py-4">
60
+
61
+ <Message>
62
+ <MessageAvatar src="" fallback="U" alt="User" />
63
+ <MessageContent>
64
+ I have a Chrome extension that needs to sync user data across devices. Should I use Chrome's built-in storage.sync API or implement my own sync layer with a backend? The data is about 50KB per user, updated frequently (every few minutes), and includes nested objects with arrays.
65
+ </MessageContent>
66
+ </Message>
67
+
68
+ <Message>
69
+ <MessageAvatar src="" fallback="AI" alt="Assistant" />
70
+ <div class="flex-1 space-y-3">
71
+
72
+ {/* Chain of thought reasoning */}
73
+ <ChainOfThought>
74
+ <ChainOfThoughtStep>
75
+ <ChainOfThoughtTrigger leftIcon={<Search class="size-3.5" />}>
76
+ Analyzing data size and sync limits
77
+ </ChainOfThoughtTrigger>
78
+ <ChainOfThoughtContent>
79
+ <ChainOfThoughtItem>
80
+ chrome.storage.sync has a total limit of 102,400 bytes and 512 items max. At 50KB per user, that's within the total limit but leaves little headroom. Each item is capped at 8,192 bytes, so nested objects would need splitting. Write operations are throttled to 120/minute in normal mode, 1,800/hour.
81
+ </ChainOfThoughtItem>
82
+ </ChainOfThoughtContent>
83
+ </ChainOfThoughtStep>
84
+
85
+ <ChainOfThoughtStep>
86
+ <ChainOfThoughtTrigger leftIcon={<Calculator class="size-3.5" />}>
87
+ Evaluating update frequency constraints
88
+ </ChainOfThoughtTrigger>
89
+ <ChainOfThoughtContent>
90
+ <ChainOfThoughtItem>
91
+ "Updated every few minutes" means roughly 20-30 writes per hour. That's well within the 1,800/hour limit. But if the data has nested objects with arrays, each update might need to write multiple storage items (due to the 8KB per-item limit), multiplying the write count. 50KB / 8KB = ~7 items minimum, so 7 * 30 = 210 writes/hour. Still within limits.
92
+ </ChainOfThoughtItem>
93
+ </ChainOfThoughtContent>
94
+ </ChainOfThoughtStep>
95
+
96
+ <ChainOfThoughtStep>
97
+ <ChainOfThoughtTrigger leftIcon={<Lightbulb class="size-3.5" />}>
98
+ Considering conflict resolution
99
+ </ChainOfThoughtTrigger>
100
+ <ChainOfThoughtContent>
101
+ <ChainOfThoughtItem>
102
+ Chrome storage.sync uses last-write-wins. For nested objects updated from multiple devices simultaneously, this could cause data loss. If a user has the extension open on laptop and desktop, and both modify different array elements, the last write overwrites the other's changes. A custom backend could use CRDTs or operational transforms for proper merging.
103
+ </ChainOfThoughtItem>
104
+ </ChainOfThoughtContent>
105
+ </ChainOfThoughtStep>
106
+
107
+ <ChainOfThoughtStep isLast>
108
+ <ChainOfThoughtTrigger leftIcon={<Lightbulb class="size-3.5" />}>
109
+ Weighing implementation complexity
110
+ </ChainOfThoughtTrigger>
111
+ <ChainOfThoughtContent>
112
+ <ChainOfThoughtItem>
113
+ Custom backend means: server costs, auth, API design, offline handling, retry logic, and maintenance. Chrome storage.sync is free, built-in, handles offline automatically, and requires zero infrastructure. For a personal tool, the operational burden of a backend is significant.
114
+ </ChainOfThoughtItem>
115
+ </ChainOfThoughtContent>
116
+ </ChainOfThoughtStep>
117
+ </ChainOfThought>
118
+
119
+ {/* Final answer */}
120
+ <MessageContent markdown>
121
+ {`Given your constraints, I'd recommend a **hybrid approach**:
122
+
123
+ ### Use \`chrome.storage.sync\` as primary, with a chunking strategy
124
+
125
+ 1. **Split your 50KB into logical chunks** -- group related data into objects under 8KB each. Use a manifest key that tracks which chunks exist and their versions.
126
+
127
+ 2. **Implement delta syncing** -- instead of writing the full dataset every time, only write the chunks that changed. This keeps you well under rate limits.
128
+
129
+ 3. **Add a simple conflict guard** -- store a \`lastModifiedBy\` device ID and timestamp in each chunk. On read, if you detect a conflict, merge arrays by union rather than overwrite.
130
+
131
+ \`\`\`typescript
132
+ interface SyncChunk {
133
+ version: number;
134
+ deviceId: string;
135
+ updatedAt: number;
136
+ data: Record<string, unknown>;
137
+ }
138
+
139
+ // Write only changed chunks
140
+ async function syncChunks(changes: Map<string, SyncChunk>) {
141
+ const batch: Record<string, SyncChunk> = {};
142
+ for (const [key, chunk] of changes) {
143
+ batch[\`chunk_\${key}\`] = { ...chunk, version: chunk.version + 1 };
144
+ }
145
+ await chrome.storage.sync.set(batch);
146
+ }
147
+ \`\`\`
148
+
149
+ ### When to upgrade to a backend
150
+
151
+ Move to a custom sync layer if:
152
+ - Data grows beyond **80KB** (hitting limits)
153
+ - You need **real-time collaboration** (multiple devices editing simultaneously)
154
+ - You want **version history** or undo across devices
155
+ - You add team/sharing features`}
156
+ </MessageContent>
157
+
158
+ <MessageActions>
159
+ <Button variant="ghost" size="icon-sm" aria-label="Copy message"><Copy class="size-3.5" /></Button>
160
+ <Button variant="ghost" size="icon-sm" aria-label="Good response"><ThumbsUp class="size-3.5" /></Button>
161
+ <Button variant="ghost" size="icon-sm" aria-label="Bad response"><ThumbsDown class="size-3.5" /></Button>
162
+ </MessageActions>
163
+ </div>
164
+ </Message>
165
+
166
+ <ChatContainerScrollAnchor />
167
+ </ChatContainerContent>
168
+ </ChatContainer>
169
+
170
+ <div class="px-4 pb-4">
171
+ <PromptInput>
172
+ <PromptInputTextarea placeholder="Ask a follow-up..." />
173
+ <PromptInputActions class="justify-end">
174
+ <Button variant="default" size="icon-sm" class="rounded-full" aria-label="Send message">
175
+ <ArrowUp class="size-4" />
176
+ </Button>
177
+ </PromptInputActions>
178
+ </PromptInput>
179
+ </div>
180
+ </div>
181
+ ),
182
+ };