Skip to content

feat(featureFlags): employee-gated session feature flag overrides via ?ff= - #15033

Merged
huang47 merged 7 commits into
mainfrom
glary/feature-flag-url-overrides
Aug 11, 2026
Merged

feat(featureFlags): employee-gated session feature flag overrides via ?ff=#15033
huang47 merged 7 commits into
mainfrom
glary/feature-flag-url-overrides

Conversation

@huang47

@huang47 huang47 commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Summary

Lets a verified Comfy employee override any server feature flag for their own cloud browser tab with ?ff=name or ?ff=name:value. This supports testing branches and bugs against real cloud data without changing remote configuration.

Changes

  • Adds src/utils/sessionFeatureFlagOverride.ts.
  • Resolves flags in this order: URL session override → dev localStorage → remote config → server flag → default.
  • Covers both useFeatureFlags and direct api.getServerFeature / serverSupportsFeature reads.
  • Supports repeated ?ff= parameters and JSON value semantics:
    • ?ff=featuretrue
    • ?ff=feature:falsefalse
    • ?ff=max_upload_size:209715200 → number
    • ?ff=signup_turnstile:enforce → string
    • ?ff= → clear all overrides for the tab
  • Stores overrides in sessionStorage: they survive reload/navigation and disappear when the tab closes.
  • Applies only to verified @comfy.org users in the cloud distribution. The identity gate is re-evaluated on every read so overrides activate after auth resolves and stop applying after sign-out.
  • Adds characterization and precedence coverage for both feature-flag access paths.
  • Adds no dependencies.

Review notes

  • Every flag is overridable; there is no opt-in registry.
  • The verified employee check is the only authorization boundary, so the signed-out/non-employee negative case is security-critical.
  • The override reads raw window.location.search because feature flags are consumed before Vue Router and extension setup are available.
  • useCurrentUser() is used instead of authStore to avoid the authStore → useFeatureFlags import cycle and app-wide module side effects.

Verification

Current GitHub checks pass, including unit, E2E, Storybook, lint/format, website build/E2E, binary size, telemetry, OSS assets, and cloud frontend build dispatch.

Manually verified in cloud and localhost distributions for employee gating, lookalike and unverified addresses, precedence, typed values, reload/navigation persistence, per-tab isolation, clearing, and non-cloud inactivity.

Follow-ups

  • FE-1551 — centralize query-state reading without regressing pre-router access.
  • FE-1552 — storage-helper extraction decision; deferred unless identical semantics recur.

Adds an opt-in registry of feature flags that Comfy employees can override
per browser tab with ?ff=name or ?ff=name:value, resolved ahead of the dev
localStorage override, remote config and the server flag.

Overrides are captured into sessionStorage so they survive reloads and
in-app navigation but die with the tab, and are only applied for a signed-in
user with a verified @comfy.org address on the cloud distribution.
…emoised

Persist the query string the overrides were captured from, so re-reading the
same URL neither re-warns nor rewrites storage. This drops the module-level
memo, which per docs/guidance/vitest.md forced the test file to re-import
behind vi.resetModules() for every case.
@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown

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

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

🎨 Storybook: ✅ Built — View Storybook

Details

⏰ Completed at: 08/11/2026, 07:33:20 AM UTC

Links

📦 Bundle: 8.69 MB gzip 🔴 +487 B

Details

Summary

  • Raw size: 36.7 MB baseline 36.7 MB — 🔴 +1.06 kB
  • Gzip: 8.69 MB baseline 8.69 MB — 🔴 +487 B
  • Brotli: 6.06 MB baseline 6.06 MB — 🔴 +20 B
  • Bundles: 437 current • 437 baseline • 191 added / 191 removed

Category Glance
Data & Services 🔴 +687 B (3.5 MB) · Utilities & Hooks 🔴 +268 B (550 kB) · Vendor & Third-Party 🔴 +103 B (16.3 MB) · Graph Workspace 🔴 +1 B (1.36 MB) · Other ⚪ 0 B (14 MB) · Panels & Settings ⚪ 0 B (565 kB) · + 5 more

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

Main entry bundles and manifests

File Before After Δ Raw Δ Gzip Δ Brotli
assets/index-CsdVvOfy.js (removed) 3.67 kB 🟢 -3.67 kB 🟢 -1.84 kB 🟢 -1.61 kB
assets/index-QPXOtvvR.js (new) 3.67 kB 🔴 +3.67 kB 🔴 +1.84 kB 🔴 +1.59 kB

Status: 1 added / 1 removed

Graph Workspace — 1.36 MB (baseline 1.36 MB) • 🔴 +1 B

Graph editor runtime, canvas, workflow orchestration

File Before After Δ Raw Δ Gzip Δ Brotli
assets/GraphView-CKd6Mata.js (new) 1.35 MB 🔴 +1.35 MB 🔴 +294 kB 🔴 +221 kB
assets/GraphView-Bp6iLw__.js (removed) 1.35 MB 🟢 -1.35 MB 🟢 -294 kB 🟢 -221 kB
assets/WidgetCompositor-BDlwd84T.js (new) 8.17 kB 🔴 +8.17 kB 🔴 +2.75 kB 🔴 +2.46 kB
assets/WidgetCompositor-DwOcQWFT.js (removed) 8.17 kB 🟢 -8.17 kB 🟢 -2.75 kB 🟢 -2.45 kB

Status: 2 added / 2 removed / 1 unchanged

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

Top-level views, pages, and routed surfaces

File Before After Δ Raw Δ Gzip Δ Brotli
assets/CloudSurveyView-BtGJnxGI.js (removed) 25 kB 🟢 -25 kB 🟢 -6.24 kB 🟢 -5.52 kB
assets/CloudSurveyView-CJvW2E_X.js (new) 25 kB 🔴 +25 kB 🔴 +6.24 kB 🔴 +5.52 kB
assets/CloudLayoutView-CfdKToSh.js (new) 21.8 kB 🔴 +21.8 kB 🔴 +6.56 kB 🔴 +5.74 kB
assets/CloudLayoutView-Dg-dF3c0.js (removed) 21.8 kB 🟢 -21.8 kB 🟢 -6.56 kB 🟢 -5.74 kB
assets/OAuthConsentView-Bi7WnOex.js (removed) 15.5 kB 🟢 -15.5 kB 🟢 -4.25 kB 🟢 -3.71 kB
assets/OAuthConsentView-D8UBYPfB.js (new) 15.5 kB 🔴 +15.5 kB 🔴 +4.25 kB 🔴 +3.71 kB
assets/UserCheckView-BrJFMOBY.js (removed) 8.75 kB 🟢 -8.75 kB 🟢 -2.19 kB 🟢 -1.9 kB
assets/UserCheckView-kCYOSkae.js (new) 8.75 kB 🔴 +8.75 kB 🔴 +2.19 kB 🔴 +1.89 kB
assets/CloudLoginView-CEXOZcQn.js (removed) 8.74 kB 🟢 -8.74 kB 🟢 -2.55 kB 🟢 -2.26 kB
assets/CloudLoginView-H3-0wxAz.js (new) 8.74 kB 🔴 +8.74 kB 🔴 +2.55 kB 🔴 +2.27 kB
assets/useCloudAuthPage-ChjmqJRo.js (removed) 7.08 kB 🟢 -7.08 kB 🟢 -2.51 kB 🟢 -2.22 kB
assets/useCloudAuthPage-D7HjQ_E0.js (new) 7.08 kB 🔴 +7.08 kB 🔴 +2.51 kB 🔴 +2.21 kB
assets/CloudSignupView-CsLLX3O4.js (removed) 6.56 kB 🟢 -6.56 kB 🟢 -2.17 kB 🟢 -1.91 kB
assets/CloudSignupView-x2cPLvUF.js (new) 6.56 kB 🔴 +6.56 kB 🔴 +2.17 kB 🔴 +1.9 kB
assets/WidgetTextPreview-2ZYbs5ng.js (new) 6.07 kB 🔴 +6.07 kB 🔴 +2.13 kB 🔴 +1.89 kB
assets/WidgetTextPreview-Dzp0tiJS.js (removed) 6.07 kB 🟢 -6.07 kB 🟢 -2.13 kB 🟢 -1.89 kB
assets/CloudSubscriptionRedirectView-DosHt7PD.js (new) 6.05 kB 🔴 +6.05 kB 🔴 +2.25 kB 🔴 +1.96 kB
assets/CloudSubscriptionRedirectView-uOP6gipr.js (removed) 6.05 kB 🟢 -6.05 kB 🟢 -2.25 kB 🟢 -1.96 kB
assets/UserSelectView-DrCErb6u.js (new) 5.49 kB 🔴 +5.49 kB 🔴 +1.95 kB 🔴 +1.71 kB
assets/UserSelectView-zBVaaBZy.js (removed) 5.49 kB 🟢 -5.49 kB 🟢 -1.95 kB 🟢 -1.71 kB
assets/CloudForgotPasswordView-C6R-RQvQ.js (new) 4.97 kB 🔴 +4.97 kB 🔴 +1.72 kB 🔴 +1.49 kB
assets/CloudForgotPasswordView-C8cA7eg0.js (removed) 4.97 kB 🟢 -4.97 kB 🟢 -1.72 kB 🟢 -1.49 kB
assets/CloudAuthTimeoutView-_wBTKS09.js (new) 4.43 kB 🔴 +4.43 kB 🔴 +1.54 kB 🔴 +1.33 kB
assets/CloudAuthTimeoutView-POMlrWof.js (removed) 4.43 kB 🟢 -4.43 kB 🟢 -1.54 kB 🟢 -1.33 kB
assets/OAuthLayoutView-8PdbZIuZ.js (removed) 1.31 kB 🟢 -1.31 kB 🟢 -697 B 🟢 -594 B
assets/OAuthLayoutView-DFV7hJBs.js (new) 1.31 kB 🔴 +1.31 kB 🔴 +698 B 🔴 +597 B
assets/layout-BZ215wfF.js (new) 403 B 🔴 +403 B 🔴 +251 B 🔴 +206 B
assets/layout-JbjMjTKo.js (removed) 403 B 🟢 -403 B 🟢 -252 B 🟢 -207 B
assets/WidgetTextPreview-BULDJeL5.js (removed) 131 B 🟢 -131 B 🟢 -100 B 🟢 -90 B
assets/WidgetTextPreview-hIr1WHqB.js (new) 131 B 🔴 +131 B 🔴 +100 B 🔴 +91 B

Status: 15 added / 15 removed / 2 unchanged

Panels & Settings — 565 kB (baseline 565 kB) • ⚪ 0 B

Configuration panels, inspectors, and settings screens

File Before After Δ Raw Δ Gzip Δ Brotli
assets/KeybindingPanel-BiT56uKL.js (new) 49.4 kB 🔴 +49.4 kB 🔴 +9.94 kB 🔴 +8.81 kB
assets/KeybindingPanel-D6aCtJZc.js (removed) 49.4 kB 🟢 -49.4 kB 🟢 -9.94 kB 🟢 -8.8 kB
assets/SecretsPanel-BAc3LWPZ.js (removed) 33.7 kB 🟢 -33.7 kB 🟢 -7.86 kB 🟢 -6.9 kB
assets/SecretsPanel-CFv4Sk3p.js (new) 33.7 kB 🔴 +33.7 kB 🔴 +7.87 kB 🔴 +6.89 kB
assets/CreditsPanel-Dsl0se-F.js (new) 11.5 kB 🔴 +11.5 kB 🔴 +3.2 kB 🔴 +2.81 kB
assets/CreditsPanel-Ohu5h1rD.js (removed) 11.5 kB 🟢 -11.5 kB 🟢 -3.2 kB 🟢 -2.81 kB
assets/AboutPanel-DtvaNHH4.js (removed) 11.2 kB 🟢 -11.2 kB 🟢 -3.02 kB 🟢 -2.72 kB
assets/AboutPanel-Dy_pGxA6.js (new) 11.2 kB 🔴 +11.2 kB 🔴 +3.03 kB 🔴 +2.72 kB
assets/ExtensionPanel-C1pHOglh.js (new) 9.19 kB 🔴 +9.19 kB 🔴 +2.51 kB 🔴 +2.23 kB
assets/ExtensionPanel-CT6NxHoC.js (removed) 9.19 kB 🟢 -9.19 kB 🟢 -2.51 kB 🟢 -2.23 kB
assets/ServerConfigPanel-By-RUCQG.js (new) 6.09 kB 🔴 +6.09 kB 🔴 +1.93 kB 🔴 +1.73 kB
assets/ServerConfigPanel-DPDL3u6T.js (removed) 6.09 kB 🟢 -6.09 kB 🟢 -1.93 kB 🟢 -1.72 kB
assets/UserPanel-CHdDg4ur.js (removed) 5.73 kB 🟢 -5.73 kB 🟢 -1.78 kB 🟢 -1.54 kB
assets/UserPanel-Nmw53yku.js (new) 5.73 kB 🔴 +5.73 kB 🔴 +1.78 kB 🔴 +1.54 kB
assets/refreshRemoteConfig-BjJe6pnT.js (new) 3.44 kB 🔴 +3.44 kB 🔴 +1.33 kB 🔴 +1.18 kB
assets/refreshRemoteConfig-Drm3ODl0.js (removed) 3.44 kB 🟢 -3.44 kB 🟢 -1.33 kB 🟢 -1.17 kB
assets/cloudRemoteConfig-B6-1-cy7.js (removed) 951 B 🟢 -951 B 🟢 -515 B 🟢 -438 B
assets/cloudRemoteConfig-Dox9Vnt5.js (new) 951 B 🔴 +951 B 🔴 +514 B 🔴 +424 B
assets/refreshRemoteConfig-DUr1Kvcl.js (removed) 110 B 🟢 -110 B 🟢 -89 B 🟢 -81 B
assets/refreshRemoteConfig-WlVQ6HRJ.js (new) 110 B 🔴 +110 B 🔴 +89 B 🔴 +77 B

Status: 10 added / 10 removed / 16 unchanged

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

Authentication, profile, and account management bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/SignUpForm-BV1di1Wq.js (removed) 12.8 kB 🟢 -12.8 kB 🟢 -4.31 kB 🟢 -3.76 kB
assets/SignUpForm-DwCn-6sg.js (new) 12.8 kB 🔴 +12.8 kB 🔴 +4.32 kB 🔴 +3.76 kB
assets/auth-CgvVRDUZ.js (removed) 3.71 kB 🟢 -3.71 kB 🟢 -1.28 kB 🟢 -1.1 kB
assets/auth-D4VxBmiv.js (new) 3.71 kB 🔴 +3.71 kB 🔴 +1.28 kB 🔴 +1.1 kB
assets/UpdatePasswordContent-gqm-Oigv.js (removed) 1.85 kB 🟢 -1.85 kB 🟢 -841 B 🟢 -733 B
assets/UpdatePasswordContent-n6Vo6dfk.js (new) 1.85 kB 🔴 +1.85 kB 🔴 +843 B 🔴 +733 B
assets/authStore-C22quI3s.js (removed) 128 B 🟢 -128 B 🟢 -104 B 🟢 -105 B
assets/authStore-OS4i8xB-.js (new) 128 B 🔴 +128 B 🔴 +104 B 🔴 +106 B
assets/workspaceAuthStore-1qo2NuiL.js (new) 108 B 🔴 +108 B 🔴 +99 B 🔴 +103 B
assets/workspaceAuthStore-mFwmURH2.js (removed) 108 B 🟢 -108 B 🟢 -99 B 🟢 -106 B
assets/auth-DfgaADUo.js (new) 105 B 🔴 +105 B 🔴 +96 B 🔴 +81 B
assets/auth-DiohBFl6.js (removed) 105 B 🟢 -105 B 🟢 -96 B 🟢 -88 B

Status: 6 added / 6 removed / 4 unchanged

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

Modals, dialogs, drawers, and in-app editors

File Before After Δ Raw Δ Gzip Δ Brotli
assets/ComfyHubPublishDialog-Bu1MjtHr.js (new) 90.1 kB 🔴 +90.1 kB 🔴 +19.3 kB 🔴 +16.5 kB
assets/ComfyHubPublishDialog-CRtk-Wko.js (removed) 90.1 kB 🟢 -90.1 kB 🟢 -19.3 kB 🟢 -16.5 kB
assets/useShareDialog-BKOODSdb.js (removed) 23.9 kB 🟢 -23.9 kB 🟢 -5.69 kB 🟢 -5.03 kB
assets/useShareDialog-Cq4kmgfh.js (new) 23.9 kB 🔴 +23.9 kB 🔴 +5.69 kB 🔴 +5.04 kB
assets/feedbackDialog-DkpuYzvA.js (removed) 4.45 kB 🟢 -4.45 kB 🟢 -1.87 kB 🟢 -1.59 kB
assets/feedbackDialog-q3mSEuU6.js (new) 4.45 kB 🔴 +4.45 kB 🔴 +1.86 kB 🔴 +1.59 kB
assets/useRangeEditor-DmOR_5ge.js (new) 3.29 kB 🔴 +3.29 kB 🔴 +1.14 kB 🔴 +1.04 kB
assets/useRangeEditor-DYEfNKT9.js (removed) 3.29 kB 🟢 -3.29 kB 🟢 -1.14 kB 🟢 -1.05 kB
assets/SubscriptionInactiveMemberDialog-CjfkWeDv.js (new) 2.48 kB 🔴 +2.48 kB 🔴 +999 B 🔴 +853 B
assets/SubscriptionInactiveMemberDialog-Dkyt0X_b.js (removed) 2.48 kB 🟢 -2.48 kB 🟢 -999 B 🟢 -853 B
assets/useLayerEditor-B1gZDS-M.js (new) 1.01 kB 🔴 +1.01 kB 🔴 +489 B 🔴 +407 B
assets/useLayerEditor-D7qxD9Mh.js (removed) 1.01 kB 🟢 -1.01 kB 🟢 -490 B 🟢 -405 B
assets/ComfyHubPublishDialog-Cj0JG1MK.js (removed) 143 B 🟢 -143 B 🟢 -105 B 🟢 -91 B
assets/ComfyHubPublishDialog-DkXI-ZZo.js (new) 143 B 🔴 +143 B 🔴 +105 B 🔴 +91 B
assets/useSubscriptionDialog-BSkUK2gW.js (removed) 108 B 🟢 -108 B 🟢 -102 B 🟢 -101 B
assets/useSubscriptionDialog-CXcz3NCc.js (new) 108 B 🔴 +108 B 🔴 +102 B 🔴 +93 B

Status: 8 added / 8 removed

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

Reusable component library chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/ComfyQueueButton-BdFXUr1g.js (new) 14.6 kB 🔴 +14.6 kB 🔴 +3.97 kB 🔴 +3.52 kB
assets/ComfyQueueButton-D3X6U9QA.js (removed) 14.6 kB 🟢 -14.6 kB 🟢 -3.96 kB 🟢 -3.52 kB
assets/useTerminalTabs-BiZUhls4.js (removed) 11.8 kB 🟢 -11.8 kB 🟢 -3.69 kB 🟢 -3.27 kB
assets/useTerminalTabs-CVhphZNr.js (new) 11.8 kB 🔴 +11.8 kB 🔴 +3.69 kB 🔴 +3.27 kB
assets/InviteMembersForm-9b4lvg1u.js (removed) 8.2 kB 🟢 -8.2 kB 🟢 -2.72 kB 🟢 -2.44 kB
assets/InviteMembersForm-Bba_lwsh.js (new) 8.2 kB 🔴 +8.2 kB 🔴 +2.72 kB 🔴 +2.44 kB
assets/TopbarBadge-BTObHhks.js (new) 7.65 kB 🔴 +7.65 kB 🔴 +1.83 kB 🔴 +1.62 kB
assets/TopbarBadge-Fcns33U-.js (removed) 7.65 kB 🟢 -7.65 kB 🟢 -1.83 kB 🟢 -1.62 kB
assets/ScrubableNumberInput-CFpmYT-5.js (new) 6.49 kB 🔴 +6.49 kB 🔴 +2.17 kB 🔴 +1.91 kB
assets/ScrubableNumberInput-CuEIyVtY.js (removed) 6.49 kB 🟢 -6.49 kB 🟢 -2.16 kB 🟢 -1.91 kB
assets/WidgetInputNumberInput-BW6AI6PD.js (new) 5.83 kB 🔴 +5.83 kB 🔴 +2.07 kB 🔴 +1.85 kB
assets/WidgetInputNumberInput-DiJqlqXU.js (removed) 5.83 kB 🟢 -5.83 kB 🟢 -2.07 kB 🟢 -1.85 kB
assets/Button-BAb1E1z4.js (new) 4.66 kB 🔴 +4.66 kB 🔴 +1.63 kB 🔴 +1.43 kB
assets/Button-Bqrwj1vb.js (removed) 4.66 kB 🟢 -4.66 kB 🟢 -1.63 kB 🟢 -1.43 kB
assets/SubscribeButton-CfPCiA5w.js (new) 2.15 kB 🔴 +2.15 kB 🔴 +968 B 🔴 +847 B
assets/SubscribeButton-DotVNWqR.js (removed) 2.15 kB 🟢 -2.15 kB 🟢 -966 B 🟢 -847 B
assets/WidgetButton-BHpomUDI.js (removed) 1.93 kB 🟢 -1.93 kB 🟢 -888 B 🟢 -777 B
assets/WidgetButton-PIGhL-j2.js (new) 1.93 kB 🔴 +1.93 kB 🔴 +888 B 🔴 +777 B
assets/Input-BcBdlwDn.js (new) 1.76 kB 🔴 +1.76 kB 🔴 +872 B 🔴 +767 B
assets/Input-hJdQNyFo.js (removed) 1.76 kB 🟢 -1.76 kB 🟢 -872 B 🟢 -769 B
assets/cloudFeedbackTopbarButton-BQ9Ymxtx.js (removed) 705 B 🟢 -705 B 🟢 -422 B 🟢 -361 B
assets/cloudFeedbackTopbarButton-CuJCgre4.js (new) 705 B 🔴 +705 B 🔴 +422 B 🔴 +359 B
assets/ComfyQueueButton-DpWIRuQa.js (new) 128 B 🔴 +128 B 🔴 +99 B 🔴 +88 B
assets/ComfyQueueButton-hVIz6T8A.js (removed) 128 B 🟢 -128 B 🟢 -99 B 🟢 -92 B
assets/toggle-group-BxYEJQJI.js (removed) 40 B 🟢 -40 B 🟢 -60 B 🟢 -44 B
assets/toggle-group-DXi8KFqz.js (new) 40 B 🔴 +40 B 🔴 +60 B 🔴 +44 B

Status: 13 added / 13 removed / 1 unchanged

Data & Services — 3.5 MB (baseline 3.5 MB) • 🔴 +687 B

Stores, services, APIs, and repositories

File Before After Δ Raw Δ Gzip Δ Brotli
assets/settingStore-DTN2C9gq.js (new) 3.22 MB 🔴 +3.22 MB 🔴 +746 kB 🔴 +562 kB
assets/settingStore-CHhlVqX7.js (removed) 3.22 MB 🟢 -3.22 MB 🟢 -746 kB 🟢 -562 kB
assets/load3dService-BPDuRJ6f.js (removed) 132 kB 🟢 -132 kB 🟢 -29.3 kB 🟢 -24.6 kB
assets/load3dService-C2XjchNX.js (new) 132 kB 🔴 +132 kB 🔴 +29.3 kB 🔴 +24.6 kB
assets/api-CMXlKE6d.js (new) 98.3 kB 🔴 +98.3 kB 🔴 +27.2 kB 🔴 +23.3 kB
assets/api-B1KmAG0n.js (removed) 97.7 kB 🟢 -97.7 kB 🟢 -26.9 kB 🟢 -23.2 kB
assets/workflowShareService-aZV2C7Hf.js (removed) 16.5 kB 🟢 -16.5 kB 🟢 -4.91 kB 🟢 -4.35 kB
assets/workflowShareService-H0xcYALY.js (new) 16.5 kB 🔴 +16.5 kB 🔴 +4.91 kB 🔴 +4.36 kB
assets/keybindingService-BlKV1L02.js (removed) 6.89 kB 🟢 -6.89 kB 🟢 -1.73 kB 🟢 -1.5 kB
assets/keybindingService-BPmVKGoE.js (new) 6.89 kB 🔴 +6.89 kB 🔴 +1.73 kB 🔴 +1.5 kB
assets/releaseStore-CMfm1e7u.js (new) 6.72 kB 🔴 +6.72 kB 🔴 +2.03 kB 🔴 +1.77 kB
assets/releaseStore-D6Sg_Pji.js (removed) 6.72 kB 🟢 -6.72 kB 🟢 -2.03 kB 🟢 -1.78 kB
assets/dialogStore-BWLPYke-.js (new) 5.35 kB 🔴 +5.35 kB 🔴 +1.63 kB 🔴 +1.41 kB
assets/dialogStore-DiVVDHx7.js (removed) 5.35 kB 🟢 -5.35 kB 🟢 -1.64 kB 🟢 -1.41 kB
assets/systemStatsStore-BMbIghv9.js (new) 4.93 kB 🔴 +4.93 kB 🔴 +1.74 kB 🔴 +1.47 kB
assets/systemStatsStore-cIzNTwd1.js (removed) 4.93 kB 🟢 -4.93 kB 🟢 -1.74 kB 🟢 -1.47 kB
assets/userStore-BV53H9RA.js (new) 2.38 kB 🔴 +2.38 kB 🔴 +899 B 🔴 +793 B
assets/userStore-CCtOxW1e.js (removed) 2.38 kB 🟢 -2.38 kB 🟢 -898 B 🟢 -791 B
assets/audioService-B_t9mYye.js (removed) 1.71 kB 🟢 -1.71 kB 🟢 -828 B 🟢 -721 B
assets/audioService-DHiC5E82.js (new) 1.71 kB 🔴 +1.71 kB 🔴 +829 B 🔴 +721 B
assets/dialogService-BwJciBGW.js (removed) 98 B 🟢 -98 B 🟢 -97 B 🟢 -81 B
assets/dialogService-CzJfrf8d.js (new) 98 B 🔴 +98 B 🔴 +97 B 🔴 +88 B
assets/releaseStore-CIdnujPZ.js (new) 95 B 🔴 +95 B 🔴 +86 B 🔴 +86 B
assets/releaseStore-d15fXsVK.js (removed) 95 B 🟢 -95 B 🟢 -86 B 🟢 -84 B
assets/settingStore-B28PUo0j.js (new) 95 B 🔴 +95 B 🔴 +86 B 🔴 +90 B
assets/settingStore-Cif5PrIu.js (removed) 95 B 🟢 -95 B 🟢 -86 B 🟢 -87 B
assets/assetsStore-BTpxrXst.js (new) 94 B 🔴 +94 B 🔴 +92 B 🔴 +85 B
assets/assetsStore-D0DapZAM.js (removed) 94 B 🟢 -94 B 🟢 -92 B 🟢 -86 B
assets/api-B47lYnS1.js (new) 62 B 🔴 +62 B 🔴 +74 B 🔴 +66 B
assets/api-DMuuapzT.js (removed) 62 B 🟢 -62 B 🟢 -74 B 🟢 -66 B

Status: 15 added / 15 removed / 2 unchanged

Utilities & Hooks — 550 kB (baseline 550 kB) • 🔴 +268 B

Helpers, composables, and utility bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useConflictDetection-DTDOQ_53.js (new) 236 kB 🔴 +236 kB 🔴 +53 kB 🔴 +43.2 kB
assets/useConflictDetection-tN0CGC1p.js (removed) 236 kB 🟢 -236 kB 🟢 -53 kB 🟢 -43.2 kB
assets/useLayerEditorSession-BpbC2E8L.js (new) 158 kB 🔴 +158 kB 🔴 +40.8 kB 🔴 +34.3 kB
assets/useLayerEditorSession-BYMLYJCX.js (removed) 158 kB 🟢 -158 kB 🟢 -40.8 kB 🟢 -34.3 kB
assets/useLoad3d-Bi91ewqg.js (new) 25.8 kB 🔴 +25.8 kB 🔴 +5.8 kB 🔴 +5.14 kB
assets/useLoad3d-Dj9n-tgO.js (removed) 25.8 kB 🟢 -25.8 kB 🟢 -5.8 kB 🟢 -5.14 kB
assets/useLoad3dViewer--TTeJtJw.js (new) 21.2 kB 🔴 +21.2 kB 🔴 +4.98 kB 🔴 +4.36 kB
assets/useLoad3dViewer-DEosAUyZ.js (removed) 21.2 kB 🟢 -21.2 kB 🟢 -4.98 kB 🟢 -4.37 kB
assets/useImageCrop-Cmbeio52.js (new) 14.9 kB 🔴 +14.9 kB 🔴 +3.42 kB 🔴 +2.98 kB
assets/useImageCrop-I28SBebZ.js (removed) 14.9 kB 🟢 -14.9 kB 🟢 -3.42 kB 🟢 -2.98 kB
assets/useDowngradeToPersonal-C5tNie6E.js (new) 10.9 kB 🔴 +10.9 kB 🔴 +2.75 kB 🔴 +2.36 kB
assets/useDowngradeToPersonal-DxKrw56k.js (removed) 10.9 kB 🟢 -10.9 kB 🟢 -2.75 kB 🟢 -2.37 kB
assets/useFeatureFlags-C34T3khZ.js (new) 6.99 kB 🔴 +6.99 kB 🔴 +2 kB 🔴 +1.7 kB
assets/useFeatureFlags-BFcFdq0X.js (removed) 6.73 kB 🟢 -6.73 kB 🟢 -1.96 kB 🟢 -1.66 kB
assets/useCompositorLayers-C_0v-Tb_.js (new) 2.94 kB 🔴 +2.94 kB 🔴 +899 B 🔴 +798 B
assets/useCompositorLayers-D5XBaXw2.js (removed) 2.94 kB 🟢 -2.94 kB 🟢 -898 B 🟢 -797 B
assets/assetPreviewUtil-CdhkXxEq.js (new) 2.35 kB 🔴 +2.35 kB 🔴 +972 B 🔴 +837 B
assets/assetPreviewUtil-CJKWOUxd.js (removed) 2.35 kB 🟢 -2.35 kB 🟢 -972 B 🟢 -835 B
assets/useUpstreamValue-BCQvAQal.js (new) 1.99 kB 🔴 +1.99 kB 🔴 +758 B 🔴 +678 B
assets/useUpstreamValue-CQ-qnLK4.js (removed) 1.99 kB 🟢 -1.99 kB 🟢 -758 B 🟢 -676 B
assets/useWorkspaceTierLabel-BxYzgUHP.js (new) 1.93 kB 🔴 +1.93 kB 🔴 +812 B 🔴 +698 B
assets/useWorkspaceTierLabel-oMfEGtxb.js (removed) 1.93 kB 🟢 -1.93 kB 🟢 -813 B 🟢 -698 B
assets/subscriptionCheckoutUtil-B-D3kwrQ.js (new) 877 B 🔴 +877 B 🔴 +522 B 🔴 +436 B
assets/subscriptionCheckoutUtil-DaaICchh.js (removed) 877 B 🟢 -877 B 🟢 -522 B 🟢 -437 B
assets/useSessionCookie-BfwF1_pr.js (new) 652 B 🔴 +652 B 🔴 +338 B 🔴 +290 B
assets/useSessionCookie-CKhWAPHd.js (removed) 652 B 🟢 -652 B 🟢 -338 B 🟢 -292 B
assets/useLoad3d-BI02SpXi.js (new) 311 B 🔴 +311 B 🔴 +163 B 🔴 +149 B
assets/useLoad3d-ChX79jS-.js (removed) 311 B 🟢 -311 B 🟢 -162 B 🟢 -147 B
assets/useSessionCookie-Cq7gCAw8.js (removed) 101 B 🟢 -101 B 🟢 -86 B 🟢 -80 B
assets/useSessionCookie-DBqKnCgb.js (new) 101 B 🔴 +101 B 🔴 +86 B 🔴 +77 B
assets/useFeatureFlags-CISu5PEe.js (new) 98 B 🔴 +98 B 🔴 +85 B 🔴 +80 B
assets/useFeatureFlags-DRTVy6QD.js (removed) 98 B 🟢 -98 B 🟢 -85 B 🟢 -82 B
assets/useLoad3dViewer-BoM6_ond.js (removed) 98 B 🟢 -98 B 🟢 -85 B 🟢 -83 B
assets/useLoad3dViewer-RSijmeSz.js (new) 98 B 🔴 +98 B 🔴 +85 B 🔴 +85 B
assets/useCurrentUser-Bpc6t9rX.js (new) 94 B 🔴 +94 B 🔴 +95 B 🔴 +89 B
assets/useCurrentUser-Dnnd-52o.js (removed) 94 B 🟢 -94 B 🟢 -95 B 🟢 -88 B

Status: 18 added / 18 removed / 20 unchanged

Vendor & Third-Party — 16.3 MB (baseline 16.3 MB) • 🔴 +103 B

External libraries and shared vendor chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/vendor-other-CpJsKsbF.js (new) 2.23 MB 🔴 +2.23 MB 🔴 +483 kB 🔴 +380 kB
assets/vendor-other-DFhyGpUj.js (removed) 2.23 MB 🟢 -2.23 MB 🟢 -483 kB 🟢 -380 kB
assets/vendor-firebase-CNFqGy-2.js (removed) 1.15 MB 🟢 -1.15 MB 🟢 -233 kB 🟢 -186 kB
assets/vendor-firebase-DZ9gHliD.js (new) 1.15 MB 🔴 +1.15 MB 🔴 +233 kB 🔴 +186 kB

Status: 2 added / 2 removed / 15 unchanged

Other — 14 MB (baseline 14 MB) • ⚪ 0 B

Bundles that do not match a named category

File Before After Δ Raw Δ Gzip Δ Brotli
assets/core-CtzzCLcS.js (new) 115 kB 🔴 +115 kB 🔴 +29.7 kB 🔴 +25.1 kB
assets/core-DF2GN5vt.js (removed) 115 kB 🟢 -115 kB 🟢 -29.7 kB 🟢 -25.1 kB
assets/WidgetSelect-B0IUQVPx.js (new) 88.8 kB 🔴 +88.8 kB 🔴 +20.1 kB 🔴 +17.2 kB
assets/WidgetSelect-BPJubciE.js (removed) 88.8 kB 🟢 -88.8 kB 🟢 -20.1 kB 🟢 -17.2 kB
assets/SubscriptionPanelContentWorkspace-B01PUDGT.js (new) 80 kB 🔴 +80 kB 🔴 +15.8 kB 🔴 +13.6 kB
assets/SubscriptionPanelContentWorkspace-CU2gVHcm.js (removed) 80 kB 🟢 -80 kB 🟢 -15.8 kB 🟢 -13.6 kB
assets/Load3D-CN8jkAbZ.js (new) 71.3 kB 🔴 +71.3 kB 🔴 +11.7 kB 🔴 +9.98 kB
assets/Load3D-DCc_h2Op.js (removed) 71.3 kB 🟢 -71.3 kB 🟢 -11.7 kB 🟢 -9.99 kB
assets/SubscriptionTransitionPreviewWorkspace-Bo8N3jmd.js (new) 66.4 kB 🔴 +66.4 kB 🔴 +13.4 kB 🔴 +11.7 kB
assets/SubscriptionTransitionPreviewWorkspace-DajaBa6L.js (removed) 66.4 kB 🟢 -66.4 kB 🟢 -13.4 kB 🟢 -11.7 kB
assets/WidgetVideoEdit-BI-EnWvM.js (removed) 63.4 kB 🟢 -63.4 kB 🟢 -14.6 kB 🟢 -12.8 kB
assets/WidgetVideoEdit-C6I-2-e4.js (new) 63.4 kB 🔴 +63.4 kB 🔴 +14.6 kB 🔴 +12.9 kB
assets/WorkspaceSettingsPanelContent-B-E_y_Nb.js (new) 61.7 kB 🔴 +61.7 kB 🔴 +13.2 kB 🔴 +11.5 kB
assets/WorkspaceSettingsPanelContent-B390wmE6.js (removed) 61.7 kB 🟢 -61.7 kB 🟢 -13.2 kB 🟢 -11.5 kB
assets/Preview3d-DgG-2Id8.js (removed) 50.9 kB 🟢 -50.9 kB 🟢 -8.31 kB 🟢 -7.24 kB
assets/Preview3d-DjJIbJI8.js (new) 50.9 kB 🔴 +50.9 kB 🔴 +8.31 kB 🔴 +7.26 kB
assets/main-Dd7Kqb0-.js (new) 45.1 kB 🔴 +45.1 kB 🔴 +13.1 kB 🔴 +11.4 kB
assets/main-nEmiXA8z.js (removed) 45.1 kB 🟢 -45.1 kB 🟢 -13.1 kB 🟢 -11.5 kB
assets/SubscriptionRequiredDialogContentUnified-CAYNv4YP.js (new) 42.6 kB 🔴 +42.6 kB 🔴 +9.35 kB 🔴 +8.16 kB
assets/SubscriptionRequiredDialogContentUnified-CFSlgRg_.js (removed) 42.6 kB 🟢 -42.6 kB 🟢 -9.35 kB 🟢 -8.16 kB
assets/LayerEditorContent-CDfOr_UO.js (new) 42.5 kB 🔴 +42.5 kB 🔴 +9.61 kB 🔴 +8.43 kB
assets/LayerEditorContent-CK7g5f8L.js (removed) 42.5 kB 🟢 -42.5 kB 🟢 -9.62 kB 🟢 -8.44 kB
assets/WidgetBoundingBoxes-C5iqglF9.js (removed) 33.7 kB 🟢 -33.7 kB 🟢 -9.15 kB 🟢 -8.12 kB
assets/WidgetBoundingBoxes-DZ8ZIZ38.js (new) 33.7 kB 🔴 +33.7 kB 🔴 +9.16 kB 🔴 +8.13 kB
assets/WidgetPainter-Dym0q1Ln.js (removed) 32.6 kB 🟢 -32.6 kB 🟢 -7.86 kB 🟢 -6.96 kB
assets/WidgetPainter-hezH6k8l.js (new) 32.6 kB 🔴 +32.6 kB 🔴 +7.87 kB 🔴 +6.97 kB
assets/Load3dViewerContent-DcyNj37c.js (new) 30.8 kB 🔴 +30.8 kB 🔴 +6.28 kB 🔴 +5.45 kB
assets/Load3dViewerContent-z2J_eYEX.js (removed) 30.8 kB 🟢 -30.8 kB 🟢 -6.28 kB 🟢 -5.45 kB
assets/HdrViewerContent-Bm4Ym20D.js (removed) 29.8 kB 🟢 -29.8 kB 🟢 -8.24 kB 🟢 -7.27 kB
assets/HdrViewerContent-Dd_isTCq.js (new) 29.8 kB 🔴 +29.8 kB 🔴 +8.24 kB 🔴 +7.26 kB
assets/ColorPicker-1Sbv-uCw.js (removed) 29.2 kB 🟢 -29.2 kB 🟢 -7.58 kB 🟢 -6.66 kB
assets/ColorPicker-bqsTnhNV.js (new) 29.2 kB 🔴 +29.2 kB 🔴 +7.58 kB 🔴 +6.67 kB
assets/SubscriptionRequiredDialogContent-Do8EFJIF.js (new) 27 kB 🔴 +27 kB 🔴 +6.37 kB 🔴 +5.61 kB
assets/SubscriptionRequiredDialogContent-DXIs_i0W.js (removed) 27 kB 🟢 -27 kB 🟢 -6.37 kB 🟢 -5.62 kB
assets/SubscriptionRequiredDialogContentWorkspace-B1OdiMTt.js (new) 25.1 kB 🔴 +25.1 kB 🔴 +5.79 kB 🔴 +5.09 kB
assets/SubscriptionRequiredDialogContentWorkspace-DwXWuSBY.js (removed) 25.1 kB 🟢 -25.1 kB 🟢 -5.79 kB 🟢 -5.1 kB
assets/CreditsTile-BjCphHdj.js (removed) 24.9 kB 🟢 -24.9 kB 🟢 -6.64 kB 🟢 -5.83 kB
assets/CreditsTile-qif_Bq9j.js (new) 24.9 kB 🔴 +24.9 kB 🔴 +6.64 kB 🔴 +5.83 kB
assets/load3d-ByNPdzjm.js (new) 22.2 kB 🔴 +22.2 kB 🔴 +5.41 kB 🔴 +4.67 kB
assets/load3d-D_g8ZibG.js (removed) 22.2 kB 🟢 -22.2 kB 🟢 -5.4 kB 🟢 -4.67 kB
assets/CurrentUserPopoverWorkspace-BoJKJwp8.js (removed) 21.5 kB 🟢 -21.5 kB 🟢 -4.84 kB 🟢 -4.32 kB
assets/CurrentUserPopoverWorkspace-COLgcyCg.js (new) 21.5 kB 🔴 +21.5 kB 🔴 +4.84 kB 🔴 +4.32 kB
assets/SignInContent-BA4Z1FZ2.js (new) 20.2 kB 🔴 +20.2 kB 🔴 +5.09 kB 🔴 +4.44 kB
assets/SignInContent-CijQsr0w.js (removed) 20.2 kB 🟢 -20.2 kB 🟢 -5.09 kB 🟢 -4.45 kB
assets/DialogHeader-BdtXhFfB.js (new) 17.4 kB 🔴 +17.4 kB 🔴 +5.24 kB 🔴 +4.57 kB
assets/DialogHeader-Be_F2CyP.js (removed) 17.4 kB 🟢 -17.4 kB 🟢 -5.24 kB 🟢 -4.57 kB
assets/WidgetRecordAudio-D64A7P6h.js (new) 16.6 kB 🔴 +16.6 kB 🔴 +4.6 kB 🔴 +4.1 kB
assets/WidgetRecordAudio-X5x8VRC_.js (removed) 16.6 kB 🟢 -16.6 kB 🟢 -4.6 kB 🟢 -4.1 kB
assets/WidgetInputNumber-C2oX0OI0.js (new) 13.9 kB 🔴 +13.9 kB 🔴 +3.63 kB 🔴 +3.21 kB
assets/WidgetInputNumber-DGJ6vbHo.js (removed) 13.9 kB 🟢 -13.9 kB 🟢 -3.63 kB 🟢 -3.21 kB
assets/WidgetRange-Dn4bm41Z.js (new) 13.7 kB 🔴 +13.7 kB 🔴 +3.55 kB 🔴 +3.12 kB
assets/WidgetRange-Zu8_Vv9n.js (removed) 13.7 kB 🟢 -13.7 kB 🟢 -3.55 kB 🟢 -3.12 kB
assets/WaveAudioPlayer-06OltQMz.js (removed) 12.8 kB 🟢 -12.8 kB 🟢 -3.46 kB 🟢 -3.04 kB
assets/WaveAudioPlayer-CaIprQbT.js (new) 12.8 kB 🔴 +12.8 kB 🔴 +3.46 kB 🔴 +3.03 kB
assets/WidgetCurve-BKU15n9L.js (new) 11.2 kB 🔴 +11.2 kB 🔴 +3.47 kB 🔴 +3.15 kB
assets/WidgetCurve-BtV_RG4a.js (removed) 11.2 kB 🟢 -11.2 kB 🟢 -3.47 kB 🟢 -3.15 kB
assets/AudioPreviewPlayer-B1ylZcwj.js (removed) 10.6 kB 🟢 -10.6 kB 🟢 -3.05 kB 🟢 -2.72 kB
assets/AudioPreviewPlayer-BDalp3hE.js (new) 10.6 kB 🔴 +10.6 kB 🔴 +3.05 kB 🔴 +2.72 kB
assets/TeamWorkspacesDialogContent-B1tIfuRG.js (removed) 10.3 kB 🟢 -10.3 kB 🟢 -2.96 kB 🟢 -2.62 kB
assets/TeamWorkspacesDialogContent-DTF0O9ht.js (new) 10.3 kB 🔴 +10.3 kB 🔴 +2.97 kB 🔴 +2.63 kB
assets/SelectValue-CIPLUGFv.js (removed) 10.1 kB 🟢 -10.1 kB 🟢 -2.46 kB 🟢 -2.18 kB
assets/SelectValue-CwnKZtjF.js (new) 10.1 kB 🔴 +10.1 kB 🔴 +2.46 kB 🔴 +2.17 kB
assets/onboardingCloudRoutes-Bry_vRci.js (removed) 9.22 kB 🟢 -9.22 kB 🟢 -2.82 kB 🟢 -2.4 kB
assets/onboardingCloudRoutes-DVs9aVa3.js (new) 9.22 kB 🔴 +9.22 kB 🔴 +2.82 kB 🔴 +2.42 kB
assets/Load3DConfiguration-Cbrz09wH.js (removed) 8.91 kB 🟢 -8.91 kB 🟢 -2.61 kB 🟢 -2.3 kB
assets/Load3DConfiguration-DtAa1utI.js (new) 8.91 kB 🔴 +8.91 kB 🔴 +2.61 kB 🔴 +2.3 kB
assets/WidgetImageCrop-CDJs2dJ2.js (new) 8.49 kB 🔴 +8.49 kB 🔴 +2.65 kB 🔴 +2.34 kB
assets/WidgetImageCrop-DArnB-JA.js (removed) 8.49 kB 🟢 -8.49 kB 🟢 -2.65 kB 🟢 -2.36 kB
assets/SetMemberCreditLimitDialogContent-C8pr06Y7.js (removed) 8.47 kB 🟢 -8.47 kB 🟢 -2.34 kB 🟢 -2.06 kB
assets/SetMemberCreditLimitDialogContent-DxU7yxUY.js (new) 8.47 kB 🔴 +8.47 kB 🔴 +2.34 kB 🔴 +2.05 kB
assets/nodeTemplates-ClDKyyAU.js (new) 8.32 kB 🔴 +8.32 kB 🔴 +2.85 kB 🔴 +2.5 kB
assets/nodeTemplates-Dgej0-OC.js (removed) 8.32 kB 🟢 -8.32 kB 🟢 -2.85 kB 🟢 -2.51 kB
assets/WidgetImageCompare-CAb5LKpw.js (removed) 8.13 kB 🟢 -8.13 kB 🟢 -2.3 kB 🟢 -2.01 kB
assets/WidgetImageCompare-DzZgb5aj.js (new) 8.13 kB 🔴 +8.13 kB 🔴 +2.3 kB 🔴 +2.01 kB
assets/NightlySurveyController-C7bI_881.js (new) 7.5 kB 🔴 +7.5 kB 🔴 +2.55 kB 🔴 +2.24 kB
assets/NightlySurveyController-DTOSOMY6.js (removed) 7.5 kB 🟢 -7.5 kB 🟢 -2.55 kB 🟢 -2.25 kB
assets/Popover-CIldDoHH.js (removed) 7.48 kB 🟢 -7.48 kB 🟢 -2.68 kB 🟢 -2.36 kB
assets/Popover-DrTgp-Rw.js (new) 7.48 kB 🔴 +7.48 kB 🔴 +2.68 kB 🔴 +2.38 kB
assets/CloudRunButtonWrapper-DoIB-SnS.js (removed) 6.84 kB 🟢 -6.84 kB 🟢 -2.18 kB 🟢 -1.91 kB
assets/CloudRunButtonWrapper-FXXZIEjb.js (new) 6.84 kB 🔴 +6.84 kB 🔴 +2.18 kB 🔴 +1.91 kB
assets/WidgetWithControl-B55gj3IH.js (removed) 6.41 kB 🟢 -6.41 kB 🟢 -2.61 kB 🟢 -2.3 kB
assets/WidgetWithControl-BvwEivTF.js (new) 6.41 kB 🔴 +6.41 kB 🔴 +2.61 kB 🔴 +2.31 kB
assets/missingModelMetadata-CUbjRmJb.js (removed) 6.17 kB 🟢 -6.17 kB 🟢 -2.13 kB 🟢 -1.86 kB
assets/missingModelMetadata-DnzxoY_Z.js (new) 6.17 kB 🔴 +6.17 kB 🔴 +2.13 kB 🔴 +1.86 kB
assets/AnimationControls-CVuk1Djb.js (removed) 6.1 kB 🟢 -6.1 kB 🟢 -1.85 kB 🟢 -1.64 kB
assets/AnimationControls-IR_pRc6h.js (new) 6.1 kB 🔴 +6.1 kB 🔴 +1.85 kB 🔴 +1.64 kB
assets/CancelSubscriptionDialogContent-A4j0r6xT.js (new) 5.97 kB 🔴 +5.97 kB 🔴 +1.98 kB 🔴 +1.75 kB
assets/CancelSubscriptionDialogContent-BOOekIfy.js (removed) 5.97 kB 🟢 -5.97 kB 🟢 -1.98 kB 🟢 -1.74 kB
assets/load3dPreviewExtensions-B4T9g1V8.js (new) 5.88 kB 🔴 +5.88 kB 🔴 +1.81 kB 🔴 +1.59 kB
assets/load3dPreviewExtensions-BzfH3bDB.js (removed) 5.88 kB 🟢 -5.88 kB 🟢 -1.81 kB 🟢 -1.59 kB
assets/DowngradeRemoveMembersDialogContent-iMrMfwfB.js (new) 5.58 kB 🔴 +5.58 kB 🔴 +1.88 kB 🔴 +1.64 kB
assets/DowngradeRemoveMembersDialogContent-jwkFK81a.js (removed) 5.58 kB 🟢 -5.58 kB 🟢 -1.88 kB 🟢 -1.64 kB
assets/launchCancellationFlow-BS02Tv7K.js (new) 5.18 kB 🔴 +5.18 kB 🔴 +1.78 kB 🔴 +1.56 kB
assets/launchCancellationFlow-gPpiZcuR.js (removed) 5.18 kB 🟢 -5.18 kB 🟢 -1.78 kB 🟢 -1.56 kB
assets/CreateWorkspaceDialogContent-Br6ZQvke.js (new) 5.12 kB 🔴 +5.12 kB 🔴 +1.79 kB 🔴 +1.55 kB
assets/CreateWorkspaceDialogContent-CpYVNX2n.js (removed) 5.12 kB 🟢 -5.12 kB 🟢 -1.79 kB 🟢 -1.55 kB
assets/CloudNotificationContent-BGfuH_hS.js (removed) 5.05 kB 🟢 -5.05 kB 🟢 -1.76 kB 🟢 -1.51 kB
assets/CloudNotificationContent-VqLpPD2F.js (new) 5.05 kB 🔴 +5.05 kB 🔴 +1.76 kB 🔴 +1.5 kB
assets/ChangeMemberRoleDialogContent-BRjf3ISx.js (removed) 4.97 kB 🟢 -4.97 kB 🟢 -1.63 kB 🟢 -1.43 kB
assets/ChangeMemberRoleDialogContent-DOPQY9SO.js (new) 4.97 kB 🔴 +4.97 kB 🔴 +1.64 kB 🔴 +1.43 kB
assets/InviteMemberDialogContent-IcWS16qX.js (new) 4.96 kB 🔴 +4.96 kB 🔴 +1.64 kB 🔴 +1.43 kB
assets/InviteMemberDialogContent-jgxEZ3if.js (removed) 4.96 kB 🟢 -4.96 kB 🟢 -1.64 kB 🟢 -1.43 kB
assets/EditWorkspaceDialogContent-BL3VhTB_.js (removed) 4.93 kB 🟢 -4.93 kB 🟢 -1.76 kB 🟢 -1.53 kB
assets/EditWorkspaceDialogContent-dikLkS7W.js (new) 4.93 kB 🔴 +4.93 kB 🔴 +1.76 kB 🔴 +1.53 kB
assets/PaletteSwatchRow-C1Vb_yHa.js (new) 4.86 kB 🔴 +4.86 kB 🔴 +1.86 kB 🔴 +1.66 kB
assets/PaletteSwatchRow-C8ERNye0.js (removed) 4.86 kB 🟢 -4.86 kB 🟢 -1.86 kB 🟢 -1.66 kB
assets/WidgetTextarea-CX2Awo8z.js (removed) 4.81 kB 🟢 -4.81 kB 🟢 -1.87 kB 🟢 -1.64 kB
assets/WidgetTextarea-XWXNO9OF.js (new) 4.81 kB 🔴 +4.81 kB 🔴 +1.87 kB 🔴 +1.63 kB
assets/saveMesh-B3KTGm2F.js (removed) 4.76 kB 🟢 -4.76 kB 🟢 -1.52 kB 🟢 -1.35 kB
assets/saveMesh-sux1l3Nh.js (new) 4.76 kB 🔴 +4.76 kB 🔴 +1.52 kB 🔴 +1.34 kB
assets/WorkspacePanelContent-BnuymJcl.js (new) 4.74 kB 🔴 +4.74 kB 🔴 +1.62 kB 🔴 +1.43 kB
assets/WorkspacePanelContent-BPCpHGbM.js (removed) 4.74 kB 🟢 -4.74 kB 🟢 -1.62 kB 🟢 -1.44 kB
assets/ValueControlPopover-DIZeNpZc.js (new) 4.49 kB 🔴 +4.49 kB 🔴 +1.55 kB 🔴 +1.38 kB
assets/ValueControlPopover-y6P0U8io.js (removed) 4.49 kB 🟢 -4.49 kB 🟢 -1.55 kB 🟢 -1.38 kB
assets/ToggleGroupItem-CwNixDOS.js (new) 4.14 kB 🔴 +4.14 kB 🔴 +1.41 kB 🔴 +1.24 kB
assets/ToggleGroupItem-Xh56XluV.js (removed) 4.14 kB 🟢 -4.14 kB 🟢 -1.41 kB 🟢 -1.24 kB
assets/ApiNodesSignInContent-BNxtZxlu.js (new) 4.08 kB 🔴 +4.08 kB 🔴 +1.31 kB 🔴 +1.15 kB
assets/ApiNodesSignInContent-CZEbPbKk.js (removed) 4.08 kB 🟢 -4.08 kB 🟢 -1.31 kB 🟢 -1.15 kB
assets/Slider-Cp70OY6d.js (new) 3.91 kB 🔴 +3.91 kB 🔴 +1.43 kB 🔴 +1.25 kB
assets/Slider-Dx1K1AoJ.js (removed) 3.91 kB 🟢 -3.91 kB 🟢 -1.43 kB 🟢 -1.25 kB
assets/DeleteWorkspaceDialogContent-CxPtgSmV.js (removed) 3.84 kB 🟢 -3.84 kB 🟢 -1.44 kB 🟢 -1.24 kB
assets/DeleteWorkspaceDialogContent-FlC6aDpk.js (new) 3.84 kB 🔴 +3.84 kB 🔴 +1.44 kB 🔴 +1.24 kB
assets/WidgetBoundingBox-Cxgt9rH-.js (removed) 3.76 kB 🟢 -3.76 kB 🟢 -1.03 kB 🟢 -898 B
assets/WidgetBoundingBox-DAs1NZhL.js (new) 3.76 kB 🔴 +3.76 kB 🔴 +1.03 kB 🔴 +896 B
assets/RemoveMemberDialogContent-BTLNBWUT.js (new) 3.76 kB 🔴 +3.76 kB 🔴 +1.38 kB 🔴 +1.19 kB
assets/RemoveMemberDialogContent-Xryo1RzD.js (removed) 3.76 kB 🟢 -3.76 kB 🟢 -1.38 kB 🟢 -1.2 kB
assets/RevokeInviteDialogContent-D-UhUmsG.js (removed) 3.67 kB 🟢 -3.67 kB 🟢 -1.39 kB 🟢 -1.21 kB
assets/RevokeInviteDialogContent-Qtn-sExT.js (new) 3.67 kB 🔴 +3.67 kB 🔴 +1.39 kB 🔴 +1.21 kB
assets/LeaveWorkspaceDialogContent-BkaVfsB2.js (removed) 3.67 kB 🟢 -3.67 kB 🟢 -1.38 kB 🟢 -1.19 kB
assets/LeaveWorkspaceDialogContent-CXZZtJFz.js (new) 3.67 kB 🔴 +3.67 kB 🔴 +1.38 kB 🔴 +1.19 kB
assets/WidgetToggleSwitch-CQQbOGD6.js (removed) 3.56 kB 🟢 -3.56 kB 🟢 -1.3 kB 🟢 -1.14 kB
assets/WidgetToggleSwitch-DPx1UUP6.js (new) 3.56 kB 🔴 +3.56 kB 🔴 +1.3 kB 🔴 +1.14 kB
assets/InviteMemberUpsellDialogContent-Dfnl-LiX.js (removed) 3.47 kB 🟢 -3.47 kB 🟢 -1.24 kB 🟢 -1.07 kB
assets/InviteMemberUpsellDialogContent-DlGkTfVs.js (new) 3.47 kB 🔴 +3.47 kB 🔴 +1.24 kB 🔴 +1.06 kB
assets/workspaceCheckoutTelemetry-8bexMh1w.js (new) 3.4 kB 🔴 +3.4 kB 🔴 +1.52 kB 🔴 +1.33 kB
assets/workspaceCheckoutTelemetry-K5t6Ou23.js (removed) 3.4 kB 🟢 -3.4 kB 🟢 -1.52 kB 🟢 -1.33 kB
assets/GlobalToast-B3yg_pP8.js (new) 3.25 kB 🔴 +3.25 kB 🔴 +1.3 kB 🔴 +1.11 kB
assets/GlobalToast-y3by1hvB.js (removed) 3.25 kB 🟢 -3.25 kB 🟢 -1.3 kB 🟢 -1.11 kB
assets/Media3DTop-BV9LO06b.js (removed) 3.21 kB 🟢 -3.21 kB 🟢 -1.26 kB 🟢 -1.1 kB
assets/Media3DTop-DPviyLLl.js (new) 3.21 kB 🔴 +3.21 kB 🔴 +1.26 kB 🔴 +1.1 kB
assets/WidgetMarkdown-CTZHE7cn.js (new) 3.07 kB 🔴 +3.07 kB 🔴 +1.26 kB 🔴 +1.1 kB
assets/WidgetMarkdown-xoCCA7Av.js (removed) 3.07 kB 🟢 -3.07 kB 🟢 -1.25 kB 🟢 -1.11 kB
assets/WidgetInputText-B7ew3nfx.js (removed) 3.01 kB 🟢 -3.01 kB 🟢 -1.26 kB 🟢 -1.14 kB
assets/WidgetInputText-D2dLYDwR.js (new) 3.01 kB 🔴 +3.01 kB 🔴 +1.27 kB 🔴 +1.13 kB
assets/MediaVideoTop-DRJEsTSX.js (removed) 2.86 kB 🟢 -2.86 kB 🟢 -1.14 kB 🟢 -1.01 kB
assets/MediaVideoTop-ktoITyeO.js (new) 2.86 kB 🔴 +2.86 kB 🔴 +1.14 kB 🔴 +1 kB
assets/load3dAdvanced-DANJ5-XW.js (new) 2.82 kB 🔴 +2.82 kB 🔴 +1.09 kB 🔴 +956 B
assets/load3dAdvanced-JLHVTRM3.js (removed) 2.82 kB 🟢 -2.82 kB 🟢 -1.1 kB 🟢 -955 B
assets/ErrorPanelSurveyCta-BNW2MGcy.js (removed) 2.64 kB 🟢 -2.64 kB 🟢 -1.14 kB 🟢 -1.01 kB
assets/ErrorPanelSurveyCta-CBS9MoSr.js (new) 2.64 kB 🔴 +2.64 kB 🔴 +1.14 kB 🔴 +1.01 kB
assets/Switch-C237YTfd.js (removed) 2.49 kB 🟢 -2.49 kB 🟢 -1.09 kB 🟢 -973 B
assets/Switch-DDJdaZbW.js (new) 2.49 kB 🔴 +2.49 kB 🔴 +1.09 kB 🔴 +940 B
assets/SubscribeToRun-DkJlrD_a.js (new) 2.39 kB 🔴 +2.39 kB 🔴 +1.03 kB 🔴 +900 B
assets/SubscribeToRun-ntr2t3_i.js (removed) 2.39 kB 🟢 -2.39 kB 🟢 -1.03 kB 🟢 -901 B
assets/WidgetLayoutField-BVLfATXc.js (new) 2.38 kB 🔴 +2.38 kB 🔴 +1.03 kB 🔴 +924 B
assets/WidgetLayoutField-D2lG1_RW.js (removed) 2.38 kB 🟢 -2.38 kB 🟢 -1.02 kB 🟢 -900 B
assets/TopBarHeader-CcgaLSyK.js (removed) 2.01 kB 🟢 -2.01 kB 🟢 -842 B 🟢 -737 B
assets/TopBarHeader-CSedOc8u.js (new) 2.01 kB 🔴 +2.01 kB 🔴 +841 B 🔴 +735 B
assets/WidgetColorPicker-BztNcpGh.js (removed) 1.69 kB 🟢 -1.69 kB 🟢 -755 B 🟢 -670 B
assets/WidgetColorPicker-CT-Q3vQ3.js (new) 1.69 kB 🔴 +1.69 kB 🔴 +755 B 🔴 +668 B
assets/Loader-BKEeKywz.js (removed) 1.64 kB 🟢 -1.64 kB 🟢 -781 B 🟢 -704 B
assets/Loader-Xp_4g4Ht.js (new) 1.64 kB 🔴 +1.64 kB 🔴 +782 B 🔴 +697 B
assets/WidgetColors-492fhvji.js (removed) 1.63 kB 🟢 -1.63 kB 🟢 -805 B 🟢 -710 B
assets/WidgetColors-BZCR5jLS.js (new) 1.63 kB 🔴 +1.63 kB 🔴 +805 B 🔴 +710 B
assets/MediaAudioTop-B-U4Qpnk.js (removed) 1.62 kB 🟢 -1.62 kB 🟢 -807 B 🟢 -669 B
assets/MediaAudioTop-fYTwyDec.js (new) 1.62 kB 🔴 +1.62 kB 🔴 +806 B 🔴 +671 B
assets/Textarea-Cu_WHPvZ.js (removed) 1.56 kB 🟢 -1.56 kB 🟢 -813 B 🟢 -708 B
assets/Textarea-DWIqBYn2.js (new) 1.56 kB 🔴 +1.56 kB 🔴 +813 B 🔴 +709 B
assets/VideoPlayOverlay-CirO1lkW.js (removed) 1.46 kB 🟢 -1.46 kB 🟢 -726 B 🟢 -646 B
assets/VideoPlayOverlay-DHHhvM-o.js (new) 1.46 kB 🔴 +1.46 kB 🔴 +730 B 🔴 +645 B
assets/ButtonGroup-C__B3FPu.js (new) 1.24 kB 🔴 +1.24 kB 🔴 +642 B 🔴 +566 B
assets/ButtonGroup-WpmcAVbI.js (removed) 1.24 kB 🟢 -1.24 kB 🟢 -644 B 🟢 -566 B
assets/DialogClose-DDX5Hg9R.js (removed) 1.18 kB 🟢 -1.18 kB 🟢 -608 B 🟢 -551 B
assets/DialogClose-Dp8meObC.js (new) 1.18 kB 🔴 +1.18 kB 🔴 +608 B 🔴 +552 B
assets/cloudSessionCookie-CTkhi6Pi.js (new) 933 B 🔴 +933 B 🔴 +434 B 🔴 +378 B
assets/cloudSessionCookie-IJCjxJBg.js (removed) 933 B 🟢 -933 B 🟢 -433 B 🟢 -378 B
assets/cloudBadges-d2FHv6a_.js (new) 922 B 🔴 +922 B 🔴 +514 B 🔴 +462 B
assets/cloudBadges-rKiTNlro.js (removed) 922 B 🟢 -922 B 🟢 -514 B 🟢 -465 B
assets/Load3DAdvanced-BawEXUQS.js (removed) 761 B 🟢 -761 B 🟢 -422 B 🟢 -357 B
assets/Load3DAdvanced-BjNhshmR.js (new) 761 B 🔴 +761 B 🔴 +424 B 🔴 +359 B
assets/nightlyBadges-BESDYAsE.js (new) 411 B 🔴 +411 B 🔴 +271 B 🔴 +265 B
assets/nightlyBadges-YgqxVfnv.js (removed) 411 B 🟢 -411 B 🟢 -272 B 🟢 -265 B
assets/src-fQojukAW.js (new) 407 B 🔴 +407 B 🔴 +287 B 🔴 +245 B
assets/src-hgxVSJpP.js (removed) 407 B 🟢 -407 B 🟢 -287 B 🟢 -245 B
assets/Load3dViewerContent-BUKGpo1Y.js (new) 137 B 🔴 +137 B 🔴 +103 B 🔴 +94 B
assets/Load3dViewerContent-D1Z2TtAt.js (removed) 137 B 🟢 -137 B 🟢 -103 B 🟢 -112 B
assets/WidgetToggleSwitch-CKEF8dgk.js (new) 134 B 🔴 +134 B 🔴 +101 B 🔴 +94 B
assets/WidgetToggleSwitch-DUFXu89q.js (removed) 134 B 🟢 -134 B 🟢 -101 B 🟢 -96 B
assets/WidgetBoundingBox-CHkpS0XF.js (new) 131 B 🔴 +131 B 🔴 +100 B 🔴 +93 B
assets/WidgetBoundingBox-CiK7tzJz.js (removed) 131 B 🟢 -131 B 🟢 -100 B 🟢 -91 B
assets/missingModelMetadata-Bog1xPTj.js (removed) 125 B 🟢 -125 B 🟢 -103 B 🟢 -103 B
assets/missingModelMetadata-C6YKiVbq.js (new) 125 B 🔴 +125 B 🔴 +103 B 🔴 +105 B
assets/Load3DAdvanced-ew65wD9d.js (new) 122 B 🔴 +122 B 🔴 +97 B 🔴 +88 B
assets/Load3DAdvanced-IrrGGWpv.js (removed) 122 B 🟢 -122 B 🟢 -97 B 🟢 -91 B
assets/WidgetLegacy-BlbMt_ke.js (removed) 117 B 🟢 -117 B 🟢 -106 B 🟢 -103 B
assets/WidgetLegacy-Cz0V1mrM.js (new) 117 B 🔴 +117 B 🔴 +106 B 🔴 +104 B
assets/workflowDraftStoreV2-B8S4ec3P.js (new) 112 B 🔴 +112 B 🔴 +101 B 🔴 +107 B
assets/workflowDraftStoreV2-Jy-djYez.js (removed) 112 B 🟢 -112 B 🟢 -101 B 🟢 -106 B
assets/Load3D-D4AaI8Ha.js (new) 98 B 🔴 +98 B 🔴 +89 B 🔴 +87 B
assets/Load3D-K8toQJrr.js (removed) 98 B 🟢 -98 B 🟢 -89 B 🟢 -92 B
assets/changeTracker-B4VDpW9F.js (removed) 91 B 🟢 -91 B 🟢 -93 B 🟢 -85 B
assets/changeTracker-BNKzZIFC.js (new) 91 B 🔴 +91 B 🔴 +93 B 🔴 +86 B

Status: 101 added / 101 removed / 185 unchanged

⚡ Performance Report

canvas-idle: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 55.7 MB heap
canvas-mouse-sweep: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 55.6 MB heap
canvas-zoom-sweep: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 69.5 MB heap
dom-widget-clipping: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 50.7 MB heap
large-graph-idle: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 65.0 MB heap
large-graph-pan: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 62.2 MB heap
large-graph-zoom: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 68.3 MB heap
minimap-idle: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 64.2 MB heap
subgraph-dom-widget-clipping: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 51.4 MB heap
subgraph-idle: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 44.1 MB heap
subgraph-mouse-sweep: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 45.2 MB heap
subgraph-transition-enter: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 137ms TBT · 93.3 MB heap
viewport-pan-sweep: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 68.6 MB heap
vue-large-graph-idle: · 56.3 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 158.9 MB heap
vue-large-graph-pan: · 55.4 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 183ms TBT · 164.0 MB heap
workflow-execution: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 65.3 MB heap

⚠️ 6 regressions detected

Show regressions
Metric Baseline PR (median) Δ Sig
canvas-idle: task duration 424ms 535ms +26% ⚠️ z=4.5
canvas-zoom-sweep: task duration 296ms 378ms +28% ⚠️ z=2.2
large-graph-idle: task duration 529ms 675ms +28% ⚠️ z=2.4
large-graph-pan: task duration 916ms 1247ms +36% ⚠️ z=3.8
minimap-idle: task duration 543ms 678ms +25% ⚠️ z=3.2
subgraph-idle: task duration 408ms 498ms +22% ⚠️ z=4.1
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 +0%
canvas-idle: layout duration 0ms 0ms +0%
canvas-idle: style recalc duration 5ms 8ms +40% z=-3.6
canvas-idle: layout count 0 0 +0%
canvas-idle: style recalc count 10 9 -10% z=-3.8
canvas-idle: task duration 424ms 535ms +26% ⚠️ z=4.5
canvas-idle: script duration 13ms 17ms +28% z=-3.9
canvas-idle: TBT 0ms 0ms +0%
canvas-idle: heap used 44.6 MB 55.7 MB +25%
canvas-idle: DOM nodes -279 -131 -53% z=-120.0
canvas-idle: event listeners -151 -74 -51% z=-18.0
canvas-mouse-sweep: avg frame time 17ms 17ms -0% z=-2.0
canvas-mouse-sweep: p95 frame time 17ms 17ms -0%
canvas-mouse-sweep: layout duration 2ms 3ms +45% z=-2.0
canvas-mouse-sweep: style recalc duration 23ms 36ms +58% z=-2.1
canvas-mouse-sweep: layout count 12 12 +0%
canvas-mouse-sweep: style recalc count 72 75 +3% z=-1.8
canvas-mouse-sweep: task duration 620ms 854ms +38% z=-0.2
canvas-mouse-sweep: script duration 74ms 120ms +64% z=-2.3
canvas-mouse-sweep: TBT 0ms 0ms +0%
canvas-mouse-sweep: heap used 49.3 MB 55.6 MB +13%
canvas-mouse-sweep: DOM nodes -278 -113 -59% z=-67.8
canvas-mouse-sweep: event listeners -151 -75 -51% z=-19.9
canvas-zoom-sweep: avg frame time 17ms 17ms -0% z=-0.3
canvas-zoom-sweep: p95 frame time 17ms 17ms +1%
canvas-zoom-sweep: layout duration 0ms 1ms +22% z=-2.6
canvas-zoom-sweep: style recalc duration 12ms 16ms +32% z=-2.3
canvas-zoom-sweep: layout count 6 6 +0%
canvas-zoom-sweep: style recalc count 31 33 +5% z=2.7
canvas-zoom-sweep: task duration 296ms 378ms +28% ⚠️ z=2.2
canvas-zoom-sweep: script duration 15ms 18ms +23% z=-2.9
canvas-zoom-sweep: TBT 0ms 0ms +0%
canvas-zoom-sweep: heap used 69.3 MB 69.5 MB +0%
canvas-zoom-sweep: DOM nodes 79 79 +0% z=-0.3
canvas-zoom-sweep: event listeners 19 19 +0% z=-0.9
dom-widget-clipping: avg frame time 17ms 17ms +0% z=0.1
dom-widget-clipping: p95 frame time 17ms 17ms +0%
dom-widget-clipping: layout duration 0ms 0ms +0%
dom-widget-clipping: style recalc duration 5ms 7ms +48% z=-3.6
dom-widget-clipping: layout count 0 0 +0%
dom-widget-clipping: style recalc count 12 11 -8% z=-4.2
dom-widget-clipping: task duration 285ms 370ms +30% z=0.3
dom-widget-clipping: script duration 40ms 59ms +47% z=-2.8
dom-widget-clipping: TBT 0ms 0ms +0%
dom-widget-clipping: heap used 50.6 MB 50.7 MB +0%
dom-widget-clipping: DOM nodes 20 18 -10% z=-2.9
dom-widget-clipping: event listeners 2 0 -100% variance too high
large-graph-idle: avg frame time 17ms 17ms +0% z=-0.6
large-graph-idle: p95 frame time 17ms 17ms +0%
large-graph-idle: layout duration 0ms 0ms +0%
large-graph-idle: style recalc duration 5ms 8ms +46% z=-4.2
large-graph-idle: layout count 0 0 +0%
large-graph-idle: style recalc count 10 9 -10% z=-8.3
large-graph-idle: task duration 529ms 675ms +28% ⚠️ z=2.4
large-graph-idle: script duration 63ms 106ms +67% z=0.3
large-graph-idle: TBT 0ms 0ms +0%
large-graph-idle: heap used 63.7 MB 65.0 MB +2%
large-graph-idle: DOM nodes -279 -282 +1% z=-339.3
large-graph-idle: event listeners -177 -149 -16% z=-28.7
large-graph-pan: avg frame time 17ms 17ms +0% z=0.8
large-graph-pan: p95 frame time 17ms 17ms +0%
large-graph-pan: layout duration 0ms 0ms +0%
large-graph-pan: style recalc duration 9ms 12ms +41% z=-6.2
large-graph-pan: layout count 0 0 +0%
large-graph-pan: style recalc count 68 68 +0% z=-2.4
large-graph-pan: task duration 916ms 1247ms +36% ⚠️ z=3.8
large-graph-pan: script duration 276ms 423ms +53% z=0.7
large-graph-pan: TBT 0ms 0ms +0%
large-graph-pan: heap used 64.0 MB 62.2 MB -3%
large-graph-pan: DOM nodes -282 -286 +1% z=-184.6
large-graph-pan: event listeners -149 -148 -1% z=-184.7
large-graph-zoom: avg frame time 17ms 17ms -0%
large-graph-zoom: p95 frame time 17ms 17ms +1%
large-graph-zoom: layout duration 5ms 7ms +37%
large-graph-zoom: style recalc duration 10ms 14ms +38%
large-graph-zoom: layout count 60 60 +0%
large-graph-zoom: style recalc count 66 66 -1%
large-graph-zoom: task duration 1043ms 1390ms +33%
large-graph-zoom: script duration 333ms 516ms +55%
large-graph-zoom: TBT 0ms 0ms +0%
large-graph-zoom: heap used 79.1 MB 68.3 MB -14%
large-graph-zoom: DOM nodes 16 -139 -966%
large-graph-zoom: event listeners 6 -73 -1308%
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 6ms 7ms +27% z=-3.0
minimap-idle: layout count 0 0 +0%
minimap-idle: style recalc count 10 8 -20% z=-2.3
minimap-idle: task duration 543ms 678ms +25% ⚠️ z=3.2
minimap-idle: script duration 66ms 107ms +63% z=0.9
minimap-idle: TBT 0ms 0ms +0%
minimap-idle: heap used 65.0 MB 64.2 MB -1%
minimap-idle: DOM nodes -282 -284 +1% z=-221.3
minimap-idle: event listeners -149 -149 +0% z=-232.6
subgraph-dom-widget-clipping: avg frame time 17ms 17ms +0% z=0.1
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 7ms 10ms +46% z=-2.4
subgraph-dom-widget-clipping: layout count 0 0 +0%
subgraph-dom-widget-clipping: style recalc count 47 47 +0% z=-1.6
subgraph-dom-widget-clipping: task duration 298ms 397ms +33% z=1.0
subgraph-dom-widget-clipping: script duration 85ms 119ms +39% z=-1.5
subgraph-dom-widget-clipping: TBT 0ms 0ms +0%
subgraph-dom-widget-clipping: heap used 51.0 MB 51.4 MB +1%
subgraph-dom-widget-clipping: DOM nodes 20 20 +0% z=-1.9
subgraph-dom-widget-clipping: event listeners 8 6 -25% 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 5ms 9ms +75% z=-1.6
subgraph-idle: layout count 0 0 +0%
subgraph-idle: style recalc count 10 11 +10% z=0.2
subgraph-idle: task duration 408ms 498ms +22% ⚠️ z=4.1
subgraph-idle: script duration 11ms 14ms +22% z=-2.4
subgraph-idle: TBT 0ms 0ms +0%
subgraph-idle: heap used 44.1 MB 44.1 MB +0%
subgraph-idle: DOM nodes 1 -139 -13950% z=-107.2
subgraph-idle: event listeners 6 -75 -1342% 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 3ms 4ms +52% z=-2.4
subgraph-mouse-sweep: style recalc duration 24ms 34ms +44% z=-2.6
subgraph-mouse-sweep: layout count 16 16 +0%
subgraph-mouse-sweep: style recalc count 78 78 -1% z=-1.4
subgraph-mouse-sweep: task duration 551ms 796ms +44% z=0.4
subgraph-mouse-sweep: script duration 56ms 90ms +59% z=-1.7
subgraph-mouse-sweep: TBT 0ms 0ms +0%
subgraph-mouse-sweep: heap used 58.1 MB 45.2 MB -22%
subgraph-mouse-sweep: DOM nodes 67 -280 -518% z=-155.3
subgraph-mouse-sweep: event listeners 4 -153 -3925% variance too high
subgraph-transition-enter: avg frame time 17ms 17ms +0%
subgraph-transition-enter: p95 frame time 17ms 17ms +0%
subgraph-transition-enter: layout duration 8ms 12ms +45%
subgraph-transition-enter: style recalc duration 22ms 30ms +32%
subgraph-transition-enter: layout count 15 13 -13%
subgraph-transition-enter: style recalc count 19 17 -11%
subgraph-transition-enter: task duration 694ms 935ms +35%
subgraph-transition-enter: script duration 19ms 34ms +77%
subgraph-transition-enter: TBT 97ms 137ms +41%
subgraph-transition-enter: heap used 94.2 MB 93.3 MB -1%
subgraph-transition-enter: DOM nodes 13673 13673 +0%
subgraph-transition-enter: event listeners 2373 2375 +0%
viewport-pan-sweep: avg frame time 17ms 17ms -0%
viewport-pan-sweep: p95 frame time 17ms 17ms -0%
viewport-pan-sweep: layout duration 0ms 0ms +0%
viewport-pan-sweep: style recalc duration 22ms 35ms +59%
viewport-pan-sweep: layout count 0 0 +0%
viewport-pan-sweep: style recalc count 249 250 +0%
viewport-pan-sweep: task duration 3141ms 4461ms +42%
viewport-pan-sweep: script duration 879ms 1403ms +60%
viewport-pan-sweep: TBT 0ms 0ms +0%
viewport-pan-sweep: heap used 61.4 MB 68.6 MB +12%
viewport-pan-sweep: DOM nodes -279 -281 +1%
viewport-pan-sweep: event listeners -163 -133 -18%
vue-large-graph-idle: avg frame time 17ms 18ms +7%
vue-large-graph-idle: p95 frame time 17ms 17ms +1%
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 10783ms 16831ms +56%
vue-large-graph-idle: script duration 394ms 552ms +40%
vue-large-graph-idle: TBT 0ms 0ms +0%
vue-large-graph-idle: heap used 161.6 MB 158.9 MB -2%
vue-large-graph-idle: DOM nodes -8312 -8312 +0%
vue-large-graph-idle: event listeners -16389 -16387 -0%
vue-large-graph-pan: avg frame time 17ms 18ms +8%
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 11ms 17ms +59%
vue-large-graph-pan: layout count 0 0 +0%
vue-large-graph-pan: style recalc count 67 145 +116%
vue-large-graph-pan: task duration 13077ms 20661ms +58%
vue-large-graph-pan: script duration 645ms 915ms +42%
vue-large-graph-pan: TBT 0ms 183ms
vue-large-graph-pan: heap used 167.6 MB 164.0 MB -2%
vue-large-graph-pan: DOM nodes -8312 -8312 +0%
vue-large-graph-pan: event listeners -16389 -16383 -0%
workflow-execution: avg frame time 17ms 17ms -0% z=0.1
workflow-execution: p95 frame time 17ms 17ms -0%
workflow-execution: layout duration 0ms 1ms +204% z=-2.4
workflow-execution: style recalc duration 16ms 21ms +35% z=-1.4
workflow-execution: layout count 2 3 +50% z=-3.6
workflow-execution: style recalc count 14 16 +11% z=-1.1
workflow-execution: task duration 80ms 112ms +41% z=-1.0
workflow-execution: script duration 6ms 9ms +43% z=-6.9
workflow-execution: TBT 0ms 0ms +0%
workflow-execution: heap used 65.4 MB 65.3 MB -0%
workflow-execution: DOM nodes 125 136 +8% z=-3.6
workflow-execution: event listeners 97 98 +1% z=10.6
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-08-11T07:50:20.130Z",
  "gitSha": "4d0fa0ea10908f9bd8e02cac4239c02482f26bc9",
  "branch": "glary/feature-flag-url-overrides",
  "measurements": [
    {
      "name": "canvas-idle",
      "durationMs": 2023.021,
      "styleRecalcs": 9,
      "styleRecalcDurationMs": 7.339999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 563.4920000000001,
      "heapDeltaBytes": 5844024,
      "heapUsedBytes": 70195664,
      "domNodes": 18,
      "jsHeapTotalBytes": 24379392,
      "scriptDurationMs": 16.302,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "canvas-idle",
      "durationMs": 2021.6159999999945,
      "styleRecalcs": 9,
      "styleRecalcDurationMs": 7.875,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 507.324,
      "heapDeltaBytes": -17585048,
      "heapUsedBytes": 46576036,
      "domNodes": -279,
      "jsHeapTotalBytes": 23044096,
      "scriptDurationMs": 16.802999999999997,
      "eventListeners": -153,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 1872.5749999999834,
      "styleRecalcs": 75,
      "styleRecalcDurationMs": 36.738,
      "layouts": 12,
      "layoutDurationMs": 3.366,
      "taskDurationMs": 880.494,
      "heapDeltaBytes": -12018908,
      "heapUsedBytes": 52311960,
      "domNodes": -281,
      "jsHeapTotalBytes": 23306240,
      "scriptDurationMs": 121.806,
      "eventListeners": -153,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.660000000000007,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 1801.3349999999946,
      "styleRecalcs": 74,
      "styleRecalcDurationMs": 35.145999999999994,
      "layouts": 12,
      "layoutDurationMs": 3.0789999999999997,
      "taskDurationMs": 828.1070000000001,
      "heapDeltaBytes": 9672,
      "heapUsedBytes": 64239416,
      "domNodes": 55,
      "jsHeapTotalBytes": 25165824,
      "scriptDurationMs": 119.18999999999998,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "canvas-zoom-sweep",
      "durationMs": 1721.2319999999863,
      "styleRecalcs": 32,
      "styleRecalcDurationMs": 15.527,
      "layouts": 6,
      "layoutDurationMs": 0.526,
      "taskDurationMs": 384.11,
      "heapDeltaBytes": 8561100,
      "heapUsedBytes": 72918744,
      "domNodes": 79,
      "jsHeapTotalBytes": 24641536,
      "scriptDurationMs": 18.749999999999996,
      "eventListeners": 19,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "canvas-zoom-sweep",
      "durationMs": 1722.2589999998945,
      "styleRecalcs": 33,
      "styleRecalcDurationMs": 15.598,
      "layouts": 6,
      "layoutDurationMs": 0.528,
      "taskDurationMs": 372.0640000000001,
      "heapDeltaBytes": 8580700,
      "heapUsedBytes": 72861860,
      "domNodes": 79,
      "jsHeapTotalBytes": 25165824,
      "scriptDurationMs": 17.810000000000002,
      "eventListeners": 19,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 562.9659999999603,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 7.173000000000001,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 364.37,
      "heapDeltaBytes": -11168604,
      "heapUsedBytes": 53159940,
      "domNodes": 18,
      "jsHeapTotalBytes": 25952256,
      "scriptDurationMs": 58.97599999999999,
      "eventListeners": 0,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.699999999999818
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 578.1279999999924,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 6.8290000000000015,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 374.992,
      "heapDeltaBytes": -11235016,
      "heapUsedBytes": 53132960,
      "domNodes": 18,
      "jsHeapTotalBytes": 25690112,
      "scriptDurationMs": 58.57300000000001,
      "eventListeners": 0,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "large-graph-idle",
      "durationMs": 2070.9620000000086,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 8.906999999999998,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 681.924,
      "heapDeltaBytes": 7484212,
      "heapUsedBytes": 69155900,
      "domNodes": -280,
      "jsHeapTotalBytes": 2461696,
      "scriptDurationMs": 107.898,
      "eventListeners": -149,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-idle",
      "durationMs": 2033.5540000000947,
      "styleRecalcs": 8,
      "styleRecalcDurationMs": 6.769999999999998,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 667.4,
      "heapDeltaBytes": 7421948,
      "heapUsedBytes": 67149928,
      "domNodes": -284,
      "jsHeapTotalBytes": 3248128,
      "scriptDurationMs": 103.52399999999999,
      "eventListeners": -149,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-pan",
      "durationMs": 2142.774999999972,
      "styleRecalcs": 68,
      "styleRecalcDurationMs": 12.389000000000001,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1248.7240000000002,
      "heapDeltaBytes": 4206492,
      "heapUsedBytes": 65336624,
      "domNodes": -284,
      "jsHeapTotalBytes": 4489216,
      "scriptDurationMs": 423.44,
      "eventListeners": -147,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "large-graph-pan",
      "durationMs": 2187.718000000018,
      "styleRecalcs": 68,
      "styleRecalcDurationMs": 12.354000000000001,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1245.793,
      "heapDeltaBytes": 4433888,
      "heapUsedBytes": 65015428,
      "domNodes": -287,
      "jsHeapTotalBytes": 3440640,
      "scriptDurationMs": 421.80400000000003,
      "eventListeners": -149,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.670000000000012,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "large-graph-zoom",
      "durationMs": 3117.15700000002,
      "styleRecalcs": 66,
      "styleRecalcDurationMs": 14.297,
      "layouts": 60,
      "layoutDurationMs": 6.967999999999999,
      "taskDurationMs": 1298.365,
      "heapDeltaBytes": 21111168,
      "heapUsedBytes": 83096188,
      "domNodes": 14,
      "jsHeapTotalBytes": 7864320,
      "scriptDurationMs": 509.9180000000001,
      "eventListeners": 8,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "large-graph-zoom",
      "durationMs": 3196.8219999999974,
      "styleRecalcs": 65,
      "styleRecalcDurationMs": 13.517000000000001,
      "layouts": 60,
      "layoutDurationMs": 7.058,
      "taskDurationMs": 1482.106,
      "heapDeltaBytes": -2218252,
      "heapUsedBytes": 60066176,
      "domNodes": -291,
      "jsHeapTotalBytes": 6393856,
      "scriptDurationMs": 522.8670000000001,
      "eventListeners": -153,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "minimap-idle",
      "durationMs": 2022.736000000009,
      "styleRecalcs": 8,
      "styleRecalcDurationMs": 6.821999999999998,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 670.2239999999999,
      "heapDeltaBytes": 6430772,
      "heapUsedBytes": 66840620,
      "domNodes": -284,
      "jsHeapTotalBytes": 4296704,
      "scriptDurationMs": 106.88699999999999,
      "eventListeners": -149,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "minimap-idle",
      "durationMs": 2021.8730000000278,
      "styleRecalcs": 8,
      "styleRecalcDurationMs": 7.353999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 684.8510000000001,
      "heapDeltaBytes": 7259664,
      "heapUsedBytes": 67824264,
      "domNodes": -283,
      "jsHeapTotalBytes": 3510272,
      "scriptDurationMs": 107.33500000000001,
      "eventListeners": -149,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 590.7960000000116,
      "styleRecalcs": 47,
      "styleRecalcDurationMs": 10.361000000000002,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 405.424,
      "heapDeltaBytes": -10458980,
      "heapUsedBytes": 53822192,
      "domNodes": 20,
      "jsHeapTotalBytes": 25427968,
      "scriptDurationMs": 122.696,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 558.9820000000145,
      "styleRecalcs": 47,
      "styleRecalcDurationMs": 10.516,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 387.63599999999997,
      "heapDeltaBytes": -10734584,
      "heapUsedBytes": 53906864,
      "domNodes": 20,
      "jsHeapTotalBytes": 26214400,
      "scriptDurationMs": 114.89000000000001,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "subgraph-idle",
      "durationMs": 1995.9519999999884,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 9.120999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 495.646,
      "heapDeltaBytes": -17655832,
      "heapUsedBytes": 46740532,
      "domNodes": -279,
      "jsHeapTotalBytes": 24092672,
      "scriptDurationMs": 13.847,
      "eventListeners": -153,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.699999999999818
    },
    {
      "name": "subgraph-idle",
      "durationMs": 2039.480000000026,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 9.263,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 500.25,
      "heapDeltaBytes": -18680200,
      "heapUsedBytes": 45780720,
      "domNodes": 2,
      "jsHeapTotalBytes": 23044096,
      "scriptDurationMs": 13.603,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.699999999999818
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1743.710999999962,
      "styleRecalcs": 78,
      "styleRecalcDurationMs": 33.893,
      "layouts": 16,
      "layoutDurationMs": 3.713,
      "taskDurationMs": 798.852,
      "heapDeltaBytes": -16429436,
      "heapUsedBytes": 48015184,
      "domNodes": -280,
      "jsHeapTotalBytes": 23568384,
      "scriptDurationMs": 88.602,
      "eventListeners": -153,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1737.7970000000005,
      "styleRecalcs": 77,
      "styleRecalcDurationMs": 33.841,
      "layouts": 16,
      "layoutDurationMs": 4.128000000000001,
      "taskDurationMs": 793.654,
      "heapDeltaBytes": -17716376,
      "heapUsedBytes": 46759116,
      "domNodes": -280,
      "jsHeapTotalBytes": 24616960,
      "scriptDurationMs": 90.48200000000001,
      "eventListeners": -153,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "subgraph-transition-enter",
      "durationMs": 1400.480000000016,
      "styleRecalcs": 17,
      "styleRecalcDurationMs": 29.557,
      "layouts": 13,
      "layoutDurationMs": 12.133000000000001,
      "taskDurationMs": 935.3970000000003,
      "heapDeltaBytes": 31032228,
      "heapUsedBytes": 97823704,
      "domNodes": 13673,
      "jsHeapTotalBytes": 15204352,
      "scriptDurationMs": 34.086000000000006,
      "eventListeners": 2375,
      "totalBlockingTimeMs": 137,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "viewport-pan-sweep",
      "durationMs": 8264.202000000012,
      "styleRecalcs": 250,
      "styleRecalcDurationMs": 35.028000000000006,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 4509.715,
      "heapDeltaBytes": 13017676,
      "heapUsedBytes": 72645076,
      "domNodes": -281,
      "jsHeapTotalBytes": 6324224,
      "scriptDurationMs": 1440.601,
      "eventListeners": -133,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "viewport-pan-sweep",
      "durationMs": 8304.745000000024,
      "styleRecalcs": 250,
      "styleRecalcDurationMs": 35.189,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 4412.447999999999,
      "heapDeltaBytes": 11717948,
      "heapUsedBytes": 71177712,
      "domNodes": -281,
      "jsHeapTotalBytes": 6324224,
      "scriptDurationMs": 1365.839,
      "eventListeners": -133,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.669999999999952,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "vue-large-graph-idle",
      "durationMs": 16920.018000000025,
      "styleRecalcs": 0,
      "styleRecalcDurationMs": 0,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 16862.836,
      "heapDeltaBytes": -59029736,
      "heapUsedBytes": 167207104,
      "domNodes": -8312,
      "jsHeapTotalBytes": -7282688,
      "scriptDurationMs": 540.2510000000001,
      "eventListeners": -16387,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 17.776666666666642,
      "p95FrameDurationMs": 16.80000000000291
    },
    {
      "name": "vue-large-graph-idle",
      "durationMs": 16842.01799999994,
      "styleRecalcs": 0,
      "styleRecalcDurationMs": 0,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 16799.467999999997,
      "heapDeltaBytes": -38292676,
      "heapUsedBytes": 166100604,
      "domNodes": -8312,
      "jsHeapTotalBytes": -9379840,
      "scriptDurationMs": 563.3240000000001,
      "eventListeners": -16387,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 17.776666666666642,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "vue-large-graph-pan",
      "durationMs": 20550.42199999997,
      "styleRecalcs": 144,
      "styleRecalcDurationMs": 16.158999999999978,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 20497.696,
      "heapDeltaBytes": -29666180,
      "heapUsedBytes": 176986992,
      "domNodes": -8312,
      "jsHeapTotalBytes": -10498048,
      "scriptDurationMs": 902.3129999999999,
      "eventListeners": -16381,
      "totalBlockingTimeMs": 148,
      "frameDurationMs": 17.776666666666642,
      "p95FrameDurationMs": 16.80000000000291
    },
    {
      "name": "vue-large-graph-pan",
      "durationMs": 20867.22599999996,
      "styleRecalcs": 145,
      "styleRecalcDurationMs": 17.828999999999983,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 20824.563000000002,
      "heapDeltaBytes": -39149820,
      "heapUsedBytes": 166940180,
      "domNodes": -8312,
      "jsHeapTotalBytes": -8855552,
      "scriptDurationMs": 928.6589999999999,
      "eventListeners": -16385,
      "totalBlockingTimeMs": 217,
      "frameDurationMs": 18.333333333333332,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "workflow-execution",
      "durationMs": 452.0919999999933,
      "styleRecalcs": 15,
      "styleRecalcDurationMs": 21.897999999999996,
      "layouts": 3,
      "layoutDurationMs": 1.182,
      "taskDurationMs": 111.947,
      "heapDeltaBytes": 5060508,
      "heapUsedBytes": 68561088,
      "domNodes": 128,
      "jsHeapTotalBytes": 4980736,
      "scriptDurationMs": 8.924999999999999,
      "eventListeners": 99,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "workflow-execution",
      "durationMs": 465.6150000000707,
      "styleRecalcs": 16,
      "styleRecalcDurationMs": 20.354999999999997,
      "layouts": 3,
      "layoutDurationMs": 1.2109999999999999,
      "taskDurationMs": 112.403,
      "heapDeltaBytes": 5165792,
      "heapUsedBytes": 68381368,
      "domNodes": 143,
      "jsHeapTotalBytes": 4980736,
      "scriptDurationMs": 8.456999999999999,
      "eventListeners": 97,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.800000000000182
    }
  ]
}

