Skip to content

Feat/workspaces 6 billing#8508

Merged
simula-r merged 16 commits intomainfrom
feat/workspaces-6-billing
Feb 7, 2026
Merged

Feat/workspaces 6 billing#8508
simula-r merged 16 commits intomainfrom
feat/workspaces-6-billing

Conversation

@simula-r
Copy link
Contributor

@simula-r simula-r commented Jan 31, 2026

Summary

Implements billing infrastructure for team workspaces, separate from legacy personal billing.

Changes

  • Billing abstraction: New useBillingContext composable that switches between legacy (personal) and workspace billing based on context
  • Workspace subscription flows: Pricing tables, plan transitions, cancellation dialogs, and payment preview components for workspace billing
  • Top-up credits: Workspace-specific top-up dialog with polling for payment confirmation
  • Workspace API: Extended with billing endpoints (subscriptions, invoices, payment methods, credits top-up)
  • Workspace switcher: Now displays tier badges for each workspace
  • Subscribe polling: Added polling mechanisms (useSubscribePolling, useTopupPolling) for async payment flows

Review Focus

  • Billing flow correctness for workspace vs legacy contexts
  • Polling timeout and error handling in payment flows

┆Issue is synchronized with this Notion page by Unito

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 31, 2026

📝 Walkthrough

Walkthrough

Introduces a unified billing composable useBillingContext, workspace billing adapters, new workspace subscription UIs and API endpoints, a billing operation store with polling/backoff, top-up and cancel dialogs, and updates many components/tests to consume the new billing context (replacing prior useSubscription usages).

Changes

Cohort / File(s) Summary
Billing core & adapters
src/composables/billing/types.ts, src/composables/billing/useBillingContext.ts, src/composables/billing/useLegacyBilling.ts, src/composables/billing/useWorkspaceBilling.ts, src/composables/billing/useBillingContext.test.ts
Adds billing types and a shared useBillingContext that proxies legacy and workspace adapters; implements workspace and legacy adapters with actions, polling, and shared API. Includes a test suite for the billing context.
Workspace subscription UI (new)
src/platform/cloud/subscription/components/PricingTableWorkspace.vue, .../SubscriptionAddPaymentPreviewWorkspace.vue, .../SubscriptionTransitionPreviewWorkspace.vue, .../SubscriptionRequiredDialogContentWorkspace.vue
New workspace-focused subscription components: pricing table, payment preview, transition preview, and multi-step required-subscription dialog with preview/subscribe flows.
Top-up & Cancel dialogs
src/components/dialog/content/TopUpCreditsDialogContentWorkspace.vue, src/components/dialog/content/subscription/CancelSubscriptionDialogContent.vue, src/components/dialog/content/TopUpCreditsDialogContentLegacy.vue
Adds workspace top-up component and cancel-subscription dialog; updates legacy top-up. Integrates top-up create, status handling, toasts, and operation polling.
Billing operation store & tests
src/stores/billingOperationStore.ts, src/stores/billingOperationStore.test.ts
New Pinia store to track billing operations with polling, exponential backoff, timeout handling, toasts, and comprehensive tests.
Workspace API & types
src/platform/workspace/api/workspaceApi.ts
Replaces BillingPortal endpoints with a new billing API surface: plans, preview/subscribe, cancel/resubscribe, topups, events, ops; adds many request/response types and enums.
Workspace store updates
src/platform/workspace/stores/teamWorkspaceStore.ts
Adds subscriptionTier to workspace state, broadens subscription plan typing, and exposes updateActiveWorkspace.
Migration to useBillingContext
multiple files: src/components/.../CloudRunButtonWrapper.vue, LegacyCreditsPanel.vue, src/composables/useCoreCommands.ts, src/extensions/core/..., src/platform/cloud/subscription/components/SubscribeButton.vue, SubscribeToRun.vue, SubscriptionPanel.vue, src/platform/settings/composables/useSettingUI.ts, src/scripts/app.ts, src/renderer/extensions/linearMode/LinearControls.vue, src/platform/assets/components/UploadModelUpgradeModal.vue, ...
Many call sites changed from useSubscription() to useBillingContext() and adapted to the unified return shape (isActiveSubscription, manage/require/showSubscriptionDialog, fetchStatus/fetchBalance).
Subscription panel / workspace flow
src/platform/cloud/subscription/components/SubscriptionPanelContentWorkspace.vue, SubscriptionPanel.vue
Refactors subscription panel for workspace billing: resubscribe/cancel flows, member counts, invoice card, manage/resubscribe wiring, focus refresh, and API-backed helpers.
Dialog service & selection
src/services/dialogService.ts, src/platform/cloud/subscription/composables/useSubscriptionDialog.ts
Dialog service now selects legacy vs workspace top-up component by billing context; adds showCancelSubscriptionDialog; subscription dialog selection uses feature flags and workspace context.
Billing plans & pricing
src/platform/cloud/subscription/composables/useBillingPlans.ts, src/platform/cloud/subscription/constants/tierPricing.ts
Adds useBillingPlans composable and extends tier feature definitions with maxMembers.
Credits & balance migration
src/platform/cloud/subscription/composables/useSubscriptionCredits.ts, tests
Switches balance source from firebase auth store to useBillingContext.balance, adds balance formatting helper, updates related tests to mock billing context.
Topbar / popovers / tests
src/components/topbar/*, src/components/topbar/CurrentUserButton.test.ts, CurrentUserPopoverLegacy.test.ts
Adds popover refresh hook and refreshBalance exposure, updates popover imports/mocks, and ties workspace tier labeling to billing context.
Localization & UI touches
src/locales/en/main.json, src/components/dialog/header/SettingDialogHeader.vue, src/components/toast/GlobalToast.vue
Adds extensive i18n keys for billing flows, feature-flag-driven header padding, and a dedicated billing-operation toast group.
Tests & mocks updated
various tests: src/composables/billing/useBillingContext.test.ts, src/composables/useCoreCommands.test.ts, src/platform/cloud/.../*.test.ts, src/platform/cloud/subscription/composables/useSubscriptionCredits.test.ts
Adds a billing-context test suite and updates many tests to mock or use useBillingContext.
Misc small changes
src/stores/firebaseAuthStore.ts, .gitignore, src/platform/assets/components/UploadModelUpgradeModal.vue, src/components/dialog/content/TopUpCreditsDialogContentLegacy.vue
Swapped an auth header helper to Firebase-specific helper in one path, added .amp to .gitignore, and adjusted subscription dialog wiring to the billing context.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant PricingUI as PricingTableWorkspace
    participant BillingAPI as Billing Service
    participant PaymentUI as Payment Preview
    participant Backend as Backend

    User->>PricingUI: Select tier & billing cycle
    PricingUI->>BillingAPI: previewSubscribe(plan_slug)
    BillingAPI->>Backend: POST /billing/preview-subscribe
    Backend-->>BillingAPI: PreviewSubscribeResponse
    BillingAPI-->>PricingUI: preview data
    PricingUI->>PaymentUI: Show preview
    User->>PaymentUI: Confirm
    PaymentUI->>BillingAPI: subscribe(plan_slug)
    BillingAPI->>Backend: POST /billing/subscribe
    Backend-->>BillingAPI: SubscribeResponse (status, billing_op_id, payment_url?)
    BillingAPI-->>PaymentUI: subscribe result
    alt needs payment method
        PaymentUI->>User: Redirect to payment URL
    else pending/awaiting
        PaymentUI->>BillingAPI: poll getBillingStatus(billing_op_id)
        BillingAPI->>Backend: GET /billing/ops/:id or /billing/status
        Backend-->>BillingAPI: status -> active
        BillingAPI-->>PaymentUI: active
    end
    PaymentUI->>User: Show success toast & close
Loading
sequenceDiagram
    participant User
    participant TopUpUI as TopUpDialog
    participant BillingAPI as Billing Service
    participant PollStore as BillingOperationStore
    participant Backend as Backend

    User->>TopUpUI: Enter amount & click Buy
    TopUpUI->>BillingAPI: createTopup(amount_cents)
    BillingAPI->>Backend: POST /billing/topup
    Backend-->>BillingAPI: CreateTopupResponse {billing_op_id, topup_id, status}
    BillingAPI-->>TopUpUI: Topup created
    TopUpUI->>PollStore: startOperation(billing_op_id / topup_id, 'topup')
    PollStore->>BillingAPI: getTopupStatus(topup_id)
    BillingAPI->>Backend: GET /billing/topup/:id
    Backend-->>BillingAPI: TopupStatus
    alt completed
        PollStore->>BillingAPI: getBillingBalance()
        PollStore->>TopUpUI: close & show success toast
    else pending
        PollStore->>PollStore: wait & retry (exponential backoff)
    else failed or timeout
        PollStore->>TopUpUI: show error toast & cleanup
    end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~150 minutes

Poem

🐰 I hopped through code, found billing's new song,
Legacy and workspace now playing along.
Plans, top-ups, and polls keep the ledger bright,
Dialogs and toasts cheer the billing-night—
A joyful wiggle for engineers tonight! 🎉

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.45% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Feat/workspaces 6 billing' is vague and uses abbreviated/non-descriptive terms that don't clearly convey the main change. A developer scanning history wouldn't understand what was implemented without context. Revise to a more descriptive title like 'Add workspace billing infrastructure and subscription management' or 'Implement billing abstraction for workspace and legacy billing contexts'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The description is well-structured with clear sections (Summary, Changes, Review Focus) and comprehensively covers the major changes, but lacks specific examples or breaking change clarification despite significant API additions.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/workspaces-6-billing

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

github-actions bot commented Jan 31, 2026

🎭 Playwright Tests: ⏳ Running...

Tests started at 02/06/2026, 05:50:39 AM UTC

📊 Browser Tests
  • chromium: Running...
  • chromium-0.5x: Running...
  • chromium-2x: Running...
  • mobile-chrome: Running...

@github-actions
Copy link

github-actions bot commented Jan 31, 2026

🎨 Storybook Build Status

Build completed successfully!

⏰ Completed at: 02/06/2026, 05:43:48 AM UTC

🔗 Links


🎉 Your Storybook is ready for review!

@github-actions
Copy link

github-actions bot commented Jan 31, 2026

Bundle Size Report

Summary

  • Raw size: 20.3 MB baseline 20.2 MB — 🔴 +96.2 kB
  • Gzip: 4.34 MB baseline 4.32 MB — 🔴 +16.9 kB
  • Brotli: 3.35 MB baseline 3.34 MB — 🔴 +12.3 kB
  • Bundles: 232 current • 233 baseline • 216 added / 217 removed

Category Glance
Data & Services 🔴 +51.3 kB (2.15 MB) · Other 🔴 +47.2 kB (7.14 MB) · Editors & Dialogs 🟢 -2.65 kB (818 B) · UI Components 🟢 -1.14 kB (36.7 kB) · Views & Navigation 🔴 +612 B (69.6 kB) · Panels & Settings 🔴 +349 B (411 kB) · + 5 more

Per-category breakdown
App Entry Points — 22.5 kB (baseline 22.5 kB) • ⚪ 0 B

Main entry bundles and manifests

File Before After Δ Raw Δ Gzip Δ Brotli
assets/index-CC5KWfN0.js (new) 22.5 kB 🔴 +22.5 kB 🔴 +7.17 kB 🔴 +6.23 kB
assets/index-Dj4QzKF0.js (removed) 22.5 kB 🟢 -22.5 kB 🟢 -7.18 kB 🟢 -6.24 kB

Status: 1 added / 1 removed

Graph Workspace — 840 kB (baseline 840 kB) • 🔴 +183 B

Graph editor runtime, canvas, workflow orchestration

File Before After Δ Raw Δ Gzip Δ Brotli
assets/GraphView-Co3Irg5I.js (new) 840 kB 🔴 +840 kB 🔴 +180 kB 🔴 +137 kB
assets/GraphView-BoT0juUT.js (removed) 840 kB 🟢 -840 kB 🟢 -180 kB 🟢 -137 kB

Status: 1 added / 1 removed

Views & Navigation — 69.6 kB (baseline 69 kB) • 🔴 +612 B

Top-level views, pages, and routed surfaces

File Before After Δ Raw Δ Gzip Δ Brotli
assets/CloudSurveyView-DgH6yRB2.js (new) 15.8 kB 🔴 +15.8 kB 🔴 +3.5 kB 🔴 +2.97 kB
assets/CloudSurveyView-CdvBA3Nl.js (removed) 15.8 kB 🟢 -15.8 kB 🟢 -3.46 kB 🟢 -2.95 kB
assets/CloudLoginView-B_tWLfqX.js (new) 10.1 kB 🔴 +10.1 kB 🔴 +2.97 kB 🔴 +2.6 kB
assets/CloudLoginView-CJBh090I.js (removed) 10 kB 🟢 -10 kB 🟢 -2.93 kB 🟢 -2.57 kB
assets/UserCheckView-Cr-VikHo.js (new) 8.44 kB 🔴 +8.44 kB 🔴 +2.24 kB 🔴 +1.95 kB
assets/UserCheckView-Cx7E6AFj.js (removed) 8.39 kB 🟢 -8.39 kB 🟢 -2.23 kB 🟢 -1.94 kB
assets/CloudSignupView-Ej08ZktH.js (new) 7.46 kB 🔴 +7.46 kB 🔴 +2.35 kB 🔴 +2.04 kB
assets/CloudSignupView-lOHQ-z6C.js (removed) 7.38 kB 🟢 -7.38 kB 🟢 -2.31 kB 🟢 -2.02 kB
assets/CloudLayoutView-DudBqb8P.js (new) 6.51 kB 🔴 +6.51 kB 🔴 +2.16 kB 🔴 +1.88 kB
assets/CloudLayoutView-C2e08tqG.js (removed) 6.43 kB 🟢 -6.43 kB 🟢 -2.12 kB 🟢 -1.83 kB
assets/CloudForgotPasswordView-CpYiYJda.js (new) 5.64 kB 🔴 +5.64 kB 🔴 +1.98 kB 🔴 +1.74 kB
assets/CloudForgotPasswordView-Cg-UGqAM.js (removed) 5.56 kB 🟢 -5.56 kB 🟢 -1.94 kB 🟢 -1.72 kB
assets/CloudAuthTimeoutView-BHtqukLh.js (new) 4.99 kB 🔴 +4.99 kB 🔴 +1.81 kB 🔴 +1.59 kB
assets/CloudAuthTimeoutView-BLck4lp7.js (removed) 4.91 kB 🟢 -4.91 kB 🟢 -1.77 kB 🟢 -1.55 kB
assets/CloudSubscriptionRedirectView-DY39J7Mq.js (new) 4.79 kB 🔴 +4.79 kB 🔴 +1.82 kB 🔴 +1.61 kB
assets/CloudSubscriptionRedirectView-T8B_rKuq.js (removed) 4.71 kB 🟢 -4.71 kB 🟢 -1.77 kB 🟢 -1.57 kB
assets/UserSelectView-DdOdr88o.js (new) 4.49 kB 🔴 +4.49 kB 🔴 +1.64 kB 🔴 +1.46 kB
assets/UserSelectView-RGdfDzzz.js (removed) 4.49 kB 🟢 -4.49 kB 🟢 -1.64 kB 🟢 -1.46 kB
assets/CloudSorryContactSupportView-BPXBOTez.js (removed) 1.02 kB 🟢 -1.02 kB 🟢 -539 B 🟢 -464 B
assets/CloudSorryContactSupportView-CxIpFCaO.js (new) 1.02 kB 🔴 +1.02 kB 🔴 +539 B 🔴 +470 B
assets/layout-Cxcanm82.js (new) 296 B 🔴 +296 B 🔴 +220 B 🔴 +194 B
assets/layout-ncetNFDR.js (removed) 296 B 🟢 -296 B 🟢 -222 B 🟢 -186 B

Status: 11 added / 11 removed

Panels & Settings — 411 kB (baseline 410 kB) • 🔴 +349 B

Configuration panels, inspectors, and settings screens

File Before After Δ Raw Δ Gzip Δ Brotli
assets/settings-BmEXiKRV.js (removed) 32 kB 🟢 -32 kB 🟢 -8.08 kB 🟢 -6.63 kB
assets/settings-HfVYOjFC.js (new) 32 kB 🔴 +32 kB 🔴 +8.08 kB 🔴 +6.63 kB
assets/settings-CBYZ18F3.js (new) 28.1 kB 🔴 +28.1 kB 🔴 +7.15 kB 🔴 +6.03 kB
assets/settings-CKCscONF.js (removed) 28.1 kB 🟢 -28.1 kB 🟢 -7.15 kB 🟢 -6.03 kB
assets/WorkspacePanel-EC7DJYGe.js (removed) 26.9 kB 🟢 -26.9 kB 🟢 -5.66 kB 🟢 -4.96 kB
assets/WorkspacePanel-DUKSrmMw.js (new) 26.9 kB 🔴 +26.9 kB 🔴 +5.66 kB 🔴 +4.96 kB
assets/settings-B4SEKci9.js (new) 26.8 kB 🔴 +26.8 kB 🔴 +7 kB 🔴 +5.68 kB
assets/settings-D95NIw0v.js (removed) 26.8 kB 🟢 -26.8 kB 🟢 -7 kB 🟢 -5.67 kB
assets/settings-Cnka_dKX.js (new) 25.7 kB 🔴 +25.7 kB 🔴 +7.28 kB 🔴 +6.1 kB
assets/settings-DQe4OZm2.js (removed) 25.7 kB 🟢 -25.7 kB 🟢 -7.28 kB 🟢 -6.1 kB
assets/settings-BgcR6REE.js (removed) 25 kB 🟢 -25 kB 🟢 -6.99 kB 🟢 -6.11 kB
assets/settings-D6HkNk7G.js (new) 25 kB 🔴 +25 kB 🔴 +6.99 kB 🔴 +6.11 kB
assets/settings-DmYc-PH9.js (removed) 24.2 kB 🟢 -24.2 kB 🟢 -6.75 kB 🟢 -5.74 kB
assets/settings-gIPnacUu.js (new) 24.2 kB 🔴 +24.2 kB 🔴 +6.74 kB 🔴 +5.73 kB
assets/settings-Bc2B6dtS.js (new) 24.1 kB 🔴 +24.1 kB 🔴 +6.88 kB 🔴 +6.02 kB
assets/settings-BNs5rnMt.js (removed) 24.1 kB 🟢 -24.1 kB 🟢 -6.88 kB 🟢 -6.02 kB
assets/settings-BC1cc0fS.js (new) 23.6 kB 🔴 +23.6 kB 🔴 +6.64 kB 🔴 +5.8 kB
assets/settings-C5jqELHS.js (removed) 23.6 kB 🟢 -23.6 kB 🟢 -6.64 kB 🟢 -5.8 kB
assets/settings-BthVWLgQ.js (new) 23.3 kB 🔴 +23.3 kB 🔴 +7 kB 🔴 +5.81 kB
assets/settings-f2yAwEpW.js (removed) 23.3 kB 🟢 -23.3 kB 🟢 -7 kB 🟢 -5.81 kB
assets/SecretsPanel-C0Ccjtcq.js (removed) 21.5 kB 🟢 -21.5 kB 🟢 -5.31 kB 🟢 -4.65 kB
assets/SecretsPanel-D6Oy-uQH.js (new) 21.5 kB 🔴 +21.5 kB 🔴 +5.31 kB 🔴 +4.65 kB
assets/LegacyCreditsPanel-DLy1p5cJ.js (new) 20.9 kB 🔴 +20.9 kB 🔴 +5.64 kB 🔴 +4.95 kB
assets/settings-BcpoYtvB.js (new) 20.8 kB 🔴 +20.8 kB 🔴 +6.85 kB 🔴 +5.49 kB
assets/settings-CzcL_U24.js (removed) 20.8 kB 🟢 -20.8 kB 🟢 -6.85 kB 🟢 -5.49 kB
assets/LegacyCreditsPanel-e4I_EcQB.js (removed) 20.8 kB 🟢 -20.8 kB 🟢 -5.62 kB 🟢 -4.93 kB
assets/settings-Dxocpd_-.js (removed) 20.3 kB 🟢 -20.3 kB 🟢 -6.62 kB 🟢 -5.16 kB
assets/settings-KJ7OpQSN.js (new) 20.3 kB 🔴 +20.3 kB 🔴 +6.62 kB 🔴 +5.16 kB
assets/SubscriptionPanel-DGXud9NC.js (removed) 18.8 kB 🟢 -18.8 kB 🟢 -4.77 kB 🟢 -4.21 kB
assets/SubscriptionPanel-B4rCnagX.js (new) 18.7 kB 🔴 +18.7 kB 🔴 +4.77 kB 🔴 +4.21 kB
assets/KeybindingPanel-B2iP41Dt.js (new) 12.7 kB 🔴 +12.7 kB 🔴 +3.67 kB 🔴 +3.25 kB
assets/KeybindingPanel-CFRnolTC.js (removed) 12.6 kB 🟢 -12.6 kB 🟢 -3.63 kB 🟢 -3.2 kB
assets/ExtensionPanel-DwQChQ-R.js (new) 9.57 kB 🔴 +9.57 kB 🔴 +2.73 kB 🔴 +2.42 kB
assets/ExtensionPanel-DSY9As5h.js (removed) 9.49 kB 🟢 -9.49 kB 🟢 -2.69 kB 🟢 -2.39 kB
assets/AboutPanel-BCXCOvSM.js (new) 8.62 kB 🔴 +8.62 kB 🔴 +2.46 kB 🔴 +2.21 kB
assets/AboutPanel-Btv4aEbD.js (removed) 8.62 kB 🟢 -8.62 kB 🟢 -2.46 kB 🟢 -2.21 kB
assets/ServerConfigPanel-BDQSoAlK.js (new) 6.72 kB 🔴 +6.72 kB 🔴 +2.19 kB 🔴 +1.99 kB
assets/ServerConfigPanel-DDW0HSo0.js (removed) 6.64 kB 🟢 -6.64 kB 🟢 -2.15 kB 🟢 -1.95 kB
assets/UserPanel-CKObrxEs.js (new) 6.36 kB 🔴 +6.36 kB 🔴 +2.07 kB 🔴 +1.82 kB
assets/UserPanel-CQgjaEdm.js (removed) 6.28 kB 🟢 -6.28 kB 🟢 -2.02 kB 🟢 -1.77 kB
assets/cloudRemoteConfig-CyMMla1y.js (new) 1.52 kB 🔴 +1.52 kB 🔴 +746 B 🔴 +643 B
assets/cloudRemoteConfig-3RjIrMkK.js (removed) 1.44 kB 🟢 -1.44 kB 🟢 -700 B 🟢 -609 B
assets/refreshRemoteConfig-BgyiXDuv.js (removed) 1.13 kB 🟢 -1.13 kB 🟢 -514 B 🟢 -448 B
assets/refreshRemoteConfig-DkwhZ6qt.js (new) 1.13 kB 🔴 +1.13 kB 🔴 +514 B 🔴 +449 B
assets/config-CJ8VnGvP.js (new) 1.01 kB 🔴 +1.01 kB 🔴 +549 B 🔴 +445 B
assets/config-HAvaVehF.js (removed) 1.01 kB 🟢 -1.01 kB 🟢 -549 B 🟢 -445 B
assets/refreshRemoteConfig-DpJ6LOBE.js (new) 345 B 🔴 +345 B 🔴 +202 B 🔴 +201 B
assets/refreshRemoteConfig-DPKKOPo3.js (removed) 345 B 🟢 -345 B 🟢 -201 B 🟢 -204 B
assets/remoteConfig-CUBB_j_I.js 581 B 581 B ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 24 added / 24 removed

User & Accounts — 16.2 kB (baseline 16 kB) • 🔴 +160 B

Authentication, profile, and account management bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/PasswordFields-BAi3GPM-.js (removed) 4.51 kB 🟢 -4.51 kB 🟢 -1.36 kB 🟢 -1.19 kB
assets/PasswordFields-DowW4xPk.js (new) 4.51 kB 🔴 +4.51 kB 🔴 +1.36 kB 🔴 +1.2 kB
assets/auth-B4tEzJ7G.js (removed) 3.4 kB 🟢 -3.4 kB 🟢 -1.18 kB 🟢 -995 B
assets/auth-Bs3ufZBf.js (new) 3.4 kB 🔴 +3.4 kB 🔴 +1.18 kB 🔴 +991 B
assets/SignUpForm-BIod3vws.js (new) 3.01 kB 🔴 +3.01 kB 🔴 +1.24 kB 🔴 +1.1 kB
assets/SignUpForm-BSpae2Cf.js (removed) 3.01 kB 🟢 -3.01 kB 🟢 -1.23 kB 🟢 -1.09 kB
assets/UpdatePasswordContent-VZDaVzts.js (new) 2.45 kB 🔴 +2.45 kB 🔴 +1.11 kB 🔴 +978 B
assets/UpdatePasswordContent-L0JzGx5w.js (removed) 2.37 kB 🟢 -2.37 kB 🟢 -1.07 kB 🟢 -946 B
assets/firebaseAuthStore-C5LEd0Nt.js (new) 870 B 🔴 +870 B 🔴 +428 B 🔴 +377 B
assets/firebaseAuthStore-mq59gNxh.js (removed) 790 B 🟢 -790 B 🟢 -390 B 🟢 -345 B
assets/auth-B_MdxkbS.js (new) 347 B 🔴 +347 B 🔴 +222 B 🔴 +191 B
assets/auth-Dcy-z73C.js (removed) 347 B 🟢 -347 B 🟢 -221 B 🟢 -211 B
assets/WorkspaceProfilePic-B0BztYjc.js 1.57 kB 1.57 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 6 added / 6 removed

Editors & Dialogs — 818 B (baseline 3.47 kB) • 🟢 -2.65 kB

Modals, dialogs, drawers, and in-app editors

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useSubscriptionDialog-Pm320iRZ.js (removed) 2.68 kB 🟢 -2.68 kB 🟢 -1.32 kB 🟢 -1.18 kB
assets/useSubscriptionDialog-DEH6M60f.js (new) 818 B 🔴 +818 B 🔴 +419 B 🔴 +360 B
assets/useSubscriptionDialog-D-WmTKxp.js (removed) 783 B 🟢 -783 B 🟢 -389 B 🟢 -343 B

Status: 1 added / 2 removed

UI Components — 36.7 kB (baseline 37.8 kB) • 🟢 -1.14 kB

Reusable component library chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useTerminalTabs-DpSdm0IO.js (new) 9.98 kB 🔴 +9.98 kB 🔴 +3.44 kB 🔴 +3.03 kB
assets/useTerminalTabs-BkQJHR3C.js (removed) 9.9 kB 🟢 -9.9 kB 🟢 -3.41 kB 🟢 -3 kB
assets/TopbarBadge-BsZTG314.js (removed) 7.52 kB 🟢 -7.52 kB 🟢 -1.82 kB 🟢 -1.6 kB
assets/TopbarBadge-DcujSN0T.js (new) 7.52 kB 🔴 +7.52 kB 🔴 +1.82 kB 🔴 +1.61 kB
assets/ComfyQueueButton-BCot3PGl.js (removed) 7.13 kB 🟢 -7.13 kB 🟢 -2.31 kB 🟢 -2.06 kB
assets/ComfyQueueButton-DZX9ESVS.js (new) 7.13 kB 🔴 +7.13 kB 🔴 +2.31 kB 🔴 +2.06 kB
assets/SubscribeButton-DcXjUYdv.js (removed) 3.68 kB 🟢 -3.68 kB 🟢 -1.38 kB 🟢 -1.25 kB
assets/Button-ChqYCdrL.js (removed) 3 kB 🟢 -3 kB 🟢 -1.21 kB 🟢 -1.08 kB
assets/Button-v5wVyJNm.js (new) 3 kB 🔴 +3 kB 🔴 +1.21 kB 🔴 +1.07 kB
assets/SubscribeButton-CvN1w2xf.js (new) 2.31 kB 🔴 +2.31 kB 🔴 +1.01 kB 🔴 +879 B
assets/WidgetButton-CZlfmO15.js (new) 1.84 kB 🔴 +1.84 kB 🔴 +877 B 🔴 +773 B
assets/WidgetButton-DVfWCi_V.js (removed) 1.84 kB 🟢 -1.84 kB 🟢 -877 B 🟢 -776 B
assets/cloudFeedbackTopbarButton-zC4C6CSn.js (new) 1.68 kB 🔴 +1.68 kB 🔴 +892 B 🔴 +805 B
assets/cloudFeedbackTopbarButton-DQlwc7nC.js (removed) 1.6 kB 🟢 -1.6 kB 🟢 -857 B 🟢 -735 B
assets/CloudBadge-BqZRIK-4.js (new) 1.2 kB 🔴 +1.2 kB 🔴 +594 B 🔴 +510 B
assets/CloudBadge-nZ5YFAbJ.js (removed) 1.2 kB 🟢 -1.2 kB 🟢 -595 B 🟢 -512 B
assets/UserAvatar-C3Xd6Bkb.js (new) 1.17 kB 🔴 +1.17 kB 🔴 +622 B 🔴 +528 B
assets/UserAvatar-Dtdiv1Hu.js (removed) 1.17 kB 🟢 -1.17 kB 🟢 -619 B 🟢 -528 B
assets/ComfyQueueButton-Bi4tYoYE.js (new) 875 B 🔴 +875 B 🔴 +432 B 🔴 +385 B
assets/ComfyQueueButton-DlZg_exB.js (removed) 795 B 🟢 -795 B 🟢 -394 B 🟢 -352 B

Status: 10 added / 10 removed

Data & Services — 2.15 MB (baseline 2.1 MB) • 🔴 +51.3 kB

Stores, services, APIs, and repositories

File Before After Δ Raw Δ Gzip Δ Brotli
assets/dialogService-_maur7he.js (new) 1.38 MB 🔴 +1.38 MB 🔴 +312 kB 🔴 +242 kB
assets/dialogService-9SmI0wfa.js (removed) 1.3 MB 🟢 -1.3 MB 🟢 -298 kB 🟢 -232 kB
assets/api-CF5pxGYW.js (new) 641 kB 🔴 +641 kB 🔴 +144 kB 🔴 +116 kB
assets/api-7EHTuGQP.js (removed) 641 kB 🟢 -641 kB 🟢 -144 kB 🟢 -115 kB
assets/load3dService-DXIWW9oH.js (new) 91.2 kB 🔴 +91.2 kB 🔴 +19.1 kB 🔴 +16.4 kB
assets/load3dService-CQbDlxIr.js (removed) 91.1 kB 🟢 -91.1 kB 🟢 -19.1 kB 🟢 -16.4 kB
assets/teamWorkspaceStore-u-xjun7f.js (removed) 27.1 kB 🟢 -27.1 kB 🟢 -6.12 kB 🟢 -5.41 kB
assets/systemStatsStore-DpwWWYi1.js (removed) 12.3 kB 🟢 -12.3 kB 🟢 -4.29 kB 🟢 -3.77 kB
assets/systemStatsStore-DZitzjf4.js (new) 12.3 kB 🔴 +12.3 kB 🔴 +4.29 kB 🔴 +3.77 kB
assets/releaseStore-BhM59aKx.js (new) 8.14 kB 🔴 +8.14 kB 🔴 +2.23 kB 🔴 +1.97 kB
assets/releaseStore-DWAS8XnF.js (removed) 8.14 kB 🟢 -8.14 kB 🟢 -2.24 kB 🟢 -1.97 kB
assets/keybindingService-oR8W5ZJk.js (removed) 6.58 kB 🟢 -6.58 kB 🟢 -1.72 kB 🟢 -1.48 kB
assets/keybindingService-RqyNQ-Pf.js (new) 6.58 kB 🔴 +6.58 kB 🔴 +1.71 kB 🔴 +1.48 kB
assets/dialogStore-Bzw2tpu5.js (new) 4.1 kB 🔴 +4.1 kB 🔴 +1.24 kB 🔴 +1.09 kB
assets/dialogStore-D2gUGUn6.js (removed) 4.1 kB 🟢 -4.1 kB 🟢 -1.24 kB 🟢 -1.1 kB
assets/serverConfigStore-Cg1Zzouw.js (new) 2.32 kB 🔴 +2.32 kB 🔴 +789 B 🔴 +693 B
assets/serverConfigStore-DB58Z5s2.js (removed) 2.32 kB 🟢 -2.32 kB 🟢 -791 B 🟢 -690 B
assets/bootstrapStore-BddSu94k.js (removed) 2.13 kB 🟢 -2.13 kB 🟢 -880 B 🟢 -814 B
assets/bootstrapStore-E-qUEKCW.js (new) 2.13 kB 🔴 +2.13 kB 🔴 +881 B 🔴 +816 B
assets/userStore-CgWhHgOa.js (new) 1.85 kB 🔴 +1.85 kB 🔴 +722 B 🔴 +632 B
assets/userStore-D-p_SVm_.js (removed) 1.85 kB 🟢 -1.85 kB 🟢 -721 B 🟢 -676 B
assets/audioService-9bOdtrWS.js (new) 1.73 kB 🔴 +1.73 kB 🔴 +844 B 🔴 +730 B
assets/audioService-BouFc6he.js (removed) 1.73 kB 🟢 -1.73 kB 🟢 -846 B 🟢 -729 B
assets/releaseStore-BXXNbmH2.js (new) 842 B 🔴 +842 B 🔴 +424 B 🔴 +367 B
assets/releaseStore-FtDZWDtW.js (removed) 762 B 🟢 -762 B 🟢 -388 B 🟢 -334 B

Status: 12 added / 13 removed

Utilities & Hooks — 235 kB (baseline 234 kB) • 🔴 +279 B

Helpers, composables, and utility bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useConflictDetection-BsbcTtH3.js (new) 177 kB 🔴 +177 kB 🔴 +38.9 kB 🔴 +32.4 kB
assets/useConflictDetection-Cyk81ZLv.js (removed) 177 kB 🟢 -177 kB 🟢 -38.9 kB 🟢 -32.4 kB
assets/useLoad3d--JLfQ151.js (removed) 14.4 kB 🟢 -14.4 kB 🟢 -3.56 kB 🟢 -3.14 kB
assets/useLoad3d-DxsSw1gk.js (new) 14.4 kB 🔴 +14.4 kB 🔴 +3.56 kB 🔴 +3.14 kB
assets/useLoad3dViewer-BlKMRpl0.js (new) 14.2 kB 🔴 +14.2 kB 🔴 +3.16 kB 🔴 +2.8 kB
assets/useLoad3dViewer-DLGsls-S.js (removed) 14.2 kB 🟢 -14.2 kB 🟢 -3.16 kB 🟢 -2.8 kB
assets/colorUtil-CxDtjj8K.js (new) 7 kB 🔴 +7 kB 🔴 +2.14 kB 🔴 +1.89 kB
assets/colorUtil-FW_Ghng2.js (removed) 7 kB 🟢 -7 kB 🟢 -2.14 kB 🟢 -1.9 kB
assets/useErrorHandling-B-3BGL76.js (removed) 4.26 kB 🟢 -4.26 kB 🟢 -1.35 kB 🟢 -1.18 kB
assets/useWorkspaceUI-CTUia8rM.js (removed) 3.16 kB 🟢 -3.16 kB 🟢 -881 B 🟢 -761 B
assets/useWorkspaceUI-C8KQOoRW.js (new) 3.15 kB 🔴 +3.15 kB 🔴 +891 B 🔴 +766 B
assets/useFeatureFlags-B0YDmeNr.js (new) 3.07 kB 🔴 +3.07 kB 🔴 +962 B 🔴 +832 B
assets/useSubscriptionCredits-DfG5SkJ8.js (new) 2.71 kB 🔴 +2.71 kB 🔴 +1.03 kB 🔴 +896 B
assets/useSubscriptionActions-Cc97tziM.js (removed) 1.76 kB 🟢 -1.76 kB 🟢 -754 B 🟢 -653 B
assets/subscriptionCheckoutUtil-B-4Pt16A.js (new) 1.75 kB 🔴 +1.75 kB 🔴 +784 B 🔴 +684 B
assets/subscriptionCheckoutUtil-D--An4VW.js (removed) 1.75 kB 🟢 -1.75 kB 🟢 -784 B 🟢 -683 B
assets/useExternalLink-DWs-fjHA.js (removed) 1.63 kB 🟢 -1.63 kB 🟢 -756 B 🟢 -671 B
assets/useExternalLink-ISJjOy1p.js (new) 1.63 kB 🔴 +1.63 kB 🔴 +757 B 🔴 +671 B
assets/useCopyToClipboard-BCOSf9ef.js (removed) 1.57 kB 🟢 -1.57 kB 🟢 -669 B 🟢 -564 B
assets/useCopyToClipboard-MIfEa5zQ.js (new) 1.57 kB 🔴 +1.57 kB 🔴 +668 B 🔴 +553 B
assets/markdownRendererUtil-Cc2LFa5s.js (removed) 1.56 kB 🟢 -1.56 kB 🟢 -812 B 🟢 -694 B
assets/markdownRendererUtil-DhD9LPmW.js (new) 1.56 kB 🔴 +1.56 kB 🔴 +811 B 🔴 +700 B
assets/useErrorHandling-BOWKVFLU.js (new) 1.34 kB 🔴 +1.34 kB 🔴 +557 B 🔴 +478 B
assets/useSubscriptionCredits-08m0FdFr.js (removed) 1.07 kB 🟢 -1.07 kB 🟢 -499 B 🟢 -430 B
assets/networkUtil-Du2a_n7V.js (removed) 1.07 kB 🟢 -1.07 kB 🟢 -585 B 🟢 -476 B
assets/networkUtil-OoGWgngm.js (new) 1.07 kB 🔴 +1.07 kB 🔴 +587 B 🔴 +489 B
assets/useLoad3d-C6m-5gPM.js (new) 941 B 🔴 +941 B 🔴 +463 B 🔴 +413 B
assets/useLoad3dViewer-Biv-ZhmD.js (new) 920 B 🔴 +920 B 🔴 +449 B 🔴 +401 B
assets/useLoad3d-Dn8tD2IX.js (removed) 861 B 🟢 -861 B 🟢 -427 B 🟢 -381 B
assets/audioUtils-8ftIr8xG.js (removed) 858 B 🟢 -858 B 🟢 -498 B 🟢 -404 B
assets/audioUtils-D56qRMm6.js (new) 858 B 🔴 +858 B 🔴 +498 B 🔴 +420 B
assets/useLoad3dViewer-DXJ_mziW.js (removed) 840 B 🟢 -840 B 🟢 -410 B 🟢 -373 B
assets/useCurrentUser-C98CANcc.js (new) 804 B 🔴 +804 B 🔴 +414 B 🔴 +364 B
assets/useCurrentUser-BGbQUrcJ.js (removed) 724 B 🟢 -724 B 🟢 -372 B 🟢 -327 B
assets/_plugin-vue_export-helper-CAbbkOlw.js (removed) 315 B 🟢 -315 B 🟢 -230 B 🟢 -206 B
assets/_plugin-vue_export-helper-D53b894U.js (new) 315 B 🔴 +315 B 🔴 +230 B 🔴 +206 B
assets/SkeletonUtils-CWsb-x0f.js (new) 133 B 🔴 +133 B 🔴 +114 B 🔴 +110 B
assets/SkeletonUtils-DwBAnKr-.js (removed) 133 B 🟢 -133 B 🟢 -114 B 🟢 -112 B
assets/envUtil-Djb4sHrV.js 524 B 524 B ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 19 added / 19 removed

Vendor & Third-Party — 9.37 MB (baseline 9.37 MB) • ⚪ 0 B

External libraries and shared vendor chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/vendor-other-Dcr_8s0Y.js (new) 2.16 MB 🔴 +2.16 MB 🔴 +456 kB 🔴 +346 kB
assets/vendor-other-DPcKX1Hx.js (removed) 2.16 MB 🟢 -2.16 MB 🟢 -456 kB 🟢 -346 kB
assets/vendor-three-CJhOuqDg.js (removed) 1.8 MB 🟢 -1.8 MB 🟢 -386 kB 🟢 -280 kB
assets/vendor-three-Q97wQk05.js (new) 1.8 MB 🔴 +1.8 MB 🔴 +386 kB 🔴 +280 kB
assets/vendor-primevue-Dw8RivU1.js (removed) 1.74 MB 🟢 -1.74 MB 🟢 -313 kB 🟢 -190 kB
assets/vendor-primevue-DX2TTSWh.js (new) 1.74 MB 🔴 +1.74 MB 🔴 +313 kB 🔴 +190 kB
assets/vendor-tiptap-CvX7mojg.js (removed) 632 kB 🟢 -632 kB 🟢 -147 kB 🟢 -119 kB
assets/vendor-tiptap-eGUDVAYp.js (new) 632 kB 🔴 +632 kB 🔴 +147 kB 🔴 +119 kB
assets/vendor-chart-BVSddqq4.js (new) 399 kB 🔴 +399 kB 🔴 +95.8 kB 🔴 +79.5 kB
assets/vendor-chart-CIdI9Yl4.js (removed) 399 kB 🟢 -399 kB 🟢 -95.7 kB 🟢 -79.6 kB
assets/vendor-xterm-CZroAclV.js (new) 398 kB 🔴 +398 kB 🔴 +79.3 kB 🔴 +63.6 kB
assets/vendor-xterm-DOEEpxXk.js (removed) 398 kB 🟢 -398 kB 🟢 -79.3 kB 🟢 -63.5 kB
assets/vendor-markdown-BkVHYrKV.js (removed) 102 kB 🟢 -102 kB 🟢 -24.9 kB 🟢 -22 kB
assets/vendor-markdown-BTLyLMi2.js (new) 102 kB 🔴 +102 kB 🔴 +24.9 kB 🔴 +22 kB
assets/vendor-axios-D0w6WYH0.js 71.6 kB 71.6 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-firebase-CLEC0CcJ.js 842 kB 842 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-i18n-C6_f776o.js 132 kB 132 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-reka-ui-CPrt7lw0.js 240 kB 240 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-sentry-BVA5kbUC.js 183 kB 183 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-vue-core-BBP9kkTE.js 312 kB 312 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-vueuse-CS5mlw4Q.js 111 kB 111 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-yjs-BtrvIr1x.js 143 kB 143 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-zod-gAWmQIF-.js 110 kB 110 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 7 added / 7 removed

Other — 7.14 MB (baseline 7.1 MB) • 🔴 +47.2 kB

Bundles that do not match a named category

File Before After Δ Raw Δ Gzip Δ Brotli
assets/i18n-DX6wiaNS.js (new) 482 kB 🔴 +482 kB 🔴 +92.4 kB 🔴 +72 kB
assets/i18n-0PstQIzB.js (removed) 480 kB 🟢 -480 kB 🟢 -91.7 kB 🟢 -71.5 kB
assets/nodeDefs-Bjx0mgI7.js (removed) 442 kB 🟢 -442 kB 🟢 -67.1 kB 🟢 -46.4 kB
assets/nodeDefs-oC4LLwGi.js (new) 442 kB 🔴 +442 kB 🔴 +67.1 kB 🔴 +46.4 kB
assets/nodeDefs-DLQi6_4L.js (new) 406 kB 🔴 +406 kB 🔴 +61 kB 🔴 +42.9 kB
assets/nodeDefs-fPFi65f3.js (removed) 406 kB 🟢 -406 kB 🟢 -61 kB 🟢 -42.9 kB
assets/nodeDefs-BtRKM3M2.js (new) 405 kB 🔴 +405 kB 🔴 +63.1 kB 🔴 +44.5 kB
assets/nodeDefs-DDR1mCTO.js (removed) 405 kB 🟢 -405 kB 🟢 -63.1 kB 🟢 -44.5 kB
assets/nodeDefs-BSsCapVw.js (new) 375 kB 🔴 +375 kB 🔴 +61.1 kB 🔴 +42.8 kB
assets/nodeDefs-Ch6ZXz-h.js (removed) 375 kB 🟢 -375 kB 🟢 -61.1 kB 🟢 -42.7 kB
assets/nodeDefs-BCF6YMp3.js (removed) 365 kB 🟢 -365 kB 🟢 -59.9 kB 🟢 -42.5 kB
assets/nodeDefs-ZrZ0pB9x.js (new) 365 kB 🔴 +365 kB 🔴 +59.9 kB 🔴 +42.5 kB
assets/nodeDefs-4ZA9-QzB.js (removed) 361 kB 🟢 -361 kB 🟢 -58.6 kB 🟢 -42.8 kB
assets/nodeDefs-eHA8ICwM.js (new) 361 kB 🔴 +361 kB 🔴 +58.6 kB 🔴 +42.8 kB
assets/nodeDefs-DSY1ao6T.js (new) 361 kB 🔴 +361 kB 🔴 +60 kB 🔴 +43.6 kB
assets/nodeDefs-DyA4_B3a.js (removed) 361 kB 🟢 -361 kB 🟢 -59.9 kB 🟢 -43.6 kB
assets/nodeDefs-D18zvGhR.js (removed) 358 kB 🟢 -358 kB 🟢 -57.4 kB 🟢 -41.9 kB
assets/nodeDefs-DJgvVsx3.js (new) 358 kB 🔴 +358 kB 🔴 +57.4 kB 🔴 +41.9 kB
assets/nodeDefs-DT6NpDBI.js (removed) 354 kB 🟢 -354 kB 🟢 -56.5 kB 🟢 -41.3 kB
assets/nodeDefs-GqHfcoE5.js (new) 354 kB 🔴 +354 kB 🔴 +56.5 kB 🔴 +41.3 kB
assets/nodeDefs-_IXEWmm8.js (removed) 332 kB 🟢 -332 kB 🟢 -58.5 kB 🟢 -41.2 kB
assets/nodeDefs-DjqMN7vc.js (new) 332 kB 🔴 +332 kB 🔴 +58.5 kB 🔴 +41.2 kB
assets/nodeDefs-BS6wnxse.js (removed) 329 kB 🟢 -329 kB 🟢 -57.5 kB 🟢 -40 kB
assets/nodeDefs-ClO6R62B.js (new) 329 kB 🔴 +329 kB 🔴 +57.5 kB 🔴 +40 kB
assets/main-Cgqg3q6e.js (new) 180 kB 🔴 +180 kB 🔴 +47.6 kB 🔴 +38 kB
assets/main-CN7pSufO.js (removed) 180 kB 🟢 -180 kB 🟢 -47.6 kB 🟢 -38 kB
assets/main-CgjHOkrX.js (new) 161 kB 🔴 +161 kB 🔴 +42.9 kB 🔴 +35 kB
assets/main-Dt6bQoC3.js (removed) 161 kB 🟢 -161 kB 🟢 -42.9 kB 🟢 -35 kB
assets/main-5vUvgJMx.js (removed) 155 kB 🟢 -155 kB 🟢 -42.8 kB 🟢 -34.6 kB
assets/main-MY2-qMci.js (new) 155 kB 🔴 +155 kB 🔴 +42.8 kB 🔴 +34.6 kB
assets/main-B_En4SpT.js (removed) 148 kB 🟢 -148 kB 🟢 -42.6 kB 🟢 -34.4 kB
assets/main-DY2iV8sv.js (new) 148 kB 🔴 +148 kB 🔴 +42.6 kB 🔴 +34.4 kB
assets/main-BYoy9XSQ.js (removed) 136 kB 🟢 -136 kB 🟢 -41.3 kB 🟢 -35 kB
assets/main-DmB5sahX.js (new) 136 kB 🔴 +136 kB 🔴 +41.3 kB 🔴 +35 kB
assets/main-2HxSpwOp.js (removed) 133 kB 🟢 -133 kB 🟢 -40.8 kB 🟢 -33.4 kB
assets/main-ZndfQssq.js (new) 133 kB 🔴 +133 kB 🔴 +40.8 kB 🔴 +33.4 kB
assets/main-D5rAzkkP.js (new) 131 kB 🔴 +131 kB 🔴 +40.4 kB 🔴 +34 kB
assets/main-Dn8ME7jP.js (removed) 131 kB 🟢 -131 kB 🟢 -40.4 kB 🟢 -34 kB
assets/main-BuWOig7p.js (removed) 129 kB 🟢 -129 kB 🟢 -40.2 kB 🟢 -34 kB
assets/main-CUnKsvCy.js (new) 129 kB 🔴 +129 kB 🔴 +40.2 kB 🔴 +34 kB
assets/main-CqLSuwgg.js (new) 128 kB 🔴 +128 kB 🔴 +39.6 kB 🔴 +33.6 kB
assets/main-dc7AuxCr.js (removed) 128 kB 🟢 -128 kB 🟢 -39.6 kB 🟢 -33.6 kB
assets/main-C38gxHH8.js (new) 114 kB 🔴 +114 kB 🔴 +39.4 kB 🔴 +31.7 kB
assets/main-CKN_4E4Z.js (removed) 114 kB 🟢 -114 kB 🟢 -39.4 kB 🟢 -31.7 kB
assets/main-BpnmNj1n.js (removed) 113 kB 🟢 -113 kB 🟢 -39.4 kB 🟢 -31.5 kB
assets/main-D1jWNpGv.js (new) 113 kB 🔴 +113 kB 🔴 +39.4 kB 🔴 +31.5 kB
assets/core-DkkgKMTI.js (new) 71.6 kB 🔴 +71.6 kB 🔴 +18.4 kB 🔴 +15.8 kB
assets/core-BM-D68wz.js (removed) 71.4 kB 🟢 -71.4 kB 🟢 -18.4 kB 🟢 -15.8 kB
assets/groupNode-C8t7IizW.js (removed) 70.9 kB 🟢 -70.9 kB 🟢 -17.5 kB 🟢 -15.4 kB
assets/groupNode-DQLmi1Ki.js (new) 70.9 kB 🔴 +70.9 kB 🔴 +17.5 kB 🔴 +15.4 kB
assets/WidgetSelect-DP96gCFF.js (new) 56.7 kB 🔴 +56.7 kB 🔴 +12 kB 🔴 +10.4 kB
assets/WidgetSelect-Dgd1FI-v.js (removed) 56.6 kB 🟢 -56.6 kB 🟢 -12 kB 🟢 -10.4 kB
assets/SubscriptionRequiredDialogContentWorkspace-CcZl9EJP.js (new) 46.1 kB 🔴 +46.1 kB 🔴 +8.64 kB 🔴 +7.46 kB
assets/SettingDialogContent-DEezyb5k.js (removed) 30.8 kB 🟢 -30.8 kB 🟢 -8 kB 🟢 -7.05 kB
assets/Load3DControls-BUbKM3pY.js (removed) 30.8 kB 🟢 -30.8 kB 🟢 -5.33 kB 🟢 -4.63 kB
assets/Load3DControls-DLlMfZOJ.js (new) 30.8 kB 🔴 +30.8 kB 🔴 +5.33 kB 🔴 +4.65 kB
assets/SettingDialogContent-t5ZtKyzZ.js (new) 30.8 kB 🔴 +30.8 kB 🔴 +8.01 kB 🔴 +7.04 kB
assets/SubscriptionRequiredDialogContent-DLuSMEhl.js (new) 25.6 kB 🔴 +25.6 kB 🔴 +6.45 kB 🔴 +5.66 kB
assets/SubscriptionRequiredDialogContent-DkD0XxhY.js (removed) 25.4 kB 🟢 -25.4 kB 🟢 -6.36 kB 🟢 -5.58 kB
assets/Load3dViewerContent-DrfrPW9b.js (removed) 23.2 kB 🟢 -23.2 kB 🟢 -5.24 kB 🟢 -4.54 kB
assets/Load3dViewerContent-DSTpK8eO.js (new) 23.2 kB 🔴 +23.2 kB 🔴 +5.24 kB 🔴 +4.54 kB
assets/WidgetImageCrop-CoXHHmxF.js (new) 22.4 kB 🔴 +22.4 kB 🔴 +5.55 kB 🔴 +4.87 kB
assets/WidgetImageCrop-fw5biYXq.js (removed) 22.3 kB 🟢 -22.3 kB 🟢 -5.51 kB 🟢 -4.85 kB
assets/SubscriptionPanelContentWorkspace-B-m9z9eM.js (new) 21.8 kB 🔴 +21.8 kB 🔴 +5.19 kB 🔴 +4.59 kB
assets/CurrentUserPopoverWorkspace-DThBuMyM.js (new) 20.7 kB 🔴 +20.7 kB 🔴 +5.11 kB 🔴 +4.54 kB
assets/FormItem-9zDE7Ble.js (new) 20.2 kB 🔴 +20.2 kB 🔴 +4.87 kB 🔴 +4.24 kB
assets/FormItem-BnAJeA_N.js (removed) 20.2 kB 🟢 -20.2 kB 🟢 -4.87 kB 🟢 -4.24 kB
assets/CurrentUserPopoverWorkspace-DF9LGjXS.js (removed) 19.7 kB 🟢 -19.7 kB 🟢 -4.74 kB 🟢 -4.22 kB
assets/SignInContent-1c0zGs5S.js (new) 19.1 kB 🔴 +19.1 kB 🔴 +4.84 kB 🔴 +4.24 kB
assets/SignInContent-B35MPKl1.js (removed) 19 kB 🟢 -19 kB 🟢 -4.8 kB 🟢 -4.21 kB
assets/commands-D7Ez6YeH.js (removed) 17.9 kB 🟢 -17.9 kB 🟢 -3.84 kB 🟢 -3 kB
assets/commands-DpS8y4O5.js (new) 17.9 kB 🔴 +17.9 kB 🔴 +3.84 kB 🔴 +2.99 kB
assets/WidgetRecordAudio-BUyq6-1X.js (new) 17.3 kB 🔴 +17.3 kB 🔴 +4.96 kB 🔴 +4.44 kB
assets/WidgetRecordAudio-BMRt3gQd.js (removed) 17.2 kB 🟢 -17.2 kB 🟢 -4.93 kB 🟢 -4.41 kB
assets/TopUpCreditsDialogContent-pyD71Pkl.js (removed) 16.9 kB 🟢 -16.9 kB 🟢 -4.8 kB 🟢 -4.25 kB
assets/commands-B1ebAe6N.js (new) 16.7 kB 🔴 +16.7 kB 🔴 +3.54 kB 🔴 +2.75 kB
assets/commands-DuYqNMWX.js (removed) 16.7 kB 🟢 -16.7 kB 🟢 -3.54 kB 🟢 -2.75 kB
assets/commands-Ds7u8tBA.js (removed) 16.7 kB 🟢 -16.7 kB 🟢 -3.57 kB 🟢 -2.84 kB
assets/commands-FXC3_zxH.js (new) 16.7 kB 🔴 +16.7 kB 🔴 +3.57 kB 🔴 +2.84 kB
assets/commands-5h65sKz2.js (new) 16.2 kB 🔴 +16.2 kB 🔴 +3.64 kB 🔴 +2.89 kB
assets/commands-ovF76AYH.js (removed) 16.2 kB 🟢 -16.2 kB 🟢 -3.64 kB 🟢 -2.89 kB
assets/Load3D-DHbJQGmW.js (removed) 16.2 kB 🟢 -16.2 kB 🟢 -4.03 kB 🟢 -3.5 kB
assets/Load3D-DP4rYzeC.js (new) 16.2 kB 🔴 +16.2 kB 🔴 +4.03 kB 🔴 +3.5 kB
assets/MissingModelsWarning-CfyEHef8.js (new) 16.1 kB 🔴 +16.1 kB 🔴 +4.44 kB 🔴 +3.96 kB
assets/MissingModelsWarning-BCIwifWU.js (removed) 16.1 kB 🟢 -16.1 kB 🟢 -4.4 kB 🟢 -3.92 kB
assets/commands-BTIhwSf_.js (removed) 15.9 kB 🟢 -15.9 kB 🟢 -3.39 kB 🟢 -2.84 kB
assets/commands-SmCqaJk0.js (new) 15.9 kB 🔴 +15.9 kB 🔴 +3.39 kB 🔴 +2.84 kB
assets/SubscriptionPanelContentWorkspace-Bh_La5tZ.js (removed) 15.8 kB 🟢 -15.8 kB 🟢 -4.03 kB 🟢 -3.54 kB
assets/WidgetInputNumber-D-9YNiYt.js (new) 15.8 kB 🔴 +15.8 kB 🔴 +4.26 kB 🔴 +3.8 kB
assets/WidgetInputNumber-tToPLxp8.js (removed) 15.8 kB 🟢 -15.8 kB 🟢 -4.26 kB 🟢 -3.8 kB
assets/commands-CRx-vqG1.js (new) 15.4 kB 🔴 +15.4 kB 🔴 +3.37 kB 🔴 +2.82 kB
assets/commands-DFfk2Km5.js (removed) 15.4 kB 🟢 -15.4 kB 🟢 -3.37 kB 🟢 -2.83 kB
assets/commands-tMQq9SrH.js (removed) 15.4 kB 🟢 -15.4 kB 🟢 -3.28 kB 🟢 -2.67 kB
assets/commands-WU4257sl.js (new) 15.4 kB 🔴 +15.4 kB 🔴 +3.28 kB 🔴 +2.67 kB
assets/commands-BYGUQRX1.js (removed) 15.3 kB 🟢 -15.3 kB 🟢 -3.23 kB 🟢 -2.67 kB
assets/commands-DMlRtEU0.js (new) 15.3 kB 🔴 +15.3 kB 🔴 +3.23 kB 🔴 +2.67 kB
assets/commands-CtxsvHdf.js (removed) 15.2 kB 🟢 -15.2 kB 🟢 -3.5 kB 🟢 -2.75 kB
assets/commands-DIwahzUa.js (new) 15.2 kB 🔴 +15.2 kB 🔴 +3.5 kB 🔴 +2.75 kB
assets/load3d-CdU6yi-2.js (new) 14.9 kB 🔴 +14.9 kB 🔴 +4.22 kB 🔴 +3.68 kB
assets/load3d-a1oc6-Hh.js (removed) 14.8 kB 🟢 -14.8 kB 🟢 -4.18 kB 🟢 -3.65 kB
assets/commands-BxiMWv9a.js (removed) 14.6 kB 🟢 -14.6 kB 🟢 -3.42 kB 🟢 -2.56 kB
assets/commands-hWSmKRGf.js (new) 14.6 kB 🔴 +14.6 kB 🔴 +3.42 kB 🔴 +2.56 kB
assets/commands-D4xkLWYG.js (removed) 14.4 kB 🟢 -14.4 kB 🟢 -3.41 kB 🟢 -2.5 kB
assets/commands-j-tQgnMj.js (new) 14.4 kB 🔴 +14.4 kB 🔴 +3.4 kB 🔴 +2.5 kB
assets/LazyImage-Bir4uhOp.js (removed) 12.3 kB 🟢 -12.3 kB 🟢 -3.8 kB 🟢 -3.34 kB
assets/LazyImage-Ddanfrls.js (new) 12.3 kB 🔴 +12.3 kB 🔴 +3.79 kB 🔴 +3.34 kB
assets/AudioPreviewPlayer-B9VMQV11.js (new) 10.9 kB 🔴 +10.9 kB 🔴 +3.24 kB 🔴 +2.89 kB
assets/AudioPreviewPlayer-CCaWnOVy.js (removed) 10.9 kB 🟢 -10.9 kB 🟢 -3.2 kB 🟢 -2.86 kB
assets/NodeConflictDialogContent-BNN58QqO.js (new) 10.5 kB 🔴 +10.5 kB 🔴 +2.39 kB 🔴 +2.1 kB
assets/NodeConflictDialogContent-pRvuOb4U.js (removed) 10.5 kB 🟢 -10.5 kB 🟢 -2.37 kB 🟢 -2.08 kB
assets/nodeTemplates-BpN3Qufs.js (new) 9.41 kB 🔴 +9.41 kB 🔴 +3.31 kB 🔴 +2.91 kB
assets/nodeTemplates-Dx8Vn6YL.js (removed) 9.33 kB 🟢 -9.33 kB 🟢 -3.27 kB 🟢 -2.88 kB
assets/SelectValue-1KJOqzqF.js (removed) 8.94 kB 🟢 -8.94 kB 🟢 -2.27 kB 🟢 -1.99 kB
assets/SelectValue-CH_x0KmM.js (new) 8.94 kB 🔴 +8.94 kB 🔴 +2.27 kB 🔴 +1.99 kB
assets/InviteMemberDialogContent-BKo1Oohj.js (new) 7.99 kB 🔴 +7.99 kB 🔴 +2.59 kB 🔴 +2.25 kB
assets/InviteMemberDialogContent-Dh_qcQkX.js (removed) 7.95 kB 🟢 -7.95 kB 🟢 -2.57 kB 🟢 -2.22 kB
assets/WidgetWithControl-D7X6HLCr.js (new) 7.04 kB 🔴 +7.04 kB 🔴 +2.63 kB 🔴 +2.36 kB
assets/WidgetWithControl-_HbqmUtr.js (removed) 6.97 kB 🟢 -6.97 kB 🟢 -2.59 kB 🟢 -2.32 kB
assets/Load3DConfiguration-CqwgsOQZ.js (new) 6.36 kB 🔴 +6.36 kB 🔴 +1.93 kB 🔴 +1.69 kB
assets/Load3DConfiguration-D7iBFAe5.js (removed) 6.36 kB 🟢 -6.36 kB 🟢 -1.92 kB 🟢 -1.69 kB
assets/MissingNodesContent-6VyFOYnB.js (new) 6.18 kB 🔴 +6.18 kB 🔴 +2.11 kB 🔴 +1.87 kB
assets/MissingNodesContent-C7X5SzHK.js (removed) 6.14 kB 🟢 -6.14 kB 🟢 -2.09 kB 🟢 -1.87 kB
assets/CreateWorkspaceDialogContent-BdPTBXbP.js (new) 5.61 kB 🔴 +5.61 kB 🔴 +2.03 kB 🔴 +1.79 kB
assets/CreateWorkspaceDialogContent-snzMBivf.js (removed) 5.58 kB 🟢 -5.58 kB 🟢 -2 kB 🟢 -1.74 kB
assets/EditWorkspaceDialogContent-DsLsYHKN.js (new) 5.42 kB 🔴 +5.42 kB 🔴 +1.99 kB 🔴 +1.72 kB
assets/EditWorkspaceDialogContent-BKBEkZsx.js (removed) 5.38 kB 🟢 -5.38 kB 🟢 -1.96 kB 🟢 -1.71 kB
assets/ValueControlPopover-BimH0sl0.js (new) 5 kB 🔴 +5 kB 🔴 +1.81 kB 🔴 +1.61 kB
assets/ValueControlPopover-BFj7wcD8.js (removed) 4.92 kB 🟢 -4.92 kB 🟢 -1.78 kB 🟢 -1.58 kB
assets/Preview3d-DlFJN8KI.js (new) 4.89 kB 🔴 +4.89 kB 🔴 +1.61 kB 🔴 +1.4 kB
assets/CancelSubscriptionDialogContent-BwEPJrr9.js (new) 4.88 kB 🔴 +4.88 kB 🔴 +1.82 kB 🔴 +1.59 kB
assets/MissingNodesFooter-BVXQEiqD.js (new) 4.84 kB 🔴 +4.84 kB 🔴 +1.8 kB 🔴 +1.6 kB
assets/Preview3d-BrC06JlD.js (removed) 4.82 kB 🟢 -4.82 kB 🟢 -1.57 kB 🟢 -1.36 kB
assets/MissingNodesFooter-INS5FJaH.js (removed) 4.8 kB 🟢 -4.8 kB 🟢 -1.78 kB 🟢 -1.58 kB
assets/AnimationControls-bPyINTJI.js (removed) 4.61 kB 🟢 -4.61 kB 🟢 -1.6 kB 🟢 -1.41 kB
assets/AnimationControls-CQITeygt.js (new) 4.61 kB 🔴 +4.61 kB 🔴 +1.61 kB 🔴 +1.41 kB
assets/DeleteWorkspaceDialogContent-CVEx1qj6.js (new) 4.32 kB 🔴 +4.32 kB 🔴 +1.67 kB 🔴 +1.45 kB
assets/DeleteWorkspaceDialogContent-_OkqUp95.js (removed) 4.28 kB 🟢 -4.28 kB 🟢 -1.65 kB 🟢 -1.43 kB
assets/LeaveWorkspaceDialogContent-Bm1NIRf8.js (new) 4.15 kB 🔴 +4.15 kB 🔴 +1.62 kB 🔴 +1.4 kB
assets/RemoveMemberDialogContent-BrNblLdK.js (new) 4.13 kB 🔴 +4.13 kB 🔴 +1.56 kB 🔴 +1.36 kB
assets/LeaveWorkspaceDialogContent-BwYLn7bK.js (removed) 4.11 kB 🟢 -4.11 kB 🟢 -1.59 kB 🟢 -1.38 kB
assets/RemoveMemberDialogContent-8Tq55Y_d.js (removed) 4.09 kB 🟢 -4.09 kB 🟢 -1.54 kB 🟢 -1.34 kB
assets/RevokeInviteDialogContent-DNTENpVv.js (new) 4.04 kB 🔴 +4.04 kB 🔴 +1.58 kB 🔴 +1.39 kB
assets/RevokeInviteDialogContent-BGC99qW6.js (removed) 4 kB 🟢 -4 kB 🟢 -1.55 kB 🟢 -1.36 kB
assets/WidgetBoundingBox-2w5BjjHm.js (removed) 3.94 kB 🟢 -3.94 kB 🟢 -996 B 🟢 -862 B
assets/WidgetBoundingBox-q5CDrAhg.js (new) 3.94 kB 🔴 +3.94 kB 🔴 +994 B 🔴 +859 B
assets/WidgetGalleria-C8ikSQqn.js (removed) 3.61 kB 🟢 -3.61 kB 🟢 -1.39 kB 🟢 -1.25 kB
assets/WidgetGalleria-k7EQgXzH.js (new) 3.61 kB 🔴 +3.61 kB 🔴 +1.4 kB 🔴 +1.25 kB
assets/Slider-Bh_ZdHn9.js (removed) 3.52 kB 🟢 -3.52 kB 🟢 -1.36 kB 🟢 -1.19 kB
assets/Slider-DFNKKaWT.js (new) 3.52 kB 🔴 +3.52 kB 🔴 +1.36 kB 🔴 +1.19 kB
assets/saveMesh-Cgp9YBzs.js (new) 3.46 kB 🔴 +3.46 kB 🔴 +1.49 kB 🔴 +1.32 kB
assets/saveMesh-BGVzMHF6.js (removed) 3.38 kB 🟢 -3.38 kB 🟢 -1.45 kB 🟢 -1.29 kB
assets/cloudSessionCookie-Pyzb-yH6.js (new) 3.15 kB 🔴 +3.15 kB 🔴 +1.11 kB 🔴 +985 B
assets/WidgetTextarea-CGhBRfY6.js (new) 3.14 kB 🔴 +3.14 kB 🔴 +1.3 kB 🔴 +1.14 kB
assets/WidgetTextarea-D07MktMK.js (removed) 3.14 kB 🟢 -3.14 kB 🟢 -1.3 kB 🟢 -1.14 kB
assets/WidgetImageCompare-B1RSu0_k.js (removed) 3.1 kB 🟢 -3.1 kB 🟢 -1.15 kB 🟢 -1.02 kB
assets/WidgetImageCompare-D8fnwO5D.js (new) 3.1 kB 🔴 +3.1 kB 🔴 +1.15 kB 🔴 +1.01 kB
assets/cloudSessionCookie-m6dX4uAQ.js (removed) 3.07 kB 🟢 -3.07 kB 🟢 -1.08 kB 🟢 -957 B
assets/GlobalToast-y_R2Bgeq.js (new) 2.91 kB 🔴 +2.91 kB 🔴 +1.21 kB 🔴 +1.07 kB
assets/WidgetColorPicker-Bkv-1jaL.js (new) 2.9 kB 🔴 +2.9 kB 🔴 +1.23 kB 🔴 +1.11 kB
assets/WidgetColorPicker-jDbRm4n6.js (removed) 2.9 kB 🟢 -2.9 kB 🟢 -1.23 kB 🟢 -1.11 kB
assets/WidgetMarkdown-CkBsDVy-.js (new) 2.88 kB 🔴 +2.88 kB 🔴 +1.22 kB 🔴 +1.08 kB
assets/WidgetMarkdown-wePHc2fh.js (removed) 2.88 kB 🟢 -2.88 kB 🟢 -1.22 kB 🟢 -1.07 kB
assets/ApiNodesSignInContent-B7254Sdw.js (new) 2.69 kB 🔴 +2.69 kB 🔴 +1.05 kB 🔴 +916 B
assets/ApiNodesSignInContent-SdEmJCMH.js (removed) 2.69 kB 🟢 -2.69 kB 🟢 -1.05 kB 🟢 -919 B
assets/WidgetToggleSwitch-CoKD027B.js (removed) 2.5 kB 🟢 -2.5 kB 🟢 -1.09 kB 🟢 -990 B
assets/WidgetToggleSwitch-W_gO1Bmm.js (new) 2.5 kB 🔴 +2.5 kB 🔴 +1.09 kB 🔴 +998 B
assets/ImportFailedNodeContent-kBLlIN_-.js (new) 2.48 kB 🔴 +2.48 kB 🔴 +972 B 🔴 +829 B
assets/ImportFailedNodeContent-VPjQzi79.js (removed) 2.48 kB 🟢 -2.48 kB 🟢 -974 B 🟢 -828 B
assets/NodeConflictFooter-Dh-VROp2.js (removed) 2.37 kB 🟢 -2.37 kB 🟢 -1.03 kB 🟢 -899 B
assets/NodeConflictFooter-wHIF58ml.js (new) 2.37 kB 🔴 +2.37 kB 🔴 +1.03 kB 🔴 +909 B
assets/GlobalToast-BeqkcP-w.js (removed) 2.34 kB 🟢 -2.34 kB 🟢 -962 B 🟢 -800 B
assets/MediaVideoTop-CQw3f2T6.js (removed) 2.23 kB 🟢 -2.23 kB 🟢 -937 B 🟢 -800 B
assets/MediaVideoTop-MY4C12Xl.js (new) 2.23 kB 🔴 +2.23 kB 🔴 +936 B 🔴 +798 B
assets/WidgetChart-CCWIsKdb.js (new) 2.21 kB 🔴 +2.21 kB 🔴 +951 B 🔴 +821 B
assets/WidgetChart-Dsp6bf_j.js (removed) 2.21 kB 🟢 -2.21 kB 🟢 -948 B 🟢 -821 B
assets/SubscribeToRun-DIT2idN4.js (new) 2.16 kB 🔴 +2.16 kB 🔴 +997 B 🔴 +882 B
assets/SubscribeToRun-P00x3VuU.js (removed) 2.16 kB 🟢 -2.16 kB 🟢 -990 B 🟢 -870 B
assets/WidgetLayoutField-BcUSnWUK.js (removed) 1.95 kB 🟢 -1.95 kB 🟢 -879 B 🟢 -763 B
assets/WidgetLayoutField-DYh1nH56.js (new) 1.95 kB 🔴 +1.95 kB 🔴 +881 B 🔴 +762 B
assets/ImportFailedNodeFooter-D_BxHHK1.js (removed) 1.88 kB 🟢 -1.88 kB 🟢 -866 B 🟢 -752 B
assets/ImportFailedNodeFooter-Dg7qXJCa.js (new) 1.88 kB 🔴 +1.88 kB 🔴 +868 B 🔴 +750 B
assets/WidgetInputText-DIuOYOkU.js (new) 1.85 kB 🔴 +1.85 kB 🔴 +870 B 🔴 +810 B
assets/WidgetInputText-MXN3EUoN.js (removed) 1.85 kB 🟢 -1.85 kB 🟢 -872 B 🟢 -795 B
assets/SettingDialogHeader-C2zHC3of.js (new) 1.85 kB 🔴 +1.85 kB 🔴 +906 B 🔴 +830 B
assets/Media3DTop-68BNrAjP.js (new) 1.82 kB 🔴 +1.82 kB 🔴 +897 B 🔴 +767 B
assets/Media3DTop-DTYkrVtB.js (removed) 1.82 kB 🟢 -1.82 kB 🟢 -897 B 🟢 -767 B
assets/CloudRunButtonWrapper-vD_abjmi.js (new) 1.76 kB 🔴 +1.76 kB 🔴 +827 B 🔴 +757 B
assets/BaseViewTemplate-BnXUotno.js (removed) 1.75 kB 🟢 -1.75 kB 🟢 -910 B 🟢 -799 B
assets/BaseViewTemplate-CUITYRai.js (new) 1.75 kB 🔴 +1.75 kB 🔴 +912 B 🔴 +826 B
assets/MediaImageTop-ATOoJCQr.js (removed) 1.75 kB 🟢 -1.75 kB 🟢 -879 B 🟢 -750 B
assets/MediaImageTop-Ci7hK6Hx.js (new) 1.75 kB 🔴 +1.75 kB 🔴 +879 B 🔴 +750 B
assets/auto-DjoqfnTX.js (removed) 1.7 kB 🟢 -1.7 kB 🟢 -622 B 🟢 -552 B
assets/auto-DXvetOUE.js (new) 1.7 kB 🔴 +1.7 kB 🔴 +620 B 🔴 +570 B
assets/CloudRunButtonWrapper-H5A44p3r.js (removed) 1.67 kB 🟢 -1.67 kB 🟢 -781 B 🟢 -707 B
assets/SettingDialogHeader-BHt-6Ao_.js (removed) 1.62 kB 🟢 -1.62 kB 🟢 -810 B 🟢 -719 B
assets/signInSchema-BTV3Zegh.js (new) 1.53 kB 🔴 +1.53 kB 🔴 +563 B 🔴 +526 B
assets/signInSchema-DmfohtOF.js (removed) 1.53 kB 🟢 -1.53 kB 🟢 -562 B 🟢 -518 B
assets/cloudBadges-BDpb7emG.js (new) 1.45 kB 🔴 +1.45 kB 🔴 +740 B 🔴 +645 B
assets/MediaAudioTop-AFmsiWi3.js (removed) 1.43 kB 🟢 -1.43 kB 🟢 -759 B 🟢 -632 B
assets/MediaAudioTop-Iq5PlzyI.js (new) 1.43 kB 🔴 +1.43 kB 🔴 +761 B 🔴 +633 B
assets/cloudSubscription-DmYse79v.js (new) 1.41 kB 🔴 +1.41 kB 🔴 +696 B 🔴 +600 B
assets/cloudBadges-CWO9dEw-.js (removed) 1.37 kB 🟢 -1.37 kB 🟢 -701 B 🟢 -612 B
assets/cloudSubscription-i7pEWEr0.js (removed) 1.32 kB 🟢 -1.32 kB 🟢 -652 B 🟢 -562 B
assets/PanelTemplate-BSPIeLqH.js (new) 1.2 kB 🔴 +1.2 kB 🔴 +615 B 🔴 +541 B
assets/PanelTemplate-C1h0RxBM.js (removed) 1.2 kB 🟢 -1.2 kB 🟢 -612 B 🟢 -539 B
assets/Load3D-Ctpae6PR.js (new) 1.12 kB 🔴 +1.12 kB 🔴 +524 B 🔴 +463 B
assets/widgetPropFilter-DyQHILEf.js (removed) 1.1 kB 🟢 -1.1 kB 🟢 -509 B 🟢 -448 B
assets/widgetPropFilter-HMrZZpDm.js (new) 1.1 kB 🔴 +1.1 kB 🔴 +509 B 🔴 +449 B
assets/MissingNodesHeader-BOlbqJ0O.js (removed) 1.09 kB 🟢 -1.09 kB 🟢 -580 B 🟢 -501 B
assets/MissingNodesHeader-YwveLyUp.js (new) 1.09 kB 🔴 +1.09 kB 🔴 +579 B 🔴 +503 B
assets/NodeConflictHeader-DRxQs5TH.js (removed) 1.09 kB 🟢 -1.09 kB 🟢 -565 B 🟢 -478 B
assets/NodeConflictHeader-y4bZqYin.js (new) 1.09 kB 🔴 +1.09 kB 🔴 +567 B 🔴 +477 B
assets/nightlyBadges-uJOAS0U8.js (new) 1.08 kB 🔴 +1.08 kB 🔴 +573 B 🔴 +509 B
assets/ImportFailedNodeHeader-C9K0iSCZ.js (new) 1.08 kB 🔴 +1.08 kB 🔴 +553 B 🔴 +473 B
assets/ImportFailedNodeHeader-DUns4_x2.js (removed) 1.08 kB 🟢 -1.08 kB 🟢 -550 B 🟢 -471 B
assets/SubscriptionPanelContentWorkspace-DMZ3tPiu.js (removed) 1.07 kB 🟢 -1.07 kB 🟢 -479 B 🟢 -418 B
assets/Load3dViewerContent-CUK0ah3E.js (new) 1.04 kB 🔴 +1.04 kB 🔴 +491 B 🔴 +434 B
assets/Load3D-DRRU1QUy.js (removed) 1.04 kB 🟢 -1.04 kB 🟢 -485 B 🟢 -431 B
assets/SubscriptionPanelContentWorkspace-BBiFXFJa.js (new) 1.01 kB 🔴 +1.01 kB 🔴 +476 B 🔴 +415 B
assets/nightlyBadges-DmY3YJpU.js (removed) 1 kB 🟢 -1 kB 🟢 -537 B 🟢 -475 B
assets/Load3dViewerContent-B-PGladc.js (removed) 961 B 🟢 -961 B 🟢 -457 B 🟢 -410 B
assets/SettingDialogContent-BeCNrGjo.js (new) 954 B 🔴 +954 B 🔴 +460 B 🔴 +405 B
assets/SettingDialogContent-CDodO9RO.js (removed) 917 B 🟢 -917 B 🟢 -442 B 🟢 -389 B
assets/ComfyOrgHeader-32ZIoIYN.js (removed) 909 B 🟢 -909 B 🟢 -495 B 🟢 -434 B
assets/ComfyOrgHeader-Cb9w2YJJ.js (new) 909 B 🔴 +909 B 🔴 +496 B 🔴 +434 B
assets/WidgetLegacy-B8euFomV.js (new) 827 B 🔴 +827 B 🔴 +424 B 🔴 +373 B
assets/graphHasMissingNodes-CIUysw1W.js (removed) 761 B 🟢 -761 B 🟢 -376 B 🟢 -328 B
assets/graphHasMissingNodes-CTXf7egm.js (new) 761 B 🔴 +761 B 🔴 +375 B 🔴 +326 B
assets/WidgetLegacy-t9yZZZku.js (removed) 747 B 🟢 -747 B 🟢 -387 B 🟢 -338 B
assets/previousFullPath-B7z583a8.js (new) 665 B 🔴 +665 B 🔴 +368 B 🔴 +320 B
assets/previousFullPath-DsBk66MD.js (removed) 665 B 🟢 -665 B 🟢 -368 B 🟢 -321 B
assets/SettingDialogHeader-lScpbcxe.js (new) 412 B 🔴 +412 B 🔴 +238 B 🔴 +232 B
assets/widgetTypes-BCsKJASV.js (removed) 393 B 🟢 -393 B 🟢 -258 B 🟢 -234 B
assets/widgetTypes-CYQJBz0f.js (new) 393 B 🔴 +393 B 🔴 +260 B 🔴 +245 B
assets/WidgetInputNumber-BM486kQJ.js (new) 392 B 🔴 +392 B 🔴 +229 B 🔴 +210 B
assets/WidgetInputNumber-DwJ042Vx.js (removed) 392 B 🟢 -392 B 🟢 -229 B 🟢 -194 B
assets/src-Cn28aUma.js (new) 251 B 🔴 +251 B 🔴 +208 B 🔴 +188 B
assets/src-DAMctjF_.js (removed) 251 B 🟢 -251 B 🟢 -210 B 🟢 -177 B
assets/SettingDialogHeader-D5wathvf.js (removed) 244 B 🟢 -244 B 🟢 -165 B 🟢 -141 B
assets/i18n-5jgb-x6k.js (new) 199 B 🔴 +199 B 🔴 +162 B 🔴 +142 B
assets/i18n-BHeCxExo.js (removed) 199 B 🟢 -199 B 🟢 -161 B 🟢 -139 B
assets/comfy-logo-single-CzGozBag.js (new) 198 B 🔴 +198 B 🔴 +161 B 🔴 +126 B
assets/comfy-logo-single-DCVL1uOL.js (removed) 198 B 🟢 -198 B 🟢 -160 B 🟢 -126 B
assets/WidgetBoundingBox-Djm16aYN.js (removed) 131 B 🟢 -131 B 🟢 -100 B 🟢 -104 B
assets/WidgetBoundingBox-DNUpZ6Xj.js (new) 131 B 🔴 +131 B 🔴 +100 B 🔴 +91 B
assets/mixpanel.module-Dq6XmUX_.js (removed) 92 B 🟢 -92 B 🟢 -100 B 🟢 -81 B
assets/mixpanel.module-hpuqJDVF.js (new) 92 B 🔴 +92 B 🔴 +100 B 🔴 +86 B
assets/OBJLoader2WorkerModule-DTMpvldF.js 109 kB 109 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/rolldown-runtime-Ca2S-reV.js 1.87 kB 1.87 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/types-VDank0gz.js 318 B 318 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widget-RLUqfB5N.js 445 B 445 B ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 124 added / 123 removed

@simula-r simula-r added the preview-cpu Creates a preview ephemeral environment for this PR (CPU only) label Feb 4, 2026
@simula-r simula-r force-pushed the feat/workspaces-6-billing branch from f3086f2 to fe13e86 Compare February 4, 2026 05:10
coderabbitai[bot]

This comment was marked as duplicate.

coderabbitai[bot]

This comment was marked as outdated.

@simula-r simula-r force-pushed the feat/workspaces-6-billing branch from bed3cd5 to 6d9dc29 Compare February 5, 2026 04:58
coderabbitai[bot]

This comment was marked as outdated.

@simula-r simula-r force-pushed the feat/workspaces-6-billing branch from 4796688 to ab70024 Compare February 5, 2026 06:18
@simula-r simula-r marked this pull request as ready for review February 5, 2026 06:19
@simula-r simula-r requested review from a team and pythongosssss as code owners February 5, 2026 06:19
@dosubot dosubot bot added the size:XXL This PR changes 1000+ lines, ignoring generated files. label Feb 5, 2026
coderabbitai[bot]

This comment was marked as outdated.

@simula-r simula-r added needs-backport Fix/change that needs to be cherry-picked to the current feature freeze branch cloud/1.37 Backport PRs for cloud 1.37 cloud/1.38 Backport PRs for cloud 1.38 labels Feb 5, 2026
coderabbitai[bot]

This comment was marked as outdated.

simula-r and others added 6 commits February 5, 2026 21:27
- Update useBillingContext.test.ts to include new balance fields
  (prepaidBalanceMicros, cloudCreditBalanceMicros)
- Add useBillingContext mock to useCoreCommands.test.ts
- Add useBillingContext mock to CloudSubscriptionRedirectView.test.ts
- Add useBillingContext mock to useSubscriptionActions.test.ts
- Add useBillingContext mock to SubscriptionPanel.test.ts

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…cription

The useSubscription() return object was being assigned directly to
isSubscriptionEnabled, causing the ternary to always evaluate as truthy
(object truthiness) instead of the actual subscription state.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@simula-r simula-r force-pushed the feat/workspaces-6-billing branch from f33962c to 9e962ac Compare February 6, 2026 05:28
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/platform/cloud/subscription/composables/useSubscriptionActions.ts (1)

48-54: ⚠️ Potential issue | 🟡 Minor

Inconsistent balance fetching: use useBillingContext.fetchBalance() instead of authActions.fetchBalance()

Both fetchStatus() and fetchBalance() are intentionally called (confirmed by tests), as they fetch different data (subscription status vs. account balance). However, handleRefresh calls authActions.fetchBalance() directly while using useBillingContext().fetchStatus() for status. This creates an architectural inconsistency: when workspace billing is active, fetchStatus() respects the context but fetchBalance() always uses the Firebase/legacy path, potentially ignoring workspace-specific balance. Refactor to destructure and use fetchBalance from useBillingContext (like billingOperationStore.ts does) to ensure both calls respect the active context.

src/platform/workspace/stores/teamWorkspaceStore.ts (1)

606-609: ⚠️ Potential issue | 🟡 Minor

Remove or update the unused subscribeWorkspace function—the TODO is stale and billing infrastructure is now fully implemented.

The function is exported but never called anywhere in the codebase. Its console.warn body claims "Billing endpoint has not been added yet," which is outdated; workspace billing is fully implemented via useWorkspaceBilling() and the /api/billing/subscribe endpoint. Either remove the unused export or integrate it with the existing billing infrastructure.

🤖 Fix all issues with AI agents
In `@src/components/dialog/content/TopUpCreditsDialogContentWorkspace.vue`:
- Around line 215-217: The helper function formatNumber currently hardcodes the
'en-US' locale; replace its usage with the i18n number formatter (n) from
useI18n to respect the app locale: remove or stop using formatNumber and update
template calls like formatNumber(usdToCredits(MIN_AMOUNT)) and
formatNumber(usdToCredits(MAX_AMOUNT)) to use n(usdToCredits(MIN_AMOUNT)) and
n(usdToCredits(MAX_AMOUNT)); if formatNumber is kept, refactor it to call n
internally (ensure useI18n's n is imported/available) and remove the hardcoded
toLocaleString('en-US') call.

In `@src/composables/billing/useBillingContext.ts`:
- Around line 159-174: The early-return guard in initialize() is ineffective
because the watcher force-sets isInitialized.value = false, allowing concurrent
calls to pass the guard; fix by introducing single-flight behavior: add an
initializingPromise (or isInitializing flag) scoped alongside
isInitialized/isLoading and inside initialize() if initializingPromise exists
return/await it, otherwise set initializingPromise =
activeContext.value.initialize() and await it, then clear initializingPromise
and set isInitialized.value = true; reference initialize(), isInitialized,
isLoading, activeContext.value.initialize to locate where to add the
promise/flag and where to clear it in the try/catch/finally.
- Around line 134-157: The async watch callback in useBillingContext (watch on
store.activeWorkspace?.id) can run multiple concurrent initialize() calls and
race on shared refs (isInitialized, isLoading, error); add a lightweight
generation token or abort counter in the composable (e.g.,
billingInitGeneration) that you increment before each call, capture into a local
variable inside the watch, and have initialize() or the post-await state
mutations check the captured token against the current billingInitGeneration so
only the latest invocation mutates refs; ensure errors are still assigned to
error ref only when the generation matches to avoid stale state from
earlier/faster completions.

In `@src/locales/en/main.json`:
- Around line 2192-2194: There is a duplicate "invoiceHistory" key inside the
subscription JSON object; remove the redundant "invoiceHistory" entry so only a
single definition of "invoiceHistory" remains in that object (keep the
intended/accurate translation), and run your locale sanity checks (or tests) to
ensure no other duplicates exist and that consumers still read the expected
value.

In `@src/platform/cloud/subscription/components/PricingTableWorkspace.vue`:
- Around line 439-441: The popover ref may be null when the <Popover> hasn't
rendered, so update the togglePopover handler to guard before calling
popover.value.toggle(event); e.g. check popover.value exists (or use optional
chaining popover.value?.toggle(event)) and keep the same signature for
togglePopover; reference the popover ref and the togglePopover function to
ensure the runtime null-check prevents calling .toggle() on undefined.

In
`@src/platform/cloud/subscription/components/SubscriptionPanelContentWorkspace.vue`:
- Around line 628-631: The onMounted fire-and-forget Promise.all([fetchStatus(),
fetchBalance()]) swallows errors; update the onMounted handler (where
fetchStatus and fetchBalance are invoked, alongside handleWindowFocus) to attach
a .catch that handles failures — e.g., set a component error state or call the
existing toast/error helper to surface the error and optionally retry; ensure
the .catch distinguishes which call failed if needed and preserves existing
focus listener cleanup.

In
`@src/platform/cloud/subscription/components/SubscriptionRequiredDialogContentWorkspace.vue`:
- Around line 291-314: The handler handleResubscribe uses hardcoded user strings
and calls workspaceApi.resubscribe() directly; replace the hardcoded 'Error' and
'Failed to resubscribe' with vue-i18n lookups (e.g. use t('common.error') for
the toast summary and a new/appropriate key such as
t('subscription.resubscribeFailed') for the detail) and update the toast.add
calls to use those keys, and verify/replace the direct API call with the billing
context method (e.g. use billingContext.subscribe or the equivalent resubscribe
helper) if the billing context supports resubscription; if it does not, leave
the resubscribe call but add a comment explaining why billingContext was not
used.

In `@src/platform/workspace/api/workspaceApi.ts`:
- Around line 594-614: The subscribe method is missing support for idempotency
keys; update async subscribe(planSlug: string, returnUrl?: string, cancelUrl?:
string) to accept an optional idempotencyKey?: string parameter and include
idempotency_key: idempotencyKey in the request payload (alongside plan_slug,
return_url, cancel_url) so the body satisfies SubscribeRequest; keep
getAuthHeaderOrThrow usage and error handling the same.
🧹 Nitpick comments (19)
src/components/dialog/content/subscription/CancelSubscriptionDialogContent.vue (2)

55-57: Use reactive prop destructuring per project conventions.

The coding guidelines prescribe const { prop1 } = defineProps<...>() style. Since cancelAt is only used in one place (line 67), destructuring keeps it consistent with the codebase convention.

♻️ Suggested refactor
-const props = defineProps<{
-  cancelAt?: string
-}>()
+const { cancelAt } = defineProps<{
+  cancelAt?: string
+}>()

And update the usage on line 67:

-  const dateStr = props.cancelAt ?? subscription.value?.endDate
+  const dateStr = cancelAt ?? subscription.value?.endDate

As per coding guidelines: const { prop1, prop2 = default } = defineProps<{ prop1: Type; prop2?: Type }>() — use reactive prop destructuring, not withDefaults or runtime props.


12-19: Consider using IconButton instead of a plain <button> for the close action.

The repo convention is to prefer common button components from src/components/button/ (e.g., IconButton.vue) over plain <button> elements for consistent theming. This is a minor nit since the styling here is self-contained and appropriate for a dialog close button.

Based on learnings: prefer the repo's common button components from src/components/button/ over plain HTML <button> elements for consistency and theming.

src/components/dialog/header/SettingDialogHeader.vue (1)

3-3: cn() is unnecessary for a single conditional class.

The ternary already resolves to a single class string — there are no conflicting classes to merge. A plain :class binding is simpler.

Proposed simplification
-    <h2 :class="cn(flags.teamWorkspacesEnabled ? 'px-6' : 'px-4')">
+    <h2 :class="flags.teamWorkspacesEnabled ? 'px-6' : 'px-4'">

This would also eliminate the cn import entirely.

src/platform/cloud/subscription/components/SubscriptionPanel.vue (1)

99-101: Prefer a function declaration over a function expression.

Per coding guidelines, use function declarations for standalone functions. Also, this is a trivial wrapper — consider assigning manageSubscription directly if no additional logic is needed.

Option A: function declaration
-const handleInvoiceHistory = async () => {
-  await manageSubscription()
-}
+async function handleInvoiceHistory() {
+  await manageSubscription()
+}
Option B: direct alias (if the wrapper adds no value)
-const handleInvoiceHistory = async () => {
-  await manageSubscription()
-}
+const handleInvoiceHistory = manageSubscription
src/stores/billingOperationStore.ts (2)

117-127: First retry delay is 1.5× the initial interval, not INITIAL_INTERVAL_MS.

scheduleNextPoll multiplies the current interval before scheduling, so the first retry wait is 1500 ms (not the 1000 ms suggested by INITIAL_INTERVAL_MS). If the intent is for the first retry to use the initial interval:

Proposed fix — schedule at currentInterval, then bump
 function scheduleNextPoll(opId: string) {
   const currentInterval = intervals.get(opId) ?? INITIAL_INTERVAL_MS
   const nextInterval = Math.min(
     currentInterval * BACKOFF_MULTIPLIER,
     MAX_INTERVAL_MS
   )
-  intervals.set(opId, nextInterval)
-
-  const timeoutId = setTimeout(() => void poll(opId), nextInterval)
+  const timeoutId = setTimeout(() => void poll(opId), currentInterval)
   timeouts.set(opId, timeoutId)
+  intervals.set(opId, nextInterval)
 }

If the 1500 ms first delay is intentional, consider renaming the constant to BASE_INTERVAL_MS to avoid confusion.


200-233: Completed operations are never auto-cleaned from the Map.

cleanup() clears timeouts, intervals, and toasts but keeps the operation entry in operations. Only clearOperation() removes it. If consumers don't call clearOperation, completed/failed/timed-out operations accumulate indefinitely in the Map.

Consider having handleSuccess, handleFailure, and handleTimeout auto-remove operations after a short delay, or document the contract that consumers must call clearOperation.

src/composables/auth/useFirebaseAuthActions.ts (1)

85-87: Lazy useBillingContext() call inside purchaseCredits is safe but could be hoisted.

Calling useBillingContext() inside the async callback means it runs on every purchaseCredits invocation. While createSharedComposable caches the result, hoisting the call to the top of useFirebaseAuthActions (alongside other composable initializations at lines 23-25) would be more consistent with the composable initialization pattern and avoid the repeated lookup.

Proposed refactor
 export const useFirebaseAuthActions = () => {
   const authStore = useFirebaseAuthStore()
   const toastStore = useToastStore()
   const { wrapWithErrorHandlingAsync, toastErrorHandler } = useErrorHandling()
+  const { isActiveSubscription } = useBillingContext()

   const accessError = ref(false)
   ...

   const purchaseCredits = wrapWithErrorHandlingAsync(async (amount: number) => {
-    const { isActiveSubscription } = useBillingContext()
     if (!isActiveSubscription.value) return
src/composables/billing/useBillingContext.test.ts (3)

93-96: No test coverage for workspace billing path.

All tests exercise the personal/legacy workspace scenario. The workspace billing adapter path (when isInPersonalWorkspace is false) is untested. Since the PR introduces workspace billing as a core feature, this gap could let regressions in the workspace billing adapter go undetected.


6-21: Mock uses mutable closure state with a custom _setPersonalWorkspace setter — consider vi.hoisted().

The mock creates mutable isInPersonalWorkspace and activeWorkspace objects inside the factory closure, with a _setPersonalWorkspace method to mutate them. Per coding guidelines, prefer vi.hoisted() for per-test mock state manipulation to keep module mocks contained. Currently _setPersonalWorkspace is never called in any test, making it dead code.


87-96: createSharedComposable caches across the test suite — workspace-switching scenarios won't work as expected.

useBillingContext wraps useBillingContextInternal in createSharedComposable, which caches the setup result on first call. The current tests only exercise the personal/legacy workspace path, so caching poses no problem. However, if you later add tests that call _setPersonalWorkspace(false) to switch to a team workspace, the cached composable instance won't re-evaluate because the internal store reference captured during setup contains stale values.

Current workaround: Keep workspace-switching scenarios out of unit tests (test a single workspace type per test suite). If you need to test both paths, consider separate test files with isolated Pinia instances, or mock at a higher level (component/integration tests).

Note: useBillingContextInternal is not exported, so testing it directly is not an option.

src/platform/cloud/subscription/composables/useSubscriptionCredits.test.ts (2)

8-14: Module-level mutable let state should use vi.hoisted() for clarity.

mockBillingBalance and mockBillingIsLoading are module-level let variables read by vi.mock factories. Since vi.mock is hoisted above all other statements, it's best practice to use vi.hoisted() to make the shared mutable state explicit and guarantee initialization order.

♻️ Suggested refactor
-// Shared mock state (reset in beforeEach)
-let mockBillingBalance: {
-  amountMicros: number
-  cloudCreditBalanceMicros?: number
-  prepaidBalanceMicros?: number
-} | null = null
-let mockBillingIsLoading = false
+// Shared mock state (reset in beforeEach)
+const { mockBillingBalance, mockBillingIsLoading } = vi.hoisted(() => ({
+  mockBillingBalance: { value: null as {
+    amountMicros: number
+    cloudCreditBalanceMicros?: number
+    prepaidBalanceMicros?: number
+  } | null },
+  mockBillingIsLoading: { value: false }
+}))

Then update usages to access .value and adjust the mock/beforeEach accordingly.

Based on learnings: "Keep module mocks contained; do not use global mutable state within test files; use vi.hoisted() if necessary for per-test Arrange phase manipulation."


106-115: Re-instantiating the composable to observe state changes works but is fragile.

The comment explains that computed caches the value, requiring a fresh useSubscriptionCredits() call. This is because mockBillingIsLoading is a plain boolean, not a Vue ref — the computed(() => mockBillingIsLoading) in the mock doesn't track reactive dependencies. If this mock were converted to use a ref (or if vi.hoisted wrappers with .value were used), the existing computed would reactively update without needing to re-instantiate. This would make the test more realistic and less prone to confusion.

src/platform/settings/composables/useSettingUI.ts (1)

278-282: Secrets panel positioning depends on core category ordering — somewhat fragile.

The slice(0, 1) / slice(1) split inserts the secrets panel after the first core category (currently "Comfy"). If CORE_CATEGORIES_ORDER is reordered or the first element changes, the secrets panel's placement shifts silently. Consider using findIndex to locate a specific anchor category, or adding a brief comment noting the intent (e.g., "Insert Secrets after Comfy settings").

src/platform/cloud/subscription/components/SubscriptionTransitionPreviewWorkspace.vue (1)

232-257: Proration derivation relies on an implicit API contract.

proratedRefundCents is computed as newPlanCost - costToday (the "hidden" credit from the current plan), and proratedChargeCents equals costToday. This arithmetic checks out for upgrades (costToday < newPlanCost), but could display misleading numbers for edge cases where the API returns unexpected values (e.g., negative cost_today_cents on a downgrade with credit). Since showProration is gated on is_immediate, this is likely safe in practice.

src/stores/billingOperationStore.test.ts (1)

234-280: Minor inconsistency in timeout test setup.

The subscription timeout test (line 245) advances timers by 0 first to flush the immediate poll before advancing by 121_000, while the topup timeout test (line 271) jumps straight to 121_000. Both should work because advanceTimersByTimeAsync(121_000) subsumes the immediate tick, but aligning them would improve clarity. Not a bug.

src/platform/cloud/subscription/components/SubscriptionRequiredDialogContentWorkspace.vue (1)

191-194: Hardcoded payment redirect URLs should be extracted to a shared constant.

'https://www.comfy.org/payment/success' and 'https://www.comfy.org/payment/failed' appear in both handleAddCreditCard (lines 193–194) and handleConfirmTransition (lines 248–249). These are environment-specific and duplicated. Extract them alongside the handler-duplication refactor.

💡 Suggested extraction
+const PAYMENT_SUCCESS_URL = 'https://www.comfy.org/payment/success'
+const PAYMENT_FAILED_URL = 'https://www.comfy.org/payment/failed'
+
 async function handleAddCreditCard() {
   // ...
     const response = await subscribe(
       planSlug,
-      'https://www.comfy.org/payment/success',
-      'https://www.comfy.org/payment/failed'
+      PAYMENT_SUCCESS_URL,
+      PAYMENT_FAILED_URL
     )

Ideally these would live in a shared billing constants module so other components can reuse them.

Also applies to: 246-249

src/platform/cloud/subscription/components/PricingTableWorkspace.vue (1)

335-371: Static tier config uses t() from @/i18n — won't react to locale changes.

Lines 335-371 build billingCycleOptions and tiers using the static t import (line 288) rather than the reactive t from useI18n(). These values are computed once at setup time. If the app supports runtime locale switching, these labels will remain in the initial locale. If that's acceptable (likely for a billing page), this is fine — just flagging the trade-off.

src/composables/billing/types.ts (1)

24-30: BalanceInfo propagates the Micros naming inconsistency from the API.

The fields are named amountMicros, effectiveBalanceMicros, etc., but consumers (e.g., CurrentUserPopoverWorkspace.vue) treat them as cents. Consider adding a JSDoc note on the interface documenting this known API discrepancy so future developers aren't misled by the field names.

src/platform/workspace/api/workspaceApi.ts (1)

289-293: No request timeout configured on workspaceApiClient.

All billing API calls (status, balance, subscribe, topup, etc.) use this client without a timeout. If the billing service is slow or unresponsive, requests will hang indefinitely, potentially blocking UI flows. Consider adding a default timeout.

Proposed fix
 const workspaceApiClient = axios.create({
   headers: {
     'Content-Type': 'application/json'
-  }
+  },
+  timeout: 30_000
 })

Comment on lines +215 to +217
function formatNumber(num: number): string {
return num.toLocaleString('en-US')
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

formatNumber hardcodes 'en-US' locale.

The n function from useI18n() (line 173) already handles locale-aware number formatting. Use it instead of toLocaleString('en-US') for consistency with the rest of the i18n setup.

💡 Suggested fix
-function formatNumber(num: number): string {
-  return num.toLocaleString('en-US')
-}

Then in the template, replace formatNumber(usdToCredits(MIN_AMOUNT)) with n(usdToCredits(MIN_AMOUNT)) and similarly for MAX_AMOUNT.

As per coding guidelines: Use vue-i18n in Composition API for any string literals.

🤖 Prompt for AI Agents
In `@src/components/dialog/content/TopUpCreditsDialogContentWorkspace.vue` around
lines 215 - 217, The helper function formatNumber currently hardcodes the
'en-US' locale; replace its usage with the i18n number formatter (n) from
useI18n to respect the app locale: remove or stop using formatNumber and update
template calls like formatNumber(usdToCredits(MIN_AMOUNT)) and
formatNumber(usdToCredits(MAX_AMOUNT)) to use n(usdToCredits(MIN_AMOUNT)) and
n(usdToCredits(MAX_AMOUNT)); if formatNumber is kept, refactor it to call n
internally (ensure useI18n's n is imported/available) and remove the hardcoded
toLocaleString('en-US') call.

Comment on lines +134 to +157
// Initialize billing when workspace changes
watch(
() => store.activeWorkspace?.id,
async (newWorkspaceId, oldWorkspaceId) => {
if (!newWorkspaceId) {
// No workspace selected - reset state
isInitialized.value = false
error.value = null
return
}

if (newWorkspaceId !== oldWorkspaceId) {
// Workspace changed - reinitialize
isInitialized.value = false
try {
await initialize()
} catch (err) {
// Error is already captured in error ref
console.error('Failed to initialize billing context:', err)
}
}
},
{ immediate: true }
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Potential race on rapid workspace changes during async initialize.

The watch is async and calls await initialize(). If the active workspace changes while initialize() is still in flight, the watch fires again, resets isInitialized, and starts a second initialize() concurrently. Both compete over isLoading/isInitialized/error refs, so the first completion can set isInitialized = true while the second is still running—leaving stale state.

A lightweight guard (e.g., an abort counter / generation token) would prevent the earlier call from mutating shared state after it becomes irrelevant.

💡 Suggested approach
+ let initGeneration = 0
+
  watch(
    () => store.activeWorkspace?.id,
    async (newWorkspaceId, oldWorkspaceId) => {
      if (!newWorkspaceId) {
        isInitialized.value = false
        error.value = null
        return
      }

      if (newWorkspaceId !== oldWorkspaceId) {
        isInitialized.value = false
+       const gen = ++initGeneration
        try {
          await initialize()
+         // Discard result if workspace changed again while awaiting
+         if (gen !== initGeneration) return
        } catch (err) {
+         if (gen !== initGeneration) return
          console.error('Failed to initialize billing context:', err)
        }
      }
    },
    { immediate: true }
  )
🤖 Prompt for AI Agents
In `@src/composables/billing/useBillingContext.ts` around lines 134 - 157, The
async watch callback in useBillingContext (watch on store.activeWorkspace?.id)
can run multiple concurrent initialize() calls and race on shared refs
(isInitialized, isLoading, error); add a lightweight generation token or abort
counter in the composable (e.g., billingInitGeneration) that you increment
before each call, capture into a local variable inside the watch, and have
initialize() or the post-await state mutations check the captured token against
the current billingInitGeneration so only the latest invocation mutates refs;
ensure errors are still assigned to error ref only when the generation matches
to avoid stale state from earlier/faster completions.

Comment on lines +159 to +174
async function initialize(): Promise<void> {
if (isInitialized.value) return

isLoading.value = true
error.value = null
try {
await activeContext.value.initialize()
isInitialized.value = true
} catch (err) {
error.value =
err instanceof Error ? err.message : 'Failed to initialize billing'
throw err
} finally {
isLoading.value = false
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

initialize early-return guard is defeated by the watcher.

The guard if (isInitialized.value) return on line 160 is always bypassed because the watcher explicitly sets isInitialized.value = false before calling initialize(). If a second workspace change occurs while the first initialize is still in flight, both invocations will proceed past this guard concurrently. This is a downstream symptom of the race described above—the generation-token fix would also address this.

🤖 Prompt for AI Agents
In `@src/composables/billing/useBillingContext.ts` around lines 159 - 174, The
early-return guard in initialize() is ineffective because the watcher force-sets
isInitialized.value = false, allowing concurrent calls to pass the guard; fix by
introducing single-flight behavior: add an initializingPromise (or
isInitializing flag) scoped alongside isInitialized/isLoading and inside
initialize() if initializingPromise exists return/await it, otherwise set
initializingPromise = activeContext.value.initialize() and await it, then clear
initializingPromise and set isInitialized.value = true; reference initialize(),
isInitialized, isLoading, activeContext.value.initialize to locate where to add
the promise/flag and where to clear it in the try/catch/finally.

Comment on lines +2192 to +2194
"nextMonthInvoice": "Next month invoice",
"invoiceHistory": "Invoice history",
"memberCount": "{count} member | {count} members",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Duplicate invoiceHistory key in the subscription object.

invoiceHistory is defined on both Line 2140 and Line 2193 within the same subscription object. Duplicate JSON keys have undefined behavior per spec — most parsers silently take the last value. Remove one to avoid maintenance confusion if values ever diverge.

✏️ Proposed fix — remove the duplicate on Line 2193
     "membersLabel": "Up to {count} members",
     "nextMonthInvoice": "Next month invoice",
-    "invoiceHistory": "Invoice history",
     "memberCount": "{count} member | {count} members",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"nextMonthInvoice": "Next month invoice",
"invoiceHistory": "Invoice history",
"memberCount": "{count} member | {count} members",
"nextMonthInvoice": "Next month invoice",
"memberCount": "{count} member | {count} members",
🤖 Prompt for AI Agents
In `@src/locales/en/main.json` around lines 2192 - 2194, There is a duplicate
"invoiceHistory" key inside the subscription JSON object; remove the redundant
"invoiceHistory" entry so only a single definition of "invoiceHistory" remains
in that object (keep the intended/accurate translation), and run your locale
sanity checks (or tests) to ensure no other duplicates exist and that consumers
still read the expected value.

Comment on lines +439 to +441
const togglePopover = (event: Event) => {
popover.value.toggle(event)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Null-check the popover ref before calling .toggle().

popover is typed as ref() (implicitly Ref<any>), so there's no compile-time safety. If the <Popover> hasn't rendered, this will throw at runtime.

Proposed fix
-const popover = ref()
+const popover = ref<InstanceType<typeof Popover> | null>(null)

 const togglePopover = (event: Event) => {
-  popover.value.toggle(event)
+  popover.value?.toggle(event)
 }
🤖 Prompt for AI Agents
In `@src/platform/cloud/subscription/components/PricingTableWorkspace.vue` around
lines 439 - 441, The popover ref may be null when the <Popover> hasn't rendered,
so update the togglePopover handler to guard before calling
popover.value.toggle(event); e.g. check popover.value exists (or use optional
chaining popover.value?.toggle(event)) and keep the same signature for
togglePopover; reference the popover ref and the togglePopover function to
ensure the runtime null-check prevents calling .toggle() on undefined.

Comment on lines 628 to 631
onMounted(() => {
window.addEventListener('focus', handleWindowFocus)
void Promise.all([fetchStatus(), fetchBalance()])
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fire-and-forget fetchStatus / fetchBalance on mount — errors silently swallowed.

If either call fails (network issue, auth expired), the user sees a stale or empty state with no feedback. Consider adding a .catch to show a toast or set an error state.

Proposed fix
 onMounted(() => {
   window.addEventListener('focus', handleWindowFocus)
-  void Promise.all([fetchStatus(), fetchBalance()])
+  void Promise.all([fetchStatus(), fetchBalance()]).catch(() => {
+    toast.add({
+      severity: 'error',
+      summary: t('g.error'),
+      detail: t('subscription.loadFailed'),
+      life: 5000
+    })
+  })
 })
🤖 Prompt for AI Agents
In
`@src/platform/cloud/subscription/components/SubscriptionPanelContentWorkspace.vue`
around lines 628 - 631, The onMounted fire-and-forget
Promise.all([fetchStatus(), fetchBalance()]) swallows errors; update the
onMounted handler (where fetchStatus and fetchBalance are invoked, alongside
handleWindowFocus) to attach a .catch that handles failures — e.g., set a
component error state or call the existing toast/error helper to surface the
error and optionally retry; ensure the .catch distinguishes which call failed if
needed and preserves existing focus listener cleanup.

Comment on lines +291 to +314
async function handleResubscribe() {
isResubscribing.value = true
try {
await workspaceApi.resubscribe()
toast.add({
severity: 'success',
summary: t('subscription.resubscribeSuccess'),
life: 5000
})
await Promise.all([fetchStatus(), fetchBalance()])
emit('close', true)
} catch (error) {
const message =
error instanceof Error ? error.message : 'Failed to resubscribe'
toast.add({
severity: 'error',
summary: 'Error',
detail: message,
life: 5000
})
} finally {
isResubscribing.value = false
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

handleResubscribe has the same i18n violations as the other handlers.

Lines 304 and 307 use hardcoded strings 'Failed to resubscribe' and 'Error'. Also, this function calls workspaceApi.resubscribe() directly instead of going through the billing context—verify this is intentional (the billing context's subscribe may not support resubscription).

💡 i18n fix
   } catch (error) {
     const message =
-      error instanceof Error ? error.message : 'Failed to resubscribe'
+      error instanceof Error ? error.message : t('subscription.required.resubscribeFailed')
     toast.add({
       severity: 'error',
-      summary: 'Error',
+      summary: t('subscription.required.errorSummary'),
       detail: message,
       life: 5000
     })

As per coding guidelines: Use vue-i18n for ALL user-facing strings.

🤖 Prompt for AI Agents
In
`@src/platform/cloud/subscription/components/SubscriptionRequiredDialogContentWorkspace.vue`
around lines 291 - 314, The handler handleResubscribe uses hardcoded user
strings and calls workspaceApi.resubscribe() directly; replace the hardcoded
'Error' and 'Failed to resubscribe' with vue-i18n lookups (e.g. use
t('common.error') for the toast summary and a new/appropriate key such as
t('subscription.resubscribeFailed') for the detail) and update the toast.add
calls to use those keys, and verify/replace the direct API call with the billing
context method (e.g. use billingContext.subscribe or the equivalent resubscribe
helper) if the billing context supports resubscription; if it does not, leave
the resubscribe call but add a comment explaining why billingContext was not
used.

Comment on lines +594 to +614
async subscribe(
planSlug: string,
returnUrl?: string,
cancelUrl?: string
): Promise<SubscribeResponse> {
const headers = await getAuthHeaderOrThrow()
try {
const response = await workspaceApiClient.post<SubscribeResponse>(
api.apiURL('/billing/subscribe'),
{
plan_slug: planSlug,
return_url: returnUrl,
cancel_url: cancelUrl
} satisfies SubscribeRequest,
{ headers }
)
return response.data
} catch (err) {
handleAxiosError(err)
}
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

subscribe() omits idempotency_key — inconsistent with other mutating endpoints.

cancelSubscription (line 621) and resubscribe (line 643) accept an optional idempotencyKey parameter, but subscribe does not, even though SubscribeRequest (line 130) defines idempotency_key. For billing mutations, idempotency keys are important to prevent duplicate charges on retry.

Proposed fix
   async subscribe(
     planSlug: string,
     returnUrl?: string,
-    cancelUrl?: string
+    cancelUrl?: string,
+    idempotencyKey?: string
   ): Promise<SubscribeResponse> {
     const headers = await getAuthHeaderOrThrow()
     try {
       const response = await workspaceApiClient.post<SubscribeResponse>(
         api.apiURL('/billing/subscribe'),
         {
           plan_slug: planSlug,
+          idempotency_key: idempotencyKey,
           return_url: returnUrl,
           cancel_url: cancelUrl
         } satisfies SubscribeRequest,
         { headers }
       )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async subscribe(
planSlug: string,
returnUrl?: string,
cancelUrl?: string
): Promise<SubscribeResponse> {
const headers = await getAuthHeaderOrThrow()
try {
const response = await workspaceApiClient.post<SubscribeResponse>(
api.apiURL('/billing/subscribe'),
{
plan_slug: planSlug,
return_url: returnUrl,
cancel_url: cancelUrl
} satisfies SubscribeRequest,
{ headers }
)
return response.data
} catch (err) {
handleAxiosError(err)
}
},
async subscribe(
planSlug: string,
returnUrl?: string,
cancelUrl?: string,
idempotencyKey?: string
): Promise<SubscribeResponse> {
const headers = await getAuthHeaderOrThrow()
try {
const response = await workspaceApiClient.post<SubscribeResponse>(
api.apiURL('/billing/subscribe'),
{
plan_slug: planSlug,
idempotency_key: idempotencyKey,
return_url: returnUrl,
cancel_url: cancelUrl
} satisfies SubscribeRequest,
{ headers }
)
return response.data
} catch (err) {
handleAxiosError(err)
}
},
🤖 Prompt for AI Agents
In `@src/platform/workspace/api/workspaceApi.ts` around lines 594 - 614, The
subscribe method is missing support for idempotency keys; update async
subscribe(planSlug: string, returnUrl?: string, cancelUrl?: string) to accept an
optional idempotencyKey?: string parameter and include idempotency_key:
idempotencyKey in the request payload (alongside plan_slug, return_url,
cancel_url) so the body satisfies SubscribeRequest; keep getAuthHeaderOrThrow
usage and error handling the same.

Mock useDialogService and useDialogStore to prevent unresolved module
imports causing "Closing rpc while fetch was pending" errors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Contributor

@christian-byrne christian-byrne left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

almost finished reviewing (31/54)

:model-value="payAmount"
:min="0"
:max="MAX_AMOUNT"
:step="getStepAmount"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: it seems like it should be a computed prop rather than a getter.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels like for organization, it would be nicer to put this (and other files) in a src/platform/workspace folder rather than src/components. Then it might be easy to see all the files together and handle the transition better.

Comment on lines +268 to +269
handleClose(false)
dialogService.showSettingsDialog('workspace')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we worried at all about the timing if this has possibility of tearing down the state of the current component?

toast.add({
severity: 'error',
summary: t('credits.topUp.purchaseError'),
detail: t('credits.topUp.unknownError'),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WDYT about sending something to Sentry here and below -- to benefit our future selves if we have to debug.

Comment on lines +346 to +348
if (isActiveSubscription.value) {
void fetchBalance()
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would we want to fetch the balance even if isActiveSubscription was false here or would it never work?

const { subscriptionTierName: userSubscriptionTierName } = useSubscription()
const { subscription } = useBillingContext()

const tierKeyMap: Record<string, string> = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use this

export type TierKey = 'standard' | 'creator' | 'pro' | 'founder'
export const TIER_TO_KEY: Record<SubscriptionTier, TierKey> = {
STANDARD: 'standard',
CREATOR: 'creator',
PRO: 'pro',
FOUNDERS_EDITION: 'founder'
}

Comment on lines +153 to +163
function formatTierName(
tier: string | null | undefined,
isYearly: boolean
): string {
if (!tier) return ''
const key = tierKeyMap[tier] ?? 'standard'
const baseName = t(`subscription.tiers.${key}.name`)
return isYearly
? t('subscription.tierNameYearly', { name: baseName })
: baseName
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file makes me think OOP truly is the best

await legacyManageSubscription()
}

async function fetchPlans(): Promise<void> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the legendary async no-op

Copy link
Contributor

@christian-byrne christian-byrne left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM with nits

Comment on lines +20 to +30
function formatBalance(maybeCents: number | undefined, locale: string): string {
const cents = maybeCents ?? 0
return formatCreditsFromCents({
cents,
locale,
numberOptions: {
minimumFractionDigits: 0,
maximumFractionDigits: 0
}
})
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: might be worth moving to shared file in the future

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great, nice work here

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: would like it to be in domain folder for billing/credits/subscriptions

@christian-byrne christian-byrne removed the cloud/1.37 Backport PRs for cloud 1.37 label Feb 7, 2026
@simula-r simula-r merged commit c5431de into main Feb 7, 2026
42 of 43 checks passed
@simula-r simula-r deleted the feat/workspaces-6-billing branch February 7, 2026 04:52
@github-actions
Copy link

github-actions bot commented Feb 7, 2026

⚠️ Backport to cloud/1.38 failed

Reason: Merge conflicts detected during cherry-pick of c5431de

📄 Conflicting files
src/platform/cloud/subscription/composables/useSubscriptionCredits.test.ts
src/services/dialogService.ts
🤖 Prompt for AI Agents
Backport PR #8508 (https://github.com/Comfy-Org/ComfyUI_frontend/pull/8508) to cloud/1.38.
Cherry-pick merge commit c5431de123b6dd74e075b8e2225b27fed816da0f onto new branch
backport-8508-to-cloud-1.38 from origin/cloud/1.38.
Resolve conflicts in: src/platform/cloud/subscription/composables/useSubscriptionCredits.test.ts src/services/dialogService.ts .
For test snapshots (browser_tests/**/*-snapshots/), accept PR version if
changed in original PR, else keep target. For package.json versions, keep
target branch. For pnpm-lock.yaml, regenerate with pnpm install.
Ask user for non-obvious conflicts.
Create PR titled "[backport cloud/1.38] <original title>" with label "backport".
See .github/workflows/pr-backport.yaml for workflow details.

cc @simula-r

simula-r added a commit that referenced this pull request Feb 7, 2026
Implements billing infrastructure for team workspaces, separate from
legacy personal billing.

- **Billing abstraction**: New `useBillingContext` composable that
switches between legacy (personal) and workspace billing based on
context
- **Workspace subscription flows**: Pricing tables, plan transitions,
cancellation dialogs, and payment preview components for workspace
billing
- **Top-up credits**: Workspace-specific top-up dialog with polling for
payment confirmation
- **Workspace API**: Extended with billing endpoints (subscriptions,
invoices, payment methods, credits top-up)
- **Workspace switcher**: Now displays tier badges for each workspace
- **Subscribe polling**: Added polling mechanisms
(`useSubscribePolling`, `useTopupPolling`) for async payment flows

- Billing flow correctness for workspace vs legacy contexts
- Polling timeout and error handling in payment flows

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8508-Feat-workspaces-6-billing-2f96d73d365081f69f65c1ddf369010d)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
simula-r added a commit that referenced this pull request Feb 7, 2026
Backport of #8508 to `cloud/1.38`

Automatically created by manual backport (cherry-pick of
c5431de).

Conflicts resolved:
-
`src/platform/cloud/subscription/composables/useSubscriptionCredits.test.ts`
— accepted PR version (uses `useBillingContext` mock instead of
pinia/firebase auth)
- `src/services/dialogService.ts` — merged: kept cloud/1.38 eager
imports for dialog components, replaced `TopUpCreditsDialogContent` with
Legacy/Workspace variants, replaced `useSubscription` with
`useBillingContext`, added workspace/legacy component selection in
`showTopUpCreditsDialog`; skipped lazy loader refactor (separate PR, not
part of #8508)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cloud/1.38 Backport PRs for cloud 1.38 needs-backport Fix/change that needs to be cherry-picked to the current feature freeze branch preview-cpu Creates a preview ephemeral environment for this PR (CPU only) size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants