@lostgradient/cinder 0.3.0 → 0.4.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 (921) hide show
  1. package/CHANGELOG.md +146 -0
  2. package/README.md +2 -2
  3. package/components.json +138 -86
  4. package/dist/_internal/locale-context.d.ts +9 -0
  5. package/dist/_internal/text-direction.d.ts +4 -0
  6. package/dist/_internal/tree-context.d.ts +2 -0
  7. package/dist/api-contract.d.ts +1 -1
  8. package/dist/components/_internal/create-command-list-state.svelte.d.ts +43 -0
  9. package/dist/components/_internal/create-sliding-dialog-state.svelte.d.ts +26 -0
  10. package/dist/components/access-gate/index.js +379 -513
  11. package/dist/components/alert/alert.css +1 -1
  12. package/dist/components/alert/alert.schema.js +4 -4
  13. package/dist/components/alert/alert.types.d.ts +4 -4
  14. package/dist/components/alert/index.js +4 -4
  15. package/dist/components/alert-dialog/index.js +9 -3
  16. package/dist/components/approval-card/approval-card-state.d.ts +3 -0
  17. package/dist/components/approval-card/approval-card.css +239 -0
  18. package/dist/components/approval-card/approval-card.schema.js +236 -0
  19. package/dist/components/approval-card/approval-card.svelte.d.ts +21 -0
  20. package/dist/components/approval-card/approval-card.types.d.ts +155 -0
  21. package/dist/components/{subscription-badge/subscription-badge.variables.js → approval-card/approval-card.variables.js} +4 -4
  22. package/dist/components/approval-card/index.d.ts +5 -0
  23. package/dist/components/approval-card/index.js +3794 -0
  24. package/dist/components/autocomplete/autocomplete.css +12 -1
  25. package/dist/components/autocomplete/index.js +106 -81
  26. package/dist/components/avatar-group/index.js +3 -2
  27. package/dist/components/badge/badge.schema.js +6 -3
  28. package/dist/components/badge/badge.svelte.d.ts +4 -0
  29. package/dist/components/badge/badge.types.d.ts +18 -4
  30. package/dist/components/badge/index.d.ts +1 -1
  31. package/dist/components/badge/index.js +99 -3
  32. package/dist/components/banner/banner.schema.js +2 -2
  33. package/dist/components/banner/banner.types.d.ts +1 -1
  34. package/dist/components/banner/index.js +3 -3
  35. package/dist/components/button/button.css +1 -1
  36. package/dist/components/capability-gate/capability-gate.css +15 -2
  37. package/dist/components/capability-gate/capability-gate.schema.js +4 -4
  38. package/dist/components/capability-gate/capability-gate.types.d.ts +6 -6
  39. package/dist/components/capability-gate/index.js +7 -7
  40. package/dist/components/chat/chat.schema.js +11 -26
  41. package/dist/components/chat/chat.types.d.ts +30 -15
  42. package/dist/components/chat/container/chat-events.d.ts +2 -2
  43. package/dist/components/chat/container/chat-history-trigger.svelte.d.ts +1 -1
  44. package/dist/components/chat/container/chat.svelte.d.ts +1 -1
  45. package/dist/components/chat/container/use-chat-scroll-state.svelte.d.ts +4 -4
  46. package/dist/components/chat/container/use-chat-unread-state.svelte.d.ts +6 -6
  47. package/dist/components/chat/index.d.ts +1 -1
  48. package/dist/components/chat/index.js +2257 -1913
  49. package/dist/components/chat/input/chat-input.svelte.d.ts +3 -1
  50. package/dist/components/chat-conversation-header/index.js +261 -29
  51. package/dist/components/chat-conversation-list/chat-conversation-list.css +13 -0
  52. package/dist/components/checkbox/checkbox.css +3 -0
  53. package/dist/components/checkbox/checkbox.schema.js +6 -1
  54. package/dist/components/checkbox/checkbox.types.d.ts +2 -0
  55. package/dist/components/checkbox/index.js +78 -22
  56. package/dist/components/checkbox-group/checkbox-group.css +3 -0
  57. package/dist/components/checkbox-group/checkbox-group.schema.js +4 -4
  58. package/dist/components/checkbox-group/checkbox-group.types.d.ts +12 -7
  59. package/dist/components/checkbox-group/index.js +50 -21
  60. package/dist/components/choice-grid/index.js +2 -2
  61. package/dist/components/choice-grid-item/index.js +2 -2
  62. package/dist/components/click-away-listener/click-away-listener.schema.js +3 -3
  63. package/dist/components/click-away-listener/click-away-listener.types.d.ts +3 -3
  64. package/dist/components/click-away-listener/index.js +3 -3
  65. package/dist/components/code-block/code-block.css +4 -3
  66. package/dist/components/code-block/index.js +6 -5
  67. package/dist/components/collapsible/collapsible.schema.js +2 -2
  68. package/dist/components/collapsible/collapsible.types.d.ts +2 -2
  69. package/dist/components/collapsible/index.js +3 -3
  70. package/dist/components/color-field/color-field.schema.js +3 -3
  71. package/dist/components/color-field/color-field.types.d.ts +2 -2
  72. package/dist/components/color-field/index.js +133 -69
  73. package/dist/components/color-picker/index.js +29 -18
  74. package/dist/components/color-swatch-picker/index.js +29 -18
  75. package/dist/components/combobox/combobox.schema.js +6 -2
  76. package/dist/components/combobox/combobox.types.d.ts +7 -1
  77. package/dist/components/combobox/index.js +112 -60
  78. package/dist/components/command-item/command-item.css +10 -0
  79. package/dist/components/command-item/command-item.schema.js +9 -1
  80. package/dist/components/command-item/command-item.types.d.ts +4 -0
  81. package/dist/components/command-item/index.js +9 -1
  82. package/dist/components/command-menu/index.js +257 -161
  83. package/dist/components/command-palette/command-palette.schema.js +2 -3
  84. package/dist/components/command-palette/command-palette.types.d.ts +1 -2
  85. package/dist/components/command-palette/index.js +282 -192
  86. package/dist/components/confirm-dialog/index.js +9 -3
  87. package/dist/components/context-menu/context-menu.context.d.ts +2 -0
  88. package/dist/components/context-menu/index.js +300 -40
  89. package/dist/components/context-menu-trigger/index.js +11 -8
  90. package/dist/components/copy-button/index.js +6 -5
  91. package/dist/components/data-grid/_internal/column-model.svelte.d.ts +1 -0
  92. package/dist/components/data-grid/data-grid.css +25 -0
  93. package/dist/components/data-grid/data-grid.schema.js +3 -3
  94. package/dist/components/data-grid/data-grid.types.d.ts +8 -2
  95. package/dist/components/data-grid/index.js +11 -8
  96. package/dist/components/data-list/data-list.schema.js +7 -10
  97. package/dist/components/data-list/data-list.types.d.ts +5 -9
  98. package/dist/components/data-list/index.js +15 -39
  99. package/dist/components/data-table/data-table.css +13 -0
  100. package/dist/components/data-table/index.js +32 -19
  101. package/dist/components/date-range-field/date-range-field.schema.js +7 -3
  102. package/dist/components/date-range-field/date-range-field.svelte.d.ts +2 -2
  103. package/dist/components/date-range-field/date-range-field.types.d.ts +15 -10
  104. package/dist/components/date-range-field/index.d.ts +1 -1
  105. package/dist/components/date-range-field/index.js +160 -17
  106. package/dist/components/diff-viewer/diff-front-matter.svelte.d.ts +2 -2
  107. package/dist/components/diff-viewer/diff-line.svelte.d.ts +2 -2
  108. package/dist/components/diff-viewer/diff-toolbar.svelte.d.ts +2 -2
  109. package/dist/components/diff-viewer/diff-viewer.svelte.d.ts +1 -1
  110. package/dist/components/diff-viewer/diff-viewer.types.d.ts +3 -3
  111. package/dist/components/diff-viewer/index.d.ts +1 -1
  112. package/dist/components/diff-viewer/index.js +171 -67
  113. package/dist/components/drawer/index.js +353 -283
  114. package/dist/components/dropdown/dropdown.css +7 -1
  115. package/dist/components/dropdown/dropdown.types.d.ts +3 -0
  116. package/dist/components/dropdown/index.js +267 -35
  117. package/dist/components/dropdown-group/dropdown-group.schema.js +4 -4
  118. package/dist/components/dropdown-group/dropdown-group.types.d.ts +4 -4
  119. package/dist/components/dropdown-group/index.js +7 -7
  120. package/dist/components/dropdown-item/dropdown-item.schema.js +9 -1
  121. package/dist/components/dropdown-item/dropdown-item.types.d.ts +5 -0
  122. package/dist/components/dropdown-item/index.js +13 -8
  123. package/dist/components/dropdown-menu/index.js +236 -14
  124. package/dist/components/dropdown-trigger/index.js +11 -12
  125. package/dist/components/event-stream-viewer/event-stream-viewer-keys.d.ts +6 -0
  126. package/dist/components/event-stream-viewer/event-stream-viewer.css +46 -0
  127. package/dist/components/event-stream-viewer/event-stream-viewer.schema.js +83 -10
  128. package/dist/components/event-stream-viewer/event-stream-viewer.svelte.d.ts +3 -3
  129. package/dist/components/event-stream-viewer/event-stream-viewer.types.d.ts +124 -3
  130. package/dist/components/event-stream-viewer/index.d.ts +1 -1
  131. package/dist/components/event-stream-viewer/index.js +764 -409
  132. package/dist/components/faceted-filter-bar/faceted-filter-bar.css +9 -0
  133. package/dist/components/faceted-filter-bar/index.js +25 -6
  134. package/dist/components/file-upload/file-upload.css +5 -5
  135. package/dist/components/floating-action-button/floating-action-button.css +22 -22
  136. package/dist/components/floating-action-button/floating-action-button.schema.js +5 -5
  137. package/dist/components/floating-action-button/floating-action-button.svelte.d.ts +1 -1
  138. package/dist/components/floating-action-button/floating-action-button.types.d.ts +21 -21
  139. package/dist/components/floating-action-button/index.d.ts +1 -1
  140. package/dist/components/floating-action-button/index.js +8 -8
  141. package/dist/components/form-field/form-field.css +3 -13
  142. package/dist/components/form-field/index.js +2 -2
  143. package/dist/components/hover-card/index.js +3 -2
  144. package/dist/components/input/index.js +131 -67
  145. package/dist/components/input/input.schema.js +6 -1
  146. package/dist/components/input/input.types.d.ts +2 -0
  147. package/dist/components/invocation-rule-builder/index.d.ts +1 -1
  148. package/dist/components/invocation-rule-builder/index.js +184 -107
  149. package/dist/components/invocation-rule-builder/invocation-rule-builder.css +63 -6
  150. package/dist/components/invocation-rule-builder/invocation-rule-builder.schema.js +133 -35
  151. package/dist/components/invocation-rule-builder/invocation-rule-builder.svelte.d.ts +2 -2
  152. package/dist/components/invocation-rule-builder/invocation-rule-builder.types.d.ts +85 -8
  153. package/dist/components/json-schema-editor/form-view.svelte.d.ts +1 -0
  154. package/dist/components/json-schema-editor/index.js +677 -357
  155. package/dist/components/json-schema-editor/json-schema-editor.css +6 -1
  156. package/dist/components/json-schema-editor/json-schema-toolbar.svelte.d.ts +1 -0
  157. package/dist/components/json-schema-editor/property-editor.svelte.d.ts +2 -0
  158. package/dist/components/json-schema-editor/property-list-validation.d.ts +1 -0
  159. package/dist/components/json-schema-editor/property-list.svelte.d.ts +1 -0
  160. package/dist/components/json-viewer/_json-viewer-node.svelte.d.ts +3 -0
  161. package/dist/components/json-viewer/index.js +112 -26
  162. package/dist/components/json-viewer/json-viewer.css +11 -0
  163. package/dist/components/kanban-board/index.js +18 -13
  164. package/dist/components/kanban-board/kanban-board.css +56 -3
  165. package/dist/components/keyboard-shortcuts/index.js +2 -2
  166. package/dist/components/keyboard-shortcuts/keyboard-shortcuts.css +2 -2
  167. package/dist/components/label/index.js +2 -2
  168. package/dist/components/label/label.css +2 -9
  169. package/dist/components/load-more/index.js +4 -4
  170. package/dist/components/load-more/load-more.css +13 -5
  171. package/dist/components/load-more/load-more.schema.js +4 -4
  172. package/dist/components/load-more/load-more.types.d.ts +3 -3
  173. package/dist/components/locale-provider/index.d.ts +4 -0
  174. package/dist/components/locale-provider/index.js +76 -0
  175. package/dist/components/{connection-indicator/connection-indicator.schema.js → locale-provider/locale-provider.schema.js} +18 -14
  176. package/dist/components/locale-provider/locale-provider.svelte.d.ts +17 -0
  177. package/dist/components/locale-provider/locale-provider.types.d.ts +17 -0
  178. package/dist/components/{connection-indicator/connection-indicator.variables.js → locale-provider/locale-provider.variables.js} +4 -4
  179. package/dist/components/markdown-editor/editor-toolbar/editor-toolbar.svelte.d.ts +2 -0
  180. package/dist/components/markdown-editor/index.js +437 -128
  181. package/dist/components/markdown-editor/markdown-editor.schema.js +4 -4
  182. package/dist/components/markdown-editor/markdown-editor.types.d.ts +4 -4
  183. package/dist/components/masonry/masonry.schema.js +4 -3
  184. package/dist/components/masonry/masonry.types.d.ts +13 -2
  185. package/dist/components/matrix-chart/index.js +245 -224
  186. package/dist/components/media-controls/index.js +7 -7
  187. package/dist/components/media-controls/media-controls.css +15 -4
  188. package/dist/components/media-controls/media-controls.schema.js +4 -4
  189. package/dist/components/media-controls/media-controls.types.d.ts +7 -7
  190. package/dist/components/menu-bar/menu-bar.css +11 -0
  191. package/dist/components/menu-bar/menu-bar.svelte.d.ts +1 -0
  192. package/dist/components/modal/index.js +9 -3
  193. package/dist/components/navigation-bar/index.js +4 -4
  194. package/dist/components/navigation-bar/navigation-bar.schema.js +2 -2
  195. package/dist/components/navigation-bar/navigation-bar.types.d.ts +1 -1
  196. package/dist/components/number-input/index.js +88 -42
  197. package/dist/components/number-input/number-input.schema.js +4 -3
  198. package/dist/components/number-input/number-input.types.d.ts +16 -5
  199. package/dist/components/payload-inspector/index.d.ts +1 -1
  200. package/dist/components/payload-inspector/index.js +271 -64
  201. package/dist/components/payload-inspector/payload-inspector.css +6 -0
  202. package/dist/components/payload-inspector/payload-inspector.schema.js +27 -15
  203. package/dist/components/payload-inspector/payload-inspector.svelte.d.ts +2 -2
  204. package/dist/components/payload-inspector/payload-inspector.types.d.ts +52 -3
  205. package/dist/components/permission-matrix/permission-matrix.css +10 -0
  206. package/dist/components/phone-input/index.js +56 -33
  207. package/dist/components/phone-input/phone-input.schema.js +2 -2
  208. package/dist/components/phone-input/phone-input.types.d.ts +3 -2
  209. package/dist/components/pin-input/index.js +46 -41
  210. package/dist/components/pin-input/pin-input.schema.js +9 -2
  211. package/dist/components/pin-input/pin-input.types.d.ts +10 -1
  212. package/dist/components/popover/index.js +11 -4
  213. package/dist/components/portal/index.js +3 -2
  214. package/dist/components/pricing-card/index.js +22 -7
  215. package/dist/components/radio-group/index.js +51 -21
  216. package/dist/components/radio-group/radio-group.css +3 -0
  217. package/dist/components/radio-group/radio-group.schema.js +3 -3
  218. package/dist/components/radio-group/radio-group.types.d.ts +6 -2
  219. package/dist/components/rating/index.js +1 -6
  220. package/dist/components/rating/rating.schema.js +9 -2
  221. package/dist/components/rating/rating.types.d.ts +6 -0
  222. package/dist/components/review-editor/comment-list.svelte.d.ts +1 -1
  223. package/dist/components/review-editor/index.d.ts +1 -1
  224. package/dist/components/review-editor/index.js +789 -314
  225. package/dist/components/review-editor/review-editor-controls.svelte.d.ts +1 -1
  226. package/dist/components/review-editor/review-editor-impl.svelte.d.ts +4 -4
  227. package/dist/components/review-editor/review-editor-selection-types.d.ts +1 -1
  228. package/dist/components/review-editor/review-editor-state.svelte.d.ts +1 -1
  229. package/dist/components/review-editor/review-editor-threads.svelte.d.ts +2 -2
  230. package/dist/components/review-editor/review-editor.svelte.d.ts +2 -1
  231. package/dist/components/review-editor/review-editor.types.d.ts +77 -3
  232. package/dist/components/review-editor/thread-popover.svelte.d.ts +1 -1
  233. package/dist/components/run-step-timeline/index.d.ts +1 -1
  234. package/dist/components/run-step-timeline/index.js +747 -273
  235. package/dist/components/run-step-timeline/run-step-timeline.css +25 -5
  236. package/dist/components/run-step-timeline/run-step-timeline.schema.js +351 -21
  237. package/dist/components/run-step-timeline/run-step-timeline.svelte.d.ts +1 -1
  238. package/dist/components/run-step-timeline/run-step-timeline.types.d.ts +184 -3
  239. package/dist/components/schema-form/index.d.ts +1 -1
  240. package/dist/components/schema-form/index.js +1916 -474
  241. package/dist/components/schema-form/schema-form-async.fixture.d.ts +1 -0
  242. package/dist/components/schema-form/schema-form-body.svelte.d.ts +25 -0
  243. package/dist/components/schema-form/schema-form-model.d.ts +1 -4
  244. package/dist/components/schema-form/schema-form-validation.d.ts +1 -2
  245. package/dist/components/schema-form/schema-form.css +12 -74
  246. package/dist/components/schema-form/schema-form.schema.js +10 -3
  247. package/dist/components/schema-form/schema-form.svelte.d.ts +3 -3
  248. package/dist/components/schema-form/schema-form.types.d.ts +15 -8
  249. package/dist/components/scroll-area/index.js +3 -3
  250. package/dist/components/scroll-area/scroll-area.schema.js +2 -2
  251. package/dist/components/scroll-area/scroll-area.types.d.ts +1 -1
  252. package/dist/components/secret-value-field/index.js +10 -4
  253. package/dist/components/secret-value-field/secret-value-field.svelte.d.ts +1 -1
  254. package/dist/components/segmented-control/segmented-control.css +22 -1
  255. package/dist/components/select/index.js +37 -24
  256. package/dist/components/select/select.css +9 -2
  257. package/dist/components/selection-popover/index.js +11 -4
  258. package/dist/components/share-card/index.js +3 -4
  259. package/dist/components/share-card/share-card.css +15 -4
  260. package/dist/components/sheet/index.js +365 -300
  261. package/dist/components/shortcut-hint/index.js +3 -3
  262. package/dist/components/sidebar/index.js +423 -353
  263. package/dist/components/sidebar/sidebar.schema.js +2 -2
  264. package/dist/components/sidebar/sidebar.types.d.ts +1 -1
  265. package/dist/components/slider/index.js +211 -9
  266. package/dist/components/slider/slider.css +14 -9
  267. package/dist/components/sortable-list/sortable-list.css +1 -1
  268. package/dist/components/spectrogram/index.js +224 -203
  269. package/dist/components/spectrum-chart/index.js +208 -187
  270. package/dist/components/speed-dial/index.js +11 -11
  271. package/dist/components/speed-dial/speed-dial.css +3 -3
  272. package/dist/components/speed-dial-action/index.js +9 -9
  273. package/dist/components/stat/index.js +26 -4
  274. package/dist/components/stat/stat.schema.js +2 -2
  275. package/dist/components/stat/stat.types.d.ts +2 -2
  276. package/dist/components/stat-group/index.js +26 -4
  277. package/dist/components/stat-group/stat-group.css +1 -1
  278. package/dist/components/status-dot/index.d.ts +1 -1
  279. package/dist/components/status-dot/index.js +47 -8
  280. package/dist/components/status-dot/status-dot.css +45 -1
  281. package/dist/components/status-dot/status-dot.schema.js +12 -5
  282. package/dist/components/status-dot/status-dot.svelte.d.ts +5 -1
  283. package/dist/components/status-dot/status-dot.types.d.ts +16 -6
  284. package/dist/components/steps/steps.css +3 -3
  285. package/dist/components/table/index.js +23 -17
  286. package/dist/components/table/table.css +10 -0
  287. package/dist/components/table/table.types.d.ts +3 -3
  288. package/dist/components/table-header/index.js +5 -5
  289. package/dist/components/table-header/table-header.schema.js +3 -3
  290. package/dist/components/table-header/table-header.types.d.ts +2 -2
  291. package/dist/components/table-header-cell/index.js +11 -5
  292. package/dist/components/table-row/index.js +7 -7
  293. package/dist/components/table-row/table-row.schema.js +3 -3
  294. package/dist/components/table-row/table-row.types.d.ts +6 -6
  295. package/dist/components/tabs/index.js +17 -4
  296. package/dist/components/tabs/tabs.schema.js +6 -1
  297. package/dist/components/tabs/tabs.types.d.ts +2 -0
  298. package/dist/components/textarea/index.js +37 -24
  299. package/dist/components/textarea/textarea.css +16 -1
  300. package/dist/components/time-field/index.d.ts +5 -0
  301. package/dist/components/time-field/index.js +456 -0
  302. package/dist/components/time-field/time-field.css +60 -0
  303. package/dist/components/time-field/time-field.schema.d.ts +3 -0
  304. package/dist/components/time-field/time-field.schema.js +107 -0
  305. package/dist/components/time-field/time-field.svelte.d.ts +18 -0
  306. package/dist/components/time-field/time-field.types.d.ts +63 -0
  307. package/dist/components/time-field/time-field.variables.d.ts +2 -0
  308. package/dist/components/{experimental/connection-indicator/index.js → time-field/time-field.variables.js} +5 -14
  309. package/dist/components/toggle/index.js +39 -7
  310. package/dist/components/toggle/toggle.schema.js +11 -2
  311. package/dist/components/toggle/toggle.types.d.ts +2 -0
  312. package/dist/components/tooltip/index.js +3 -2
  313. package/dist/components/transfer-list/index.js +41 -11
  314. package/dist/components/transfer-list/transfer-list.css +22 -0
  315. package/dist/components/transfer-list/transfer-list.schema.js +2 -2
  316. package/dist/components/transfer-list/transfer-list.types.d.ts +2 -2
  317. package/dist/components/tree/index.js +238 -153
  318. package/dist/components/tree/tree.css +69 -0
  319. package/dist/components/tree/tree.schema.js +3 -3
  320. package/dist/components/tree/tree.types.d.ts +2 -2
  321. package/dist/components/tree-item/index.js +107 -68
  322. package/dist/components/tree-item/tree-item.schema.js +5 -5
  323. package/dist/components/tree-item/tree-item.types.d.ts +4 -4
  324. package/dist/components/virtual-list/index.js +98 -63
  325. package/dist/components/waveform/index.js +185 -164
  326. package/dist/editor/attach.d.ts +2 -2
  327. package/dist/editor/attach.js +10 -16
  328. package/dist/editor/editor.js +6 -6
  329. package/dist/editor/keymap-plugin.d.ts +2 -2
  330. package/dist/editor/keymap-plugin.js +4 -4
  331. package/dist/editor/types.d.ts +11 -11
  332. package/dist/index.d.ts +14 -12
  333. package/dist/index.js +106 -105
  334. package/dist/server/components/access-gate/index.js +176 -305
  335. package/dist/server/components/alert/alert.schema.js +4 -4
  336. package/dist/server/components/alert/index.css +1 -1
  337. package/dist/server/components/alert/index.js +4 -4
  338. package/dist/server/components/alert-dialog/index.js +9 -3
  339. package/dist/server/components/approval-card/approval-card.schema.js +217 -0
  340. package/dist/server/components/approval-card/approval-card.variables.js +12 -0
  341. package/dist/server/components/approval-card/index.css +1767 -0
  342. package/dist/server/components/approval-card/index.js +2499 -0
  343. package/dist/server/components/autocomplete/index.css +12 -1
  344. package/dist/server/components/autocomplete/index.js +34 -24
  345. package/dist/server/components/badge/badge.schema.js +6 -3
  346. package/dist/server/components/badge/index.js +71 -4
  347. package/dist/server/components/banner/banner.schema.js +2 -2
  348. package/dist/server/components/banner/index.js +3 -3
  349. package/dist/server/components/button/index.css +1 -1
  350. package/dist/server/components/capability-gate/capability-gate.schema.js +4 -4
  351. package/dist/server/components/capability-gate/index.css +10 -2
  352. package/dist/server/components/capability-gate/index.js +5 -5
  353. package/dist/server/components/chat/chat.schema.js +11 -26
  354. package/dist/server/components/chat/index.js +695 -438
  355. package/dist/server/components/chat-conversation-header/index.css +7 -1
  356. package/dist/server/components/chat-conversation-header/index.js +215 -17
  357. package/dist/server/components/chat-conversation-list/index.css +8 -0
  358. package/dist/server/components/checkbox/checkbox.schema.js +6 -1
  359. package/dist/server/components/checkbox/index.css +3 -0
  360. package/dist/server/components/checkbox/index.js +41 -4
  361. package/dist/server/components/checkbox-group/checkbox-group.schema.js +4 -4
  362. package/dist/server/components/checkbox-group/index.css +3 -0
  363. package/dist/server/components/checkbox-group/index.js +23 -4
  364. package/dist/server/components/choice-grid/index.js +2 -2
  365. package/dist/server/components/choice-grid-item/index.js +2 -2
  366. package/dist/server/components/click-away-listener/click-away-listener.schema.js +3 -3
  367. package/dist/server/components/click-away-listener/index.js +2 -2
  368. package/dist/server/components/code-block/index.css +4 -3
  369. package/dist/server/components/code-block/index.js +1 -6
  370. package/dist/server/components/collapsible/collapsible.schema.js +2 -2
  371. package/dist/server/components/collapsible/index.js +3 -3
  372. package/dist/server/components/color-field/color-field.schema.js +3 -3
  373. package/dist/server/components/color-field/index.js +61 -17
  374. package/dist/server/components/color-picker/index.js +29 -12
  375. package/dist/server/components/color-swatch-picker/index.js +29 -12
  376. package/dist/server/components/combobox/combobox.schema.js +6 -2
  377. package/dist/server/components/combobox/index.js +61 -12
  378. package/dist/server/components/command-item/command-item.schema.js +9 -1
  379. package/dist/server/components/command-item/index.css +1 -0
  380. package/dist/server/components/command-item/index.js +10 -2
  381. package/dist/server/components/command-menu/index.css +1 -0
  382. package/dist/server/components/command-menu/index.js +193 -102
  383. package/dist/server/components/command-palette/command-palette.schema.js +2 -3
  384. package/dist/server/components/command-palette/index.css +1 -0
  385. package/dist/server/components/command-palette/index.js +190 -106
  386. package/dist/server/components/confirm-dialog/index.js +9 -3
  387. package/dist/server/components/context-menu/index.css +7 -1
  388. package/dist/server/components/context-menu/index.js +244 -26
  389. package/dist/server/components/context-menu-trigger/index.js +7 -4
  390. package/dist/server/components/copy-button/index.js +1 -6
  391. package/dist/server/components/data-grid/data-grid.schema.js +3 -3
  392. package/dist/server/components/data-grid/index.css +23 -0
  393. package/dist/server/components/data-grid/index.js +9 -7
  394. package/dist/server/components/data-list/data-list.schema.js +7 -10
  395. package/dist/server/components/data-list/index.js +7 -21
  396. package/dist/server/components/data-table/index.css +20 -0
  397. package/dist/server/components/data-table/index.js +26 -16
  398. package/dist/server/components/date-range-field/date-range-field.schema.js +7 -3
  399. package/dist/server/components/date-range-field/index.js +146 -12
  400. package/dist/server/components/diff-viewer/index.js +79 -7
  401. package/dist/server/components/drawer/index.js +260 -193
  402. package/dist/server/components/dropdown/index.css +7 -1
  403. package/dist/server/components/dropdown/index.js +221 -23
  404. package/dist/server/components/dropdown-group/dropdown-group.schema.js +4 -4
  405. package/dist/server/components/dropdown-group/index.js +7 -7
  406. package/dist/server/components/dropdown-item/dropdown-item.schema.js +9 -1
  407. package/dist/server/components/dropdown-item/index.js +12 -7
  408. package/dist/server/components/dropdown-menu/index.js +197 -13
  409. package/dist/server/components/dropdown-trigger/index.js +5 -2
  410. package/dist/server/components/event-stream-viewer/event-stream-viewer.schema.js +83 -10
  411. package/dist/server/components/event-stream-viewer/index.css +323 -0
  412. package/dist/server/components/event-stream-viewer/index.js +406 -139
  413. package/dist/server/components/faceted-filter-bar/index.css +10 -1
  414. package/dist/server/components/faceted-filter-bar/index.js +24 -6
  415. package/dist/server/components/file-upload/index.css +3 -3
  416. package/dist/server/components/floating-action-button/floating-action-button.schema.js +5 -5
  417. package/dist/server/components/floating-action-button/index.css +16 -16
  418. package/dist/server/components/floating-action-button/index.js +5 -5
  419. package/dist/server/components/form-field/index.css +0 -10
  420. package/dist/server/components/form-field/index.js +2 -2
  421. package/dist/server/components/input/index.js +59 -15
  422. package/dist/server/components/input/input.schema.js +6 -1
  423. package/dist/server/components/invocation-rule-builder/index.css +51 -6
  424. package/dist/server/components/invocation-rule-builder/index.js +56 -17
  425. package/dist/server/components/invocation-rule-builder/invocation-rule-builder.schema.js +133 -35
  426. package/dist/server/components/json-schema-editor/index.css +4 -1
  427. package/dist/server/components/json-schema-editor/index.js +282 -52
  428. package/dist/server/components/json-viewer/index.css +7 -1
  429. package/dist/server/components/json-viewer/index.js +83 -11
  430. package/dist/server/components/kanban-board/index.css +42 -3
  431. package/dist/server/components/kanban-board/index.js +7 -6
  432. package/dist/server/components/keyboard-shortcuts/index.css +2 -2
  433. package/dist/server/components/keyboard-shortcuts/index.js +2 -2
  434. package/dist/server/components/label/index.css +0 -10
  435. package/dist/server/components/label/index.js +2 -2
  436. package/dist/server/components/load-more/index.css +12 -4
  437. package/dist/server/components/load-more/index.js +5 -5
  438. package/dist/server/components/load-more/load-more.schema.js +4 -4
  439. package/dist/server/components/locale-provider/index.js +55 -0
  440. package/dist/server/components/locale-provider/locale-provider.schema.js +35 -0
  441. package/dist/server/components/locale-provider/locale-provider.variables.js +12 -0
  442. package/dist/server/components/markdown-editor/index.js +297 -60
  443. package/dist/server/components/markdown-editor/markdown-editor.schema.js +4 -4
  444. package/dist/server/components/masonry/masonry.schema.js +4 -3
  445. package/dist/server/components/matrix-chart/index.js +62 -39
  446. package/dist/server/components/media-controls/index.css +11 -3
  447. package/dist/server/components/media-controls/index.js +7 -7
  448. package/dist/server/components/media-controls/media-controls.schema.js +4 -4
  449. package/dist/server/components/menu-bar/index.css +15 -1
  450. package/dist/server/components/modal/index.js +9 -3
  451. package/dist/server/components/navigation-bar/index.js +3 -3
  452. package/dist/server/components/navigation-bar/navigation-bar.schema.js +2 -2
  453. package/dist/server/components/number-input/index.js +56 -18
  454. package/dist/server/components/number-input/number-input.schema.js +4 -3
  455. package/dist/server/components/payload-inspector/index.css +616 -0
  456. package/dist/server/components/payload-inspector/index.js +181 -22
  457. package/dist/server/components/payload-inspector/payload-inspector.schema.js +27 -15
  458. package/dist/server/components/permission-matrix/index.css +8 -0
  459. package/dist/server/components/phone-input/index.js +21 -3
  460. package/dist/server/components/phone-input/phone-input.schema.js +2 -2
  461. package/dist/server/components/pin-input/index.js +12 -7
  462. package/dist/server/components/pin-input/pin-input.schema.js +9 -2
  463. package/dist/server/components/popover/index.js +9 -3
  464. package/dist/server/components/pricing-card/index.js +22 -7
  465. package/dist/server/components/radio-group/index.css +3 -0
  466. package/dist/server/components/radio-group/index.js +24 -3
  467. package/dist/server/components/radio-group/radio-group.schema.js +3 -3
  468. package/dist/server/components/rating/rating.schema.js +9 -2
  469. package/dist/server/components/review-editor/index.js +426 -80
  470. package/dist/server/components/run-step-timeline/index.css +503 -6
  471. package/dist/server/components/run-step-timeline/index.js +455 -109
  472. package/dist/server/components/run-step-timeline/run-step-timeline.schema.js +351 -21
  473. package/dist/server/components/schema-form/index.css +11 -69
  474. package/dist/server/components/schema-form/index.js +1291 -218
  475. package/dist/server/components/schema-form/schema-form.schema.js +10 -3
  476. package/dist/server/components/scroll-area/index.js +3 -3
  477. package/dist/server/components/scroll-area/scroll-area.schema.js +2 -2
  478. package/dist/server/components/secret-value-field/index.js +4 -4
  479. package/dist/server/components/segmented-control/index.css +15 -1
  480. package/dist/server/components/select/index.css +9 -2
  481. package/dist/server/components/select/index.js +9 -2
  482. package/dist/server/components/selection-popover/index.js +9 -3
  483. package/dist/server/components/share-card/index.css +11 -3
  484. package/dist/server/components/share-card/index.js +1 -5
  485. package/dist/server/components/sheet/index.js +265 -195
  486. package/dist/server/components/sidebar/index.js +274 -207
  487. package/dist/server/components/sidebar/sidebar.schema.js +2 -2
  488. package/dist/server/components/slider/index.css +13 -9
  489. package/dist/server/components/slider/index.js +173 -4
  490. package/dist/server/components/sortable-list/index.css +1 -1
  491. package/dist/server/components/spectrogram/index.js +68 -45
  492. package/dist/server/components/spectrum-chart/index.js +54 -31
  493. package/dist/server/components/speed-dial/index.css +3 -3
  494. package/dist/server/components/speed-dial/index.js +8 -8
  495. package/dist/server/components/speed-dial-action/index.js +6 -6
  496. package/dist/server/components/stat/index.js +26 -4
  497. package/dist/server/components/stat/stat.schema.js +2 -2
  498. package/dist/server/components/stat-group/index.css +1 -1
  499. package/dist/server/components/stat-group/index.js +26 -4
  500. package/dist/server/components/status-dot/index.css +45 -1
  501. package/dist/server/components/status-dot/index.js +35 -7
  502. package/dist/server/components/status-dot/status-dot.schema.js +12 -5
  503. package/dist/server/components/steps/index.css +3 -3
  504. package/dist/server/components/table/index.css +8 -0
  505. package/dist/server/components/table/index.js +22 -16
  506. package/dist/server/components/table-header/index.js +6 -6
  507. package/dist/server/components/table-header/table-header.schema.js +3 -3
  508. package/dist/server/components/table-header-cell/index.js +9 -3
  509. package/dist/server/components/table-row/index.js +7 -7
  510. package/dist/server/components/table-row/table-row.schema.js +3 -3
  511. package/dist/server/components/tabs/index.js +18 -4
  512. package/dist/server/components/tabs/tabs.schema.js +6 -1
  513. package/dist/server/components/textarea/index.css +16 -1
  514. package/dist/server/components/textarea/index.js +9 -2
  515. package/dist/server/components/time-field/index.css +58 -0
  516. package/dist/server/components/time-field/index.js +291 -0
  517. package/dist/server/components/time-field/time-field.schema.js +88 -0
  518. package/dist/server/components/time-field/time-field.variables.js +12 -0
  519. package/dist/server/components/toggle/index.js +40 -7
  520. package/dist/server/components/toggle/toggle.schema.js +11 -2
  521. package/dist/server/components/transfer-list/index.css +16 -0
  522. package/dist/server/components/transfer-list/index.js +37 -11
  523. package/dist/server/components/transfer-list/transfer-list.schema.js +2 -2
  524. package/dist/server/components/tree/index.css +68 -0
  525. package/dist/server/components/tree/index.js +67 -20
  526. package/dist/server/components/tree/tree.schema.js +3 -3
  527. package/dist/server/components/tree-item/index.js +34 -13
  528. package/dist/server/components/tree-item/tree-item.schema.js +5 -5
  529. package/dist/server/components/virtual-list/index.js +36 -13
  530. package/dist/server/components/waveform/index.js +51 -28
  531. package/dist/server/editor/attach.d.ts +2 -2
  532. package/dist/server/editor/attach.js +10 -16
  533. package/dist/server/editor/editor.js +6 -6
  534. package/dist/server/editor/keymap-plugin.d.ts +2 -2
  535. package/dist/server/editor/keymap-plugin.js +4 -4
  536. package/dist/server/editor/types.d.ts +11 -11
  537. package/dist/server/index.css +2033 -1463
  538. package/dist/server/index.js +7203 -5135
  539. package/dist/utilities/attachments.d.ts +6 -6
  540. package/dist/utilities/value-change.d.ts +2 -0
  541. package/package.json +96 -63
  542. package/src/_internal/locale-context.ts +21 -0
  543. package/src/_internal/text-direction.ts +213 -0
  544. package/src/_internal/tree-context.ts +2 -0
  545. package/src/components/_internal/create-command-list-state.svelte.ts +171 -0
  546. package/src/components/_internal/create-sliding-dialog-state.svelte.ts +170 -0
  547. package/src/components/_radio/README.md +1 -1
  548. package/src/components/access-gate/access-gate-inline.svelte +3 -1
  549. package/src/components/alert/README.md +8 -8
  550. package/src/components/alert/alert.css +1 -1
  551. package/src/components/alert/alert.examples.json +4 -4
  552. package/src/components/alert/alert.svelte +3 -5
  553. package/src/components/alert/alert.types.ts +4 -4
  554. package/src/components/approval-card/README.md +92 -0
  555. package/src/components/approval-card/approval-card-state.ts +24 -0
  556. package/src/components/approval-card/approval-card.css +239 -0
  557. package/src/components/approval-card/approval-card.examples.json +25 -0
  558. package/src/components/approval-card/approval-card.svelte +583 -0
  559. package/src/components/approval-card/approval-card.types.ts +180 -0
  560. package/src/components/approval-card/index.ts +16 -0
  561. package/src/components/autocomplete/autocomplete.css +12 -1
  562. package/src/components/autocomplete/autocomplete.svelte +53 -27
  563. package/src/components/badge/README.md +8 -7
  564. package/src/components/badge/badge.svelte +80 -3
  565. package/src/components/badge/badge.types.ts +36 -13
  566. package/src/components/badge/index.ts +1 -1
  567. package/src/components/banner/README.md +2 -2
  568. package/src/components/banner/banner.examples.json +19 -0
  569. package/src/components/banner/banner.svelte +2 -2
  570. package/src/components/banner/banner.types.ts +1 -1
  571. package/src/components/button/button.css +1 -1
  572. package/src/components/callout/callout.examples.json +19 -0
  573. package/src/components/capability-gate/README.md +3 -3
  574. package/src/components/capability-gate/capability-gate.css +15 -2
  575. package/src/components/capability-gate/capability-gate.examples.json +1 -1
  576. package/src/components/capability-gate/capability-gate.svelte +7 -7
  577. package/src/components/capability-gate/capability-gate.types.ts +6 -6
  578. package/src/components/chat/README.md +6 -10
  579. package/src/components/chat/chat.examples.json +8 -8
  580. package/src/components/chat/chat.types.ts +31 -15
  581. package/src/components/chat/container/chat-events.ts +2 -2
  582. package/src/components/chat/container/chat-history-trigger.svelte +9 -1
  583. package/src/components/chat/container/chat.svelte +47 -46
  584. package/src/components/chat/container/use-chat-scroll-state.svelte.ts +21 -18
  585. package/src/components/chat/container/use-chat-unread-state.svelte.ts +19 -19
  586. package/src/components/chat/index.ts +1 -1
  587. package/src/components/chat/input/chat-input.svelte +35 -13
  588. package/src/components/chat/message/image-lightbox.svelte +38 -15
  589. package/src/components/chat/message/message-attachments.svelte +1 -1
  590. package/src/components/chat/message/parts/reasoning-part.svelte +8 -0
  591. package/src/components/chat/message/parts/tool-approval-part.svelte +22 -9
  592. package/src/components/chat-conversation-list/chat-conversation-list.css +13 -0
  593. package/src/components/checkbox/README.md +1 -0
  594. package/src/components/checkbox/checkbox.css +3 -0
  595. package/src/components/checkbox/checkbox.svelte +44 -2
  596. package/src/components/checkbox/checkbox.types.ts +2 -0
  597. package/src/components/checkbox-group/README.md +2 -2
  598. package/src/components/checkbox-group/checkbox-group.css +3 -0
  599. package/src/components/checkbox-group/checkbox-group.examples.json +13 -0
  600. package/src/components/checkbox-group/checkbox-group.svelte +23 -2
  601. package/src/components/checkbox-group/checkbox-group.types.ts +12 -7
  602. package/src/components/choice-grid-item/choice-grid-item.svelte +1 -1
  603. package/src/components/click-away-listener/README.md +2 -2
  604. package/src/components/click-away-listener/click-away-listener.examples.json +1 -1
  605. package/src/components/click-away-listener/click-away-listener.svelte +2 -2
  606. package/src/components/click-away-listener/click-away-listener.types.ts +3 -3
  607. package/src/components/code-block/code-block.css +4 -3
  608. package/src/components/collapsible/README.md +2 -2
  609. package/src/components/collapsible/collapsible.svelte +2 -2
  610. package/src/components/collapsible/collapsible.types.ts +5 -2
  611. package/src/components/color-field/README.md +18 -18
  612. package/src/components/color-field/color-field.svelte +2 -2
  613. package/src/components/color-field/color-field.types.ts +2 -2
  614. package/src/components/color-swatch-picker/color-swatch-picker.examples.json +13 -0
  615. package/src/components/color-swatch-picker/color-swatch-picker.svelte +38 -24
  616. package/src/components/combobox/README.md +2 -1
  617. package/src/components/combobox/combobox.svelte +38 -17
  618. package/src/components/combobox/combobox.types.ts +7 -1
  619. package/src/components/command-item/README.md +13 -11
  620. package/src/components/command-item/command-item.css +10 -0
  621. package/src/components/command-item/command-item.svelte +11 -0
  622. package/src/components/command-item/command-item.types.ts +4 -0
  623. package/src/components/command-menu/command-menu.svelte +21 -102
  624. package/src/components/command-palette/README.md +12 -12
  625. package/src/components/command-palette/command-palette.examples.json +3 -3
  626. package/src/components/command-palette/command-palette.svelte +17 -134
  627. package/src/components/command-palette/command-palette.types.ts +1 -2
  628. package/src/components/context-menu/context-menu.context.ts +2 -0
  629. package/src/components/context-menu/context-menu.svelte +35 -9
  630. package/src/components/context-menu-trigger/context-menu-trigger.svelte +9 -6
  631. package/src/components/copy-button/copy-button.svelte +4 -3
  632. package/src/components/data-grid/README.md +3 -3
  633. package/src/components/data-grid/_internal/column-model.svelte.ts +2 -0
  634. package/src/components/data-grid/data-grid.css +25 -0
  635. package/src/components/data-grid/data-grid.svelte +7 -7
  636. package/src/components/data-grid/data-grid.types.ts +8 -2
  637. package/src/components/data-list/README.md +9 -9
  638. package/src/components/data-list/data-list.examples.json +1 -1
  639. package/src/components/data-list/data-list.svelte +3 -9
  640. package/src/components/data-list/data-list.types.ts +5 -9
  641. package/src/components/data-table/data-table.css +13 -0
  642. package/src/components/data-table/data-table.svelte +13 -3
  643. package/src/components/date-range-field/README.md +9 -8
  644. package/src/components/date-range-field/date-range-field.svelte +193 -19
  645. package/src/components/date-range-field/date-range-field.types.ts +16 -10
  646. package/src/components/date-range-field/index.ts +1 -0
  647. package/src/components/diff-viewer/diff-front-matter.svelte +2 -2
  648. package/src/components/diff-viewer/diff-line.svelte +32 -8
  649. package/src/components/diff-viewer/diff-toolbar.svelte +3 -3
  650. package/src/components/diff-viewer/diff-viewer.svelte +4 -4
  651. package/src/components/diff-viewer/diff-viewer.types.ts +3 -3
  652. package/src/components/diff-viewer/index.ts +1 -1
  653. package/src/components/drawer/drawer.svelte +26 -145
  654. package/src/components/dropdown/dropdown.css +7 -1
  655. package/src/components/dropdown/dropdown.types.ts +3 -0
  656. package/src/components/dropdown-group/README.md +6 -6
  657. package/src/components/dropdown-group/dropdown-group.svelte +8 -8
  658. package/src/components/dropdown-group/dropdown-group.types.ts +4 -4
  659. package/src/components/dropdown-item/README.md +2 -0
  660. package/src/components/dropdown-item/dropdown-item.svelte +14 -11
  661. package/src/components/dropdown-item/dropdown-item.types.ts +5 -0
  662. package/src/components/dropdown-menu/dropdown-menu.svelte +42 -9
  663. package/src/components/dropdown-trigger/dropdown-trigger.svelte +4 -6
  664. package/src/components/event-stream-viewer/README.md +38 -16
  665. package/src/components/event-stream-viewer/event-stream-viewer-keys.ts +74 -0
  666. package/src/components/event-stream-viewer/event-stream-viewer.css +46 -0
  667. package/src/components/event-stream-viewer/event-stream-viewer.examples.json +6 -0
  668. package/src/components/event-stream-viewer/event-stream-viewer.svelte +262 -69
  669. package/src/components/event-stream-viewer/event-stream-viewer.types.ts +130 -3
  670. package/src/components/event-stream-viewer/index.ts +6 -0
  671. package/src/components/faceted-filter-bar/faceted-filter-bar.css +9 -0
  672. package/src/components/faceted-filter-bar/faceted-filter-bar.svelte +31 -5
  673. package/src/components/file-upload/file-upload.css +5 -5
  674. package/src/components/file-upload/file-upload.examples.json +13 -0
  675. package/src/components/floating-action-button/README.md +9 -9
  676. package/src/components/floating-action-button/floating-action-button.css +22 -22
  677. package/src/components/floating-action-button/floating-action-button.examples.json +1 -1
  678. package/src/components/floating-action-button/floating-action-button.svelte +7 -7
  679. package/src/components/floating-action-button/floating-action-button.types.ts +21 -21
  680. package/src/components/floating-action-button/index.ts +1 -1
  681. package/src/components/form-field/form-field.css +3 -13
  682. package/src/components/form-field/form-field.svelte +4 -1
  683. package/src/components/image/image.examples.json +13 -0
  684. package/src/components/input/README.md +16 -15
  685. package/src/components/input/input.examples.json +1 -1
  686. package/src/components/input/input.svelte +65 -28
  687. package/src/components/input/input.types.ts +2 -0
  688. package/src/components/invocation-rule-builder/README.md +14 -14
  689. package/src/components/invocation-rule-builder/index.ts +1 -0
  690. package/src/components/invocation-rule-builder/invocation-rule-builder.css +63 -6
  691. package/src/components/invocation-rule-builder/invocation-rule-builder.svelte +73 -16
  692. package/src/components/invocation-rule-builder/invocation-rule-builder.types.ts +99 -8
  693. package/src/components/json-schema-editor/form-view.svelte +3 -1
  694. package/src/components/json-schema-editor/json-schema-editor-impl.svelte +30 -18
  695. package/src/components/json-schema-editor/json-schema-editor.css +6 -1
  696. package/src/components/json-schema-editor/json-schema-toolbar.svelte +6 -0
  697. package/src/components/json-schema-editor/json-view.svelte +1 -1
  698. package/src/components/json-schema-editor/property-editor.svelte +38 -2
  699. package/src/components/json-schema-editor/property-list-validation.ts +10 -0
  700. package/src/components/json-schema-editor/property-list.svelte +48 -3
  701. package/src/components/json-viewer/_json-viewer-node.svelte +55 -9
  702. package/src/components/json-viewer/json-viewer.css +11 -0
  703. package/src/components/json-viewer/json-viewer.svelte +63 -1
  704. package/src/components/kanban-board/kanban-board.css +56 -3
  705. package/src/components/kanban-board/kanban-board.svelte +10 -3
  706. package/src/components/keyboard-shortcuts/keyboard-shortcuts.css +2 -2
  707. package/src/components/keyboard-shortcuts/keyboard-shortcuts.svelte +1 -1
  708. package/src/components/label/README.md +1 -1
  709. package/src/components/label/label.css +2 -9
  710. package/src/components/label/label.svelte +4 -2
  711. package/src/components/load-more/README.md +4 -4
  712. package/src/components/load-more/load-more.css +13 -5
  713. package/src/components/load-more/load-more.examples.json +2 -2
  714. package/src/components/load-more/load-more.svelte +4 -4
  715. package/src/components/load-more/load-more.types.ts +3 -3
  716. package/src/components/locale-provider/README.md +43 -0
  717. package/src/components/locale-provider/index.ts +5 -0
  718. package/src/components/locale-provider/locale-provider.svelte +40 -0
  719. package/src/components/locale-provider/locale-provider.types.ts +21 -0
  720. package/src/components/markdown-editor/README.md +3 -3
  721. package/src/components/markdown-editor/editor-toolbar/editor-toolbar.svelte +5 -2
  722. package/src/components/markdown-editor/editor-toolbar/link-popover.svelte +13 -0
  723. package/src/components/markdown-editor/editor-toolbar/toolbar-dropdown.svelte +2 -0
  724. package/src/components/markdown-editor/markdown-editor.svelte +14 -12
  725. package/src/components/markdown-editor/markdown-editor.types.ts +4 -4
  726. package/src/components/masonry/README.md +7 -7
  727. package/src/components/masonry/masonry.types.ts +24 -2
  728. package/src/components/matrix-chart/matrix-chart.svelte +7 -21
  729. package/src/components/media-controls/README.md +3 -3
  730. package/src/components/media-controls/media-controls.css +15 -4
  731. package/src/components/media-controls/media-controls.examples.json +1 -1
  732. package/src/components/media-controls/media-controls.svelte +6 -6
  733. package/src/components/media-controls/media-controls.types.ts +10 -7
  734. package/src/components/menu-bar/menu-bar.css +11 -0
  735. package/src/components/menu-bar/menu-bar.svelte +81 -12
  736. package/src/components/navigation-bar/README.md +1 -1
  737. package/src/components/navigation-bar/navigation-bar.examples.json +1 -1
  738. package/src/components/navigation-bar/navigation-bar.svelte +2 -2
  739. package/src/components/navigation-bar/navigation-bar.types.ts +1 -1
  740. package/src/components/number-input/README.md +18 -18
  741. package/src/components/number-input/number-input.examples.json +13 -0
  742. package/src/components/number-input/number-input.svelte +77 -38
  743. package/src/components/number-input/number-input.types.ts +15 -5
  744. package/src/components/pagination/README.md +3 -1
  745. package/src/components/payload-inspector/README.md +11 -11
  746. package/src/components/payload-inspector/index.ts +2 -0
  747. package/src/components/payload-inspector/payload-inspector.css +6 -0
  748. package/src/components/payload-inspector/payload-inspector.svelte +22 -4
  749. package/src/components/payload-inspector/payload-inspector.types.ts +54 -3
  750. package/src/components/permission-matrix/permission-matrix.css +10 -0
  751. package/src/components/phone-input/README.md +1 -1
  752. package/src/components/phone-input/phone-input.svelte +7 -1
  753. package/src/components/phone-input/phone-input.types.ts +3 -2
  754. package/src/components/pin-input/README.md +19 -19
  755. package/src/components/pin-input/pin-input.svelte +16 -12
  756. package/src/components/pin-input/pin-input.types.ts +10 -1
  757. package/src/components/portal/portal.utilities.svelte.ts +4 -1
  758. package/src/components/pricing-card/pricing-card.svelte +26 -1
  759. package/src/components/radio-group/README.md +12 -12
  760. package/src/components/radio-group/radio-group.css +3 -0
  761. package/src/components/radio-group/radio-group.examples.json +3 -3
  762. package/src/components/radio-group/radio-group.svelte +22 -2
  763. package/src/components/radio-group/radio-group.types.ts +6 -2
  764. package/src/components/rating/README.md +18 -18
  765. package/src/components/rating/rating.svelte +0 -8
  766. package/src/components/rating/rating.types.ts +6 -0
  767. package/src/components/review-editor/comment-list.svelte +1 -1
  768. package/src/components/review-editor/index.ts +1 -1
  769. package/src/components/review-editor/review-editor-controls.svelte +8 -3
  770. package/src/components/review-editor/review-editor-impl.svelte +11 -7
  771. package/src/components/review-editor/review-editor-selection-types.ts +1 -1
  772. package/src/components/review-editor/review-editor-selection.svelte.ts +1 -1
  773. package/src/components/review-editor/review-editor-state.svelte.ts +1 -1
  774. package/src/components/review-editor/review-editor-threads.svelte.ts +2 -2
  775. package/src/components/review-editor/review-editor.types.ts +91 -3
  776. package/src/components/review-editor/thread-popover.svelte +1 -1
  777. package/src/components/run-step-timeline/README.md +53 -6
  778. package/src/components/run-step-timeline/index.ts +6 -0
  779. package/src/components/run-step-timeline/run-step-timeline.css +25 -5
  780. package/src/components/run-step-timeline/run-step-timeline.examples.json +3 -3
  781. package/src/components/run-step-timeline/run-step-timeline.svelte +314 -78
  782. package/src/components/run-step-timeline/run-step-timeline.types.ts +190 -3
  783. package/src/components/schema-form/README.md +9 -9
  784. package/src/components/schema-form/index.ts +0 -1
  785. package/src/components/schema-form/schema-form-async.fixture.ts +101 -0
  786. package/src/components/schema-form/schema-form-body.svelte +646 -0
  787. package/src/components/schema-form/schema-form-model.ts +4 -44
  788. package/src/components/schema-form/schema-form-validation.ts +83 -46
  789. package/src/components/schema-form/schema-form.css +12 -74
  790. package/src/components/schema-form/schema-form.examples.json +0 -6
  791. package/src/components/schema-form/schema-form.svelte +30 -598
  792. package/src/components/schema-form/schema-form.types.ts +16 -12
  793. package/src/components/scroll-area/README.md +1 -1
  794. package/src/components/scroll-area/scroll-area.examples.json +3 -3
  795. package/src/components/scroll-area/scroll-area.svelte +5 -5
  796. package/src/components/scroll-area/scroll-area.types.ts +1 -1
  797. package/src/components/secret-value-field/secret-value-field.svelte +10 -6
  798. package/src/components/segmented-control/segmented-control.css +22 -1
  799. package/src/components/segmented-control/segmented-control.examples.json +2 -2
  800. package/src/components/select/select.css +9 -2
  801. package/src/components/select/select.svelte +6 -1
  802. package/src/components/selection-popover/selection-popover.examples.json +1 -1
  803. package/src/components/share-card/share-card.css +15 -4
  804. package/src/components/share-card/share-card.svelte +2 -4
  805. package/src/components/sheet/sheet.svelte +30 -145
  806. package/src/components/shortcut-hint/shortcut-hint.svelte +1 -1
  807. package/src/components/side-navigation/side-navigation.examples.json +1 -1
  808. package/src/components/sidebar/README.md +6 -2
  809. package/src/components/sidebar/sidebar.examples.json +1 -1
  810. package/src/components/sidebar/sidebar.svelte +4 -4
  811. package/src/components/sidebar/sidebar.types.ts +1 -1
  812. package/src/components/slider/slider.css +14 -9
  813. package/src/components/slider/slider.svelte +33 -2
  814. package/src/components/sortable-list/README.md +18 -1
  815. package/src/components/sortable-list/sortable-list.css +1 -1
  816. package/src/components/sortable-list/sortable-list.examples.json +13 -0
  817. package/src/components/spectrogram/spectrogram.examples.json +1 -0
  818. package/src/components/spectrogram/spectrogram.svelte +7 -19
  819. package/src/components/spectrum-chart/spectrum-chart.svelte +8 -20
  820. package/src/components/speed-dial/speed-dial.css +3 -3
  821. package/src/components/speed-dial/speed-dial.examples.json +1 -1
  822. package/src/components/speed-dial/speed-dial.svelte +2 -2
  823. package/src/components/speed-dial-action/speed-dial-action.svelte +1 -1
  824. package/src/components/stacked-list-item/stacked-list-item.examples.json +1 -1
  825. package/src/components/stat/README.md +1 -1
  826. package/src/components/stat/stat.svelte +7 -2
  827. package/src/components/stat/stat.types.ts +2 -2
  828. package/src/components/stat-group/stat-group.css +1 -1
  829. package/src/components/status-dot/README.md +9 -7
  830. package/src/components/status-dot/index.ts +6 -1
  831. package/src/components/status-dot/status-dot.css +45 -1
  832. package/src/components/status-dot/status-dot.examples.json +1 -1
  833. package/src/components/status-dot/status-dot.svelte +57 -8
  834. package/src/components/status-dot/status-dot.types.ts +17 -6
  835. package/src/components/steps/steps.css +3 -3
  836. package/src/components/table/table.css +10 -0
  837. package/src/components/table/table.examples.json +1 -1
  838. package/src/components/table/table.svelte +2 -2
  839. package/src/components/table/table.types.ts +3 -3
  840. package/src/components/table-header/README.md +2 -2
  841. package/src/components/table-header/table-header.svelte +5 -5
  842. package/src/components/table-header/table-header.types.ts +2 -2
  843. package/src/components/table-header-cell/table-header-cell.svelte +13 -2
  844. package/src/components/table-row/README.md +2 -2
  845. package/src/components/table-row/table-row.svelte +6 -6
  846. package/src/components/table-row/table-row.types.ts +6 -6
  847. package/src/components/tabs/README.md +1 -0
  848. package/src/components/tabs/tabs.svelte +11 -3
  849. package/src/components/tabs/tabs.types.ts +2 -0
  850. package/src/components/textarea/textarea.css +16 -1
  851. package/src/components/textarea/textarea.svelte +6 -1
  852. package/src/components/time-field/README.md +60 -0
  853. package/src/components/time-field/index.ts +6 -0
  854. package/src/components/time-field/time-field.css +60 -0
  855. package/src/components/time-field/time-field.examples.json +13 -0
  856. package/src/components/time-field/time-field.svelte +277 -0
  857. package/src/components/time-field/time-field.types.ts +70 -0
  858. package/src/components/toggle/README.md +12 -11
  859. package/src/components/toggle/toggle.svelte +19 -6
  860. package/src/components/toggle/toggle.types.ts +2 -0
  861. package/src/components/transfer-list/README.md +1 -1
  862. package/src/components/transfer-list/transfer-list.css +22 -0
  863. package/src/components/transfer-list/transfer-list.svelte +44 -10
  864. package/src/components/transfer-list/transfer-list.types.ts +5 -2
  865. package/src/components/tree/README.md +2 -2
  866. package/src/components/tree/tree.css +69 -0
  867. package/src/components/tree/tree.examples.json +1 -1
  868. package/src/components/tree/tree.svelte +52 -9
  869. package/src/components/tree/tree.types.ts +2 -2
  870. package/src/components/tree-item/README.md +4 -4
  871. package/src/components/tree-item/tree-item.svelte +42 -12
  872. package/src/components/tree-item/tree-item.types.ts +4 -4
  873. package/src/components/virtual-list/virtual-list.svelte +5 -5
  874. package/src/components/waveform/waveform.svelte +7 -19
  875. package/src/index.ts +40 -16
  876. package/src/styles/components/_field-label.css +20 -0
  877. package/src/styles/components/_input-frame.css +2 -1
  878. package/src/styles/components.css +3 -2
  879. package/src/styles/index.css +1 -0
  880. package/src/styles/tokens-base.css +3 -0
  881. package/src/utilities/attachments.ts +15 -12
  882. package/src/utilities/value-change.ts +11 -0
  883. package/dist/components/connection-indicator/connection-indicator.css +0 -63
  884. package/dist/components/connection-indicator/connection-indicator.svelte.d.ts +0 -18
  885. package/dist/components/connection-indicator/connection-indicator.types.d.ts +0 -14
  886. package/dist/components/connection-indicator/index.d.ts +0 -5
  887. package/dist/components/connection-indicator/index.js +0 -75
  888. package/dist/components/experimental/connection-indicator/index.d.ts +0 -2
  889. package/dist/components/review-editor/review-editor-types.d.ts +0 -77
  890. package/dist/components/subscription-badge/index.d.ts +0 -5
  891. package/dist/components/subscription-badge/index.js +0 -353
  892. package/dist/components/subscription-badge/subscription-badge.css +0 -18
  893. package/dist/components/subscription-badge/subscription-badge.schema.js +0 -47
  894. package/dist/components/subscription-badge/subscription-badge.svelte.d.ts +0 -19
  895. package/dist/components/subscription-badge/subscription-badge.types.d.ts +0 -28
  896. package/dist/server/components/connection-indicator/connection-indicator.schema.js +0 -31
  897. package/dist/server/components/connection-indicator/connection-indicator.variables.js +0 -12
  898. package/dist/server/components/connection-indicator/index.css +0 -60
  899. package/dist/server/components/connection-indicator/index.js +0 -42
  900. package/dist/server/components/experimental/connection-indicator/index.js +0 -23
  901. package/dist/server/components/subscription-badge/index.css +0 -2
  902. package/dist/server/components/subscription-badge/index.js +0 -308
  903. package/dist/server/components/subscription-badge/subscription-badge.schema.js +0 -28
  904. package/dist/server/components/subscription-badge/subscription-badge.variables.js +0 -12
  905. package/src/components/connection-indicator/README.md +0 -41
  906. package/src/components/connection-indicator/connection-indicator.css +0 -63
  907. package/src/components/connection-indicator/connection-indicator.svelte +0 -44
  908. package/src/components/connection-indicator/connection-indicator.types.ts +0 -18
  909. package/src/components/connection-indicator/index.ts +0 -6
  910. package/src/components/experimental/connection-indicator/index.ts +0 -20
  911. package/src/components/review-editor/review-editor-types.ts +0 -92
  912. package/src/components/subscription-badge/README.md +0 -50
  913. package/src/components/subscription-badge/index.ts +0 -6
  914. package/src/components/subscription-badge/subscription-badge.css +0 -18
  915. package/src/components/subscription-badge/subscription-badge.examples.json +0 -19
  916. package/src/components/subscription-badge/subscription-badge.svelte +0 -69
  917. package/src/components/subscription-badge/subscription-badge.types.ts +0 -37
  918. /package/dist/components/{connection-indicator/connection-indicator.schema.d.ts → approval-card/approval-card.schema.d.ts} +0 -0
  919. /package/dist/components/{connection-indicator/connection-indicator.variables.d.ts → approval-card/approval-card.variables.d.ts} +0 -0
  920. /package/dist/components/{subscription-badge/subscription-badge.schema.d.ts → locale-provider/locale-provider.schema.d.ts} +0 -0
  921. /package/dist/components/{subscription-badge/subscription-badge.variables.d.ts → locale-provider/locale-provider.variables.d.ts} +0 -0
@@ -33,7 +33,7 @@
33
33
  let {
34
34
  variant = 'info',
35
35
  dismissible = true,
36
- onDismiss,
36
+ ondismiss,
37
37
  class: className,
38
38
  children,
39
39
  actions,
@@ -49,7 +49,7 @@
49
49
  visible = false;
50
50
  await tick();
51
51
  if (focusTarget?.isConnected) focusTarget.focus();
52
- onDismiss?.();
52
+ ondismiss?.();
53
53
  }
54
54
 
55
55
  function resolveFocusTarget(bannerElement: HTMLElement): HTMLElement | null {
@@ -32,7 +32,7 @@ export type BannerProps = Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'cla
32
32
  /** Whether the banner shows a dismiss (×) button. Default `true`. */
33
33
  dismissible?: boolean;
34
34
  /** Called after the dismiss button is clicked. Use to persist state. */
35
- onDismiss?: () => void;
35
+ ondismiss?: () => void;
36
36
  /** Banner body content. */
37
37
  children: Snippet;
38
38
  /** Optional trailing CTA region (e.g., "Renew now" button). */
@@ -86,7 +86,7 @@
86
86
  border: 2px solid currentColor;
87
87
  border-inline-end-color: transparent;
88
88
  border-radius: 50%;
89
- animation: cinder-button-spin 0.6s linear infinite;
89
+ animation: cinder-button-spin var(--cinder-duration-spin) linear infinite;
90
90
  }
91
91
 
92
92
  /* At `xs` size the button is ~24px tall and the spinner ends up ~9px square; a 2px border
@@ -0,0 +1,19 @@
1
+ {
2
+ "$schema": "../../schemas/examples.schema.json",
3
+ "component": "callout",
4
+ "import": "@lostgradient/cinder/callout",
5
+ "examples": [
6
+ {
7
+ "id": "basic",
8
+ "title": "Basic callout",
9
+ "description": "Inline aside highlighting a tip next to body content.",
10
+ "code": "<script lang=\"ts\">\n import { Callout } from '@lostgradient/cinder/callout';\n</script>\n\n<Callout title=\"Note\">\n This setting applies to all projects in your organization and cannot be overridden per-project.\n</Callout>\n"
11
+ },
12
+ {
13
+ "id": "variants",
14
+ "title": "Callout variants",
15
+ "description": "All four semantic variant styles for inline callouts.",
16
+ "code": "<script lang=\"ts\">\n import { Callout } from '@lostgradient/cinder/callout';\n</script>\n\n<div style=\"display: flex; flex-direction: column; gap: 0.75rem;\">\n <Callout variant=\"info\" title=\"Info\">Keyboard shortcuts are available for most actions.</Callout>\n <Callout variant=\"success\" title=\"Success\">Your configuration has been validated.</Callout>\n <Callout variant=\"warning\" title=\"Warning\">This action cannot be undone once confirmed.</Callout>\n <Callout variant=\"danger\" title=\"Danger\">Deleting this record cannot be undone.</Callout>\n</div>\n"
17
+ }
18
+ ]
19
+ }
@@ -37,9 +37,9 @@ Present feature availability and next action for browser permission or support s
37
37
  | `state` | `"supported"` \| `"unsupported"` \| `"permission-needed"` \| `"permission-denied"` \| `"loading"` \| `"unavailable"` | yes | — | Current availability state. |
38
38
  | `variant` | `"inline"` \| `"banner"` \| `"callout"` | no | `"inline"` | Presentation variant. |
39
39
  | `children` | `(opaque)` | no | — | Custom content rendered below the status text and before the actions. Not expressible in JSON Schema; see the component types for the signature. |
40
- | `onDismiss` | `(opaque)` | no | — | Called when the gate is dismissed. Not expressible in JSON Schema; see the component types for the signature. |
41
- | `onFallbackAction` | `(opaque)` | no | — | Called when the fallback action button is activated. Not expressible in JSON Schema; see the component types for the signature. |
42
- | `onPrimaryAction` | `(opaque)` | no | — | Called when the primary action button is activated. Not expressible in JSON Schema; see the component types for the signature. |
40
+ | `ondismiss` | `(opaque)` | no | — | Called when the gate is dismissed. Not expressible in JSON Schema; see the component types for the signature. |
41
+ | `onfallbackaction` | `(opaque)` | no | — | Called when the fallback action button is activated. Not expressible in JSON Schema; see the component types for the signature. |
42
+ | `onprimaryaction` | `(opaque)` | no | — | Called when the primary action button is activated. Not expressible in JSON Schema; see the component types for the signature. |
43
43
 
44
44
  <!-- generated:props:end -->
45
45
 
@@ -49,7 +49,7 @@
49
49
  }
50
50
 
51
51
  .cinder-capability-gate__feature {
52
- font-weight: 600;
52
+ font-weight: var(--cinder-font-semibold);
53
53
  font-size: var(--cinder-text-sm);
54
54
  /* No text-transform: `feature` is consumer-owned text — capitalizing it
55
55
  would corrupt names like "MIDI" → "Midi" or "iOS" → "Ios". */
@@ -79,7 +79,7 @@
79
79
  padding: var(--cinder-space-1) var(--cinder-space-2);
80
80
  border-radius: var(--cinder-radius-sm);
81
81
  font-size: var(--cinder-text-sm);
82
- font-weight: 500;
82
+ font-weight: var(--cinder-font-medium);
83
83
  cursor: pointer;
84
84
  border: 1px solid transparent;
85
85
  text-decoration: none;
@@ -107,6 +107,19 @@
107
107
  0 0 0 calc(var(--cinder-ring-offset) + var(--cinder-ring-width)) var(--cinder-ring-color);
108
108
  }
109
109
 
110
+ @media (forced-colors: active) {
111
+ .cinder-capability-gate__primary:focus-visible,
112
+ .cinder-capability-gate__fallback:focus-visible,
113
+ .cinder-capability-gate__dismiss:focus-visible {
114
+ outline: var(--cinder-ring-width) solid ButtonText;
115
+ /* 3px separates the outline from ButtonBorder (which renders in forced-colors
116
+ on all button-like controls and shares the ButtonText color family in HCM);
117
+ at 2px the ring and border can visually merge. */
118
+ outline-offset: 3px;
119
+ box-shadow: none;
120
+ }
121
+ }
122
+
110
123
  .cinder-capability-gate__fallback {
111
124
  background-color: transparent;
112
125
  color: var(--cinder-text);
@@ -7,7 +7,7 @@
7
7
  "id": "microphone",
8
8
  "title": "Microphone permission gate",
9
9
  "description": "Shows the capability gate in each state for a microphone permission request.",
10
- "code": "<script lang=\"ts\">\n import { Button } from '@lostgradient/cinder/button';\n import { CapabilityGate } from '@lostgradient/cinder/capability-gate';\n import type { CapabilityGateState } from '@lostgradient/cinder/capability-gate';\n\n let capabilityState = $state<CapabilityGateState>('permission-needed');\n const needsAction = $derived(capabilityState === 'permission-needed');\n\n const states: CapabilityGateState[] = [\n 'supported',\n 'unsupported',\n 'permission-needed',\n 'permission-denied',\n 'loading',\n 'unavailable',\n ];\n</script>\n\n<div style=\"display: flex; flex-direction: column; gap: 1rem; max-width: 28rem;\">\n {#if needsAction}\n <CapabilityGate\n feature=\"Microphone\"\n state={capabilityState}\n primaryAction=\"Allow microphone\"\n onPrimaryAction={() => {\n capabilityState = 'supported';\n }}\n fallbackAction=\"Use text instead\"\n onFallbackAction={() => {\n capabilityState = 'unsupported';\n }}\n >\n <p style=\"font-size: 0.875rem; color: var(--cinder-text-muted); margin: 0;\">\n Microphone access is needed for voice input. Your audio is never stored or shared.\n </p>\n </CapabilityGate>\n {:else}\n <CapabilityGate feature=\"Microphone\" state={capabilityState} />\n {/if}\n\n <div style=\"display: flex; gap: 0.5rem; flex-wrap: wrap;\">\n {#each states as s (s)}\n <Button\n size=\"sm\"\n variant={capabilityState === s ? 'secondary' : 'ghost'}\n onclick={() => {\n capabilityState = s;\n }}\n >\n {s}\n </Button>\n {/each}\n </div>\n</div>\n"
10
+ "code": "<script lang=\"ts\">\n import { Button } from '@lostgradient/cinder/button';\n import { CapabilityGate } from '@lostgradient/cinder/capability-gate';\n import type { CapabilityGateState } from '@lostgradient/cinder/capability-gate';\n\n let capabilityState = $state<CapabilityGateState>('permission-needed');\n const needsAction = $derived(capabilityState === 'permission-needed');\n\n const states: CapabilityGateState[] = [\n 'supported',\n 'unsupported',\n 'permission-needed',\n 'permission-denied',\n 'loading',\n 'unavailable',\n ];\n</script>\n\n<div style=\"display: flex; flex-direction: column; gap: 1rem; max-width: 28rem;\">\n {#if needsAction}\n <CapabilityGate\n feature=\"Microphone\"\n state={capabilityState}\n primaryAction=\"Allow microphone\"\n onprimaryaction={() => {\n capabilityState = 'supported';\n }}\n fallbackAction=\"Use text instead\"\n onfallbackaction={() => {\n capabilityState = 'unsupported';\n }}\n >\n <p style=\"font-size: 0.875rem; color: var(--cinder-text-muted); margin: 0;\">\n Microphone access is needed for voice input. Your audio is never stored or shared.\n </p>\n </CapabilityGate>\n {:else}\n <CapabilityGate feature=\"Microphone\" state={capabilityState} />\n {/if}\n\n <div style=\"display: flex; gap: 0.5rem; flex-wrap: wrap;\">\n {#each states as s (s)}\n <Button\n size=\"sm\"\n variant={capabilityState === s ? 'secondary' : 'ghost'}\n onclick={() => {\n capabilityState = s;\n }}\n >\n {s}\n </Button>\n {/each}\n </div>\n</div>\n"
11
11
  },
12
12
  {
13
13
  "id": "notifications",
@@ -51,12 +51,12 @@
51
51
  state: stateProp,
52
52
  variant = 'inline',
53
53
  primaryAction,
54
- onPrimaryAction,
54
+ onprimaryaction,
55
55
  fallbackAction,
56
56
  fallbackHref,
57
- onFallbackAction,
57
+ onfallbackaction,
58
58
  dismissAction,
59
- onDismiss,
59
+ ondismiss,
60
60
  children,
61
61
  class: customClassName,
62
62
  ...rest
@@ -104,10 +104,10 @@
104
104
  // Blur the dismiss button before it unmounts so focus moves to <body>
105
105
  // predictably rather than being stranded on a detached node. The consumer
106
106
  // owns where focus should land next (e.g. a trigger to re-open the gate) and
107
- // can set it in `onDismiss` — the component cannot know the right target.
107
+ // can set it in `ondismiss` — the component cannot know the right target.
108
108
  dismissButton?.blur();
109
109
  visible = false;
110
- onDismiss?.();
110
+ ondismiss?.();
111
111
  }
112
112
  </script>
113
113
 
@@ -143,7 +143,7 @@
143
143
  <button
144
144
  type="button"
145
145
  class="cinder-capability-gate__primary"
146
- onclick={onPrimaryAction}
146
+ onclick={onprimaryaction}
147
147
  aria-label="{primaryAction} for {feature}"
148
148
  >
149
149
  {primaryAction}
@@ -162,7 +162,7 @@
162
162
  <button
163
163
  type="button"
164
164
  class="cinder-capability-gate__fallback"
165
- onclick={onFallbackAction}
165
+ onclick={onfallbackaction}
166
166
  aria-label="{fallbackAction} for {feature}"
167
167
  >
168
168
  {fallbackAction}
@@ -27,13 +27,13 @@ export type CapabilityGateProps = Omit<
27
27
  /** Label for the primary action button (e.g. "Allow access", "Enable notifications"). */
28
28
  primaryAction?: string;
29
29
  /** Called when the user clicks the primary action button. */
30
- onPrimaryAction?: () => void;
30
+ onprimaryaction?: () => void;
31
31
  /** Label for the fallback action (e.g. "Use a different method"). */
32
32
  fallbackAction?: string;
33
33
  /** Href for a fallback link (renders an anchor instead of a button). */
34
34
  fallbackHref?: string;
35
35
  /** Called when the user clicks the fallback action (only when no fallbackHref). */
36
- onFallbackAction?: () => void;
36
+ onfallbackaction?: () => void;
37
37
  /** Label for the dismiss action. When provided a dismiss button is rendered. */
38
38
  dismissAction?: string;
39
39
  /**
@@ -42,7 +42,7 @@ export type CapabilityGateProps = Omit<
42
42
  * re-opens the gate) — the component blurs the dismiss button first so focus
43
43
  * is not stranded, but only the consumer knows the right next focus target.
44
44
  */
45
- onDismiss?: () => void;
45
+ ondismiss?: () => void;
46
46
  /** Custom content rendered below the status text and before the actions. */
47
47
  children?: Snippet;
48
48
  /** Additional class names merged with `.cinder-capability-gate`. */
@@ -73,11 +73,11 @@ export interface CapabilityGateSchemaProps {
73
73
  /** Additional class names merged with `.cinder-capability-gate`. */
74
74
  class?: string;
75
75
  /** Called when the primary action button is activated. */
76
- onPrimaryAction?: () => void;
76
+ onprimaryaction?: () => void;
77
77
  /** Called when the fallback action button is activated. */
78
- onFallbackAction?: () => void;
78
+ onfallbackaction?: () => void;
79
79
  /** Called when the gate is dismissed. */
80
- onDismiss?: () => void;
80
+ ondismiss?: () => void;
81
81
  /** Custom content rendered below the status text and before the actions. */
82
82
  children?: Snippet;
83
83
  }
@@ -116,24 +116,19 @@ of overflowing the page.
116
116
 
117
117
  | Prop | Type | Required | Default | Description |
118
118
  | ---------------------------------- | ------------------------------ | -------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
119
- | `allowAttachments` | `boolean` | no | — | Whether file attachments are enabled in the composer. When `false`, the attachment button is hidden and drag-and-drop onto the chat surface is suppressed. Default `true`. |
120
- | `allowCopy` | `boolean` | no | — | Whether per-message copy buttons are shown in the message action bar. Default `true`. |
121
- | `allowEditing` | `boolean` | no | — | Whether user messages can be edited inline. Default `true`. |
122
- | `allowRetry` | `boolean` | no | — | Whether failed assistant messages show a retry button. Default `true`. |
123
- | `allowSearch` | `boolean` | no | — | Whether in-conversation search is enabled. When `true`, pressing Ctrl+F / Cmd+F opens a search bar that highlights matching messages. Default `true`. |
119
+ | `atBottom` | `boolean` | no | — | Whether the message viewport is scrolled to the bottom. Bindable; updated automatically as the user scrolls. Default `true`. |
124
120
  | `bottomThreshold` | `number` | no | — | Distance in pixels from the bottom of the scroll viewport within which the chat is considered "at bottom" and will auto-scroll on new messages. Default `150`. |
125
121
  | `class` | `string` | no | — | Additional class name merged onto the `.chat-container` root element. |
126
122
  | `density` | `"comfortable"` \| `"compact"` | no | — | Controls spacing density of the message timeline. - `'comfortable'` (default): standard padding and gap. - `'compact'`: tighter spacing for data-dense contexts (e.g., embedded panels). Action buttons keep `min-height: var(--cinder-touch-target-min)` regardless. |
127
123
  | `emptyPrompts` | `string`[] | no | — | List of suggested starter prompt strings shown as clickable buttons in the default empty state. Clicking a prompt submits it immediately as a user message. Has no effect when a custom `empty` snippet is provided. |
128
- | `hasMoreHistory` | `boolean` | no | — | Whether the explicit "Load earlier messages" trigger is shown when a load handler exists. Default `true`. |
129
- | `hasNewMessageIndicator` | `boolean` | no | — | Whether the "new messages" indicator is currently visible above the composer. Bindable; cleared automatically when the viewport reaches the bottom. Default `false`. |
130
124
  | `id` | `string` | yes | — | Unique identifier used to scope accessibility attributes across the chat surface. |
131
- | `isAtBottom` | `boolean` | no | — | Whether the message viewport is scrolled to the bottom. Bindable; updated automatically as the user scrolls. Default `true`. |
132
- | `isStreaming` | `boolean` | no | — | Whether a streaming response is currently in progress. When `true`, the composer is disabled and a stop-generating button is shown. Default `false`. |
133
125
  | `jumpThreshold` | `number` | no | — | Distance in pixels scrolled up from the bottom required before the jump-to-latest button appears. Must be greater than `bottomThreshold` to prevent button flickering. Default `200`. |
134
126
  | `loadEarlierLabel` | `string` | no | — | Label for the history pagination trigger. Default `"Load earlier messages"`. |
135
127
  | `loadingEarlierLabel` | `string` | no | — | Status text while older messages are loading. Default `"Loading earlier messages"`. |
136
- | `streamingStatus` | `string` | no | — | Optional status label displayed in the typing indicator while `isStreaming` is `true` and no streaming content has arrived yet (e.g. `"Thinking…"` or `"Analyzing file…"`). When omitted, three animated dots are shown. |
128
+ | `moreHistoryAvailable` | `boolean` | no | — | Whether the explicit "Load earlier messages" trigger is shown when a load handler exists. Default `true`. |
129
+ | `newMessageIndicatorVisible` | `boolean` | no | — | Whether the "new messages" indicator is currently visible above the composer. Bindable; cleared automatically when the viewport reaches the bottom. Default `false`. |
130
+ | `streaming` | `boolean` | no | — | Whether a streaming response is currently in progress. When `true`, the composer is disabled and a stop-generating button is shown. Default `false`. |
131
+ | `streamingStatus` | `string` | no | — | Optional status label displayed in the typing indicator while `streaming` is `true` and no streaming content has arrived yet (e.g. `"Thinking…"` or `"Analyzing file…"`). When omitted, three animated dots are shown. |
137
132
  | `surfaceMode` | `"default"` \| `"transparent"` | no | — | Controls the background of the chat surface. Use `'transparent'` to inherit the host element's background when embedding chat inside a card or panel. Default `'default'`. |
138
133
  | `unreadCount` | `number` | no | — | Number of messages received while the viewport was scrolled away from the bottom. Bindable; resets to `0` when the user scrolls to the bottom. Default `0`. |
139
134
  | `variant` | `"bubble"` \| `"flat"` | no | — | Visual treatment for message bubbles. - `'bubble'` (default): colored backgrounds differentiate user from assistant. - `'flat'`: no bubble backgrounds; role is communicated via alignment and role label. Text-on-surface contrast meets WCAG AA via `--cinder-text` on `--cinder-surface-inset`. |
@@ -142,6 +137,7 @@ of overflowing the page.
142
137
  | `virtualizationOverscan` | `number` | no | — | Number of extra virtual rows rendered before and after the viewport. Default `3`. |
143
138
  | `virtualized` | `boolean` | no | — | Use the virtualized message render path for long transcripts. The complete `ConversationHistory` remains unchanged; only the DOM window is reduced. Default `false`. |
144
139
  | `adapter` | `(opaque)` | no | — | Optional command/transport boundary around `conversation`. Its methods take precedence over the matching callback props (e.g. `sendMessage` over `onsubmit`); omit it and Chat behaves exactly as with plain callbacks. Not expressible in JSON Schema; see the component types for the signature. |
140
+ | `capabilities` | `(opaque)` | no | — | Feature-capability flags. Pass a `ChatCapabilities` object to enable or disable individual Chat features (attachments, search, copy, editing, retry) as a group rather than as five separate boolean props. Not expressible in JSON Schema; see the component types for the signature. |
145
141
  | `conversation` | `(opaque)` | yes | — | The conversation transcript to render. Pass a {@link ConversationHistory} snapshot; consumers holding a stateful conversation object pass its current snapshot (e.g. `conversation.current`). Not expressible in JSON Schema; see the component types for the signature. |
146
142
  | `empty` | `(opaque)` | no | — | A function or snippet prop. Its shape is not captured by the JSON schema; see the component types for the exact signature. |
147
143
  | `header` | `(opaque)` | no | — | A function or snippet prop. Its shape is not captured by the JSON schema; see the component types for the exact signature. |
@@ -7,49 +7,49 @@
7
7
  "id": "basic",
8
8
  "title": "Basic chat",
9
9
  "description": "A conversation with editable input and markdown rendering.",
10
- "code": "<script lang=\"ts\">\n import {\n Chat,\n appendAssistantMessage,\n appendUserMessage,\n createConversation,\n } from '@lostgradient/cinder/chat';\n\n // `id` below is the conversation snapshot's data key — it never reaches a DOM\n // element id (Chat derives every rendered id from its `id` prop, scoped above\n // as `chatId`), so it needs no mount-prefix scoping.\n let conversation = $state(\n appendAssistantMessage(\n appendUserMessage(createConversation({ id: 'basic-chat' }), 'Can you summarize this plan?'),\n `The plan is ready to implement.\n\n1. Port the component surface.\n2. Add focused demos.\n3. Keep the public API importable by subpath.`,\n ),\n );\n</script>\n\n<div style=\"height: 34rem;\">\n <Chat id=\"chat\" {conversation} allowAttachments={false} />\n</div>\n"
10
+ "code": "<script lang=\"ts\">\n import {\n Chat,\n type ChatSubmitEvent,\n appendAssistantMessage,\n appendUserMessage,\n createConversation,\n } from '@lostgradient/cinder/chat';\n\n // `id` below is the conversation snapshot's data key — it never reaches a DOM\n // element id (Chat derives every rendered id from its `id` prop, scoped above\n // as `chatId`), so it needs no mount-prefix scoping.\n let conversation = $state(\n appendAssistantMessage(\n appendUserMessage(createConversation({ id: 'basic-chat' }), 'Can you summarize this plan?'),\n `The plan is ready to implement.\n\n1. Port the component surface.\n2. Add focused demos.\n3. Keep the public API importable by subpath.`,\n ),\n );\n\n function handleSubmit(event: ChatSubmitEvent): void {\n conversation = appendUserMessage(conversation, event.message.content);\n }\n</script>\n\n<div style=\"height: 34rem;\">\n <Chat id=\"chat\" {conversation} capabilities={{ attachments: false }} onsubmit={handleSubmit} />\n</div>\n"
11
11
  },
12
12
  {
13
13
  "id": "density-and-variant",
14
14
  "title": "Density and variant",
15
15
  "description": "Compare comfortable vs compact density and bubble vs flat visual variant on the same conversationalist-shaped transcript.",
16
- "code": "<script lang=\"ts\">\n import {\n Chat,\n appendAssistantMessage,\n appendUserMessage,\n createConversation,\n } from '@lostgradient/cinder/chat';\n import { Select } from '@lostgradient/cinder/select';\n\n // Build a shared conversationalist-shaped transcript (no Cinder-only fields).\n const baseConversation = appendAssistantMessage(\n appendUserMessage(\n appendAssistantMessage(\n appendUserMessage(\n createConversation({ id: 'density-variant-chat' }),\n 'What are the density and variant props for?',\n ),\n 'The `density` prop controls spacing — `comfortable` uses standard padding and gap, `compact` tightens them for data-dense contexts like embedded side panels.\\n\\nThe `variant` prop controls visual treatment — `bubble` uses colored backgrounds to differentiate roles; `flat` removes backgrounds and relies on alignment and the role label instead.',\n ),\n 'Does compact affect the action buttons?',\n ),\n 'No — action buttons keep `min-height: var(--cinder-touch-target-min)` regardless of density. Only padding and gap change.',\n );\n\n type Density = 'comfortable' | 'compact';\n type Variant = 'bubble' | 'flat';\n\n const densityOptions = [\n { value: 'comfortable', label: 'comfortable' },\n { value: 'compact', label: 'compact' },\n ] satisfies { value: Density; label: string }[];\n\n const variantOptions = [\n { value: 'bubble', label: 'bubble' },\n { value: 'flat', label: 'flat' },\n ] satisfies { value: Variant; label: string }[];\n\n let density = $state<Density>('comfortable');\n let variant = $state<Variant>('bubble');\n</script>\n\n<div style=\"display: grid; gap: 1rem;\">\n <div style=\"display: flex; gap: 1rem; align-items: flex-end; flex-wrap: wrap;\">\n <Select id=\"density-select\" bind:value={density} options={densityOptions} label=\"Density\" />\n <Select id=\"variant-select\" bind:value={variant} options={variantOptions} label=\"Variant\" />\n </div>\n\n <div style=\"height: 28rem;\">\n <Chat\n id=\"density-variant\"\n conversation={baseConversation}\n {density}\n {variant}\n allowAttachments={false}\n />\n </div>\n</div>\n"
16
+ "code": "<script lang=\"ts\">\n import {\n Chat,\n appendAssistantMessage,\n appendUserMessage,\n createConversation,\n } from '@lostgradient/cinder/chat';\n import { Select } from '@lostgradient/cinder/select';\n\n // Build a shared conversationalist-shaped transcript (no Cinder-only fields).\n const baseConversation = appendAssistantMessage(\n appendUserMessage(\n appendAssistantMessage(\n appendUserMessage(\n createConversation({ id: 'density-variant-chat' }),\n 'What are the density and variant props for?',\n ),\n 'The `density` prop controls spacing — `comfortable` uses standard padding and gap, `compact` tightens them for data-dense contexts like embedded side panels.\\n\\nThe `variant` prop controls visual treatment — `bubble` uses colored backgrounds to differentiate roles; `flat` removes backgrounds and relies on alignment and the role label instead.',\n ),\n 'Does compact affect the action buttons?',\n ),\n 'No — action buttons keep `min-height: var(--cinder-touch-target-min)` regardless of density. Only padding and gap change.',\n );\n\n type Density = 'comfortable' | 'compact';\n type Variant = 'bubble' | 'flat';\n\n const densityOptions = [\n { value: 'comfortable', label: 'comfortable' },\n { value: 'compact', label: 'compact' },\n ] satisfies { value: Density; label: string }[];\n\n const variantOptions = [\n { value: 'bubble', label: 'bubble' },\n { value: 'flat', label: 'flat' },\n ] satisfies { value: Variant; label: string }[];\n\n let density = $state<Density>('comfortable');\n let variant = $state<Variant>('bubble');\n</script>\n\n<div style=\"display: grid; gap: 1rem;\">\n <div style=\"display: flex; gap: 1rem; align-items: flex-end; flex-wrap: wrap;\">\n <Select id=\"density-select\" bind:value={density} options={densityOptions} label=\"Density\" />\n <Select id=\"variant-select\" bind:value={variant} options={variantOptions} label=\"Variant\" />\n </div>\n\n <div style=\"height: 28rem;\">\n <Chat\n id=\"density-variant\"\n conversation={baseConversation}\n {density}\n {variant}\n capabilities={{ attachments: false }}\n />\n </div>\n</div>\n"
17
17
  },
18
18
  {
19
19
  "id": "interactive-harness",
20
20
  "title": "Interactive harness",
21
21
  "description": "Drive Chat from a control panel: reply as the other side (instant / typing / streaming), inject tool calls, toggle features, and watch every callback fire.",
22
- "code": "<script lang=\"ts\" module>\n export const title = 'Interactive harness';\n export const description =\n 'Drive Chat from a control panel: reply as the other side (instant / typing / streaming), inject tool calls, toggle features, and watch every callback fire.';\n\n export function createHistoryMessageTimestamp(page: number, index: number): string {\n return new Date(Date.UTC(2026, 4, 30 - page, 12, index)).toISOString();\n }\n</script>\n\n<script lang=\"ts\">\n import {\n Chat,\n appendAssistantMessage,\n appendMessages,\n appendUserMessage,\n createConversation,\n type ChatSubmitEvent,\n type ConversationHistory,\n type JSONValue,\n type Message,\n type ToolErrorCategory,\n } from '@lostgradient/cinder/chat';\n import { Button } from '@lostgradient/cinder/button';\n import { Input } from '@lostgradient/cinder/input';\n import { Segment } from '@lostgradient/cinder/segment';\n import { SegmentedControl } from '@lostgradient/cinder/segmented-control';\n import { Select } from '@lostgradient/cinder/select';\n import { Textarea } from '@lostgradient/cinder/textarea';\n import { Toggle } from '@lostgradient/cinder/toggle';\n import { onDestroy } from 'svelte';\n\n // Out of scope for this harness (documented, not silently dropped):\n // bottomThreshold / jumpThreshold (kept at defaults so overflow + jump are\n // testable without tuning), viewportAttachment (advanced escape hatch), and\n // the isAtBottom / unreadCount / hasNewMessageIndicator bindables (read-only\n // mirrors of behavior the wired callbacks already surface).\n\n // --- Chat instance (for the imperative streaming + scroll API) ---\n // Plain `let`: only read via `chat?.method()` calls, never reactively.\n let chat: ReturnType<typeof Chat> | undefined;\n\n // --- Conversation state (immutable snapshots via the cinder/chat builders) ---\n let conversation = $state<ConversationHistory>(createConversation({ id: 'harness' }));\n\n // --- Feature toggles wired straight onto <Chat> ---\n let allowAttachments = $state(true);\n let allowSearch = $state(true);\n let allowCopy = $state(true);\n let allowEditing = $state(true);\n let allowRetry = $state(true);\n let transparentSurface = $state(false);\n let withEmptyPrompts = $state(true);\n let autoReply = $state(true);\n let virtualized = $state(false);\n let historyEnabled = $state(false);\n let historyPage = 0;\n\n // --- Reply controls ---\n let replyText = $state('Here is the answer, delivered in a few deliberate chunks.');\n let replyMode = $state<'instant' | 'typing' | 'streaming'>('typing');\n let streamMechanism = $state<'imperative' | 'content-mutation'>('imperative');\n let isStreaming = $state(false);\n let streamingStatus = $state('');\n\n // --- Tool-call controls ---\n let toolName = $state('exports_check');\n let toolArguments = $state('{ \"package\": \"@lostgradient/cinder\" }');\n let toolOutcome = $state<'success' | 'error' | 'action_required'>('success');\n const parsedToolArguments = $derived.by<{ ok: boolean; value: JSONValue }>(() => {\n try {\n return { ok: true, value: JSON.parse(toolArguments) as JSONValue };\n } catch {\n return { ok: false, value: null };\n }\n });\n\n // --- Event log (queryable: data-event + data-payload per entry) ---\n // Chat emits a burst of identical onunreadindicatorchange events while its\n // empty state settles on mount (all `{unreadCount:0, hasNewMessageIndicator:\n // false}`). We de-duplicate: a callback whose (event, payload) is identical to\n // the most recent entry for that event is dropped. That kills the mount noise\n // deterministically (no timing dependency) while still recording every real,\n // distinct callback so tests can assert \"this fired because of my action\".\n type LogEntry = { id: number; event: string; payload: string };\n let log = $state<LogEntry[]>([]);\n let logCounter = 0;\n const lastPayloadByEvent = new Map<string, string>();\n function record(event: string, payload: unknown = ''): void {\n const text = typeof payload === 'string' ? payload : JSON.stringify(payload);\n if (lastPayloadByEvent.get(event) === text) return;\n lastPayloadByEvent.set(event, text);\n logCounter += 1;\n log = [{ id: logCounter, event, payload: text }, ...log].slice(0, 50);\n }\n\n // --- Pending-operation discipline (instance-local; NEVER module scope) ---\n // A single in-flight typing/streaming op at a time. Timers are tracked so we\n // can cancel deterministically; streamingPartial holds the accumulated text so\n // Stop can preserve it.\n let pendingTimers = new Set<ReturnType<typeof setTimeout>>();\n // Plain `let`: read only inside the streaming functions, never in the\n // template (the assistant message content comes from `conversation`).\n let streamingPartial = '';\n let streamingMessageId: string | null = null;\n // Monotonic token identifying the current stream/typing op. Each scheduled\n // chunk captures the token active when it was queued and bails if a newer op\n // has started — so an already-dequeued timer can't write into a fresh stream.\n let activeOperation = 0;\n\n function later(callback: () => void, delay: number): void {\n const handle = setTimeout(() => {\n pendingTimers.delete(handle);\n callback();\n }, delay);\n pendingTimers.add(handle);\n }\n\n function clearTimers(): void {\n for (const handle of pendingTimers) clearTimeout(handle);\n pendingTimers.clear();\n }\n\n /**\n * Cancel the in-flight op. Modes differ in how they treat partial content:\n * - preservePartial (Stop): keep what streamed so far, commit it to the message.\n * - discard (Clear / Seed / mode switch / new reply): drop the in-flight\n * (possibly empty) assistant message so no blank bubble is left behind.\n * - destroy (onDestroy): just clear timers.\n */\n function cancelPending(mode: 'preservePartial' | 'discard' | 'destroy'): void {\n clearTimers();\n // Invalidate the current op so any timer callback already dequeued (past\n // clearTimers) bails instead of mutating freshly-reset state.\n activeOperation += 1;\n if (mode === 'destroy') return;\n\n if (streamingMessageId) {\n if (mode === 'preservePartial') {\n conversation = replaceMessageContent(conversation, streamingMessageId, streamingPartial);\n } else {\n conversation = removeMessage(conversation, streamingMessageId);\n }\n chat?.endStreaming();\n }\n streamingMessageId = null;\n streamingPartial = '';\n isStreaming = false;\n streamingStatus = '';\n }\n\n // --- Immutable message helpers (replace/remove by id) ---\n function replaceMessageContent(\n current: ConversationHistory,\n id: string,\n content: string,\n ): ConversationHistory {\n const existing = current.messages[id];\n if (!existing) return current;\n return {\n ...current,\n messages: { ...current.messages, [id]: { ...existing, content } },\n updatedAt: new Date().toISOString(),\n };\n }\n\n function removeMessage(current: ConversationHistory, id: string): ConversationHistory {\n if (!current.messages[id]) return current;\n const messages = { ...current.messages };\n delete messages[id];\n return {\n ...current,\n ids: current.ids.filter((existing) => existing !== id),\n messages,\n updatedAt: new Date().toISOString(),\n };\n }\n\n function lastMessageId(current: ConversationHistory): string {\n return current.ids[current.ids.length - 1] ?? '';\n }\n\n function prependHistoryMessages(current: ConversationHistory): ConversationHistory {\n historyPage += 1;\n const olderMessages: Message[] = Array.from({ length: 6 }, (_, index) => {\n const id = `older-${historyPage}-${index}`;\n return {\n id,\n role: index % 2 === 0 ? 'user' : 'assistant',\n content: `Earlier context ${historyPage}.${index + 1}`,\n position: index,\n createdAt: createHistoryMessageTimestamp(historyPage, index),\n metadata: {},\n hidden: false,\n };\n });\n const messages = Object.fromEntries(olderMessages.map((message) => [message.id, message]));\n for (const [index, id] of current.ids.entries()) {\n const message = current.messages[id];\n if (message) {\n messages[id] = { ...message, position: olderMessages.length + index };\n }\n }\n return {\n ...current,\n ids: [...olderMessages.map((message) => message.id), ...current.ids],\n messages,\n updatedAt: new Date().toISOString(),\n };\n }\n\n // --- Reply as the other side ---\n function sendReply(text: string): void {\n const trimmed = text.trim();\n if (!trimmed) return;\n cancelPending('discard');\n // Start a new operation; stale timers from a prior op will no longer match.\n const operation = ++activeOperation;\n\n if (replyMode === 'instant') {\n conversation = appendAssistantMessage(conversation, trimmed);\n return;\n }\n\n if (replyMode === 'typing') {\n isStreaming = true;\n streamingStatus = 'Assistant is typing…';\n // A deliberately long-ish window so the \"typing\" state is comfortably\n // observable (by a person and by a test) before the reply lands.\n later(() => {\n if (operation !== activeOperation) return;\n isStreaming = false;\n streamingStatus = '';\n conversation = appendAssistantMessage(conversation, trimmed);\n }, 1200);\n return;\n }\n\n // streaming: append one empty assistant message, then grow ITS content.\n conversation = appendAssistantMessage(conversation, '');\n const messageId = lastMessageId(conversation);\n streamingMessageId = messageId;\n streamingPartial = '';\n // `isStreaming` drives the composer's Stop affordance for BOTH mechanisms;\n // only `beginStreaming()` (the rAF-buffered token path) is imperative-only.\n isStreaming = true;\n streamingStatus = 'Streaming…';\n if (streamMechanism === 'imperative') {\n chat?.beginStreaming(messageId);\n }\n // Deterministic cadence: a fixed number of chunks at a fixed delay so an\n // intermediate partial state is reliably observable before completion.\n const chunks = splitIntoChunks(trimmed, 5);\n streamChunk(chunks, 0, activeOperation);\n }\n\n function streamChunk(chunks: string[], index: number, operation: number): void {\n // A newer reply/clear/seed started — this is a stale continuation, bail.\n if (operation !== activeOperation) return;\n\n if (index >= chunks.length) {\n // Commit the final content into the message, then end the stream.\n conversation = replaceMessageContent(\n conversation,\n streamingMessageId ?? '',\n streamingPartial,\n );\n if (streamMechanism === 'imperative') chat?.endStreaming();\n streamingMessageId = null;\n isStreaming = false;\n streamingStatus = '';\n return;\n }\n const chunk = chunks[index] ?? '';\n streamingPartial += chunk;\n if (streamMechanism === 'imperative') {\n chat?.pushToken(chunk);\n } else {\n conversation = replaceMessageContent(\n conversation,\n streamingMessageId ?? '',\n streamingPartial,\n );\n }\n later(() => streamChunk(chunks, index + 1, operation), 120);\n }\n\n function splitIntoChunks(text: string, count: number): string[] {\n const words = text.split(' ');\n const perChunk = Math.max(1, Math.ceil(words.length / count));\n const chunks: string[] = [];\n for (let index = 0; index < words.length; index += perChunk) {\n const slice = words.slice(index, index + perChunk).join(' ');\n chunks.push(index === 0 ? slice : ` ${slice}`);\n }\n return chunks;\n }\n\n // --- Inject a tool call + paired result (all outcomes) ---\n function injectToolCall(): void {\n if (!parsedToolArguments.ok) return;\n // Unique per injection — deriving from logCounter would reuse the same id\n // for repeated injections that don't add a log entry, breaking pairing.\n const callId = `call-${crypto.randomUUID()}`;\n const result =\n toolOutcome === 'error'\n ? {\n callId,\n outcome: 'error' as const,\n content: null,\n error: {\n code: 'tool_failed',\n category: 'transient' as ToolErrorCategory,\n retryable: true,\n message: 'The tool failed: upstream returned a transient error.',\n },\n }\n : toolOutcome === 'action_required'\n ? {\n callId,\n outcome: 'action_required' as const,\n content: null,\n action: {\n type: 'approval' as const,\n message: 'Approve running this tool before it executes?',\n },\n }\n : { callId, outcome: 'success' as const, content: { status: 'ok' } };\n\n conversation = appendMessages(\n conversation,\n {\n role: 'tool-call',\n content: '',\n toolCall: { id: callId, name: toolName, arguments: parsedToolArguments.value },\n },\n { role: 'tool-result', content: '', toolResult: result },\n );\n }\n\n // --- Seed / clear ---\n function seedThread(): void {\n cancelPending('discard');\n let next = createConversation({ id: 'harness-seeded' });\n // Repeated token \"alpha\" for deterministic search assertions, plus enough\n // messages to overflow the fixed-height viewport.\n next = appendUserMessage(next, 'Tell me about alpha.');\n next = appendAssistantMessage(next, 'alpha is the first item we track.');\n for (let index = 0; index < 12; index += 1) {\n next = appendUserMessage(next, `Follow-up question number ${index + 1}.`);\n next = appendAssistantMessage(\n next,\n `Detailed answer number ${index + 1}, with alpha context.`,\n );\n }\n conversation = next;\n }\n\n function seedFailedMessage(): void {\n conversation = appendMessages(conversation, {\n role: 'user',\n content: 'This message failed to send.',\n metadata: { _deliveryStatus: 'failed' },\n });\n }\n\n function clearConversation(): void {\n cancelPending('discard');\n conversation = createConversation({ id: 'harness-cleared' });\n historyPage = 0;\n }\n\n // --- Chat callbacks ---\n function handleSubmit(event: ChatSubmitEvent): void {\n const content = typeof event.message.content === 'string' ? event.message.content : '';\n record('onsubmit', content);\n conversation = appendMessages(conversation, {\n role: 'user',\n content: event.message.content,\n });\n if (autoReply) sendReply(replyText);\n }\n\n function handleEdit(event: { messageId: string; content: string }): void {\n record('onedit', event);\n conversation = replaceMessageContent(conversation, event.messageId, event.content);\n }\n\n async function handleLoadHistory(): Promise<void> {\n conversation = prependHistoryMessages(conversation);\n record('onloadhistory', historyPage);\n }\n\n onDestroy(() => cancelPending('destroy'));\n\n const surfaceMode = $derived<'default' | 'transparent'>(\n transparentSurface ? 'transparent' : 'default',\n );\n // `emptyPrompts` is included only when ON. Under exactOptionalPropertyTypes an\n // optional prop must be omitted, not passed as `undefined`, so spread it\n // conditionally rather than passing `emptyPrompts={undefined}`.\n const emptyPromptsProp = $derived(\n withEmptyPrompts ? { emptyPrompts: ['Tell me about alpha', 'Summarize the thread'] } : {},\n );\n const historyProps = $derived(\n historyEnabled ? { hasMoreHistory: true, onloadhistory: handleLoadHistory } : {},\n );\n</script>\n\n<div style=\"display: grid; grid-template-columns: 22rem 1fr; gap: 1rem; align-items: start;\">\n <!-- Control panel -->\n <div\n data-testid=\"harness-controls\"\n style=\"display: grid; gap: 1rem; padding: 1rem; border: 1px solid var(--cinder-border-muted); border-radius: var(--cinder-radius-md); background: var(--cinder-surface-inset);\"\n >\n <section style=\"display: grid; gap: 0.5rem;\">\n <strong>Reply as the other side</strong>\n <Textarea\n id=\"harness-reply-text\"\n data-testid=\"reply-text\"\n label=\"Reply text\"\n bind:value={replyText}\n rows={2}\n />\n <SegmentedControl\n id=\"harness-reply-mode\"\n selectionMode=\"single\"\n bind:value={replyMode}\n label=\"Reply mode\"\n >\n <Segment value=\"instant\">Instant</Segment>\n <Segment value=\"typing\">Typing</Segment>\n <Segment value=\"streaming\">Streaming</Segment>\n </SegmentedControl>\n {#if replyMode === 'streaming'}\n <SegmentedControl\n id=\"harness-stream-mechanism\"\n selectionMode=\"single\"\n bind:value={streamMechanism}\n label=\"Streaming mechanism\"\n >\n <Segment value=\"imperative\">Imperative</Segment>\n <Segment value=\"content-mutation\">Content</Segment>\n </SegmentedControl>\n {/if}\n <Button data-testid=\"send-reply\" onclick={() => sendReply(replyText)}>Send reply</Button>\n </section>\n\n <section style=\"display: grid; gap: 0.5rem;\">\n <strong>Inject tool call</strong>\n <Input\n id=\"harness-tool-name\"\n data-testid=\"tool-name\"\n label=\"Tool name\"\n bind:value={toolName}\n />\n <Textarea\n id=\"harness-tool-arguments\"\n data-testid=\"tool-arguments\"\n label=\"Tool arguments (JSON)\"\n bind:value={toolArguments}\n rows={2}\n />\n {#if !parsedToolArguments.ok}\n <span\n data-testid=\"tool-arguments-error\"\n style=\"color: var(--cinder-danger); font-size: var(--cinder-text-xs);\"\n >\n Arguments must be valid JSON.\n </span>\n {/if}\n <Select\n id=\"harness-tool-outcome\"\n label=\"Outcome\"\n bind:value={toolOutcome}\n options={[\n { value: 'success', label: 'success' },\n { value: 'error', label: 'error' },\n { value: 'action_required', label: 'action_required' },\n ]}\n />\n <Button data-testid=\"inject-tool\" disabled={!parsedToolArguments.ok} onclick={injectToolCall}>\n Inject tool call\n </Button>\n </section>\n\n <section style=\"display: grid; gap: 0.4rem;\">\n <strong>Features</strong>\n <Toggle id=\"t-attachments\" label=\"allowAttachments\" bind:checked={allowAttachments} />\n <Toggle id=\"t-search\" label=\"allowSearch\" bind:checked={allowSearch} />\n <Toggle id=\"t-copy\" label=\"allowCopy\" bind:checked={allowCopy} />\n <Toggle id=\"t-editing\" label=\"allowEditing\" bind:checked={allowEditing} />\n <Toggle id=\"t-retry\" label=\"allowRetry\" bind:checked={allowRetry} />\n <Toggle id=\"t-surface\" label=\"transparent surface\" bind:checked={transparentSurface} />\n <Toggle id=\"t-prompts\" label=\"emptyPrompts\" bind:checked={withEmptyPrompts} />\n <Toggle id=\"t-autoreply\" label=\"auto-reply on submit\" bind:checked={autoReply} />\n <Toggle id=\"t-virtualized\" label=\"virtualized transcript\" bind:checked={virtualized} />\n <Toggle id=\"t-history\" label=\"history pagination\" bind:checked={historyEnabled} />\n </section>\n\n <section style=\"display: grid; gap: 0.4rem;\">\n <strong>State</strong>\n <Button data-testid=\"seed-thread\" variant=\"secondary\" onclick={seedThread}\n >Seed sample thread</Button\n >\n <Button data-testid=\"seed-failed\" variant=\"secondary\" onclick={seedFailedMessage}\n >Seed failed message</Button\n >\n <Button data-testid=\"clear\" variant=\"secondary\" onclick={clearConversation}>Clear</Button>\n <Button data-testid=\"scroll-top\" variant=\"ghost\" onclick={() => chat?.scrollToTop()}\n >Scroll to top</Button\n >\n <Button data-testid=\"scroll-bottom\" variant=\"ghost\" onclick={() => chat?.scrollToBottom()}\n >Scroll to bottom</Button\n >\n <Button data-testid=\"focus-input\" variant=\"ghost\" onclick={() => chat?.focusInput()}\n >Focus composer</Button\n >\n </section>\n\n <section style=\"display: grid; gap: 0.25rem;\">\n <strong>Event log</strong>\n <!-- tabindex=0 gives the scrollable region keyboard access (axe\n scrollable-region-focusable). A group label names it without a `log`\n role, which would collide with the chat timeline's own role=\"log\".\n svelte:element keeps the rendered DOM as a div while avoiding the\n static a11y warning for this focusable scroll-region pattern. -->\n <svelte:element\n this={'div'}\n data-testid=\"event-log\"\n role=\"group\"\n aria-label=\"Event log\"\n tabindex={0}\n style=\"display: grid; gap: 0.15rem; max-height: 10rem; overflow: auto; font-size: var(--cinder-text-xs); font-family: var(--cinder-font-mono);\"\n >\n {#each log as entry (entry.id)}\n <div data-testid=\"event-log-entry\" data-event={entry.event} data-payload={entry.payload}>\n {entry.event}: {entry.payload}\n </div>\n {/each}\n </svelte:element>\n </section>\n </div>\n\n <!-- The Chat under test, in a fixed-height box so the thread overflows. -->\n <div style=\"height: 34rem;\">\n <Chat\n bind:this={chat}\n id=\"harness-chat\"\n {conversation}\n {isStreaming}\n {streamingStatus}\n {allowAttachments}\n {allowSearch}\n {allowCopy}\n {allowEditing}\n {allowRetry}\n {surfaceMode}\n {virtualized}\n virtualizationEstimatedRowHeight={72}\n virtualizationInitialHeight={480}\n virtualizationOverscan={2}\n {...emptyPromptsProp}\n {...historyProps}\n onsubmit={handleSubmit}\n onedit={handleEdit}\n onretry={(messageId: string) => record('onretry', messageId)}\n onstopgenerating={(event: { messageId: string }) => {\n cancelPending('preservePartial');\n record('onstopgenerating', event.messageId);\n }}\n onjumptolatest={() => record('onjumptolatest')}\n onscrollstatechange={(event: { isAtBottom: boolean }) =>\n record('onscrollstatechange', { isAtBottom: event.isAtBottom })}\n onunreadindicatorchange={(event: { unreadCount: number; hasNewMessageIndicator: boolean }) =>\n record('onunreadindicatorchange', event)}\n onexpandedchange={(expanded: boolean) => record('onexpandedchange', { expanded })}\n onattachmentadd={(attachment: { id: string }) => record('onattachmentadd', attachment.id)}\n onattachmentremove={(attachment: { id: string }) =>\n record('onattachmentremove', attachment.id)}\n onattachmentfailure={(_file: File, error: string) => record('onattachmentfailure', error)}\n >\n {#snippet header()}\n <div\n data-testid=\"harness-header\"\n style=\"padding: 0.5rem 0.75rem; font-weight: var(--cinder-font-semibold);\"\n >\n Harness conversation\n </div>\n {/snippet}\n {#snippet messageActions(message: Message)}\n <Button\n iconOnly={true}\n label=\"Message action\"\n size=\"xs\"\n variant=\"ghost\"\n data-testid=\"harness-message-action\"\n data-message-id={message.id}\n >\n ★\n </Button>\n {/snippet}\n {#snippet messageStatus(message: Message)}\n <span data-testid=\"harness-message-status\" data-message-id={message.id}>·</span>\n {/snippet}\n </Chat>\n </div>\n</div>\n"
22
+ "code": "<script lang=\"ts\" module>\n export const title = 'Interactive harness';\n export const description =\n 'Drive Chat from a control panel: reply as the other side (instant / typing / streaming), inject tool calls, toggle features, and watch every callback fire.';\n\n export function createHistoryMessageTimestamp(page: number, index: number): string {\n return new Date(Date.UTC(2026, 4, 30 - page, 12, index)).toISOString();\n }\n</script>\n\n<script lang=\"ts\">\n import {\n Chat,\n appendAssistantMessage,\n appendMessages,\n appendUserMessage,\n createConversation,\n type ChatSubmitEvent,\n type ConversationHistory,\n type JSONValue,\n type Message,\n type ToolErrorCategory,\n } from '@lostgradient/cinder/chat';\n import { Button } from '@lostgradient/cinder/button';\n import { Input } from '@lostgradient/cinder/input';\n import { Segment } from '@lostgradient/cinder/segment';\n import { SegmentedControl } from '@lostgradient/cinder/segmented-control';\n import { Select } from '@lostgradient/cinder/select';\n import { Textarea } from '@lostgradient/cinder/textarea';\n import { Toggle } from '@lostgradient/cinder/toggle';\n import { onDestroy } from 'svelte';\n\n // Out of scope for this harness (documented, not silently dropped):\n // bottomThreshold / jumpThreshold (kept at defaults so overflow + jump are\n // testable without tuning), viewportAttachment (advanced escape hatch), and\n // the atBottom / unreadCount / newMessageIndicatorVisible bindables (read-only\n // mirrors of behavior the wired callbacks already surface).\n\n // --- Chat instance (for the imperative streaming + scroll API) ---\n // Plain `let`: only read via `chat?.method()` calls, never reactively.\n let chat: ReturnType<typeof Chat> | undefined;\n\n // --- Conversation state (immutable snapshots via the cinder/chat builders) ---\n let conversation = $state<ConversationHistory>(createConversation({ id: 'harness' }));\n\n // --- Feature toggles wired straight onto <Chat> ---\n let attachments = $state(true);\n let search = $state(true);\n let copy = $state(true);\n let editing = $state(true);\n let retry = $state(true);\n let transparentSurface = $state(false);\n let withEmptyPrompts = $state(true);\n let autoReply = $state(true);\n let virtualized = $state(false);\n let historyEnabled = $state(false);\n let historyPage = 0;\n\n // --- Reply controls ---\n let replyText = $state('Here is the answer, delivered in a few deliberate chunks.');\n let replyMode = $state<'instant' | 'typing' | 'streaming'>('typing');\n let streamMechanism = $state<'imperative' | 'content-mutation'>('imperative');\n let streaming = $state(false);\n let streamingStatus = $state('');\n\n // --- Tool-call controls ---\n let toolName = $state('exports_check');\n let toolArguments = $state('{ \"package\": \"@lostgradient/cinder\" }');\n let toolOutcome = $state<'success' | 'error' | 'action_required'>('success');\n const parsedToolArguments = $derived.by<{ ok: boolean; value: JSONValue }>(() => {\n try {\n return { ok: true, value: JSON.parse(toolArguments) as JSONValue };\n } catch {\n return { ok: false, value: null };\n }\n });\n\n // --- Event log (queryable: data-event + data-payload per entry) ---\n // Chat emits a burst of identical onunreadindicatorchange events while its\n // empty state settles on mount (all `{unreadCount:0, newMessageIndicatorVisible:\n // false}`). We de-duplicate: a callback whose (event, payload) is identical to\n // the most recent entry for that event is dropped. That kills the mount noise\n // deterministically (no timing dependency) while still recording every real,\n // distinct callback so tests can assert \"this fired because of my action\".\n type LogEntry = { id: number; event: string; payload: string };\n let log = $state<LogEntry[]>([]);\n let logCounter = 0;\n const lastPayloadByEvent = new Map<string, string>();\n function record(event: string, payload: unknown = ''): void {\n const text = typeof payload === 'string' ? payload : JSON.stringify(payload);\n if (lastPayloadByEvent.get(event) === text) return;\n lastPayloadByEvent.set(event, text);\n logCounter += 1;\n log = [{ id: logCounter, event, payload: text }, ...log].slice(0, 50);\n }\n\n // --- Pending-operation discipline (instance-local; NEVER module scope) ---\n // A single in-flight typing/streaming op at a time. Timers are tracked so we\n // can cancel deterministically; streamingPartial holds the accumulated text so\n // Stop can preserve it.\n let pendingTimers = new Set<ReturnType<typeof setTimeout>>();\n // Plain `let`: read only inside the streaming functions, never in the\n // template (the assistant message content comes from `conversation`).\n let streamingPartial = '';\n let streamingMessageId: string | null = null;\n // Monotonic token identifying the current stream/typing op. Each scheduled\n // chunk captures the token active when it was queued and bails if a newer op\n // has started — so an already-dequeued timer can't write into a fresh stream.\n let activeOperation = 0;\n\n function later(callback: () => void, delay: number): void {\n const handle = setTimeout(() => {\n pendingTimers.delete(handle);\n callback();\n }, delay);\n pendingTimers.add(handle);\n }\n\n function clearTimers(): void {\n for (const handle of pendingTimers) clearTimeout(handle);\n pendingTimers.clear();\n }\n\n /**\n * Cancel the in-flight op. Modes differ in how they treat partial content:\n * - preservePartial (Stop): keep what streamed so far, commit it to the message.\n * - discard (Clear / Seed / mode switch / new reply): drop the in-flight\n * (possibly empty) assistant message so no blank bubble is left behind.\n * - destroy (onDestroy): just clear timers.\n */\n function cancelPending(mode: 'preservePartial' | 'discard' | 'destroy'): void {\n clearTimers();\n // Invalidate the current op so any timer callback already dequeued (past\n // clearTimers) bails instead of mutating freshly-reset state.\n activeOperation += 1;\n if (mode === 'destroy') return;\n\n if (streamingMessageId) {\n if (mode === 'preservePartial') {\n conversation = replaceMessageContent(conversation, streamingMessageId, streamingPartial);\n } else {\n conversation = removeMessage(conversation, streamingMessageId);\n }\n chat?.endStreaming();\n }\n streamingMessageId = null;\n streamingPartial = '';\n streaming = false;\n streamingStatus = '';\n }\n\n // --- Immutable message helpers (replace/remove by id) ---\n function replaceMessageContent(\n current: ConversationHistory,\n id: string,\n content: string,\n ): ConversationHistory {\n const existing = current.messages[id];\n if (!existing) return current;\n return {\n ...current,\n messages: { ...current.messages, [id]: { ...existing, content } },\n updatedAt: new Date().toISOString(),\n };\n }\n\n function removeMessage(current: ConversationHistory, id: string): ConversationHistory {\n if (!current.messages[id]) return current;\n const messages = { ...current.messages };\n delete messages[id];\n return {\n ...current,\n ids: current.ids.filter((existing) => existing !== id),\n messages,\n updatedAt: new Date().toISOString(),\n };\n }\n\n function lastMessageId(current: ConversationHistory): string {\n return current.ids[current.ids.length - 1] ?? '';\n }\n\n function prependHistoryMessages(current: ConversationHistory): ConversationHistory {\n historyPage += 1;\n const olderMessages: Message[] = Array.from({ length: 6 }, (_, index) => {\n const id = `older-${historyPage}-${index}`;\n return {\n id,\n role: index % 2 === 0 ? 'user' : 'assistant',\n content: `Earlier context ${historyPage}.${index + 1}`,\n position: index,\n createdAt: createHistoryMessageTimestamp(historyPage, index),\n metadata: {},\n hidden: false,\n };\n });\n const messages = Object.fromEntries(olderMessages.map((message) => [message.id, message]));\n for (const [index, id] of current.ids.entries()) {\n const message = current.messages[id];\n if (message) {\n messages[id] = { ...message, position: olderMessages.length + index };\n }\n }\n return {\n ...current,\n ids: [...olderMessages.map((message) => message.id), ...current.ids],\n messages,\n updatedAt: new Date().toISOString(),\n };\n }\n\n // --- Reply as the other side ---\n function sendReply(text: string): void {\n const trimmed = text.trim();\n if (!trimmed) return;\n cancelPending('discard');\n // Start a new operation; stale timers from a prior op will no longer match.\n const operation = ++activeOperation;\n\n if (replyMode === 'instant') {\n conversation = appendAssistantMessage(conversation, trimmed);\n return;\n }\n\n if (replyMode === 'typing') {\n streaming = true;\n streamingStatus = 'Assistant is typing…';\n // A deliberately long-ish window so the \"typing\" state is comfortably\n // observable (by a person and by a test) before the reply lands.\n later(() => {\n if (operation !== activeOperation) return;\n streaming = false;\n streamingStatus = '';\n conversation = appendAssistantMessage(conversation, trimmed);\n }, 1200);\n return;\n }\n\n // streaming: append one empty assistant message, then grow ITS content.\n conversation = appendAssistantMessage(conversation, '');\n const messageId = lastMessageId(conversation);\n streamingMessageId = messageId;\n streamingPartial = '';\n // `streaming` drives the composer's Stop affordance for BOTH mechanisms;\n // only `beginStreaming()` (the rAF-buffered token path) is imperative-only.\n streaming = true;\n streamingStatus = 'Streaming…';\n if (streamMechanism === 'imperative') {\n chat?.beginStreaming(messageId);\n }\n // Deterministic cadence: a fixed number of chunks at a fixed delay so an\n // intermediate partial state is reliably observable before completion.\n const chunks = splitIntoChunks(trimmed, 5);\n streamChunk(chunks, 0, activeOperation);\n }\n\n function streamChunk(chunks: string[], index: number, operation: number): void {\n // A newer reply/clear/seed started — this is a stale continuation, bail.\n if (operation !== activeOperation) return;\n\n if (index >= chunks.length) {\n // Commit the final content into the message, then end the stream.\n conversation = replaceMessageContent(\n conversation,\n streamingMessageId ?? '',\n streamingPartial,\n );\n if (streamMechanism === 'imperative') chat?.endStreaming();\n streamingMessageId = null;\n streaming = false;\n streamingStatus = '';\n return;\n }\n const chunk = chunks[index] ?? '';\n streamingPartial += chunk;\n if (streamMechanism === 'imperative') {\n chat?.pushToken(chunk);\n } else {\n conversation = replaceMessageContent(\n conversation,\n streamingMessageId ?? '',\n streamingPartial,\n );\n }\n later(() => streamChunk(chunks, index + 1, operation), 120);\n }\n\n function splitIntoChunks(text: string, count: number): string[] {\n const words = text.split(' ');\n const perChunk = Math.max(1, Math.ceil(words.length / count));\n const chunks: string[] = [];\n for (let index = 0; index < words.length; index += perChunk) {\n const slice = words.slice(index, index + perChunk).join(' ');\n chunks.push(index === 0 ? slice : ` ${slice}`);\n }\n return chunks;\n }\n\n // --- Inject a tool call + paired result (all outcomes) ---\n function injectToolCall(): void {\n if (!parsedToolArguments.ok) return;\n // Unique per injection — deriving from logCounter would reuse the same id\n // for repeated injections that don't add a log entry, breaking pairing.\n const callId = `call-${crypto.randomUUID()}`;\n const result =\n toolOutcome === 'error'\n ? {\n callId,\n outcome: 'error' as const,\n content: null,\n error: {\n code: 'tool_failed',\n category: 'transient' as ToolErrorCategory,\n retryable: true,\n message: 'The tool failed: upstream returned a transient error.',\n },\n }\n : toolOutcome === 'action_required'\n ? {\n callId,\n outcome: 'action_required' as const,\n content: null,\n action: {\n type: 'approval' as const,\n message: 'Approve running this tool before it executes?',\n },\n }\n : { callId, outcome: 'success' as const, content: { status: 'ok' } };\n\n conversation = appendMessages(\n conversation,\n {\n role: 'tool-call',\n content: '',\n toolCall: { id: callId, name: toolName, arguments: parsedToolArguments.value },\n },\n { role: 'tool-result', content: '', toolResult: result },\n );\n }\n\n // --- Seed / clear ---\n function seedThread(): void {\n cancelPending('discard');\n let next = createConversation({ id: 'harness-seeded' });\n // Repeated token \"alpha\" for deterministic search assertions, plus enough\n // messages to overflow the fixed-height viewport.\n next = appendUserMessage(next, 'Tell me about alpha.');\n next = appendAssistantMessage(next, 'alpha is the first item we track.');\n for (let index = 0; index < 12; index += 1) {\n next = appendUserMessage(next, `Follow-up question number ${index + 1}.`);\n next = appendAssistantMessage(\n next,\n `Detailed answer number ${index + 1}, with alpha context.`,\n );\n }\n conversation = next;\n }\n\n function seedFailedMessage(): void {\n conversation = appendMessages(conversation, {\n role: 'user',\n content: 'This message failed to send.',\n metadata: { _deliveryStatus: 'failed' },\n });\n }\n\n function clearConversation(): void {\n cancelPending('discard');\n conversation = createConversation({ id: 'harness-cleared' });\n historyPage = 0;\n }\n\n // --- Chat callbacks ---\n function handleSubmit(event: ChatSubmitEvent): void {\n const content = typeof event.message.content === 'string' ? event.message.content : '';\n record('onsubmit', content);\n conversation = appendMessages(conversation, {\n role: 'user',\n content: event.message.content,\n });\n if (autoReply) sendReply(replyText);\n }\n\n function handleEdit(event: { messageId: string; content: string }): void {\n record('onedit', event);\n conversation = replaceMessageContent(conversation, event.messageId, event.content);\n }\n\n async function handleLoadHistory(): Promise<void> {\n conversation = prependHistoryMessages(conversation);\n record('onloadhistory', historyPage);\n }\n\n onDestroy(() => cancelPending('destroy'));\n\n const surfaceMode = $derived<'default' | 'transparent'>(\n transparentSurface ? 'transparent' : 'default',\n );\n // `emptyPrompts` is included only when ON. Under exactOptionalPropertyTypes an\n // optional prop must be omitted, not passed as `undefined`, so spread it\n // conditionally rather than passing `emptyPrompts={undefined}`.\n const emptyPromptsProp = $derived(\n withEmptyPrompts ? { emptyPrompts: ['Tell me about alpha', 'Summarize the thread'] } : {},\n );\n const historyProps = $derived(\n historyEnabled ? { moreHistoryAvailable: true, onloadhistory: handleLoadHistory } : {},\n );\n</script>\n\n<div style=\"display: grid; grid-template-columns: 22rem 1fr; gap: 1rem; align-items: start;\">\n <!-- Control panel -->\n <div\n data-testid=\"harness-controls\"\n style=\"display: grid; gap: 1rem; padding: 1rem; border: 1px solid var(--cinder-border-muted); border-radius: var(--cinder-radius-md); background: var(--cinder-surface-inset);\"\n >\n <section style=\"display: grid; gap: 0.5rem;\">\n <strong>Reply as the other side</strong>\n <Textarea\n id=\"harness-reply-text\"\n data-testid=\"reply-text\"\n label=\"Reply text\"\n bind:value={replyText}\n rows={2}\n />\n <SegmentedControl\n id=\"harness-reply-mode\"\n selectionMode=\"single\"\n bind:value={replyMode}\n label=\"Reply mode\"\n >\n <Segment value=\"instant\">Instant</Segment>\n <Segment value=\"typing\">Typing</Segment>\n <Segment value=\"streaming\">Streaming</Segment>\n </SegmentedControl>\n {#if replyMode === 'streaming'}\n <SegmentedControl\n id=\"harness-stream-mechanism\"\n selectionMode=\"single\"\n bind:value={streamMechanism}\n label=\"Streaming mechanism\"\n >\n <Segment value=\"imperative\">Imperative</Segment>\n <Segment value=\"content-mutation\">Content</Segment>\n </SegmentedControl>\n {/if}\n <Button data-testid=\"send-reply\" onclick={() => sendReply(replyText)}>Send reply</Button>\n </section>\n\n <section style=\"display: grid; gap: 0.5rem;\">\n <strong>Inject tool call</strong>\n <Input\n id=\"harness-tool-name\"\n data-testid=\"tool-name\"\n label=\"Tool name\"\n bind:value={toolName}\n />\n <Textarea\n id=\"harness-tool-arguments\"\n data-testid=\"tool-arguments\"\n label=\"Tool arguments (JSON)\"\n bind:value={toolArguments}\n rows={2}\n />\n {#if !parsedToolArguments.ok}\n <span\n data-testid=\"tool-arguments-error\"\n style=\"color: var(--cinder-danger); font-size: var(--cinder-text-xs);\"\n >\n Arguments must be valid JSON.\n </span>\n {/if}\n <Select\n id=\"harness-tool-outcome\"\n label=\"Outcome\"\n bind:value={toolOutcome}\n options={[\n { value: 'success', label: 'success' },\n { value: 'error', label: 'error' },\n { value: 'action_required', label: 'action_required' },\n ]}\n />\n <Button data-testid=\"inject-tool\" disabled={!parsedToolArguments.ok} onclick={injectToolCall}>\n Inject tool call\n </Button>\n </section>\n\n <section style=\"display: grid; gap: 0.4rem;\">\n <strong>Features</strong>\n <Toggle id=\"t-attachments\" label=\"attachments\" bind:checked={attachments} />\n <Toggle id=\"t-search\" label=\"search\" bind:checked={search} />\n <Toggle id=\"t-copy\" label=\"copy\" bind:checked={copy} />\n <Toggle id=\"t-editing\" label=\"editing\" bind:checked={editing} />\n <Toggle id=\"t-retry\" label=\"retry\" bind:checked={retry} />\n <Toggle id=\"t-surface\" label=\"transparent surface\" bind:checked={transparentSurface} />\n <Toggle id=\"t-prompts\" label=\"emptyPrompts\" bind:checked={withEmptyPrompts} />\n <Toggle id=\"t-autoreply\" label=\"auto-reply on submit\" bind:checked={autoReply} />\n <Toggle id=\"t-virtualized\" label=\"virtualized transcript\" bind:checked={virtualized} />\n <Toggle id=\"t-history\" label=\"history pagination\" bind:checked={historyEnabled} />\n </section>\n\n <section style=\"display: grid; gap: 0.4rem;\">\n <strong>State</strong>\n <Button data-testid=\"seed-thread\" variant=\"secondary\" onclick={seedThread}\n >Seed sample thread</Button\n >\n <Button data-testid=\"seed-failed\" variant=\"secondary\" onclick={seedFailedMessage}\n >Seed failed message</Button\n >\n <Button data-testid=\"clear\" variant=\"secondary\" onclick={clearConversation}>Clear</Button>\n <Button data-testid=\"scroll-top\" variant=\"ghost\" onclick={() => chat?.scrollToTop()}\n >Scroll to top</Button\n >\n <Button data-testid=\"scroll-bottom\" variant=\"ghost\" onclick={() => chat?.scrollToBottom()}\n >Scroll to bottom</Button\n >\n <Button data-testid=\"focus-input\" variant=\"ghost\" onclick={() => chat?.focusInput()}\n >Focus composer</Button\n >\n </section>\n\n <section style=\"display: grid; gap: 0.25rem;\">\n <strong>Event log</strong>\n <!-- tabindex=0 gives the scrollable region keyboard access (axe\n scrollable-region-focusable). A group label names it without a `log`\n role, which would collide with the chat timeline's own role=\"log\".\n svelte:element keeps the rendered DOM as a div while avoiding the\n static a11y warning for this focusable scroll-region pattern. -->\n <svelte:element\n this={'div'}\n data-testid=\"event-log\"\n role=\"group\"\n aria-label=\"Event log\"\n tabindex={0}\n style=\"display: grid; gap: 0.15rem; max-height: 10rem; overflow: auto; font-size: var(--cinder-text-xs); font-family: var(--cinder-font-mono);\"\n >\n {#each log as entry (entry.id)}\n <div data-testid=\"event-log-entry\" data-event={entry.event} data-payload={entry.payload}>\n {entry.event}: {entry.payload}\n </div>\n {/each}\n </svelte:element>\n </section>\n </div>\n\n <!-- The Chat under test, in a fixed-height box so the thread overflows. -->\n <div style=\"height: 34rem;\">\n <Chat\n bind:this={chat}\n id=\"harness-chat\"\n {conversation}\n {streaming}\n {streamingStatus}\n capabilities={{ attachments, search, copy, editing, retry }}\n {surfaceMode}\n {virtualized}\n virtualizationEstimatedRowHeight={72}\n virtualizationInitialHeight={480}\n virtualizationOverscan={2}\n {...emptyPromptsProp}\n {...historyProps}\n onsubmit={handleSubmit}\n onedit={handleEdit}\n onretry={(messageId: string) => record('onretry', messageId)}\n onstopgenerating={(event: { messageId: string }) => {\n cancelPending('preservePartial');\n record('onstopgenerating', event.messageId);\n }}\n onjumptolatest={() => record('onjumptolatest')}\n onscrollstatechange={(event: { atBottom: boolean }) =>\n record('onscrollstatechange', { atBottom: event.atBottom })}\n onunreadindicatorchange={(event: {\n unreadCount: number;\n newMessageIndicatorVisible: boolean;\n }) => record('onunreadindicatorchange', event)}\n onexpandedchange={(expanded: boolean) => record('onexpandedchange', { expanded })}\n onattachmentadd={(attachment: { id: string }) => record('onattachmentadd', attachment.id)}\n onattachmentremove={(attachment: { id: string }) =>\n record('onattachmentremove', attachment.id)}\n onattachmentfailure={(_file: File, error: string) => record('onattachmentfailure', error)}\n >\n {#snippet header()}\n <div\n data-testid=\"harness-header\"\n style=\"padding: 0.5rem 0.75rem; font-weight: var(--cinder-font-semibold);\"\n >\n Harness conversation\n </div>\n {/snippet}\n {#snippet messageActions(message: Message)}\n <Button\n iconOnly={true}\n label=\"Message action\"\n size=\"xs\"\n variant=\"ghost\"\n data-testid=\"harness-message-action\"\n data-message-id={message.id}\n >\n ★\n </Button>\n {/snippet}\n {#snippet messageStatus(message: Message)}\n <span data-testid=\"harness-message-status\" data-message-id={message.id}>·</span>\n {/snippet}\n </Chat>\n </div>\n</div>\n"
23
23
  },
24
24
  {
25
25
  "id": "streaming",
26
26
  "title": "Streaming chat",
27
27
  "description": "Streaming status keeps the composer in stop mode.",
28
- "code": "<script lang=\"ts\">\n import {\n Chat,\n appendAssistantMessage,\n appendUserMessage,\n createConversation,\n } from '@lostgradient/cinder/chat';\n\n let conversation = $state(\n appendAssistantMessage(\n appendUserMessage(\n createConversation({ id: 'streaming-chat' }),\n 'What is happening in the current run?',\n ),\n 'The assistant is applying the port, checking compiler feedback, and updating the demos as each component lands.',\n ),\n );\n</script>\n\n<div style=\"height: 34rem;\">\n <Chat\n id=\"playground-streaming-chat\"\n {conversation}\n isStreaming\n streamingStatus=\"Writing response...\"\n allowAttachments={false}\n />\n</div>\n"
28
+ "code": "<script lang=\"ts\">\n import {\n Chat,\n appendAssistantMessage,\n appendUserMessage,\n createConversation,\n } from '@lostgradient/cinder/chat';\n\n let conversation = $state(\n appendAssistantMessage(\n appendUserMessage(\n createConversation({ id: 'streaming-chat' }),\n 'What is happening in the current run?',\n ),\n 'The assistant is applying the port, checking compiler feedback, and updating the demos as each component lands.',\n ),\n );\n</script>\n\n<div style=\"height: 34rem;\">\n <Chat\n id=\"playground-streaming-chat\"\n {conversation}\n streaming\n streamingStatus=\"Writing response...\"\n capabilities={{ attachments: false }}\n />\n</div>\n"
29
29
  },
30
30
  {
31
31
  "id": "with-reasoning-and-steps",
32
32
  "title": "With reasoning and steps",
33
33
  "description": "Assistant messages can surface a collapsible reasoning block (extended thinking) and an ordered step list before the final answer. Both are UI-only overlays derived from message metadata — the underlying transcript is unchanged.",
34
- "code": "<script lang=\"ts\">\n import {\n Chat,\n appendMessages,\n appendUserMessage,\n createConversation,\n } from '@lostgradient/cinder/chat';\n\n const reasoningContent =\n 'The user is asking me to walk them through solving a quadratic equation step by step. I should break this down into a structured sequence of steps: first identify the coefficients, then apply the quadratic formula, then simplify the result. I should also surface my reasoning process so the user can see my thinking before I present the final answer.';\n\n const conversation = appendMessages(\n appendUserMessage(\n createConversation({ id: 'reasoning-steps-chat' }),\n 'Can you solve 2x² - 4x - 6 = 0 and walk me through the steps?',\n ),\n {\n role: 'assistant',\n content:\n 'The solutions are **x = 3** and **x = -1**.\\n\\nYou can verify these by substituting back: 2(9) − 4(3) − 6 = 18 − 12 − 6 = 0 ✓ and 2(1) − 4(−1) − 6 = 2 + 4 − 6 = 0 ✓',\n metadata: {\n 'cinder:reasoning': reasoningContent,\n 'cinder:steps': [\n {\n title: 'Identify coefficients',\n content: 'a = 2, b = −4, c = −6',\n status: 'done',\n },\n {\n title: 'Apply the quadratic formula',\n content: 'x = (4 ± √(16 + 48)) / 4 = (4 ± √64) / 4 = (4 ± 8) / 4',\n status: 'done',\n },\n {\n title: 'Calculate both solutions',\n content: 'x₁ = (4 + 8) / 4 = 3 · x₂ = (4 − 8) / 4 = −1',\n status: 'done',\n },\n ],\n },\n },\n );\n</script>\n\n<div style=\"height: 34rem;\">\n <Chat id=\"playground-reasoning-steps-chat\" {conversation} allowAttachments={false} />\n</div>\n"
34
+ "code": "<script lang=\"ts\">\n import {\n Chat,\n appendMessages,\n appendUserMessage,\n createConversation,\n } from '@lostgradient/cinder/chat';\n\n const reasoningContent =\n 'The user is asking me to walk them through solving a quadratic equation step by step. I should break this down into a structured sequence of steps: first identify the coefficients, then apply the quadratic formula, then simplify the result. I should also surface my reasoning process so the user can see my thinking before I present the final answer.';\n\n const conversation = appendMessages(\n appendUserMessage(\n createConversation({ id: 'reasoning-steps-chat' }),\n 'Can you solve 2x² - 4x - 6 = 0 and walk me through the steps?',\n ),\n {\n role: 'assistant',\n content:\n 'The solutions are **x = 3** and **x = -1**.\\n\\nYou can verify these by substituting back: 2(9) − 4(3) − 6 = 18 − 12 − 6 = 0 ✓ and 2(1) − 4(−1) − 6 = 2 + 4 − 6 = 0 ✓',\n metadata: {\n 'cinder:reasoning': reasoningContent,\n 'cinder:steps': [\n {\n title: 'Identify coefficients',\n content: 'a = 2, b = −4, c = −6',\n status: 'done',\n },\n {\n title: 'Apply the quadratic formula',\n content: 'x = (4 ± √(16 + 48)) / 4 = (4 ± √64) / 4 = (4 ± 8) / 4',\n status: 'done',\n },\n {\n title: 'Calculate both solutions',\n content: 'x₁ = (4 + 8) / 4 = 3 · x₂ = (4 − 8) / 4 = −1',\n status: 'done',\n },\n ],\n },\n },\n );\n</script>\n\n<div style=\"height: 34rem;\">\n <Chat id=\"playground-reasoning-steps-chat\" {conversation} capabilities={{ attachments: false }} />\n</div>\n"
35
35
  },
36
36
  {
37
37
  "id": "with-suggestions",
38
38
  "title": "With suggested replies",
39
39
  "description": "After the final assistant message, suggested follow-up labels appear as clickable chips. Clicking a chip calls onsuggestionselect(label). The chips are derived from message metadata — the underlying transcript is unchanged.",
40
- "code": "<script lang=\"ts\">\n import {\n Chat,\n appendMessages,\n appendUserMessage,\n createConversation,\n } from '@lostgradient/cinder/chat';\n\n let conversation = $state(\n appendMessages(\n appendUserMessage(\n createConversation({ id: 'suggestions-chat' }),\n 'What are some good topics to learn about AI?',\n ),\n {\n role: 'assistant',\n content:\n 'Great question! Here are some foundational areas to explore:\\n\\n- **Prompt engineering** — crafting effective inputs for language models\\n- **RAG (Retrieval-Augmented Generation)** — combining search with generation\\n- **Fine-tuning** — adapting pre-trained models to specific tasks\\n- **AI safety** — understanding alignment, interpretability, and robustness\\n\\nWhich of these interests you most?',\n metadata: {\n 'cinder:suggestions': [\n 'Tell me more about prompt engineering',\n 'Explain RAG in simple terms',\n 'How does fine-tuning work?',\n 'What is AI safety?',\n ],\n },\n },\n ),\n );\n\n function handleSuggestionSelect(label: string): void {\n conversation = appendUserMessage(conversation, label);\n }\n</script>\n\n<div style=\"height: 34rem;\">\n <Chat\n id=\"playground-suggestions-chat\"\n {conversation}\n allowAttachments={false}\n onsuggestionselect={handleSuggestionSelect}\n onsubmit={(event) => {\n conversation = appendUserMessage(conversation, event.message.content as string);\n }}\n />\n</div>\n"
40
+ "code": "<script lang=\"ts\">\n import {\n Chat,\n appendMessages,\n appendUserMessage,\n createConversation,\n } from '@lostgradient/cinder/chat';\n\n let conversation = $state(\n appendMessages(\n appendUserMessage(\n createConversation({ id: 'suggestions-chat' }),\n 'What are some good topics to learn about AI?',\n ),\n {\n role: 'assistant',\n content:\n 'Great question! Here are some foundational areas to explore:\\n\\n- **Prompt engineering** — crafting effective inputs for language models\\n- **RAG (Retrieval-Augmented Generation)** — combining search with generation\\n- **Fine-tuning** — adapting pre-trained models to specific tasks\\n- **AI safety** — understanding alignment, interpretability, and robustness\\n\\nWhich of these interests you most?',\n metadata: {\n 'cinder:suggestions': [\n 'Tell me more about prompt engineering',\n 'Explain RAG in simple terms',\n 'How does fine-tuning work?',\n 'What is AI safety?',\n ],\n },\n },\n ),\n );\n\n function handleSuggestionSelect(label: string): void {\n conversation = appendUserMessage(conversation, label);\n }\n</script>\n\n<div style=\"height: 34rem;\">\n <Chat\n id=\"playground-suggestions-chat\"\n {conversation}\n capabilities={{ attachments: false }}\n onsuggestionselect={handleSuggestionSelect}\n onsubmit={(event) => {\n conversation = appendUserMessage(conversation, event.message.content as string);\n }}\n />\n</div>\n"
41
41
  },
42
42
  {
43
43
  "id": "with-tool-approval",
44
44
  "title": "With tool approval",
45
45
  "description": "Action-required tool results render as approval prompts the user must accept or reject before the tool can continue.",
46
- "code": "<script lang=\"ts\">\n import {\n Chat,\n appendMessages,\n appendUserMessage,\n createConversation,\n } from '@lostgradient/cinder/chat';\n\n let conversation = $state(\n appendMessages(\n appendUserMessage(\n createConversation({ id: 'tool-approval-chat' }),\n 'Deploy the latest build to production.',\n ),\n {\n role: 'tool-call',\n content: '',\n toolCall: {\n id: 'call-deploy-prod',\n name: 'deploy_to_production',\n arguments: { environment: 'production', version: '2.4.1' },\n },\n },\n {\n role: 'tool-result',\n content: '',\n toolResult: {\n callId: 'call-deploy-prod',\n outcome: 'action_required',\n content: null,\n action: {\n type: 'approval',\n message:\n 'Deploying version 2.4.1 to production will replace the live environment. This action cannot be undone automatically. Do you want to proceed?',\n schema: { environment: 'production', version: '2.4.1', replicas: 3 },\n },\n },\n },\n ),\n );\n\n function handleApprove(toolCallId: string) {\n conversation = appendMessages(conversation, {\n role: 'assistant',\n content: `Approved. Deploying version 2.4.1 to production now. Tool call ID: ${toolCallId}`,\n });\n }\n\n function handleDeny(toolCallId: string) {\n conversation = appendMessages(conversation, {\n role: 'assistant',\n content: `Deployment cancelled. The production environment was not modified. Tool call ID: ${toolCallId}`,\n });\n }\n</script>\n\n<div style=\"height: 34rem;\">\n <Chat\n id=\"playground-tool-approval-chat\"\n {conversation}\n allowAttachments={false}\n onapprove={handleApprove}\n ondeny={handleDeny}\n />\n</div>\n"
46
+ "code": "<script lang=\"ts\">\n import {\n Chat,\n appendMessages,\n appendUserMessage,\n createConversation,\n } from '@lostgradient/cinder/chat';\n\n let conversation = $state(\n appendMessages(\n appendUserMessage(\n createConversation({ id: 'tool-approval-chat' }),\n 'Deploy the latest build to production.',\n ),\n {\n role: 'tool-call',\n content: '',\n toolCall: {\n id: 'call-deploy-prod',\n name: 'deploy_to_production',\n arguments: { environment: 'production', version: '2.4.1' },\n },\n },\n {\n role: 'tool-result',\n content: '',\n toolResult: {\n callId: 'call-deploy-prod',\n outcome: 'action_required',\n content: null,\n action: {\n type: 'approval',\n message:\n 'Deploying version 2.4.1 to production will replace the live environment. This action cannot be undone automatically. Do you want to proceed?',\n schema: { environment: 'production', version: '2.4.1', replicas: 3 },\n },\n },\n },\n ),\n );\n\n function handleApprove(toolCallId: string) {\n conversation = appendMessages(conversation, {\n role: 'assistant',\n content: `Approved. Deploying version 2.4.1 to production now. Tool call ID: ${toolCallId}`,\n });\n }\n\n function handleDeny(toolCallId: string) {\n conversation = appendMessages(conversation, {\n role: 'assistant',\n content: `Deployment cancelled. The production environment was not modified. Tool call ID: ${toolCallId}`,\n });\n }\n</script>\n\n<div style=\"height: 34rem;\">\n <Chat\n id=\"playground-tool-approval-chat\"\n {conversation}\n capabilities={{ attachments: false }}\n onapprove={handleApprove}\n ondeny={handleDeny}\n />\n</div>\n"
47
47
  },
48
48
  {
49
49
  "id": "with-tool-calls",
50
50
  "title": "With tool calls",
51
51
  "description": "Tool call and result messages render as paired expandable groups.",
52
- "code": "<script lang=\"ts\">\n import {\n Chat,\n appendMessages,\n appendUserMessage,\n createConversation,\n } from '@lostgradient/cinder/chat';\n\n let conversation = $state(\n appendMessages(\n appendUserMessage(\n createConversation({ id: 'tool-call-chat' }),\n 'Check whether the package has drift.',\n ),\n {\n role: 'tool-call',\n content: '',\n toolCall: {\n id: 'call-exports-check',\n name: 'exports_check',\n arguments: { package: '@lostgradient/cinder' },\n },\n },\n {\n role: 'tool-result',\n content: '',\n toolResult: {\n callId: 'call-exports-check',\n outcome: 'success',\n content: { status: 'ok', drift: false },\n },\n },\n {\n role: 'assistant',\n content: 'The exports map is current and the component subpaths are available.',\n },\n ),\n );\n</script>\n\n<div style=\"height: 34rem;\">\n <Chat id=\"playground-tool-call-chat\" {conversation} allowAttachments={false} />\n</div>\n"
52
+ "code": "<script lang=\"ts\">\n import {\n Chat,\n appendMessages,\n appendUserMessage,\n createConversation,\n } from '@lostgradient/cinder/chat';\n\n let conversation = $state(\n appendMessages(\n appendUserMessage(\n createConversation({ id: 'tool-call-chat' }),\n 'Check whether the package has drift.',\n ),\n {\n role: 'tool-call',\n content: '',\n toolCall: {\n id: 'call-exports-check',\n name: 'exports_check',\n arguments: { package: '@lostgradient/cinder' },\n },\n },\n {\n role: 'tool-result',\n content: '',\n toolResult: {\n callId: 'call-exports-check',\n outcome: 'success',\n content: { status: 'ok', drift: false },\n },\n },\n {\n role: 'assistant',\n content: 'The exports map is current and the component subpaths are available.',\n },\n ),\n );\n</script>\n\n<div style=\"height: 34rem;\">\n <Chat id=\"playground-tool-call-chat\" {conversation} capabilities={{ attachments: false }} />\n</div>\n"
53
53
  }
54
54
  ]
55
55
  }
@@ -56,6 +56,26 @@ export type ReadReceipt = {
56
56
  */
57
57
  export type ChatRowOverride = Snippet<[message: Message, renderDefault: Snippet]>;
58
58
 
59
+ /**
60
+ * Feature-capability flags for the Chat component. Grouped here so consumers
61
+ * pass a single object instead of five scattered boolean props.
62
+ *
63
+ * All flags default to `true` when the `capabilities` prop is omitted or when
64
+ * an individual key is absent from the object.
65
+ */
66
+ export type ChatCapabilities = {
67
+ /** Whether file attachments are enabled in the composer. When `false`, the attachment button is hidden and drag-and-drop onto the chat surface is suppressed. Default `true`. */
68
+ attachments?: boolean;
69
+ /** Whether in-conversation search is enabled. When `true`, pressing Ctrl+F / Cmd+F opens a search bar that highlights matching messages. Default `true`. */
70
+ search?: boolean;
71
+ /** Whether per-message copy buttons are shown in the message action bar. Default `true`. */
72
+ copy?: boolean;
73
+ /** Whether user messages can be edited inline. Default `true`. */
74
+ editing?: boolean;
75
+ /** Whether failed assistant messages show a retry button. Default `true`. */
76
+ retry?: boolean;
77
+ };
78
+
59
79
  /** Props for the Chat component. */
60
80
  // `onsubmit` is redefined below with a ChatSubmitEvent payload, so strip the
61
81
  // native DOM SubmitEvent handler from the base attributes to avoid an
@@ -70,11 +90,11 @@ export type ChatProps = Omit<HTMLAttributes<HTMLElement>, 'class' | 'onsubmit'>
70
90
  */
71
91
  conversation: ConversationHistory;
72
92
  /** Whether the message viewport is scrolled to the bottom. Bindable; updated automatically as the user scrolls. Default `true`. */
73
- isAtBottom?: boolean;
93
+ atBottom?: boolean;
74
94
  /** Number of messages received while the viewport was scrolled away from the bottom. Bindable; resets to `0` when the user scrolls to the bottom. Default `0`. */
75
95
  unreadCount?: number;
76
96
  /** Whether the "new messages" indicator is currently visible above the composer. Bindable; cleared automatically when the viewport reaches the bottom. Default `false`. */
77
- hasNewMessageIndicator?: boolean;
97
+ newMessageIndicatorVisible?: boolean;
78
98
  /** Additional class name merged onto the `.chat-container` root element. */
79
99
  class?: string;
80
100
  /** Controls the background of the chat surface. Use `'transparent'` to inherit the host element's background when embedding chat inside a card or panel. Default `'default'`. */
@@ -98,13 +118,15 @@ export type ChatProps = Omit<HTMLAttributes<HTMLElement>, 'class' | 'onsubmit'>
98
118
  /** Distance in pixels scrolled up from the bottom required before the jump-to-latest button appears. Must be greater than `bottomThreshold` to prevent button flickering. Default `200`. */
99
119
  jumpThreshold?: number;
100
120
  /** Whether a streaming response is currently in progress. When `true`, the composer is disabled and a stop-generating button is shown. Default `false`. */
101
- isStreaming?: boolean;
102
- /** Optional status label displayed in the typing indicator while `isStreaming` is `true` and no streaming content has arrived yet (e.g. `"Thinking…"` or `"Analyzing file…"`). When omitted, three animated dots are shown. */
121
+ streaming?: boolean;
122
+ /** Optional status label displayed in the typing indicator while `streaming` is `true` and no streaming content has arrived yet (e.g. `"Thinking…"` or `"Analyzing file…"`). When omitted, three animated dots are shown. */
103
123
  streamingStatus?: string;
104
- /** Whether file attachments are enabled in the composer. When `false`, the attachment button is hidden and drag-and-drop onto the chat surface is suppressed. Default `true`. */
105
- allowAttachments?: boolean;
106
- /** Whether in-conversation search is enabled. When `true`, pressing Ctrl+F / Cmd+F opens a search bar that highlights matching messages. Default `true`. */
107
- allowSearch?: boolean;
124
+ /**
125
+ * Feature-capability flags. Pass a `ChatCapabilities` object to enable or disable
126
+ * individual Chat features (attachments, search, copy, editing, retry) as a group
127
+ * rather than as five separate boolean props.
128
+ */
129
+ capabilities?: ChatCapabilities;
108
130
  /** Use the virtualized message render path for long transcripts. The complete `ConversationHistory` remains unchanged; only the DOM window is reduced. Default `false`. */
109
131
  virtualized?: boolean;
110
132
  /** Estimated row height in pixels for virtualized message rows. Default `88`. */
@@ -114,17 +136,11 @@ export type ChatProps = Omit<HTMLAttributes<HTMLElement>, 'class' | 'onsubmit'>
114
136
  /** Initial virtualized viewport height used before measurement. Default `640`. */
115
137
  virtualizationInitialHeight?: number;
116
138
  /** Whether the explicit "Load earlier messages" trigger is shown when a load handler exists. Default `true`. */
117
- hasMoreHistory?: boolean;
139
+ moreHistoryAvailable?: boolean;
118
140
  /** Label for the history pagination trigger. Default `"Load earlier messages"`. */
119
141
  loadEarlierLabel?: string;
120
142
  /** Status text while older messages are loading. Default `"Loading earlier messages"`. */
121
143
  loadingEarlierLabel?: string;
122
- /** Whether per-message copy buttons are shown in the message action bar. Default `true`. */
123
- allowCopy?: boolean;
124
- /** Whether user messages can be edited inline. Default `true`. */
125
- allowEditing?: boolean;
126
- /** Whether failed assistant messages show a retry button. Default `true`. */
127
- allowRetry?: boolean;
128
144
  header?: Snippet;
129
145
  empty?: Snippet;
130
146
  /** List of suggested starter prompt strings shown as clickable buttons in the default empty state. Clicking a prompt submits it immediately as a user message. Has no effect when a custom `empty` snippet is provided. */