@coderabbitai

coderabbitai Bot commented Aug 11, 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-only, session-scoped feature flag overrides. Query parameters are parsed and persisted in sessionStorage, access is restricted to verified Comfy employees, and overrides take precedence over existing flag sources.

Changes

Session feature flag overrides

Layer / File(s) Summary
Override parsing and persistence
src/utils/sessionFeatureFlagOverride.ts, src/utils/sessionFeatureFlagOverride.test.ts
Parses repeated ff parameters, supports boolean, JSON, and string values, merges or clears session state, and handles malformed storage and persistence failures.
Employee-gated override access
src/utils/sessionFeatureFlagOverride.ts, src/utils/sessionFeatureFlagOverride.test.ts
Adds getSessionOverride, which returns stored values only for verified @comfy.org users in cloud distributions.
Feature flag resolution integration
src/composables/useFeatureFlags.ts, src/composables/useFeatureFlags.test.ts, src/scripts/api.ts, src/scripts/api.featureFlags.test.ts, src/scripts/api.sessionOverride.test.ts
Checks session overrides before development, remote, server, and authentication-window fallback values. Tests cover precedence, falsy values, nested lookups, and execution without a Vue instance.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant useFeatureFlags
  participant api
  participant sessionFeatureFlagOverride
  participant sessionStorage
  participant getCurrentUserIdentity
  Browser->>sessionFeatureFlagOverride: provide ff query parameters
  sessionFeatureFlagOverride->>sessionStorage: persist session overrides
  useFeatureFlags->>sessionFeatureFlagOverride: request flag override
  sessionFeatureFlagOverride->>getCurrentUserIdentity: resolve authentication
  getCurrentUserIdentity-->>sessionFeatureFlagOverride: return verified employee state
  sessionFeatureFlagOverride-->>useFeatureFlags: return typed override or undefined
  useFeatureFlags->>api: request server feature fallback
  api-->>useFeatureFlags: return resolved feature value
  useFeatureFlags-->>Browser: resolve feature flag value
Loading

Possibly related PRs

Suggested labels: cursor-review

Suggested reviewers: christian-byrne


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 Linked issue #39 concerns migrating the mask editor, while this PR adds employee-gated session feature-flag overrides. Link the PR to the relevant feature-flag issue, or add changes that satisfy #39's mask-editor migration requirements.
Out of Scope Changes check ⚠️ Warning The reviewed changes implement session feature-flag overrides, which are unrelated to the directly linked #39 objective, "Migrate mask editor." Remove the unrelated feature-flag changes or correct the linked issue to match the PR objective.
End-To-End Regression Coverage For Fixes ❓ Inconclusive The PR metadata lists changed files and the description, but it does not provide the PR title or commit subjects required to evaluate the bug-fix condition. Provide the PR title and all commit subjects, then reassess whether the end-to-end regression coverage requirement applies.
✅ Passed checks (3 passed)
Check name Status Explanation
Adr Compliance For Entity/Litegraph Changes ✅ Passed The changed files are limited to composables, utils, and scripts; none are under src/lib/litegraph, src/ecs, or graph-entity paths, so this check does not apply.
Title check ✅ Passed The title clearly and concisely identifies the employee-gated session feature flag override as the primary change.
Description check ✅ Passed The description covers the required summary, changes, review focus, verification, and dependency information with clear implementation and security details.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch glary/feature-flag-url-overrides

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.

@huang47 huang47 added the preview-cpu Creates a preview ephemeral environment for this PR (CPU only) label Aug 11, 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: 3

🤖 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/composables/useFeatureFlags.test.ts`:
- Around line 533-541: Remove the test case around useFeatureFlags that mocks
getSessionOverride to return true for ServerFeatureFlag.BILLING_CONTROL_ENABLED
while isCloud is false, since that scenario is unreachable and the flag is not
registered for overrides. Do not retain the expectation unless you instead
register an intended cloud-only auth-gated override and set isCloud to true.

In `@src/utils/sessionFeatureFlagOverride.test.ts`:
- Around line 16-18: Introduce or reuse a project-owned authentication adapter
that exposes only the user fields required by isComfyEmployee(), update the
production code to call that adapter instead of VueFire directly, and change the
test mock in sessionFeatureFlagOverride.test.ts to mock the adapter rather than
vuefire’s useCurrentUser.

In `@src/utils/sessionFeatureFlagOverride.ts`:
- Around line 180-183: Remove rawValue from the warning emitted by the session
feature-flag override validation in
src/utils/sessionFeatureFlagOverride.ts:180-183, leaving only the flag name and
expected type. Update the related test in
src/utils/sessionFeatureFlagOverride.test.ts:121-129 to assert the redacted
warning and verify that the submitted invalid value is not logged.
🪄 Autofix

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: 731317a6-9035-4972-84fa-bbe8c5a3764a

📥 Commits

Reviewing files that changed from the base of the PR and between 47f0e7e and f9321e6.

📒 Files selected for processing (4)
  • src/composables/useFeatureFlags.test.ts
  • src/composables/useFeatureFlags.ts
  • src/utils/sessionFeatureFlagOverride.test.ts
  • src/utils/sessionFeatureFlagOverride.ts

Comment thread src/composables/useFeatureFlags.test.ts Outdated
Comment thread src/utils/sessionFeatureFlagOverride.test.ts
Comment thread src/utils/sessionFeatureFlagOverride.ts Outdated
@codecov

codecov Bot commented Aug 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.73684% with 4 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/utils/sessionFeatureFlagOverride.ts 94.11% 4 Missing ⚠️
@@            Coverage Diff             @@
##             main   #15033      +/-   ##
==========================================
+ Coverage   80.79%   80.84%   +0.04%     
==========================================
  Files        1872     1873       +1     
  Lines      121619   121892     +273     
  Branches    37546    37105     -441     
==========================================
+ Hits        98268    98539     +271     
+ Misses      22812    22804       -8     
- Partials      539      549      +10     
Flag Coverage Δ
unit 72.51% <94.73%> (+0.02%) ⬆️

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

Files with missing lines Coverage Δ
src/composables/useFeatureFlags.ts 87.96% <100.00%> (+2.38%) ⬆️
src/scripts/api.ts 73.48% <100.00%> (+0.14%) ⬆️
src/utils/sessionFeatureFlagOverride.ts 94.11% <94.11%> (ø)

... and 9 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.

Drops the opt-in registry: any flag can now be overridden from `?ff=`, and
`api.getServerFeature` consults the session override too so flags that never
went through `resolveFlag` (max_upload_size, node_replacements) are covered.

Value typing moves from the registry to JSON parsing, matching the `ff:`
localStorage override, so `:false` is boolean and `:12` a number while
anything JSON rejects stays a string. The verified @comfy.org gate is now the
only boundary, so the rejected-value diagnostic no longer echoes user input.

Adds a project-owned currentUserIdentity adapter so tests mock our own seam
rather than vuefire, and fixes the auth-gated precedence test to assert a
reachable scenario.
@glary-bot

glary-bot Bot commented Aug 11, 2026

Copy link
Copy Markdown

Heads up for reviewers: the PR description above is now partly stale. 96f7042 removed the opt-in registry on product feedback ("all feature flags should be overridable"), which changes three things worth re-reading the diff for:

1. No whitelist. OVERRIDABLE_FEATURE_FLAGS and isOverridableFlag are gone. Any flag name works.

2. Coverage extended to api.getServerFeature. Flags like max_upload_size and node_replacements never went through resolveFlag, so they'd have stayed un-overridable. getServerFeature now consults the session override ahead of the existing getDevOverride, matching how the two mechanisms already sat together.

3. Typing moved from the registry to JSON parsing, matching the sibling ff: localStorage override:

URL Resolves to
?ff=some_flag true
?ff=some_flag:false false (boolean, not the truthy "false")
?ff=max_upload_size:209715200 209715200 (number)
?ff=signup_turnstile:enforce "enforce" (JSON rejects it → string)
?ff=churnkey_app_id:"12345" "12345" (quote to keep a numeric-looking value a string)
?ff=node_replacements:{"a":"b"} { a: 'b' }
?ff= clears the session

The security posture changed and deserves a look: the registry used to be a second boundary, so the verified @comfy.org check is now the only one. Same commit stopped echoing user-supplied values to the console, since Sentry collects console output as breadcrumbs and a ?ff= value can carry pasted secrets.

Verified in a real browser on the cloud build — including the two cases that only exist because of this change (unified_cloud_auth, previously un-registerable, and max_upload_size resolving as a number through api.getServerFeature), plus the gate still withholding everything when signed out in the same tab:

Gates: typecheck, lint, knip, build:cloud clean. Full unit suite 15,843 passing; remaining failures are pre-existing flakes that also fail on main (main failed 7 in the same comparison run, this branch 5, and the odd one out passes in isolation).

Screenshots

Live cloud build showing every flag overridable: unified_cloud_auth (previously un-registerable) resolving true, max_upload_size resolving as a number via api.getServerFeature, string and quoted-string values, explicit boolean false, and all overrides withheld when signed out in the same tab

coderabbitai[bot]
coderabbitai Bot previously approved these changes Aug 11, 2026

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.

This feels overly refactored

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Agreed — deleted in 3a26b58. The whole file, the src/platform/auth/identity/ directory it lived in, and the exported UserIdentity type are gone.

You put your finger on something I should have caught myself: that module existed only to move a test mock boundary off vuefire (a CodeRabbit nitpick citing "Don't Mock What You Don't Own"). I added production structure to satisfy a test preference, which is backwards — and it wasn't free: a new directory and an exported type for one 6-line caller, plus a fresh object allocated on every flag read just to narrow a type.

The employee check is now a local function in sessionFeatureFlagOverride.ts:

function isComfyEmployee(): boolean {
  try {
    const user = useCurrentUser().value
    if (!user?.emailVerified) return false

    return user.email?.toLowerCase().endsWith(EMPLOYEE_EMAIL_DOMAIN) ?? false
  } catch {
    return false
  }
}

The test mocks vuefire again, and I'm fine with that trade: useCurrentUser() returning a ref is a trivial, stable contract, and a human "this is over-abstracted" beats a bot's mocking preference.

One thing I deliberately kept, now folded into that function's docstring — it must not read authStore. authStore imports useFeatureFlags, so going through it puts the entire app module graph behind every feature-flag read. That's not theoretical: it broke useTurnstile.test.ts and nodeReplacementStore.test.ts with api.addEventListener is not a function because ComfyApp got constructed as an import side effect. Both still pass, along with the rest.

Net: −25 lines, one fewer directory, same behaviour. Re-verified in the browser after the change (employee gate, lookalike domain, unverified address, number/string/boolean parsing) plus typecheck, lint and knip clean.

@huang47
huang47 marked this pull request as ready for review August 11, 2026 06:53
@huang47
huang47 requested a review from a team August 11, 2026 06:53
@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Aug 11, 2026
…er module

The currentUserIdentity adapter existed only to move a test mock boundary off
vuefire, and cost a new directory, an exported type and a per-read object copy
for one 6-line caller. Reading VueFire directly in isComfyEmployee keeps the
one constraint that matters -- never import authStore -- with less machinery.
@huang47

huang47 commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

@Glary-Bot Use characterization tests to make sure we have enough coverage before making changes and split the changes into granular, atomic commits.

@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/utils/sessionFeatureFlagOverride.ts`:
- Line 1: Update getSessionOverride to avoid calling useCurrentUser directly
from synchronous utility and store paths; instead accept a resolved identity or
delegate identity lookup through a project-owned adapter initialized within a
valid injectable Vue context. Preserve valid employee overrides when
api.getServerFeature() is called without component setup, and add coverage for
that invocation path.
🪄 Autofix

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: 9ed87834-7a60-4449-91c3-546809116e55

📥 Commits

Reviewing files that changed from the base of the PR and between 96f7042 and 3a26b58.

📒 Files selected for processing (2)
  • src/utils/sessionFeatureFlagOverride.test.ts
  • src/utils/sessionFeatureFlagOverride.ts

Comment thread src/utils/sessionFeatureFlagOverride.ts
…de layer

Pins the behaviour getServerFeature had with no override in front of it:
verbatim server values, falsy values that must not fall back to the default,
dot-notation paths, and default handling. Verified to pass against main's
unmodified api.ts, so it characterises existing behaviour rather than the
change on this branch.
serverSupportsFeature does not delegate to getServerFeature; it repeats the
override lookup itself, so it honoured the ff: localStorage override but
skipped the session override. Flags read through it -- such as
supports_progress_text_metadata in the websocket parser -- were silently
un-overridable.

Adds coverage for reaching api.getServerFeature from plain module scope with
no active Vue instance, which is how stores and utilities call it.
@glary-bot

glary-bot Bot commented Aug 11, 2026

Copy link
Copy Markdown

@huang47 both done — fd1c150 and 42e1041. (Replying here rather than inline; your comment is on the PR conversation, so a threaded reply wasn't possible.)

Characterization tests

Added to api.featureFlags.test.ts, pinning what getServerFeature did before an override layer sat in front of it: verbatim server values, falsy values (false / 0 / '') that must not fall through to the default, dot-notation paths, and default handling.

To prove they characterize existing behaviour rather than my change, I swapped in main's unmodified api.ts and ran them against it:

main's api.ts        (no override layer)  → 24 passed
this branch's api.ts (with override)      → 24 passed

Identical both sides, so the pre-existing resolution behaviour is provably unchanged. The falsy-value cases were the ones worth pinning — inserting a !== undefined check above that chain is exactly where a truthiness slip would silently break false, 0 and ''.

They paid for themselves immediately. Writing the companion coverage for the api.getServerFeature path surfaced a real bug I had missed: serverSupportsFeature doesn't delegate to getServerFeature, it repeats the override lookup itself — so it honoured the ff: localStorage override but skipped the session override:

serverSupportsFeature(featureName: string): boolean {
  const override = getDevOverride<boolean>(featureName)   // ← honoured
  if (override !== undefined) return override             //   session override: skipped
  return get(this.serverFeatureFlags.value, featureName) === true
}

Flags read through it were silently un-overridable, including supports_progress_text_metadata in the websocket binary parser. Fixed in 42e1041 and confirmed in a browser: false signed out, true for a verified employee, same URL. Your instinct that coverage should come first was right, and it caught something three review passes hadn't.

Atomic commits

The two new commits are split by concern — test: for characterization, fix: for the bug plus its regression test — and I'll hold that granularity going forward.

Your criticism lands on 96f7042, which bundled five concerns (registry removal, JSON value parsing, the getServerFeature extension, the secrets fix, a test fix). I can rebase into a clean sequence:

test:  characterize feature flag resolution
feat:  session overrides via ?ff= (employee-gated, session-scoped)
feat:  extend overrides to api.getServerFeature / serverSupportsFeature
fix:   stop echoing override values to the console

I've held off on one deliberate ground: it needs a force-push, and there are four live review threads on this PR (yours, christian's, two of CodeRabbit's) anchored to the current commits. The final tree wouldn't change, so line-anchored comments should survive, but commit-anchored context would shift mid-conversation. Say go and I'll do it — I didn't want to reshuffle history underneath an active review unasked.

Current state: 6 commits, 15,859 unit tests passing, typecheck / lint / knip / build:cloud clean. Remaining suite failures are pre-existing flakes that also fail on main (main failed 7 in a comparison run; this branch 3, a strict subset).

@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: 3

🤖 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/scripts/api.featureFlags.test.ts`:
- Around line 437-439: Update the suite’s afterEach cleanup to clear
sessionStorage and restore the URL in addition to clearing localStorage,
ensuring session overrides and URL state cannot leak between tests. Preserve the
existing localStorage reset behavior.

In `@src/scripts/api.sessionOverride.test.ts`:
- Around line 14-23: Remove the vuefire mock around the module-scope tests and
let the real VueFire behavior exercise its default-app fallback. Keep API
precedence coverage focused on the project-owned session-override boundary, and
move identity-resolution coverage to the project-owned upstream adapter rather
than mocking the external dependency.
- Around line 78-83: Add a test alongside the existing “reports the override
through serverSupportsFeature too” case that sets
serverFeatureFlags.value.some_flag to true, applies the query override
“some_flag:false” via window.history.replaceState, and asserts
api.serverSupportsFeature('some_flag') returns false, verifying explicit false
precedence.
🪄 Autofix

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: 4b1f1128-4b8b-4aec-95ae-f5e97c83572e

📥 Commits

Reviewing files that changed from the base of the PR and between 3a26b58 and 42e1041.

📒 Files selected for processing (3)
  • src/scripts/api.featureFlags.test.ts
  • src/scripts/api.sessionOverride.test.ts
  • src/scripts/api.ts

Comment thread src/scripts/api.featureFlags.test.ts
Comment thread src/scripts/api.sessionOverride.test.ts Outdated
Comment thread src/scripts/api.sessionOverride.test.ts
… override

The characterization suite cleared only localStorage, so a stray ?ff= URL or
sessionStorage entry could change its results now that getServerFeature reads
both. Resets all three around every case.

Adds the missing falsy case: an explicit ?ff=flag:false must turn a server
value of true off through both serverSupportsFeature and getServerFeature, so a
future truthiness check cannot pass unnoticed.

Also corrects the suite docstring, which claimed to verify VueFire's
default-app fallback -- the vuefire stub bypasses it, and that guarantee is
verified against a real Firebase app in the browser instead.

@christian-byrne christian-byrne 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.

Ideally we could centralize with other query state readers (so it's not hidden away and unexpected) and have this be an extension + use the onAuthUserResolved hook (for dependency direction and disableability) -- but I think it's the best shape if this needs to load before extensions.

}

function readStoredState(): StoredState {
let raw: string | null

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.

Nit: I believe we have some shared helpers for storage I/O

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I went looking, and I don't think either candidate fits — but the reasons are specific rather than "didn't feel like it", so tell me if you disagree and I'll change it.

src/platform/workflow/persistence/base/storageIO.ts is the closest thing to a shared storage I/O module, but it's workflow-draft-specific and would actively break this. Every export is typed to DraftIndexV2 / DraftPayloadV2 / ActivePathPointer, keys come from StorageKeys.draftIndex(workspaceId), and — the real problem — it carries module-level write gating:

let workflowWritesBlocked = false
function writeStorage(storage, key, value) {
  if (!storageAvailable || workflowWritesBlocked) return   // silent no-op
  ...
}

prepareWorkflowWorkspaceTransition() flips that flag, so switching workspace would silently stop persisting feature-flag overrides. Borrowing it would couple flag overrides to workflow-persistence global state for no gain.

VueUse useStorage is the genuine shared helper and is used elsewhere in the repo (versionCompatibilityStore, useConflictAcknowledgment). Two things rule it out here:

  1. It's eager — calling it creates a watcher and a storage listener at call time. This module is imported by src/scripts/api.ts, so that side effect would land in nearly every module graph and every test file. Keeping this module inert on import is the constraint that made the earlier authStore version unworkable.
  2. It caches into a ref, so it wouldn't observe an external sessionStorage.clear() — which is exactly how the app-level clear and the tests reset state. Cached-ref semantics would drift from actual storage.

On the duplication itself: you're right that preservedQueryManager.ts hand-rolls the same try/catch + JSON + sessionStorage shape, so this is the second occurrence. I'd rather not extract a shared helper for two call sites — that's the same instinct that produced the currentUserIdentity adapter you (correctly) told me to delete. Happy to do it as a follow-up if you want it as a real utility with its own tests, rather than something I invent mid-PR.

@glary-bot

glary-bot Bot commented Aug 11, 2026

Copy link
Copy Markdown

@christian-byrne on the review summary — thanks for the approval. I checked your load-order premise rather than just agreeing with it, and it holds:

onAuthUserResolved is a real hook (src/types/comfy.ts:256, wired at extensionService.ts:100-103), so the extension shape you describe is genuinely available. But extension setup runs at app.ts:1035 via invokeExtensionsAsync('setup'), and flags are read well before that:

  • router.ts:239 reads flags.onboardingSurveyEnabled inside a navigation guard — initial navigation happens at mount, before ComfyApp.setup().
  • authStore reads flags.unifiedCloudAuthEnabled at five call sites (193, 251, 296, 406, 515) on the sign-in and token-refresh paths, which run during bootstrap.
  • api.getServerFeature / serverSupportsFeature are called from inside api.init() itself — the websocket binary parser checks supports_progress_text_metadata per message.

So an extension registering on onAuthUserResolved would land after the first reads, and those early consumers would silently see un-overridden values. Your instinct that it "needs to load before extensions" is the binding constraint.

There's a second reason the hook wouldn't be sufficient on its own: onAuthUserResolved fires once when the user resolves, whereas the gate is re-evaluated on every read. That's what lets a flag flip the moment auth lands and flip back on sign-out, without anything having to push state around.

On centralizing with the other query-state readerspreservedQueryManager is the natural home and I'd support moving there eventually. It's currently LocationQuery/namespace-oriented around vue-router, while this needs a raw window.location.search read before the router exists. Worth a follow-up to reconcile, but a bigger change than belongs in this PR.

Happy to file both as follow-up issues if you want them tracked rather than left in a PR thread.

@huang47

huang47 commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

@christian-byrne on the review summary — thanks for the approval. I checked your load-order premise rather than just agreeing with it, and it holds:

onAuthUserResolved is a real hook (src/types/comfy.ts:256, wired at extensionService.ts:100-103), so the extension shape you describe is genuinely available. But extension setup runs at app.ts:1035 via invokeExtensionsAsync('setup'), and flags are read well before that:

  • router.ts:239 reads flags.onboardingSurveyEnabled inside a navigation guard — initial navigation happens at mount, before ComfyApp.setup().
  • authStore reads flags.unifiedCloudAuthEnabled at five call sites (193, 251, 296, 406, 515) on the sign-in and token-refresh paths, which run during bootstrap.
  • api.getServerFeature / serverSupportsFeature are called from inside api.init() itself — the websocket binary parser checks supports_progress_text_metadata per message.

So an extension registering on onAuthUserResolved would land after the first reads, and those early consumers would silently see un-overridden values. Your instinct that it "needs to load before extensions" is the binding constraint.

There's a second reason the hook wouldn't be sufficient on its own: onAuthUserResolved fires once when the user resolves, whereas the gate is re-evaluated on every read. That's what lets a flag flip the moment auth lands and flip back on sign-out, without anything having to push state around.

On centralizing with the other query-state readerspreservedQueryManager is the natural home and I'd support moving there eventually. It's currently LocationQuery/namespace-oriented around vue-router, while this needs a raw window.location.search read before the router exists. Worth a follow-up to reconcile, but a bigger change than belongs in this PR.

Happy to file both as follow-up issues if you want them tracked rather than left in a PR thread.

@Glary-Bot file both as follow-up issues in linear

@huang47
huang47 enabled auto-merge August 11, 2026 13:46
@huang47

huang47 commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai approve

@huang47
huang47 added this pull request to the merge queue Aug 11, 2026
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Comments resolved and changes approved.

@glary-bot

glary-bot Bot commented Aug 11, 2026

Copy link
Copy Markdown

@huang47 both filed in Linear, on Frontend Engineering, low priority, Tech Debt label, unassigned:

Both reference this PR and FE-1471 as origin, and carry the concrete findings from the review rather than just the headline — the load-order evidence for FE-1551, and for FE-1552 the two candidate helpers that were considered and why each was rejected, including that storageIO.ts gates writes on workflowWritesBlocked so borrowing it would silently stop persistence during a workspace transition.

I deliberately did not attach them to this PR as GitHub-linked issues. Linear's PR attachments drive close-on-merge automation, and these are follow-ups this PR doesn't resolve — linking them would auto-close both when #15033 merges. They reference the PR by URL in the body instead.

Neither is a duplicate; I searched first. Worth noting FE-1108 is the same shape of ticket (extract-a-shared-helper follow-up from a review thread), so I matched its conventions.

Also left unassigned and without a project — no existing FE project looked like a clean fit for frontend tech debt, and I'd rather they sit in Triage for you to route than be mis-filed.

Merged via the queue into main with commit b9964c6 Aug 11, 2026
71 checks passed
@huang47
huang47 deleted the glary/feature-flag-url-overrides branch August 11, 2026 14:02
christian-byrne pushed a commit to balpreetgrowthnatives/ComfyUI_frontend that referenced this pull request Aug 16, 2026
…veFlag (Comfy-Org#15125)

## Summary

`nodeLibraryEssentialsEnabled` hand-rolled its own resolution and read
remote config *before* the override layers, so
`?ff=node_library_essentials_enabled:false` and the `ff:` localStorage
dev override were both silently ignored — this getter now goes through
the shared `resolveFlag` helper like every other remote-config-backed
flag.

## Changes

- **What**: `nodeLibraryEssentialsEnabled` now calls `resolveFlag(...)`,
so precedence is session override → dev override → remote config →
server value, matching every other flag in `useFeatureFlags`. The
nightly/dev default-on moves *below* the override lookup: `isNightly ||
import.meta.env.DEV` becomes `resolveFlag`'s `defaultValue` instead of
an unconditional early `return true`.
- **Behavior**: on a stable/production build nothing changes (`isNightly
|| DEV` is `false` there, which is the default the old code already
passed). Two deltas, both intentional: (1) an employee `?ff=` link or an
`ff:` dev override now actually changes the Essentials tab, and (2) on
nightly and local dev builds an explicitly-served value (`/features`
remote config or the server feature flag) now wins over the built-in on
— previously nothing could turn the Essentials tab off locally, which is
half of the reported bug.
- **Tests**: one case in the existing `session override precedence`
block asserting a `false` session override beats a `true` remote-config
value, plus a `nodeLibraryEssentialsEnabled` block covering the nightly
axis — nightly + nothing served → on, nightly + remote-config `false` →
off, nightly + served server `false` → off. All three `false` cases are
red on `main` (it returns `true`) and green with the fix.

## Review Focus

- Chesterton's fence on the early return: `git log -L` shows the getter
arrived in Comfy-Org#9067, whose description states the intent as *"Defaults to
`true` in dev/nightly builds, `false` in production. Overridable via
remote config or server feature flags"* — i.e. dev/nightly was always
meant as a **default**, and the early return only reads as a hard
override because the `?ff=` session layer (Comfy-Org#15033) landed ~6 months
later and this getter never opted in. Making it `resolveFlag`'s
`defaultValue` is the shape that commit describes.
- The one visible change beyond override precedence is nightly/dev now
honoring an explicitly-served `false` (item 2 above). Flagging it
explicitly rather than burying it: it is the intended semantics, but it
is a behavior change for anyone on nightly whose `/features` endpoint
serves this flag. The cursor-review panel raised this; it is now pinned
by the `nodeLibraryEssentialsEnabled` test block.
- `linearToggleEnabled` has the same `if (isNightly) return true`
short-circuit above its `resolveFlag` call and is un-overridable on
nightly for the same reason. Deliberately left alone here to keep this
diff to one getter; worth a separate change.

## Test Plan

- `pnpm exec vitest run src/composables/useFeatureFlags.test.ts
src/components/sidebar/tabs/NodeLibrarySidebarTabV2.test.ts
src/components/searchbox/v2/NodeSearchContent.test.ts` — 79 passed (the
two component specs are the flag's consumers).
- `pnpm typecheck` — clean. `oxfmt --check` and `eslint` on both changed
files — clean.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

preview-cpu Creates a preview ephemeral environment for this PR (CPU only) size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants