Skip to content

feat: role-aware billing status banner for team workspaces (FE-1246)#13641

Merged
dante01yoon merged 10 commits into
mainfrom
jaewon/fe-1246-team-credit-exhaustion-blocked-state-ux-role-aware-cta
Jul 17, 2026
Merged

feat: role-aware billing status banner for team workspaces (FE-1246)#13641
dante01yoon merged 10 commits into
mainfrom
jaewon/fe-1246-team-credit-exhaustion-blocked-state-ux-role-aware-cta

Conversation

@dante01yoon

@dante01yoon dante01yoon commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

Adds the single billing-status banner slot for team plans, rendered in priority order — paused > payment declined > out of credits > ending — with role-aware CTAs. At most one state shows at a time.

Placement matches the prototype (comfydesigner/team-workspaces-v1) and Figma: the banner lives in the workspace settings panel, in one slot between the tab list and the tab body, so it shows on every tab. Our container has two tabs, so one mount covers both rather than duplicating the banner into each panel as the prototype does.

Derivation lives in a pure deriveBillingBanner(); the component only renders. State comes from billing-status fields, never from error strings (per Luke's user story).

Gated on the plan, not the workspace type

useBillingContext gains isTeamPlan: a credit stop marks the per-credit Team plan, a team- slug the retired seat-based ones. Workspace type is the wrong question in both directions:

  • A team workspace can sit on a retired seat-based plan (tier STANDARD/CREATOR/PRO) — the type gate admits it.
  • Once consolidated billing lands (cloud#5010), a personal workspace can hold a team plan. The backend blocks that today pending BE-1526, so the type gate is only accidentally right, and only for now. Per Hunter: "We should deprecate the workspace type and derive it from the subscription."

Two deliberate non-choices:

  • Not gated on isActiveSubscription, unlike the existing isLegacyTeamPlan. The spend gate folds billing_status into is_active, so a paused or payment-failed team plan reports is_active=false — it must still read as a team plan exactly when the banner is needed.
  • Not gated on subscription_tier === 'TEAM', which would silently drop every legacy team subscriber. The FE cannot express 'TEAM' anyway: tierPricing.ts resolves SubscriptionTier from the registry spec for what is an ingest field. That's a real bug, but orthogonal — filing separately.

Fixes payment_failed, which was dead code

payment_failed denies spend, so it always arrives with is_active=false — and the check sat below the is_active gate, two lines under a comment documenting that exact trap for paused. Every team in Stripe dunning saw no banner. Now hoisted alongside paused.

Its tests passed only because they spread the funded fixture (isActiveSubscription: true) onto payment_failed — a pairing the backend never emits. They now pin is_active=false. Both this and the isTeamPlan decoupling are mutation-tested: reverting either kills tests.

Members no longer fall through from payment_failed to out-of-credits — with the real pairing that path was unreachable, and DES-380 says members never see the payment banner.

Renders

The six role/state variants render from the Storybook stories at Platform/Workspace/BillingStatusBanner. Each story drives the real deriveBillingBanner through a stubbed billing context, so a story can only show a state the backend can actually produce — not a hand-set banner kind.

Run them with DISTRIBUTION=cloud pnpm storybook — the banner is cloud-only and isCloud is compile-time, so a plain pnpm storybook renders every story empty.

Icon language follows the prototype's severity rule: amber triangle-alert for every action-needed state (paused, payment declined, out of credits), muted circle-alert reserved for the informational "plan ends" notice.

Feature flag

Personal-workspace routing now keys off billing_control_enabled, replacing consolidated_billing_enabled (which had exactly one consumer — this routing check — so it is replaced rather than left alongside). Cloud registers the new flag in Comfy-Org/cloud#5091.

This coupling is load-bearing for the banner. isTeamPlan requires the workspace billing rail, so a personal workspace holding a team plan only surfaces billing state once its routing flag is on. Had routing stayed on consolidated_billing_enabled while the subscribe path unblocks under billing_control_enabled, the two could diverge: a personal workspace could buy a team plan and then be routed to legacy, which carries no billing_status at all, blanking the banner.

Merge order: cloud#5091 must merge and billing_control_enabled must exist in PostHog before this ships, otherwise the flag resolves false for everyone and every personal workspace routes to legacy. Both flags default false, so the floor is today's behaviour — but any users already rolled out on consolidated_billing_enabled would revert to legacy billing until billing_control_enabled is rolled out to them. Worth confirming the current rollout state before merge.

Backend status

billing_status: 'paused' merged in cloud #5075 but is not emitted yet: the lifecycle handler that writes it is unreachable in prod (ingest's forward allowlist omits customer.subscription.*; billing-api is ClusterIP with no ingress). That's BE-1530's unfinished half, tracked backend-side. The paused banner stays inert until then.

The other three states render from fields the API already returns: billing_status=payment_failed + renewal_date, has_funds, and cancel_at.

How has this been tested?

  • useBillingContext.test.tsisTeamPlan across per-credit, legacy, paused, payment-failed, and team-workspace-on-personal-plan
  • deriveBillingBanner.test.ts — priority order, role gating, and the realistic is_active=false pairing for both paused and payment_failed
  • BillingStatusBanner.test.ts — per-variant copy and actions, date interpolation + no-date fallback, cross-mount dismiss, ending read-only for a non-original owner
  • useBillingBanner.test.ts — dismiss resets after a top-up so a later exhaustion re-shows
  • WorkspacePanelContent.test.ts — the banner takes one slot above the tab body
  • 696 tests pass across the touched areas; typecheck / lint / knip clean

Known gaps

  • Blocked-run surface: settings-scoped per the prototype, so a user who exhausts credits mid-canvas won't see it until they open settings. Queue-time blocked-submit is carried by the existing insufficient-credits dialog. An app-shell surface would be a design call, not this PR.
  • Personal workspaces on consolidated billing get no banner — isTeamPlan excludes them by design, matching FE-1246's team scope and the !isInPersonalWorkspace precedent in useSubscriptionDialog. Their copy would need personal variants (outOfCredits.body says "Your team…") and a free-tier decision. Belongs with the consolidated-billing rollout.
  • Prototype slot: the prototype's banner exposes an actions slot used only by its Invoices view. We have no such view, so it's omitted until needed.

screenshots

01-paused-owner 02-paused-member 03-payment-declined 04-payment-declined-no-date 05-out-of-credits-owner 06-out-of-credits-member 07-ending-owner 08-ending-non-original-owner

@dante01yoon
dante01yoon requested a review from a team July 14, 2026 19:00
@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Jul 14, 2026
@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown

🎭 Playwright: ✅ 1712 passed, 0 failed · 2 flaky

📊 Browser Reports
  • chromium: View Report (✅ 1691 / ❌ 0 / ⚠️ 2 / ⏭️ 5)
  • chromium-2x: View Report (✅ 2 / ❌ 0 / ⚠️ 0 / ⏭️ 0)
  • chromium-0.5x: View Report (✅ 1 / ❌ 0 / ⚠️ 0 / ⏭️ 0)
  • mobile-chrome: View Report (✅ 18 / ❌ 0 / ⚠️ 0 / ⏭️ 0)

🎨 Storybook: ✅ Built — View Storybook

Details

⏰ Completed at: 07/16/2026, 06:16:51 PM UTC

Links

📦 Bundle: 7.86 MB gzip 🔴 +2.1 kB

Details

Summary

  • Raw size: 33.1 MB baseline 33.1 MB — 🔴 +11.9 kB
  • Gzip: 7.86 MB baseline 7.86 MB — 🔴 +2.1 kB
  • Brotli: 5.42 MB baseline 5.41 MB — 🔴 +1.88 kB
  • Bundles: 302 current • 302 baseline • 148 added / 148 removed

Category Glance
Other 🔴 +8.97 kB (11.9 MB) · Utilities & Hooks 🔴 +3.01 kB (3.42 MB) · Panels & Settings 🟢 -30 B (553 kB) · Vendor & Third-Party ⚪ 0 B (15.3 MB) · Graph Workspace ⚪ 0 B (1.27 MB) · Data & Services ⚪ 0 B (275 kB) · + 5 more

App Entry Points — 53.2 kB (baseline 53.2 kB) • ⚪ 0 B

Main entry bundles and manifests

File Before After Δ Raw Δ Gzip Δ Brotli
assets/index-CtKe7n5z.js (new) 53.2 kB 🔴 +53.2 kB 🔴 +15.7 kB 🔴 +13.5 kB
assets/index-DYHBhzMG.js (removed) 53.2 kB 🟢 -53.2 kB 🟢 -15.7 kB 🟢 -13.5 kB

Status: 1 added / 1 removed

Graph Workspace — 1.27 MB (baseline 1.27 MB) • ⚪ 0 B

Graph editor runtime, canvas, workflow orchestration

File Before After Δ Raw Δ Gzip Δ Brotli
assets/GraphView-CgGDUzXY.js (new) 1.27 MB 🔴 +1.27 MB 🔴 +271 kB 🔴 +204 kB
assets/GraphView-vbaiR39X.js (removed) 1.27 MB 🟢 -1.27 MB 🟢 -271 kB 🟢 -204 kB

Status: 1 added / 1 removed

Views & Navigation — 110 kB (baseline 110 kB) • ⚪ 0 B

Top-level views, pages, and routed surfaces

File Before After Δ Raw Δ Gzip Δ Brotli
assets/CloudSurveyView-BYeNjgbm.js (removed) 25 kB 🟢 -25 kB 🟢 -6.25 kB 🟢 -5.53 kB
assets/CloudSurveyView-COIZoypD.js (new) 25 kB 🔴 +25 kB 🔴 +6.25 kB 🔴 +5.53 kB
assets/CloudLoginView-B_r-C4j1.js (removed) 11.4 kB 🟢 -11.4 kB 🟢 -3.07 kB 🟢 -2.68 kB
assets/CloudLoginView-D6G1M3oW.js (new) 11.4 kB 🔴 +11.4 kB 🔴 +3.06 kB 🔴 +2.69 kB
assets/CloudSignupView-D3xICupo.js (removed) 9.79 kB 🟢 -9.79 kB 🟢 -2.74 kB 🟢 -2.41 kB
assets/CloudSignupView-DeXhfzBh.js (new) 9.79 kB 🔴 +9.79 kB 🔴 +2.74 kB 🔴 +2.4 kB
assets/CloudLayoutView-CPn_XOsD.js (removed) 9.59 kB 🟢 -9.59 kB 🟢 -2.37 kB 🟢 -2.05 kB
assets/CloudLayoutView-Ct1K2bRL.js (new) 9.59 kB 🔴 +9.59 kB 🔴 +2.37 kB 🔴 +2.06 kB
assets/UserCheckView-BnxMToFh.js (new) 8.8 kB 🔴 +8.8 kB 🔴 +2.22 kB 🔴 +1.93 kB
assets/UserCheckView-Djd15RtU.js (removed) 8.8 kB 🟢 -8.8 kB 🟢 -2.22 kB 🟢 -1.93 kB
assets/CloudSubscriptionRedirectView-D304tSwG.js (removed) 6.98 kB 🟢 -6.98 kB 🟢 -2.58 kB 🟢 -2.28 kB
assets/CloudSubscriptionRedirectView-y70jZmeJ.js (new) 6.98 kB 🔴 +6.98 kB 🔴 +2.58 kB 🔴 +2.28 kB
assets/WidgetTextPreview-BSyP3U9M.js (new) 6.13 kB 🔴 +6.13 kB 🔴 +2.16 kB 🔴 +1.92 kB
assets/WidgetTextPreview-D4lHEKno.js (removed) 6.13 kB 🟢 -6.13 kB 🟢 -2.15 kB 🟢 -1.91 kB
assets/UserSelectView-DlCM41z-.js (removed) 6 kB 🟢 -6 kB 🟢 -2.15 kB 🟢 -1.89 kB
assets/UserSelectView-DPadhttY.js (new) 6 kB 🔴 +6 kB 🔴 +2.15 kB 🔴 +1.89 kB
assets/CloudForgotPasswordView-BAz38LUY.js (new) 5.15 kB 🔴 +5.15 kB 🔴 +1.76 kB 🔴 +1.54 kB
assets/CloudForgotPasswordView-DZebnDdk.js (removed) 5.15 kB 🟢 -5.15 kB 🟢 -1.76 kB 🟢 -1.54 kB
assets/CloudAuthTimeoutView-BGZC1Xtp.js (removed) 4.49 kB 🟢 -4.49 kB 🟢 -1.57 kB 🟢 -1.37 kB
assets/CloudAuthTimeoutView-ZvOO5F37.js (new) 4.49 kB 🔴 +4.49 kB 🔴 +1.57 kB 🔴 +1.37 kB
assets/WidgetTextPreview-4FDO9-Nn.js (removed) 131 B 🟢 -131 B 🟢 -100 B 🟢 -90 B
assets/WidgetTextPreview-Pn-uL8_z.js (new) 131 B 🔴 +131 B 🔴 +100 B 🔴 +88 B

Status: 11 added / 11 removed / 3 unchanged

Panels & Settings — 553 kB (baseline 553 kB) • 🟢 -30 B

Configuration panels, inspectors, and settings screens

File Before After Δ Raw Δ Gzip Δ Brotli
assets/KeybindingPanel-CqCvZL9g.js (removed) 49.4 kB 🟢 -49.4 kB 🟢 -9.97 kB 🟢 -8.83 kB
assets/KeybindingPanel-N9ZL0Gez.js (new) 49.4 kB 🔴 +49.4 kB 🔴 +9.97 kB 🔴 +8.82 kB
assets/SecretsPanel-Dq7Fn2ya.js (removed) 31 kB 🟢 -31 kB 🟢 -7.51 kB 🟢 -6.56 kB
assets/SecretsPanel-Y_C8AP1T.js (new) 31 kB 🔴 +31 kB 🔴 +7.51 kB 🔴 +6.57 kB
assets/CreditsPanel-C4H5FJs0.js (new) 15.9 kB 🔴 +15.9 kB 🔴 +4.66 kB 🔴 +4.08 kB
assets/CreditsPanel-CdAvmAPW.js (removed) 15.9 kB 🟢 -15.9 kB 🟢 -4.66 kB 🟢 -4.08 kB
assets/AboutPanel-Ci7BIT6y.js (removed) 12 kB 🟢 -12 kB 🟢 -3.29 kB 🟢 -2.95 kB
assets/AboutPanel-DXWS2RN4.js (new) 12 kB 🔴 +12 kB 🔴 +3.29 kB 🔴 +2.95 kB
assets/SubscriptionPanel-0eKPoR2g.js (new) 11.5 kB 🔴 +11.5 kB 🔴 +3.66 kB 🔴 +3.21 kB
assets/SubscriptionPanel-BOJHS19Y.js (removed) 11.5 kB 🟢 -11.5 kB 🟢 -3.66 kB 🟢 -3.21 kB
assets/ExtensionPanel-C9ch796R.js (new) 9.03 kB 🔴 +9.03 kB 🔴 +2.5 kB 🔴 +2.2 kB
assets/ExtensionPanel-DZnNpx2r.js (removed) 9.03 kB 🟢 -9.03 kB 🟢 -2.5 kB 🟢 -2.2 kB
assets/ServerConfigPanel-CCGYKbUL.js (removed) 6.15 kB 🟢 -6.15 kB 🟢 -1.98 kB 🟢 -1.76 kB
assets/ServerConfigPanel-CseeLzet.js (new) 6.15 kB 🔴 +6.15 kB 🔴 +1.97 kB 🔴 +1.76 kB
assets/UserPanel-C8p4y38B.js (removed) 5.78 kB 🟢 -5.78 kB 🟢 -1.82 kB 🟢 -1.58 kB
assets/UserPanel-SZyf50WL.js (new) 5.78 kB 🔴 +5.78 kB 🔴 +1.82 kB 🔴 +1.58 kB
assets/refreshRemoteConfig-Cv9Zlxte.js (removed) 2.88 kB 🟢 -2.88 kB 🟢 -1.26 kB 🟢 -1.1 kB
assets/refreshRemoteConfig-D004RAwa.js (new) 2.86 kB 🔴 +2.86 kB 🔴 +1.26 kB 🔴 +1.1 kB
assets/remoteConfig-DjUkM6Dg.js (removed) 1.51 kB 🟢 -1.51 kB 🟢 -725 B 🟢 -599 B
assets/remoteConfig-BCKemzVc.js (new) 1.5 kB 🔴 +1.5 kB 🔴 +720 B 🔴 +596 B
assets/cloudRemoteConfig-C83QBVOO.js (removed) 990 B 🟢 -990 B 🟢 -542 B 🟢 -467 B
assets/cloudRemoteConfig-C8Ndl6PN.js (new) 990 B 🔴 +990 B 🔴 +542 B 🔴 +440 B
assets/refreshRemoteConfig-B2Cll5U1.js (new) 110 B 🔴 +110 B 🔴 +89 B 🔴 +83 B
assets/refreshRemoteConfig-DBhAjhks.js (removed) 110 B 🟢 -110 B 🟢 -89 B 🟢 -82 B

Status: 12 added / 12 removed / 15 unchanged

User & Accounts — 29.1 kB (baseline 29.1 kB) • ⚪ 0 B

Authentication, profile, and account management bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/SignUpForm-C_6tOM8P.js (removed) 12.3 kB 🟢 -12.3 kB 🟢 -4.19 kB 🟢 -3.65 kB
assets/SignUpForm-DnhJuZIg.js (new) 12.3 kB 🔴 +12.3 kB 🔴 +4.19 kB 🔴 +3.65 kB
assets/auth-CCk7ayti.js (new) 3.69 kB 🔴 +3.69 kB 🔴 +1.3 kB 🔴 +1.11 kB
assets/auth-Cls13zM0.js (removed) 3.69 kB 🟢 -3.69 kB 🟢 -1.3 kB 🟢 -1.13 kB
assets/usePostAuthRedirect-CmSDm9Cq.js (removed) 3.33 kB 🟢 -3.33 kB 🟢 -1.28 kB 🟢 -1.11 kB
assets/usePostAuthRedirect-IiRJdHS8.js (new) 3.33 kB 🔴 +3.33 kB 🔴 +1.27 kB 🔴 +1.11 kB
assets/UpdatePasswordContent-CChNRA9V.js (new) 1.92 kB 🔴 +1.92 kB 🔴 +876 B 🔴 +768 B
assets/UpdatePasswordContent-Da4LOmfI.js (removed) 1.92 kB 🟢 -1.92 kB 🟢 -878 B 🟢 -768 B
assets/authStore-CITZUTjF.js (new) 130 B 🔴 +130 B 🔴 +109 B 🔴 +104 B
assets/authStore-FXiqJTKi.js (removed) 130 B 🟢 -130 B 🟢 -109 B 🟢 -106 B
assets/workspaceAuthStore-BVbrGT7j.js (new) 110 B 🔴 +110 B 🔴 +104 B 🔴 +114 B
assets/workspaceAuthStore-mQ4zjWoU.js (removed) 110 B 🟢 -110 B 🟢 -104 B 🟢 -114 B
assets/auth-DHN5beEs.js (new) 105 B 🔴 +105 B 🔴 +96 B 🔴 +83 B
assets/auth-MtmuvACg.js (removed) 105 B 🟢 -105 B 🟢 -96 B 🟢 -84 B

Status: 7 added / 7 removed / 3 unchanged

Editors & Dialogs — 117 kB (baseline 117 kB) • ⚪ 0 B

Modals, dialogs, drawers, and in-app editors

File Before After Δ Raw Δ Gzip Δ Brotli
assets/ComfyHubPublishDialog-Cd_pjvVl.js (new) 90.5 kB 🔴 +90.5 kB 🔴 +19.3 kB 🔴 +16.5 kB
assets/ComfyHubPublishDialog-Cqxb2y-G.js (removed) 90.5 kB 🟢 -90.5 kB 🟢 -19.3 kB 🟢 -16.5 kB
assets/useShareDialog-BbAdipCL.js (removed) 23.7 kB 🟢 -23.7 kB 🟢 -5.59 kB 🟢 -4.98 kB
assets/useShareDialog-pO4ugBy9.js (new) 23.7 kB 🔴 +23.7 kB 🔴 +5.58 kB 🔴 +4.97 kB
assets/ComfyHubPublishDialog-CqhSn07Z.js (removed) 143 B 🟢 -143 B 🟢 -105 B 🟢 -88 B
assets/ComfyHubPublishDialog-D2UxSNhS.js (new) 143 B 🔴 +143 B 🔴 +105 B 🔴 +91 B
assets/useSubscriptionDialog-Cd2KBSnj.js (removed) 110 B 🟢 -110 B 🟢 -102 B 🟢 -98 B
assets/useSubscriptionDialog-Iv2jVuMf.js (new) 110 B 🔴 +110 B 🔴 +102 B 🔴 +87 B

Status: 4 added / 4 removed / 1 unchanged

UI Components — 57.5 kB (baseline 57.5 kB) • ⚪ 0 B

Reusable component library chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/ComfyQueueButton-B-YKsNa6.js (new) 13.6 kB 🔴 +13.6 kB 🔴 +3.82 kB 🔴 +3.41 kB
assets/ComfyQueueButton-Oc3DugdP.js (removed) 13.6 kB 🟢 -13.6 kB 🟢 -3.82 kB 🟢 -3.41 kB
assets/useTerminalTabs-DD7qUxle.js (new) 12.1 kB 🔴 +12.1 kB 🔴 +3.84 kB 🔴 +3.38 kB
assets/useTerminalTabs-fbn4-Iz9.js (removed) 12.1 kB 🟢 -12.1 kB 🟢 -3.84 kB 🟢 -3.38 kB
assets/SubscribeButton-B7g7nLhN.js (removed) 2.38 kB 🟢 -2.38 kB 🟢 -1.05 kB 🟢 -946 B
assets/SubscribeButton-DYemF-6P.js (new) 2.38 kB 🔴 +2.38 kB 🔴 +1.05 kB 🔴 +952 B
assets/cloudFeedbackTopbarButton-Cio69ouS.js (removed) 829 B 🟢 -829 B 🟢 -495 B 🟢 -421 B
assets/cloudFeedbackTopbarButton-DzOkawnd.js (new) 829 B 🔴 +829 B 🔴 +498 B 🔴 +418 B
assets/ComfyQueueButton-C9zJgNvN.js (new) 128 B 🔴 +128 B 🔴 +99 B 🔴 +91 B
assets/ComfyQueueButton-DRUo0Llt.js (removed) 128 B 🟢 -128 B 🟢 -99 B 🟢 -90 B

Status: 5 added / 5 removed / 8 unchanged

Data & Services — 275 kB (baseline 275 kB) • ⚪ 0 B

Stores, services, APIs, and repositories

File Before After Δ Raw Δ Gzip Δ Brotli
assets/load3dService-CAx4nHf3.js (new) 132 kB 🔴 +132 kB 🔴 +29.6 kB 🔴 +25 kB
assets/load3dService-DIsXFVny.js (removed) 132 kB 🟢 -132 kB 🟢 -29.6 kB 🟢 -24.9 kB
assets/api-Ba4_z64o.js (new) 91.5 kB 🔴 +91.5 kB 🔴 +25.3 kB 🔴 +21.6 kB
assets/api-C_eFOPcg.js (removed) 91.5 kB 🟢 -91.5 kB 🟢 -25.3 kB 🟢 -21.7 kB
assets/workflowShareService-Cn1qJ4zh.js (removed) 17 kB 🟢 -17 kB 🟢 -5.01 kB 🟢 -4.45 kB
assets/workflowShareService-e0fgTBlV.js (new) 17 kB 🔴 +17 kB 🔴 +5.01 kB 🔴 +4.43 kB
assets/releaseStore-CnRLmdlO.js (removed) 8.2 kB 🟢 -8.2 kB 🟢 -2.32 kB 🟢 -2.03 kB
assets/releaseStore-pGyCPXyL.js (new) 8.2 kB 🔴 +8.2 kB 🔴 +2.33 kB 🔴 +2.03 kB
assets/keybindingService-HIT9r3iQ.js (removed) 7.46 kB 🟢 -7.46 kB 🟢 -1.92 kB 🟢 -1.64 kB
assets/keybindingService-MhF2k-6e.js (new) 7.46 kB 🔴 +7.46 kB 🔴 +1.92 kB 🔴 +1.64 kB
assets/extensionStore-Bj8NQ5rc.js (removed) 5.29 kB 🟢 -5.29 kB 🟢 -1.86 kB 🟢 -1.57 kB
assets/extensionStore-ChApwCq4.js (new) 5.29 kB 🔴 +5.29 kB 🔴 +1.86 kB 🔴 +1.57 kB
assets/userStore-Ctjp8TKh.js (new) 2.42 kB 🔴 +2.42 kB 🔴 +932 B 🔴 +818 B
assets/userStore-CUKG9yw8.js (removed) 2.42 kB 🟢 -2.42 kB 🟢 -932 B 🟢 -821 B
assets/audioService-BbOwarLg.js (removed) 1.76 kB 🟢 -1.76 kB 🟢 -860 B 🟢 -749 B
assets/audioService-ByhTzWRo.js (new) 1.76 kB 🔴 +1.76 kB 🔴 +861 B 🔴 +750 B
assets/dialogService-CKo8sHx9.js (new) 100 B 🔴 +100 B 🔴 +99 B 🔴 +90 B
assets/dialogService-Dih_TdnK.js (removed) 100 B 🟢 -100 B 🟢 -99 B 🟢 -100 B
assets/settingStore-DmDHfhB-.js (new) 98 B 🔴 +98 B 🔴 +98 B 🔴 +88 B
assets/settingStore-WgScHTeb.js (removed) 98 B 🟢 -98 B 🟢 -98 B 🟢 -100 B
assets/assetsStore-ByefEPlv.js (new) 96 B 🔴 +96 B 🔴 +97 B 🔴 +89 B
assets/assetsStore-Dp1M4gRL.js (removed) 96 B 🟢 -96 B 🟢 -97 B 🟢 -89 B
assets/releaseStore-BLSiajDK.js (new) 95 B 🔴 +95 B 🔴 +86 B 🔴 +87 B
assets/releaseStore-BT8xTcsz.js (removed) 95 B 🟢 -95 B 🟢 -86 B 🟢 -88 B
assets/api-cdD6gHz0.js (new) 62 B 🔴 +62 B 🔴 +74 B 🔴 +66 B
assets/api-Dj8TUnjH.js (removed) 62 B 🟢 -62 B 🟢 -74 B 🟢 -66 B

Status: 13 added / 13 removed / 3 unchanged

Utilities & Hooks — 3.42 MB (baseline 3.42 MB) • 🔴 +3.01 kB

Helpers, composables, and utility bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/promotionUtils-1WX6snTs.js (new) 3.06 MB 🔴 +3.06 MB 🔴 +707 kB 🔴 +533 kB
assets/promotionUtils-Bs9FhZej.js (removed) 3.05 MB 🟢 -3.05 MB 🟢 -707 kB 🟢 -533 kB
assets/useConflictDetection-BKMgAY53.js (removed) 234 kB 🟢 -234 kB 🟢 -52.3 kB 🟢 -42.6 kB
assets/useConflictDetection-D-rxTOYn.js (new) 234 kB 🔴 +234 kB 🔴 +52.3 kB 🔴 +42.6 kB
assets/useLoad3d-C-7swaZu.js (removed) 25.7 kB 🟢 -25.7 kB 🟢 -5.82 kB 🟢 -5.15 kB
assets/useLoad3d-xhQ6jjsk.js (new) 25.7 kB 🔴 +25.7 kB 🔴 +5.82 kB 🔴 +5.15 kB
assets/useLoad3dViewer-Bl0yW-9a.js (removed) 21.2 kB 🟢 -21.2 kB 🟢 -4.98 kB 🟢 -4.37 kB
assets/useLoad3dViewer-BV2Nrzen.js (new) 21.2 kB 🔴 +21.2 kB 🔴 +4.98 kB 🔴 +4.37 kB
assets/useFeatureFlags-VlSvHS07.js (removed) 6.51 kB 🟢 -6.51 kB 🟢 -2.02 kB 🟢 -1.7 kB
assets/useFeatureFlags-CkUNsKEc.js (new) 6.46 kB 🔴 +6.46 kB 🔴 +2.01 kB 🔴 +1.68 kB
assets/downloadUtil-BwdMLPVp.js (removed) 4.68 kB 🟢 -4.68 kB 🟢 -1.85 kB 🟢 -1.54 kB
assets/downloadUtil-DYvovsO-.js (new) 4.68 kB 🔴 +4.68 kB 🔴 +1.85 kB 🔴 +1.54 kB
assets/subscriptionCheckoutUtil-CweoWsWo.js (new) 3.85 kB 🔴 +3.85 kB 🔴 +1.54 kB 🔴 +1.35 kB
assets/subscriptionCheckoutUtil-Dic2ekTV.js (removed) 3.85 kB 🟢 -3.85 kB 🟢 -1.54 kB 🟢 -1.35 kB
assets/useSessionCookie-DZwHdK6P.js (removed) 3.61 kB 🟢 -3.61 kB 🟢 -1.21 kB 🟢 -1.05 kB
assets/useSessionCookie-UFBrSBPi.js (new) 3.61 kB 🔴 +3.61 kB 🔴 +1.21 kB 🔴 +1.03 kB
assets/useExternalLink-BKr8lYv9.js (removed) 3.03 kB 🟢 -3.03 kB 🟢 -1.16 kB 🟢 -1.03 kB
assets/useExternalLink-F1pE8Gb0.js (new) 3.03 kB 🔴 +3.03 kB 🔴 +1.16 kB 🔴 +1.03 kB
assets/useDowngradeToPersonal-Bbsmu6RX.js (removed) 3 kB 🟢 -3 kB 🟢 -1.18 kB 🟢 -1.03 kB
assets/useDowngradeToPersonal-pijkxlXD.js (new) 3 kB 🔴 +3 kB 🔴 +1.18 kB 🔴 +1.05 kB
assets/assetPreviewUtil-B6sQtPRv.js (new) 2.41 kB 🔴 +2.41 kB 🔴 +1 kB 🔴 +875 B
assets/assetPreviewUtil-jQ1m55sY.js (removed) 2.41 kB 🟢 -2.41 kB 🟢 -1 kB 🟢 -873 B
assets/useUpstreamValue-CibdNOFb.js (removed) 2.04 kB 🟢 -2.04 kB 🟢 -793 B 🟢 -711 B
assets/useUpstreamValue-u-N1VO21.js (new) 2.04 kB 🔴 +2.04 kB 🔴 +794 B 🔴 +711 B
assets/useWorkspaceTierLabel-BpcNwix3.js (removed) 1.99 kB 🟢 -1.99 kB 🟢 -853 B 🟢 -730 B
assets/useWorkspaceTierLabel-BPM0hfv7.js (new) 1.99 kB 🔴 +1.99 kB 🔴 +854 B 🔴 +737 B
assets/useErrorHandling-C46EsPsz.js (new) 1.54 kB 🔴 +1.54 kB 🔴 +646 B 🔴 +547 B
assets/useErrorHandling-CuG36Xz9.js (removed) 1.54 kB 🟢 -1.54 kB 🟢 -647 B 🟢 -556 B
assets/useLoad3d-DmqOIQPc.js (new) 311 B 🔴 +311 B 🔴 +163 B 🔴 +148 B
assets/useLoad3d-DxptWji1.js (removed) 311 B 🟢 -311 B 🟢 -163 B 🟢 -148 B
assets/useSessionCookie-8J6TV-Jm.js (new) 101 B 🔴 +101 B 🔴 +86 B 🔴 +84 B
assets/useSessionCookie-BBbbCMDL.js (removed) 101 B 🟢 -101 B 🟢 -86 B 🟢 -78 B
assets/useFeatureFlags-CoqT27Vf.js (new) 98 B 🔴 +98 B 🔴 +85 B 🔴 +82 B
assets/useFeatureFlags-Dx67bzCG.js (removed) 98 B 🟢 -98 B 🟢 -85 B 🟢 -91 B
assets/useLoad3dViewer-D02_AMKP.js (new) 98 B 🔴 +98 B 🔴 +85 B 🔴 +82 B
assets/useLoad3dViewer-D4HhcG9N.js (removed) 98 B 🟢 -98 B 🟢 -85 B 🟢 -86 B
assets/useCurrentUser-Blq88p6q.js (removed) 96 B 🟢 -96 B 🟢 -97 B 🟢 -91 B
assets/useCurrentUser-Dxmig6PM.js (new) 96 B 🔴 +96 B 🔴 +97 B 🔴 +82 B

Status: 19 added / 19 removed / 17 unchanged

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

External libraries and shared vendor chunks

Status: 16 unchanged

Other — 11.9 MB (baseline 11.9 MB) • 🔴 +8.97 kB

Bundles that do not match a named category

File Before After Δ Raw Δ Gzip Δ Brotli
assets/main-CYrFvMta.js (new) 189 kB 🔴 +189 kB 🔴 +55.2 kB 🔴 +46.4 kB
assets/main-D1kxZjBy.js (removed) 188 kB 🟢 -188 kB 🟢 -54.8 kB 🟢 -46.1 kB
assets/core-B36uTOOv.js (removed) 121 kB 🟢 -121 kB 🟢 -31 kB 🟢 -26.2 kB
assets/core-CjMDhJsR.js (new) 121 kB 🔴 +121 kB 🔴 +31.1 kB 🔴 +26.2 kB
assets/WidgetSelect-_o5oJzWj.js (new) 89.9 kB 🔴 +89.9 kB 🔴 +20.4 kB 🔴 +17.4 kB
assets/WidgetSelect-Q1vxlQ6u.js (removed) 89.9 kB 🟢 -89.9 kB 🟢 -20.4 kB 🟢 -17.5 kB
assets/Load3D-BYKyB_xu.js (new) 68.9 kB 🔴 +68.9 kB 🔴 +11 kB 🔴 +9.52 kB
assets/Load3D-GSTfkJlA.js (removed) 68.9 kB 🟢 -68.9 kB 🟢 -11 kB 🟢 -9.52 kB
assets/Preview3d-BP2PDdGg.js (removed) 51 kB 🟢 -51 kB 🟢 -8.36 kB 🟢 -7.29 kB
assets/Preview3d-CattxSi4.js (new) 51 kB 🔴 +51 kB 🔴 +8.36 kB 🔴 +7.29 kB
assets/SubscriptionTransitionPreviewWorkspace-BwHPjzM8.js (removed) 46.8 kB 🟢 -46.8 kB 🟢 -9.66 kB 🟢 -8.54 kB
assets/SubscriptionTransitionPreviewWorkspace-CGoG7dDe.js (new) 46.8 kB 🔴 +46.8 kB 🔴 +9.66 kB 🔴 +8.54 kB
assets/WorkspacePanelContent-B5rLwJMT.js (new) 42 kB 🔴 +42 kB 🔴 +8.87 kB 🔴 +7.85 kB
assets/SubscriptionRequiredDialogContentUnified-BsQgwUZf.js (removed) 41.2 kB 🟢 -41.2 kB 🟢 -9.16 kB 🟢 -7.97 kB
assets/SubscriptionRequiredDialogContentUnified-CL6bO4jg.js (new) 41.2 kB 🔴 +41.2 kB 🔴 +9.16 kB 🔴 +7.97 kB
assets/WorkspacePanelContent-IonEWnS6.js (removed) 34.5 kB 🟢 -34.5 kB 🟢 -7.49 kB 🟢 -6.61 kB
assets/WidgetBoundingBoxes-CEu1tIs9.js (new) 33.8 kB 🔴 +33.8 kB 🔴 +9.2 kB 🔴 +8.15 kB
assets/WidgetBoundingBoxes-DQ-cKr5T.js (removed) 33.8 kB 🟢 -33.8 kB 🟢 -9.2 kB 🟢 -8.15 kB
assets/WidgetPainter-Byv4DiUA.js (new) 32.7 kB 🔴 +32.7 kB 🔴 +7.9 kB 🔴 +7 kB
assets/WidgetPainter-C_DzKs1-.js (removed) 32.7 kB 🟢 -32.7 kB 🟢 -7.9 kB 🟢 -7.01 kB
assets/Load3dViewerContent-CAfa9-ug.js (new) 30.9 kB 🔴 +30.9 kB 🔴 +6.3 kB 🔴 +5.47 kB
assets/Load3dViewerContent-CghugzUm.js (removed) 30.9 kB 🟢 -30.9 kB 🟢 -6.3 kB 🟢 -5.47 kB
assets/HdrViewerContent-B5GBNjLV.js (removed) 29.8 kB 🟢 -29.8 kB 🟢 -8.27 kB 🟢 -7.3 kB
assets/HdrViewerContent-Dbe_OyPS.js (new) 29.8 kB 🔴 +29.8 kB 🔴 +8.27 kB 🔴 +7.3 kB
assets/SubscriptionRequiredDialogContent-4BXXvRfa.js (new) 27.4 kB 🔴 +27.4 kB 🔴 +6.75 kB 🔴 +5.96 kB
assets/SubscriptionRequiredDialogContent-wxIQmqSH.js (removed) 27.4 kB 🟢 -27.4 kB 🟢 -6.75 kB 🟢 -5.98 kB
assets/initHostTelemetry-BB0ov_HW.js (removed) 26.4 kB 🟢 -26.4 kB 🟢 -6.42 kB 🟢 -5.35 kB
assets/initHostTelemetry-D_cZWQAF.js (new) 26.4 kB 🔴 +26.4 kB 🔴 +6.42 kB 🔴 +5.36 kB
assets/SubscriptionPanelContentWorkspace-IvA1oTAd.js (new) 25 kB 🔴 +25 kB 🔴 +5.85 kB 🔴 +5.14 kB
assets/SubscriptionPanelContentWorkspace-Cz1tIrc7.js (removed) 25 kB 🟢 -25 kB 🟢 -5.84 kB 🟢 -5.13 kB
assets/SubscriptionRequiredDialogContentWorkspace-DFL_u-mE.js (new) 24.6 kB 🔴 +24.6 kB 🔴 +5.66 kB 🔴 +4.96 kB
assets/SubscriptionRequiredDialogContentWorkspace-Jk-QED1W.js (removed) 24.6 kB 🟢 -24.6 kB 🟢 -5.66 kB 🟢 -4.96 kB
assets/WidgetImageCrop-CAdv_Klm.js (new) 23.3 kB 🔴 +23.3 kB 🔴 +5.75 kB 🔴 +5.04 kB
assets/WidgetImageCrop-CX-kM3Ze.js (removed) 23.3 kB 🟢 -23.3 kB 🟢 -5.75 kB 🟢 -5.05 kB
assets/load3d-DmMCqwyZ.js (new) 22.3 kB 🔴 +22.3 kB 🔴 +5.45 kB 🔴 +4.71 kB
assets/load3d-RrWt0JLl.js (removed) 22.3 kB 🟢 -22.3 kB 🟢 -5.45 kB 🟢 -4.71 kB
assets/CurrentUserPopoverWorkspace-BB8fHpJ7.js (removed) 20.6 kB 🟢 -20.6 kB 🟢 -4.75 kB 🟢 -4.23 kB
assets/CurrentUserPopoverWorkspace-CFrOdmpl.js (new) 20.6 kB 🔴 +20.6 kB 🔴 +4.74 kB 🔴 +4.23 kB
assets/SignInContent-B78V0zkh.js (removed) 20.1 kB 🟢 -20.1 kB 🟢 -5.07 kB 🟢 -4.43 kB
assets/SignInContent-f_tIGgAC.js (new) 20.1 kB 🔴 +20.1 kB 🔴 +5.07 kB 🔴 +4.43 kB
assets/WidgetInputNumber-Ce-a5xXo.js (new) 19.1 kB 🔴 +19.1 kB 🔴 +4.82 kB 🔴 +4.28 kB
assets/WidgetInputNumber-Cw2_pB4U.js (removed) 19.1 kB 🟢 -19.1 kB 🟢 -4.82 kB 🟢 -4.28 kB
assets/CreditsTile-BfkZ6QWd.js (removed) 17.1 kB 🟢 -17.1 kB 🟢 -4.54 kB 🟢 -4 kB
assets/CreditsTile-Dd_L362Z.js (new) 17.1 kB 🔴 +17.1 kB 🔴 +4.54 kB 🔴 +4.01 kB
assets/WidgetRecordAudio-CTW-sqkI.js (new) 16.6 kB 🔴 +16.6 kB 🔴 +4.63 kB 🔴 +4.14 kB
assets/WidgetRecordAudio-tu6Wy9pB.js (removed) 16.6 kB 🟢 -16.6 kB 🟢 -4.63 kB 🟢 -4.14 kB
assets/WidgetRange-BGrpm7hw.js (new) 16.2 kB 🔴 +16.2 kB 🔴 +4.16 kB 🔴 +3.71 kB
assets/WidgetRange-ZAWbUWhM.js (removed) 16.2 kB 🟢 -16.2 kB 🟢 -4.17 kB 🟢 -3.73 kB
assets/WaveAudioPlayer-Bix78w2y.js (new) 12.8 kB 🔴 +12.8 kB 🔴 +3.48 kB 🔴 +3.06 kB
assets/WaveAudioPlayer-lH79nl-_.js (removed) 12.8 kB 🟢 -12.8 kB 🟢 -3.48 kB 🟢 -3.06 kB
assets/i18n-BucEgvSl.js (removed) 12.5 kB 🟢 -12.5 kB 🟢 -3.32 kB 🟢 -2.81 kB
assets/i18n-CXffSCsG.js (new) 12.5 kB 🔴 +12.5 kB 🔴 +3.32 kB 🔴 +2.81 kB
assets/WidgetCurve-BI5Zp1ci.js (removed) 11.3 kB 🟢 -11.3 kB 🟢 -3.5 kB 🟢 -3.16 kB
assets/WidgetCurve-DmI7cDiG.js (new) 11.3 kB 🔴 +11.3 kB 🔴 +3.5 kB 🔴 +3.16 kB
assets/AudioPreviewPlayer-DBDhgKzw.js (new) 10.6 kB 🔴 +10.6 kB 🔴 +3.06 kB 🔴 +2.73 kB
assets/AudioPreviewPlayer-DXQ3NWSb.js (removed) 10.6 kB 🟢 -10.6 kB 🟢 -3.06 kB 🟢 -2.73 kB
assets/TeamWorkspacesDialogContent-D4d49bOh.js (removed) 10.3 kB 🟢 -10.3 kB 🟢 -3 kB 🟢 -2.66 kB
assets/TeamWorkspacesDialogContent-k1z_gGQv.js (new) 10.3 kB 🔴 +10.3 kB 🔴 +3 kB 🔴 +2.66 kB
assets/Load3DConfiguration-BsAHMrl7.js (removed) 9.02 kB 🟢 -9.02 kB 🟢 -2.66 kB 🟢 -2.35 kB
assets/Load3DConfiguration-CXy6Ttam.js (new) 9.02 kB 🔴 +9.02 kB 🔴 +2.66 kB 🔴 +2.35 kB
assets/onboardingCloudRoutes-Db0uVjWg.js (new) 8.5 kB 🔴 +8.5 kB 🔴 +2.66 kB 🔴 +2.28 kB
assets/onboardingCloudRoutes-tIMjK6rA.js (removed) 8.5 kB 🟢 -8.5 kB 🟢 -2.67 kB 🟢 -2.28 kB
assets/nodeTemplates-89iPwquo.js (new) 8.33 kB 🔴 +8.33 kB 🔴 +2.88 kB 🔴 +2.54 kB
assets/nodeTemplates-Dijpy1-e.js (removed) 8.33 kB 🟢 -8.33 kB 🟢 -2.87 kB 🟢 -2.54 kB
assets/NightlySurveyController-BISoyP1D.js (new) 8.01 kB 🔴 +8.01 kB 🔴 +2.71 kB 🔴 +2.38 kB
assets/NightlySurveyController-CUzgJ9Ny.js (removed) 8.01 kB 🟢 -8.01 kB 🟢 -2.71 kB 🟢 -2.41 kB
assets/InviteMemberDialogContent-D7yySjv3.js (new) 6.76 kB 🔴 +6.76 kB 🔴 +2.22 kB 🔴 +1.96 kB
assets/InviteMemberDialogContent-DoytlD9n.js (removed) 6.76 kB 🟢 -6.76 kB 🟢 -2.22 kB 🟢 -1.96 kB
assets/WidgetWithControl-CJhn5ORv.js (removed) 6.38 kB 🟢 -6.38 kB 🟢 -2.59 kB 🟢 -2.31 kB
assets/WidgetWithControl-pkUenP-v.js (new) 6.38 kB 🔴 +6.38 kB 🔴 +2.59 kB 🔴 +2.29 kB
assets/tierBenefits-Cnxn33vB.js (new) 6.02 kB 🔴 +6.02 kB 🔴 +1.93 kB 🔴 +1.67 kB
assets/tierBenefits-iZwXMy6L.js (removed) 6.02 kB 🟢 -6.02 kB 🟢 -1.93 kB 🟢 -1.68 kB
assets/load3dPreviewExtensions-CNI5RA9P.js (new) 5.95 kB 🔴 +5.95 kB 🔴 +1.85 kB 🔴 +1.64 kB
assets/load3dPreviewExtensions-FOTtZOtT.js (removed) 5.95 kB 🟢 -5.95 kB 🟢 -1.85 kB 🟢 -1.64 kB
assets/main-Dgu6xnFD.js (new) 5.78 kB 🔴 +5.78 kB 🔴 +1.84 kB 🔴 +1.56 kB
assets/main-Dly2M-m9.js (removed) 5.78 kB 🟢 -5.78 kB 🟢 -1.84 kB 🟢 -1.56 kB
assets/CancelSubscriptionDialogContent-B8ziVFNx.js (new) 5.76 kB 🔴 +5.76 kB 🔴 +1.97 kB 🔴 +1.73 kB
assets/CancelSubscriptionDialogContent-DGnMZy5M.js (removed) 5.76 kB 🟢 -5.76 kB 🟢 -1.98 kB 🟢 -1.73 kB
assets/FreeTierDialogContent-By69hxf4.js (new) 5.25 kB 🔴 +5.25 kB 🔴 +1.76 kB 🔴 +1.55 kB
assets/FreeTierDialogContent-DMQmz9q2.js (removed) 5.25 kB 🟢 -5.25 kB 🟢 -1.76 kB 🟢 -1.55 kB
assets/CreateWorkspaceDialogContent-DICk26co.js (removed) 5.19 kB 🟢 -5.19 kB 🟢 -1.83 kB 🟢 -1.58 kB
assets/CreateWorkspaceDialogContent-Dot29I18.js (new) 5.19 kB 🔴 +5.19 kB 🔴 +1.83 kB 🔴 +1.58 kB
assets/missingModelDownload-C0VktyMM.js (new) 5.07 kB 🔴 +5.07 kB 🔴 +1.98 kB 🔴 +1.72 kB
assets/missingModelDownload-IhN6o8dz.js (removed) 5.07 kB 🟢 -5.07 kB 🟢 -1.98 kB 🟢 -1.72 kB
assets/ChangeMemberRoleDialogContent-Dw19KDSR.js (removed) 5.04 kB 🟢 -5.04 kB 🟢 -1.67 kB 🟢 -1.46 kB
assets/ChangeMemberRoleDialogContent-hV23sH7P.js (new) 5.04 kB 🔴 +5.04 kB 🔴 +1.67 kB 🔴 +1.46 kB
assets/EditWorkspaceDialogContent-Bxc4apDz.js (removed) 5 kB 🟢 -5 kB 🟢 -1.79 kB 🟢 -1.56 kB
assets/EditWorkspaceDialogContent-DSQq5S0o.js (new) 5 kB 🔴 +5 kB 🔴 +1.79 kB 🔴 +1.56 kB
assets/WidgetTextarea-CQ24_R9N.js (new) 4.86 kB 🔴 +4.86 kB 🔴 +1.89 kB 🔴 +1.68 kB
assets/WidgetTextarea-D7ctTYw-.js (removed) 4.86 kB 🟢 -4.86 kB 🟢 -1.89 kB 🟢 -1.66 kB
assets/saveMesh-BrX7te6W.js (new) 4.81 kB 🔴 +4.81 kB 🔴 +1.56 kB 🔴 +1.37 kB
assets/saveMesh-Dowc0Rrb.js (removed) 4.81 kB 🟢 -4.81 kB 🟢 -1.56 kB 🟢 -1.37 kB
assets/ValueControlPopover-BizDZ0kh.js (new) 4.55 kB 🔴 +4.55 kB 🔴 +1.59 kB 🔴 +1.42 kB
assets/ValueControlPopover-Dexk121H.js (removed) 4.55 kB 🟢 -4.55 kB 🟢 -1.59 kB 🟢 -1.42 kB
assets/ApiNodesSignInContent-BSKUeni3.js (new) 4.14 kB 🔴 +4.14 kB 🔴 +1.34 kB 🔴 +1.18 kB
assets/ApiNodesSignInContent-CG3bIroE.js (removed) 4.14 kB 🟢 -4.14 kB 🟢 -1.34 kB 🟢 -1.18 kB
assets/DeleteWorkspaceDialogContent-BB8QkqvT.js (new) 3.91 kB 🔴 +3.91 kB 🔴 +1.47 kB 🔴 +1.27 kB
assets/DeleteWorkspaceDialogContent-wvTaqGMv.js (removed) 3.91 kB 🟢 -3.91 kB 🟢 -1.47 kB 🟢 -1.27 kB
assets/LeaveWorkspaceDialogContent-BgbeQRhu.js (new) 3.73 kB 🔴 +3.73 kB 🔴 +1.42 kB 🔴 +1.22 kB
assets/LeaveWorkspaceDialogContent-no-YP36U.js (removed) 3.73 kB 🟢 -3.73 kB 🟢 -1.42 kB 🟢 -1.23 kB
assets/RemoveMemberDialogContent-CzCLgE0J.js (new) 3.71 kB 🔴 +3.71 kB 🔴 +1.37 kB 🔴 +1.19 kB
assets/RemoveMemberDialogContent-KQdLulbB.js (removed) 3.71 kB 🟢 -3.71 kB 🟢 -1.37 kB 🟢 -1.19 kB
assets/RevokeInviteDialogContent-CK9M9XN5.js (removed) 3.63 kB 🟢 -3.63 kB 🟢 -1.38 kB 🟢 -1.2 kB
assets/RevokeInviteDialogContent-WsGR2eQd.js (new) 3.63 kB 🔴 +3.63 kB 🔴 +1.38 kB 🔴 +1.21 kB
assets/InviteMemberUpsellDialogContent-B0LY_A80.js (new) 3.52 kB 🔴 +3.52 kB 🔴 +1.27 kB 🔴 +1.12 kB
assets/InviteMemberUpsellDialogContent-BTNVuhFU.js (removed) 3.52 kB 🟢 -3.52 kB 🟢 -1.27 kB 🟢 -1.11 kB
assets/workspaceCheckoutTelemetry-BqSmdz-o.js (removed) 3.47 kB 🟢 -3.47 kB 🟢 -1.56 kB 🟢 -1.35 kB
assets/workspaceCheckoutTelemetry-XeJANUUk.js (new) 3.47 kB 🔴 +3.47 kB 🔴 +1.56 kB 🔴 +1.36 kB
assets/Media3DTop-ByVcGASP.js (removed) 3.26 kB 🟢 -3.26 kB 🟢 -1.3 kB 🟢 -1.13 kB
assets/Media3DTop-CBPQ10Jr.js (new) 3.26 kB 🔴 +3.26 kB 🔴 +1.3 kB 🔴 +1.13 kB
assets/GlobalToast-B2AlpwXK.js (removed) 3.05 kB 🟢 -3.05 kB 🟢 -1.26 kB 🟢 -1.09 kB
assets/GlobalToast-G3JVLhir.js (new) 3.05 kB 🔴 +3.05 kB 🔴 +1.26 kB 🔴 +1.08 kB
assets/load3dAdvanced-Cvk1mWXi.js (removed) 2.87 kB 🟢 -2.87 kB 🟢 -1.13 kB 🟢 -983 B
assets/load3dAdvanced-CyclBu7u.js (new) 2.87 kB 🔴 +2.87 kB 🔴 +1.13 kB 🔴 +979 B
assets/SubscribeToRun--OgrMF5l.js (new) 2.56 kB 🔴 +2.56 kB 🔴 +1.11 kB 🔴 +972 B
assets/SubscribeToRun-CInlsEr4.js (removed) 2.56 kB 🟢 -2.56 kB 🟢 -1.11 kB 🟢 -970 B
assets/graphHasMissingNodes-Cn6dWx47.js (removed) 1.93 kB 🟢 -1.93 kB 🟢 -907 B 🟢 -792 B
assets/graphHasMissingNodes-CpLurKil.js (new) 1.93 kB 🔴 +1.93 kB 🔴 +906 B 🔴 +805 B
assets/MediaAudioTop-BRw8i1xa.js (new) 1.67 kB 🔴 +1.67 kB 🔴 +835 B 🔴 +696 B
assets/MediaAudioTop-DyolDbEL.js (removed) 1.67 kB 🟢 -1.67 kB 🟢 -835 B 🟢 -696 B
assets/signInSchema-ap64edE7.js (new) 1.6 kB 🔴 +1.6 kB 🔴 +586 B 🔴 +507 B
assets/signInSchema-CvZPEHY5.js (removed) 1.6 kB 🟢 -1.6 kB 🟢 -586 B 🟢 -548 B
assets/CloudRunButtonWrapper-BDR2fRYS.js (new) 1.13 kB 🔴 +1.13 kB 🔴 +551 B 🔴 +487 B
assets/CloudRunButtonWrapper-BU5KYZ9p.js (removed) 1.13 kB 🟢 -1.13 kB 🟢 -551 B 🟢 -511 B
assets/cloudSessionCookie-D8OeBqg-.js (new) 991 B 🔴 +991 B 🔴 +467 B 🔴 +412 B
assets/cloudSessionCookie-DKvEOQhk.js (removed) 991 B 🟢 -991 B 🟢 -468 B 🟢 -417 B
assets/cloudBadges-BHBcr3Zj.js (new) 973 B 🔴 +973 B 🔴 +550 B 🔴 +471 B
assets/cloudBadges-CKJkNIVn.js (removed) 973 B 🟢 -973 B 🟢 -552 B 🟢 -469 B
assets/Load3DAdvanced-Bx2ZJuOb.js (removed) 813 B 🟢 -813 B 🟢 -457 B 🟢 -411 B
assets/Load3DAdvanced-JfrkIprd.js (new) 813 B 🔴 +813 B 🔴 +454 B 🔴 +408 B
assets/nightlyBadges-B_0DTGkM.js (new) 464 B 🔴 +464 B 🔴 +306 B 🔴 +297 B
assets/nightlyBadges-Dq4JJUuE.js (removed) 464 B 🟢 -464 B 🟢 -305 B 🟢 -253 B
assets/missingModelDownload-BcJC4cJa.js (new) 228 B 🔴 +228 B 🔴 +147 B 🔴 +127 B
assets/missingModelDownload-Dkz88Twg.js (removed) 228 B 🟢 -228 B 🟢 -150 B 🟢 -129 B
assets/SubscriptionPanelContentWorkspace-BfghB1mT.js (removed) 179 B 🟢 -179 B 🟢 -117 B 🟢 -89 B
assets/SubscriptionPanelContentWorkspace-CAgyE9a4.js (new) 179 B 🔴 +179 B 🔴 +117 B 🔴 +90 B
assets/Load3dViewerContent-CHJE0el5.js (new) 137 B 🔴 +137 B 🔴 +103 B 🔴 +95 B
assets/Load3dViewerContent-DtSlHNi0.js (removed) 137 B 🟢 -137 B 🟢 -103 B 🟢 -91 B
assets/Load3DAdvanced-BtqQBLAV.js (new) 122 B 🔴 +122 B 🔴 +97 B 🔴 +87 B
assets/Load3DAdvanced-D7PY_gi_.js (removed) 122 B 🟢 -122 B 🟢 -97 B 🟢 -88 B
assets/WidgetLegacy-BTx80D19.js (new) 119 B 🔴 +119 B 🔴 +108 B 🔴 +97 B
assets/WidgetLegacy-DkIQDxjX.js (removed) 119 B 🟢 -119 B 🟢 -108 B 🟢 -94 B
assets/workflowDraftStoreV2-6W6L5YCu.js (removed) 113 B 🟢 -113 B 🟢 -105 B 🟢 -111 B
assets/workflowDraftStoreV2-UpkB2mBc.js (new) 113 B 🔴 +113 B 🔴 +105 B 🔴 +114 B
assets/Load3D-DGlObIDK.js (new) 98 B 🔴 +98 B 🔴 +89 B 🔴 +78 B
assets/Load3D-yPEHvECj.js (removed) 98 B 🟢 -98 B 🟢 -89 B 🟢 -82 B
assets/i18n-173QDJdH.js (removed) 97 B 🟢 -97 B 🟢 -92 B 🟢 -87 B
assets/i18n-Dvdh3wRJ.js (new) 97 B 🔴 +97 B 🔴 +92 B 🔴 +85 B
assets/changeTracker-CauynnUg.js (removed) 93 B 🟢 -93 B 🟢 -95 B 🟢 -81 B
assets/changeTracker-R2XkWmh8.js (new) 93 B 🔴 +93 B 🔴 +95 B 🔴 +85 B

Status: 75 added / 75 removed / 88 unchanged

⚡ Performance Report

canvas-idle: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 53.1 MB heap
canvas-mouse-sweep: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 53.0 MB heap
canvas-zoom-sweep: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 60.2 MB heap
dom-widget-clipping: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 55.0 MB heap
large-graph-idle: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 57.5 MB heap
large-graph-pan: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 73.7 MB heap
large-graph-zoom: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 65.4 MB heap
minimap-idle: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 57.5 MB heap
subgraph-dom-widget-clipping: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 65.2 MB heap
subgraph-idle: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 54.1 MB heap
subgraph-mouse-sweep: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 47.3 MB heap
subgraph-transition-enter: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 162ms TBT · 89.4 MB heap
viewport-pan-sweep: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 65.9 MB heap
vue-large-graph-idle: · 58.1 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 157.5 MB heap
vue-large-graph-pan: · 58.1 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 33ms TBT · 161.8 MB heap
workflow-execution: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 44.5 MB heap

⚠️ 3 regressions detected

Show regressions
Metric Baseline PR (median) Δ Sig
canvas-zoom-sweep: layout duration 1ms 1ms +8% ⚠️ z=2.5
large-graph-pan: style recalc duration 18ms 19ms +7% ⚠️ z=2.1
workflow-execution: task duration 97ms 188ms +93% ⚠️ z=6.0
All metrics
Metric Baseline PR (median) Δ Sig
canvas-idle: avg frame time 17ms 17ms -0% z=-0.5
canvas-idle: p95 frame time 17ms 17ms -1%
canvas-idle: layout duration 0ms 0ms +0%
canvas-idle: style recalc duration 8ms 9ms +14% z=-1.7
canvas-idle: layout count 0 0 +0%
canvas-idle: style recalc count 9 10 +6% z=-2.9
canvas-idle: task duration 410ms 412ms +0% z=0.5
canvas-idle: script duration 15ms 18ms +17% z=-3.3
canvas-idle: TBT 0ms 0ms +0%
canvas-idle: heap used 45.0 MB 53.1 MB +18%
canvas-idle: DOM nodes -290 -284 -2% z=-240.0
canvas-idle: event listeners -199 -199 +0% z=-44.5
canvas-mouse-sweep: avg frame time 17ms 17ms -0% z=-0.4
canvas-mouse-sweep: p95 frame time 17ms 17ms +0%
canvas-mouse-sweep: layout duration 4ms 4ms +3% z=1.1
canvas-mouse-sweep: style recalc duration 37ms 43ms +16% z=-0.0
canvas-mouse-sweep: layout count 12 12 +0%
canvas-mouse-sweep: style recalc count 74 77 +3% z=-0.9
canvas-mouse-sweep: task duration 800ms 874ms +9% z=0.1
canvas-mouse-sweep: script duration 110ms 133ms +21% z=-0.5
canvas-mouse-sweep: TBT 0ms 0ms +0%
canvas-mouse-sweep: heap used 45.9 MB 53.0 MB +15%
canvas-mouse-sweep: DOM nodes -269 -89 -67% z=-58.5
canvas-mouse-sweep: event listeners -199 -97 -52% z=-25.2
canvas-zoom-sweep: avg frame time 17ms 17ms +0% z=0.5
canvas-zoom-sweep: p95 frame time 17ms 17ms +0%
canvas-zoom-sweep: layout duration 1ms 1ms +8% ⚠️ z=2.5
canvas-zoom-sweep: style recalc duration 16ms 19ms +17% z=-0.3
canvas-zoom-sweep: layout count 6 6 +0%
canvas-zoom-sweep: style recalc count 30 32 +7% z=1.6
canvas-zoom-sweep: task duration 322ms 349ms +8% z=0.9
canvas-zoom-sweep: script duration 18ms 21ms +21% z=-1.9
canvas-zoom-sweep: TBT 0ms 0ms +0%
canvas-zoom-sweep: heap used 58.9 MB 60.2 MB +2%
canvas-zoom-sweep: DOM nodes 76 -82 -207% z=-203.1
canvas-zoom-sweep: event listeners 19 -84 -539% z=-20.7
dom-widget-clipping: avg frame time 17ms 17ms +0% z=0.5
dom-widget-clipping: p95 frame time 17ms 17ms +1%
dom-widget-clipping: layout duration 0ms 0ms +0%
dom-widget-clipping: style recalc duration 8ms 9ms +22% z=-0.6
dom-widget-clipping: layout count 0 0 +0%
dom-widget-clipping: style recalc count 11 12 +5% z=-3.2
dom-widget-clipping: task duration 376ms 394ms +5% z=1.8
dom-widget-clipping: script duration 55ms 61ms +12% z=-2.1
dom-widget-clipping: TBT 0ms 0ms +0%
dom-widget-clipping: heap used 61.5 MB 55.0 MB -10%
dom-widget-clipping: DOM nodes -290 -128 -56% z=-105.8
dom-widget-clipping: event listeners -203 -101 -50% variance too high
large-graph-idle: avg frame time 17ms 17ms +0% z=-0.2
large-graph-idle: p95 frame time 17ms 17ms +1%
large-graph-idle: layout duration 0ms 0ms +0%
large-graph-idle: style recalc duration 8ms 7ms -16% z=-5.4
large-graph-idle: layout count 0 0 +0%
large-graph-idle: style recalc count 10 7 -30% z=-14.7
large-graph-idle: task duration 515ms 523ms +2% z=-0.3
large-graph-idle: script duration 86ms 89ms +3% z=-1.3
large-graph-idle: TBT 0ms 0ms +0%
large-graph-idle: heap used 61.1 MB 57.5 MB -6%
large-graph-idle: DOM nodes 20 -275 -1475% z=-331.6
large-graph-idle: event listeners 4 -198 -5050% z=-36.7
large-graph-pan: avg frame time 17ms 17ms +0% z=0.3
large-graph-pan: p95 frame time 17ms 17ms -1%
large-graph-pan: layout duration 0ms 0ms +0%
large-graph-pan: style recalc duration 18ms 19ms +7% ⚠️ z=2.1
large-graph-pan: layout count 0 0 +0%
large-graph-pan: style recalc count 69 69 -1% z=-1.7
large-graph-pan: task duration 1030ms 1090ms +6% z=0.2
large-graph-pan: script duration 360ms 389ms +8% z=-1.0
large-graph-pan: TBT 0ms 0ms +0%
large-graph-pan: heap used 78.0 MB 73.7 MB -5%
large-graph-pan: DOM nodes -275 -267 -3% z=-173.3
large-graph-pan: event listeners -197 -163 -18% z=-202.2
large-graph-zoom: avg frame time 17ms 17ms -0%
large-graph-zoom: p95 frame time 17ms 17ms -0%
large-graph-zoom: layout duration 9ms 8ms -15%
large-graph-zoom: style recalc duration 19ms 16ms -16%
large-graph-zoom: layout count 60 60 +0%
large-graph-zoom: style recalc count 64 63 -2%
large-graph-zoom: task duration 1439ms 1313ms -9%
large-graph-zoom: script duration 546ms 488ms -11%
large-graph-zoom: TBT 0ms 0ms +0%
large-graph-zoom: heap used 68.1 MB 65.4 MB -4%
large-graph-zoom: DOM nodes 8 -138 -1819%
large-graph-zoom: event listeners 8 8 +0%
minimap-idle: avg frame time 17ms 17ms -0% z=-0.4
minimap-idle: p95 frame time 17ms 17ms +0%
minimap-idle: layout duration 0ms 0ms +0%
minimap-idle: style recalc duration 5ms 6ms +18% z=-3.9
minimap-idle: layout count 0 0 +0%
minimap-idle: style recalc count 7 6 -14% z=-5.2
minimap-idle: task duration 502ms 543ms +8% z=0.3
minimap-idle: script duration 73ms 97ms +33% z=-0.1
minimap-idle: TBT 0ms 0ms +0%
minimap-idle: heap used 58.1 MB 57.5 MB -1%
minimap-idle: DOM nodes -275 -276 +0% z=-215.8
minimap-idle: event listeners -197 -197 +0% z=-305.3
subgraph-dom-widget-clipping: avg frame time 17ms 17ms -0% z=-0.4
subgraph-dom-widget-clipping: p95 frame time 17ms 17ms -0%
subgraph-dom-widget-clipping: layout duration 0ms 0ms +0%
subgraph-dom-widget-clipping: style recalc duration 12ms 11ms -8% z=-1.7
subgraph-dom-widget-clipping: layout count 0 0 +0%
subgraph-dom-widget-clipping: style recalc count 47 46 -2% z=-3.3
subgraph-dom-widget-clipping: task duration 419ms 366ms -13% z=-0.6
subgraph-dom-widget-clipping: script duration 132ms 123ms -7% z=-0.8
subgraph-dom-widget-clipping: TBT 0ms 0ms +0%
subgraph-dom-widget-clipping: heap used 59.1 MB 65.2 MB +10%
subgraph-dom-widget-clipping: DOM nodes -288 18 -106% z=-3.7
subgraph-dom-widget-clipping: event listeners -197 6 -103% z=-1.7
subgraph-idle: avg frame time 17ms 17ms -0% z=-0.2
subgraph-idle: p95 frame time 17ms 17ms +0%
subgraph-idle: layout duration 0ms 0ms +0%
subgraph-idle: style recalc duration 7ms 10ms +35% z=-0.7
subgraph-idle: layout count 0 0 +0%
subgraph-idle: style recalc count 10 10 +0% z=-1.4
subgraph-idle: task duration 389ms 419ms +8% z=1.6
subgraph-idle: script duration 13ms 17ms +33% z=-1.3
subgraph-idle: TBT 0ms 0ms +0%
subgraph-idle: heap used 55.5 MB 54.1 MB -3%
subgraph-idle: DOM nodes -288 -283 -2% z=-203.5
subgraph-idle: event listeners -199 -199 +0% variance too high
subgraph-mouse-sweep: avg frame time 17ms 17ms -0% z=-0.5
subgraph-mouse-sweep: p95 frame time 17ms 17ms -0%
subgraph-mouse-sweep: layout duration 5ms 4ms -9% z=-1.7
subgraph-mouse-sweep: style recalc duration 38ms 38ms -1% z=-1.3
subgraph-mouse-sweep: layout count 16 16 +0%
subgraph-mouse-sweep: style recalc count 75 76 +1% z=-2.4
subgraph-mouse-sweep: task duration 721ms 703ms -3% z=-0.9
subgraph-mouse-sweep: script duration 86ms 94ms +9% z=-1.1
subgraph-mouse-sweep: TBT 0ms 0ms +0%
subgraph-mouse-sweep: heap used 51.3 MB 47.3 MB -8%
subgraph-mouse-sweep: DOM nodes -251 62 -125% z=-2.2
subgraph-mouse-sweep: event listeners -199 4 -102% variance too high
subgraph-transition-enter: avg frame time 17ms 17ms -0%
subgraph-transition-enter: p95 frame time 17ms 17ms +1%
subgraph-transition-enter: layout duration 14ms 17ms +16%
subgraph-transition-enter: style recalc duration 32ms 33ms +4%
subgraph-transition-enter: layout count 14 15 +7%
subgraph-transition-enter: style recalc count 18 19 +6%
subgraph-transition-enter: task duration 749ms 903ms +21%
subgraph-transition-enter: script duration 27ms 33ms +21%
subgraph-transition-enter: TBT 151ms 162ms +7%
subgraph-transition-enter: heap used 97.1 MB 89.4 MB -8%
subgraph-transition-enter: DOM nodes 13673 12277 -10%
subgraph-transition-enter: event listeners 2533 862 -66%
viewport-pan-sweep: avg frame time 17ms 17ms +0%
viewport-pan-sweep: p95 frame time 17ms 17ms -1%
viewport-pan-sweep: layout duration 0ms 0ms +0%
viewport-pan-sweep: style recalc duration 58ms 53ms -9%
viewport-pan-sweep: layout count 0 0 +0%
viewport-pan-sweep: style recalc count 250 250 +0%
viewport-pan-sweep: task duration 3852ms 3836ms -0%
viewport-pan-sweep: script duration 1367ms 1251ms -8%
viewport-pan-sweep: TBT 0ms 0ms +0%
viewport-pan-sweep: heap used 74.0 MB 65.9 MB -11%
viewport-pan-sweep: DOM nodes -272 -273 +0%
viewport-pan-sweep: event listeners -183 -183 +0%
vue-large-graph-idle: avg frame time 17ms 17ms +0%
vue-large-graph-idle: p95 frame time 17ms 17ms -0%
vue-large-graph-idle: layout duration 0ms 0ms +0%
vue-large-graph-idle: style recalc duration 0ms 0ms +0%
vue-large-graph-idle: layout count 0 0 +0%
vue-large-graph-idle: style recalc count 0 0 +0%
vue-large-graph-idle: task duration 12723ms 12527ms -2%
vue-large-graph-idle: script duration 548ms 596ms +9%
vue-large-graph-idle: TBT 0ms 0ms +0%
vue-large-graph-idle: heap used 160.6 MB 157.5 MB -2%
vue-large-graph-idle: DOM nodes -3300 -3300 +0%
vue-large-graph-idle: event listeners -16378 -16375 -0%
vue-large-graph-pan: avg frame time 18ms 17ms -3%
vue-large-graph-pan: p95 frame time 17ms 17ms +0%
vue-large-graph-pan: layout duration 0ms 0ms +0%
vue-large-graph-pan: style recalc duration 23ms 19ms -15%
vue-large-graph-pan: layout count 0 0 +0%
vue-large-graph-pan: style recalc count 112 77 -31%
vue-large-graph-pan: task duration 16627ms 15402ms -7%
vue-large-graph-pan: script duration 822ms 847ms +3%
vue-large-graph-pan: TBT 11ms 33ms +195%
vue-large-graph-pan: heap used 160.6 MB 161.8 MB +1%
vue-large-graph-pan: DOM nodes -3300 -3297 -0%
vue-large-graph-pan: event listeners -16369 -16367 -0%
workflow-execution: avg frame time 17ms 17ms -0% z=0.1
workflow-execution: p95 frame time 17ms 17ms -0%
workflow-execution: layout duration 1ms 1ms +4% z=-5.9
workflow-execution: style recalc duration 19ms 20ms +10% z=-1.8
workflow-execution: layout count 3 3 -17% z=-4.5
workflow-execution: style recalc count 13 13 +0% z=-2.3
workflow-execution: task duration 97ms 188ms +93% ⚠️ z=6.0
workflow-execution: script duration 9ms 12ms +33% z=-5.9
workflow-execution: TBT 0ms 0ms +0%
workflow-execution: heap used 54.4 MB 44.5 MB -18%
workflow-execution: DOM nodes 117 -199 -270% z=-50.6
workflow-execution: event listeners 65 -133 -305% z=-42.3
Historical variance (last 15 runs)
Metric μ σ CV
canvas-idle: avg frame time 17ms 0ms 0.0%
canvas-idle: layout duration 0ms 0ms 0.0%
canvas-idle: style recalc duration 11ms 1ms 8.2%
canvas-idle: layout count 0 0 0.0%
canvas-idle: style recalc count 11 1 5.0%
canvas-idle: task duration 395ms 31ms 7.9%
canvas-idle: script duration 25ms 2ms 8.8%
canvas-idle: TBT 0ms 0ms 0.0%
canvas-idle: DOM nodes 23 1 5.6%
canvas-idle: event listeners 12 5 40.9%
canvas-mouse-sweep: avg frame time 17ms 0ms 0.0%
canvas-mouse-sweep: layout duration 4ms 0ms 5.4%
canvas-mouse-sweep: style recalc duration 43ms 3ms 7.4%
canvas-mouse-sweep: layout count 12 0 0.0%
canvas-mouse-sweep: style recalc count 79 2 3.0%
canvas-mouse-sweep: task duration 865ms 58ms 6.7%
canvas-mouse-sweep: script duration 136ms 6ms 4.8%
canvas-mouse-sweep: TBT 0ms 0ms 0.0%
canvas-mouse-sweep: DOM nodes 62 3 4.2%
canvas-mouse-sweep: event listeners 8 4 49.4%
canvas-zoom-sweep: avg frame time 17ms 0ms 0.0%
canvas-zoom-sweep: layout duration 1ms 0ms 7.0%
canvas-zoom-sweep: style recalc duration 19ms 2ms 8.0%
canvas-zoom-sweep: layout count 6 0 0.0%
canvas-zoom-sweep: style recalc count 31 0 1.5%
canvas-zoom-sweep: task duration 327ms 23ms 7.1%
canvas-zoom-sweep: script duration 27ms 3ms 11.1%
canvas-zoom-sweep: TBT 0ms 0ms 0.0%
canvas-zoom-sweep: DOM nodes 79 1 1.0%
canvas-zoom-sweep: event listeners 24 5 21.8%
dom-widget-clipping: avg frame time 17ms 0ms 0.0%
dom-widget-clipping: layout duration 0ms 0ms 0.0%
dom-widget-clipping: style recalc duration 10ms 1ms 8.0%
dom-widget-clipping: layout count 0 0 0.0%
dom-widget-clipping: style recalc count 13 0 3.8%
dom-widget-clipping: task duration 365ms 16ms 4.5%
dom-widget-clipping: script duration 68ms 3ms 4.8%
dom-widget-clipping: TBT 0ms 0ms 0.0%
dom-widget-clipping: DOM nodes 22 1 6.4%
dom-widget-clipping: event listeners 8 6 81.2%
large-graph-idle: avg frame time 17ms 0ms 0.0%
large-graph-idle: layout duration 0ms 0ms 0.0%
large-graph-idle: style recalc duration 12ms 1ms 8.6%
large-graph-idle: layout count 0 0 0.0%
large-graph-idle: style recalc count 12 0 2.7%
large-graph-idle: task duration 542ms 54ms 10.0%
large-graph-idle: script duration 102ms 11ms 10.3%
large-graph-idle: TBT 0ms 0ms 0.0%
large-graph-idle: DOM nodes 25 1 3.7%
large-graph-idle: event listeners 26 6 23.2%
large-graph-pan: avg frame time 17ms 0ms 0.0%
large-graph-pan: layout duration 0ms 0ms 0.0%
large-graph-pan: style recalc duration 17ms 1ms 4.6%
large-graph-pan: layout count 0 0 0.0%
large-graph-pan: style recalc count 70 1 0.9%
large-graph-pan: task duration 1082ms 43ms 4.0%
large-graph-pan: script duration 408ms 20ms 4.8%
large-graph-pan: TBT 0ms 0ms 0.0%
large-graph-pan: DOM nodes 19 2 8.7%
large-graph-pan: event listeners 5 1 16.8%
minimap-idle: avg frame time 17ms 0ms 0.0%
minimap-idle: layout duration 0ms 0ms 0.0%
minimap-idle: style recalc duration 10ms 1ms 8.6%
minimap-idle: layout count 0 0 0.0%
minimap-idle: style recalc count 10 1 7.1%
minimap-idle: task duration 527ms 47ms 9.0%
minimap-idle: script duration 98ms 10ms 10.1%
minimap-idle: TBT 0ms 0ms 0.0%
minimap-idle: DOM nodes 19 1 7.1%
minimap-idle: event listeners 5 1 14.4%
subgraph-dom-widget-clipping: avg frame time 17ms 0ms 0.0%
subgraph-dom-widget-clipping: layout duration 0ms 0ms 0.0%
subgraph-dom-widget-clipping: style recalc duration 13ms 1ms 7.4%
subgraph-dom-widget-clipping: layout count 0 0 0.0%
subgraph-dom-widget-clipping: style recalc count 48 1 1.2%
subgraph-dom-widget-clipping: task duration 378ms 18ms 4.9%
subgraph-dom-widget-clipping: script duration 128ms 6ms 4.9%
subgraph-dom-widget-clipping: TBT 0ms 0ms 0.0%
subgraph-dom-widget-clipping: DOM nodes 22 1 5.0%
subgraph-dom-widget-clipping: event listeners 16 6 36.0%
subgraph-idle: avg frame time 17ms 0ms 0.0%
subgraph-idle: layout duration 0ms 0ms 0.0%
subgraph-idle: style recalc duration 10ms 1ms 7.5%
subgraph-idle: layout count 0 0 0.0%
subgraph-idle: style recalc count 11 1 6.0%
subgraph-idle: task duration 370ms 31ms 8.5%
subgraph-idle: script duration 20ms 3ms 13.2%
subgraph-idle: TBT 0ms 0ms 0.0%
subgraph-idle: DOM nodes 22 1 6.9%
subgraph-idle: event listeners 10 7 64.5%
subgraph-mouse-sweep: avg frame time 17ms 0ms 0.0%
subgraph-mouse-sweep: layout duration 5ms 0ms 6.8%
subgraph-mouse-sweep: style recalc duration 42ms 3ms 7.8%
subgraph-mouse-sweep: layout count 16 0 0.0%
subgraph-mouse-sweep: style recalc count 80 2 2.4%
subgraph-mouse-sweep: task duration 766ms 69ms 9.0%
subgraph-mouse-sweep: script duration 101ms 7ms 6.5%
subgraph-mouse-sweep: TBT 0ms 0ms 0.0%
subgraph-mouse-sweep: DOM nodes 67 2 3.3%
subgraph-mouse-sweep: event listeners 8 4 52.6%
workflow-execution: avg frame time 17ms 0ms 0.0%
workflow-execution: layout duration 2ms 0ms 9.4%
workflow-execution: style recalc duration 24ms 2ms 9.1%
workflow-execution: layout count 5 1 11.0%
workflow-execution: style recalc count 18 2 11.5%
workflow-execution: task duration 123ms 11ms 8.8%
workflow-execution: script duration 29ms 3ms 10.2%
workflow-execution: TBT 0ms 0ms 0.0%
workflow-execution: DOM nodes 161 7 4.4%
workflow-execution: event listeners 52 4 8.4%
Trend (last 15 commits on main)
Metric Trend Dir Latest
canvas-idle: avg frame time ▆▃▆▁▆▃▆█▆▆▄▃▃▄▃ ➡️ 17ms
canvas-idle: p95 frame time ➡️ NaNms
canvas-idle: layout duration ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
canvas-idle: style recalc duration ▇▇▆▆▃█▄▃▄▃▇▄▁▆▇ ➡️ 11ms
canvas-idle: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0
canvas-idle: style recalc count █▃▅▂▅▆▃▁▂▁▂▅▆▅▆ ➡️ 12
canvas-idle: task duration ▃▃▃▆▂▃▃▅▆▂█▃▁▃▃ ➡️ 391ms
canvas-idle: script duration ▄▃▅▇▂▅▃▆▇▅█▄▁▅▆ ➡️ 27ms
canvas-idle: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
canvas-idle: heap used ➡️ NaN MB
canvas-idle: DOM nodes █▇▆▅▃▇▃▁▂▂▅▆▆▆▇ ➡️ 24
canvas-idle: event listeners ▅█▅▄▁▅▁▁▁▄▅▅▁▅▄ 📉 11
canvas-mouse-sweep: avg frame time ▆█▆▃▁▃▁▆▆▁▃▆▆▃▃ ➡️ 17ms
canvas-mouse-sweep: p95 frame time ➡️ NaNms
canvas-mouse-sweep: layout duration ▁▃▂▄▁▂▁▃▆▂█▇▆▄▃ ➡️ 4ms
canvas-mouse-sweep: style recalc duration ▄▄▂▄▁▂▃▃▅▄█▆▂▄▄ ➡️ 43ms
canvas-mouse-sweep: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 12
canvas-mouse-sweep: style recalc count █▅▄▃▂▂▁▄▄▅▆▅▂▇▄ ➡️ 79
canvas-mouse-sweep: task duration █▆▄▂▂▃▂▄▄▅█▆▁▆▄ ➡️ 868ms
canvas-mouse-sweep: script duration ▄▅▄▆▄▆▆▆▅▅█▆▁▅▆ ➡️ 139ms
canvas-mouse-sweep: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
canvas-mouse-sweep: heap used ➡️ NaN MB
canvas-mouse-sweep: DOM nodes █▅▃▃▁▂▂▃▂▄▆▅▃▅▅ ➡️ 64
canvas-mouse-sweep: event listeners █▁▁▁▁▁▇▁▁▁██▇▁█ 📈 13
canvas-zoom-sweep: avg frame time ▅▅█▄▅▁▁▁▅▁▁▅▄▅▁ ➡️ 17ms
canvas-zoom-sweep: p95 frame time ➡️ NaNms
canvas-zoom-sweep: layout duration ▆▅▅▄▁▁█▅▃▅▇▆▁▂▆ ➡️ 1ms
canvas-zoom-sweep: style recalc duration ▆▅▄▆▅▃█▆▇▅▇▄▁▃▅ ➡️ 20ms
canvas-zoom-sweep: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 6
canvas-zoom-sweep: style recalc count ▁▁▃▄▆▃▆█▄▄▆▁▆▁▆ ➡️ 32
canvas-zoom-sweep: task duration ▄▂▁▇▂▂▄▅▆▃█▄▁▁▅ ➡️ 338ms
canvas-zoom-sweep: script duration ▃▃▂▇▂▂▅▇▆▅█▄▁▂▆ ➡️ 30ms
canvas-zoom-sweep: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
canvas-zoom-sweep: heap used ➡️ NaN MB
canvas-zoom-sweep: DOM nodes ▄▃▁▅█▁▃▆▄▅▅▃▃▄▃ ➡️ 79
canvas-zoom-sweep: event listeners ▁▁▂▅█▂▁▅▁▅▅▄▁▅▁ ➡️ 19
dom-widget-clipping: avg frame time ▂▄▅▅▂▄█▇▅▇▇▅▅▁▇ ➡️ 17ms
dom-widget-clipping: p95 frame time ➡️ NaNms
dom-widget-clipping: layout duration ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
dom-widget-clipping: style recalc duration ▆▆▂▆▄▃██▄▁▆▇▆▃▅ ➡️ 10ms
dom-widget-clipping: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0
dom-widget-clipping: style recalc count ▇█▅█▅▄█▇▇▁▇▄▇▂▅ ➡️ 13
dom-widget-clipping: task duration ▃▃▁▅▄▃▅▆▅▂▇█▁▅▅ ➡️ 371ms
dom-widget-clipping: script duration ▅▄▄▆▆▅▇▇▆▃█▇▁▇▇ ➡️ 71ms
dom-widget-clipping: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
dom-widget-clipping: heap used ➡️ NaN MB
dom-widget-clipping: DOM nodes ▇▇▄▇▅▄█▇▅▁▅▄▇▃▄ ➡️ 21
dom-widget-clipping: event listeners ▅▅▅▅▁▅██▁▁▁▁█▁▁ 📉 2
large-graph-idle: avg frame time ▅▅▅▅▅▂▁▂▄▅▄▂▂▅█ ➡️ 17ms
large-graph-idle: p95 frame time ➡️ NaNms
large-graph-idle: layout duration ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
large-graph-idle: style recalc duration ▅▅▅▆▄▅▃▄▅▅▆█▁▄▆ ➡️ 13ms
large-graph-idle: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0
large-graph-idle: style recalc count █▆█▃▃▁▃▆▃▆▆▃▆██ ➡️ 12
large-graph-idle: task duration ▂▃▂▆▂▃▃▇▅▃██▁▂▅ ➡️ 569ms
large-graph-idle: script duration ▄▅▄▆▄▅▅▇▆▅█▆▁▃▆ ➡️ 110ms
large-graph-idle: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
large-graph-idle: heap used ➡️ NaN MB
large-graph-idle: DOM nodes ▆█▅▂▅▃▁▂▃▅▅▆▂▆▅ ➡️ 25
large-graph-idle: event listeners ███▇██▄▁▄▇▇█▂█▇ ➡️ 29
large-graph-pan: avg frame time ▆▃▃▆█▃▁█▆▆▆▆█▁▆ ➡️ 17ms
large-graph-pan: p95 frame time ➡️ NaNms
large-graph-pan: layout duration ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
large-graph-pan: style recalc duration ▃▂▄▄▁▅▂▂▁▄▄█▃▁▂ ➡️ 17ms
large-graph-pan: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0
large-graph-pan: style recalc count ▆▃█▂▃▂▂▂▁▇▅▃█▆▃ ➡️ 69
large-graph-pan: task duration ▄▃▄▆▄▄▄▆▄▄█▆▁▂▅ ➡️ 1100ms
large-graph-pan: script duration ▅▄▅▆▆▅▄▆▄▅█▄▁▄▅ ➡️ 413ms
large-graph-pan: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
large-graph-pan: heap used ➡️ NaN MB
large-graph-pan: DOM nodes ▅▃▆▂▄▁▃▁▁▅▁▂█▅▂ ➡️ 18
large-graph-pan: event listeners █▆█▁▁▆▁▁▃▆▁▃██▃ ➡️ 5
minimap-idle: avg frame time ▃▆▆▃█▁█▆▆▃▃▆█▆█ ➡️ 17ms
minimap-idle: p95 frame time ➡️ NaNms
minimap-idle: layout duration ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
minimap-idle: style recalc duration ▄█▁█▅▅█▅▅▃▅▁▁▄▆ ➡️ 10ms
minimap-idle: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0
minimap-idle: style recalc count ▃▅▂▄█▃▆▁▂▅▂▁▅▆▃ ➡️ 9
minimap-idle: task duration ▃▄▁▅▁▃▄▅▇▃█▅▁▁▅ ➡️ 547ms
minimap-idle: script duration ▄▆▃▇▃▅▆▆▇▅█▅▁▃▆ ➡️ 106ms
minimap-idle: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
minimap-idle: heap used ➡️ NaN MB
minimap-idle: DOM nodes ▃▅▂▄█▃▆▁▂▅▂▁▅▆▃ ➡️ 19
minimap-idle: event listeners ▃▃▆▁▁▁▃▁▁▆▁▃█▆▁ ➡️ 4
subgraph-dom-widget-clipping: avg frame time ▅▄▄▄▄▄█▄▄▄▃▁▆▃▃ ➡️ 17ms
subgraph-dom-widget-clipping: p95 frame time ➡️ NaNms
subgraph-dom-widget-clipping: layout duration ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
subgraph-dom-widget-clipping: style recalc duration ▂▄▃▅▅▃▂▅▇▃▄█▁▄▆ ➡️ 14ms
subgraph-dom-widget-clipping: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0
subgraph-dom-widget-clipping: style recalc count ▇█▆▃▆▃▁▆█▇▃▆▇█▅ ➡️ 48
subgraph-dom-widget-clipping: task duration ▂▃▃▆▅▅▂▅█▂▆█▁▂▇ ➡️ 398ms
subgraph-dom-widget-clipping: script duration ▃▃▃▄▅▅▂▄█▂▅▇▁▂▅ ➡️ 131ms
subgraph-dom-widget-clipping: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
subgraph-dom-widget-clipping: heap used ➡️ NaN MB
subgraph-dom-widget-clipping: DOM nodes ▅▇▅▂▅▂▁▅▅▅▁▇▅█▄ ➡️ 22
subgraph-dom-widget-clipping: event listeners ▅▅▅▂▅▁▅██▁▁█▅█▅ 📈 16
subgraph-idle: avg frame time ▆▆█▁▆▃▆▆▆▃▆▁▃▆█ ➡️ 17ms
subgraph-idle: p95 frame time ➡️ NaNms
subgraph-idle: layout duration ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
subgraph-idle: style recalc duration ▁▇▃▆▂▄▂▃▃▆▆▄▃▇█ ➡️ 12ms
subgraph-idle: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0
subgraph-idle: style recalc count ▃▆▃▃▂▅▁▂▁▆▃▃██▇ ➡️ 12
subgraph-idle: task duration ▁▃▁▇▁▁▃▆▅▂█▅▁▁▄ ➡️ 378ms
subgraph-idle: script duration ▁▃▂▇▁▂▃▇▆▂█▅▂▁▅ ➡️ 22ms
subgraph-idle: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
subgraph-idle: heap used ➡️ NaN MB
subgraph-idle: DOM nodes ▃▅▃▂▁▄▁▂▁▅▃▂▇█▇ ➡️ 24
subgraph-idle: event listeners ▁▅▁▁▁▁▁▁▁▅▄▁███ 📈 21
subgraph-mouse-sweep: avg frame time ▅▄▁▃▃▄▆▄▆▃▃█▁▃▃ ➡️ 17ms
subgraph-mouse-sweep: p95 frame time ➡️ NaNms
subgraph-mouse-sweep: layout duration ▁▄▄▄▃▃▅▅▅▂█▇▂▃▆ ➡️ 5ms
subgraph-mouse-sweep: style recalc duration ▃▂▄▅▂▃▄▅█▃█▆▁▂▅ ➡️ 43ms
subgraph-mouse-sweep: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 16
subgraph-mouse-sweep: style recalc count ▅▂▅▅▁▄▃▅█▅▆▄▂▄▅ ➡️ 81
subgraph-mouse-sweep: task duration ▃▂▄▅▂▄▄▅▇▄█▆▁▃▅ ➡️ 785ms
subgraph-mouse-sweep: script duration ▄▅▄▇▅▅▆▇▆▅██▁▄▆ ➡️ 105ms
subgraph-mouse-sweep: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
subgraph-mouse-sweep: heap used ➡️ NaN MB
subgraph-mouse-sweep: DOM nodes ▅▁▄▅▁▄▃▃█▅▅▄▂▅▃ ➡️ 66
subgraph-mouse-sweep: event listeners ▇▁▂▇▁▂▂▂█▇▂▂▇▇▂ 📈 5
workflow-execution: avg frame time ▆▆▆▄▆▆▃▄▁▄█▆▅▄▆ ➡️ 17ms
workflow-execution: p95 frame time ➡️ NaNms
workflow-execution: layout duration ▁▆▁▃▂▄▃▂▃▃▅█▄▂▅ ➡️ 2ms
workflow-execution: style recalc duration ▃▇▅▇▁▅▆▇█▁██▂▄▆ ➡️ 25ms
workflow-execution: layout count ▁█▂▃▂▃▃▁▃▃▄▃▂▃▂ ➡️ 5
workflow-execution: style recalc count ▃█▅▇▁▄▅▆▅▅▅▅▄▄▂ ➡️ 15
workflow-execution: task duration ▂▅▄▅▁▄▆▆▆▁▇█▁▃▃ ➡️ 120ms
workflow-execution: script duration ▄▃▄▄▃▅▄▅▆▂▇█▁▃▄ ➡️ 29ms
workflow-execution: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
workflow-execution: heap used ➡️ NaN MB
workflow-execution: DOM nodes ▂█▃▆▁▄▃▅▃█▃▃▄▃▁ ➡️ 152
workflow-execution: event listeners ▅███▁▅███▁██▅█▅ ➡️ 49
Raw data
{
  "timestamp": "2026-07-16T18:26:35.312Z",
  "gitSha": "c861e3d2fe317e14240349d6fed4a8253e313354",
  "branch": "jaewon/fe-1246-team-credit-exhaustion-blocked-state-ux-role-aware-cta",
  "measurements": [
    {
      "name": "canvas-idle",
      "durationMs": 2017.15200000001,
      "styleRecalcs": 9,
      "styleRecalcDurationMs": 9.337,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 400.25399999999996,
      "heapDeltaBytes": -4892328,
      "heapUsedBytes": 47982536,
      "domNodes": -290,
      "jsHeapTotalBytes": 16379904,
      "scriptDurationMs": 17.05,
      "eventListeners": -199,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "canvas-idle",
      "durationMs": 2104.4019999999364,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 9.315999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 422.991,
      "heapDeltaBytes": -6559224,
      "heapUsedBytes": 63478640,
      "domNodes": -277,
      "jsHeapTotalBytes": 18751488,
      "scriptDurationMs": 18.572000000000003,
      "eventListeners": -199,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.699999999999818
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 1924.1690000000062,
      "styleRecalcs": 77,
      "styleRecalcDurationMs": 42.300999999999995,
      "layouts": 12,
      "layoutDurationMs": 3.7740000000000005,
      "taskDurationMs": 894.876,
      "heapDeltaBytes": -5662472,
      "heapUsedBytes": 53386140,
      "domNodes": 59,
      "jsHeapTotalBytes": 24903680,
      "scriptDurationMs": 139.303,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 1882.0890000000645,
      "styleRecalcs": 76,
      "styleRecalcDurationMs": 42.925,
      "layouts": 12,
      "layoutDurationMs": 3.883,
      "taskDurationMs": 852.187,
      "heapDeltaBytes": -12155460,
      "heapUsedBytes": 57836320,
      "domNodes": -237,
      "jsHeapTotalBytes": 18751488,
      "scriptDurationMs": 125.76599999999999,
      "eventListeners": -199,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66999999999998,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "canvas-zoom-sweep",
      "durationMs": 1738.2390000000214,
      "styleRecalcs": 33,
      "styleRecalcDurationMs": 18.713,
      "layouts": 6,
      "layoutDurationMs": 0.7699999999999998,
      "taskDurationMs": 351.84900000000005,
      "heapDeltaBytes": 6817716,
      "heapUsedBytes": 68782168,
      "domNodes": 78,
      "jsHeapTotalBytes": 24117248,
      "scriptDurationMs": 23.929,
      "eventListeners": 19,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "canvas-zoom-sweep",
      "durationMs": 1741.2269999999808,
      "styleRecalcs": 31,
      "styleRecalcDurationMs": 18.642,
      "layouts": 6,
      "layoutDurationMs": 0.745,
      "taskDurationMs": 345.58200000000005,
      "heapDeltaBytes": 5185796,
      "heapUsedBytes": 57362288,
      "domNodes": -241,
      "jsHeapTotalBytes": 1437696,
      "scriptDurationMs": 18.928,
      "eventListeners": -186,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 657.400999999993,
      "styleRecalcs": 12,
      "styleRecalcDurationMs": 10.306999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 430.298,
      "heapDeltaBytes": -22546564,
      "heapUsedBytes": 47627296,
      "domNodes": -274,
      "jsHeapTotalBytes": 6692864,
      "scriptDurationMs": 62.137,
      "eventListeners": -201,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66999999999998,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 596.9860000000153,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 8.459999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 356.931,
      "heapDeltaBytes": 7850432,
      "heapUsedBytes": 67738060,
      "domNodes": 18,
      "jsHeapTotalBytes": 19922944,
      "scriptDurationMs": 59.876,
      "eventListeners": 0,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "large-graph-idle",
      "durationMs": 2026.1629999999968,
      "styleRecalcs": 8,
      "styleRecalcDurationMs": 7.8919999999999995,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 553.604,
      "heapDeltaBytes": -8814176,
      "heapUsedBytes": 59863836,
      "domNodes": -273,
      "jsHeapTotalBytes": -1855488,
      "scriptDurationMs": 93.59500000000001,
      "eventListeners": -199,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "large-graph-idle",
      "durationMs": 2014.6899999999732,
      "styleRecalcs": 6,
      "styleRecalcDurationMs": 5.311,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 492.753,
      "heapDeltaBytes": -1056592,
      "heapUsedBytes": 60735572,
      "domNodes": -277,
      "jsHeapTotalBytes": -1101824,
      "scriptDurationMs": 84.64899999999999,
      "eventListeners": -197,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "large-graph-pan",
      "durationMs": 2159.208000000035,
      "styleRecalcs": 68,
      "styleRecalcDurationMs": 19.979,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1130.152,
      "heapDeltaBytes": 10421484,
      "heapUsedBytes": 71482792,
      "domNodes": -266,
      "jsHeapTotalBytes": 5160960,
      "scriptDurationMs": 392.396,
      "eventListeners": -128,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.670000000000012,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-pan",
      "durationMs": 2095.17900000003,
      "styleRecalcs": 69,
      "styleRecalcDurationMs": 18.006000000000004,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1049.231,
      "heapDeltaBytes": 29712328,
      "heapUsedBytes": 83085820,
      "domNodes": -268,
      "jsHeapTotalBytes": 204800,
      "scriptDurationMs": 385.97400000000005,
      "eventListeners": -197,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.699999999999818
    },
    {
      "name": "large-graph-zoom",
      "durationMs": 3111.2249999999904,
      "styleRecalcs": 63,
      "styleRecalcDurationMs": 16.496999999999996,
      "layouts": 60,
      "layoutDurationMs": 7.5600000000000005,
      "taskDurationMs": 1309.371,
      "heapDeltaBytes": 12983040,
      "heapUsedBytes": 67306572,
      "domNodes": -281,
      "jsHeapTotalBytes": 0,
      "scriptDurationMs": 483.272,
      "eventListeners": 8,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-zoom",
      "durationMs": 3125.904999999989,
      "styleRecalcs": 63,
      "styleRecalcDurationMs": 15.901999999999996,
      "layouts": 60,
      "layoutDurationMs": 7.521999999999999,
      "taskDurationMs": 1317.5789999999997,
      "heapDeltaBytes": 15048348,
      "heapUsedBytes": 69780476,
      "domNodes": 6,
      "jsHeapTotalBytes": 0,
      "scriptDurationMs": 492.40799999999996,
      "eventListeners": 8,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "minimap-idle",
      "durationMs": 2026.3229999999908,
      "styleRecalcs": 5,
      "styleRecalcDurationMs": 4.717000000000002,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 530.04,
      "heapDeltaBytes": -3143420,
      "heapUsedBytes": 60523772,
      "domNodes": -278,
      "jsHeapTotalBytes": -1626112,
      "scriptDurationMs": 93.10600000000002,
      "eventListeners": -197,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "minimap-idle",
      "durationMs": 2025.859999999966,
      "styleRecalcs": 7,
      "styleRecalcDurationMs": 7.960999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 556.666,
      "heapDeltaBytes": -16325788,
      "heapUsedBytes": 60045428,
      "domNodes": -274,
      "jsHeapTotalBytes": 3473408,
      "scriptDurationMs": 101.721,
      "eventListeners": -197,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 549.3930000000091,
      "styleRecalcs": 46,
      "styleRecalcDurationMs": 10.819,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 373.14399999999995,
      "heapDeltaBytes": 7846076,
      "heapUsedBytes": 67188452,
      "domNodes": 18,
      "jsHeapTotalBytes": 18087936,
      "scriptDurationMs": 124.019,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 563.5670000000346,
      "styleRecalcs": 46,
      "styleRecalcDurationMs": 11.316999999999998,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 359.758,
      "heapDeltaBytes": 6972800,
      "heapUsedBytes": 69630484,
      "domNodes": 18,
      "jsHeapTotalBytes": 21245952,
      "scriptDurationMs": 121.957,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.663333333333338,
      "p95FrameDurationMs": 16.699999999999818
    },
    {
      "name": "subgraph-idle",
      "durationMs": 2045.299,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 10.187,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 416.085,
      "heapDeltaBytes": -7294868,
      "heapUsedBytes": 62857220,
      "domNodes": -273,
      "jsHeapTotalBytes": 19275776,
      "scriptDurationMs": 15.115,
      "eventListeners": -199,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.699999999999818
    },
    {
      "name": "subgraph-idle",
      "durationMs": 2039.103999999952,
      "styleRecalcs": 9,
      "styleRecalcDurationMs": 9.476,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 421.73799999999994,
      "heapDeltaBytes": -1879352,
      "heapUsedBytes": 50589356,
      "domNodes": -292,
      "jsHeapTotalBytes": 16642048,
      "scriptDurationMs": 18.492,
      "eventListeners": -199,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1727.1809999999732,
      "styleRecalcs": 75,
      "styleRecalcDurationMs": 38.997,
      "layouts": 16,
      "layoutDurationMs": 4.175,
      "taskDurationMs": 689.286,
      "heapDeltaBytes": -9590668,
      "heapUsedBytes": 49540596,
      "domNodes": 62,
      "jsHeapTotalBytes": 25165824,
      "scriptDurationMs": 93.418,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1707.4219999999514,
      "styleRecalcs": 76,
      "styleRecalcDurationMs": 37.352999999999994,
      "layouts": 16,
      "layoutDurationMs": 4.13,
      "taskDurationMs": 716.275,
      "heapDeltaBytes": -9626732,
      "heapUsedBytes": 49564464,
      "domNodes": 62,
      "jsHeapTotalBytes": 25952256,
      "scriptDurationMs": 94.25999999999999,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "subgraph-transition-enter",
      "durationMs": 1082.6039999999466,
      "styleRecalcs": 19,
      "styleRecalcDurationMs": 33.385999999999996,
      "layouts": 15,
      "layoutDurationMs": 16.596,
      "taskDurationMs": 902.8800000000001,
      "heapDeltaBytes": 20814836,
      "heapUsedBytes": 93793664,
      "domNodes": 12277,
      "jsHeapTotalBytes": 14626816,
      "scriptDurationMs": 32.81399999999999,
      "eventListeners": 862,
      "totalBlockingTimeMs": 162,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "viewport-pan-sweep",
      "durationMs": 8184.425000000033,
      "styleRecalcs": 250,
      "styleRecalcDurationMs": 53.44900000000001,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 3912.1470000000004,
      "heapDeltaBytes": 816668,
      "heapUsedBytes": 69063480,
      "domNodes": -272,
      "jsHeapTotalBytes": 970752,
      "scriptDurationMs": 1272.528,
      "eventListeners": -183,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "viewport-pan-sweep",
      "durationMs": 8147.74900000009,
      "styleRecalcs": 250,
      "styleRecalcDurationMs": 52.873999999999995,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 3759.8250000000003,
      "heapDeltaBytes": 7521128,
      "heapUsedBytes": 69088836,
      "domNodes": -274,
      "jsHeapTotalBytes": 1200128,
      "scriptDurationMs": 1228.537,
      "eventListeners": -183,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "vue-large-graph-idle",
      "durationMs": 12488.72199999994,
      "styleRecalcs": 0,
      "styleRecalcDurationMs": 0,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 12470.693000000001,
      "heapDeltaBytes": -50300028,
      "heapUsedBytes": 166238244,
      "domNodes": -3300,
      "jsHeapTotalBytes": 7077888,
      "scriptDurationMs": 581.3539999999999,
      "eventListeners": -16371,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 17.223333333333358,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "vue-large-graph-idle",
      "durationMs": 12603.27499999994,
      "styleRecalcs": 0,
      "styleRecalcDurationMs": 0,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 12583.402,
      "heapDeltaBytes": -36947124,
      "heapUsedBytes": 164127024,
      "domNodes": -3300,
      "jsHeapTotalBytes": 757760,
      "scriptDurationMs": 610.754,
      "eventListeners": -16378,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 17.223333333333358,
      "p95FrameDurationMs": 16.80000000000291
    },
    {
      "name": "vue-large-graph-pan",
      "durationMs": 15360.319000000003,
      "styleRecalcs": 78,
      "styleRecalcDurationMs": 19.064999999999998,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 15334.896,
      "heapDeltaBytes": -20793712,
      "heapUsedBytes": 158357988,
      "domNodes": -3291,
      "jsHeapTotalBytes": 16515072,
      "scriptDurationMs": 891.2220000000001,
      "eventListeners": -16364,
      "totalBlockingTimeMs": 8,
      "frameDurationMs": 17.219999999999953,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "vue-large-graph-pan",
      "durationMs": 15495.034000000032,
      "styleRecalcs": 76,
      "styleRecalcDurationMs": 19.63599999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 15470.053,
      "heapDeltaBytes": -11911192,
      "heapUsedBytes": 180944364,
      "domNodes": -3302,
      "jsHeapTotalBytes": 21118976,
      "scriptDurationMs": 802.2710000000001,
      "eventListeners": -16370,
      "totalBlockingTimeMs": 57,
      "frameDurationMs": 17.220000000000073,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "workflow-execution",
      "durationMs": 472.6590000000215,
      "styleRecalcs": 13,
      "styleRecalcDurationMs": 17.679000000000002,
      "layouts": 2,
      "layoutDurationMs": 0.41100000000000014,
      "taskDurationMs": 168.64700000000002,
      "heapDeltaBytes": -22561848,
      "heapUsedBytes": 46781228,
      "domNodes": -194,
      "jsHeapTotalBytes": 4595712,
      "scriptDurationMs": 10.718000000000002,
      "eventListeners": -134,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "workflow-execution",
      "durationMs": 501.7250000000786,
      "styleRecalcs": 13,
      "styleRecalcDurationMs": 23.006,
      "layouts": 3,
      "layoutDurationMs": 0.9789999999999999,
      "taskDurationMs": 206.74399999999997,
      "heapDeltaBytes": -22836540,
      "heapUsedBytes": 46616712,
      "domNodes": -203,
      "jsHeapTotalBytes": 5644288,
      "scriptDurationMs": 12.578999999999999,
      "eventListeners": -132,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.799999999999272
    }
  ]
}

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds cloud billing-status banners, credit-error classification, workspace member dialogs, permission-aware top-up routing, reactive billing composables, localized copy, tests, and Storybook scenarios.

Changes

Credit exhaustion experience

Layer / File(s) Summary
Credit classification and refresh
src/platform/errorCatalog/*, src/platform/cloud/subscription/composables/useAccountPreconditionDialog*
Insufficient-credit responses now route to the credits precondition, which refreshes billing status and balance before opening the top-up dialog.
Billing state and banner rendering
src/platform/workspace/composables/*BillingBanner*, src/platform/workspace/components/dialogs/settings/BillingStatusBanner.*, src/locales/en/main.json, src/platform/workspace/api/workspaceApi.ts
Adds prioritized paused, payment-failed, out-of-credits, and ending banner states with localized content, actions, date handling, dismissal, and tests.
Workspace member and subscription dialogs
src/services/dialogService*, src/platform/workspace/components/InsufficientCreditsMemberDialog.vue, src/platform/cloud/subscription/composables/useSubscriptionDialog*, src/locales/en/main.json
Routes members without top-up or subscription permissions to read-only dialogs while preserving legacy billing behavior.
Billing banner integration and Storybook state
src/platform/workspace/components/dialogs/settings/WorkspacePanelContent.*, src/platform/workspace/components/dialogs/settings/BillingStatusBanner.stories.ts, src/storybook/mocks/*, .storybook/main.ts
Places the banner above workspace tabs and adds reactive Storybook mocks and scenarios for billing states and workspace roles.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant BillingContext
  participant useBillingBanner
  participant BillingStatusBanner
  participant dialogService
  BillingContext->>useBillingBanner: provide billing status and subscription state
  useBillingBanner-->>BillingStatusBanner: return banner kind
  BillingStatusBanner->>dialogService: request add credits
  dialogService-->>BillingStatusBanner: open purchase or member dialog
Loading

Possibly related PRs

Suggested labels: size:XL, released:cloud, cloud/1.45

Suggested reviewers: christian-byrne, jtydhr88, drjkl


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR implements billing-banner work, not the guided onboarding tour, gating, telemetry, or template-picker requirements of #13640. Implement the onboarding tour, cloud/new-user gating, telemetry, and template-picker flow required by #13640; the billing-banner changes belong in a separate PR.
Out of Scope Changes check ⚠️ Warning Most of the diff is billing-banner, billing-context, and Storybook work unrelated to the linked onboarding-tour issue. Split the billing-banner feature into its own PR or replace it with the onboarding-tour changes required by #13640.
End-To-End Regression Coverage For Fixes ❓ Inconclusive PR title is a feat, but commit subjects aren’t available in the review context, so the bug-fix signal can’t be fully verified. Provide the PR’s commit subjects (or a complete metadata list) so I can check whether any use fix/fixed/fixes/hotfix language and apply the E2E test rule.
✅ Passed checks (3 passed)
Check name Status Explanation
Adr Compliance For Entity/Litegraph Changes ✅ Passed No changed files touch src/lib/litegraph/, src/ecs/, or graph-entity code, so ADR 0003/0008 checks are not applicable.
Title check ✅ Passed The title clearly matches the main change: a role-aware billing status banner for team workspaces.
Description check ✅ Passed The description covers the summary, behavior, testing, and screenshots, though it doesn’t follow the template’s exact section headings or bullet structure.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jaewon/fe-1246-team-credit-exhaustion-blocked-state-ux-role-aware-cta

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/platform/cloud/subscription/composables/useAccountPreconditionDialog.ts`:
- Around line 32-44: Move the useBillingContext() call out of the credits branch
in open and invoke it synchronously at the root of useAccountPreconditionDialog
during setup. Reuse the returned fetchStatus and fetchBalance methods in the
existing credits handling, preserving the best-effort Promise.allSettled refresh
and top-up dialog behavior.

In `@src/platform/workspace/components/CreditsExhaustedBanner.vue`:
- Around line 10-14: Update the text color utilities in the
CreditsExhaustedBanner template: replace text-base-foreground,
text-muted-foreground, and text-warning-background with their corresponding
arbitrary-value CSS token utilities using --base-foreground, --muted-foreground,
and --warning-background. Preserve the existing layout and icon styling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0cce2f9e-f5ab-4f36-8179-0adc0cb17da1

📥 Commits

Reviewing files that changed from the base of the PR and between 1a98362 and 6ff79af.

📒 Files selected for processing (17)
  • src/locales/en/main.json
  • src/platform/cloud/subscription/composables/useAccountPreconditionDialog.test.ts
  • src/platform/cloud/subscription/composables/useAccountPreconditionDialog.ts
  • src/platform/cloud/subscription/composables/useSubscriptionDialog.test.ts
  • src/platform/cloud/subscription/composables/useSubscriptionDialog.ts
  • src/platform/errorCatalog/accountPreconditionRouting.test.ts
  • src/platform/errorCatalog/runtimeErrorMatcher.ts
  • src/platform/workspace/components/CreditsExhaustedBanner.test.ts
  • src/platform/workspace/components/CreditsExhaustedBanner.vue
  • src/platform/workspace/components/InsufficientCreditsMemberDialog.vue
  • src/platform/workspace/composables/useCreditExhaustion.ts
  • src/services/dialogService.topUpCredits.test.ts
  • src/services/dialogService.ts
  • src/views/GraphView.test.ts
  • src/views/GraphView.vue
  • src/views/LinearView.test.ts
  • src/views/LinearView.vue

Comment thread src/platform/workspace/components/CreditsExhaustedBanner.vue Outdated
@codecov

codecov Bot commented Jul 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 81.81818% with 26 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/storybook/mocks/useBillingContext.ts 0.00% 10 Missing ⚠️
src/storybook/mocks/useWorkspaceUI.ts 0.00% 8 Missing ⚠️
...ace/components/InsufficientCreditsMemberDialog.vue 55.55% 4 Missing ⚠️
...omponents/dialogs/settings/BillingStatusBanner.vue 96.87% 2 Missing ⚠️
src/composables/billing/useBillingContext.ts 80.00% 1 Missing ⚠️
src/storybook/mocks/useFeatureFlags.ts 0.00% 1 Missing ⚠️
@@            Coverage Diff             @@
##             main   #13641      +/-   ##
==========================================
- Coverage   77.96%   77.78%   -0.18%     
==========================================
  Files        1667     1677      +10     
  Lines       95411    97028    +1617     
  Branches    33434    34584    +1150     
==========================================
+ Hits        74383    75472    +1089     
- Misses      20362    20870     +508     
- Partials      666      686      +20     
Flag Coverage Δ
unit 67.10% <81.81%> (+0.41%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/composables/billing/useBillingRouting.ts 100.00% <100.00%> (ø)
src/composables/useFeatureFlags.ts 82.27% <100.00%> (ø)
...iption/composables/useAccountPreconditionDialog.ts 100.00% <100.00%> (ø)
.../subscription/composables/useSubscriptionDialog.ts 65.59% <100.00%> (-21.56%) ⬇️
src/platform/errorCatalog/runtimeErrorMatcher.ts 98.29% <100.00%> (+<0.01%) ⬆️
src/platform/remoteConfig/refreshRemoteConfig.ts 100.00% <100.00%> (ø)
src/platform/remoteConfig/remoteConfig.ts 100.00% <100.00%> (ø)
src/platform/workspace/api/workspaceApi.ts 55.46% <ø> (ø)
...ponents/dialogs/settings/WorkspacePanelContent.vue 96.29% <ø> (ø)
...platform/workspace/composables/useBillingBanner.ts 100.00% <100.00%> (ø)
... and 7 more

... and 36 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@dante01yoon dante01yoon changed the title feat: role-aware credit exhaustion UX for team workspaces feat: role-aware credit exhaustion UX for team workspaces (FE-1246) Jul 14, 2026
@dante01yoon
dante01yoon force-pushed the jaewon/fe-1246-team-credit-exhaustion-blocked-state-ux-role-aware-cta branch from 6ff79af to 49c4cd7 Compare July 14, 2026 22:23
@dante01yoon dante01yoon changed the title feat: role-aware credit exhaustion UX for team workspaces (FE-1246) feat: role-aware billing status banner for team workspaces Jul 14, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/platform/workspace/composables/useBillingBanner.ts`:
- Around line 87-95: Add test coverage for the funded-reset watcher in the
billing banner composable: update the existing BillingStatusBanner test flow to
dismiss the out-of-credits banner, change the workspace subscription state to
funded, then verify dismissal resets and a subsequent exhaustion displays the
banner again. Keep deriveBillingBanner.test.ts focused on the pure function and
exercise the reactive watch through the banner/composable test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 62819318-8292-4aa1-9ba2-ed8eca4c3b8e

📥 Commits

Reviewing files that changed from the base of the PR and between 6ff79af and 49c4cd7.

📒 Files selected for processing (19)
  • src/locales/en/main.json
  • src/platform/cloud/subscription/composables/useAccountPreconditionDialog.test.ts
  • src/platform/cloud/subscription/composables/useAccountPreconditionDialog.ts
  • src/platform/cloud/subscription/composables/useSubscriptionDialog.test.ts
  • src/platform/cloud/subscription/composables/useSubscriptionDialog.ts
  • src/platform/errorCatalog/accountPreconditionRouting.test.ts
  • src/platform/errorCatalog/runtimeErrorMatcher.ts
  • src/platform/workspace/api/workspaceApi.ts
  • src/platform/workspace/components/BillingStatusBanner.test.ts
  • src/platform/workspace/components/BillingStatusBanner.vue
  • src/platform/workspace/components/InsufficientCreditsMemberDialog.vue
  • src/platform/workspace/composables/deriveBillingBanner.test.ts
  • src/platform/workspace/composables/useBillingBanner.ts
  • src/services/dialogService.topUpCredits.test.ts
  • src/services/dialogService.ts
  • src/views/GraphView.test.ts
  • src/views/GraphView.vue
  • src/views/LinearView.test.ts
  • src/views/LinearView.vue

Comment thread src/platform/workspace/composables/useBillingBanner.ts
Surface a single priority-ordered billing banner on the graph and linear
shells (FE-1246 + FE-968 banner states):

- BillingStatusBanner renders one of paused > payment_failed > out-of-credits
  > ending, gated to team workspaces. Owner variants get an action (Update
  payment / Add credits / Reactivate); members get read-only copy for paused
  and out-of-credits, and fall through owner-only states.
- Pure deriveBillingBanner() encodes the priority + role gating; a thin
  useBillingbanner() shared composable feeds it from useBillingContext, and
  owns the session dismiss for the out-of-credits banner.
- out-of-credits also routes the submit-time rejection to the credits
  account-precondition modal (team 429 message + personal insufficient_credits
  type), with team members getting a read-only notice instead of the top-up
  dialog.
- paused is an inert stub until the backend projects a 'paused' subscription
  status; every other state renders from today's billing fields.
@dante01yoon
dante01yoon force-pushed the jaewon/fe-1246-team-credit-exhaustion-blocked-state-ux-role-aware-cta branch from 49c4cd7 to 596c71a Compare July 14, 2026 23:15

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/platform/cloud/subscription/composables/useAccountPreconditionDialog.ts (1)

14-44: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Call composables synchronously in setup.

useBillingContext() is a Vue composable and should be called synchronously at the root of useAccountPreconditionDialog (during the component's setup phase), rather than asynchronously inside the open method. Calling composables inside event handlers or delayed callbacks can fail if they rely on getCurrentInstance() or inject() and haven't been initialized in an active effect scope.

🐛 Proposed fix
 export function useAccountPreconditionDialog() {
   const dialogService = useDialogService()
+  const { fetchStatus, fetchBalance } = useBillingContext()
 
   function open(
     precondition: AccountPrecondition,
     context: AccountPreconditionContext = {}
   ): void {
     switch (precondition) {
       case 'sign_in':
         void dialogService.showApiNodesSignInDialog(
           context.nodeType ? [context.nodeType] : []
         )
         return
       case 'subscription':
         void dialogService.showSubscriptionRequiredDialog({
           reason: 'subscription_required'
         })
         return
       case 'credits': {
         // The server just declared the balance exhausted; there is no push or
         // polling for billing state, so refresh it here to converge
         // hasFunds-keyed surfaces such as the credits-exhausted banner. The
         // refresh is best-effort: allSettled keeps a flaky billing API from
         // surfacing as unhandled rejections.
-        const { fetchStatus, fetchBalance } = useBillingContext()
         void Promise.allSettled([fetchStatus(), fetchBalance()])
         void dialogService.showTopUpCreditsDialog({
           isInsufficientCredits: true
         })
         return
       }
     }
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/platform/cloud/subscription/composables/useAccountPreconditionDialog.ts`
around lines 14 - 44, Move the useBillingContext() call from the credits branch
of open into the root of useAccountPreconditionDialog, alongside
useDialogService(), and retain the returned fetchStatus and fetchBalance
functions for use in the credits case. Keep the existing best-effort
Promise.allSettled refresh and dialog behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/platform/workspace/components/InsufficientCreditsMemberDialog.vue`:
- Around line 9-25: Replace the Tailwind foreground alias classes in the title,
close button, and description within the dialog template: change
text-base-foreground to text-(--base-foreground) and text-muted-foreground to
text-(--muted-foreground), preserving the existing styling and behavior.

In `@src/platform/workspace/composables/useBillingBanner.ts`:
- Around line 69-104: Scope the out-of-credits dismissed state in
useBillingBannerInternal to the current workspace, clearing it when the active
workspace changes so dismissal cannot carry across workspaces while the shared
composable remains mounted. Preserve the existing simultaneous-mount behavior.
Add a workspace-switch test in
src/platform/workspace/components/BillingStatusBanner.test.ts lines 190-203
covering dismissal, switching workspaces, and re-showing the banner.

---

Duplicate comments:
In `@src/platform/cloud/subscription/composables/useAccountPreconditionDialog.ts`:
- Around line 14-44: Move the useBillingContext() call from the credits branch
of open into the root of useAccountPreconditionDialog, alongside
useDialogService(), and retain the returned fetchStatus and fetchBalance
functions for use in the credits case. Keep the existing best-effort
Promise.allSettled refresh and dialog behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 28309e64-c48d-4915-a3ce-6a7e92e62a3a

📥 Commits

Reviewing files that changed from the base of the PR and between 49c4cd7 and 596c71a.

📒 Files selected for processing (20)
  • src/locales/en/main.json
  • src/platform/cloud/subscription/composables/useAccountPreconditionDialog.test.ts
  • src/platform/cloud/subscription/composables/useAccountPreconditionDialog.ts
  • src/platform/cloud/subscription/composables/useSubscriptionDialog.test.ts
  • src/platform/cloud/subscription/composables/useSubscriptionDialog.ts
  • src/platform/errorCatalog/accountPreconditionRouting.test.ts
  • src/platform/errorCatalog/runtimeErrorMatcher.ts
  • src/platform/workspace/api/workspaceApi.ts
  • src/platform/workspace/components/BillingStatusBanner.test.ts
  • src/platform/workspace/components/BillingStatusBanner.vue
  • src/platform/workspace/components/InsufficientCreditsMemberDialog.vue
  • src/platform/workspace/composables/deriveBillingBanner.test.ts
  • src/platform/workspace/composables/useBillingBanner.test.ts
  • src/platform/workspace/composables/useBillingBanner.ts
  • src/services/dialogService.topUpCredits.test.ts
  • src/services/dialogService.ts
  • src/views/GraphView.test.ts
  • src/views/GraphView.vue
  • src/views/LinearView.test.ts
  • src/views/LinearView.vue

Comment thread src/platform/workspace/composables/useBillingBanner.ts
@dante01yoon dante01yoon changed the title feat: role-aware billing status banner for team workspaces feat: role-aware billing status banner for team workspaces (FE-1246) Jul 14, 2026
Investigating the cloud backend showed the paused signal belongs on the
payment-lifecycle axis, not the subscription-activity one: a Stripe-paused
subscription row stays `active`, and the pause is carried by billing_status
(CONVENTIONS §4 T3). Cloud PR #5075 adds the `paused` value there.

Two consequences the previous shape got wrong:

- The check has to sit above the isActiveSubscription gate. The backend folds
  billing_status into is_active, so a paused workspace always reports
  is_active=false and a check below the gate would be unreachable.
- The paused tests inherited isActiveSubscription: true from the funded fixture
  — a pairing the backend can never emit, so they passed without exercising the
  gate at all. They now pin is_active=false.

subscription_status is no longer read by the banner, so it drops out of
BillingBannerInputs and the mocks that fed it.

Adds Storybook stories covering all six role/state variants. They drive the real
deriveBillingBanner through a stubbed billing context, so a story can only show
a state the backend can actually produce.
@dosubot dosubot Bot added size:XL This PR changes 500-999 lines, ignoring generated files. and removed size:L This PR changes 100-499 lines, ignoring generated files. labels Jul 15, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

♻️ Duplicate comments (2)
src/platform/workspace/composables/useBillingBanner.ts (2)

64-96: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Scope the dismissal state to a workspace, not the shared composable.

Because createSharedComposable only drops this ref after the last subscriber unmounts, a dismissal can carry into another active workspace while the banner stays mounted. If a user switches from one out-of-credits workspace to another out-of-credits workspace, the watcher won't fire and the banner will incorrectly remain dismissed.

Please scope the out-of-credits dismissed state to the current workspace, clearing it when the active workspace changes so dismissal cannot carry across workspaces. Preserve the existing simultaneous-mount behavior, and add a workspace-switch test in BillingStatusBanner.test.ts covering dismissal, switching workspaces, and re-showing the banner.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/platform/workspace/composables/useBillingBanner.ts` around lines 64 - 96,
The dismissed state in the shared billing composable must be reset when the
active workspace changes, rather than persisting across workspaces. Update the
workspace-aware logic around dismissed, hasExhaustedFunds, and the existing
workspace identity source to watch workspace changes and clear dismissal while
preserving shared simultaneous-mount behavior. Add a BillingStatusBanner.test.ts
case that dismisses the banner, switches to another out-of-credits workspace,
and verifies it reappears.

81-89: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Missing test coverage for the funded-reset watcher.

The watch(hasExhaustedFunds, ...) reset behavior described in the comment isn't exercised by any provided test. As per path instructions for .test.ts files, you must ensure that missing tests for any new decision logic are covered.

Please update the existing BillingStatusBanner.test.ts flow to dismiss the out-of-credits banner, change the workspace subscription state to funded, and then verify dismissal resets and a subsequent exhaustion displays the banner again. Keep deriveBillingBanner.test.ts focused on the pure function and exercise the reactive watch through the banner/composable test.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/platform/workspace/composables/useBillingBanner.ts` around lines 81 - 89,
Extend the existing BillingStatusBanner.test.ts flow to cover the
hasExhaustedFunds watcher: dismiss the out-of-credits banner, update the
workspace subscription to funded, assert dismissal resets, then transition back
to exhausted and verify the banner appears again. Keep
deriveBillingBanner.test.ts limited to pure-function behavior and exercise this
reactive reset through the banner/composable test.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@src/platform/workspace/composables/useBillingBanner.ts`:
- Around line 64-96: The dismissed state in the shared billing composable must
be reset when the active workspace changes, rather than persisting across
workspaces. Update the workspace-aware logic around dismissed,
hasExhaustedFunds, and the existing workspace identity source to watch workspace
changes and clear dismissal while preserving shared simultaneous-mount behavior.
Add a BillingStatusBanner.test.ts case that dismisses the banner, switches to
another out-of-credits workspace, and verifies it reappears.
- Around line 81-89: Extend the existing BillingStatusBanner.test.ts flow to
cover the hasExhaustedFunds watcher: dismiss the out-of-credits banner, update
the workspace subscription to funded, assert dismissal resets, then transition
back to exhausted and verify the banner appears again. Keep
deriveBillingBanner.test.ts limited to pure-function behavior and exercise this
reactive reset through the banner/composable test.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 3eaf819f-1240-4509-b3a9-8f162c2524f0

📥 Commits

Reviewing files that changed from the base of the PR and between 596c71a and 5c5de46.

⛔ Files ignored due to path filters (6)
  • .github/pr-assets/banner-ending-owner.png is excluded by !**/*.png
  • .github/pr-assets/banner-out-of-credits-member.png is excluded by !**/*.png
  • .github/pr-assets/banner-out-of-credits-owner.png is excluded by !**/*.png
  • .github/pr-assets/banner-paused-member.png is excluded by !**/*.png
  • .github/pr-assets/banner-paused-owner.png is excluded by !**/*.png
  • .github/pr-assets/banner-payment-declined.png is excluded by !**/*.png
📒 Files selected for processing (9)
  • .storybook/main.ts
  • src/platform/workspace/api/workspaceApi.ts
  • src/platform/workspace/components/BillingStatusBanner.stories.ts
  • src/platform/workspace/components/BillingStatusBanner.test.ts
  • src/platform/workspace/composables/deriveBillingBanner.test.ts
  • src/platform/workspace/composables/useBillingBanner.test.ts
  • src/platform/workspace/composables/useBillingBanner.ts
  • src/storybook/mocks/useBillingContext.ts
  • src/storybook/mocks/useWorkspaceUI.ts
💤 Files with no reviewable changes (1)
  • src/platform/workspace/composables/useBillingBanner.test.ts

The prototype on comfydesigner/team-workspaces-v1 mounts BillingStatusBanner
only inside the settings panels (MembersPanelContent, PlanCreditsPanelContent,
PartnerNodesPanelContent) and puts no billing banner in GraphView or LinearView.
Mounting it in the app shell was unfounded — match the prototype and Figma.

Ours takes a single slot in WorkspacePanelContent between the tab list and the
tab body, which is what "one banner above every settings tab" means for our
two-tab container, rather than duplicating it into each panel.

Drops the app-shell wrapper padding: spacing now comes from the mount site, and
the @container stays on the banner so it isn't coupled to a parent's classes.

The GraphView/LinearView placement tests go with the mounts. WorkspacePanelContent
gains one pinning the banner above the tab body.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
src/platform/workspace/components/dialogs/settings/BillingStatusBanner.vue (1)

13-16: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use the information icon for the muted state.

The PR description specifies that the "Plan ends" state (which corresponds to banner.muted) should display an "information icon (ⓘ)". However, icon-[lucide--circle-alert] renders an exclamation mark. Consider changing this to icon-[lucide--info] (or your project's equivalent info icon) to match the intended design.

🎨 Proposed fix
-                  ? 'icon-[lucide--circle-alert] text-muted-foreground'
+                  ? 'icon-[lucide--info] text-muted-foreground'
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/platform/workspace/components/dialogs/settings/BillingStatusBanner.vue`
around lines 13 - 16, Update the muted-state icon selection in the banner class
binding to use the project’s information icon, such as icon-[lucide--info],
instead of the alert-circle icon; keep the existing warning icon for non-muted
banners unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@src/platform/workspace/components/dialogs/settings/WorkspacePanelContent.test.ts`:
- Around line 116-121: Rename the local variable planPanel to planTabTrigger in
the test, including its declaration and the compareDocumentPosition assertion,
while leaving the selected element and assertion behavior unchanged.

---

Outside diff comments:
In `@src/platform/workspace/components/dialogs/settings/BillingStatusBanner.vue`:
- Around line 13-16: Update the muted-state icon selection in the banner class
binding to use the project’s information icon, such as icon-[lucide--info],
instead of the alert-circle icon; keep the existing warning icon for non-muted
banners unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ff30f8d3-89f2-4124-9240-f642efbc56d6

📥 Commits

Reviewing files that changed from the base of the PR and between 5c5de46 and 03ac671.

⛔ Files ignored due to path filters (6)
  • .github/pr-assets/banner-ending-owner.png is excluded by !**/*.png
  • .github/pr-assets/banner-out-of-credits-member.png is excluded by !**/*.png
  • .github/pr-assets/banner-out-of-credits-owner.png is excluded by !**/*.png
  • .github/pr-assets/banner-paused-member.png is excluded by !**/*.png
  • .github/pr-assets/banner-paused-owner.png is excluded by !**/*.png
  • .github/pr-assets/banner-payment-declined.png is excluded by !**/*.png
📒 Files selected for processing (5)
  • src/platform/workspace/components/dialogs/settings/BillingStatusBanner.stories.ts
  • src/platform/workspace/components/dialogs/settings/BillingStatusBanner.test.ts
  • src/platform/workspace/components/dialogs/settings/BillingStatusBanner.vue
  • src/platform/workspace/components/dialogs/settings/WorkspacePanelContent.test.ts
  • src/platform/workspace/components/dialogs/settings/WorkspacePanelContent.vue

The banner claimed to target team-plan users but tested workspaceType === 'team'.
Those are different things, and the gap is real in both directions:

- A team workspace can sit on a retired seat-based plan (tier STANDARD/CREATOR/
  PRO), which the type gate admits.
- Once consolidated billing lands, a personal workspace can hold a team plan
  (cloud#5010). Today the backend still blocks that pending BE-1526, so the type
  gate is only accidentally right, and only for now.

Gate on plan identity instead. useBillingContext gains isTeamPlan: a credit stop
marks the per-credit Team plan, a `team-` slug the retired seat-based ones.
Deliberately not gated on isActiveSubscription, unlike isLegacyTeamPlan — the
spend gate folds billing_status into is_active, so a paused or payment-failed
team plan reports is_active=false and would otherwise stop reading as a team
plan exactly when the banner is needed. Not gated on subscription_tier either:
that would drop every legacy team subscriber, and the FE cannot even express
'TEAM' (tierPricing.ts resolves SubscriptionTier from the registry spec for what
is an ingest field — a separate bug).

Also fixes payment_failed, which was unreachable. It denies spend, so it always
arrives with is_active=false, and the check sat below the is_active gate — the
same trap already documented for paused two lines above it. Every team in Stripe
dunning saw no banner. Hoisted alongside paused.

Its tests passed only because they spread the funded fixture (isActiveSubscription:
true) onto payment_failed, a pairing the backend never emits. They now pin
is_active=false. Both fixes are mutation-tested: reverting either kills tests.

Members no longer fall through from payment_failed to out-of-credits — with the
real pairing that path was never reachable, and DES-380 says members never see
the payment banner.
Replaces consolidated_billing_enabled as the flag that decides whether a
personal workspace uses workspace-scoped billing. Team workspaces are
unaffected — they are always workspace-scoped.

This matters for the banner: isTeamPlan requires the workspace billing rail, so
a personal workspace holding a team plan only surfaces billing state once its
routing flag is on. Leaving routing on consolidated_billing_enabled while the
subscribe path unblocks under billing_control_enabled would let the two diverge
— a personal workspace could buy a team plan and then be routed to legacy, which
has no billing_status at all, blanking the banner.

consolidated_billing_enabled had exactly one consumer (this routing check), so it
is replaced rather than left alongside. Cloud registers the new flag in
Comfy-Org/cloud#5091.

Renames the whole chain: remote config field, cached session ref, ServerFeatureFlag
member, and the flags accessor.
@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. and removed size:XL This PR changes 500-999 lines, ignoring generated files. labels Jul 15, 2026
jtydhr88
jtydhr88 previously approved these changes Jul 15, 2026
@jtydhr88 jtydhr88 assigned dante01yoon and unassigned jtydhr88 Jul 15, 2026
The six PNGs were committed only to embed in the PR description. The Storybook
stories they were captured from are the reproducible artifact and stay.
@dosubot dosubot Bot added size:XL This PR changes 500-999 lines, ignoring generated files. and removed size:L This PR changes 100-499 lines, ignoring generated files. labels Jul 16, 2026
An audit of this branch's comments against the code found three assertions that
are not true of the system:

- "a team workspace can sit on a personal-tier legacy plan today" — it cannot.
  Plan rows are exclusive by construction (repository.go: AllowsTeam &&
  !AllowsPersonal), and subscribe/preview/list all reject the cross-type case
  with INCOMPATIBLE_PLAN. The BE-1526 clause alone justifies the plan gate, so
  the false half is dropped rather than reworded.
- "Shared across every banner mount (graph + linear)" — those mounts were
  deleted earlier on this branch (03ac671) and the parenthetical was left
  behind. createSharedComposable is still right, but for a different reason: the
  settings panel unmounts when the dialog closes.
- The dismiss test's name repeated the same graph/linear claim. It now says what
  the test actually checks.
The flag rename missed browser_tests, so the spec kept booting with
consolidated_billing_enabled. That no longer routes personal workspaces to the
unified pricing table, and the deep-link assertions timed out waiting for a
heading that never rendered.

The spec types BOOT_FEATURES as `satisfies RemoteConfig`, which would have caught
this — but pnpm typecheck only covers the root tsconfig, whose include list omits
browser_tests. Verified with vue-tsc against browser_tests/tsconfig.json.
Put the whole FE-1246 banner behind the flag so a PostHog rollback hides
it for every workspace, not just personal ones routed off the flag.
@jtydhr88 jtydhr88 assigned dante01yoon and unassigned jtydhr88 Jul 16, 2026
@dante01yoon
dante01yoon enabled auto-merge July 17, 2026 00:02
@dante01yoon
dante01yoon disabled auto-merge July 17, 2026 00:02
@dante01yoon

Copy link
Copy Markdown
Collaborator Author

@coderabbitai approve

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Comments resolved and changes approved.

@dante01yoon
dante01yoon added this pull request to the merge queue Jul 17, 2026
Merged via the queue into main with commit cfaf89e Jul 17, 2026
61 checks passed
@dante01yoon
dante01yoon deleted the jaewon/fe-1246-team-credit-exhaustion-blocked-state-ux-role-aware-cta branch July 17, 2026 00:21
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 17, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants