@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.
- package/billing/AutoRechargeCard.d.ts +38 -0
- package/billing/AutoRechargeCard.d.ts.map +1 -0
- package/billing/AutoRechargeCard.js +90 -0
- package/billing/AutoRechargeCard.js.map +1 -0
- package/billing/BillingSection.d.ts +32 -0
- package/billing/BillingSection.d.ts.map +1 -0
- package/billing/BillingSection.js +81 -0
- package/billing/BillingSection.js.map +1 -0
- package/billing/CreditBalanceCard.d.ts +25 -0
- package/billing/CreditBalanceCard.d.ts.map +1 -0
- package/billing/CreditBalanceCard.js +28 -0
- package/billing/CreditBalanceCard.js.map +1 -0
- package/billing/CreditLedgerTable.d.ts +22 -0
- package/billing/CreditLedgerTable.d.ts.map +1 -0
- package/billing/CreditLedgerTable.js +75 -0
- package/billing/CreditLedgerTable.js.map +1 -0
- package/billing/CreditPackGrid.d.ts +31 -0
- package/billing/CreditPackGrid.d.ts.map +1 -0
- package/billing/CreditPackGrid.js +35 -0
- package/billing/CreditPackGrid.js.map +1 -0
- package/billing/LowBalanceBanner.d.ts +26 -0
- package/billing/LowBalanceBanner.d.ts.map +1 -0
- package/billing/LowBalanceBanner.js +33 -0
- package/billing/LowBalanceBanner.js.map +1 -0
- package/billing/PaymentMethodCard.d.ts +35 -0
- package/billing/PaymentMethodCard.d.ts.map +1 -0
- package/billing/PaymentMethodCard.js +48 -0
- package/billing/PaymentMethodCard.js.map +1 -0
- package/billing/credit-packs.d.ts +25 -0
- package/billing/credit-packs.d.ts.map +1 -0
- package/billing/credit-packs.js +39 -0
- package/billing/credit-packs.js.map +1 -0
- package/billing/format.d.ts +39 -0
- package/billing/format.d.ts.map +1 -0
- package/billing/format.js +90 -0
- package/billing/format.js.map +1 -0
- package/billing/index.d.ts +32 -0
- package/billing/index.d.ts.map +1 -0
- package/billing/index.js +21 -0
- package/billing/index.js.map +1 -0
- package/billing/useBillingAccount.d.ts +40 -0
- package/billing/useBillingAccount.d.ts.map +1 -0
- package/billing/useBillingAccount.js +35 -0
- package/billing/useBillingAccount.js.map +1 -0
- package/billing/useBillingUsageReport.d.ts +42 -0
- package/billing/useBillingUsageReport.d.ts.map +1 -0
- package/billing/useBillingUsageReport.js +43 -0
- package/billing/useBillingUsageReport.js.map +1 -0
- package/billing/useCreateBillingPortalSession.d.ts +35 -0
- package/billing/useCreateBillingPortalSession.d.ts.map +1 -0
- package/billing/useCreateBillingPortalSession.js +50 -0
- package/billing/useCreateBillingPortalSession.js.map +1 -0
- package/billing/useCreateCheckoutSession.d.ts +54 -0
- package/billing/useCreateCheckoutSession.d.ts.map +1 -0
- package/billing/useCreateCheckoutSession.js +58 -0
- package/billing/useCreateCheckoutSession.js.map +1 -0
- package/billing/useCreditLedger.d.ts +48 -0
- package/billing/useCreditLedger.d.ts.map +1 -0
- package/billing/useCreditLedger.js +39 -0
- package/billing/useCreditLedger.js.map +1 -0
- package/billing/useCustomerModelPricing.d.ts +41 -0
- package/billing/useCustomerModelPricing.d.ts.map +1 -0
- package/billing/useCustomerModelPricing.js +37 -0
- package/billing/useCustomerModelPricing.js.map +1 -0
- package/billing/useSetAutoRechargeConfig.d.ts +50 -0
- package/billing/useSetAutoRechargeConfig.d.ts.map +1 -0
- package/billing/useSetAutoRechargeConfig.js +53 -0
- package/billing/useSetAutoRechargeConfig.js.map +1 -0
- package/composer/ComposerToolbar.js +1 -1
- package/composer/ComposerToolbar.js.map +1 -1
- package/composer/SessionComposer.d.ts +1 -1
- package/composer/SessionComposer.d.ts.map +1 -1
- package/composer/SessionComposer.js +19 -4
- package/composer/SessionComposer.js.map +1 -1
- package/composer/__tests__/SessionComposer-memo.test.d.ts +2 -0
- package/composer/__tests__/SessionComposer-memo.test.d.ts.map +1 -0
- package/composer/__tests__/SessionComposer-memo.test.js +23 -0
- package/composer/__tests__/SessionComposer-memo.test.js.map +1 -0
- package/execution/ApprovalCard.d.ts +5 -1
- package/execution/ApprovalCard.d.ts.map +1 -1
- package/execution/ApprovalCard.js +7 -3
- package/execution/ApprovalCard.js.map +1 -1
- package/execution/ExecutionPhaseBadge.d.ts +1 -1
- package/execution/ExecutionPhaseBadge.d.ts.map +1 -1
- package/execution/ExecutionPhaseBadge.js +3 -2
- package/execution/ExecutionPhaseBadge.js.map +1 -1
- package/execution/MessageEntry.d.ts +7 -3
- package/execution/MessageEntry.d.ts.map +1 -1
- package/execution/MessageEntry.js +19 -8
- package/execution/MessageEntry.js.map +1 -1
- package/execution/MessageThread.d.ts +84 -3
- package/execution/MessageThread.d.ts.map +1 -1
- package/execution/MessageThread.js +113 -65
- package/execution/MessageThread.js.map +1 -1
- package/execution/SetupProgress.d.ts +1 -1
- package/execution/SetupProgress.d.ts.map +1 -1
- package/execution/SetupProgress.js +3 -3
- package/execution/SetupProgress.js.map +1 -1
- package/execution/SubAgentSection.d.ts +5 -1
- package/execution/SubAgentSection.d.ts.map +1 -1
- package/execution/SubAgentSection.js +13 -7
- package/execution/SubAgentSection.js.map +1 -1
- package/execution/ThreadSkeleton.d.ts +22 -0
- package/execution/ThreadSkeleton.d.ts.map +1 -0
- package/execution/ThreadSkeleton.js +26 -0
- package/execution/ThreadSkeleton.js.map +1 -0
- package/execution/ToolCallGroup.d.ts +16 -1
- package/execution/ToolCallGroup.d.ts.map +1 -1
- package/execution/ToolCallGroup.js +31 -3
- package/execution/ToolCallGroup.js.map +1 -1
- package/execution/UsageWidget.d.ts +1 -1
- package/execution/__tests__/message-entry.test.d.ts +2 -0
- package/execution/__tests__/message-entry.test.d.ts.map +1 -0
- package/execution/__tests__/message-entry.test.js +178 -0
- package/execution/__tests__/message-entry.test.js.map +1 -0
- package/execution/__tests__/thread-keys.test.d.ts +2 -0
- package/execution/__tests__/thread-keys.test.d.ts.map +1 -0
- package/execution/__tests__/thread-keys.test.js +289 -0
- package/execution/__tests__/thread-keys.test.js.map +1 -0
- package/execution/__tests__/thread-memoization.test.d.ts +2 -0
- package/execution/__tests__/thread-memoization.test.d.ts.map +1 -0
- package/execution/__tests__/thread-memoization.test.js +262 -0
- package/execution/__tests__/thread-memoization.test.js.map +1 -0
- package/execution/__tests__/thread-skeleton.test.d.ts +2 -0
- package/execution/__tests__/thread-skeleton.test.d.ts.map +1 -0
- package/execution/__tests__/thread-skeleton.test.js +35 -0
- package/execution/__tests__/thread-skeleton.test.js.map +1 -0
- package/execution/__tests__/useExecutionStream.test.js +73 -10
- package/execution/__tests__/useExecutionStream.test.js.map +1 -1
- package/execution/__tests__/useSessionVariables-stability.test.d.ts +2 -0
- package/execution/__tests__/useSessionVariables-stability.test.d.ts.map +1 -0
- package/execution/__tests__/useSessionVariables-stability.test.js +69 -0
- package/execution/__tests__/useSessionVariables-stability.test.js.map +1 -0
- package/execution/__tests__/virtualized-thread.test.d.ts +2 -0
- package/execution/__tests__/virtualized-thread.test.d.ts.map +1 -0
- package/execution/__tests__/virtualized-thread.test.js +274 -0
- package/execution/__tests__/virtualized-thread.test.js.map +1 -0
- package/execution/index.d.ts +2 -0
- package/execution/index.d.ts.map +1 -1
- package/execution/index.js +1 -0
- package/execution/index.js.map +1 -1
- package/execution/useExecutionStream.d.ts +35 -10
- package/execution/useExecutionStream.d.ts.map +1 -1
- package/execution/useExecutionStream.js +79 -40
- package/execution/useExecutionStream.js.map +1 -1
- package/execution/useSessionVariables.d.ts.map +1 -1
- package/execution/useSessionVariables.js +4 -3
- package/execution/useSessionVariables.js.map +1 -1
- package/github/useGitHubConnection.d.ts.map +1 -1
- package/github/useGitHubConnection.js +5 -4
- package/github/useGitHubConnection.js.map +1 -1
- package/identity-account/index.d.ts +2 -0
- package/identity-account/index.d.ts.map +1 -0
- package/identity-account/index.js +2 -0
- package/identity-account/index.js.map +1 -0
- package/identity-account/useIdentityAccountGate.d.ts +81 -0
- package/identity-account/useIdentityAccountGate.d.ts.map +1 -0
- package/identity-account/useIdentityAccountGate.js +100 -0
- package/identity-account/useIdentityAccountGate.js.map +1 -0
- package/index.d.ts +10 -4
- package/index.d.ts.map +1 -1
- package/index.js +8 -2
- package/index.js.map +1 -1
- package/internal/FetchCacheProvider.d.ts +44 -0
- package/internal/FetchCacheProvider.d.ts.map +1 -0
- package/internal/FetchCacheProvider.js +61 -0
- package/internal/FetchCacheProvider.js.map +1 -0
- package/internal/JumpToLatestButton.d.ts +14 -0
- package/internal/JumpToLatestButton.d.ts.map +1 -0
- package/internal/JumpToLatestButton.js +19 -0
- package/internal/JumpToLatestButton.js.map +1 -0
- package/internal/ThreadItemWrapper.d.ts +20 -0
- package/internal/ThreadItemWrapper.d.ts.map +1 -0
- package/internal/ThreadItemWrapper.js +44 -0
- package/internal/ThreadItemWrapper.js.map +1 -0
- package/internal/VirtualizedThread.d.ts +25 -0
- package/internal/VirtualizedThread.d.ts.map +1 -0
- package/internal/VirtualizedThread.js +58 -0
- package/internal/VirtualizedThread.js.map +1 -0
- package/internal/__tests__/fetch-cache.test.d.ts +2 -0
- package/internal/__tests__/fetch-cache.test.d.ts.map +1 -0
- package/internal/__tests__/fetch-cache.test.js +182 -0
- package/internal/__tests__/fetch-cache.test.js.map +1 -0
- package/internal/__tests__/stream-controller.test.d.ts +2 -0
- package/internal/__tests__/stream-controller.test.d.ts.map +1 -0
- package/internal/__tests__/stream-controller.test.js +294 -0
- package/internal/__tests__/stream-controller.test.js.map +1 -0
- package/internal/__tests__/thread-animation.test.d.ts +2 -0
- package/internal/__tests__/thread-animation.test.d.ts.map +1 -0
- package/internal/__tests__/thread-animation.test.js +79 -0
- package/internal/__tests__/thread-animation.test.js.map +1 -0
- package/internal/__tests__/useAutoScroll.test.d.ts +2 -0
- package/internal/__tests__/useAutoScroll.test.d.ts.map +1 -0
- package/internal/__tests__/useAutoScroll.test.js +188 -0
- package/internal/__tests__/useAutoScroll.test.js.map +1 -0
- package/internal/__tests__/useFetch-cache.test.d.ts +2 -0
- package/internal/__tests__/useFetch-cache.test.d.ts.map +1 -0
- package/internal/__tests__/useFetch-cache.test.js +137 -0
- package/internal/__tests__/useFetch-cache.test.js.map +1 -0
- package/internal/dev/__tests__/use-key-stability.test.d.ts +2 -0
- package/internal/dev/__tests__/use-key-stability.test.d.ts.map +1 -0
- package/internal/dev/__tests__/use-key-stability.test.js +72 -0
- package/internal/dev/__tests__/use-key-stability.test.js.map +1 -0
- package/internal/dev/__tests__/use-render-tracer.test.d.ts +2 -0
- package/internal/dev/__tests__/use-render-tracer.test.d.ts.map +1 -0
- package/internal/dev/__tests__/use-render-tracer.test.js +55 -0
- package/internal/dev/__tests__/use-render-tracer.test.js.map +1 -0
- package/internal/dev/dom-counter.d.ts +14 -0
- package/internal/dev/dom-counter.d.ts.map +1 -0
- package/internal/dev/dom-counter.js +39 -0
- package/internal/dev/dom-counter.js.map +1 -0
- package/internal/dev/index.d.ts +6 -0
- package/internal/dev/index.d.ts.map +1 -0
- package/internal/dev/index.js +6 -0
- package/internal/dev/index.js.map +1 -0
- package/internal/dev/profiler-wrapper.d.ts +16 -0
- package/internal/dev/profiler-wrapper.d.ts.map +1 -0
- package/internal/dev/profiler-wrapper.js +31 -0
- package/internal/dev/profiler-wrapper.js.map +1 -0
- package/internal/dev/use-key-stability.d.ts +22 -0
- package/internal/dev/use-key-stability.d.ts.map +1 -0
- package/internal/dev/use-key-stability.js +67 -0
- package/internal/dev/use-key-stability.js.map +1 -0
- package/internal/dev/use-render-tracer.d.ts +13 -0
- package/internal/dev/use-render-tracer.d.ts.map +1 -0
- package/internal/dev/use-render-tracer.js +57 -0
- package/internal/dev/use-render-tracer.js.map +1 -0
- package/internal/dev/use-stream-rate.d.ts +23 -0
- package/internal/dev/use-stream-rate.d.ts.map +1 -0
- package/internal/dev/use-stream-rate.js +94 -0
- package/internal/dev/use-stream-rate.js.map +1 -0
- package/internal/fetch-cache.d.ts +72 -0
- package/internal/fetch-cache.d.ts.map +1 -0
- package/internal/fetch-cache.js +118 -0
- package/internal/fetch-cache.js.map +1 -0
- package/internal/store/__tests__/conversation-store.test.d.ts +2 -0
- package/internal/store/__tests__/conversation-store.test.d.ts.map +1 -0
- package/internal/store/__tests__/conversation-store.test.js +200 -0
- package/internal/store/__tests__/conversation-store.test.js.map +1 -0
- package/internal/store/__tests__/structural-share.test.d.ts +2 -0
- package/internal/store/__tests__/structural-share.test.d.ts.map +1 -0
- package/internal/store/__tests__/structural-share.test.js +368 -0
- package/internal/store/__tests__/structural-share.test.js.map +1 -0
- package/internal/store/conversation-store.d.ts +62 -0
- package/internal/store/conversation-store.d.ts.map +1 -0
- package/internal/store/conversation-store.js +95 -0
- package/internal/store/conversation-store.js.map +1 -0
- package/internal/store/index.d.ts +31 -0
- package/internal/store/index.d.ts.map +1 -0
- package/internal/store/index.js +54 -0
- package/internal/store/index.js.map +1 -0
- package/internal/store/structural-share.d.ts +13 -0
- package/internal/store/structural-share.d.ts.map +1 -0
- package/internal/store/structural-share.js +240 -0
- package/internal/store/structural-share.js.map +1 -0
- package/internal/stream-controller.d.ts +85 -0
- package/internal/stream-controller.d.ts.map +1 -0
- package/internal/stream-controller.js +146 -0
- package/internal/stream-controller.js.map +1 -0
- package/internal/useAutoScroll.d.ts +32 -0
- package/internal/useAutoScroll.d.ts.map +1 -0
- package/internal/useAutoScroll.js +97 -0
- package/internal/useAutoScroll.js.map +1 -0
- package/internal/useFetch.d.ts +14 -0
- package/internal/useFetch.d.ts.map +1 -1
- package/internal/useFetch.js +32 -2
- package/internal/useFetch.js.map +1 -1
- package/package.json +7 -5
- package/session/__tests__/useNewSessionFlow.test.js +16 -0
- package/session/__tests__/useNewSessionFlow.test.js.map +1 -1
- package/session/__tests__/usePersistedModel.test.d.ts +2 -0
- package/session/__tests__/usePersistedModel.test.d.ts.map +1 -0
- package/session/__tests__/usePersistedModel.test.js +82 -0
- package/session/__tests__/usePersistedModel.test.js.map +1 -0
- package/session/__tests__/useSession.test.d.ts +2 -0
- package/session/__tests__/useSession.test.d.ts.map +1 -0
- package/session/__tests__/useSession.test.js +130 -0
- package/session/__tests__/useSession.test.js.map +1 -0
- package/session/useNewSessionFlow.d.ts.map +1 -1
- package/session/useNewSessionFlow.js +12 -6
- package/session/useNewSessionFlow.js.map +1 -1
- package/session/usePersistedModel.d.ts +3 -0
- package/session/usePersistedModel.d.ts.map +1 -1
- package/session/usePersistedModel.js +27 -2
- package/session/usePersistedModel.js.map +1 -1
- package/session/useSession.d.ts.map +1 -1
- package/session/useSession.js +1 -1
- package/session/useSession.js.map +1 -1
- package/session/useSessionConversation.d.ts.map +1 -1
- package/session/useSessionConversation.js +9 -1
- package/session/useSessionConversation.js.map +1 -1
- package/session/useSessionExecutions.d.ts.map +1 -1
- package/session/useSessionExecutions.js +1 -1
- package/session/useSessionExecutions.js.map +1 -1
- package/session/useSessionPageFlow.js +1 -1
- package/session/useSessionPageFlow.js.map +1 -1
- package/session/useSessionUsage.d.ts +24 -40
- package/session/useSessionUsage.d.ts.map +1 -1
- package/session/useSessionUsage.js +64 -97
- package/session/useSessionUsage.js.map +1 -1
- package/settings/BillingSection.d.ts +3 -0
- package/settings/BillingSection.d.ts.map +1 -0
- package/settings/BillingSection.js +3 -0
- package/settings/BillingSection.js.map +1 -0
- package/settings/index.d.ts +2 -0
- package/settings/index.d.ts.map +1 -1
- package/settings/index.js +1 -0
- package/settings/index.js.map +1 -1
- package/settings/settings-nav.js +1 -1
- package/settings/settings-nav.js.map +1 -1
- package/src/billing/AutoRechargeCard.tsx +274 -0
- package/src/billing/BillingSection.tsx +255 -0
- package/src/billing/CreditBalanceCard.tsx +81 -0
- package/src/billing/CreditLedgerTable.tsx +281 -0
- package/src/billing/CreditPackGrid.tsx +132 -0
- package/src/billing/LowBalanceBanner.tsx +67 -0
- package/src/billing/PaymentMethodCard.tsx +133 -0
- package/src/billing/credit-packs.ts +54 -0
- package/src/billing/format.ts +97 -0
- package/src/billing/index.ts +51 -0
- package/src/billing/useBillingAccount.ts +64 -0
- package/src/billing/useBillingUsageReport.ts +73 -0
- package/src/billing/useCreateBillingPortalSession.ts +76 -0
- package/src/billing/useCreateCheckoutSession.ts +101 -0
- package/src/billing/useCreditLedger.ts +79 -0
- package/src/billing/useCustomerModelPricing.ts +67 -0
- package/src/billing/useSetAutoRechargeConfig.ts +90 -0
- package/src/composer/ComposerToolbar.tsx +1 -1
- package/src/composer/SessionComposer.tsx +22 -4
- package/src/composer/__tests__/SessionComposer-memo.test.ts +26 -0
- package/src/execution/ApprovalCard.tsx +7 -3
- package/src/execution/ExecutionPhaseBadge.tsx +3 -2
- package/src/execution/MessageEntry.tsx +27 -16
- package/src/execution/MessageThread.tsx +308 -131
- package/src/execution/SetupProgress.tsx +3 -3
- package/src/execution/SubAgentSection.tsx +14 -6
- package/src/execution/ThreadSkeleton.tsx +73 -0
- package/src/execution/ToolCallGroup.tsx +36 -3
- package/src/execution/UsageWidget.tsx +1 -1
- package/src/execution/__tests__/message-entry.test.tsx +236 -0
- package/src/execution/__tests__/thread-keys.test.ts +409 -0
- package/src/execution/__tests__/thread-memoization.test.ts +320 -0
- package/src/execution/__tests__/thread-skeleton.test.tsx +44 -0
- package/src/execution/__tests__/useExecutionStream.test.tsx +109 -12
- package/src/execution/__tests__/useSessionVariables-stability.test.ts +95 -0
- package/src/execution/__tests__/virtualized-thread.test.tsx +401 -0
- package/src/execution/index.ts +3 -0
- package/src/execution/useExecutionStream.ts +123 -48
- package/src/execution/useSessionVariables.ts +17 -12
- package/src/github/useGitHubConnection.ts +18 -13
- package/src/identity-account/index.ts +5 -0
- package/src/identity-account/useIdentityAccountGate.ts +163 -0
- package/src/index.ts +73 -0
- package/src/internal/FetchCacheProvider.tsx +74 -0
- package/src/internal/JumpToLatestButton.tsx +61 -0
- package/src/internal/ThreadItemWrapper.tsx +65 -0
- package/src/internal/VirtualizedThread.tsx +162 -0
- package/src/internal/__tests__/fetch-cache.test.ts +230 -0
- package/src/internal/__tests__/stream-controller.test.ts +395 -0
- package/src/internal/__tests__/thread-animation.test.tsx +121 -0
- package/src/internal/__tests__/useAutoScroll.test.tsx +261 -0
- package/src/internal/__tests__/useFetch-cache.test.ts +214 -0
- package/src/internal/dev/__tests__/use-key-stability.test.ts +124 -0
- package/src/internal/dev/__tests__/use-render-tracer.test.ts +78 -0
- package/src/internal/dev/dom-counter.ts +47 -0
- package/src/internal/dev/index.ts +5 -0
- package/src/internal/dev/profiler-wrapper.tsx +52 -0
- package/src/internal/dev/use-key-stability.ts +86 -0
- package/src/internal/dev/use-render-tracer.ts +70 -0
- package/src/internal/dev/use-stream-rate.ts +138 -0
- package/src/internal/fetch-cache.ts +155 -0
- package/src/internal/store/__tests__/conversation-store.test.ts +257 -0
- package/src/internal/store/__tests__/structural-share.test.ts +454 -0
- package/src/internal/store/conversation-store.ts +128 -0
- package/src/internal/store/index.ts +68 -0
- package/src/internal/store/structural-share.ts +318 -0
- package/src/internal/stream-controller.ts +201 -0
- package/src/internal/useAutoScroll.ts +121 -0
- package/src/internal/useFetch.ts +51 -2
- package/src/session/__tests__/useNewSessionFlow.test.tsx +22 -0
- package/src/session/__tests__/usePersistedModel.test.tsx +117 -0
- package/src/session/__tests__/useSession.test.tsx +187 -0
- package/src/session/useNewSessionFlow.ts +12 -6
- package/src/session/usePersistedModel.ts +28 -2
- package/src/session/useSession.ts +1 -0
- package/src/session/useSessionConversation.ts +11 -2
- package/src/session/useSessionExecutions.ts +1 -0
- package/src/session/useSessionPageFlow.ts +1 -1
- package/src/session/useSessionUsage.ts +102 -123
- package/src/settings/BillingSection.tsx +4 -0
- package/src/settings/index.ts +2 -0
- package/src/settings/settings-nav.ts +1 -1
- package/src/styles.css +31 -0
- package/src/usage/AgentBreakdownList.tsx +147 -0
- package/src/usage/CreditRunwayIndicator.tsx +71 -0
- package/src/usage/ExportButton.tsx +115 -0
- package/src/usage/HarnessSplitCard.tsx +103 -0
- package/src/usage/OrgUsagePanel.tsx +109 -45
- package/src/usage/index.ts +15 -0
- package/src/usage/useExportCSV.ts +115 -0
- package/src/usage/useOrgUsageReport.ts +2 -1
- package/src/workspace/__tests__/useWorkspaceEntries-stability.test.ts +76 -0
- package/src/workspace/useWorkspaceEntries.ts +16 -11
- package/styles.css +1 -1
- package/usage/AgentBreakdownList.d.ts +21 -0
- package/usage/AgentBreakdownList.d.ts.map +1 -0
- package/usage/AgentBreakdownList.js +44 -0
- package/usage/AgentBreakdownList.js.map +1 -0
- package/usage/CreditRunwayIndicator.d.ts +21 -0
- package/usage/CreditRunwayIndicator.d.ts.map +1 -0
- package/usage/CreditRunwayIndicator.js +38 -0
- package/usage/CreditRunwayIndicator.js.map +1 -0
- package/usage/ExportButton.d.ts +20 -0
- package/usage/ExportButton.d.ts.map +1 -0
- package/usage/ExportButton.js +36 -0
- package/usage/ExportButton.js.map +1 -0
- package/usage/HarnessSplitCard.d.ts +17 -0
- package/usage/HarnessSplitCard.d.ts.map +1 -0
- package/usage/HarnessSplitCard.js +38 -0
- package/usage/HarnessSplitCard.js.map +1 -0
- package/usage/OrgUsagePanel.d.ts.map +1 -1
- package/usage/OrgUsagePanel.js +30 -22
- package/usage/OrgUsagePanel.js.map +1 -1
- package/usage/index.d.ts +10 -0
- package/usage/index.d.ts.map +1 -1
- package/usage/index.js +5 -0
- package/usage/index.js.map +1 -1
- package/usage/useExportCSV.d.ts +23 -0
- package/usage/useExportCSV.d.ts.map +1 -0
- package/usage/useExportCSV.js +81 -0
- package/usage/useExportCSV.js.map +1 -0
- package/usage/useOrgUsageReport.d.ts +2 -1
- package/usage/useOrgUsageReport.d.ts.map +1 -1
- package/usage/useOrgUsageReport.js +2 -1
- package/usage/useOrgUsageReport.js.map +1 -1
- package/workspace/__tests__/useWorkspaceEntries-stability.test.d.ts +2 -0
- package/workspace/__tests__/useWorkspaceEntries-stability.test.d.ts.map +1 -0
- package/workspace/__tests__/useWorkspaceEntries-stability.test.js +57 -0
- package/workspace/__tests__/useWorkspaceEntries-stability.test.js.map +1 -0
- package/workspace/useWorkspaceEntries.d.ts.map +1 -1
- package/workspace/useWorkspaceEntries.js +5 -4
- package/workspace/useWorkspaceEntries.js.map +1 -1
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { renderHook } from "@testing-library/react";
|
|
3
|
+
import { useKeyStability } from "../use-key-stability";
|
|
4
|
+
|
|
5
|
+
interface KeyedItem {
|
|
6
|
+
readonly key: string;
|
|
7
|
+
readonly kind: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function makeItems(...specs: Array<[string, string]>): KeyedItem[] {
|
|
11
|
+
return specs.map(([key, kind]) => ({ key, kind }));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe("useKeyStability", () => {
|
|
15
|
+
let warnSpy: ReturnType<typeof vi.spyOn>;
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
warnSpy.mockRestore();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("stays silent on first render (no previous to compare)", () => {
|
|
26
|
+
renderHook(() =>
|
|
27
|
+
useKeyStability(makeItems(["msg-1", "message"], ["msg-2", "message"])),
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
expect(warnSpy).not.toHaveBeenCalled();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("stays silent when keys are stable across renders", () => {
|
|
34
|
+
const { rerender } = renderHook(
|
|
35
|
+
({ items }) => useKeyStability(items),
|
|
36
|
+
{
|
|
37
|
+
initialProps: {
|
|
38
|
+
items: makeItems(["msg-1", "message"], ["msg-2", "message"]),
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
rerender({
|
|
44
|
+
items: makeItems(["msg-1", "message"], ["msg-2", "message"]),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
expect(warnSpy).not.toHaveBeenCalled();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("stays silent when items are appended (no swap)", () => {
|
|
51
|
+
const { rerender } = renderHook(
|
|
52
|
+
({ items }) => useKeyStability(items),
|
|
53
|
+
{
|
|
54
|
+
initialProps: {
|
|
55
|
+
items: makeItems(["msg-1", "message"]),
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
rerender({
|
|
61
|
+
items: makeItems(["msg-1", "message"], ["msg-2", "message"]),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
expect(warnSpy).not.toHaveBeenCalled();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("warns when a key is swapped at the same index", () => {
|
|
68
|
+
const { rerender } = renderHook(
|
|
69
|
+
({ items }) => useKeyStability(items),
|
|
70
|
+
{
|
|
71
|
+
initialProps: {
|
|
72
|
+
items: makeItems(
|
|
73
|
+
["e0-m0", "message"],
|
|
74
|
+
["e0-m1", "message"],
|
|
75
|
+
),
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
rerender({
|
|
81
|
+
items: makeItems(
|
|
82
|
+
["e0-m0", "message"],
|
|
83
|
+
["e1-m0", "message"],
|
|
84
|
+
),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
expect(warnSpy).toHaveBeenCalledTimes(1);
|
|
88
|
+
const msg = warnSpy.mock.calls[0][0] as string;
|
|
89
|
+
expect(msg).toContain("[stgm:perf:keys]");
|
|
90
|
+
expect(msg).toContain("e0-m1");
|
|
91
|
+
expect(msg).toContain("e1-m0");
|
|
92
|
+
expect(msg).toContain("index 1");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("warns about widespread instability when many keys swap", () => {
|
|
96
|
+
const { rerender } = renderHook(
|
|
97
|
+
({ items }) => useKeyStability(items),
|
|
98
|
+
{
|
|
99
|
+
initialProps: {
|
|
100
|
+
items: makeItems(
|
|
101
|
+
["a", "message"],
|
|
102
|
+
["b", "message"],
|
|
103
|
+
["c", "message"],
|
|
104
|
+
["d", "message"],
|
|
105
|
+
),
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
rerender({
|
|
111
|
+
items: makeItems(
|
|
112
|
+
["w", "message"],
|
|
113
|
+
["x", "message"],
|
|
114
|
+
["y", "message"],
|
|
115
|
+
["z", "message"],
|
|
116
|
+
),
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const calls = warnSpy.mock.calls.map((c) => c[0] as string);
|
|
120
|
+
const summaryCall = calls.find((m) => m.includes("key swaps detected"));
|
|
121
|
+
expect(summaryCall).toBeDefined();
|
|
122
|
+
expect(summaryCall).toContain("4 key swaps");
|
|
123
|
+
});
|
|
124
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { renderHook } from "@testing-library/react";
|
|
3
|
+
import { useRenderTracer } from "../use-render-tracer";
|
|
4
|
+
|
|
5
|
+
describe("useRenderTracer", () => {
|
|
6
|
+
let debugSpy: ReturnType<typeof vi.spyOn>;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
debugSpy = vi.spyOn(console, "debug").mockImplementation(() => {});
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
debugSpy.mockRestore();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("logs on the first render", () => {
|
|
17
|
+
renderHook(() => useRenderTracer("TestComponent", { foo: "bar" }));
|
|
18
|
+
|
|
19
|
+
expect(debugSpy).toHaveBeenCalledTimes(1);
|
|
20
|
+
const msg = debugSpy.mock.calls[0][0] as string;
|
|
21
|
+
expect(msg).toContain("[stgm:perf:render]");
|
|
22
|
+
expect(msg).toContain("TestComponent");
|
|
23
|
+
expect(msg).toContain("render=#1");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("samples output — skips intermediate renders, logs every 10th", () => {
|
|
27
|
+
const { rerender } = renderHook(
|
|
28
|
+
({ count }) => useRenderTracer("Counter", { count }),
|
|
29
|
+
{ initialProps: { count: 0 } },
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
debugSpy.mockClear();
|
|
33
|
+
|
|
34
|
+
// Renders 2-9: no output (sampled every 10th, first was #1)
|
|
35
|
+
for (let i = 1; i < 9; i++) {
|
|
36
|
+
rerender({ count: i });
|
|
37
|
+
}
|
|
38
|
+
expect(debugSpy).not.toHaveBeenCalled();
|
|
39
|
+
|
|
40
|
+
// Render #10: should log
|
|
41
|
+
rerender({ count: 9 });
|
|
42
|
+
expect(debugSpy).toHaveBeenCalledTimes(1);
|
|
43
|
+
const msg = debugSpy.mock.calls[0][0] as string;
|
|
44
|
+
expect(msg).toContain("render=#10");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("reports changed props on sampled renders", () => {
|
|
48
|
+
const stableRef = {};
|
|
49
|
+
const { rerender } = renderHook(
|
|
50
|
+
({ a, b }) => useRenderTracer("Diff", { a, b }),
|
|
51
|
+
{ initialProps: { a: stableRef as unknown, b: 1 as unknown } },
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
debugSpy.mockClear();
|
|
55
|
+
|
|
56
|
+
// Drive to render #10 with only `b` changing each time
|
|
57
|
+
for (let i = 1; i < 9; i++) {
|
|
58
|
+
rerender({ a: stableRef, b: i + 1 });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Render #10
|
|
62
|
+
rerender({ a: stableRef, b: 10 });
|
|
63
|
+
expect(debugSpy).toHaveBeenCalledTimes(1);
|
|
64
|
+
|
|
65
|
+
const msg = debugSpy.mock.calls[0][0] as string;
|
|
66
|
+
expect(msg).toContain("changed=[b]");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("includes primitive prop values in output", () => {
|
|
70
|
+
renderHook(() =>
|
|
71
|
+
useRenderTracer("Props", { count: 42, active: true }),
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const msg = debugSpy.mock.calls[0][0] as string;
|
|
75
|
+
expect(msg).toContain("count=42");
|
|
76
|
+
expect(msg).toContain("active=true");
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type RefObject, useEffect, useRef } from "react";
|
|
2
|
+
|
|
3
|
+
const DEV = process.env.NODE_ENV !== "production";
|
|
4
|
+
|
|
5
|
+
/** Log the node count every Nth trigger. */
|
|
6
|
+
const LOG_EVERY = 10;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Dev-only hook that periodically counts DOM nodes under a container
|
|
10
|
+
* element and logs the result.
|
|
11
|
+
*
|
|
12
|
+
* Measurement runs inside `requestIdleCallback` (with a `setTimeout`
|
|
13
|
+
* fallback for environments that don't support it) so it never blocks
|
|
14
|
+
* rendering or scroll.
|
|
15
|
+
*
|
|
16
|
+
* @param containerRef - Ref to the DOM element whose subtree to count.
|
|
17
|
+
* @param label - Identifier for the console output.
|
|
18
|
+
*/
|
|
19
|
+
export function useDomNodeCount(
|
|
20
|
+
containerRef: RefObject<HTMLElement | null>,
|
|
21
|
+
label: string,
|
|
22
|
+
): void {
|
|
23
|
+
const triggerCountRef = useRef(0);
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (!DEV) return;
|
|
27
|
+
|
|
28
|
+
triggerCountRef.current += 1;
|
|
29
|
+
if (triggerCountRef.current % LOG_EVERY !== 0) return;
|
|
30
|
+
|
|
31
|
+
const el = containerRef.current;
|
|
32
|
+
if (!el) return;
|
|
33
|
+
|
|
34
|
+
const measure = () => {
|
|
35
|
+
const count = el.querySelectorAll("*").length;
|
|
36
|
+
console.debug(`[stgm:perf:dom] ${label} nodes=${count}`);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
if (typeof requestIdleCallback === "function") {
|
|
40
|
+
const handle = requestIdleCallback(measure);
|
|
41
|
+
return () => cancelIdleCallback(handle);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const handle = setTimeout(measure, 0);
|
|
45
|
+
return () => clearTimeout(handle);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { useRenderTracer } from "./use-render-tracer";
|
|
2
|
+
export { useKeyStability } from "./use-key-stability";
|
|
3
|
+
export { useStreamRate, type StreamRateTracker } from "./use-stream-rate";
|
|
4
|
+
export { DevProfiler } from "./profiler-wrapper";
|
|
5
|
+
export { useDomNodeCount } from "./dom-counter";
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Profiler, type ProfilerOnRenderCallback, type ReactNode } from "react";
|
|
4
|
+
|
|
5
|
+
const DEV = process.env.NODE_ENV !== "production";
|
|
6
|
+
|
|
7
|
+
/** Log every Nth commit to avoid console flood during streaming. */
|
|
8
|
+
const LOG_EVERY = 10;
|
|
9
|
+
|
|
10
|
+
let commitCount = 0;
|
|
11
|
+
|
|
12
|
+
const onRender: ProfilerOnRenderCallback = (
|
|
13
|
+
id,
|
|
14
|
+
phase,
|
|
15
|
+
actualDuration,
|
|
16
|
+
baseDuration,
|
|
17
|
+
) => {
|
|
18
|
+
commitCount += 1;
|
|
19
|
+
if (commitCount % LOG_EVERY === 0 || commitCount === 1) {
|
|
20
|
+
console.debug(
|
|
21
|
+
`[stgm:perf:profiler] ${id} phase=${phase} ` +
|
|
22
|
+
`actualDuration=${actualDuration.toFixed(1)}ms ` +
|
|
23
|
+
`baseDuration=${baseDuration.toFixed(1)}ms`,
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Thin wrapper around React's `<Profiler>`.
|
|
30
|
+
*
|
|
31
|
+
* In dev mode, logs commit-level timing (`actualDuration`,
|
|
32
|
+
* `baseDuration`, mount vs update phase) with sampled output.
|
|
33
|
+
*
|
|
34
|
+
* In production, React strips Profiler callbacks automatically, and
|
|
35
|
+
* our dev gate ensures this component renders children with zero
|
|
36
|
+
* overhead regardless.
|
|
37
|
+
*/
|
|
38
|
+
export function DevProfiler({
|
|
39
|
+
id,
|
|
40
|
+
children,
|
|
41
|
+
}: {
|
|
42
|
+
readonly id: string;
|
|
43
|
+
readonly children: ReactNode;
|
|
44
|
+
}) {
|
|
45
|
+
if (!DEV) return children;
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<Profiler id={id} onRender={onRender}>
|
|
49
|
+
{children}
|
|
50
|
+
</Profiler>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { useRef } from "react";
|
|
2
|
+
|
|
3
|
+
const DEV = process.env.NODE_ENV !== "production";
|
|
4
|
+
|
|
5
|
+
/** Minimal shape expected from a keyed thread item. */
|
|
6
|
+
interface KeyedItem {
|
|
7
|
+
readonly key: string;
|
|
8
|
+
readonly kind: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Dev-only hook that detects key instability in a list of keyed items.
|
|
13
|
+
*
|
|
14
|
+
* Compares the current render's key set against the previous render's
|
|
15
|
+
* and warns when:
|
|
16
|
+
*
|
|
17
|
+
* - Duplicate keys exist in the current render (collision bug).
|
|
18
|
+
* - A key disappears and a new key appears at the same list index
|
|
19
|
+
* (suggests a remount caused by key change, not a logical add/remove).
|
|
20
|
+
* - The total number of key replacements in a single render exceeds a
|
|
21
|
+
* threshold, indicating widespread instability.
|
|
22
|
+
*
|
|
23
|
+
* In production builds the entire function body is dead-code-eliminated.
|
|
24
|
+
*/
|
|
25
|
+
export function useKeyStability(items: readonly KeyedItem[]): void {
|
|
26
|
+
const prevRef = useRef<readonly KeyedItem[] | null>(null);
|
|
27
|
+
|
|
28
|
+
if (!DEV) return;
|
|
29
|
+
|
|
30
|
+
// Detect duplicate keys within the current render.
|
|
31
|
+
const seen = new Map<string, number>();
|
|
32
|
+
for (let i = 0; i < items.length; i++) {
|
|
33
|
+
const prevIdx = seen.get(items[i].key);
|
|
34
|
+
if (prevIdx !== undefined) {
|
|
35
|
+
console.warn(
|
|
36
|
+
`[stgm:perf:keys] Duplicate key "${items[i].key}" at indices ${prevIdx} and ${i} ` +
|
|
37
|
+
`(${items[prevIdx].kind}, ${items[i].kind}). React will silently drop one.`,
|
|
38
|
+
);
|
|
39
|
+
} else {
|
|
40
|
+
seen.set(items[i].key, i);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const prev = prevRef.current;
|
|
45
|
+
prevRef.current = items;
|
|
46
|
+
|
|
47
|
+
if (!prev || prev.length === 0) return;
|
|
48
|
+
|
|
49
|
+
const prevKeys = new Map<string, number>();
|
|
50
|
+
for (let i = 0; i < prev.length; i++) {
|
|
51
|
+
prevKeys.set(prev[i].key, i);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const curKeys = new Set<string>(seen.keys());
|
|
55
|
+
|
|
56
|
+
const removed: string[] = [];
|
|
57
|
+
for (const key of prevKeys.keys()) {
|
|
58
|
+
if (!curKeys.has(key)) removed.push(key);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (removed.length === 0) return;
|
|
62
|
+
|
|
63
|
+
let swapCount = 0;
|
|
64
|
+
|
|
65
|
+
for (const removedKey of removed) {
|
|
66
|
+
const idx = prevKeys.get(removedKey)!;
|
|
67
|
+
if (idx < items.length) {
|
|
68
|
+
const replacement = items[idx];
|
|
69
|
+
if (!prevKeys.has(replacement.key)) {
|
|
70
|
+
swapCount++;
|
|
71
|
+
console.warn(
|
|
72
|
+
`[stgm:perf:keys] Key swap at index ${idx}: ` +
|
|
73
|
+
`"${removedKey}" (${prev[idx].kind}) → "${replacement.key}" (${replacement.kind}). ` +
|
|
74
|
+
"This causes React to unmount/remount the row.",
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (swapCount > 3) {
|
|
81
|
+
console.warn(
|
|
82
|
+
`[stgm:perf:keys] ${swapCount} key swaps detected in a single render. ` +
|
|
83
|
+
"Thread items may be using unstable keys.",
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { useRef } from "react";
|
|
2
|
+
|
|
3
|
+
const DEV = process.env.NODE_ENV !== "production";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Sampling interval: log every Nth render to avoid flooding the
|
|
7
|
+
* console during high-frequency streaming (10-15 ticks/s).
|
|
8
|
+
*/
|
|
9
|
+
const LOG_EVERY = 10;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Dev-only hook that tracks render count and reports which props
|
|
13
|
+
* changed (by shallow referential equality) since the last render.
|
|
14
|
+
*
|
|
15
|
+
* In production builds the function body is dead-code-eliminated by
|
|
16
|
+
* bundlers that replace `process.env.NODE_ENV` with `"production"`.
|
|
17
|
+
*
|
|
18
|
+
* @param componentName - Stable label for console output.
|
|
19
|
+
* @param props - Key/value map of props to track. Pass only the props
|
|
20
|
+
* you care about — typically the ones that drive re-renders.
|
|
21
|
+
*/
|
|
22
|
+
export function useRenderTracer(
|
|
23
|
+
componentName: string,
|
|
24
|
+
props: Record<string, unknown>,
|
|
25
|
+
): void {
|
|
26
|
+
const countRef = useRef(0);
|
|
27
|
+
const prevRef = useRef<Record<string, unknown> | null>(null);
|
|
28
|
+
|
|
29
|
+
if (!DEV) return;
|
|
30
|
+
|
|
31
|
+
countRef.current += 1;
|
|
32
|
+
const count = countRef.current;
|
|
33
|
+
const prev = prevRef.current;
|
|
34
|
+
|
|
35
|
+
if (count % LOG_EVERY === 0 || count === 1) {
|
|
36
|
+
const changed: string[] = [];
|
|
37
|
+
if (prev) {
|
|
38
|
+
for (const key of Object.keys(props)) {
|
|
39
|
+
if (!Object.is(props[key], prev[key])) {
|
|
40
|
+
changed.push(key);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const parts = [
|
|
46
|
+
`[stgm:perf:render] ${componentName}`,
|
|
47
|
+
`render=#${count}`,
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
for (const [key, value] of Object.entries(props)) {
|
|
51
|
+
if (typeof value === "string") {
|
|
52
|
+
parts.push(`${key}=${value.length > 40 ? value.slice(0, 40) + "…" : value}`);
|
|
53
|
+
} else if (typeof value === "number" || typeof value === "boolean") {
|
|
54
|
+
parts.push(`${key}=${String(value)}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (prev) {
|
|
59
|
+
parts.push(
|
|
60
|
+
changed.length > 0
|
|
61
|
+
? `changed=[${changed.join(",")}]`
|
|
62
|
+
: "changed=[]",
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
console.debug(parts.join(" "));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
prevRef.current = { ...props };
|
|
70
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { useRef } from "react";
|
|
2
|
+
|
|
3
|
+
const DEV = process.env.NODE_ENV !== "production";
|
|
4
|
+
|
|
5
|
+
/** Rolling window size for interval statistics. */
|
|
6
|
+
const WINDOW_SIZE = 30;
|
|
7
|
+
|
|
8
|
+
/** Log a sampled line every Nth tick. */
|
|
9
|
+
const LOG_EVERY = 10;
|
|
10
|
+
|
|
11
|
+
/** Mutable state tracked across stream ticks. */
|
|
12
|
+
interface StreamRateState {
|
|
13
|
+
tickCount: number;
|
|
14
|
+
/** Timestamps (ms) of recent ticks for interval stats. */
|
|
15
|
+
timestamps: number[];
|
|
16
|
+
/** Message count from the previous tick, for delta calculation. */
|
|
17
|
+
prevMessageCount: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Imperative tracker returned by {@link useStreamRate}.
|
|
22
|
+
*
|
|
23
|
+
* Call `tick()` on each stream snapshot inside the `for await` loop.
|
|
24
|
+
* Call `summary()` when the stream ends to log aggregate stats.
|
|
25
|
+
*/
|
|
26
|
+
export interface StreamRateTracker {
|
|
27
|
+
/** Record a stream snapshot arrival. */
|
|
28
|
+
tick(messageCount: number): void;
|
|
29
|
+
/** Log aggregate statistics for the completed stream. */
|
|
30
|
+
summary(): void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const NOOP_TRACKER: StreamRateTracker = {
|
|
34
|
+
tick() {},
|
|
35
|
+
summary() {},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Dev-only hook that returns an imperative {@link StreamRateTracker}.
|
|
40
|
+
*
|
|
41
|
+
* The tracker is designed to be called inside a `useEffect` async
|
|
42
|
+
* loop (not during render), so it uses a ref-backed mutable object
|
|
43
|
+
* rather than React state.
|
|
44
|
+
*
|
|
45
|
+
* In production the hook returns a no-op tracker.
|
|
46
|
+
*/
|
|
47
|
+
export function useStreamRate(): StreamRateTracker {
|
|
48
|
+
const stateRef = useRef<StreamRateState | null>(null);
|
|
49
|
+
|
|
50
|
+
if (!DEV) return NOOP_TRACKER;
|
|
51
|
+
|
|
52
|
+
if (!stateRef.current) {
|
|
53
|
+
stateRef.current = {
|
|
54
|
+
tickCount: 0,
|
|
55
|
+
timestamps: [],
|
|
56
|
+
prevMessageCount: 0,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const state = stateRef.current;
|
|
61
|
+
|
|
62
|
+
const tracker: StreamRateTracker = {
|
|
63
|
+
tick(messageCount: number) {
|
|
64
|
+
const now = performance.now();
|
|
65
|
+
state.tickCount += 1;
|
|
66
|
+
state.timestamps.push(now);
|
|
67
|
+
if (state.timestamps.length > WINDOW_SIZE) {
|
|
68
|
+
state.timestamps.shift();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const delta = messageCount - state.prevMessageCount;
|
|
72
|
+
state.prevMessageCount = messageCount;
|
|
73
|
+
|
|
74
|
+
if (state.tickCount % LOG_EVERY === 0 || state.tickCount === 1) {
|
|
75
|
+
const intervals = computeIntervals(state.timestamps);
|
|
76
|
+
const parts = [
|
|
77
|
+
`[stgm:perf:stream] tick #${state.tickCount}`,
|
|
78
|
+
`messages=${messageCount}`,
|
|
79
|
+
`delta=+${delta}`,
|
|
80
|
+
];
|
|
81
|
+
if (intervals) {
|
|
82
|
+
parts.push(
|
|
83
|
+
`interval=${intervals.avg.toFixed(0)}ms`,
|
|
84
|
+
`rate=${intervals.rate.toFixed(1)}/s`,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
console.debug(parts.join(" "));
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
summary() {
|
|
92
|
+
if (state.tickCount === 0) return;
|
|
93
|
+
|
|
94
|
+
const intervals = computeIntervals(state.timestamps);
|
|
95
|
+
const parts = [
|
|
96
|
+
`[stgm:perf:stream] ✓ stream complete`,
|
|
97
|
+
`totalTicks=${state.tickCount}`,
|
|
98
|
+
`finalMessages=${state.prevMessageCount}`,
|
|
99
|
+
];
|
|
100
|
+
if (intervals) {
|
|
101
|
+
parts.push(
|
|
102
|
+
`avgInterval=${intervals.avg.toFixed(0)}ms`,
|
|
103
|
+
`minInterval=${intervals.min.toFixed(0)}ms`,
|
|
104
|
+
`maxInterval=${intervals.max.toFixed(0)}ms`,
|
|
105
|
+
`avgRate=${intervals.rate.toFixed(1)}/s`,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
console.debug(parts.join(" "));
|
|
109
|
+
|
|
110
|
+
state.tickCount = 0;
|
|
111
|
+
state.timestamps.length = 0;
|
|
112
|
+
state.prevMessageCount = 0;
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
return tracker;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function computeIntervals(timestamps: number[]) {
|
|
120
|
+
if (timestamps.length < 2) return null;
|
|
121
|
+
|
|
122
|
+
let min = Infinity;
|
|
123
|
+
let max = 0;
|
|
124
|
+
let sum = 0;
|
|
125
|
+
const count = timestamps.length - 1;
|
|
126
|
+
|
|
127
|
+
for (let i = 1; i < timestamps.length; i++) {
|
|
128
|
+
const dt = timestamps[i] - timestamps[i - 1];
|
|
129
|
+
sum += dt;
|
|
130
|
+
if (dt < min) min = dt;
|
|
131
|
+
if (dt > max) max = dt;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const avg = sum / count;
|
|
135
|
+
const rate = avg > 0 ? 1000 / avg : 0;
|
|
136
|
+
|
|
137
|
+
return { min, max, avg, rate };
|
|
138
|
+
}
|