@stigmer/react 0.3.3 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (442) hide show
  1. package/billing/AutoRechargeCard.d.ts +38 -0
  2. package/billing/AutoRechargeCard.d.ts.map +1 -0
  3. package/billing/AutoRechargeCard.js +90 -0
  4. package/billing/AutoRechargeCard.js.map +1 -0
  5. package/billing/BillingSection.d.ts +32 -0
  6. package/billing/BillingSection.d.ts.map +1 -0
  7. package/billing/BillingSection.js +81 -0
  8. package/billing/BillingSection.js.map +1 -0
  9. package/billing/CreditBalanceCard.d.ts +25 -0
  10. package/billing/CreditBalanceCard.d.ts.map +1 -0
  11. package/billing/CreditBalanceCard.js +28 -0
  12. package/billing/CreditBalanceCard.js.map +1 -0
  13. package/billing/CreditLedgerTable.d.ts +22 -0
  14. package/billing/CreditLedgerTable.d.ts.map +1 -0
  15. package/billing/CreditLedgerTable.js +75 -0
  16. package/billing/CreditLedgerTable.js.map +1 -0
  17. package/billing/CreditPackGrid.d.ts +31 -0
  18. package/billing/CreditPackGrid.d.ts.map +1 -0
  19. package/billing/CreditPackGrid.js +35 -0
  20. package/billing/CreditPackGrid.js.map +1 -0
  21. package/billing/LowBalanceBanner.d.ts +26 -0
  22. package/billing/LowBalanceBanner.d.ts.map +1 -0
  23. package/billing/LowBalanceBanner.js +33 -0
  24. package/billing/LowBalanceBanner.js.map +1 -0
  25. package/billing/PaymentMethodCard.d.ts +35 -0
  26. package/billing/PaymentMethodCard.d.ts.map +1 -0
  27. package/billing/PaymentMethodCard.js +48 -0
  28. package/billing/PaymentMethodCard.js.map +1 -0
  29. package/billing/credit-packs.d.ts +25 -0
  30. package/billing/credit-packs.d.ts.map +1 -0
  31. package/billing/credit-packs.js +39 -0
  32. package/billing/credit-packs.js.map +1 -0
  33. package/billing/format.d.ts +39 -0
  34. package/billing/format.d.ts.map +1 -0
  35. package/billing/format.js +90 -0
  36. package/billing/format.js.map +1 -0
  37. package/billing/index.d.ts +32 -0
  38. package/billing/index.d.ts.map +1 -0
  39. package/billing/index.js +21 -0
  40. package/billing/index.js.map +1 -0
  41. package/billing/useBillingAccount.d.ts +40 -0
  42. package/billing/useBillingAccount.d.ts.map +1 -0
  43. package/billing/useBillingAccount.js +35 -0
  44. package/billing/useBillingAccount.js.map +1 -0
  45. package/billing/useBillingUsageReport.d.ts +42 -0
  46. package/billing/useBillingUsageReport.d.ts.map +1 -0
  47. package/billing/useBillingUsageReport.js +43 -0
  48. package/billing/useBillingUsageReport.js.map +1 -0
  49. package/billing/useCreateBillingPortalSession.d.ts +35 -0
  50. package/billing/useCreateBillingPortalSession.d.ts.map +1 -0
  51. package/billing/useCreateBillingPortalSession.js +50 -0
  52. package/billing/useCreateBillingPortalSession.js.map +1 -0
  53. package/billing/useCreateCheckoutSession.d.ts +54 -0
  54. package/billing/useCreateCheckoutSession.d.ts.map +1 -0
  55. package/billing/useCreateCheckoutSession.js +58 -0
  56. package/billing/useCreateCheckoutSession.js.map +1 -0
  57. package/billing/useCreditLedger.d.ts +48 -0
  58. package/billing/useCreditLedger.d.ts.map +1 -0
  59. package/billing/useCreditLedger.js +39 -0
  60. package/billing/useCreditLedger.js.map +1 -0
  61. package/billing/useCustomerModelPricing.d.ts +41 -0
  62. package/billing/useCustomerModelPricing.d.ts.map +1 -0
  63. package/billing/useCustomerModelPricing.js +37 -0
  64. package/billing/useCustomerModelPricing.js.map +1 -0
  65. package/billing/useSetAutoRechargeConfig.d.ts +50 -0
  66. package/billing/useSetAutoRechargeConfig.d.ts.map +1 -0
  67. package/billing/useSetAutoRechargeConfig.js +53 -0
  68. package/billing/useSetAutoRechargeConfig.js.map +1 -0
  69. package/composer/ComposerToolbar.js +1 -1
  70. package/composer/ComposerToolbar.js.map +1 -1
  71. package/composer/SessionComposer.d.ts +1 -1
  72. package/composer/SessionComposer.d.ts.map +1 -1
  73. package/composer/SessionComposer.js +19 -4
  74. package/composer/SessionComposer.js.map +1 -1
  75. package/composer/__tests__/SessionComposer-memo.test.d.ts +2 -0
  76. package/composer/__tests__/SessionComposer-memo.test.d.ts.map +1 -0
  77. package/composer/__tests__/SessionComposer-memo.test.js +23 -0
  78. package/composer/__tests__/SessionComposer-memo.test.js.map +1 -0
  79. package/execution/ApprovalCard.d.ts +5 -1
  80. package/execution/ApprovalCard.d.ts.map +1 -1
  81. package/execution/ApprovalCard.js +7 -3
  82. package/execution/ApprovalCard.js.map +1 -1
  83. package/execution/ExecutionPhaseBadge.d.ts +1 -1
  84. package/execution/ExecutionPhaseBadge.d.ts.map +1 -1
  85. package/execution/ExecutionPhaseBadge.js +3 -2
  86. package/execution/ExecutionPhaseBadge.js.map +1 -1
  87. package/execution/MessageEntry.d.ts +7 -3
  88. package/execution/MessageEntry.d.ts.map +1 -1
  89. package/execution/MessageEntry.js +19 -8
  90. package/execution/MessageEntry.js.map +1 -1
  91. package/execution/MessageThread.d.ts +84 -3
  92. package/execution/MessageThread.d.ts.map +1 -1
  93. package/execution/MessageThread.js +113 -65
  94. package/execution/MessageThread.js.map +1 -1
  95. package/execution/SetupProgress.d.ts +1 -1
  96. package/execution/SetupProgress.d.ts.map +1 -1
  97. package/execution/SetupProgress.js +3 -3
  98. package/execution/SetupProgress.js.map +1 -1
  99. package/execution/SubAgentSection.d.ts +5 -1
  100. package/execution/SubAgentSection.d.ts.map +1 -1
  101. package/execution/SubAgentSection.js +13 -7
  102. package/execution/SubAgentSection.js.map +1 -1
  103. package/execution/ThreadSkeleton.d.ts +22 -0
  104. package/execution/ThreadSkeleton.d.ts.map +1 -0
  105. package/execution/ThreadSkeleton.js +26 -0
  106. package/execution/ThreadSkeleton.js.map +1 -0
  107. package/execution/ToolCallGroup.d.ts +16 -1
  108. package/execution/ToolCallGroup.d.ts.map +1 -1
  109. package/execution/ToolCallGroup.js +31 -3
  110. package/execution/ToolCallGroup.js.map +1 -1
  111. package/execution/UsageWidget.d.ts +1 -1
  112. package/execution/__tests__/message-entry.test.d.ts +2 -0
  113. package/execution/__tests__/message-entry.test.d.ts.map +1 -0
  114. package/execution/__tests__/message-entry.test.js +178 -0
  115. package/execution/__tests__/message-entry.test.js.map +1 -0
  116. package/execution/__tests__/thread-keys.test.d.ts +2 -0
  117. package/execution/__tests__/thread-keys.test.d.ts.map +1 -0
  118. package/execution/__tests__/thread-keys.test.js +289 -0
  119. package/execution/__tests__/thread-keys.test.js.map +1 -0
  120. package/execution/__tests__/thread-memoization.test.d.ts +2 -0
  121. package/execution/__tests__/thread-memoization.test.d.ts.map +1 -0
  122. package/execution/__tests__/thread-memoization.test.js +262 -0
  123. package/execution/__tests__/thread-memoization.test.js.map +1 -0
  124. package/execution/__tests__/thread-skeleton.test.d.ts +2 -0
  125. package/execution/__tests__/thread-skeleton.test.d.ts.map +1 -0
  126. package/execution/__tests__/thread-skeleton.test.js +35 -0
  127. package/execution/__tests__/thread-skeleton.test.js.map +1 -0
  128. package/execution/__tests__/useExecutionStream.test.js +73 -10
  129. package/execution/__tests__/useExecutionStream.test.js.map +1 -1
  130. package/execution/__tests__/useSessionVariables-stability.test.d.ts +2 -0
  131. package/execution/__tests__/useSessionVariables-stability.test.d.ts.map +1 -0
  132. package/execution/__tests__/useSessionVariables-stability.test.js +69 -0
  133. package/execution/__tests__/useSessionVariables-stability.test.js.map +1 -0
  134. package/execution/__tests__/virtualized-thread.test.d.ts +2 -0
  135. package/execution/__tests__/virtualized-thread.test.d.ts.map +1 -0
  136. package/execution/__tests__/virtualized-thread.test.js +274 -0
  137. package/execution/__tests__/virtualized-thread.test.js.map +1 -0
  138. package/execution/index.d.ts +2 -0
  139. package/execution/index.d.ts.map +1 -1
  140. package/execution/index.js +1 -0
  141. package/execution/index.js.map +1 -1
  142. package/execution/useExecutionStream.d.ts +35 -10
  143. package/execution/useExecutionStream.d.ts.map +1 -1
  144. package/execution/useExecutionStream.js +79 -40
  145. package/execution/useExecutionStream.js.map +1 -1
  146. package/execution/useSessionVariables.d.ts.map +1 -1
  147. package/execution/useSessionVariables.js +4 -3
  148. package/execution/useSessionVariables.js.map +1 -1
  149. package/github/useGitHubConnection.d.ts.map +1 -1
  150. package/github/useGitHubConnection.js +5 -4
  151. package/github/useGitHubConnection.js.map +1 -1
  152. package/identity-account/index.d.ts +2 -0
  153. package/identity-account/index.d.ts.map +1 -0
  154. package/identity-account/index.js +2 -0
  155. package/identity-account/index.js.map +1 -0
  156. package/identity-account/useIdentityAccountGate.d.ts +81 -0
  157. package/identity-account/useIdentityAccountGate.d.ts.map +1 -0
  158. package/identity-account/useIdentityAccountGate.js +100 -0
  159. package/identity-account/useIdentityAccountGate.js.map +1 -0
  160. package/index.d.ts +10 -4
  161. package/index.d.ts.map +1 -1
  162. package/index.js +8 -2
  163. package/index.js.map +1 -1
  164. package/internal/FetchCacheProvider.d.ts +44 -0
  165. package/internal/FetchCacheProvider.d.ts.map +1 -0
  166. package/internal/FetchCacheProvider.js +61 -0
  167. package/internal/FetchCacheProvider.js.map +1 -0
  168. package/internal/JumpToLatestButton.d.ts +14 -0
  169. package/internal/JumpToLatestButton.d.ts.map +1 -0
  170. package/internal/JumpToLatestButton.js +19 -0
  171. package/internal/JumpToLatestButton.js.map +1 -0
  172. package/internal/ThreadItemWrapper.d.ts +20 -0
  173. package/internal/ThreadItemWrapper.d.ts.map +1 -0
  174. package/internal/ThreadItemWrapper.js +44 -0
  175. package/internal/ThreadItemWrapper.js.map +1 -0
  176. package/internal/VirtualizedThread.d.ts +25 -0
  177. package/internal/VirtualizedThread.d.ts.map +1 -0
  178. package/internal/VirtualizedThread.js +58 -0
  179. package/internal/VirtualizedThread.js.map +1 -0
  180. package/internal/__tests__/fetch-cache.test.d.ts +2 -0
  181. package/internal/__tests__/fetch-cache.test.d.ts.map +1 -0
  182. package/internal/__tests__/fetch-cache.test.js +182 -0
  183. package/internal/__tests__/fetch-cache.test.js.map +1 -0
  184. package/internal/__tests__/stream-controller.test.d.ts +2 -0
  185. package/internal/__tests__/stream-controller.test.d.ts.map +1 -0
  186. package/internal/__tests__/stream-controller.test.js +294 -0
  187. package/internal/__tests__/stream-controller.test.js.map +1 -0
  188. package/internal/__tests__/thread-animation.test.d.ts +2 -0
  189. package/internal/__tests__/thread-animation.test.d.ts.map +1 -0
  190. package/internal/__tests__/thread-animation.test.js +79 -0
  191. package/internal/__tests__/thread-animation.test.js.map +1 -0
  192. package/internal/__tests__/useAutoScroll.test.d.ts +2 -0
  193. package/internal/__tests__/useAutoScroll.test.d.ts.map +1 -0
  194. package/internal/__tests__/useAutoScroll.test.js +188 -0
  195. package/internal/__tests__/useAutoScroll.test.js.map +1 -0
  196. package/internal/__tests__/useFetch-cache.test.d.ts +2 -0
  197. package/internal/__tests__/useFetch-cache.test.d.ts.map +1 -0
  198. package/internal/__tests__/useFetch-cache.test.js +137 -0
  199. package/internal/__tests__/useFetch-cache.test.js.map +1 -0
  200. package/internal/dev/__tests__/use-key-stability.test.d.ts +2 -0
  201. package/internal/dev/__tests__/use-key-stability.test.d.ts.map +1 -0
  202. package/internal/dev/__tests__/use-key-stability.test.js +72 -0
  203. package/internal/dev/__tests__/use-key-stability.test.js.map +1 -0
  204. package/internal/dev/__tests__/use-render-tracer.test.d.ts +2 -0
  205. package/internal/dev/__tests__/use-render-tracer.test.d.ts.map +1 -0
  206. package/internal/dev/__tests__/use-render-tracer.test.js +55 -0
  207. package/internal/dev/__tests__/use-render-tracer.test.js.map +1 -0
  208. package/internal/dev/dom-counter.d.ts +14 -0
  209. package/internal/dev/dom-counter.d.ts.map +1 -0
  210. package/internal/dev/dom-counter.js +39 -0
  211. package/internal/dev/dom-counter.js.map +1 -0
  212. package/internal/dev/index.d.ts +6 -0
  213. package/internal/dev/index.d.ts.map +1 -0
  214. package/internal/dev/index.js +6 -0
  215. package/internal/dev/index.js.map +1 -0
  216. package/internal/dev/profiler-wrapper.d.ts +16 -0
  217. package/internal/dev/profiler-wrapper.d.ts.map +1 -0
  218. package/internal/dev/profiler-wrapper.js +31 -0
  219. package/internal/dev/profiler-wrapper.js.map +1 -0
  220. package/internal/dev/use-key-stability.d.ts +22 -0
  221. package/internal/dev/use-key-stability.d.ts.map +1 -0
  222. package/internal/dev/use-key-stability.js +67 -0
  223. package/internal/dev/use-key-stability.js.map +1 -0
  224. package/internal/dev/use-render-tracer.d.ts +13 -0
  225. package/internal/dev/use-render-tracer.d.ts.map +1 -0
  226. package/internal/dev/use-render-tracer.js +57 -0
  227. package/internal/dev/use-render-tracer.js.map +1 -0
  228. package/internal/dev/use-stream-rate.d.ts +23 -0
  229. package/internal/dev/use-stream-rate.d.ts.map +1 -0
  230. package/internal/dev/use-stream-rate.js +94 -0
  231. package/internal/dev/use-stream-rate.js.map +1 -0
  232. package/internal/fetch-cache.d.ts +72 -0
  233. package/internal/fetch-cache.d.ts.map +1 -0
  234. package/internal/fetch-cache.js +118 -0
  235. package/internal/fetch-cache.js.map +1 -0
  236. package/internal/store/__tests__/conversation-store.test.d.ts +2 -0
  237. package/internal/store/__tests__/conversation-store.test.d.ts.map +1 -0
  238. package/internal/store/__tests__/conversation-store.test.js +200 -0
  239. package/internal/store/__tests__/conversation-store.test.js.map +1 -0
  240. package/internal/store/__tests__/structural-share.test.d.ts +2 -0
  241. package/internal/store/__tests__/structural-share.test.d.ts.map +1 -0
  242. package/internal/store/__tests__/structural-share.test.js +368 -0
  243. package/internal/store/__tests__/structural-share.test.js.map +1 -0
  244. package/internal/store/conversation-store.d.ts +62 -0
  245. package/internal/store/conversation-store.d.ts.map +1 -0
  246. package/internal/store/conversation-store.js +95 -0
  247. package/internal/store/conversation-store.js.map +1 -0
  248. package/internal/store/index.d.ts +31 -0
  249. package/internal/store/index.d.ts.map +1 -0
  250. package/internal/store/index.js +54 -0
  251. package/internal/store/index.js.map +1 -0
  252. package/internal/store/structural-share.d.ts +13 -0
  253. package/internal/store/structural-share.d.ts.map +1 -0
  254. package/internal/store/structural-share.js +240 -0
  255. package/internal/store/structural-share.js.map +1 -0
  256. package/internal/stream-controller.d.ts +85 -0
  257. package/internal/stream-controller.d.ts.map +1 -0
  258. package/internal/stream-controller.js +146 -0
  259. package/internal/stream-controller.js.map +1 -0
  260. package/internal/useAutoScroll.d.ts +32 -0
  261. package/internal/useAutoScroll.d.ts.map +1 -0
  262. package/internal/useAutoScroll.js +97 -0
  263. package/internal/useAutoScroll.js.map +1 -0
  264. package/internal/useFetch.d.ts +14 -0
  265. package/internal/useFetch.d.ts.map +1 -1
  266. package/internal/useFetch.js +32 -2
  267. package/internal/useFetch.js.map +1 -1
  268. package/package.json +7 -5
  269. package/session/__tests__/useNewSessionFlow.test.js +16 -0
  270. package/session/__tests__/useNewSessionFlow.test.js.map +1 -1
  271. package/session/__tests__/usePersistedModel.test.d.ts +2 -0
  272. package/session/__tests__/usePersistedModel.test.d.ts.map +1 -0
  273. package/session/__tests__/usePersistedModel.test.js +82 -0
  274. package/session/__tests__/usePersistedModel.test.js.map +1 -0
  275. package/session/__tests__/useSession.test.d.ts +2 -0
  276. package/session/__tests__/useSession.test.d.ts.map +1 -0
  277. package/session/__tests__/useSession.test.js +130 -0
  278. package/session/__tests__/useSession.test.js.map +1 -0
  279. package/session/useNewSessionFlow.d.ts.map +1 -1
  280. package/session/useNewSessionFlow.js +12 -6
  281. package/session/useNewSessionFlow.js.map +1 -1
  282. package/session/usePersistedModel.d.ts +3 -0
  283. package/session/usePersistedModel.d.ts.map +1 -1
  284. package/session/usePersistedModel.js +27 -2
  285. package/session/usePersistedModel.js.map +1 -1
  286. package/session/useSession.d.ts.map +1 -1
  287. package/session/useSession.js +1 -1
  288. package/session/useSession.js.map +1 -1
  289. package/session/useSessionConversation.d.ts.map +1 -1
  290. package/session/useSessionConversation.js +9 -1
  291. package/session/useSessionConversation.js.map +1 -1
  292. package/session/useSessionExecutions.d.ts.map +1 -1
  293. package/session/useSessionExecutions.js +1 -1
  294. package/session/useSessionExecutions.js.map +1 -1
  295. package/session/useSessionPageFlow.js +1 -1
  296. package/session/useSessionPageFlow.js.map +1 -1
  297. package/session/useSessionUsage.d.ts +24 -40
  298. package/session/useSessionUsage.d.ts.map +1 -1
  299. package/session/useSessionUsage.js +64 -97
  300. package/session/useSessionUsage.js.map +1 -1
  301. package/settings/BillingSection.d.ts +3 -0
  302. package/settings/BillingSection.d.ts.map +1 -0
  303. package/settings/BillingSection.js +3 -0
  304. package/settings/BillingSection.js.map +1 -0
  305. package/settings/index.d.ts +2 -0
  306. package/settings/index.d.ts.map +1 -1
  307. package/settings/index.js +1 -0
  308. package/settings/index.js.map +1 -1
  309. package/settings/settings-nav.js +1 -1
  310. package/settings/settings-nav.js.map +1 -1
  311. package/src/billing/AutoRechargeCard.tsx +274 -0
  312. package/src/billing/BillingSection.tsx +255 -0
  313. package/src/billing/CreditBalanceCard.tsx +81 -0
  314. package/src/billing/CreditLedgerTable.tsx +281 -0
  315. package/src/billing/CreditPackGrid.tsx +132 -0
  316. package/src/billing/LowBalanceBanner.tsx +67 -0
  317. package/src/billing/PaymentMethodCard.tsx +133 -0
  318. package/src/billing/credit-packs.ts +54 -0
  319. package/src/billing/format.ts +97 -0
  320. package/src/billing/index.ts +51 -0
  321. package/src/billing/useBillingAccount.ts +64 -0
  322. package/src/billing/useBillingUsageReport.ts +73 -0
  323. package/src/billing/useCreateBillingPortalSession.ts +76 -0
  324. package/src/billing/useCreateCheckoutSession.ts +101 -0
  325. package/src/billing/useCreditLedger.ts +79 -0
  326. package/src/billing/useCustomerModelPricing.ts +67 -0
  327. package/src/billing/useSetAutoRechargeConfig.ts +90 -0
  328. package/src/composer/ComposerToolbar.tsx +1 -1
  329. package/src/composer/SessionComposer.tsx +22 -4
  330. package/src/composer/__tests__/SessionComposer-memo.test.ts +26 -0
  331. package/src/execution/ApprovalCard.tsx +7 -3
  332. package/src/execution/ExecutionPhaseBadge.tsx +3 -2
  333. package/src/execution/MessageEntry.tsx +27 -16
  334. package/src/execution/MessageThread.tsx +308 -131
  335. package/src/execution/SetupProgress.tsx +3 -3
  336. package/src/execution/SubAgentSection.tsx +14 -6
  337. package/src/execution/ThreadSkeleton.tsx +73 -0
  338. package/src/execution/ToolCallGroup.tsx +36 -3
  339. package/src/execution/UsageWidget.tsx +1 -1
  340. package/src/execution/__tests__/message-entry.test.tsx +236 -0
  341. package/src/execution/__tests__/thread-keys.test.ts +409 -0
  342. package/src/execution/__tests__/thread-memoization.test.ts +320 -0
  343. package/src/execution/__tests__/thread-skeleton.test.tsx +44 -0
  344. package/src/execution/__tests__/useExecutionStream.test.tsx +109 -12
  345. package/src/execution/__tests__/useSessionVariables-stability.test.ts +95 -0
  346. package/src/execution/__tests__/virtualized-thread.test.tsx +401 -0
  347. package/src/execution/index.ts +3 -0
  348. package/src/execution/useExecutionStream.ts +123 -48
  349. package/src/execution/useSessionVariables.ts +17 -12
  350. package/src/github/useGitHubConnection.ts +18 -13
  351. package/src/identity-account/index.ts +5 -0
  352. package/src/identity-account/useIdentityAccountGate.ts +163 -0
  353. package/src/index.ts +73 -0
  354. package/src/internal/FetchCacheProvider.tsx +74 -0
  355. package/src/internal/JumpToLatestButton.tsx +61 -0
  356. package/src/internal/ThreadItemWrapper.tsx +65 -0
  357. package/src/internal/VirtualizedThread.tsx +162 -0
  358. package/src/internal/__tests__/fetch-cache.test.ts +230 -0
  359. package/src/internal/__tests__/stream-controller.test.ts +395 -0
  360. package/src/internal/__tests__/thread-animation.test.tsx +121 -0
  361. package/src/internal/__tests__/useAutoScroll.test.tsx +261 -0
  362. package/src/internal/__tests__/useFetch-cache.test.ts +214 -0
  363. package/src/internal/dev/__tests__/use-key-stability.test.ts +124 -0
  364. package/src/internal/dev/__tests__/use-render-tracer.test.ts +78 -0
  365. package/src/internal/dev/dom-counter.ts +47 -0
  366. package/src/internal/dev/index.ts +5 -0
  367. package/src/internal/dev/profiler-wrapper.tsx +52 -0
  368. package/src/internal/dev/use-key-stability.ts +86 -0
  369. package/src/internal/dev/use-render-tracer.ts +70 -0
  370. package/src/internal/dev/use-stream-rate.ts +138 -0
  371. package/src/internal/fetch-cache.ts +155 -0
  372. package/src/internal/store/__tests__/conversation-store.test.ts +257 -0
  373. package/src/internal/store/__tests__/structural-share.test.ts +454 -0
  374. package/src/internal/store/conversation-store.ts +128 -0
  375. package/src/internal/store/index.ts +68 -0
  376. package/src/internal/store/structural-share.ts +318 -0
  377. package/src/internal/stream-controller.ts +201 -0
  378. package/src/internal/useAutoScroll.ts +121 -0
  379. package/src/internal/useFetch.ts +51 -2
  380. package/src/session/__tests__/useNewSessionFlow.test.tsx +22 -0
  381. package/src/session/__tests__/usePersistedModel.test.tsx +117 -0
  382. package/src/session/__tests__/useSession.test.tsx +187 -0
  383. package/src/session/useNewSessionFlow.ts +12 -6
  384. package/src/session/usePersistedModel.ts +28 -2
  385. package/src/session/useSession.ts +1 -0
  386. package/src/session/useSessionConversation.ts +11 -2
  387. package/src/session/useSessionExecutions.ts +1 -0
  388. package/src/session/useSessionPageFlow.ts +1 -1
  389. package/src/session/useSessionUsage.ts +102 -123
  390. package/src/settings/BillingSection.tsx +4 -0
  391. package/src/settings/index.ts +2 -0
  392. package/src/settings/settings-nav.ts +1 -1
  393. package/src/styles.css +31 -0
  394. package/src/usage/AgentBreakdownList.tsx +147 -0
  395. package/src/usage/CreditRunwayIndicator.tsx +71 -0
  396. package/src/usage/ExportButton.tsx +115 -0
  397. package/src/usage/HarnessSplitCard.tsx +103 -0
  398. package/src/usage/OrgUsagePanel.tsx +109 -45
  399. package/src/usage/index.ts +15 -0
  400. package/src/usage/useExportCSV.ts +115 -0
  401. package/src/usage/useOrgUsageReport.ts +2 -1
  402. package/src/workspace/__tests__/useWorkspaceEntries-stability.test.ts +76 -0
  403. package/src/workspace/useWorkspaceEntries.ts +16 -11
  404. package/styles.css +1 -1
  405. package/usage/AgentBreakdownList.d.ts +21 -0
  406. package/usage/AgentBreakdownList.d.ts.map +1 -0
  407. package/usage/AgentBreakdownList.js +44 -0
  408. package/usage/AgentBreakdownList.js.map +1 -0
  409. package/usage/CreditRunwayIndicator.d.ts +21 -0
  410. package/usage/CreditRunwayIndicator.d.ts.map +1 -0
  411. package/usage/CreditRunwayIndicator.js +38 -0
  412. package/usage/CreditRunwayIndicator.js.map +1 -0
  413. package/usage/ExportButton.d.ts +20 -0
  414. package/usage/ExportButton.d.ts.map +1 -0
  415. package/usage/ExportButton.js +36 -0
  416. package/usage/ExportButton.js.map +1 -0
  417. package/usage/HarnessSplitCard.d.ts +17 -0
  418. package/usage/HarnessSplitCard.d.ts.map +1 -0
  419. package/usage/HarnessSplitCard.js +38 -0
  420. package/usage/HarnessSplitCard.js.map +1 -0
  421. package/usage/OrgUsagePanel.d.ts.map +1 -1
  422. package/usage/OrgUsagePanel.js +30 -22
  423. package/usage/OrgUsagePanel.js.map +1 -1
  424. package/usage/index.d.ts +10 -0
  425. package/usage/index.d.ts.map +1 -1
  426. package/usage/index.js +5 -0
  427. package/usage/index.js.map +1 -1
  428. package/usage/useExportCSV.d.ts +23 -0
  429. package/usage/useExportCSV.d.ts.map +1 -0
  430. package/usage/useExportCSV.js +81 -0
  431. package/usage/useExportCSV.js.map +1 -0
  432. package/usage/useOrgUsageReport.d.ts +2 -1
  433. package/usage/useOrgUsageReport.d.ts.map +1 -1
  434. package/usage/useOrgUsageReport.js +2 -1
  435. package/usage/useOrgUsageReport.js.map +1 -1
  436. package/workspace/__tests__/useWorkspaceEntries-stability.test.d.ts +2 -0
  437. package/workspace/__tests__/useWorkspaceEntries-stability.test.d.ts.map +1 -0
  438. package/workspace/__tests__/useWorkspaceEntries-stability.test.js +57 -0
  439. package/workspace/__tests__/useWorkspaceEntries-stability.test.js.map +1 -0
  440. package/workspace/useWorkspaceEntries.d.ts.map +1 -1
  441. package/workspace/useWorkspaceEntries.js +5 -4
  442. package/workspace/useWorkspaceEntries.js.map +1 -1
@@ -0,0 +1,103 @@
1
+ "use client";
2
+
3
+ import { cn } from "@stigmer/theme";
4
+ import type { HarnessCostSummary } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/io_pb";
5
+ import { formatCost } from "../execution/UsageWidget";
6
+
7
+ /** Props for {@link HarnessSplitCard}. */
8
+ export interface HarnessSplitCardProps {
9
+ /** Harness breakdown entries from the org usage report. */
10
+ readonly breakdown: readonly HarnessCostSummary[];
11
+ /** Additional CSS class names. */
12
+ readonly className?: string;
13
+ }
14
+
15
+ const HARNESS_LABELS: Record<string, string> = {
16
+ native: "Native",
17
+ cursor: "Cursor",
18
+ };
19
+
20
+ const HARNESS_COLORS: Record<string, string> = {
21
+ native: "bg-chart-1",
22
+ cursor: "bg-chart-3",
23
+ };
24
+
25
+ /**
26
+ * Displays the cost split between execution harnesses as a segmented bar.
27
+ *
28
+ * Renders a two-tone horizontal bar with labels showing cost and percentage
29
+ * per harness. Designed for at-a-glance understanding of native vs cursor
30
+ * cost distribution.
31
+ */
32
+ export function HarnessSplitCard({
33
+ breakdown,
34
+ className,
35
+ }: HarnessSplitCardProps) {
36
+ if (breakdown.length === 0) return null;
37
+
38
+ const totalCost = breakdown.reduce(
39
+ (sum, h) => sum + Number(h.billableCostMicros),
40
+ 0,
41
+ );
42
+
43
+ if (totalCost <= 0) return null;
44
+
45
+ return (
46
+ <div className={className}>
47
+ <h3 className="mb-2 text-xs font-semibold text-foreground">
48
+ Harness Split
49
+ </h3>
50
+ <div className="rounded-lg border border-border bg-card px-3.5 py-3">
51
+ {/* Segmented bar */}
52
+ <div
53
+ className="flex h-3 w-full overflow-hidden rounded-full"
54
+ role="img"
55
+ aria-label="Cost distribution by harness"
56
+ >
57
+ {breakdown.map((entry) => {
58
+ const cost = Number(entry.billableCostMicros);
59
+ const pct = (cost / totalCost) * 100;
60
+ if (pct <= 0) return null;
61
+ return (
62
+ <div
63
+ key={entry.harness}
64
+ className={cn(
65
+ "transition-all",
66
+ HARNESS_COLORS[entry.harness] ?? "bg-chart-4",
67
+ )}
68
+ style={{ width: `${pct}%` }}
69
+ />
70
+ );
71
+ })}
72
+ </div>
73
+
74
+ {/* Labels */}
75
+ <div className="mt-2.5 flex flex-wrap gap-x-5 gap-y-1">
76
+ {breakdown.map((entry) => {
77
+ const cost = Number(entry.billableCostMicros);
78
+ const pct = totalCost > 0 ? (cost / totalCost) * 100 : 0;
79
+ return (
80
+ <div key={entry.harness} className="flex items-center gap-1.5">
81
+ <div
82
+ className={cn(
83
+ "size-2 rounded-full",
84
+ HARNESS_COLORS[entry.harness] ?? "bg-chart-4",
85
+ )}
86
+ />
87
+ <span className="text-xs text-muted-foreground">
88
+ {HARNESS_LABELS[entry.harness] ?? entry.harness}
89
+ </span>
90
+ <span className="text-xs tabular-nums font-medium text-foreground">
91
+ {formatCost(cost / 1_000_000)}
92
+ </span>
93
+ <span className="text-[0.6rem] tabular-nums text-muted-foreground">
94
+ ({pct.toFixed(0)}%)
95
+ </span>
96
+ </div>
97
+ );
98
+ })}
99
+ </div>
100
+ </div>
101
+ </div>
102
+ );
103
+ }
@@ -17,6 +17,11 @@ import {
17
17
  presetLabel,
18
18
  type DateRangePreset,
19
19
  } from "./date-range";
20
+ import { CreditRunwayIndicator } from "./CreditRunwayIndicator";
21
+ import { AgentBreakdownList } from "./AgentBreakdownList";
22
+ import { HarnessSplitCard } from "./HarnessSplitCard";
23
+ import { ExportButton } from "./ExportButton";
24
+ import { useExportCSV } from "./useExportCSV";
20
25
 
21
26
  // ---------------------------------------------------------------------------
22
27
  // Public API
@@ -49,6 +54,8 @@ export function OrgUsagePanel({ orgId, className }: OrgUsagePanelProps) {
49
54
  const [preset, setPreset] = useState<DateRangePreset>("30d");
50
55
  const dateRange = dateRangeFromPreset(preset);
51
56
  const { report, isLoading, error } = useOrgUsageReport(orgId, dateRange);
57
+ const { exportCSV, isExporting } = useExportCSV(report, orgId);
58
+ const daysInRange = Number.parseInt(preset, 10);
52
59
 
53
60
  if (isLoading) {
54
61
  return (
@@ -90,18 +97,36 @@ export function OrgUsagePanel({ orgId, className }: OrgUsagePanelProps) {
90
97
 
91
98
  return (
92
99
  <div className={cn("space-y-6", className)}>
93
- <DateRangeSelector
94
- activePreset={preset}
95
- onPresetChange={setPreset}
96
- dateRange={dateRange}
97
- />
100
+ <div className="flex items-center justify-between gap-3">
101
+ <DateRangeSelector
102
+ activePreset={preset}
103
+ onPresetChange={setPreset}
104
+ dateRange={dateRange}
105
+ />
106
+ <ExportButton
107
+ onExport={exportCSV}
108
+ isExporting={isExporting}
109
+ disabled={!report || report.modelBreakdown.length === 0}
110
+ />
111
+ </div>
98
112
 
99
- <SummaryCards report={report} />
113
+ <SummaryCards report={report} daysInRange={daysInRange} />
100
114
 
101
115
  {report.dailyCosts.length > 0 && (
102
116
  <DailyCostChart entries={report.dailyCosts} />
103
117
  )}
104
118
 
119
+ {report.harnessBreakdown.length > 0 && (
120
+ <HarnessSplitCard breakdown={report.harnessBreakdown} />
121
+ )}
122
+
123
+ {report.topAgentsByCost.length > 0 && (
124
+ <AgentBreakdownList
125
+ agents={report.topAgentsByCost}
126
+ totalBillableCostMicros={report.totalBillableCostMicros}
127
+ />
128
+ )}
129
+
105
130
  {report.modelBreakdown.length > 0 && (
106
131
  <ModelBreakdownList models={report.modelBreakdown} />
107
132
  )}
@@ -126,7 +151,7 @@ function DateRangeSelector({
126
151
  }) {
127
152
  return (
128
153
  <div
129
- className="flex items-center justify-between gap-3"
154
+ className="flex items-center gap-3"
130
155
  role="group"
131
156
  aria-label="Date range"
132
157
  >
@@ -159,43 +184,80 @@ function DateRangeSelector({
159
184
  // SummaryCards (internal)
160
185
  // ---------------------------------------------------------------------------
161
186
 
162
- function SummaryCards({ report }: { report: GetOrgUsageReportOutput }) {
187
+ function microsToUsd(micros: bigint): number {
188
+ return Number(micros) / 1_000_000;
189
+ }
190
+
191
+ function SummaryCards({
192
+ report,
193
+ daysInRange,
194
+ }: {
195
+ report: GetOrgUsageReportOutput;
196
+ daysInRange: number;
197
+ }) {
163
198
  const { totalLlmCalls, totalTokens } = report.modelBreakdown.reduce(
164
199
  (acc, m) => ({
165
200
  totalLlmCalls: acc.totalLlmCalls + m.callCount,
166
201
  totalTokens:
167
202
  acc.totalTokens +
168
- m.inputTokens +
169
- m.outputTokens +
170
- m.cacheCreationTokens +
171
- m.cacheReadTokens,
203
+ Number(m.inputTokens) +
204
+ Number(m.outputTokens) +
205
+ Number(m.cacheCreationInputTokens) +
206
+ Number(m.cacheReadInputTokens),
172
207
  }),
173
208
  { totalLlmCalls: 0, totalTokens: 0 },
174
209
  );
175
210
 
176
- const cards: { label: string; value: string }[] = [
177
- { label: "Total Cost", value: formatCost(report.totalCostUsd) },
178
- { label: "LLM Calls", value: formatCompactNumber(totalLlmCalls) },
179
- { label: "Tokens", value: formatCompactNumber(totalTokens) },
180
- ];
181
-
182
211
  return (
183
- <div
184
- className="grid grid-cols-3 gap-3"
185
- role="group"
186
- aria-label="Usage summary"
187
- >
188
- {cards.map((card) => (
189
- <div
190
- key={card.label}
191
- className="rounded-lg border border-border bg-card px-3.5 py-3"
192
- >
212
+ <div role="group" aria-label="Usage summary" className="space-y-2">
213
+ {/* Primary row */}
214
+ <div className="grid grid-cols-3 gap-3">
215
+ <div className="rounded-lg border border-border bg-card px-3.5 py-3">
193
216
  <div className="text-lg font-semibold tabular-nums text-foreground">
194
- {card.value}
217
+ {formatCost(microsToUsd(report.totalBillableCostMicros))}
195
218
  </div>
196
- <div className="text-xs text-muted-foreground">{card.label}</div>
219
+ <div className="text-xs text-muted-foreground">Total Cost</div>
220
+ <CreditRunwayIndicator
221
+ totalBillableCostMicros={report.totalBillableCostMicros}
222
+ daysInRange={daysInRange}
223
+ className="mt-0.5 block"
224
+ />
197
225
  </div>
198
- ))}
226
+ <div className="rounded-lg border border-border bg-card px-3.5 py-3">
227
+ <div className="text-lg font-semibold tabular-nums text-foreground">
228
+ {formatCompactNumber(totalLlmCalls)}
229
+ </div>
230
+ <div className="text-xs text-muted-foreground">LLM Calls</div>
231
+ </div>
232
+ <div className="rounded-lg border border-border bg-card px-3.5 py-3">
233
+ <div className="text-lg font-semibold tabular-nums text-foreground">
234
+ {formatCompactNumber(totalTokens)}
235
+ </div>
236
+ <div className="text-xs text-muted-foreground">Tokens</div>
237
+ </div>
238
+ </div>
239
+
240
+ {/* Secondary row */}
241
+ <div className="grid grid-cols-3 gap-3">
242
+ <div className="rounded-lg border border-border-muted bg-card/50 px-3.5 py-2.5">
243
+ <div className="text-sm font-semibold tabular-nums text-foreground">
244
+ {formatCompactNumber(report.totalExecutions)}
245
+ </div>
246
+ <div className="text-[0.65rem] text-muted-foreground">Executions</div>
247
+ </div>
248
+ <div className="rounded-lg border border-border-muted bg-card/50 px-3.5 py-2.5">
249
+ <div className="text-sm font-semibold tabular-nums text-foreground">
250
+ {formatCompactNumber(report.totalAgents)}
251
+ </div>
252
+ <div className="text-[0.65rem] text-muted-foreground">Agents</div>
253
+ </div>
254
+ <div className="rounded-lg border border-border-muted bg-card/50 px-3.5 py-2.5">
255
+ <div className="text-sm font-semibold tabular-nums text-foreground">
256
+ {formatCompactNumber(report.totalSessions)}
257
+ </div>
258
+ <div className="text-[0.65rem] text-muted-foreground">Sessions</div>
259
+ </div>
260
+ </div>
199
261
  </div>
200
262
  );
201
263
  }
@@ -207,7 +269,7 @@ function SummaryCards({ report }: { report: GetOrgUsageReportOutput }) {
207
269
  const CHART_HEIGHT_PX = 128;
208
270
 
209
271
  function DailyCostChart({ entries }: { entries: readonly DailyCostEntry[] }) {
210
- const maxCost = Math.max(...entries.map((e) => e.estimatedCostUsd), 0);
272
+ const maxCost = Math.max(...entries.map((e) => microsToUsd(e.billableCostMicros)), 0);
211
273
  const [hoveredIdx, setHoveredIdx] = useState<number | null>(null);
212
274
 
213
275
  return (
@@ -222,9 +284,9 @@ function DailyCostChart({ entries }: { entries: readonly DailyCostEntry[] }) {
222
284
  <span className="text-xs tabular-nums text-muted-foreground">
223
285
  {formatChartDate(entries[hoveredIdx].date)}
224
286
  {" \u00B7 "}
225
- {formatCost(entries[hoveredIdx].estimatedCostUsd)}
287
+ {formatCost(microsToUsd(entries[hoveredIdx].billableCostMicros))}
226
288
  {" \u00B7 "}
227
- {formatTokenCount(entries[hoveredIdx].totalTokens)} tokens
289
+ {formatTokenCount(Number(entries[hoveredIdx].totalTokens))} tokens
228
290
  </span>
229
291
  )}
230
292
  </div>
@@ -238,7 +300,7 @@ function DailyCostChart({ entries }: { entries: readonly DailyCostEntry[] }) {
238
300
  >
239
301
  {entries.map((entry, i) => {
240
302
  const ratio =
241
- maxCost > 0 ? entry.estimatedCostUsd / maxCost : 0;
303
+ maxCost > 0 ? microsToUsd(entry.billableCostMicros) / maxCost : 0;
242
304
  const heightPx = Math.max(ratio * CHART_HEIGHT_PX, 2);
243
305
 
244
306
  return (
@@ -326,9 +388,11 @@ function ModelBreakdownList({
326
388
  </span>
327
389
  </div>
328
390
  {models.map((m) => {
329
- const totalInput =
330
- m.inputTokens + m.cacheCreationTokens + m.cacheReadTokens;
331
- const hasCache = m.cacheReadTokens > 0 || m.cacheCreationTokens > 0;
391
+ const inputTok = Number(m.inputTokens);
392
+ const cacheCreation = Number(m.cacheCreationInputTokens);
393
+ const cacheRead = Number(m.cacheReadInputTokens);
394
+ const totalInput = inputTok + cacheCreation + cacheRead;
395
+ const hasCache = cacheRead > 0 || cacheCreation > 0;
332
396
 
333
397
  return (
334
398
  <div
@@ -363,24 +427,24 @@ function ModelBreakdownList({
363
427
  role="cell"
364
428
  className="self-center text-right text-xs tabular-nums text-muted-foreground"
365
429
  >
366
- {formatCompactNumber(m.outputTokens)}
430
+ {formatCompactNumber(Number(m.outputTokens))}
367
431
  </span>
368
432
  <span
369
433
  role="cell"
370
434
  className="self-center text-right text-xs tabular-nums text-foreground"
371
435
  >
372
- {formatCost(m.estimatedCostUsd)}
436
+ {formatCost(microsToUsd(m.billableCostMicros))}
373
437
  </span>
374
438
  </div>
375
439
  {hasCache && (
376
440
  <div className="mt-0.5 text-[0.6rem] tabular-nums text-muted-foreground">
377
441
  cache{" "}
378
- {m.cacheReadTokens > 0 &&
379
- `${formatCompactNumber(m.cacheReadTokens)} read`}
380
- {m.cacheReadTokens > 0 && m.cacheCreationTokens > 0 &&
442
+ {cacheRead > 0 &&
443
+ `${formatCompactNumber(cacheRead)} read`}
444
+ {cacheRead > 0 && cacheCreation > 0 &&
381
445
  " · "}
382
- {m.cacheCreationTokens > 0 &&
383
- `${formatCompactNumber(m.cacheCreationTokens)} write`}
446
+ {cacheCreation > 0 &&
447
+ `${formatCompactNumber(cacheCreation)} write`}
384
448
  </div>
385
449
  )}
386
450
  </div>
@@ -4,6 +4,21 @@ export type { UseOrgUsageReportReturn } from "./useOrgUsageReport";
4
4
  export { OrgUsagePanel } from "./OrgUsagePanel";
5
5
  export type { OrgUsagePanelProps } from "./OrgUsagePanel";
6
6
 
7
+ export { CreditRunwayIndicator } from "./CreditRunwayIndicator";
8
+ export type { CreditRunwayIndicatorProps } from "./CreditRunwayIndicator";
9
+
10
+ export { AgentBreakdownList } from "./AgentBreakdownList";
11
+ export type { AgentBreakdownListProps } from "./AgentBreakdownList";
12
+
13
+ export { HarnessSplitCard } from "./HarnessSplitCard";
14
+ export type { HarnessSplitCardProps } from "./HarnessSplitCard";
15
+
16
+ export { useExportCSV } from "./useExportCSV";
17
+ export type { UseExportCSVReturn, ExportFormat } from "./useExportCSV";
18
+
19
+ export { ExportButton } from "./ExportButton";
20
+ export type { ExportButtonProps } from "./ExportButton";
21
+
7
22
  export {
8
23
  DATE_RANGE_PRESETS,
9
24
  dateRangeFromPreset,
@@ -0,0 +1,115 @@
1
+ "use client";
2
+
3
+ import { useCallback, useState } from "react";
4
+ import type { GetOrgUsageReportOutput } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/io_pb";
5
+
6
+ /** Export format for the CSV download. */
7
+ export type ExportFormat = "daily_summary" | "model_breakdown";
8
+
9
+ /** Return value of {@link useExportCSV}. */
10
+ export interface UseExportCSVReturn {
11
+ /** Trigger a CSV download for the given format. */
12
+ readonly exportCSV: (format: ExportFormat) => void;
13
+ /** Whether an export is being generated. */
14
+ readonly isExporting: boolean;
15
+ }
16
+
17
+ /**
18
+ * Behavior hook that generates and downloads usage data as CSV.
19
+ *
20
+ * Operates entirely client-side from data already in memory — no
21
+ * additional RPC calls. Supports two formats:
22
+ * - `daily_summary` — one row per day with date, executions, tokens, cost
23
+ * - `model_breakdown` — one row per model with calls, tokens, cost
24
+ *
25
+ * @param report - The org usage report data (from `useOrgUsageReport`).
26
+ * @param orgId - Organization ID for the filename.
27
+ */
28
+ export function useExportCSV(
29
+ report: GetOrgUsageReportOutput | null,
30
+ orgId: string,
31
+ ): UseExportCSVReturn {
32
+ const [isExporting, setIsExporting] = useState(false);
33
+
34
+ const exportCSV = useCallback(
35
+ (format: ExportFormat) => {
36
+ if (!report) return;
37
+ setIsExporting(true);
38
+
39
+ try {
40
+ const { csv, filename } = format === "daily_summary"
41
+ ? buildDailySummaryCSV(report, orgId)
42
+ : buildModelBreakdownCSV(report, orgId);
43
+
44
+ downloadCSV(csv, filename);
45
+ } finally {
46
+ setIsExporting(false);
47
+ }
48
+ },
49
+ [report, orgId],
50
+ );
51
+
52
+ return { exportCSV, isExporting };
53
+ }
54
+
55
+ function buildDailySummaryCSV(
56
+ report: GetOrgUsageReportOutput,
57
+ orgId: string,
58
+ ): { csv: string; filename: string } {
59
+ const header = "Date,Executions,Tokens,Cost (USD)";
60
+ const rows = report.dailyCosts.map((entry) => {
61
+ const cost = (Number(entry.billableCostMicros) / 1_000_000).toFixed(6);
62
+ return `${entry.date},${entry.executionCount},${entry.totalTokens},${cost}`;
63
+ });
64
+
65
+ return {
66
+ csv: [header, ...rows].join("\n"),
67
+ filename: `${orgId}-daily-usage.csv`,
68
+ };
69
+ }
70
+
71
+ function buildModelBreakdownCSV(
72
+ report: GetOrgUsageReportOutput,
73
+ orgId: string,
74
+ ): { csv: string; filename: string } {
75
+ const header =
76
+ "Model,Provider,Calls,Input Tokens,Output Tokens,Cache Read Tokens,Cache Write Tokens,Cost (USD)";
77
+ const rows = report.modelBreakdown.map((m) => {
78
+ const cost = (Number(m.billableCostMicros) / 1_000_000).toFixed(6);
79
+ return [
80
+ escapeCsvField(m.model),
81
+ escapeCsvField(m.provider),
82
+ m.callCount,
83
+ m.inputTokens,
84
+ m.outputTokens,
85
+ m.cacheReadInputTokens,
86
+ m.cacheCreationInputTokens,
87
+ cost,
88
+ ].join(",");
89
+ });
90
+
91
+ return {
92
+ csv: [header, ...rows].join("\n"),
93
+ filename: `${orgId}-model-usage.csv`,
94
+ };
95
+ }
96
+
97
+ function escapeCsvField(value: string): string {
98
+ if (value.includes(",") || value.includes('"') || value.includes("\n")) {
99
+ return `"${value.replace(/"/g, '""')}"`;
100
+ }
101
+ return value;
102
+ }
103
+
104
+ function downloadCSV(csv: string, filename: string): void {
105
+ const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
106
+ const url = URL.createObjectURL(blob);
107
+ const link = document.createElement("a");
108
+ link.href = url;
109
+ link.download = filename;
110
+ link.style.display = "none";
111
+ document.body.appendChild(link);
112
+ link.click();
113
+ document.body.removeChild(link);
114
+ URL.revokeObjectURL(url);
115
+ }
@@ -54,7 +54,8 @@ export interface UseOrgUsageReportReturn {
54
54
  * if (error) return <ErrorMessage error={error} />;
55
55
  * if (!report) return null;
56
56
  *
57
- * return <div>Total cost: {formatCost(report.totalCostUsd)}</div>;
57
+ * const costUsd = Number(report.totalBillableCostMicros) / 1_000_000;
58
+ * return <div>Total cost: {formatCost(costUsd)}</div>;
58
59
  * ```
59
60
  */
60
61
  export function useOrgUsageReport(
@@ -0,0 +1,76 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { renderHook, act } from "@testing-library/react";
3
+ import { useWorkspaceEntries } from "../useWorkspaceEntries";
4
+
5
+ describe("useWorkspaceEntries — return reference stability", () => {
6
+ it("returns the same object reference across re-renders when state is unchanged", () => {
7
+ const { result, rerender } = renderHook(() => useWorkspaceEntries());
8
+
9
+ const first = result.current;
10
+ rerender();
11
+ const second = result.current;
12
+
13
+ expect(second).toBe(first);
14
+ });
15
+
16
+ it("returns a new reference after adding an entry", () => {
17
+ const { result } = renderHook(() => useWorkspaceEntries());
18
+
19
+ const before = result.current;
20
+
21
+ act(() => {
22
+ result.current.addGitRepo("https://github.com/acme/repo.git", "main");
23
+ });
24
+
25
+ expect(result.current).not.toBe(before);
26
+ expect(result.current.entries).toHaveLength(1);
27
+ expect(result.current.hasEntries).toBe(true);
28
+ });
29
+
30
+ it("stabilizes after add — re-renders without state change preserve reference", () => {
31
+ const { result, rerender } = renderHook(() => useWorkspaceEntries());
32
+
33
+ act(() => {
34
+ result.current.addGitRepo("https://github.com/acme/repo.git");
35
+ });
36
+
37
+ const after = result.current;
38
+ rerender();
39
+
40
+ expect(result.current).toBe(after);
41
+ });
42
+
43
+ it("callback references are stable across re-renders", () => {
44
+ const { result, rerender } = renderHook(() => useWorkspaceEntries());
45
+
46
+ const { addGitRepo, addLocalPath, remove, clear, clearLocal, toInput } = result.current;
47
+
48
+ rerender();
49
+
50
+ expect(result.current.addGitRepo).toBe(addGitRepo);
51
+ expect(result.current.addLocalPath).toBe(addLocalPath);
52
+ expect(result.current.remove).toBe(remove);
53
+ expect(result.current.clear).toBe(clear);
54
+ expect(result.current.clearLocal).toBe(clearLocal);
55
+ expect(result.current.toInput).toBe(toInput);
56
+ });
57
+
58
+ it("returns a new reference after removing an entry", () => {
59
+ const { result } = renderHook(() => useWorkspaceEntries());
60
+
61
+ act(() => {
62
+ result.current.addLocalPath("/home/dev/project");
63
+ });
64
+
65
+ const withEntry = result.current;
66
+ const entryId = withEntry.entries[0].id;
67
+
68
+ act(() => {
69
+ result.current.remove(entryId);
70
+ });
71
+
72
+ expect(result.current).not.toBe(withEntry);
73
+ expect(result.current.entries).toHaveLength(0);
74
+ expect(result.current.hasEntries).toBe(false);
75
+ });
76
+ });
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import { useCallback, useState } from "react";
3
+ import { useCallback, useMemo, useState } from "react";
4
4
  import type { WorkspaceEntryInput, WorkspaceSourceInput } from "@stigmer/sdk";
5
5
 
6
6
  /**
@@ -156,14 +156,19 @@ export function useWorkspaceEntries(): UseWorkspaceEntriesReturn {
156
156
  });
157
157
  }, [entries]);
158
158
 
159
- return {
160
- entries,
161
- addGitRepo,
162
- addLocalPath,
163
- remove,
164
- clear,
165
- clearLocal,
166
- toInput,
167
- hasEntries: entries.length > 0,
168
- };
159
+ const hasEntries = entries.length > 0;
160
+
161
+ return useMemo(
162
+ () => ({
163
+ entries,
164
+ addGitRepo,
165
+ addLocalPath,
166
+ remove,
167
+ clear,
168
+ clearLocal,
169
+ toInput,
170
+ hasEntries,
171
+ }),
172
+ [entries, addGitRepo, addLocalPath, remove, clear, clearLocal, toInput, hasEntries],
173
+ );
169
174
  }