@signalflare-ai/ui 1.1.0 → 1.3.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 (362) hide show
  1. package/CHANGELOG.md +102 -5
  2. package/README.md +1 -1
  3. package/ai/component-registry.json +531 -79
  4. package/ai/component-registry.md +4067 -6
  5. package/ai/schemas.ts +6 -1
  6. package/dist/.build-complete +1 -1
  7. package/dist/ai/schemas.d.ts +76 -58
  8. package/dist/ai/schemas.d.ts.map +1 -1
  9. package/dist/{ai-actions-DSVeQn4e.js → ai-actions-CBfz5XEf.js} +4 -4
  10. package/dist/{ai-actions-DSVeQn4e.js.map → ai-actions-CBfz5XEf.js.map} +1 -1
  11. package/dist/{ai-agent-card-BXHwhWAU.js → ai-agent-card-CByAUe0q.js} +3 -3
  12. package/dist/ai-agent-card-CByAUe0q.js.map +1 -0
  13. package/dist/{ai-approval-aa0qvjFN.js → ai-approval-Ci8N70a7.js} +4 -3
  14. package/dist/{ai-approval-aa0qvjFN.js.map → ai-approval-Ci8N70a7.js.map} +1 -1
  15. package/dist/{ai-code-block-BgtIxtZZ.js → ai-code-block-P9TJHvaC.js} +37 -39
  16. package/dist/ai-code-block-P9TJHvaC.js.map +1 -0
  17. package/dist/ai-conversation-Qslfdi1t.js +228 -0
  18. package/dist/ai-conversation-Qslfdi1t.js.map +1 -0
  19. package/dist/{ai-info-banner-uFxHHwBA.js → ai-info-banner-B_9vtGK3.js} +8 -4
  20. package/dist/ai-info-banner-B_9vtGK3.js.map +1 -0
  21. package/dist/{ai-message-BjnFznXy.js → ai-message-Ci3gwM7G.js} +29 -10
  22. package/dist/ai-message-Ci3gwM7G.js.map +1 -0
  23. package/dist/{ai-mission-header-08__gULL.js → ai-mission-header-CaBc19-t.js} +2 -2
  24. package/dist/{ai-mission-header-08__gULL.js.map → ai-mission-header-CaBc19-t.js.map} +1 -1
  25. package/dist/{ai-part-group-DBtgTgAn.js → ai-part-group-Dx1Mr92B.js} +5 -4
  26. package/dist/ai-part-group-Dx1Mr92B.js.map +1 -0
  27. package/dist/{ai-prompt-input-CuluUzpf.js → ai-prompt-input-Bm4XoSj2.js} +44 -55
  28. package/dist/ai-prompt-input-Bm4XoSj2.js.map +1 -0
  29. package/dist/{ai-question-CHHoDJMg.js → ai-question-OyJovxGe.js} +4 -3
  30. package/dist/{ai-question-CHHoDJMg.js.map → ai-question-OyJovxGe.js.map} +1 -1
  31. package/dist/{ai-reasoning-CnL6ZSr5.js → ai-reasoning-BLfBXx3F.js} +9 -5
  32. package/dist/ai-reasoning-BLfBXx3F.js.map +1 -0
  33. package/dist/{ai-response-BEUg3xvd.js → ai-response-hbVCZJmo.js} +9 -4
  34. package/dist/ai-response-hbVCZJmo.js.map +1 -0
  35. package/dist/{ai-shimmer-By5_L05p.js → ai-shimmer-BamNMNK3.js} +2 -2
  36. package/dist/{ai-shimmer-By5_L05p.js.map → ai-shimmer-BamNMNK3.js.map} +1 -1
  37. package/dist/{ai-status-badge-BGYGWYF6.js → ai-status-badge-BZLczdkI.js} +2 -2
  38. package/dist/{ai-status-badge-BGYGWYF6.js.map → ai-status-badge-BZLczdkI.js.map} +1 -1
  39. package/dist/{ai-streaming-text-CMfoThV0.js → ai-streaming-text-DgYu64UH.js} +44 -16
  40. package/dist/ai-streaming-text-DgYu64UH.js.map +1 -0
  41. package/dist/{ai-subagent-DcPRqkAA.js → ai-subagent-p97AI1h9.js} +14 -6
  42. package/dist/ai-subagent-p97AI1h9.js.map +1 -0
  43. package/dist/{ai-suggestion-MgeCg5Ar.js → ai-suggestion-Bj6vF7CT.js} +3 -3
  44. package/dist/{ai-suggestion-MgeCg5Ar.js.map → ai-suggestion-Bj6vF7CT.js.map} +1 -1
  45. package/dist/{ai-task-list-Da9zIm00.js → ai-task-list-C_UQYpk9.js} +15 -6
  46. package/dist/ai-task-list-C_UQYpk9.js.map +1 -0
  47. package/dist/{ai-timeline-Cwu045IR.js → ai-timeline-CePL1LOU.js} +3 -3
  48. package/dist/ai-timeline-CePL1LOU.js.map +1 -0
  49. package/dist/{ai-tool-Cn1O4xjP.js → ai-tool-CfRcwmHT.js} +35 -16
  50. package/dist/ai-tool-CfRcwmHT.js.map +1 -0
  51. package/dist/{ai-usage-bar-DjS12DMp.js → ai-usage-bar-45pVRCGA.js} +2 -2
  52. package/dist/{ai-usage-bar-DjS12DMp.js.map → ai-usage-bar-45pVRCGA.js.map} +1 -1
  53. package/dist/{badge-D_eaA6wv.js → badge-Beb-6uut.js} +5 -5
  54. package/dist/{badge-D_eaA6wv.js.map → badge-Beb-6uut.js.map} +1 -1
  55. package/dist/{banner-B_6oBrsu.js → banner-CCEksxPg.js} +8 -3
  56. package/dist/banner-CCEksxPg.js.map +1 -0
  57. package/dist/{breadcrumbs-BlmeYfgq.js → breadcrumbs-HiTmgaZ4.js} +5 -5
  58. package/dist/{breadcrumbs-BlmeYfgq.js.map → breadcrumbs-HiTmgaZ4.js.map} +1 -1
  59. package/dist/{button-De0267YU.js → button-BHOgXJRU.js} +4 -4
  60. package/dist/{button-De0267YU.js.map → button-BHOgXJRU.js.map} +1 -1
  61. package/dist/catalog.js +1 -1
  62. package/dist/catalog.js.map +1 -1
  63. package/dist/{chart-BK3sVPnD.js → chart-B9FfZdKs.js} +7 -7
  64. package/dist/chart-B9FfZdKs.js.map +1 -0
  65. package/dist/{checkbox-DYhUmZNw.js → checkbox-Cy_OCyay.js} +3 -3
  66. package/dist/{checkbox-DYhUmZNw.js.map → checkbox-Cy_OCyay.js.map} +1 -1
  67. package/dist/{clipboard-text-ssybngLw.js → clipboard-text-CKSvNp9L.js} +6 -5
  68. package/dist/clipboard-text-CKSvNp9L.js.map +1 -0
  69. package/dist/{cn-YROP2_ox.js → cn-CmAOpn49.js} +2 -2
  70. package/dist/{cn-YROP2_ox.js.map → cn-CmAOpn49.js.map} +1 -1
  71. package/dist/{code-Cx-QSoOT.js → code-JsQz-0G_.js} +4 -4
  72. package/dist/{code-Cx-QSoOT.js.map → code-JsQz-0G_.js.map} +1 -1
  73. package/dist/{collapsible-DWsXeXmS.js → collapsible-1kOZ-89L.js} +2 -2
  74. package/dist/{collapsible-DWsXeXmS.js.map → collapsible-1kOZ-89L.js.map} +1 -1
  75. package/dist/{combobox-C0iW6a0r.js → combobox-CQwDmqgA.js} +4 -4
  76. package/dist/{combobox-C0iW6a0r.js.map → combobox-CQwDmqgA.js.map} +1 -1
  77. package/dist/command-line/cli.js +3 -3
  78. package/dist/{command-palette-DGzioeki.js → command-palette-Bkuv3e6o.js} +20 -5
  79. package/dist/command-palette-Bkuv3e6o.js.map +1 -0
  80. package/dist/components/ai-actions.js +1 -1
  81. package/dist/components/ai-agent-card.js +1 -1
  82. package/dist/components/ai-approval.js +1 -1
  83. package/dist/components/ai-code-block.js +1 -1
  84. package/dist/components/ai-conversation.js +2 -2
  85. package/dist/components/ai-info-banner.js +1 -1
  86. package/dist/components/ai-message.js +1 -1
  87. package/dist/components/ai-mission-header.js +1 -1
  88. package/dist/components/ai-part-group.js +1 -1
  89. package/dist/components/ai-prompt-input.js +1 -1
  90. package/dist/components/ai-question.js +1 -1
  91. package/dist/components/ai-reasoning.js +1 -1
  92. package/dist/components/ai-response.js +1 -1
  93. package/dist/components/ai-shimmer.js +1 -1
  94. package/dist/components/ai-status-badge.js +1 -1
  95. package/dist/components/ai-streaming-text.js +2 -2
  96. package/dist/components/ai-subagent.js +1 -1
  97. package/dist/components/ai-suggestion.js +1 -1
  98. package/dist/components/ai-task-list.js +1 -1
  99. package/dist/components/ai-timeline.js +1 -1
  100. package/dist/components/ai-tool.js +1 -1
  101. package/dist/components/ai-usage-bar.js +1 -1
  102. package/dist/components/badge.js +1 -1
  103. package/dist/components/banner.js +1 -1
  104. package/dist/components/breadcrumbs.js +1 -1
  105. package/dist/components/button.js +1 -1
  106. package/dist/components/chart.js +2 -2
  107. package/dist/components/checkbox.js +1 -1
  108. package/dist/components/clipboard-text.js +1 -1
  109. package/dist/components/code.js +1 -1
  110. package/dist/components/collapsible.js +1 -1
  111. package/dist/components/combobox.js +1 -1
  112. package/dist/components/command-palette.js +1 -1
  113. package/dist/components/data-grid.js +1 -1
  114. package/dist/components/date-picker.js +1 -1
  115. package/dist/components/date-range-picker.js +1 -1
  116. package/dist/components/dialog.js +1 -1
  117. package/dist/components/dropdown.js +1 -1
  118. package/dist/components/empty.js +1 -1
  119. package/dist/components/field.js +1 -1
  120. package/dist/components/filters.js +1 -1
  121. package/dist/components/flow.js +1 -1
  122. package/dist/components/grid.js +1 -1
  123. package/dist/components/input.js +2 -2
  124. package/dist/components/label.js +1 -1
  125. package/dist/components/layer-card.js +1 -1
  126. package/dist/components/link.js +3 -3
  127. package/dist/components/link.js.map +1 -1
  128. package/dist/components/loader.js +2 -2
  129. package/dist/components/menubar.js +1 -1
  130. package/dist/components/meter.js +1 -1
  131. package/dist/components/pagination.js +1 -1
  132. package/dist/components/popover.js +1 -1
  133. package/dist/components/radio.js +1 -1
  134. package/dist/components/select.js +1 -1
  135. package/dist/components/sensitive-input.js +1 -1
  136. package/dist/components/sidebar.js +1 -1
  137. package/dist/components/signalflare-ai-logo.js +1 -1
  138. package/dist/components/sparkline.js +1 -1
  139. package/dist/components/stat-card.js +1 -1
  140. package/dist/components/surface.js +1 -1
  141. package/dist/components/switch.js +1 -1
  142. package/dist/components/table.js +1 -1
  143. package/dist/components/tabs.js +1 -1
  144. package/dist/components/text-roll.js +1 -1
  145. package/dist/components/text.js +1 -1
  146. package/dist/components/theme-toggle.js +1 -1
  147. package/dist/components/toast.js +1 -1
  148. package/dist/components/tooltip.js +1 -1
  149. package/dist/components/use-agent-harness.js +1 -1
  150. package/dist/{data-grid-CG76N_hK.js → data-grid-DDSFMHud.js} +136 -53
  151. package/dist/data-grid-DDSFMHud.js.map +1 -0
  152. package/dist/{date-picker-Dqg9L4xu.js → date-picker-O34AqG3f.js} +2 -2
  153. package/dist/{date-picker-Dqg9L4xu.js.map → date-picker-O34AqG3f.js.map} +1 -1
  154. package/dist/{date-range-picker-D75LLINc.js → date-range-picker-YKYvum_r.js} +29 -39
  155. package/dist/{date-range-picker-D75LLINc.js.map → date-range-picker-YKYvum_r.js.map} +1 -1
  156. package/dist/{dialog-CyHEQXEY.js → dialog-DYqu4aDO.js} +3 -3
  157. package/dist/{dialog-CyHEQXEY.js.map → dialog-DYqu4aDO.js.map} +1 -1
  158. package/dist/{dist-1-gcEL2L.js → dist-6AtBsaJE.js} +153 -47
  159. package/dist/dist-6AtBsaJE.js.map +1 -0
  160. package/dist/{dropdown-qnEYRFXZ.js → dropdown-XzbnRLYR.js} +15 -5
  161. package/dist/dropdown-XzbnRLYR.js.map +1 -0
  162. package/dist/{echart-DURZEyai.js → echart-DGBIVAv1.js} +23 -57
  163. package/dist/{echart-DURZEyai.js.map → echart-DGBIVAv1.js.map} +1 -1
  164. package/dist/{empty-D2TypIId.js → empty-C1tAkawe.js} +12 -7
  165. package/dist/empty-C1tAkawe.js.map +1 -0
  166. package/dist/{field-Y_UK1_Cg.js → field-DBpFzzBS.js} +3 -3
  167. package/dist/{field-Y_UK1_Cg.js.map → field-DBpFzzBS.js.map} +1 -1
  168. package/dist/{filters-Bw_U6ZTx.js → filters-SmEl93za.js} +10 -10
  169. package/dist/filters-SmEl93za.js.map +1 -0
  170. package/dist/{flow-BRsYUCJa.js → flow-BLzgbq1T.js} +6 -6
  171. package/dist/flow-BLzgbq1T.js.map +1 -0
  172. package/dist/genui.js +2 -2
  173. package/dist/genui.js.map +1 -1
  174. package/dist/{grid-qUAN9hFx.js → grid-CifjQL-5.js} +2 -2
  175. package/dist/{grid-qUAN9hFx.js.map → grid-CifjQL-5.js.map} +1 -1
  176. package/dist/{highlight-to-react-ClEfL81q.js → highlight-to-react-DN9dUCS2.js} +9 -15
  177. package/dist/highlight-to-react-DN9dUCS2.js.map +1 -0
  178. package/dist/index.js +72 -72
  179. package/dist/index.js.map +1 -1
  180. package/dist/{input-DddtBN-g.js → input-COmx2M_R.js} +5 -5
  181. package/dist/{input-DddtBN-g.js.map → input-COmx2M_R.js.map} +1 -1
  182. package/dist/{input-DXYUjGgD.js → input-GkfMQZC_.js} +3 -3
  183. package/dist/{input-DXYUjGgD.js.map → input-GkfMQZC_.js.map} +1 -1
  184. package/dist/{label-QtJxtJ4u.js → label-CiGZ464N.js} +3 -3
  185. package/dist/{label-QtJxtJ4u.js.map → label-CiGZ464N.js.map} +1 -1
  186. package/dist/{layer-card-BME0eljh.js → layer-card-8l8GuLQr.js} +2 -2
  187. package/dist/{layer-card-BME0eljh.js.map → layer-card-8l8GuLQr.js.map} +1 -1
  188. package/dist/layout-CWBE0qwx.js +6207 -0
  189. package/dist/layout-CWBE0qwx.js.map +1 -0
  190. package/dist/{link-provider-BUZKXaNE.js → link-provider-BSn8YJon.js} +2 -2
  191. package/dist/link-provider-BSn8YJon.js.map +1 -0
  192. package/dist/{loader-DAcc-Uag.js → loader-BEMz8pJO.js} +1 -1
  193. package/dist/{loader-DAcc-Uag.js.map → loader-BEMz8pJO.js.map} +1 -1
  194. package/dist/measured-text-CXkdw9Yr.js +305 -0
  195. package/dist/measured-text-CXkdw9Yr.js.map +1 -0
  196. package/dist/{menubar-C8NzAjfd.js → menubar-CoOr4ocj.js} +3 -3
  197. package/dist/{menubar-C8NzAjfd.js.map → menubar-CoOr4ocj.js.map} +1 -1
  198. package/dist/{meter-CpmTenEr.js → meter-Pf_VOl59.js} +2 -2
  199. package/dist/{meter-CpmTenEr.js.map → meter-Pf_VOl59.js.map} +1 -1
  200. package/dist/{pagination-BVqdlONY.js → pagination-DSY279Ta.js} +2 -2
  201. package/dist/{pagination-BVqdlONY.js.map → pagination-DSY279Ta.js.map} +1 -1
  202. package/dist/{popover-BRQZ2b6z.js → popover-BY-e9co1.js} +2 -2
  203. package/dist/{popover-BRQZ2b6z.js.map → popover-BY-e9co1.js.map} +1 -1
  204. package/dist/{radio-BNSwOt3B.js → radio-DZwL13j0.js} +2 -2
  205. package/dist/{radio-BNSwOt3B.js.map → radio-DZwL13j0.js.map} +1 -1
  206. package/dist/{select-1w2aebGQ.js → select-BFifYqHA.js} +6 -6
  207. package/dist/{select-1w2aebGQ.js.map → select-BFifYqHA.js.map} +1 -1
  208. package/dist/{sensitive-input-82Cez3vj.js → sensitive-input-DHLZcM73.js} +4 -4
  209. package/dist/{sensitive-input-82Cez3vj.js.map → sensitive-input-DHLZcM73.js.map} +1 -1
  210. package/dist/{sidebar-CAsCmSpM.js → sidebar-odGsdvG4.js} +6 -7
  211. package/dist/sidebar-odGsdvG4.js.map +1 -0
  212. package/dist/{signalflare-ai-logo-DDhxMJD6.js → signalflare-ai-logo-CNaDT_w8.js} +2 -2
  213. package/dist/{signalflare-ai-logo-DDhxMJD6.js.map → signalflare-ai-logo-CNaDT_w8.js.map} +1 -1
  214. package/dist/{skeleton-line-Do3UmGk9.js → skeleton-line-CxxYVTO2.js} +2 -2
  215. package/dist/{skeleton-line-Do3UmGk9.js.map → skeleton-line-CxxYVTO2.js.map} +1 -1
  216. package/dist/{sparkline-DdbeM4Ai.js → sparkline-BQ-4j2W2.js} +2 -2
  217. package/dist/{sparkline-DdbeM4Ai.js.map → sparkline-BQ-4j2W2.js.map} +1 -1
  218. package/dist/src/blocks/agent-harness/agent-harness.d.ts.map +1 -1
  219. package/dist/src/blocks/agent-harness/agent-harness.tsx +40 -16
  220. package/dist/src/blocks/commander/commander.tsx +15 -15
  221. package/dist/src/blocks/map-block/map-block.d.ts.map +1 -1
  222. package/dist/src/blocks/map-block/map-block.tsx +11 -7
  223. package/dist/src/components/ai-approval/ai-approval.d.ts.map +1 -1
  224. package/dist/src/components/ai-code-block/ai-code-block.d.ts +14 -13
  225. package/dist/src/components/ai-code-block/ai-code-block.d.ts.map +1 -1
  226. package/dist/src/components/ai-conversation/ai-conversation.d.ts +69 -37
  227. package/dist/src/components/ai-conversation/ai-conversation.d.ts.map +1 -1
  228. package/dist/src/components/ai-conversation/index.d.ts +2 -1
  229. package/dist/src/components/ai-conversation/index.d.ts.map +1 -1
  230. package/dist/src/components/ai-conversation/measurement-constants.d.ts +30 -0
  231. package/dist/src/components/ai-conversation/measurement-constants.d.ts.map +1 -0
  232. package/dist/src/components/ai-info-banner/ai-info-banner.d.ts.map +1 -1
  233. package/dist/src/components/ai-message/ai-message.d.ts +3 -0
  234. package/dist/src/components/ai-message/ai-message.d.ts.map +1 -1
  235. package/dist/src/components/ai-part-group/ai-part-group.d.ts.map +1 -1
  236. package/dist/src/components/ai-prompt-input/ai-prompt-input.d.ts +13 -3
  237. package/dist/src/components/ai-prompt-input/ai-prompt-input.d.ts.map +1 -1
  238. package/dist/src/components/ai-prompt-input/controller.d.ts.map +1 -1
  239. package/dist/src/components/ai-prompt-input/index.d.ts +1 -1
  240. package/dist/src/components/ai-prompt-input/index.d.ts.map +1 -1
  241. package/dist/src/components/ai-prompt-input/types.d.ts.map +1 -1
  242. package/dist/src/components/ai-question/ai-question.d.ts.map +1 -1
  243. package/dist/src/components/ai-reasoning/ai-reasoning.d.ts.map +1 -1
  244. package/dist/src/components/ai-response/ai-response.d.ts +12 -1
  245. package/dist/src/components/ai-response/ai-response.d.ts.map +1 -1
  246. package/dist/src/components/ai-streaming-text/ai-streaming-text.d.ts +27 -0
  247. package/dist/src/components/ai-streaming-text/ai-streaming-text.d.ts.map +1 -1
  248. package/dist/src/components/ai-streaming-text/index.d.ts +1 -1
  249. package/dist/src/components/ai-streaming-text/index.d.ts.map +1 -1
  250. package/dist/src/components/ai-subagent/ai-subagent.d.ts.map +1 -1
  251. package/dist/src/components/ai-task-list/ai-task-list.d.ts.map +1 -1
  252. package/dist/src/components/ai-tool/ai-tool.d.ts.map +1 -1
  253. package/dist/src/components/banner/banner.d.ts.map +1 -1
  254. package/dist/src/components/chart/echart.d.ts.map +1 -1
  255. package/dist/src/components/clipboard-text/clipboard-text.d.ts.map +1 -1
  256. package/dist/src/components/data-grid/data-grid.d.ts +2 -1
  257. package/dist/src/components/data-grid/data-grid.d.ts.map +1 -1
  258. package/dist/src/components/data-grid/features.d.ts +20 -0
  259. package/dist/src/components/data-grid/features.d.ts.map +1 -0
  260. package/dist/src/components/data-grid/types.d.ts +38 -7
  261. package/dist/src/components/data-grid/types.d.ts.map +1 -1
  262. package/dist/src/components/empty/empty.d.ts.map +1 -1
  263. package/dist/src/components/filters/filters.d.ts.map +1 -1
  264. package/dist/src/components/flow/use-children.d.ts +1 -1
  265. package/dist/src/components/link/link.d.ts.map +1 -1
  266. package/dist/src/components/sidebar/sidebar.d.ts +1 -1
  267. package/dist/src/components/signalflare-ai-logo/signalflare-ai-logo.d.ts.map +1 -1
  268. package/dist/src/components/stat-card/stat-card.d.ts +5 -0
  269. package/dist/src/components/stat-card/stat-card.d.ts.map +1 -1
  270. package/dist/src/components/text/text.d.ts +36 -1
  271. package/dist/src/components/text/text.d.ts.map +1 -1
  272. package/dist/src/components/text-roll/text-roll.d.ts.map +1 -1
  273. package/dist/src/components/theme-toggle/theme-toggle.d.ts.map +1 -1
  274. package/dist/src/components/toast/toast.d.ts.map +1 -1
  275. package/dist/src/components/tooltip/tooltip.d.ts.map +1 -1
  276. package/dist/src/index.d.ts +2 -2
  277. package/dist/src/index.d.ts.map +1 -1
  278. package/dist/src/utils/highlight-to-react.d.ts.map +1 -1
  279. package/dist/src/utils/index.d.ts +2 -0
  280. package/dist/src/utils/index.d.ts.map +1 -1
  281. package/dist/src/utils/measured-text.d.ts +40 -0
  282. package/dist/src/utils/measured-text.d.ts.map +1 -0
  283. package/dist/src/utils/use-measured-text.d.ts +59 -0
  284. package/dist/src/utils/use-measured-text.d.ts.map +1 -0
  285. package/dist/{stat-card-CEZscNh8.js → stat-card-Bspk4XFr.js} +30 -12
  286. package/dist/stat-card-Bspk4XFr.js.map +1 -0
  287. package/dist/styles/sf-binding.css +1 -1
  288. package/dist/styles/sf-standalone.css +2 -2
  289. package/dist/styles/shadcn.css +1 -1
  290. package/dist/styles/theme-minimal.css +9 -9
  291. package/dist/styles/theme-sf.css +14 -20
  292. package/dist/styles/theme-vesper.css +91 -0
  293. package/dist/{surface-BduI7Ehl.js → surface-CWdSFVUx.js} +3 -3
  294. package/dist/{surface-BduI7Ehl.js.map → surface-CWdSFVUx.js.map} +1 -1
  295. package/dist/{switch-CzZBRBL7.js → switch-TA4cByCJ.js} +5 -5
  296. package/dist/switch-TA4cByCJ.js.map +1 -0
  297. package/dist/{table-Rv4JMy0B.js → table-BM8JBGBs.js} +3 -3
  298. package/dist/{table-Rv4JMy0B.js.map → table-BM8JBGBs.js.map} +1 -1
  299. package/dist/{tabs-1cHrYoel.js → tabs-bnH2vGLv.js} +2 -2
  300. package/dist/{tabs-1cHrYoel.js.map → tabs-bnH2vGLv.js.map} +1 -1
  301. package/dist/{text-KJmGkwnf.js → text-iQ0YUFNg.js} +27 -6
  302. package/dist/text-iQ0YUFNg.js.map +1 -0
  303. package/dist/{text-roll-BZ3I1umc.js → text-roll-C3U2jd2u.js} +5 -2
  304. package/dist/text-roll-C3U2jd2u.js.map +1 -0
  305. package/dist/{theme-toggle-Bhu681D7.js → theme-toggle-BTVxD-fD.js} +10 -9
  306. package/dist/theme-toggle-BTVxD-fD.js.map +1 -0
  307. package/dist/{toast-Nw28a5Cx.js → toast-CgZVaAkw.js} +3 -3
  308. package/dist/{toast-Nw28a5Cx.js.map → toast-CgZVaAkw.js.map} +1 -1
  309. package/dist/{tooltip-Cb7QW-7H.js → tooltip-uobk6Oh-.js} +9 -3
  310. package/dist/tooltip-uobk6Oh-.js.map +1 -0
  311. package/dist/{use-agent-harness-BMyF8pTq.js → use-agent-harness-Dl8w6X5O.js} +3 -3
  312. package/dist/{use-agent-harness-BMyF8pTq.js.map → use-agent-harness-Dl8w6X5O.js.map} +1 -1
  313. package/dist/utils.js +4 -3
  314. package/package.json +27 -24
  315. package/scripts/component-registry/discovery.ts +11 -10
  316. package/scripts/component-registry/example-cleanup.ts +8 -8
  317. package/scripts/component-registry/index.ts +6 -6
  318. package/scripts/component-registry/schema-generator.ts +1 -1
  319. package/scripts/component-registry/sub-components.ts +35 -23
  320. package/scripts/component-registry/utils.ts +11 -11
  321. package/scripts/component-registry/variant-parser.ts +17 -15
  322. package/scripts/convert-demos-to-stories.ts +5 -5
  323. package/scripts/css-build.ts +1 -1
  324. package/scripts/theme-generator/config.ts +28 -146
  325. package/scripts/theme-generator/generate-css.ts +1 -2
  326. package/scripts/theme-generator/index.ts +0 -1
  327. package/scripts/theme-generator/migrate.ts +3 -3
  328. package/dist/ai-agent-card-BXHwhWAU.js.map +0 -1
  329. package/dist/ai-code-block-BgtIxtZZ.js.map +0 -1
  330. package/dist/ai-conversation-CArP7C8K.js +0 -184
  331. package/dist/ai-conversation-CArP7C8K.js.map +0 -1
  332. package/dist/ai-info-banner-uFxHHwBA.js.map +0 -1
  333. package/dist/ai-message-BjnFznXy.js.map +0 -1
  334. package/dist/ai-part-group-DBtgTgAn.js.map +0 -1
  335. package/dist/ai-prompt-input-CuluUzpf.js.map +0 -1
  336. package/dist/ai-reasoning-CnL6ZSr5.js.map +0 -1
  337. package/dist/ai-response-BEUg3xvd.js.map +0 -1
  338. package/dist/ai-streaming-text-CMfoThV0.js.map +0 -1
  339. package/dist/ai-subagent-DcPRqkAA.js.map +0 -1
  340. package/dist/ai-task-list-Da9zIm00.js.map +0 -1
  341. package/dist/ai-timeline-Cwu045IR.js.map +0 -1
  342. package/dist/ai-tool-Cn1O4xjP.js.map +0 -1
  343. package/dist/banner-B_6oBrsu.js.map +0 -1
  344. package/dist/chart-BK3sVPnD.js.map +0 -1
  345. package/dist/clipboard-text-ssybngLw.js.map +0 -1
  346. package/dist/command-palette-DGzioeki.js.map +0 -1
  347. package/dist/data-grid-CG76N_hK.js.map +0 -1
  348. package/dist/dist-1-gcEL2L.js.map +0 -1
  349. package/dist/dropdown-qnEYRFXZ.js.map +0 -1
  350. package/dist/empty-D2TypIId.js.map +0 -1
  351. package/dist/filters-Bw_U6ZTx.js.map +0 -1
  352. package/dist/flow-BRsYUCJa.js.map +0 -1
  353. package/dist/highlight-to-react-ClEfL81q.js.map +0 -1
  354. package/dist/link-provider-BUZKXaNE.js.map +0 -1
  355. package/dist/sidebar-CAsCmSpM.js.map +0 -1
  356. package/dist/stat-card-CEZscNh8.js.map +0 -1
  357. package/dist/styles/theme-blue-tint.css +0 -98
  358. package/dist/switch-CzZBRBL7.js.map +0 -1
  359. package/dist/text-KJmGkwnf.js.map +0 -1
  360. package/dist/text-roll-BZ3I1umc.js.map +0 -1
  361. package/dist/theme-toggle-Bhu681D7.js.map +0 -1
  362. package/dist/tooltip-Cb7QW-7H.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-agent-card-CByAUe0q.js","names":[],"sources":["../src/components/ai-agent-card/ai-agent-card.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n BrainIcon,\n CheckCircleIcon,\n CircleIcon,\n CodeIcon,\n MagnifyingGlassIcon,\n RobotIcon,\n SpinnerGapIcon,\n WrenchIcon,\n XCircleIcon,\n} from \"@phosphor-icons/react\";\nimport type { ComponentProps, ElementType } from \"react\";\nimport { forwardRef } from \"react\";\n\nimport { cn } from \"../../utils/cn\";\n\n// ─── Variants ────────────────────────────────────────────────────────────────\n\nexport const SF_AI_AGENT_CARD_VARIANTS = {\n status: {\n idle: { classes: \"\", description: \"Agent is idle, not yet started\" },\n running: { classes: \"\", description: \"Agent is actively working\" },\n completed: { classes: \"\", description: \"Agent finished successfully\" },\n error: { classes: \"\", description: \"Agent encountered an error\" },\n },\n size: {\n sm: { classes: \"\", description: \"Compact card for dense grids\" },\n md: { classes: \"\", description: \"Default card size\" },\n },\n} as const;\n\nexport const SF_AI_AGENT_CARD_DEFAULT_VARIANTS = {\n status: \"idle\",\n size: \"md\",\n} as const;\n\nexport type SFAiAgentCardStatus = keyof typeof SF_AI_AGENT_CARD_VARIANTS.status;\nexport type SFAiAgentCardSize = keyof typeof SF_AI_AGENT_CARD_VARIANTS.size;\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type AiAgentCardProps = Omit<ComponentProps<\"button\">, \"children\"> & {\n /** Human-readable agent name (e.g. \"Explore\", \"Execute\"). */\n name: string;\n /** Agent type ID — used for icon mapping. */\n agentType?: string;\n /** Current status. @default \"idle\" */\n status?: SFAiAgentCardStatus;\n /** Model ID being used (e.g. \"claude-haiku-3.5\"). */\n modelId?: string;\n /** What the agent is currently doing. */\n currentTask?: string;\n /** Total elapsed duration in ms. */\n duration?: number;\n /** Number of tool calls made. */\n toolCallCount?: number;\n /** Whether this card is currently selected/active. */\n selected?: boolean;\n /** Custom icon override. */\n icon?: ElementType;\n /** Card size. @default \"md\" */\n size?: SFAiAgentCardSize;\n};\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nconst AGENT_TYPE_ICONS: Record<string, ElementType> = {\n explore: MagnifyingGlassIcon,\n search: MagnifyingGlassIcon,\n execute: CodeIcon,\n code: CodeIcon,\n plan: BrainIcon,\n think: BrainIcon,\n tool: WrenchIcon,\n build: WrenchIcon,\n};\n\nfunction getAgentIcon(\n agentType?: string,\n customIcon?: ElementType\n): ElementType {\n if (customIcon) return customIcon;\n if (agentType) {\n const lower = agentType.toLowerCase();\n for (const [key, icon] of Object.entries(AGENT_TYPE_ICONS)) {\n if (lower.includes(key)) return icon;\n }\n }\n return RobotIcon;\n}\n\nfunction formatDuration(ms: number): string {\n if (ms < 1000) return `${ms}ms`;\n const s = Math.round(ms / 100) / 10;\n if (s < 60) return `${s}s`;\n const m = Math.floor(s / 60);\n const rem = Math.round(s % 60);\n return `${m}m ${rem}s`;\n}\n\nfunction getModelShortName(modelId?: string): string {\n if (!modelId) return \"\";\n return (\n modelId\n .split(\"/\")\n .pop()\n ?.replace(/^claude-/u, \"\")\n .replace(/-\\d+$/u, \"\") ?? modelId\n );\n}\n\n// ─── Status decorations ───────────────────────────────────────────────────────\n\nconst STATUS_DOT: Record<SFAiAgentCardStatus, string> = {\n idle: \"bg-sf-fill\",\n running: \"bg-sf-brand animate-pulse\",\n completed: \"bg-sf-success\",\n error: \"bg-sf-danger\",\n};\n\nfunction StatusIcon({\n status,\n size = 14,\n}: {\n status: SFAiAgentCardStatus;\n size?: number;\n}) {\n switch (status) {\n case \"running\":\n return (\n <SpinnerGapIcon size={size} className=\"animate-spin text-sf-brand\" />\n );\n case \"completed\":\n return <CheckCircleIcon size={size} className=\"text-sf-success\" />;\n case \"error\":\n return <XCircleIcon size={size} className=\"text-sf-danger\" />;\n default:\n return <CircleIcon size={size} className=\"text-sf-inactive\" />;\n }\n}\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\n/**\n * `AiAgentCard` — compact card showing one agent's status in a commander dashboard.\n *\n * Displays: agent icon, name, status indicator, current task, model, duration,\n * and tool call count. Clickable for selection.\n *\n * @example\n * ```tsx\n * <AiAgentCard\n * name=\"Explore\"\n * agentType=\"explore\"\n * status=\"running\"\n * modelId=\"claude-haiku-3.5\"\n * currentTask=\"Scanning auth files...\"\n * duration={4200}\n * toolCallCount={3}\n * selected\n * onClick={handleSelect}\n * />\n * ```\n */\nexport const AiAgentCard = forwardRef<HTMLButtonElement, AiAgentCardProps>(\n (\n {\n name,\n agentType,\n status = SF_AI_AGENT_CARD_DEFAULT_VARIANTS.status,\n modelId,\n currentTask,\n duration,\n toolCallCount,\n selected,\n icon,\n size = SF_AI_AGENT_CARD_DEFAULT_VARIANTS.size,\n className,\n onClick,\n ...props\n },\n ref\n ) => {\n const Icon = getAgentIcon(agentType, icon);\n const modelShort = getModelShortName(modelId);\n const isSm = size === \"sm\";\n\n return (\n <button\n ref={ref}\n type=\"button\"\n aria-pressed={selected}\n onClick={onClick}\n className={cn(\n \"group flex flex-col gap-2 rounded-xl border text-left transition-all\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sf-ring focus-visible:ring-offset-1\",\n isSm ? \"p-2.5\" : \"p-3.5\",\n selected\n ? \"border-sf-line bg-sf-recessed shadow-sm\"\n : \"border-sf-line bg-sf-elevated hover:border-sf-line hover:bg-sf-tint\",\n !onClick && \"cursor-default\",\n className\n )}\n {...props}\n >\n {/* Header row */}\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex items-center gap-2 min-w-0\">\n {/* Agent icon */}\n <div\n className={cn(\n \"flex shrink-0 items-center justify-center rounded-lg\",\n isSm ? \"size-6\" : \"size-8\",\n status === \"running\"\n ? \"bg-sf-brand/10 text-sf-brand\"\n : status === \"completed\"\n ? \"bg-sf-tint text-sf-success\"\n : status === \"error\"\n ? \"bg-sf-tint text-sf-danger\"\n : \"bg-sf-fill text-sf-subtle\"\n )}\n >\n <Icon size={isSm ? 12 : 16} />\n </div>\n\n {/* Name */}\n <span\n className={cn(\n \"truncate font-medium text-sf-default\",\n isSm ? \"text-xs\" : \"text-sm\"\n )}\n >\n {name}\n </span>\n </div>\n\n {/* Status icon */}\n <StatusIcon status={status} size={isSm ? 12 : 14} />\n </div>\n\n {/* Current task */}\n {currentTask && (\n <p\n className={cn(\n \"truncate text-sf-subtle leading-snug\",\n isSm ? \"text-[10px]\" : \"text-xs\"\n )}\n >\n {currentTask}\n </p>\n )}\n\n {/* Footer row: model + stats */}\n {!isSm && (\n <div className=\"flex items-center justify-between gap-2\">\n <div className=\"flex items-center gap-1.5\">\n {/* Status dot + model */}\n <span\n className={cn(\n \"size-1.5 shrink-0 rounded-full\",\n STATUS_DOT[status]\n )}\n />\n {modelShort && (\n <span className=\"font-mono text-[10px] text-sf-inactive\">\n {modelShort}\n </span>\n )}\n </div>\n\n <div className=\"flex items-center gap-2 text-[10px] text-sf-subtle\">\n {typeof toolCallCount === \"number\" && toolCallCount > 0 && (\n <span className=\"flex items-center gap-0.5\">\n <WrenchIcon size={9} />\n {toolCallCount}\n </span>\n )}\n {typeof duration === \"number\" && (\n <span>{formatDuration(duration)}</span>\n )}\n </div>\n </div>\n )}\n </button>\n );\n }\n);\n\nAiAgentCard.displayName = \"AiAgentCard\";\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  "use client";
2
- import { t as cn } from "./cn-YROP2_ox.js";
3
- import { t as Button } from "./button-De0267YU.js";
2
+ import { t as cn } from "./cn-CmAOpn49.js";
3
+ import { t as Button } from "./button-BHOgXJRU.js";
4
4
  import { useCallback, useState } from "react";
5
5
  import { jsx, jsxs } from "react/jsx-runtime";
6
6
  import { CheckCircleIcon, ListChecksIcon, ProhibitIcon, ShieldCheckIcon } from "@phosphor-icons/react";
@@ -153,6 +153,7 @@ function AiApproval({ kind = "tool", status = "pending", title, description, ico
153
153
  }),
154
154
  children,
155
155
  isPending && showFeedback && /* @__PURE__ */ jsx("textarea", {
156
+ "aria-label": "Optional feedback",
156
157
  className: "mx-0 min-h-[60px] resize-none rounded-md border border-sf-line bg-sf-base px-2.5 py-1.5 text-xs text-sf-default outline-none placeholder:text-sf-inactive focus:border-sf-ring",
157
158
  onChange: (e) => setFeedback(e.target.value),
158
159
  placeholder: "Optional feedback…",
@@ -181,4 +182,4 @@ AiApproval.displayName = "AiApproval";
181
182
  //#endregion
182
183
  export { SF_AI_APPROVAL_DEFAULT_VARIANTS as n, SF_AI_APPROVAL_VARIANTS as r, AiApproval as t };
183
184
 
184
- //# sourceMappingURL=ai-approval-aa0qvjFN.js.map
185
+ //# sourceMappingURL=ai-approval-Ci8N70a7.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ai-approval-aa0qvjFN.js","names":[],"sources":["../src/components/ai-approval/ai-approval.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n CheckCircleIcon,\n ProhibitIcon,\n ShieldCheckIcon,\n ListChecksIcon,\n} from \"@phosphor-icons/react\";\nimport type { ElementType, HTMLAttributes, ReactNode } from \"react\";\nimport { useCallback, useState } from \"react\";\n\nimport { cn } from \"../../utils/cn\";\nimport { Button } from \"../button\";\n\n// ─── Variants ────────────────────────────────────────────────────────────────\n\nexport const SF_AI_APPROVAL_VARIANTS = {\n kind: {\n tool: { classes: \"\", description: \"Tool call approval\" },\n plan: { classes: \"\", description: \"Plan approval\" },\n },\n status: {\n pending: { classes: \"\", description: \"Awaiting user decision\" },\n approved: { classes: \"\", description: \"Approved by user\" },\n rejected: { classes: \"\", description: \"Rejected by user\" },\n },\n} as const;\n\nexport const SF_AI_APPROVAL_DEFAULT_VARIANTS = {\n kind: \"tool\",\n status: \"pending\",\n} as const;\n\nexport type SFAiApprovalKind = keyof typeof SF_AI_APPROVAL_VARIANTS.kind;\nexport type SFAiApprovalStatus = keyof typeof SF_AI_APPROVAL_VARIANTS.status;\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type AiApprovalItem = {\n id: string;\n label: string;\n description?: string;\n};\n\nexport type AiApprovalProps = Omit<HTMLAttributes<HTMLDivElement>, \"title\"> & {\n /** Approval kind — `\"tool\"` for individual tool calls, `\"plan\"` for multi-step plans. */\n kind?: SFAiApprovalKind;\n /** Current approval status. */\n status?: SFAiApprovalStatus;\n /** Title text shown in the header. */\n title: string;\n /** Optional description below the title. */\n description?: string;\n /** Custom icon. Defaults to shield (tool) or list (plan). */\n icon?: ElementType;\n /**\n * For plan approvals: list of steps/items in the plan.\n * For tool approvals: can show tool name, parameters, etc.\n */\n items?: AiApprovalItem[];\n /** Called when user approves. */\n onApprove?: () => void;\n /** Called when user rejects. Receives optional feedback string. */\n onReject?: (feedback?: string) => void;\n /** Label for the approve button. Default: `\"Approve\"`. */\n approveLabel?: string;\n /** Label for the reject button. Default: `\"Reject\"`. */\n rejectLabel?: string;\n /** Show a text input for rejection feedback. Default: `false`. */\n showFeedback?: boolean;\n /** Content rendered below items and above the action buttons. */\n children?: ReactNode;\n};\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\nconst KIND_ICONS: Record<SFAiApprovalKind, ElementType> = {\n tool: ShieldCheckIcon,\n plan: ListChecksIcon,\n};\n\nconst STATUS_CONFIG: Record<\n SFAiApprovalStatus,\n { icon: ElementType; label: string; className: string }\n> = {\n pending: {\n icon: ShieldCheckIcon,\n label: \"Awaiting approval\",\n className: \"text-sf-subtle\",\n },\n approved: {\n icon: CheckCircleIcon,\n label: \"Approved\",\n className: \"text-sf-success\",\n },\n rejected: {\n icon: ProhibitIcon,\n label: \"Rejected\",\n className: \"text-sf-danger\",\n },\n};\n\n/**\n * Approval card for tool calls and plan submissions.\n *\n * Maps to harness events: `tool_approval_required`, `plan_approval_required`,\n * `plan_approved`.\n *\n * @example\n * ```tsx\n * <AiApproval\n * kind=\"tool\"\n * title=\"Execute shell command\"\n * description=\"rm -rf /tmp/cache\"\n * onApprove={() => harness.respondToToolApproval({ decision: 'approve' })}\n * onReject={() => harness.respondToToolApproval({ decision: 'decline' })}\n * />\n *\n * <AiApproval\n * kind=\"plan\"\n * title=\"Deployment plan\"\n * items={[\n * { id: \"1\", label: \"Run tests\" },\n * { id: \"2\", label: \"Build production bundle\" },\n * { id: \"3\", label: \"Deploy to staging\" },\n * ]}\n * showFeedback\n * onApprove={() => harness.respondToPlanApproval({ planId, response: { action: 'approved' } })}\n * onReject={(feedback) => harness.respondToPlanApproval({ planId, response: { action: 'rejected', feedback } })}\n * />\n * ```\n */\nexport function AiApproval({\n kind = \"tool\",\n status = \"pending\",\n title,\n description,\n icon,\n items,\n onApprove,\n onReject,\n approveLabel = \"Approve\",\n rejectLabel = \"Reject\",\n showFeedback = false,\n children,\n className,\n ...props\n}: AiApprovalProps) {\n const [feedback, setFeedback] = useState(\"\");\n const isPending = status === \"pending\";\n\n const IconComponent = icon ?? KIND_ICONS[kind];\n const statusConfig = STATUS_CONFIG[status];\n const StatusIcon = statusConfig.icon;\n\n const handleReject = useCallback(() => {\n onReject?.(showFeedback ? feedback : undefined);\n }, [onReject, showFeedback, feedback]);\n\n return (\n <div\n className={cn(\n \"flex flex-col gap-2.5 rounded-lg border border-sf-line bg-sf-elevated p-3\",\n !isPending && \"opacity-75\",\n className\n )}\n {...props}\n >\n {/* Header */}\n <div className=\"flex items-start gap-2.5\">\n <div\n className={cn(\n \"mt-0.5 flex size-6 shrink-0 items-center justify-center rounded-md\",\n isPending ? \"bg-sf-brand/10 text-sf-brand\" : statusConfig.className\n )}\n >\n {isPending ? (\n <IconComponent className=\"size-4\" weight=\"bold\" />\n ) : (\n <StatusIcon className=\"size-4\" weight=\"bold\" />\n )}\n </div>\n <div className=\"flex min-w-0 flex-col gap-0.5\">\n <span className=\"text-sm font-medium text-sf-default\">{title}</span>\n {description && (\n <span className=\"text-xs text-sf-subtle\">{description}</span>\n )}\n </div>\n {!isPending && (\n <span\n className={cn(\n \"ml-auto shrink-0 text-xs font-medium\",\n statusConfig.className\n )}\n >\n {statusConfig.label}\n </span>\n )}\n </div>\n\n {/* Items (plan steps / tool details) */}\n {items && items.length > 0 && (\n <div className=\"flex flex-col gap-1 pl-8\">\n {items.map((item, index) => (\n <div key={item.id} className=\"flex items-start gap-2 text-xs\">\n <span className=\"mt-px shrink-0 font-mono text-sf-subtle\">\n {index + 1}.\n </span>\n <div className=\"flex min-w-0 flex-col\">\n <span className=\"text-sf-default\">{item.label}</span>\n {item.description && (\n <span className=\"text-sf-subtle\">{item.description}</span>\n )}\n </div>\n </div>\n ))}\n </div>\n )}\n\n {children}\n\n {/* Feedback input */}\n {isPending && showFeedback && (\n <textarea\n className=\"mx-0 min-h-[60px] resize-none rounded-md border border-sf-line bg-sf-base px-2.5 py-1.5 text-xs text-sf-default outline-none placeholder:text-sf-inactive focus:border-sf-ring\"\n onChange={(e) => setFeedback(e.target.value)}\n placeholder=\"Optional feedback…\"\n value={feedback}\n />\n )}\n\n {/* Action buttons */}\n {isPending && (\n <div className=\"flex items-center justify-end gap-2\">\n <Button\n onClick={handleReject}\n size=\"sm\"\n type=\"button\"\n variant=\"ghost\"\n >\n {rejectLabel}\n </Button>\n <Button onClick={onApprove} size=\"sm\" type=\"button\" variant=\"primary\">\n {approveLabel}\n </Button>\n </div>\n )}\n </div>\n );\n}\n\nAiApproval.displayName = \"AiApproval\";\n"],"mappings":";;;;;;;AAgBA,IAAa,0BAA0B;CACrC,MAAM;EACJ,MAAM;GAAE,SAAS;GAAI,aAAa;GAAsB;EACxD,MAAM;GAAE,SAAS;GAAI,aAAa;GAAiB;EACpD;CACD,QAAQ;EACN,SAAS;GAAE,SAAS;GAAI,aAAa;GAA0B;EAC/D,UAAU;GAAE,SAAS;GAAI,aAAa;GAAoB;EAC1D,UAAU;GAAE,SAAS;GAAI,aAAa;GAAoB;EAC3D;CACF;AAED,IAAa,kCAAkC;CAC7C,MAAM;CACN,QAAQ;CACT;AA6CD,IAAM,aAAoD;CACxD,MAAM;CACN,MAAM;CACP;AAED,IAAM,gBAGF;CACF,SAAS;EACP,MAAM;EACN,OAAO;EACP,WAAW;EACZ;CACD,UAAU;EACR,MAAM;EACN,OAAO;EACP,WAAW;EACZ;CACD,UAAU;EACR,MAAM;EACN,OAAO;EACP,WAAW;EACZ;CACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCD,SAAgB,WAAW,EACzB,OAAO,QACP,SAAS,WACT,OACA,aACA,MACA,OACA,WACA,UACA,eAAe,WACf,cAAc,UACd,eAAe,OACf,UACA,WACA,GAAG,SACe;CAClB,MAAM,CAAC,UAAU,eAAe,SAAS,GAAG;CAC5C,MAAM,YAAY,WAAW;CAE7B,MAAM,gBAAgB,QAAQ,WAAW;CACzC,MAAM,eAAe,cAAc;CACnC,MAAM,aAAa,aAAa;CAEhC,MAAM,eAAe,kBAAkB;AACrC,aAAW,eAAe,WAAW,KAAA,EAAU;IAC9C;EAAC;EAAU;EAAc;EAAS,CAAC;AAEtC,QACE,qBAAC,OAAD;EACE,WAAW,GACT,6EACA,CAAC,aAAa,cACd,UACD;EACD,GAAI;YANN;GASE,qBAAC,OAAD;IAAK,WAAU;cAAf;KACE,oBAAC,OAAD;MACE,WAAW,GACT,sEACA,YAAY,iCAAiC,aAAa,UAC3D;gBAEA,YACC,oBAAC,eAAD;OAAe,WAAU;OAAS,QAAO;OAAS,CAAA,GAElD,oBAAC,YAAD;OAAY,WAAU;OAAS,QAAO;OAAS,CAAA;MAE7C,CAAA;KACN,qBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,oBAAC,QAAD;OAAM,WAAU;iBAAuC;OAAa,CAAA,EACnE,eACC,oBAAC,QAAD;OAAM,WAAU;iBAA0B;OAAmB,CAAA,CAE3D;;KACL,CAAC,aACA,oBAAC,QAAD;MACE,WAAW,GACT,wCACA,aAAa,UACd;gBAEA,aAAa;MACT,CAAA;KAEL;;GAGL,SAAS,MAAM,SAAS,KACvB,oBAAC,OAAD;IAAK,WAAU;cACZ,MAAM,KAAK,MAAM,UAChB,qBAAC,OAAD;KAAmB,WAAU;eAA7B,CACE,qBAAC,QAAD;MAAM,WAAU;gBAAhB,CACG,QAAQ,GAAE,IACN;SACP,qBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,oBAAC,QAAD;OAAM,WAAU;iBAAmB,KAAK;OAAa,CAAA,EACpD,KAAK,eACJ,oBAAC,QAAD;OAAM,WAAU;iBAAkB,KAAK;OAAmB,CAAA,CAExD;QACF;OAVI,KAAK,GAUT,CACN;IACE,CAAA;GAGP;GAGA,aAAa,gBACZ,oBAAC,YAAD;IACE,WAAU;IACV,WAAW,MAAM,YAAY,EAAE,OAAO,MAAM;IAC5C,aAAY;IACZ,OAAO;IACP,CAAA;GAIH,aACC,qBAAC,OAAD;IAAK,WAAU;cAAf,CACE,oBAAC,QAAD;KACE,SAAS;KACT,MAAK;KACL,MAAK;KACL,SAAQ;eAEP;KACM,CAAA,EACT,oBAAC,QAAD;KAAQ,SAAS;KAAW,MAAK;KAAK,MAAK;KAAS,SAAQ;eACzD;KACM,CAAA,CACL;;GAEJ;;;AAIV,WAAW,cAAc"}
1
+ {"version":3,"file":"ai-approval-Ci8N70a7.js","names":[],"sources":["../src/components/ai-approval/ai-approval.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n CheckCircleIcon,\n ProhibitIcon,\n ShieldCheckIcon,\n ListChecksIcon,\n} from \"@phosphor-icons/react\";\nimport type { ElementType, HTMLAttributes, ReactNode } from \"react\";\nimport { useCallback, useState } from \"react\";\n\nimport { cn } from \"../../utils/cn\";\nimport { Button } from \"../button\";\n\n// ─── Variants ────────────────────────────────────────────────────────────────\n\nexport const SF_AI_APPROVAL_VARIANTS = {\n kind: {\n tool: { classes: \"\", description: \"Tool call approval\" },\n plan: { classes: \"\", description: \"Plan approval\" },\n },\n status: {\n pending: { classes: \"\", description: \"Awaiting user decision\" },\n approved: { classes: \"\", description: \"Approved by user\" },\n rejected: { classes: \"\", description: \"Rejected by user\" },\n },\n} as const;\n\nexport const SF_AI_APPROVAL_DEFAULT_VARIANTS = {\n kind: \"tool\",\n status: \"pending\",\n} as const;\n\nexport type SFAiApprovalKind = keyof typeof SF_AI_APPROVAL_VARIANTS.kind;\nexport type SFAiApprovalStatus = keyof typeof SF_AI_APPROVAL_VARIANTS.status;\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type AiApprovalItem = {\n id: string;\n label: string;\n description?: string;\n};\n\nexport type AiApprovalProps = Omit<HTMLAttributes<HTMLDivElement>, \"title\"> & {\n /** Approval kind — `\"tool\"` for individual tool calls, `\"plan\"` for multi-step plans. */\n kind?: SFAiApprovalKind;\n /** Current approval status. */\n status?: SFAiApprovalStatus;\n /** Title text shown in the header. */\n title: string;\n /** Optional description below the title. */\n description?: string;\n /** Custom icon. Defaults to shield (tool) or list (plan). */\n icon?: ElementType;\n /**\n * For plan approvals: list of steps/items in the plan.\n * For tool approvals: can show tool name, parameters, etc.\n */\n items?: AiApprovalItem[];\n /** Called when user approves. */\n onApprove?: () => void;\n /** Called when user rejects. Receives optional feedback string. */\n onReject?: (feedback?: string) => void;\n /** Label for the approve button. Default: `\"Approve\"`. */\n approveLabel?: string;\n /** Label for the reject button. Default: `\"Reject\"`. */\n rejectLabel?: string;\n /** Show a text input for rejection feedback. Default: `false`. */\n showFeedback?: boolean;\n /** Content rendered below items and above the action buttons. */\n children?: ReactNode;\n};\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\nconst KIND_ICONS: Record<SFAiApprovalKind, ElementType> = {\n tool: ShieldCheckIcon,\n plan: ListChecksIcon,\n};\n\nconst STATUS_CONFIG: Record<\n SFAiApprovalStatus,\n { icon: ElementType; label: string; className: string }\n> = {\n pending: {\n icon: ShieldCheckIcon,\n label: \"Awaiting approval\",\n className: \"text-sf-subtle\",\n },\n approved: {\n icon: CheckCircleIcon,\n label: \"Approved\",\n className: \"text-sf-success\",\n },\n rejected: {\n icon: ProhibitIcon,\n label: \"Rejected\",\n className: \"text-sf-danger\",\n },\n};\n\n/**\n * Approval card for tool calls and plan submissions.\n *\n * Maps to harness events: `tool_approval_required`, `plan_approval_required`,\n * `plan_approved`.\n *\n * @example\n * ```tsx\n * <AiApproval\n * kind=\"tool\"\n * title=\"Execute shell command\"\n * description=\"rm -rf /tmp/cache\"\n * onApprove={() => harness.respondToToolApproval({ decision: 'approve' })}\n * onReject={() => harness.respondToToolApproval({ decision: 'decline' })}\n * />\n *\n * <AiApproval\n * kind=\"plan\"\n * title=\"Deployment plan\"\n * items={[\n * { id: \"1\", label: \"Run tests\" },\n * { id: \"2\", label: \"Build production bundle\" },\n * { id: \"3\", label: \"Deploy to staging\" },\n * ]}\n * showFeedback\n * onApprove={() => harness.respondToPlanApproval({ planId, response: { action: 'approved' } })}\n * onReject={(feedback) => harness.respondToPlanApproval({ planId, response: { action: 'rejected', feedback } })}\n * />\n * ```\n */\nexport function AiApproval({\n kind = \"tool\",\n status = \"pending\",\n title,\n description,\n icon,\n items,\n onApprove,\n onReject,\n approveLabel = \"Approve\",\n rejectLabel = \"Reject\",\n showFeedback = false,\n children,\n className,\n ...props\n}: AiApprovalProps) {\n const [feedback, setFeedback] = useState(\"\");\n const isPending = status === \"pending\";\n\n const IconComponent = icon ?? KIND_ICONS[kind];\n const statusConfig = STATUS_CONFIG[status];\n const StatusIcon = statusConfig.icon;\n\n const handleReject = useCallback(() => {\n onReject?.(showFeedback ? feedback : undefined);\n }, [onReject, showFeedback, feedback]);\n\n return (\n <div\n className={cn(\n \"flex flex-col gap-2.5 rounded-lg border border-sf-line bg-sf-elevated p-3\",\n !isPending && \"opacity-75\",\n className\n )}\n {...props}\n >\n {/* Header */}\n <div className=\"flex items-start gap-2.5\">\n <div\n className={cn(\n \"mt-0.5 flex size-6 shrink-0 items-center justify-center rounded-md\",\n isPending ? \"bg-sf-brand/10 text-sf-brand\" : statusConfig.className\n )}\n >\n {isPending ? (\n <IconComponent className=\"size-4\" weight=\"bold\" />\n ) : (\n <StatusIcon className=\"size-4\" weight=\"bold\" />\n )}\n </div>\n <div className=\"flex min-w-0 flex-col gap-0.5\">\n <span className=\"text-sm font-medium text-sf-default\">{title}</span>\n {description && (\n <span className=\"text-xs text-sf-subtle\">{description}</span>\n )}\n </div>\n {!isPending && (\n <span\n className={cn(\n \"ml-auto shrink-0 text-xs font-medium\",\n statusConfig.className\n )}\n >\n {statusConfig.label}\n </span>\n )}\n </div>\n\n {/* Items (plan steps / tool details) */}\n {items && items.length > 0 && (\n <div className=\"flex flex-col gap-1 pl-8\">\n {items.map((item, index) => (\n <div key={item.id} className=\"flex items-start gap-2 text-xs\">\n <span className=\"mt-px shrink-0 font-mono text-sf-subtle\">\n {index + 1}.\n </span>\n <div className=\"flex min-w-0 flex-col\">\n <span className=\"text-sf-default\">{item.label}</span>\n {item.description && (\n <span className=\"text-sf-subtle\">{item.description}</span>\n )}\n </div>\n </div>\n ))}\n </div>\n )}\n\n {children}\n\n {/* Feedback input */}\n {isPending && showFeedback && (\n <textarea\n aria-label=\"Optional feedback\"\n className=\"mx-0 min-h-[60px] resize-none rounded-md border border-sf-line bg-sf-base px-2.5 py-1.5 text-xs text-sf-default outline-none placeholder:text-sf-inactive focus:border-sf-ring\"\n onChange={(e) => setFeedback(e.target.value)}\n placeholder=\"Optional feedback…\"\n value={feedback}\n />\n )}\n\n {/* Action buttons */}\n {isPending && (\n <div className=\"flex items-center justify-end gap-2\">\n <Button\n onClick={handleReject}\n size=\"sm\"\n type=\"button\"\n variant=\"ghost\"\n >\n {rejectLabel}\n </Button>\n <Button onClick={onApprove} size=\"sm\" type=\"button\" variant=\"primary\">\n {approveLabel}\n </Button>\n </div>\n )}\n </div>\n );\n}\n\nAiApproval.displayName = \"AiApproval\";\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  "use client";
2
- import { t as cn } from "./cn-YROP2_ox.js";
3
- import { t as Button } from "./button-De0267YU.js";
4
- import { t as highlightToLines } from "./highlight-to-react-ClEfL81q.js";
2
+ import { t as cn } from "./cn-CmAOpn49.js";
3
+ import { t as Button } from "./button-BHOgXJRU.js";
4
+ import { t as highlightToLines } from "./highlight-to-react-DN9dUCS2.js";
5
5
  import { createContext, memo, useContext, useState } from "react";
6
6
  import { jsx, jsxs } from "react/jsx-runtime";
7
7
  import { highlight } from "sugar-high";
@@ -21,52 +21,50 @@ var AiCodeBlockContext = createContext({ code: "" });
21
21
  * </AiCodeBlock>
22
22
  * ```
23
23
  */
24
- var AiCodeBlock = memo(({ code, language, showLineNumbers = false, className, children, ...props }) => {
24
+ var AiCodeBlockBase = memo(({ code, language, showLineNumbers = false, className, children, ...props }) => {
25
25
  const highlightedLines = highlightToLines(highlight(code));
26
+ const hasInlineOverlay = !language && Boolean(children);
26
27
  return /* @__PURE__ */ jsx(AiCodeBlockContext.Provider, {
27
28
  value: { code },
28
29
  children: /* @__PURE__ */ jsxs("div", {
29
30
  className: cn("relative w-full overflow-hidden rounded-lg border border-sf-line bg-sf-recessed font-mono text-sm text-sf-default", className),
30
31
  ...props,
31
- children: [
32
- language && /* @__PURE__ */ jsxs("div", {
33
- className: "flex items-center justify-between border-b border-sf-line px-4 py-2",
34
- children: [/* @__PURE__ */ jsx("span", {
35
- className: "text-sf-subtle text-xs",
36
- children: language
37
- }), children && /* @__PURE__ */ jsx("div", {
38
- className: "flex items-center gap-2",
39
- children
40
- })]
41
- }),
42
- /* @__PURE__ */ jsx("div", {
43
- className: "overflow-x-auto",
44
- children: /* @__PURE__ */ jsx("table", {
45
- className: "w-full border-collapse",
46
- children: /* @__PURE__ */ jsx("tbody", { children: highlightedLines.map((line, i) => /* @__PURE__ */ jsxs("tr", {
47
- className: "leading-6",
48
- children: [showLineNumbers && /* @__PURE__ */ jsx("td", {
49
- className: "select-none pr-4 pl-4 text-right text-sf-inactive",
50
- children: i + 1
51
- }), /* @__PURE__ */ jsx("td", {
52
- className: cn("pr-4", !showLineNumbers && "px-4"),
53
- children: /* @__PURE__ */ jsx("pre", {
54
- className: "whitespace-pre",
55
- children: line
56
- })
57
- })]
58
- }, i)) })
59
- })
60
- }),
61
- !language && children && /* @__PURE__ */ jsx("div", {
62
- className: "absolute top-2 right-2 flex items-center gap-2",
32
+ children: [language ? /* @__PURE__ */ jsxs("div", {
33
+ className: "flex items-center justify-between border-b border-sf-line px-4 py-2",
34
+ children: [/* @__PURE__ */ jsx("span", {
35
+ className: "text-sf-subtle text-xs",
36
+ children: language
37
+ }), children && /* @__PURE__ */ jsx("div", {
38
+ className: "flex items-center gap-2",
63
39
  children
40
+ })]
41
+ }) : hasInlineOverlay && /* @__PURE__ */ jsx("div", {
42
+ className: "absolute top-0 right-2 z-10 flex h-6 items-center gap-2",
43
+ children
44
+ }), /* @__PURE__ */ jsx("div", {
45
+ className: "overflow-x-auto",
46
+ children: /* @__PURE__ */ jsx("table", {
47
+ className: "w-full border-collapse",
48
+ children: /* @__PURE__ */ jsx("tbody", { children: highlightedLines.map((line, i) => /* @__PURE__ */ jsxs("tr", {
49
+ className: "leading-6",
50
+ children: [showLineNumbers && /* @__PURE__ */ jsx("td", {
51
+ className: "select-none pr-4 pl-4 text-right text-sf-inactive",
52
+ children: i + 1
53
+ }), /* @__PURE__ */ jsx("td", {
54
+ className: cn("pr-4", !showLineNumbers && "px-4", hasInlineOverlay && i === 0 && "pr-14"),
55
+ children: /* @__PURE__ */ jsx("pre", {
56
+ className: "whitespace-pre",
57
+ children: line
58
+ })
59
+ })]
60
+ }, i)) })
64
61
  })
65
- ]
62
+ })]
66
63
  })
67
64
  });
68
65
  }, (prev, next) => prev.code === next.code && prev.language === next.language && prev.showLineNumbers === next.showLineNumbers);
69
- AiCodeBlock.displayName = "AiCodeBlock";
66
+ AiCodeBlockBase.displayName = "AiCodeBlock";
67
+ var AiCodeBlock = Object.assign(AiCodeBlockBase, { CopyButton: AiCodeBlockCopyButton });
70
68
  /**
71
69
  * Copy-to-clipboard button for use inside `AiCodeBlock`.
72
70
  * Must be a descendant of `AiCodeBlock` to access the code via context.
@@ -107,4 +105,4 @@ function AiCodeBlockCopyButton({ onCopy, onError, timeout = 2e3, children, class
107
105
  //#endregion
108
106
  export { SF_AI_CODE_BLOCK_VARIANTS as i, AiCodeBlockCopyButton as n, SF_AI_CODE_BLOCK_DEFAULT_VARIANTS as r, AiCodeBlock as t };
109
107
 
110
- //# sourceMappingURL=ai-code-block-BgtIxtZZ.js.map
108
+ //# sourceMappingURL=ai-code-block-P9TJHvaC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-code-block-P9TJHvaC.js","names":[],"sources":["../src/components/ai-code-block/ai-code-block.tsx"],"sourcesContent":["\"use client\";\n\nimport type { ComponentProps, HTMLAttributes, ReactNode } from \"react\";\nimport { createContext, memo, useContext, useState } from \"react\";\nimport { highlight } from \"sugar-high\";\n\nimport { cn } from \"../../utils/cn\";\nimport { highlightToLines } from \"../../utils/highlight-to-react\";\nimport { Button } from \"../button\";\n\n// ─── Variants ────────────────────────────────────────────────────────────────\n\nexport const SF_AI_CODE_BLOCK_VARIANTS = {} as const;\nexport const SF_AI_CODE_BLOCK_DEFAULT_VARIANTS = {} as const;\n\n// ─── Context ─────────────────────────────────────────────────────────────────\n\ninterface AiCodeBlockContextValue {\n code: string;\n}\n\nconst AiCodeBlockContext = createContext<AiCodeBlockContextValue>({ code: \"\" });\n\n// ─── AiCodeBlock ─────────────────────────────────────────────────────────────\n\nexport type AiCodeBlockProps = HTMLAttributes<HTMLDivElement> & {\n /** The raw code string to display and make copyable. */\n code: string;\n /** Language identifier for syntax highlighting (display only, no highlighting applied). */\n language?: string;\n /** Show line numbers. @default false */\n showLineNumbers?: boolean;\n /** Extra content rendered in the top-right overlay (e.g. copy button). */\n children?: ReactNode;\n};\n\n/**\n * Displays a code block with monospace formatting and an optional copy button.\n * Does not include a syntax highlighter — avoids the heavy `react-syntax-highlighter`\n * dependency. Wrap with `AiCodeBlockCopyButton` for clipboard support.\n *\n * @example\n * ```tsx\n * <AiCodeBlock code={`const x = 1;`} language=\"ts\">\n * <AiCodeBlockCopyButton />\n * </AiCodeBlock>\n * ```\n */\nconst AiCodeBlockBase = memo(\n ({\n code,\n language,\n showLineNumbers = false,\n className,\n children,\n ...props\n }: AiCodeBlockProps) => {\n const highlightedLines = highlightToLines(highlight(code));\n const hasInlineOverlay = !language && Boolean(children);\n\n return (\n <AiCodeBlockContext.Provider value={{ code }}>\n <div\n className={cn(\n \"relative w-full overflow-hidden rounded-lg border border-sf-line bg-sf-recessed font-mono text-sm text-sf-default\",\n className\n )}\n {...props}\n >\n {language ? (\n <div className=\"flex items-center justify-between border-b border-sf-line px-4 py-2\">\n <span className=\"text-sf-subtle text-xs\">{language}</span>\n {children && (\n <div className=\"flex items-center gap-2\">{children}</div>\n )}\n </div>\n ) : (\n hasInlineOverlay && (\n <div className=\"absolute top-0 right-2 z-10 flex h-6 items-center gap-2\">\n {children}\n </div>\n )\n )}\n <div className=\"overflow-x-auto\">\n <table className=\"w-full border-collapse\">\n <tbody>\n {highlightedLines.map((line, i) => (\n // biome-ignore lint/suspicious/noArrayIndexKey: line index is stable\n <tr key={i} className=\"leading-6\">\n {showLineNumbers && (\n <td className=\"select-none pr-4 pl-4 text-right text-sf-inactive\">\n {i + 1}\n </td>\n )}\n <td\n className={cn(\n \"pr-4\",\n !showLineNumbers && \"px-4\",\n hasInlineOverlay && i === 0 && \"pr-14\"\n )}\n >\n <pre className=\"whitespace-pre\">{line}</pre>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </div>\n </AiCodeBlockContext.Provider>\n );\n },\n (prev, next) =>\n prev.code === next.code &&\n prev.language === next.language &&\n prev.showLineNumbers === next.showLineNumbers\n);\n\nAiCodeBlockBase.displayName = \"AiCodeBlock\";\n\n// Export AiCodeBlock with CopyButton sub-component (for registry detection)\nexport const AiCodeBlock = Object.assign(AiCodeBlockBase, {\n CopyButton: AiCodeBlockCopyButton,\n});\n\n// ─── AiCodeBlockCopyButton ───────────────────────────────────────────────────\n\nexport type AiCodeBlockCopyButtonProps = ComponentProps<typeof Button> & {\n /** Called after a successful copy. */\n onCopy?: () => void;\n /** Called if the clipboard API is unavailable or errors. */\n onError?: (error: Error) => void;\n /** How long (ms) to show the \"copied\" state. @default 2000 */\n timeout?: number;\n};\n\n/**\n * Copy-to-clipboard button for use inside `AiCodeBlock`.\n * Must be a descendant of `AiCodeBlock` to access the code via context.\n *\n * @example\n * ```tsx\n * <AiCodeBlock code=\"const x = 1;\" language=\"ts\">\n * <AiCodeBlockCopyButton />\n * </AiCodeBlock>\n * ```\n */\nexport function AiCodeBlockCopyButton({\n onCopy,\n onError,\n timeout = 2000,\n children,\n className,\n size = \"sm\",\n variant = \"ghost\",\n ...props\n}: AiCodeBlockCopyButtonProps) {\n const [copied, setCopied] = useState(false);\n const { code } = useContext(AiCodeBlockContext);\n\n const handleCopy = async () => {\n if (typeof navigator === \"undefined\" || !navigator.clipboard?.writeText) {\n onError?.(new Error(\"Clipboard API not available\"));\n return;\n }\n try {\n await navigator.clipboard.writeText(code);\n setCopied(true);\n onCopy?.();\n setTimeout(() => setCopied(false), timeout);\n } catch (err) {\n onError?.(err as Error);\n }\n };\n\n return (\n <Button\n className={cn(\"shrink-0\", className)}\n onClick={handleCopy}\n size={size}\n variant={variant}\n {...props}\n >\n {children ?? (copied ? \"Copied\" : \"Copy\")}\n </Button>\n );\n}\n"],"mappings":";;;;;;;;AAYA,IAAa,4BAA4B,CAAC;AAC1C,IAAa,oCAAoC,CAAC;AAQlD,IAAM,qBAAqB,cAAuC,EAAE,MAAM,GAAG,CAAC;;;;;;;;;;;;;AA2B9E,IAAM,kBAAkB,MACrB,EACC,MACA,UACA,kBAAkB,OAClB,WACA,UACA,GAAG,YACmB;CACtB,MAAM,mBAAmB,iBAAiB,UAAU,IAAI,CAAC;CACzD,MAAM,mBAAmB,CAAC,YAAY,QAAQ,QAAQ;CAEtD,OACE,oBAAC,mBAAmB,UAApB;EAA6B,OAAO,EAAE,KAAK;YACzC,qBAAC,OAAD;GACE,WAAW,GACT,qHACA,SACF;GACA,GAAI;aALN,CAOG,WACC,qBAAC,OAAD;IAAK,WAAU;cAAf,CACE,oBAAC,QAAD;KAAM,WAAU;eAA0B;IAAe,CAAA,GACxD,YACC,oBAAC,OAAD;KAAK,WAAU;KAA2B;IAAc,CAAA,CAEvD;QAEL,oBACE,oBAAC,OAAD;IAAK,WAAU;IACZ;GACE,CAAA,GAGT,oBAAC,OAAD;IAAK,WAAU;cACb,oBAAC,SAAD;KAAO,WAAU;eACf,oBAAC,SAAD,EAAA,UACG,iBAAiB,KAAK,MAAM,MAE3B,qBAAC,MAAD;MAAY,WAAU;gBAAtB,CACG,mBACC,oBAAC,MAAD;OAAI,WAAU;iBACX,IAAI;MACH,CAAA,GAEN,oBAAC,MAAD;OACE,WAAW,GACT,QACA,CAAC,mBAAmB,QACpB,oBAAoB,MAAM,KAAK,OACjC;iBAEA,oBAAC,OAAD;QAAK,WAAU;kBAAkB;OAAU,CAAA;MACzC,CAAA,CACF;QAfK,CAeL,CACL,EACI,CAAA;IACF,CAAA;GACJ,CAAA,CACF;;CACsB,CAAA;AAEjC,IACC,MAAM,SACL,KAAK,SAAS,KAAK,QACnB,KAAK,aAAa,KAAK,YACvB,KAAK,oBAAoB,KAAK,eAClC;AAEA,gBAAgB,cAAc;AAG9B,IAAa,cAAc,OAAO,OAAO,iBAAiB,EACxD,YAAY,sBACd,CAAC;;;;;;;;;;;;AAwBD,SAAgB,sBAAsB,EACpC,QACA,SACA,UAAU,KACV,UACA,WACA,OAAO,MACP,UAAU,SACV,GAAG,SAC0B;CAC7B,MAAM,CAAC,QAAQ,aAAa,SAAS,KAAK;CAC1C,MAAM,EAAE,SAAS,WAAW,kBAAkB;CAE9C,MAAM,aAAa,YAAY;EAC7B,IAAI,OAAO,cAAc,eAAe,CAAC,UAAU,WAAW,WAAW;GACvE,0BAAU,IAAI,MAAM,6BAA6B,CAAC;GAClD;EACF;EACA,IAAI;GACF,MAAM,UAAU,UAAU,UAAU,IAAI;GACxC,UAAU,IAAI;GACd,SAAS;GACT,iBAAiB,UAAU,KAAK,GAAG,OAAO;EAC5C,SAAS,KAAK;GACZ,UAAU,GAAY;EACxB;CACF;CAEA,OACE,oBAAC,QAAD;EACE,WAAW,GAAG,YAAY,SAAS;EACnC,SAAS;EACH;EACG;EACT,GAAI;YAEH,aAAa,SAAS,WAAW;CAC5B,CAAA;AAEZ"}
@@ -0,0 +1,228 @@
1
+ "use client";
2
+ import { t as cn } from "./cn-CmAOpn49.js";
3
+ import { a as prepare, t as layout } from "./layout-CWBE0qwx.js";
4
+ import { t as Button } from "./button-BHOgXJRU.js";
5
+ import { forwardRef, useCallback, useEffect, useLayoutEffect, useState } from "react";
6
+ import { jsx } from "react/jsx-runtime";
7
+ import { ArrowDownIcon } from "@phosphor-icons/react";
8
+ import { useVirtualizer } from "@tanstack/react-virtual";
9
+ //#region src/components/ai-conversation/ai-conversation.tsx
10
+ var SF_AI_CONVERSATION_VARIANTS = {};
11
+ var SF_AI_CONVERSATION_DEFAULT_VARIANTS = {};
12
+ /**
13
+ * Outer scroll container for a conversation. Sticks to the bottom as new
14
+ * messages are added. Pair with `AiConversationContent` and optionally
15
+ * `AiConversationScrollButton`.
16
+ *
17
+ * @example
18
+ * ```tsx
19
+ * <AiConversation ref={scrollRef}>
20
+ * <AiConversationContent>
21
+ * {messages.map(m => <AiMessage key={m.id} from={m.role}>...</AiMessage>)}
22
+ * </AiConversationContent>
23
+ * <AiConversationScrollButton scrollRef={scrollRef} />
24
+ * </AiConversation>
25
+ * ```
26
+ */
27
+ var AiConversation = forwardRef(({ className, children, ...props }, ref) => {
28
+ return /* @__PURE__ */ jsx("div", {
29
+ ref,
30
+ className: cn("relative flex-1 overflow-y-auto", className),
31
+ role: "log",
32
+ "aria-live": "polite",
33
+ "aria-label": "Conversation",
34
+ ...props,
35
+ children
36
+ });
37
+ });
38
+ AiConversation.displayName = "AiConversation";
39
+ /**
40
+ * Inner content wrapper with padding. Place message components inside here.
41
+ */
42
+ function AiConversationContent({ className, children, ...props }) {
43
+ return /* @__PURE__ */ jsx("div", {
44
+ className: cn("flex flex-col gap-0 overscroll-y-none p-4", className),
45
+ ...props,
46
+ children
47
+ });
48
+ }
49
+ /**
50
+ * Floating "scroll to bottom" button. Appears when the user has scrolled up
51
+ * more than `threshold` pixels from the bottom.
52
+ */
53
+ function AiConversationScrollButton({ scrollRef, threshold = 100, className, onClick, ...props }) {
54
+ const [show, setShow] = useState(false);
55
+ useEffect(() => {
56
+ const el = scrollRef?.current;
57
+ if (!el) return;
58
+ const check = () => {
59
+ setShow(el.scrollHeight - el.scrollTop - el.clientHeight > threshold);
60
+ };
61
+ check();
62
+ el.addEventListener("scroll", check, { passive: true });
63
+ return () => el.removeEventListener("scroll", check);
64
+ }, [scrollRef, threshold]);
65
+ const handleClick = useCallback((e) => {
66
+ scrollRef?.current?.scrollTo({
67
+ top: scrollRef.current.scrollHeight,
68
+ behavior: "smooth"
69
+ });
70
+ onClick?.(e);
71
+ }, [scrollRef, onClick]);
72
+ if (scrollRef && !show) return null;
73
+ return /* @__PURE__ */ jsx(Button, {
74
+ "aria-label": "Scroll to bottom",
75
+ className: cn("absolute bottom-4 left-1/2 -translate-x-1/2 rounded-full shadow-md", className),
76
+ onClick: handleClick,
77
+ size: "sm",
78
+ variant: "secondary",
79
+ ...props,
80
+ children: /* @__PURE__ */ jsx(ArrowDownIcon, { className: "size-4" })
81
+ });
82
+ }
83
+ var useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
84
+ function useConversationVirtualizer({ scrollRef, items, estimateSize = 80, overscan = 8, stickThreshold = 40 }) {
85
+ const [isAtBottom, setIsAtBottom] = useState(true);
86
+ const [containerWidth, setContainerWidth] = useState(0);
87
+ const [fontsReady, setFontsReady] = useState(() => {
88
+ if (typeof document === "undefined") return true;
89
+ return document.fonts?.status === "loaded";
90
+ });
91
+ useEffect(() => {
92
+ if (fontsReady) return;
93
+ if (typeof document === "undefined" || !document.fonts) {
94
+ setFontsReady(true);
95
+ return;
96
+ }
97
+ let cancelled = false;
98
+ document.fonts.ready.then(() => {
99
+ if (!cancelled) setFontsReady(true);
100
+ });
101
+ return () => {
102
+ cancelled = true;
103
+ };
104
+ }, [fontsReady]);
105
+ useIsomorphicLayoutEffect(() => {
106
+ const el = scrollRef.current;
107
+ if (!el) return;
108
+ const updateWidth = () => {
109
+ const style = window.getComputedStyle(el);
110
+ const padLeft = parseFloat(style.paddingLeft) || 0;
111
+ const padRight = parseFloat(style.paddingRight) || 0;
112
+ setContainerWidth(el.clientWidth - padLeft - padRight);
113
+ };
114
+ updateWidth();
115
+ const observer = new ResizeObserver(updateWidth);
116
+ observer.observe(el);
117
+ return () => observer.disconnect();
118
+ }, [scrollRef]);
119
+ const virtualizer = useVirtualizer({
120
+ count: items.length,
121
+ getScrollElement: () => scrollRef.current,
122
+ estimateSize: (index) => {
123
+ const item = items[index];
124
+ if (!item?.measure || containerWidth <= 0) return estimateSize;
125
+ const { text, font = "14px sans-serif", lineHeight = 20, padding = 0, widthFraction = 1, horizontalPadding = 0, gapAfter = 0, whiteSpace = "normal", wordBreak = "normal" } = item.measure;
126
+ const contentWidth = Math.max(40, Math.floor(containerWidth * widthFraction) - horizontalPadding);
127
+ try {
128
+ const { height } = layout(prepare(text, font, {
129
+ whiteSpace,
130
+ wordBreak
131
+ }), contentWidth, lineHeight);
132
+ return height + padding + gapAfter;
133
+ } catch {
134
+ return estimateSize;
135
+ }
136
+ },
137
+ overscan,
138
+ getItemKey: (index) => items[index]?.key ?? index,
139
+ anchorTo: "end",
140
+ followOnAppend: "smooth",
141
+ scrollEndThreshold: stickThreshold
142
+ });
143
+ useEffect(() => {
144
+ if (containerWidth > 0) virtualizer.measure();
145
+ }, [
146
+ containerWidth,
147
+ items,
148
+ virtualizer,
149
+ fontsReady
150
+ ]);
151
+ const virtualItems = virtualizer.getVirtualItems();
152
+ const totalSize = virtualizer.getTotalSize();
153
+ useEffect(() => {
154
+ const el = scrollRef.current;
155
+ if (!el) return;
156
+ const check = () => {
157
+ setIsAtBottom(virtualizer.isAtEnd(stickThreshold));
158
+ };
159
+ check();
160
+ el.addEventListener("scroll", check, { passive: true });
161
+ return () => el.removeEventListener("scroll", check);
162
+ }, [
163
+ scrollRef,
164
+ stickThreshold,
165
+ virtualizer
166
+ ]);
167
+ useEffect(() => {
168
+ setIsAtBottom(virtualizer.isAtEnd(stickThreshold));
169
+ }, [
170
+ totalSize,
171
+ stickThreshold,
172
+ virtualizer
173
+ ]);
174
+ const scrollToBottom = useCallback((behavior = "smooth") => {
175
+ if (items.length === 0) return;
176
+ virtualizer.scrollToEnd({ behavior });
177
+ }, [virtualizer, items.length]);
178
+ const measureRef = useCallback((node) => {
179
+ if (node) virtualizer.measureElement(node);
180
+ }, [virtualizer]);
181
+ return {
182
+ virtualizer,
183
+ virtualItems,
184
+ totalSize,
185
+ isAtBottom,
186
+ scrollToBottom,
187
+ measureRef,
188
+ getMeasureRef: useCallback((index) => {
189
+ if (items[index]?.measure) return null;
190
+ return measureRef;
191
+ }, [items, measureRef])
192
+ };
193
+ }
194
+ //#endregion
195
+ //#region src/components/ai-conversation/measurement-constants.ts
196
+ /**
197
+ * Shared pretext measurement constants for `useConversationVirtualizer`.
198
+ *
199
+ * These match the rendered styling of `<AiMessage>` + `<AiMessageContent>`
200
+ * (text-sm = 14px / line-height 20px, user bubble `max-w-[70%]` with `px-4 py-3`).
201
+ *
202
+ * Anywhere that builds `ConversationItem.measure` for plain-text AI chat messages
203
+ * should import from here so calibration stays in one place. If production CSS
204
+ * for `<AiMessage>` ever changes (font size, padding, bubble width), update these
205
+ * constants — pretext predictions will then automatically follow.
206
+ */
207
+ /** CSS `font` shorthand matching `text-sm` rendering. */
208
+ var SF_AI_MEASURE_FONT = "14px \"Inter\", ui-sans-serif, system-ui, sans-serif";
209
+ /** Line height in pixels, matching `text-sm`. */
210
+ var SF_AI_MEASURE_LINE_HEIGHT = 20;
211
+ /** User bubbles: `max-w-[70%]`. */
212
+ var SF_AI_USER_WIDTH_FRACTION = .7;
213
+ /** User bubbles: `px-4` → 16px × 2 horizontal padding. */
214
+ var SF_AI_USER_HORIZONTAL_PADDING = 32;
215
+ /** User bubbles: `py-3` → 12px × 2 vertical padding. */
216
+ var SF_AI_USER_VERTICAL_PADDING = 24;
217
+ /** Assistant text: full width column, no bubble. */
218
+ var SF_AI_ASSISTANT_WIDTH_FRACTION = 1;
219
+ /** Assistant text: no horizontal padding inside the bubble. */
220
+ var SF_AI_ASSISTANT_HORIZONTAL_PADDING = 0;
221
+ /** Assistant text: no vertical padding inside the bubble. */
222
+ var SF_AI_ASSISTANT_VERTICAL_PADDING = 0;
223
+ /** Default gap between conversation items. */
224
+ var SF_AI_DEFAULT_MESSAGE_GAP = 12;
225
+ //#endregion
226
+ export { SF_AI_MEASURE_FONT as a, SF_AI_USER_VERTICAL_PADDING as c, AiConversationContent as d, AiConversationScrollButton as f, useConversationVirtualizer as h, SF_AI_DEFAULT_MESSAGE_GAP as i, SF_AI_USER_WIDTH_FRACTION as l, SF_AI_CONVERSATION_VARIANTS as m, SF_AI_ASSISTANT_VERTICAL_PADDING as n, SF_AI_MEASURE_LINE_HEIGHT as o, SF_AI_CONVERSATION_DEFAULT_VARIANTS as p, SF_AI_ASSISTANT_WIDTH_FRACTION as r, SF_AI_USER_HORIZONTAL_PADDING as s, SF_AI_ASSISTANT_HORIZONTAL_PADDING as t, AiConversation as u };
227
+
228
+ //# sourceMappingURL=ai-conversation-Qslfdi1t.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-conversation-Qslfdi1t.js","names":[],"sources":["../src/components/ai-conversation/ai-conversation.tsx","../src/components/ai-conversation/measurement-constants.ts"],"sourcesContent":["\"use client\";\n\nimport { layout, prepare } from \"@chenglou/pretext\";\nimport { ArrowDownIcon } from \"@phosphor-icons/react\";\nimport { useVirtualizer } from \"@tanstack/react-virtual\";\nimport type { Virtualizer, VirtualItem } from \"@tanstack/react-virtual\";\nimport type { ComponentProps, ReactNode, RefObject } from \"react\";\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useLayoutEffect,\n useState,\n} from \"react\";\n\nimport { cn } from \"../../utils/cn\";\nimport { Button } from \"../button\";\n\n// ─── Variants ────────────────────────────────────────────────────────────────\n\nexport const SF_AI_CONVERSATION_VARIANTS = {} as const;\nexport const SF_AI_CONVERSATION_DEFAULT_VARIANTS = {} as const;\n\n// ─── AiConversation ──────────────────────────────────────────────────────────\n\nexport type AiConversationProps = ComponentProps<\"div\">;\n\n/**\n * Outer scroll container for a conversation. Sticks to the bottom as new\n * messages are added. Pair with `AiConversationContent` and optionally\n * `AiConversationScrollButton`.\n *\n * @example\n * ```tsx\n * <AiConversation ref={scrollRef}>\n * <AiConversationContent>\n * {messages.map(m => <AiMessage key={m.id} from={m.role}>...</AiMessage>)}\n * </AiConversationContent>\n * <AiConversationScrollButton scrollRef={scrollRef} />\n * </AiConversation>\n * ```\n */\nexport const AiConversation = forwardRef<HTMLDivElement, AiConversationProps>(\n ({ className, children, ...props }, ref) => {\n return (\n <div\n ref={ref}\n className={cn(\"relative flex-1 overflow-y-auto\", className)}\n role=\"log\"\n aria-live=\"polite\"\n aria-label=\"Conversation\"\n {...props}\n >\n {children}\n </div>\n );\n }\n);\n\nAiConversation.displayName = \"AiConversation\";\n\n// ─── AiConversationContent ───────────────────────────────────────────────────\n\nexport type AiConversationContentProps = ComponentProps<\"div\">;\n\n/**\n * Inner content wrapper with padding. Place message components inside here.\n */\nexport function AiConversationContent({\n className,\n children,\n ...props\n}: AiConversationContentProps) {\n return (\n <div\n className={cn(\"flex flex-col gap-0 overscroll-y-none p-4\", className)}\n {...props}\n >\n {children}\n </div>\n );\n}\n\n// ─── AiConversationScrollButton ──────────────────────────────────────────────\n\nexport type AiConversationScrollButtonProps = ComponentProps<typeof Button> & {\n /**\n * Ref to the scroll container (the `AiConversation` element).\n * When omitted, the button is always visible.\n */\n scrollRef?: RefObject<HTMLElement | null>;\n /** Scroll threshold in pixels from bottom to show the button. @default 100 */\n threshold?: number;\n};\n\n/**\n * Floating \"scroll to bottom\" button. Appears when the user has scrolled up\n * more than `threshold` pixels from the bottom.\n */\nexport function AiConversationScrollButton({\n scrollRef,\n threshold = 100,\n className,\n onClick,\n ...props\n}: AiConversationScrollButtonProps) {\n const [show, setShow] = useState(false);\n\n useEffect(() => {\n const el = scrollRef?.current;\n if (!el) return;\n\n const check = () => {\n const distFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight;\n setShow(distFromBottom > threshold);\n };\n\n check();\n el.addEventListener(\"scroll\", check, { passive: true });\n return () => el.removeEventListener(\"scroll\", check);\n }, [scrollRef, threshold]);\n\n const handleClick = useCallback(\n (e: React.MouseEvent<HTMLButtonElement>) => {\n scrollRef?.current?.scrollTo({\n top: scrollRef.current.scrollHeight,\n behavior: \"smooth\",\n });\n onClick?.(e);\n },\n [scrollRef, onClick]\n );\n\n if (scrollRef && !show) return null;\n\n return (\n <Button\n aria-label=\"Scroll to bottom\"\n className={cn(\n \"absolute bottom-4 left-1/2 -translate-x-1/2 rounded-full shadow-md\",\n className\n )}\n onClick={handleClick}\n size=\"sm\"\n variant=\"secondary\"\n {...props}\n >\n <ArrowDownIcon className=\"size-4\" />\n </Button>\n );\n}\n\n// ─── Virtualized Conversation ────────────────────────────────────────────────\n\n/** Measurement spec for predictive height calculation using pretext. */\nexport interface ConversationItemMeasureSpec {\n /** Text content to measure */\n text: string;\n /** CSS font shorthand (e.g. \"14px Inter, sans-serif\"). Auto-detected if omitted. */\n font?: string;\n /** Line height in pixels. Auto-detected if omitted. */\n lineHeight?: number;\n /** Additional vertical padding (top + bottom) in pixels @default 0 */\n padding?: number;\n /**\n * Fraction of column width this item occupies (0-1). Used to compute the\n * actual content width for items that are constrained (e.g. user message\n * bubbles at max-w-[70%]). @default 1\n */\n widthFraction?: number;\n /**\n * Horizontal padding (left + right) inside the content box, subtracted from\n * the available width before line wrapping. @default 0\n */\n horizontalPadding?: number;\n /**\n * Margin below this item (gap to next item). @default 0\n */\n gapAfter?: number;\n /**\n * White-space handling — must match the rendered CSS. Use `\"pre-wrap\"` when\n * the rendered content preserves explicit newlines (e.g. plain text inside\n * a <pre> or `whitespace-pre-wrap` div). @default \"normal\"\n */\n whiteSpace?: \"normal\" | \"pre-wrap\";\n /**\n * Word-break handling — match CSS `word-break`. Use `\"keep-all\"` for CJK\n * text that should not break mid-word. @default \"normal\"\n */\n wordBreak?: \"normal\" | \"keep-all\";\n}\n\n/** A single item in the virtualized conversation list. */\nexport interface ConversationItem {\n /** Unique key for this item. */\n key: string;\n /**\n * Render the item content. The virtualizer handles measurement and\n * positioning on the wrapper — just return the content.\n */\n render: () => ReactNode;\n /**\n * Optional predictive height measurement. When provided, the virtualizer\n * uses pretext to compute exact height without DOM measurement, eliminating\n * layout shift during streaming. Falls back to estimateSize + DOM measurement\n * when omitted.\n *\n * **Attach this only when rendering plain text** (no markdown). If your\n * `render` output uses `<AiResponse>` or any rich-content component whose\n * blocks have varying typography (headings, code, lists, blockquotes), omit\n * `measure` to let the virtualizer use DOM measurement instead. Pretext\n * predicts heights from a single (font, line-height) pair and cannot\n * accurately model multi-block markdown.\n *\n * Plain-text + `measure` gives jitter-free virtualization (no estimate-then-\n * adjust feedback loop). Rich content + DOM measurement is rendering-correct\n * but slightly less smooth on first paint.\n *\n * For streaming items, update `measure.text` as text grows so the virtualizer\n * can recalculate height per tick (rebuild the `items` array with the latest\n * text on each token arrival).\n */\n measure?: ConversationItemMeasureSpec;\n}\n\nexport interface UseConversationVirtualizerOptions {\n /** Ref to the scroll container element. */\n scrollRef: RefObject<HTMLElement | null>;\n /** The conversation items to virtualize. */\n items: ConversationItem[];\n /**\n * Estimated height of a single item in pixels.\n * Used before measurement. Doesn't need to be exact.\n * @default 80\n */\n estimateSize?: number;\n /**\n * Number of items to render beyond the visible area.\n * @default 8\n */\n overscan?: number;\n /**\n * Pixel distance from bottom to consider \"at bottom\" for stick-to-bottom.\n * @default 40\n */\n stickThreshold?: number;\n}\n\nexport interface UseConversationVirtualizerReturn {\n /** The TanStack virtualizer instance. */\n virtualizer: Virtualizer<HTMLElement, Element>;\n /** The virtual items to render. */\n virtualItems: VirtualItem[];\n /** Total measured/estimated height of all items. */\n totalSize: number;\n /** Whether the user is currently at the bottom of the conversation. */\n isAtBottom: boolean;\n /** Smoothly scroll to the bottom. */\n scrollToBottom: (behavior?: ScrollBehavior) => void;\n /**\n * Get the measurement ref for a virtual item by index.\n * @deprecated Use `getMeasureRef(index)` instead to respect predictive heights.\n */\n measureRef: (node: HTMLElement | null) => void;\n /**\n * Get the measurement ref for a specific item index.\n * Returns `null` for items with predictive height (no DOM measurement needed),\n * or the measurement callback for legacy items.\n */\n getMeasureRef: (index: number) => ((node: HTMLElement | null) => void) | null;\n}\n\n/**\n * `useConversationVirtualizer` — virtualizes a conversation list with\n * dynamic item heights and automatic stick-to-bottom behavior.\n *\n * Built on `@tanstack/react-virtual`. Items are measured dynamically via\n * `ResizeObserver`, so tool calls expanding, subagents collapsing, and\n * streaming text growing all work correctly.\n *\n * **Stick-to-bottom**: Uses TanStack Virtual's end-anchored mode\n * (`anchorTo: \"end\"` + `followOnAppend`). When the user is within\n * `stickThreshold` of the bottom, streaming growth of the last message keeps\n * the viewport pinned and newly appended messages auto-follow. When the user\n * scrolls up to read history, both disengage until they return to the bottom.\n * Prepending older history keeps the currently-visible message stable.\n *\n * @example\n * ```tsx\n * const { virtualItems, totalSize, measureRef, isAtBottom, scrollToBottom } =\n * useConversationVirtualizer({ scrollRef, items });\n *\n * return (\n * <AiConversation ref={scrollRef}>\n * <div style={{ height: totalSize, position: \"relative\" }}>\n * {virtualItems.map((vi) => {\n * const item = items[vi.index];\n * return (\n * <div key={item.key} ref={measureRef} data-index={vi.index}\n * style={{ position: \"absolute\", top: vi.start, width: \"100%\" }}>\n * {item.render()}\n * </div>\n * );\n * })}\n * </div>\n * {!isAtBottom && <button onClick={() => scrollToBottom()}>↓</button>}\n * </AiConversation>\n * );\n * ```\n */\nconst isSSR = typeof window === \"undefined\";\nconst useIsomorphicLayoutEffect = isSSR ? useEffect : useLayoutEffect;\n\nexport function useConversationVirtualizer({\n scrollRef,\n items,\n estimateSize = 80,\n overscan = 8,\n stickThreshold = 40,\n}: UseConversationVirtualizerOptions): UseConversationVirtualizerReturn {\n const [isAtBottom, setIsAtBottom] = useState(true);\n\n // Track container width for predictive height calculation\n const [containerWidth, setContainerWidth] = useState(0);\n\n // Track whether webfonts are ready. Pretext measures via canvas which uses\n // the browser font engine — if Inter (or any custom font) hasn't loaded\n // yet, the canvas falls back to system-ui and predictions diverge from the\n // rendered DOM. Once fonts.ready resolves we re-trigger measurement.\n const [fontsReady, setFontsReady] = useState(() => {\n if (typeof document === \"undefined\") return true;\n // `document.fonts.status === \"loaded\"` means all current FontFaces are\n // loaded. If still \"loading\" we wait for fonts.ready below.\n return document.fonts?.status === \"loaded\";\n });\n\n useEffect(() => {\n if (fontsReady) return;\n if (typeof document === \"undefined\" || !document.fonts) {\n setFontsReady(true);\n return;\n }\n let cancelled = false;\n document.fonts.ready.then(() => {\n if (!cancelled) setFontsReady(true);\n });\n return () => {\n cancelled = true;\n };\n }, [fontsReady]);\n\n useIsomorphicLayoutEffect(() => {\n const el = scrollRef.current;\n if (!el) return;\n\n const updateWidth = () => {\n // clientWidth excludes scrollbars but includes padding.\n // Subtract horizontal padding to get the actual content width.\n const style = window.getComputedStyle(el);\n const padLeft = parseFloat(style.paddingLeft) || 0;\n const padRight = parseFloat(style.paddingRight) || 0;\n setContainerWidth(el.clientWidth - padLeft - padRight);\n };\n\n updateWidth();\n\n const observer = new ResizeObserver(updateWidth);\n observer.observe(el);\n\n return () => observer.disconnect();\n }, [scrollRef]);\n\n const virtualizer = useVirtualizer({\n count: items.length,\n getScrollElement: () => scrollRef.current as HTMLElement | null,\n estimateSize: (index) => {\n const item = items[index];\n if (!item?.measure || containerWidth <= 0) {\n return estimateSize;\n }\n const {\n text,\n font = \"14px sans-serif\",\n lineHeight = 20,\n padding = 0,\n widthFraction = 1,\n horizontalPadding = 0,\n gapAfter = 0,\n whiteSpace = \"normal\",\n wordBreak = \"normal\",\n } = item.measure;\n\n // Apply the same width constraints as the rendered message bubble.\n const contentWidth = Math.max(\n 40,\n Math.floor(containerWidth * widthFraction) - horizontalPadding\n );\n\n try {\n const prepared = prepare(text, font, { whiteSpace, wordBreak });\n const { height } = layout(prepared, contentWidth, lineHeight);\n return height + padding + gapAfter;\n } catch {\n return estimateSize;\n }\n },\n overscan,\n getItemKey: (index) => items[index]?.key ?? index,\n // Chat/streaming scroll physics (TanStack Virtual end-anchored mode):\n // - `anchorTo: \"end\"` keeps the viewport pinned to the bottom when the\n // last message grows token-by-token, and keeps the currently-visible\n // message stable when older history is prepended.\n // - `followOnAppend` auto-scrolls to newly appended messages, but only\n // when the user was already within `scrollEndThreshold` of the end —\n // users reading history are never yanked down.\n // A stable `getItemKey` (above) is required for prepend stability.\n anchorTo: \"end\",\n followOnAppend: \"smooth\",\n scrollEndThreshold: stickThreshold,\n });\n\n // When container width, item measurement specs, or font-readiness change,\n // force TanStack to recompute sizes through `estimateSize`. For predictive\n // rows this calls pretext again and still skips DOM measurement.\n useEffect(() => {\n if (containerWidth > 0) {\n virtualizer.measure();\n }\n }, [containerWidth, items, virtualizer, fontsReady]);\n\n const virtualItems = virtualizer.getVirtualItems();\n const totalSize = virtualizer.getTotalSize();\n\n // Track \"at end\" using the virtualizer's own end-distance math so the\n // \"scroll to bottom\" affordance shares the exact threshold that drives\n // `followOnAppend`. `anchorTo: \"end\"` already pins the viewport when the\n // last item grows or items are appended — no manual scrollTop bookkeeping.\n useEffect(() => {\n const el = scrollRef.current;\n if (!el) return;\n\n const check = () => {\n setIsAtBottom(virtualizer.isAtEnd(stickThreshold));\n };\n\n check();\n el.addEventListener(\"scroll\", check, { passive: true });\n return () => el.removeEventListener(\"scroll\", check);\n }, [scrollRef, stickThreshold, virtualizer]);\n\n // Re-evaluate \"at end\" whenever the content size changes (streaming growth,\n // collapsible expand) so the scroll button visibility stays accurate even\n // without a user scroll event.\n useEffect(() => {\n setIsAtBottom(virtualizer.isAtEnd(stickThreshold));\n }, [totalSize, stickThreshold, virtualizer]);\n\n const scrollToBottom = useCallback(\n (behavior: ScrollBehavior = \"smooth\") => {\n if (items.length === 0) return;\n virtualizer.scrollToEnd({ behavior });\n },\n [virtualizer, items.length]\n );\n\n const measureRef = useCallback(\n (node: HTMLElement | null) => {\n if (node) virtualizer.measureElement(node);\n },\n [virtualizer]\n );\n\n // For items with predictive height, return null so TanStack doesn't remeasure.\n // For legacy items without measure spec, return the measurement callback.\n const getMeasureRef = useCallback(\n (index: number): ((node: HTMLElement | null) => void) | null => {\n const item = items[index];\n if (item?.measure) {\n return null; // Predicted height — no DOM measurement needed\n }\n return measureRef;\n },\n [items, measureRef]\n );\n\n return {\n virtualizer,\n virtualItems,\n totalSize,\n isAtBottom,\n scrollToBottom,\n measureRef,\n getMeasureRef,\n };\n}\n","/**\n * Shared pretext measurement constants for `useConversationVirtualizer`.\n *\n * These match the rendered styling of `<AiMessage>` + `<AiMessageContent>`\n * (text-sm = 14px / line-height 20px, user bubble `max-w-[70%]` with `px-4 py-3`).\n *\n * Anywhere that builds `ConversationItem.measure` for plain-text AI chat messages\n * should import from here so calibration stays in one place. If production CSS\n * for `<AiMessage>` ever changes (font size, padding, bubble width), update these\n * constants — pretext predictions will then automatically follow.\n */\n\n/** CSS `font` shorthand matching `text-sm` rendering. */\nexport const SF_AI_MEASURE_FONT =\n '14px \"Inter\", ui-sans-serif, system-ui, sans-serif';\n\n/** Line height in pixels, matching `text-sm`. */\nexport const SF_AI_MEASURE_LINE_HEIGHT = 20;\n\n/** User bubbles: `max-w-[70%]`. */\nexport const SF_AI_USER_WIDTH_FRACTION = 0.7;\n\n/** User bubbles: `px-4` → 16px × 2 horizontal padding. */\nexport const SF_AI_USER_HORIZONTAL_PADDING = 32;\n\n/** User bubbles: `py-3` → 12px × 2 vertical padding. */\nexport const SF_AI_USER_VERTICAL_PADDING = 24;\n\n/** Assistant text: full width column, no bubble. */\nexport const SF_AI_ASSISTANT_WIDTH_FRACTION = 1;\n\n/** Assistant text: no horizontal padding inside the bubble. */\nexport const SF_AI_ASSISTANT_HORIZONTAL_PADDING = 0;\n\n/** Assistant text: no vertical padding inside the bubble. */\nexport const SF_AI_ASSISTANT_VERTICAL_PADDING = 0;\n\n/** Default gap between conversation items. */\nexport const SF_AI_DEFAULT_MESSAGE_GAP = 12;\n"],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  "use client";
2
- import { t as cn } from "./cn-YROP2_ox.js";
2
+ import { t as cn } from "./cn-CmAOpn49.js";
3
+ import { t as Text } from "./text-iQ0YUFNg.js";
3
4
  import { jsx, jsxs } from "react/jsx-runtime";
4
5
  import { ArrowsClockwiseIcon, InfoIcon, WarningCircleIcon } from "@phosphor-icons/react";
5
6
  //#region src/components/ai-info-banner/ai-info-banner.tsx
@@ -63,8 +64,11 @@ function AiInfoBanner({ level = "info", icon, children, className, ...props }) {
63
64
  className: cn("flex items-center justify-center gap-2 border-y px-4 py-1.5", config.borderClassName, className),
64
65
  role: level === "error" ? "alert" : "status",
65
66
  ...props,
66
- children: [/* @__PURE__ */ jsx(IconComponent, { className: cn("size-3.5 shrink-0", config.iconClassName) }), /* @__PURE__ */ jsx("span", {
67
- className: "text-xs text-sf-subtle",
67
+ children: [/* @__PURE__ */ jsx(IconComponent, { className: cn("size-3.5 shrink-0", config.iconClassName) }), /* @__PURE__ */ jsx(Text, {
68
+ size: "xs",
69
+ variant: "secondary",
70
+ wrap: "balance",
71
+ as: "span",
68
72
  children
69
73
  })]
70
74
  });
@@ -73,4 +77,4 @@ AiInfoBanner.displayName = "AiInfoBanner";
73
77
  //#endregion
74
78
  export { SF_AI_INFO_BANNER_DEFAULT_VARIANTS as n, SF_AI_INFO_BANNER_VARIANTS as r, AiInfoBanner as t };
75
79
 
76
- //# sourceMappingURL=ai-info-banner-uFxHHwBA.js.map
80
+ //# sourceMappingURL=ai-info-banner-B_9vtGK3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-info-banner-B_9vtGK3.js","names":[],"sources":["../src/components/ai-info-banner/ai-info-banner.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n InfoIcon,\n WarningCircleIcon,\n ArrowsClockwiseIcon,\n} from \"@phosphor-icons/react\";\nimport type { ElementType, HTMLAttributes, ReactNode } from \"react\";\n\nimport { cn } from \"../../utils/cn\";\nimport { Text } from \"../text\";\n\n// ─── Variants ────────────────────────────────────────────────────────────────\n\nexport const SF_AI_INFO_BANNER_VARIANTS = {\n level: {\n info: { classes: \"\", description: \"Informational notice\" },\n error: { classes: \"\", description: \"Error notice\" },\n change: {\n classes: \"\",\n description: \"State change notice (model/mode switch)\",\n },\n },\n} as const;\n\nexport const SF_AI_INFO_BANNER_DEFAULT_VARIANTS = {\n level: \"info\",\n} as const;\n\nexport type SFAiInfoBannerLevel = keyof typeof SF_AI_INFO_BANNER_VARIANTS.level;\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type AiInfoBannerProps = HTMLAttributes<HTMLDivElement> & {\n /** Banner level controls icon and color treatment. */\n level?: SFAiInfoBannerLevel;\n /** Custom icon override. */\n icon?: ElementType;\n /** Content to display. */\n children: ReactNode;\n};\n\n// ─── Level config ─────────────────────────────────────────────────────────────\n\nconst LEVEL_CONFIG: Record<\n SFAiInfoBannerLevel,\n { icon: ElementType; iconClassName: string; borderClassName: string }\n> = {\n info: {\n icon: InfoIcon,\n iconClassName: \"text-sf-info\",\n borderClassName: \"border-sf-info/20\",\n },\n error: {\n icon: WarningCircleIcon,\n iconClassName: \"text-sf-danger\",\n borderClassName: \"border-sf-danger/20\",\n },\n change: {\n icon: ArrowsClockwiseIcon,\n iconClassName: \"text-sf-subtle\",\n borderClassName: \"border-sf-line\",\n },\n};\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\n/**\n * Inline conversation banner for system notices. Renders as a compact,\n * horizontally centered divider-like notice rather than a chat bubble.\n *\n * Maps to harness events: `error`, `info`, `mode_changed`, `model_changed`.\n *\n * @example\n * ```tsx\n * <AiInfoBanner level=\"error\">\n * Connection lost. Retrying in 3s…\n * </AiInfoBanner>\n *\n * <AiInfoBanner level=\"change\">\n * Switched to claude-opus-4-6\n * </AiInfoBanner>\n *\n * <AiInfoBanner level=\"info\">\n * Follow-up queued — will send after current response\n * </AiInfoBanner>\n * ```\n */\nexport function AiInfoBanner({\n level = \"info\",\n icon,\n children,\n className,\n ...props\n}: AiInfoBannerProps) {\n const config = LEVEL_CONFIG[level];\n const IconComponent = icon ?? config.icon;\n\n return (\n <div\n className={cn(\n \"flex items-center justify-center gap-2 border-y px-4 py-1.5\",\n config.borderClassName,\n className\n )}\n role={level === \"error\" ? \"alert\" : \"status\"}\n {...props}\n >\n <IconComponent\n className={cn(\"size-3.5 shrink-0\", config.iconClassName)}\n />\n <Text size=\"xs\" variant=\"secondary\" wrap=\"balance\" as=\"span\">\n {children}\n </Text>\n </div>\n );\n}\n\nAiInfoBanner.displayName = \"AiInfoBanner\";\n"],"mappings":";;;;;;AAcA,IAAa,6BAA6B,EACxC,OAAO;CACL,MAAM;EAAE,SAAS;EAAI,aAAa;CAAuB;CACzD,OAAO;EAAE,SAAS;EAAI,aAAa;CAAe;CAClD,QAAQ;EACN,SAAS;EACT,aAAa;CACf;AACF,EACF;AAEA,IAAa,qCAAqC,EAChD,OAAO,OACT;AAiBA,IAAM,eAGF;CACF,MAAM;EACJ,MAAM;EACN,eAAe;EACf,iBAAiB;CACnB;CACA,OAAO;EACL,MAAM;EACN,eAAe;EACf,iBAAiB;CACnB;CACA,QAAQ;EACN,MAAM;EACN,eAAe;EACf,iBAAiB;CACnB;AACF;;;;;;;;;;;;;;;;;;;;;;AAyBA,SAAgB,aAAa,EAC3B,QAAQ,QACR,MACA,UACA,WACA,GAAG,SACiB;CACpB,MAAM,SAAS,aAAa;CAC5B,MAAM,gBAAgB,QAAQ,OAAO;CAErC,OACE,qBAAC,OAAD;EACE,WAAW,GACT,+DACA,OAAO,iBACP,SACF;EACA,MAAM,UAAU,UAAU,UAAU;EACpC,GAAI;YAPN,CASE,oBAAC,eAAD,EACE,WAAW,GAAG,qBAAqB,OAAO,aAAa,EACxD,CAAA,GACD,oBAAC,MAAD;GAAM,MAAK;GAAK,SAAQ;GAAY,MAAK;GAAU,IAAG;GACnD;EACG,CAAA,CACH;;AAET;AAEA,aAAa,cAAc"}