Skip to content

fix: re-check tab fallback after deferred error scans - #15012

Merged
DrJKL merged 8 commits into
mainfrom
drjkl/tab-fallback-deferred-scans
Aug 13, 2026
Merged

fix: re-check tab fallback after deferred error scans#15012
DrJKL merged 8 commits into
mainfrom
drjkl/tab-fallback-deferred-scans

Conversation

@DrJKL

@DrJKL DrJKL commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Pasted and duplicated nodes register widgets before their deferred missing-model and missing-media scans finish. The right-side panel could therefore recalculate its tabs, see no selected-node error yet, and switch away from Errors permanently.

This change tracks graph-scoped added-node scans from node addition through asynchronous verification. An already-active Errors tab remains available while its selected node is being scanned, then either stays active when an error surfaces or falls back when the scan settles cleanly.

Scan lifecycle handling now:

  • reference-counts overlapping scans by root graph and execution ID
  • releases the tab hold without aborting valid verification during subgraph navigation
  • aborts verification when the scanned node is removed
  • suppresses late results after workflow/node replacement, widget changes, or ownership changes
  • waits for already-started verification when a later scan stage fails

Testing

  • Added focused component coverage for pending, error, clean fallback, unmount, and subgraph-interior selection behavior.
  • Added lifecycle coverage for overlapping scans, graph isolation, disposal, workflow replacement, model/media cancellation, changed values, and scan-stage failures.
  • Added a Playwright flow covering paste, delayed cloud verification, retained Errors selection, surfaced errors, and clean fallback after resolution.
  • pnpm test:unit src/components/rightSidePanel/RightSidePanel.test.ts src/composables/graph/useErrorClearingHooks.test.ts src/stores/executionErrorStore.test.ts
  • pnpm typecheck
  • ESLint and Oxfmt on changed files

Paste registers widgets in reactive stores before node:added fires, so
the Vue flush that recomputes the panel tabs can run before the deferred
missing-model scan registers the pasted node's error. The fallback
watchEffect then saw no relevant errors for the selection and flipped
the panel off the Errors tab permanently. Re-check one microtask later
so in-flight scans can keep the active tab.
@DrJKL
DrJKL requested a review from a team August 10, 2026 22:32
@dosubot dosubot Bot added the size:S This PR changes 10-29 lines, ignoring generated files. label Aug 10, 2026
@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown

🎭 Playwright: ✅ 1798 passed, 0 failed · 5 flaky

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

🎨 Storybook: ✅ Built — View Storybook

Details

⏰ Completed at: 08/13/2026, 12:38:43 AM UTC

Links

📦 Bundle: 8.72 MB gzip 🔴 +934 B

Details

Summary

  • Raw size: 36.8 MB baseline 36.8 MB — 🔴 +4.98 kB
  • Gzip: 8.72 MB baseline 8.72 MB — 🔴 +934 B
  • Brotli: 6.08 MB baseline 6.08 MB — 🔴 +456 B
  • Bundles: 436 current • 436 baseline • 145 added / 145 removed

Category Glance
Graph Workspace 🔴 +4 kB (1.37 MB) · Data & Services 🔴 +979 B (3.51 MB) · Vendor & Third-Party ⚪ 0 B (16.3 MB) · Other ⚪ 0 B (14.2 MB) · Panels & Settings ⚪ 0 B (565 kB) · Utilities & Hooks ⚪ 0 B (550 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-DP7ZdmLt.js (removed) 3.67 kB 🟢 -3.67 kB 🟢 -1.84 kB 🟢 -1.59 kB
assets/index-DUNgr-Ij.js (new) 3.67 kB 🔴 +3.67 kB 🔴 +1.83 kB 🔴 +1.59 kB

Status: 1 added / 1 removed

Graph Workspace — 1.37 MB (baseline 1.36 MB) • 🔴 +4 kB

Graph editor runtime, canvas, workflow orchestration

File Before After Δ Raw Δ Gzip Δ Brotli
assets/GraphView-BA0ch2FT.js (new) 1.36 MB 🔴 +1.36 MB 🔴 +296 kB 🔴 +222 kB
assets/GraphView-Du7Yh3od.js (removed) 1.36 MB 🟢 -1.36 MB 🟢 -295 kB 🟢 -222 kB
assets/WidgetCompositor-BO6RADzm.js (removed) 8.17 kB 🟢 -8.17 kB 🟢 -2.76 kB 🟢 -2.45 kB
assets/WidgetCompositor-Dgmedn2z.js (new) 8.17 kB 🔴 +8.17 kB 🔴 +2.76 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-akUlQcK3.js (new) 25 kB 🔴 +25 kB 🔴 +6.25 kB 🔴 +5.52 kB
assets/CloudSurveyView-B0Wq-RTf.js (removed) 25 kB 🟢 -25 kB 🟢 -6.24 kB 🟢 -5.52 kB
assets/CloudLayoutView-BtQlfWA9.js (new) 21.8 kB 🔴 +21.8 kB 🔴 +6.57 kB 🔴 +5.74 kB
assets/CloudLayoutView-BuKvjbnb.js (removed) 21.8 kB 🟢 -21.8 kB 🟢 -6.57 kB 🟢 -5.74 kB
assets/UserCheckView-CkKkmNMN.js (new) 8.75 kB 🔴 +8.75 kB 🔴 +2.19 kB 🔴 +1.9 kB
assets/UserCheckView-qhMV8_hI.js (removed) 8.75 kB 🟢 -8.75 kB 🟢 -2.19 kB 🟢 -1.9 kB
assets/CloudLoginView-Brx2_XNs.js (new) 8.74 kB 🔴 +8.74 kB 🔴 +2.55 kB 🔴 +2.25 kB
assets/CloudLoginView-D39rBKwx.js (removed) 8.74 kB 🟢 -8.74 kB 🟢 -2.55 kB 🟢 -2.25 kB
assets/useCloudAuthPage-o2rklmgB.js (removed) 7.08 kB 🟢 -7.08 kB 🟢 -2.51 kB 🟢 -2.19 kB
assets/useCloudAuthPage-SHxTcAAJ.js (new) 7.08 kB 🔴 +7.08 kB 🔴 +2.52 kB 🔴 +2.2 kB
assets/CloudSignupView-C6D-2Tz2.js (removed) 6.56 kB 🟢 -6.56 kB 🟢 -2.17 kB 🟢 -1.91 kB
assets/CloudSignupView-DbVuEEYr.js (new) 6.56 kB 🔴 +6.56 kB 🔴 +2.17 kB 🔴 +1.91 kB
assets/WidgetTextPreview-BTgId3r0.js (removed) 6.07 kB 🟢 -6.07 kB 🟢 -2.13 kB 🟢 -1.89 kB
assets/WidgetTextPreview-D25sw-Ub.js (new) 6.07 kB 🔴 +6.07 kB 🔴 +2.13 kB 🔴 +1.89 kB
assets/CloudSubscriptionRedirectView-BtSPNHJg.js (new) 6.05 kB 🔴 +6.05 kB 🔴 +2.25 kB 🔴 +1.96 kB
assets/CloudSubscriptionRedirectView-DsDerQJj.js (removed) 6.05 kB 🟢 -6.05 kB 🟢 -2.25 kB 🟢 -1.97 kB
assets/UserSelectView-CPnwhk-7.js (new) 5.49 kB 🔴 +5.49 kB 🔴 +1.96 kB 🔴 +1.71 kB
assets/UserSelectView-DSlzZNFP.js (removed) 5.49 kB 🟢 -5.49 kB 🟢 -1.96 kB 🟢 -1.71 kB
assets/CloudForgotPasswordView-Bd6Pfi0G.js (removed) 4.97 kB 🟢 -4.97 kB 🟢 -1.72 kB 🟢 -1.49 kB
assets/CloudForgotPasswordView-D2rUbRNw.js (new) 4.97 kB 🔴 +4.97 kB 🔴 +1.72 kB 🔴 +1.5 kB
assets/CloudAuthTimeoutView-C8fucCW0.js (removed) 4.43 kB 🟢 -4.43 kB 🟢 -1.54 kB 🟢 -1.35 kB
assets/CloudAuthTimeoutView-CQi47Yiw.js (new) 4.43 kB 🔴 +4.43 kB 🔴 +1.54 kB 🔴 +1.34 kB
assets/OAuthLayoutView-BOkcKLg1.js (removed) 1.31 kB 🟢 -1.31 kB 🟢 -704 B 🟢 -587 B
assets/OAuthLayoutView-wUxUQM1I.js (new) 1.31 kB 🔴 +1.31 kB 🔴 +701 B 🔴 +587 B
assets/WidgetTextPreview-BOTzAVCi.js (new) 131 B 🔴 +131 B 🔴 +100 B 🔴 +88 B
assets/WidgetTextPreview-D_HEXGVX.js (removed) 131 B 🟢 -131 B 🟢 -100 B 🟢 -88 B

Status: 13 added / 13 removed / 4 unchanged

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

Configuration panels, inspectors, and settings screens

File Before After Δ Raw Δ Gzip Δ Brotli
assets/KeybindingPanel-CKdwpoKz.js (new) 49.4 kB 🔴 +49.4 kB 🔴 +9.94 kB 🔴 +8.8 kB
assets/KeybindingPanel-DbJAjLx1.js (removed) 49.4 kB 🟢 -49.4 kB 🟢 -9.94 kB 🟢 -8.82 kB
assets/SecretsPanel-Ba3wDB6L.js (removed) 33.7 kB 🟢 -33.7 kB 🟢 -7.87 kB 🟢 -6.9 kB
assets/SecretsPanel-CqM-fj-3.js (new) 33.7 kB 🔴 +33.7 kB 🔴 +7.87 kB 🔴 +6.89 kB
assets/CreditsPanel-D5L8GZac.js (new) 11.5 kB 🔴 +11.5 kB 🔴 +3.2 kB 🔴 +2.81 kB
assets/CreditsPanel-D7XVTi1K.js (removed) 11.5 kB 🟢 -11.5 kB 🟢 -3.2 kB 🟢 -2.81 kB
assets/AboutPanel-Cd87LtGU.js (new) 11.2 kB 🔴 +11.2 kB 🔴 +3.03 kB 🔴 +2.71 kB
assets/AboutPanel-D2TYHcCt.js (removed) 11.2 kB 🟢 -11.2 kB 🟢 -3.03 kB 🟢 -2.71 kB
assets/ExtensionPanel-Boegd5C3.js (removed) 9.19 kB 🟢 -9.19 kB 🟢 -2.51 kB 🟢 -2.22 kB
assets/ExtensionPanel-DO9K1JtC.js (new) 9.19 kB 🔴 +9.19 kB 🔴 +2.51 kB 🔴 +2.22 kB
assets/ServerConfigPanel-CSMVXIFL.js (new) 6.09 kB 🔴 +6.09 kB 🔴 +1.94 kB 🔴 +1.72 kB
assets/ServerConfigPanel-Cz76CAaw.js (removed) 6.09 kB 🟢 -6.09 kB 🟢 -1.94 kB 🟢 -1.72 kB
assets/UserPanel-Cr3O79jR.js (removed) 5.73 kB 🟢 -5.73 kB 🟢 -1.78 kB 🟢 -1.54 kB
assets/UserPanel-DRgvYD5Q.js (new) 5.73 kB 🔴 +5.73 kB 🔴 +1.78 kB 🔴 +1.54 kB
assets/refreshRemoteConfig-BShEE_0_.js (removed) 3.44 kB 🟢 -3.44 kB 🟢 -1.33 kB 🟢 -1.17 kB
assets/refreshRemoteConfig-DCbXEP1u.js (new) 3.44 kB 🔴 +3.44 kB 🔴 +1.33 kB 🔴 +1.18 kB
assets/cloudRemoteConfig-B5gIAiht.js (new) 951 B 🔴 +951 B 🔴 +515 B 🔴 +428 B
assets/cloudRemoteConfig-Bias60LG.js (removed) 951 B 🟢 -951 B 🟢 -517 B 🟢 -432 B
assets/refreshRemoteConfig-DgT4L-Ua.js (new) 110 B 🔴 +110 B 🔴 +89 B 🔴 +82 B
assets/refreshRemoteConfig-DmChBsqd.js (removed) 110 B 🟢 -110 B 🟢 -89 B 🟢 -84 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-Bmo7QrUU.js (new) 12.8 kB 🔴 +12.8 kB 🔴 +4.32 kB 🔴 +3.76 kB
assets/SignUpForm-DbKfzbgi.js (removed) 12.8 kB 🟢 -12.8 kB 🟢 -4.32 kB 🟢 -3.76 kB
assets/auth-Bcg4grwc.js (removed) 3.71 kB 🟢 -3.71 kB 🟢 -1.28 kB 🟢 -1.1 kB
assets/auth-D8jPMDzF.js (new) 3.71 kB 🔴 +3.71 kB 🔴 +1.28 kB 🔴 +1.1 kB
assets/UpdatePasswordContent-DBC6fejw.js (new) 1.85 kB 🔴 +1.85 kB 🔴 +840 B 🔴 +734 B
assets/UpdatePasswordContent-DsceGJQ7.js (removed) 1.85 kB 🟢 -1.85 kB 🟢 -841 B 🟢 -731 B
assets/authStore-BwIPMzfc.js (new) 128 B 🔴 +128 B 🔴 +107 B 🔴 +112 B
assets/authStore-hDBC5-3G.js (removed) 128 B 🟢 -128 B 🟢 -107 B 🟢 -102 B
assets/workspaceAuthStore-ChpLJbo1.js (new) 108 B 🔴 +108 B 🔴 +99 B 🔴 +108 B
assets/workspaceAuthStore-CYWC0QpF.js (removed) 108 B 🟢 -108 B 🟢 -99 B 🟢 -105 B
assets/auth-CIGBwyN1.js (new) 105 B 🔴 +105 B 🔴 +96 B 🔴 +86 B
assets/auth-DgYiHGIc.js (removed) 105 B 🟢 -105 B 🟢 -96 B 🟢 -73 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-BNZyht75.js (removed) 90.1 kB 🟢 -90.1 kB 🟢 -19.3 kB 🟢 -16.5 kB
assets/ComfyHubPublishDialog-DrDJ5cCa.js (new) 90.1 kB 🔴 +90.1 kB 🔴 +19.3 kB 🔴 +16.5 kB
assets/useShareDialog-DkpTOkjv.js (new) 23.9 kB 🔴 +23.9 kB 🔴 +5.7 kB 🔴 +5.05 kB
assets/useShareDialog-DU_GXGuu.js (removed) 23.9 kB 🟢 -23.9 kB 🟢 -5.7 kB 🟢 -5.04 kB
assets/feedbackDialog-BWaJBTY1.js (removed) 4.45 kB 🟢 -4.45 kB 🟢 -1.87 kB 🟢 -1.59 kB
assets/feedbackDialog-DIExTiaA.js (new) 4.45 kB 🔴 +4.45 kB 🔴 +1.86 kB 🔴 +1.59 kB
assets/useRangeEditor-D3ZZW9n1.js (removed) 3.29 kB 🟢 -3.29 kB 🟢 -1.14 kB 🟢 -1.03 kB
assets/useRangeEditor-oQz6RSkU.js (new) 3.29 kB 🔴 +3.29 kB 🔴 +1.14 kB 🔴 +1.03 kB
assets/useLayerEditor-BmLO-pbm.js (new) 1.01 kB 🔴 +1.01 kB 🔴 +490 B 🔴 +407 B
assets/useLayerEditor-Y4NoTlhM.js (removed) 1.01 kB 🟢 -1.01 kB 🟢 -491 B 🟢 -409 B
assets/ComfyHubPublishDialog-CQ99eHhR.js (removed) 143 B 🟢 -143 B 🟢 -105 B 🟢 -89 B
assets/ComfyHubPublishDialog-RQ5AT80B.js (new) 143 B 🔴 +143 B 🔴 +105 B 🔴 +89 B
assets/useSubscriptionDialog-BB4rls-U.js (removed) 108 B 🟢 -108 B 🟢 -102 B 🟢 -87 B
assets/useSubscriptionDialog-Cxidu50o.js (new) 108 B 🔴 +108 B 🔴 +102 B 🔴 +89 B

Status: 7 added / 7 removed / 1 unchanged

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

Reusable component library chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/ComfyQueueButton-DF330qed.js (removed) 14.6 kB 🟢 -14.6 kB 🟢 -3.97 kB 🟢 -3.52 kB
assets/ComfyQueueButton-NcqxLDDz.js (new) 14.6 kB 🔴 +14.6 kB 🔴 +3.97 kB 🔴 +3.53 kB
assets/useTerminalTabs-CzH5h-UF.js (new) 11.8 kB 🔴 +11.8 kB 🔴 +3.69 kB 🔴 +3.28 kB
assets/useTerminalTabs-fKVOy202.js (removed) 11.8 kB 🟢 -11.8 kB 🟢 -3.69 kB 🟢 -3.27 kB
assets/InviteMembersForm-BG5XZswA.js (removed) 8.2 kB 🟢 -8.2 kB 🟢 -2.72 kB 🟢 -2.44 kB
assets/InviteMembersForm-BXomw19J.js (new) 8.2 kB 🔴 +8.2 kB 🔴 +2.72 kB 🔴 +2.42 kB
assets/SubscribeButton-B4oNYYh-.js (new) 2.15 kB 🔴 +2.15 kB 🔴 +968 B 🔴 +847 B
assets/SubscribeButton-BnevnmLT.js (removed) 2.15 kB 🟢 -2.15 kB 🟢 -969 B 🟢 -848 B
assets/cloudFeedbackTopbarButton-DEhd6mcI.js (new) 705 B 🔴 +705 B 🔴 +418 B 🔴 +361 B
assets/cloudFeedbackTopbarButton-DEXKs6bi.js (removed) 705 B 🟢 -705 B 🟢 -420 B 🟢 -362 B
assets/ComfyQueueButton-C5hWm_7S.js (removed) 128 B 🟢 -128 B 🟢 -99 B 🟢 -89 B
assets/ComfyQueueButton-Ci1qCIsQ.js (new) 128 B 🔴 +128 B 🔴 +99 B 🔴 +92 B

Status: 6 added / 6 removed / 8 unchanged

Data & Services — 3.51 MB (baseline 3.51 MB) • 🔴 +979 B

Stores, services, APIs, and repositories

File Before After Δ Raw Δ Gzip Δ Brotli
assets/settingStore-CMdCCoeD.js (new) 3.23 MB 🔴 +3.23 MB 🔴 +749 kB 🔴 +564 kB
assets/settingStore-pmTIf51J.js (removed) 3.23 MB 🟢 -3.23 MB 🟢 -749 kB 🟢 -564 kB
assets/load3dService-DdVAfKqV.js (removed) 132 kB 🟢 -132 kB 🟢 -29.3 kB 🟢 -24.6 kB
assets/load3dService-neyxSzB8.js (new) 132 kB 🔴 +132 kB 🔴 +29.3 kB 🔴 +24.6 kB
assets/api-DIdagwYh.js (removed) 98.3 kB 🟢 -98.3 kB 🟢 -27.2 kB 🟢 -23.3 kB
assets/api-DJMZhBxd.js (new) 98.3 kB 🔴 +98.3 kB 🔴 +27.2 kB 🔴 +23.4 kB
assets/workflowShareService-C48UUrrU.js (removed) 16.5 kB 🟢 -16.5 kB 🟢 -4.91 kB 🟢 -4.34 kB
assets/workflowShareService-LXPXsuV9.js (new) 16.5 kB 🔴 +16.5 kB 🔴 +4.91 kB 🔴 +4.35 kB
assets/keybindingService-B91m_ZMZ.js (new) 6.89 kB 🔴 +6.89 kB 🔴 +1.73 kB 🔴 +1.5 kB
assets/keybindingService-DXUTPYQV.js (removed) 6.89 kB 🟢 -6.89 kB 🟢 -1.73 kB 🟢 -1.5 kB
assets/releaseStore-3ph6cPbC.js (new) 6.72 kB 🔴 +6.72 kB 🔴 +2.03 kB 🔴 +1.77 kB
assets/releaseStore-YZaffsBn.js (removed) 6.72 kB 🟢 -6.72 kB 🟢 -2.03 kB 🟢 -1.77 kB
assets/systemStatsStore-Bq2TuvEg.js (new) 4.93 kB 🔴 +4.93 kB 🔴 +1.74 kB 🔴 +1.47 kB
assets/systemStatsStore-CMBridyO.js (removed) 4.93 kB 🟢 -4.93 kB 🟢 -1.74 kB 🟢 -1.47 kB
assets/userStore-Bv4PkTwV.js (removed) 2.38 kB 🟢 -2.38 kB 🟢 -897 B 🟢 -794 B
assets/userStore-rrH0evGs.js (new) 2.38 kB 🔴 +2.38 kB 🔴 +898 B 🔴 +796 B
assets/audioService-6pQHCRKW.js (new) 1.71 kB 🔴 +1.71 kB 🔴 +831 B 🔴 +723 B
assets/audioService-uudXUw96.js (removed) 1.71 kB 🟢 -1.71 kB 🟢 -829 B 🟢 -730 B
assets/dialogService-46MbtOou.js (new) 98 B 🔴 +98 B 🔴 +97 B 🔴 +92 B
assets/dialogService-c0HAAM6O.js (removed) 98 B 🟢 -98 B 🟢 -97 B 🟢 -84 B
assets/releaseStore-BEBTmc8u.js (new) 95 B 🔴 +95 B 🔴 +86 B 🔴 +84 B
assets/releaseStore-DF-GtJaz.js (removed) 95 B 🟢 -95 B 🟢 -86 B 🟢 -82 B
assets/settingStore-COzyH9BE.js (new) 95 B 🔴 +95 B 🔴 +86 B 🔴 +85 B
assets/settingStore-zMkiq7Jg.js (removed) 95 B 🟢 -95 B 🟢 -86 B 🟢 -86 B
assets/assetsStore-CT_qkxgx.js (new) 94 B 🔴 +94 B 🔴 +92 B 🔴 +82 B
assets/assetsStore-CU54G__g.js (removed) 94 B 🟢 -94 B 🟢 -92 B 🟢 -84 B
assets/api-BenHRGGF.js (new) 62 B 🔴 +62 B 🔴 +74 B 🔴 +66 B
assets/api-DfC4dw2f.js (removed) 62 B 🟢 -62 B 🟢 -74 B 🟢 -66 B

Status: 14 added / 14 removed / 3 unchanged

Utilities & Hooks — 550 kB (baseline 550 kB) • ⚪ 0 B

Helpers, composables, and utility bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useConflictDetection-CK4JdFDZ.js (removed) 236 kB 🟢 -236 kB 🟢 -53 kB 🟢 -43.1 kB
assets/useConflictDetection-TibvaHXq.js (new) 236 kB 🔴 +236 kB 🔴 +53 kB 🔴 +43.1 kB
assets/useLayerEditorSession-CUueeYNY.js (removed) 158 kB 🟢 -158 kB 🟢 -40.8 kB 🟢 -34.3 kB
assets/useLayerEditorSession-DFtU3vHq.js (new) 158 kB 🔴 +158 kB 🔴 +40.8 kB 🔴 +34.3 kB
assets/useLoad3d-BwDxR4w4.js (removed) 25.8 kB 🟢 -25.8 kB 🟢 -5.8 kB 🟢 -5.14 kB
assets/useLoad3d-CTP60hIQ.js (new) 25.8 kB 🔴 +25.8 kB 🔴 +5.8 kB 🔴 +5.14 kB
assets/useLoad3dViewer-DK6XI5XU.js (new) 21.2 kB 🔴 +21.2 kB 🔴 +4.98 kB 🔴 +4.36 kB
assets/useLoad3dViewer-rcnKk1eD.js (removed) 21.2 kB 🟢 -21.2 kB 🟢 -4.98 kB 🟢 -4.37 kB
assets/useImageCrop-C4ACSB-X.js (new) 14.9 kB 🔴 +14.9 kB 🔴 +3.42 kB 🔴 +2.98 kB
assets/useImageCrop-DyQA_Wok.js (removed) 14.9 kB 🟢 -14.9 kB 🟢 -3.42 kB 🟢 -2.98 kB
assets/useDowngradeToPersonal-2ewKKvBR.js (new) 10.9 kB 🔴 +10.9 kB 🔴 +2.75 kB 🔴 +2.36 kB
assets/useDowngradeToPersonal-BArk7bX9.js (removed) 10.9 kB 🟢 -10.9 kB 🟢 -2.75 kB 🟢 -2.36 kB
assets/useFeatureFlags-CvzVlIkq.js (new) 6.99 kB 🔴 +6.99 kB 🔴 +2 kB 🔴 +1.72 kB
assets/useFeatureFlags-RfXYYNIs.js (removed) 6.99 kB 🟢 -6.99 kB 🟢 -2 kB 🟢 -1.7 kB
assets/useCompositorLayers-CzLKehdS.js (new) 2.94 kB 🔴 +2.94 kB 🔴 +896 B 🔴 +804 B
assets/useCompositorLayers-eGjWw6wB.js (removed) 2.94 kB 🟢 -2.94 kB 🟢 -898 B 🟢 -805 B
assets/assetPreviewUtil-C_durNOS.js (new) 2.35 kB 🔴 +2.35 kB 🔴 +967 B 🔴 +845 B
assets/assetPreviewUtil-CuluUxvr.js (removed) 2.35 kB 🟢 -2.35 kB 🟢 -968 B 🟢 -845 B
assets/useUpstreamValue-CPY1iItS.js (new) 1.99 kB 🔴 +1.99 kB 🔴 +757 B 🔴 +685 B
assets/useUpstreamValue-CTWZhDn4.js (removed) 1.99 kB 🟢 -1.99 kB 🟢 -758 B 🟢 -672 B
assets/useWorkspaceTierLabel-DlY90Wrn.js (removed) 1.93 kB 🟢 -1.93 kB 🟢 -812 B 🟢 -697 B
assets/useWorkspaceTierLabel-Du_d5kBg.js (new) 1.93 kB 🔴 +1.93 kB 🔴 +811 B 🔴 +696 B
assets/subscriptionCheckoutUtil-CvmCZTN8.js (removed) 877 B 🟢 -877 B 🟢 -522 B 🟢 -434 B
assets/subscriptionCheckoutUtil-Vttiw6Q9.js (new) 877 B 🔴 +877 B 🔴 +519 B 🔴 +435 B
assets/useSessionCookie-NK_t1tI2.js (removed) 652 B 🟢 -652 B 🟢 -336 B 🟢 -293 B
assets/useSessionCookie-VqPmZmQS.js (new) 652 B 🔴 +652 B 🔴 +335 B 🔴 +290 B
assets/useLoad3d-BVnIM-yd.js (removed) 311 B 🟢 -311 B 🟢 -163 B 🟢 -148 B
assets/useLoad3d-DSVUOU3j.js (new) 311 B 🔴 +311 B 🔴 +165 B 🔴 +147 B
assets/useSessionCookie-BxfU2vsA.js (removed) 101 B 🟢 -101 B 🟢 -86 B 🟢 -83 B
assets/useSessionCookie-k8lkIiuo.js (new) 101 B 🔴 +101 B 🔴 +86 B 🔴 +82 B
assets/useFeatureFlags-B82gjKR_.js (removed) 98 B 🟢 -98 B 🟢 -85 B 🟢 -82 B
assets/useFeatureFlags-DPPXLI2I.js (new) 98 B 🔴 +98 B 🔴 +85 B 🔴 +83 B
assets/useLoad3dViewer-CDMAzch4.js (new) 98 B 🔴 +98 B 🔴 +85 B 🔴 +93 B
assets/useLoad3dViewer-zMzffSpA.js (removed) 98 B 🟢 -98 B 🟢 -85 B 🟢 -81 B
assets/useCurrentUser-BY4P2qQu.js (removed) 94 B 🟢 -94 B 🟢 -95 B 🟢 -82 B
assets/useCurrentUser-Dw76b_-2.js (new) 94 B 🔴 +94 B 🔴 +95 B 🔴 +81 B

Status: 18 added / 18 removed / 20 unchanged

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

External libraries and shared vendor chunks

Status: 17 unchanged

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

Bundles that do not match a named category

File Before After Δ Raw Δ Gzip Δ Brotli
assets/core-Bn20eXtD.js (removed) 115 kB 🟢 -115 kB 🟢 -29.7 kB 🟢 -25.1 kB
assets/core-D70Zs27A.js (new) 115 kB 🔴 +115 kB 🔴 +29.7 kB 🔴 +25.1 kB
assets/WidgetSelect-CJjFqhdn.js (new) 88.8 kB 🔴 +88.8 kB 🔴 +20.1 kB 🔴 +17.2 kB
assets/WidgetSelect-O4c-FgWC.js (removed) 88.8 kB 🟢 -88.8 kB 🟢 -20.1 kB 🟢 -17.2 kB
assets/SubscriptionPanelContentWorkspace-Bw0iLN9B.js (removed) 80 kB 🟢 -80 kB 🟢 -15.8 kB 🟢 -13.6 kB
assets/SubscriptionPanelContentWorkspace-D74BICHT.js (new) 80 kB 🔴 +80 kB 🔴 +15.8 kB 🔴 +13.6 kB
assets/Load3D-BHPzyRgQ.js (removed) 71.3 kB 🟢 -71.3 kB 🟢 -11.7 kB 🟢 -9.98 kB
assets/Load3D-Bzmfqa3L.js (new) 71.3 kB 🔴 +71.3 kB 🔴 +11.7 kB 🔴 +9.97 kB
assets/WidgetVideoEdit-DQ_GlnE8.js (new) 67.5 kB 🔴 +67.5 kB 🔴 +15.7 kB 🔴 +13.9 kB
assets/WidgetVideoEdit-KSt3WGa9.js (removed) 67.5 kB 🟢 -67.5 kB 🟢 -15.7 kB 🟢 -13.9 kB
assets/SubscriptionTransitionPreviewWorkspace-Cd3w1guh.js (new) 66.4 kB 🔴 +66.4 kB 🔴 +13.4 kB 🔴 +11.7 kB
assets/SubscriptionTransitionPreviewWorkspace-CVAbAwUK.js (removed) 66.4 kB 🟢 -66.4 kB 🟢 -13.4 kB 🟢 -11.7 kB
assets/WorkspaceSettingsPanelContent-CUpONwO5.js (new) 58.2 kB 🔴 +58.2 kB 🔴 +12.4 kB 🔴 +10.8 kB
assets/WorkspaceSettingsPanelContent-CvUGmvfm.js (removed) 58.2 kB 🟢 -58.2 kB 🟢 -12.4 kB 🟢 -10.8 kB
assets/Preview3d-BAXSSJq_.js (new) 50.9 kB 🔴 +50.9 kB 🔴 +8.32 kB 🔴 +7.25 kB
assets/Preview3d-C-lLG_xz.js (removed) 50.9 kB 🟢 -50.9 kB 🟢 -8.32 kB 🟢 -7.25 kB
assets/main-CA5023mI.js (new) 45.1 kB 🔴 +45.1 kB 🔴 +13.1 kB 🔴 +11.4 kB
assets/main-YU24xlO0.js (removed) 45.1 kB 🟢 -45.1 kB 🟢 -13.1 kB 🟢 -11.4 kB
assets/SubscriptionRequiredDialogContentUnified-DGgvt6IO.js (removed) 42.6 kB 🟢 -42.6 kB 🟢 -9.36 kB 🟢 -8.16 kB
assets/SubscriptionRequiredDialogContentUnified-UFKmr0sG.js (new) 42.6 kB 🔴 +42.6 kB 🔴 +9.36 kB 🔴 +8.16 kB
assets/LayerEditorContent-CxjoYpK2.js (removed) 42.5 kB 🟢 -42.5 kB 🟢 -9.62 kB 🟢 -8.42 kB
assets/LayerEditorContent-hOTeZpQd.js (new) 42.5 kB 🔴 +42.5 kB 🔴 +9.62 kB 🔴 +8.42 kB
assets/WidgetBoundingBoxes-BFG1laQw.js (removed) 33.7 kB 🟢 -33.7 kB 🟢 -9.16 kB 🟢 -8.12 kB
assets/WidgetBoundingBoxes-CNEJDT1I.js (new) 33.7 kB 🔴 +33.7 kB 🔴 +9.16 kB 🔴 +8.12 kB
assets/WidgetPainter-BQBBWWfa.js (removed) 32.6 kB 🟢 -32.6 kB 🟢 -7.88 kB 🟢 -6.97 kB
assets/WidgetPainter-Cbgzu1Bg.js (new) 32.6 kB 🔴 +32.6 kB 🔴 +7.88 kB 🔴 +6.96 kB
assets/Load3dViewerContent-DoeVhZY6.js (removed) 30.8 kB 🟢 -30.8 kB 🟢 -6.29 kB 🟢 -5.45 kB
assets/Load3dViewerContent-sHtCk2XU.js (new) 30.8 kB 🔴 +30.8 kB 🔴 +6.29 kB 🔴 +5.46 kB
assets/SubscriptionRequiredDialogContent-CH3cyg8r.js (new) 26.9 kB 🔴 +26.9 kB 🔴 +6.36 kB 🔴 +5.6 kB
assets/SubscriptionRequiredDialogContent-DV9v6Bhb.js (removed) 26.9 kB 🟢 -26.9 kB 🟢 -6.36 kB 🟢 -5.6 kB
assets/SubscriptionRequiredDialogContentWorkspace-CBoiv4ek.js (removed) 25.1 kB 🟢 -25.1 kB 🟢 -5.79 kB 🟢 -5.11 kB
assets/SubscriptionRequiredDialogContentWorkspace-Dxh7v4jc.js (new) 25.1 kB 🔴 +25.1 kB 🔴 +5.78 kB 🔴 +5.1 kB
assets/CreditsTile-BjpfrJ95.js (removed) 24.9 kB 🟢 -24.9 kB 🟢 -6.65 kB 🟢 -5.84 kB
assets/CreditsTile-CYOz1Uhg.js (new) 24.9 kB 🔴 +24.9 kB 🔴 +6.65 kB 🔴 +5.85 kB
assets/load3d-BGQEvOaT.js (removed) 22.2 kB 🟢 -22.2 kB 🟢 -5.41 kB 🟢 -4.67 kB
assets/load3d-C6bS_aIi.js (new) 22.2 kB 🔴 +22.2 kB 🔴 +5.4 kB 🔴 +4.67 kB
assets/CurrentUserPopoverWorkspace-BZGGTDuG.js (removed) 21.5 kB 🟢 -21.5 kB 🟢 -4.84 kB 🟢 -4.31 kB
assets/CurrentUserPopoverWorkspace-CPE2kCoL.js (new) 21.5 kB 🔴 +21.5 kB 🔴 +4.84 kB 🔴 +4.31 kB
assets/SignInContent-D5DbxBrz.js (removed) 20.2 kB 🟢 -20.2 kB 🟢 -5.09 kB 🟢 -4.45 kB
assets/SignInContent-dP1_BE2m.js (new) 20.2 kB 🔴 +20.2 kB 🔴 +5.09 kB 🔴 +4.45 kB
assets/WidgetRecordAudio-BaTdClVC.js (new) 16.6 kB 🔴 +16.6 kB 🔴 +4.6 kB 🔴 +4.1 kB
assets/WidgetRecordAudio-BX7vvGto.js (removed) 16.6 kB 🟢 -16.6 kB 🟢 -4.6 kB 🟢 -4.1 kB
assets/WidgetInputNumber-CUaVb0JB.js (new) 13.9 kB 🔴 +13.9 kB 🔴 +3.63 kB 🔴 +3.21 kB
assets/WidgetInputNumber-DbWKrzS0.js (removed) 13.9 kB 🟢 -13.9 kB 🟢 -3.63 kB 🟢 -3.21 kB
assets/WidgetRange-CcV0WH2S.js (removed) 13.7 kB 🟢 -13.7 kB 🟢 -3.55 kB 🟢 -3.13 kB
assets/WidgetRange-DAYGYb7Y.js (new) 13.7 kB 🔴 +13.7 kB 🔴 +3.55 kB 🔴 +3.13 kB
assets/WaveAudioPlayer-DiN9RaDv.js (new) 12.8 kB 🔴 +12.8 kB 🔴 +3.46 kB 🔴 +3.05 kB
assets/WaveAudioPlayer-DM94kGJs.js (removed) 12.8 kB 🟢 -12.8 kB 🟢 -3.46 kB 🟢 -3.05 kB
assets/WidgetCurve-DjKB1juj.js (removed) 11.2 kB 🟢 -11.2 kB 🟢 -3.47 kB 🟢 -3.14 kB
assets/WidgetCurve-kwibiYK8.js (new) 11.2 kB 🔴 +11.2 kB 🔴 +3.47 kB 🔴 +3.14 kB
assets/TeamWorkspacesDialogContent-CTjWKesn.js (removed) 10.3 kB 🟢 -10.3 kB 🟢 -2.97 kB 🟢 -2.63 kB
assets/TeamWorkspacesDialogContent-rjVYkozN.js (new) 10.3 kB 🔴 +10.3 kB 🔴 +2.97 kB 🔴 +2.63 kB
assets/onboardingCloudRoutes-C0-fPJq2.js (removed) 9.33 kB 🟢 -9.33 kB 🟢 -2.83 kB 🟢 -2.43 kB
assets/onboardingCloudRoutes-Ds4qeNTn.js (new) 9.33 kB 🔴 +9.33 kB 🔴 +2.84 kB 🔴 +2.42 kB
assets/Load3DConfiguration-B9mJzkqt.js (removed) 8.91 kB 🟢 -8.91 kB 🟢 -2.61 kB 🟢 -2.29 kB
assets/Load3DConfiguration-DJLEbv3L.js (new) 8.91 kB 🔴 +8.91 kB 🔴 +2.61 kB 🔴 +2.29 kB
assets/WidgetImageCrop-BeQwU1uT.js (new) 8.49 kB 🔴 +8.49 kB 🔴 +2.66 kB 🔴 +2.33 kB
assets/WidgetImageCrop-dERZBIj-.js (removed) 8.49 kB 🟢 -8.49 kB 🟢 -2.65 kB 🟢 -2.37 kB
assets/SetMemberCreditLimitDialogContent-C1cManeX.js (new) 8.47 kB 🔴 +8.47 kB 🔴 +2.34 kB 🔴 +2.05 kB
assets/SetMemberCreditLimitDialogContent-Ce8iP6HA.js (removed) 8.47 kB 🟢 -8.47 kB 🟢 -2.34 kB 🟢 -2.04 kB
assets/nodeTemplates-CS-eSpb7.js (removed) 8.32 kB 🟢 -8.32 kB 🟢 -2.85 kB 🟢 -2.5 kB
assets/nodeTemplates-DrKECJDI.js (new) 8.32 kB 🔴 +8.32 kB 🔴 +2.85 kB 🔴 +2.51 kB
assets/NightlySurveyController-CkJl8mG8.js (removed) 7.5 kB 🟢 -7.5 kB 🟢 -2.56 kB 🟢 -2.25 kB
assets/NightlySurveyController-Cn7_jZxu.js (new) 7.5 kB 🔴 +7.5 kB 🔴 +2.55 kB 🔴 +2.25 kB
assets/CloudRunButtonWrapper-Bi8GzkIj.js (new) 6.84 kB 🔴 +6.84 kB 🔴 +2.18 kB 🔴 +1.91 kB
assets/CloudRunButtonWrapper-DOy9U6A7.js (removed) 6.84 kB 🟢 -6.84 kB 🟢 -2.18 kB 🟢 -1.91 kB
assets/WidgetWithControl-600TIroG.js (new) 6.41 kB 🔴 +6.41 kB 🔴 +2.61 kB 🔴 +2.29 kB
assets/WidgetWithControl-WoDhYMiN.js (removed) 6.41 kB 🟢 -6.41 kB 🟢 -2.61 kB 🟢 -2.32 kB
assets/missingModelMetadata-Bwj4DGLM.js (removed) 6.17 kB 🟢 -6.17 kB 🟢 -2.13 kB 🟢 -1.86 kB
assets/missingModelMetadata-uiNRGbun.js (new) 6.17 kB 🔴 +6.17 kB 🔴 +2.12 kB 🔴 +1.86 kB
assets/CancelSubscriptionDialogContent-B1vPSAXQ.js (removed) 5.97 kB 🟢 -5.97 kB 🟢 -1.98 kB 🟢 -1.75 kB
assets/CancelSubscriptionDialogContent-DUgEh74j.js (new) 5.97 kB 🔴 +5.97 kB 🔴 +1.98 kB 🔴 +1.75 kB
assets/load3dPreviewExtensions-Cq-JzcSY.js (removed) 5.88 kB 🟢 -5.88 kB 🟢 -1.81 kB 🟢 -1.6 kB
assets/load3dPreviewExtensions-D4-gsy83.js (new) 5.88 kB 🔴 +5.88 kB 🔴 +1.81 kB 🔴 +1.6 kB
assets/launchCancellationFlow-Bo-QNu-4.js (removed) 5.18 kB 🟢 -5.18 kB 🟢 -1.78 kB 🟢 -1.56 kB
assets/launchCancellationFlow-Cj9_FEYb.js (new) 5.18 kB 🔴 +5.18 kB 🔴 +1.78 kB 🔴 +1.56 kB
assets/CreateWorkspaceDialogContent-CG20G-46.js (removed) 5.12 kB 🟢 -5.12 kB 🟢 -1.79 kB 🟢 -1.55 kB
assets/CreateWorkspaceDialogContent-DxmZlYPi.js (new) 5.12 kB 🔴 +5.12 kB 🔴 +1.79 kB 🔴 +1.55 kB
assets/ChangeMemberRoleDialogContent-BI8kmOaH.js (new) 4.97 kB 🔴 +4.97 kB 🔴 +1.63 kB 🔴 +1.42 kB
assets/ChangeMemberRoleDialogContent-D1wQZ9Ao.js (removed) 4.97 kB 🟢 -4.97 kB 🟢 -1.64 kB 🟢 -1.42 kB
assets/InviteMemberDialogContent-_0IWHKbd.js (removed) 4.96 kB 🟢 -4.96 kB 🟢 -1.65 kB 🟢 -1.44 kB
assets/InviteMemberDialogContent-CpG1WdOY.js (new) 4.96 kB 🔴 +4.96 kB 🔴 +1.64 kB 🔴 +1.44 kB
assets/EditWorkspaceDialogContent-CgaVirBd.js (removed) 4.93 kB 🟢 -4.93 kB 🟢 -1.76 kB 🟢 -1.53 kB
assets/EditWorkspaceDialogContent-Djsu3n6u.js (new) 4.93 kB 🔴 +4.93 kB 🔴 +1.76 kB 🔴 +1.54 kB
assets/WidgetTextarea-mjjAV6LZ.js (removed) 4.81 kB 🟢 -4.81 kB 🟢 -1.87 kB 🟢 -1.64 kB
assets/WidgetTextarea-STBJ1EPH.js (new) 4.81 kB 🔴 +4.81 kB 🔴 +1.87 kB 🔴 +1.64 kB
assets/saveMesh-DIEp_2XE.js (new) 4.76 kB 🔴 +4.76 kB 🔴 +1.51 kB 🔴 +1.34 kB
assets/saveMesh-EcaUt2Tm.js (removed) 4.76 kB 🟢 -4.76 kB 🟢 -1.52 kB 🟢 -1.34 kB
assets/WorkspacePanelContent-Bo2Ud9-e.js (new) 4.74 kB 🔴 +4.74 kB 🔴 +1.63 kB 🔴 +1.44 kB
assets/WorkspacePanelContent-CEMA3Z-b.js (removed) 4.74 kB 🟢 -4.74 kB 🟢 -1.63 kB 🟢 -1.44 kB
assets/ValueControlPopover-cbHhfobV.js (new) 4.49 kB 🔴 +4.49 kB 🔴 +1.54 kB 🔴 +1.38 kB
assets/ValueControlPopover-DeJuJsiG.js (removed) 4.49 kB 🟢 -4.49 kB 🟢 -1.55 kB 🟢 -1.38 kB
assets/DeleteWorkspaceDialogContent-BRMn6e1Q.js (removed) 3.84 kB 🟢 -3.84 kB 🟢 -1.44 kB 🟢 -1.24 kB
assets/DeleteWorkspaceDialogContent-vPM5vu3n.js (new) 3.84 kB 🔴 +3.84 kB 🔴 +1.44 kB 🔴 +1.24 kB
assets/RemoveMemberDialogContent-BXDncwET.js (removed) 3.76 kB 🟢 -3.76 kB 🟢 -1.38 kB 🟢 -1.2 kB
assets/RemoveMemberDialogContent-HUz6IDYY.js (new) 3.76 kB 🔴 +3.76 kB 🔴 +1.38 kB 🔴 +1.2 kB
assets/RevokeInviteDialogContent-48d3Ba4C.js (removed) 3.67 kB 🟢 -3.67 kB 🟢 -1.39 kB 🟢 -1.22 kB
assets/RevokeInviteDialogContent-DyIFhiA6.js (new) 3.67 kB 🔴 +3.67 kB 🔴 +1.39 kB 🔴 +1.21 kB
assets/LeaveWorkspaceDialogContent-DrEv0u_1.js (new) 3.67 kB 🔴 +3.67 kB 🔴 +1.38 kB 🔴 +1.19 kB
assets/LeaveWorkspaceDialogContent-eodH3o5u.js (removed) 3.67 kB 🟢 -3.67 kB 🟢 -1.38 kB 🟢 -1.19 kB
assets/InviteMemberUpsellDialogContent-CEAWIQvj.js (removed) 3.47 kB 🟢 -3.47 kB 🟢 -1.24 kB 🟢 -1.09 kB
assets/InviteMemberUpsellDialogContent-CX9FUyFI.js (new) 3.47 kB 🔴 +3.47 kB 🔴 +1.24 kB 🔴 +1.09 kB
assets/workspaceCheckoutTelemetry-BY3IGaNL.js (new) 3.4 kB 🔴 +3.4 kB 🔴 +1.52 kB 🔴 +1.32 kB
assets/workspaceCheckoutTelemetry-BYKM9mew.js (removed) 3.4 kB 🟢 -3.4 kB 🟢 -1.52 kB 🟢 -1.32 kB
assets/GlobalToast-ChyD8nhq.js (new) 3.25 kB 🔴 +3.25 kB 🔴 +1.3 kB 🔴 +1.11 kB
assets/GlobalToast-DPYkAnDt.js (removed) 3.25 kB 🟢 -3.25 kB 🟢 -1.3 kB 🟢 -1.11 kB
assets/Media3DTop-5Cwbi2tl.js (new) 3.21 kB 🔴 +3.21 kB 🔴 +1.26 kB 🔴 +1.11 kB
assets/Media3DTop-DYaMVPKD.js (removed) 3.21 kB 🟢 -3.21 kB 🟢 -1.26 kB 🟢 -1.1 kB
assets/load3dAdvanced-BEgNql9z.js (new) 2.82 kB 🔴 +2.82 kB 🔴 +1.09 kB 🔴 +955 B
assets/load3dAdvanced-CTEcde2Y.js (removed) 2.82 kB 🟢 -2.82 kB 🟢 -1.1 kB 🟢 -956 B
assets/SubscribeToRun-CcZe1keo.js (removed) 2.39 kB 🟢 -2.39 kB 🟢 -1.03 kB 🟢 -905 B
assets/SubscribeToRun-DSNVxLGw.js (new) 2.39 kB 🔴 +2.39 kB 🔴 +1.03 kB 🔴 +906 B
assets/MediaAudioTop-B9N61XDF.js (new) 1.62 kB 🔴 +1.62 kB 🔴 +808 B 🔴 +669 B
assets/MediaAudioTop-CnGoLcYy.js (removed) 1.62 kB 🟢 -1.62 kB 🟢 -808 B 🟢 -673 B
assets/cloudSessionCookie-B9QaBF1a.js (new) 933 B 🔴 +933 B 🔴 +431 B 🔴 +379 B
assets/cloudSessionCookie-CRUVOVvR.js (removed) 933 B 🟢 -933 B 🟢 -433 B 🟢 -378 B
assets/cloudBadges-B3qYWcY3.js (removed) 922 B 🟢 -922 B 🟢 -514 B 🟢 -435 B
assets/cloudBadges-BWTPlU8l.js (new) 922 B 🔴 +922 B 🔴 +513 B 🔴 +459 B
assets/Load3DAdvanced-B1OLGR5_.js (removed) 761 B 🟢 -761 B 🟢 -423 B 🟢 -359 B
assets/Load3DAdvanced-DBeCjtr-.js (new) 761 B 🔴 +761 B 🔴 +423 B 🔴 +358 B
assets/nightlyBadges-BB01aHOj.js (removed) 411 B 🟢 -411 B 🟢 -273 B 🟢 -231 B
assets/nightlyBadges-z6G4h97L.js (new) 411 B 🔴 +411 B 🔴 +269 B 🔴 +230 B
assets/Load3dViewerContent-6DOO1RPY.js (removed) 137 B 🟢 -137 B 🟢 -103 B 🟢 -104 B
assets/Load3dViewerContent-D2415IRI.js (new) 137 B 🔴 +137 B 🔴 +103 B 🔴 +92 B
assets/missingModelMetadata-BS_aRbbT.js (new) 125 B 🔴 +125 B 🔴 +103 B 🔴 +102 B
assets/missingModelMetadata-C__0xRp5.js (removed) 125 B 🟢 -125 B 🟢 -103 B 🟢 -104 B
assets/Load3DAdvanced-BTdyBe8n.js (removed) 122 B 🟢 -122 B 🟢 -97 B 🟢 -84 B
assets/Load3DAdvanced-Cp2BmD8_.js (new) 122 B 🔴 +122 B 🔴 +97 B 🔴 +86 B
assets/WidgetLegacy-C32WMYkY.js (removed) 117 B 🟢 -117 B 🟢 -106 B 🟢 -104 B
assets/WidgetLegacy-CpAFyfig.js (new) 117 B 🔴 +117 B 🔴 +106 B 🔴 +102 B
assets/workflowDraftStoreV2-Bj2wKzlq.js (new) 112 B 🔴 +112 B 🔴 +101 B 🔴 +109 B
assets/workflowDraftStoreV2-CTtBougi.js (removed) 112 B 🟢 -112 B 🟢 -101 B 🟢 -108 B
assets/Load3D-BDiCZHNP.js (new) 98 B 🔴 +98 B 🔴 +89 B 🔴 +81 B
assets/Load3D-CGaMhRNz.js (removed) 98 B 🟢 -98 B 🟢 -89 B 🟢 -82 B
assets/changeTracker-BKvefSiR.js (new) 91 B 🔴 +91 B 🔴 +93 B 🔴 +89 B
assets/changeTracker-BqBh2t9e.js (removed) 91 B 🟢 -91 B 🟢 -93 B 🟢 -87 B

Status: 68 added / 68 removed / 217 unchanged

⚡ Performance Report

canvas-idle: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 67.2 MB heap
canvas-mouse-sweep: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 46.5 MB heap
canvas-zoom-sweep: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 69.9 MB heap
dom-widget-clipping: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 50.8 MB heap
large-graph-idle: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 64.2 MB heap
large-graph-pan: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 61.2 MB heap
large-graph-zoom: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 60.8 MB heap
minimap-idle: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 64.0 MB heap
subgraph-dom-widget-clipping: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 51.3 MB heap
subgraph-idle: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 67.3 MB heap
subgraph-mouse-sweep: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 51.2 MB heap
subgraph-transition-enter: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 128ms TBT · 71.2 MB heap
viewport-pan-sweep: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 65.9 MB heap
vue-large-graph-idle: · 55.4 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 167.3 MB heap
vue-large-graph-pan: · 55.4 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 555ms TBT · 158.8 MB heap
workflow-execution: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 65.5 MB heap

⚠️ 7 regressions detected

Show regressions
Metric Baseline PR (median) Δ Sig
canvas-idle: task duration 461ms 541ms +17% ⚠️ z=4.7
canvas-zoom-sweep: task duration 371ms 432ms +17% ⚠️ z=4.6
dom-widget-clipping: task duration 373ms 416ms +12% ⚠️ z=3.1
large-graph-idle: task duration 671ms 781ms +16% ⚠️ z=4.4
large-graph-pan: task duration 1284ms 1317ms +3% ⚠️ z=5.5
minimap-idle: task duration 659ms 725ms +10% ⚠️ z=4.2
subgraph-idle: task duration 459ms 515ms +12% ⚠️ z=4.6
All metrics
Metric Baseline PR (median) Δ Sig
canvas-idle: avg frame time 17ms 17ms +0% z=-0.1
canvas-idle: p95 frame time 17ms 17ms +0%
canvas-idle: layout duration 0ms 0ms +0%
canvas-idle: style recalc duration 8ms 7ms -4% z=-3.8
canvas-idle: layout count 0 0 +0%
canvas-idle: style recalc count 9 8 -11% z=-5.6
canvas-idle: task duration 461ms 541ms +17% ⚠️ z=4.7
canvas-idle: script duration 16ms 20ms +29% z=-2.1
canvas-idle: TBT 0ms 0ms +0%
canvas-idle: heap used 67.1 MB 67.2 MB +0%
canvas-idle: DOM nodes 18 16 -11% z=-5.2
canvas-idle: event listeners 4 4 +0% z=-1.6
canvas-mouse-sweep: avg frame time 17ms 17ms +0% z=-0.4
canvas-mouse-sweep: p95 frame time 17ms 17ms +0%
canvas-mouse-sweep: layout duration 3ms 4ms +14% z=0.0
canvas-mouse-sweep: style recalc duration 33ms 43ms +30% z=0.2
canvas-mouse-sweep: layout count 12 12 +0%
canvas-mouse-sweep: style recalc count 74 77 +3% z=-0.9
canvas-mouse-sweep: task duration 833ms 935ms +12% z=1.2
canvas-mouse-sweep: script duration 118ms 120ms +1% z=-2.5
canvas-mouse-sweep: TBT 0ms 0ms +0%
canvas-mouse-sweep: heap used 61.3 MB 46.5 MB -24%
canvas-mouse-sweep: DOM nodes 53 -138 -360% z=-77.4
canvas-mouse-sweep: event listeners 4 -153 -3925% z=-38.7
canvas-zoom-sweep: avg frame time 17ms 17ms -0% z=-0.3
canvas-zoom-sweep: p95 frame time 17ms 17ms +0%
canvas-zoom-sweep: layout duration 1ms 1ms +4% z=-0.1
canvas-zoom-sweep: style recalc duration 16ms 18ms +16% z=-0.4
canvas-zoom-sweep: layout count 6 6 +0%
canvas-zoom-sweep: style recalc count 32 32 -2% z=0.5
canvas-zoom-sweep: task duration 371ms 432ms +17% ⚠️ z=4.6
canvas-zoom-sweep: script duration 18ms 25ms +43% z=-0.6
canvas-zoom-sweep: TBT 0ms 0ms +0%
canvas-zoom-sweep: heap used 69.9 MB 69.9 MB +0%
canvas-zoom-sweep: DOM nodes 78 78 -1% z=-2.2
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 8ms 9ms +10% z=-1.3
dom-widget-clipping: layout count 0 0 +0%
dom-widget-clipping: style recalc count 11 11 +0% z=-4.2
dom-widget-clipping: task duration 373ms 416ms +12% ⚠️ z=3.1
dom-widget-clipping: script duration 59ms 63ms +8% z=-1.4
dom-widget-clipping: TBT 0ms 0ms +0%
dom-widget-clipping: heap used 50.7 MB 50.8 MB +0%
dom-widget-clipping: DOM nodes 18 18 +0% z=-2.9
dom-widget-clipping: event listeners 0 1 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 9ms 8ms -16% z=-4.5
large-graph-idle: layout count 0 0 +0%
large-graph-idle: style recalc count 10 9 -15% z=-9.9
large-graph-idle: task duration 671ms 781ms +16% ⚠️ z=4.4
large-graph-idle: script duration 102ms 118ms +16% z=1.5
large-graph-idle: TBT 0ms 0ms +0%
large-graph-idle: heap used 63.2 MB 64.2 MB +2%
large-graph-idle: DOM nodes -280 -281 +0% z=-338.2
large-graph-idle: event listeners -149 -147 -1% z=-28.4
large-graph-pan: avg frame time 17ms 17ms +0% z=0.3
large-graph-pan: p95 frame time 17ms 17ms +0%
large-graph-pan: layout duration 0ms 0ms +0%
large-graph-pan: style recalc duration 14ms 15ms +11% z=-2.3
large-graph-pan: layout count 0 0 +0%
large-graph-pan: style recalc count 69 69 -1% z=-1.7
large-graph-pan: task duration 1284ms 1317ms +3% ⚠️ z=5.5
large-graph-pan: script duration 455ms 418ms -8% z=0.5
large-graph-pan: TBT 0ms 0ms +0%
large-graph-pan: heap used 62.7 MB 61.2 MB -2%
large-graph-pan: DOM nodes -283 -283 +0% z=-183.0
large-graph-pan: event listeners -149 -149 +0% z=-185.9
large-graph-zoom: avg frame time 17ms 17ms +0%
large-graph-zoom: p95 frame time 17ms 17ms +1%
large-graph-zoom: layout duration 7ms 8ms +19%
large-graph-zoom: style recalc duration 13ms 16ms +19%
large-graph-zoom: layout count 60 60 +0%
large-graph-zoom: style recalc count 65 65 +0%
large-graph-zoom: task duration 1461ms 1615ms +11%
large-graph-zoom: script duration 519ms 547ms +5%
large-graph-zoom: TBT 0ms 0ms +0%
large-graph-zoom: heap used 56.6 MB 60.8 MB +7%
large-graph-zoom: DOM nodes -288 -289 +0%
large-graph-zoom: event listeners -153 -153 +0%
minimap-idle: avg frame time 17ms 17ms +0% z=0.1
minimap-idle: p95 frame time 17ms 17ms +0%
minimap-idle: layout duration 0ms 0ms +0%
minimap-idle: style recalc duration 7ms 6ms -16% z=-4.4
minimap-idle: layout count 0 0 +0%
minimap-idle: style recalc count 8 7 -13% z=-3.8
minimap-idle: task duration 659ms 725ms +10% ⚠️ z=4.2
minimap-idle: script duration 102ms 109ms +7% z=1.1
minimap-idle: TBT 0ms 0ms +0%
minimap-idle: heap used 64.8 MB 64.0 MB -1%
minimap-idle: DOM nodes -282 -282 +0% z=-220.2
minimap-idle: event listeners -147 -164 +12% z=-255.3
subgraph-dom-widget-clipping: avg frame time 17ms 17ms -0% z=-0.4
subgraph-dom-widget-clipping: p95 frame time 17ms 17ms -0%
subgraph-dom-widget-clipping: layout duration 0ms 0ms +0%
subgraph-dom-widget-clipping: style recalc duration 9ms 11ms +11% z=-2.3
subgraph-dom-widget-clipping: layout count 0 0 +0%
subgraph-dom-widget-clipping: style recalc count 46 47 +2% z=-1.6
subgraph-dom-widget-clipping: task duration 391ms 413ms +5% z=1.9
subgraph-dom-widget-clipping: script duration 119ms 123ms +4% z=-0.8
subgraph-dom-widget-clipping: TBT 0ms 0ms +0%
subgraph-dom-widget-clipping: heap used 51.4 MB 51.3 MB -0%
subgraph-dom-widget-clipping: DOM nodes 18 20 +11% z=-1.9
subgraph-dom-widget-clipping: event listeners 6 6 +0% z=-1.7
subgraph-idle: avg frame time 17ms 17ms +0% z=-0.2
subgraph-idle: p95 frame time 17ms 17ms +1%
subgraph-idle: layout duration 0ms 0ms +0%
subgraph-idle: style recalc duration 8ms 8ms +7% z=-2.6
subgraph-idle: layout count 0 0 +0%
subgraph-idle: style recalc count 10 10 -5% z=-2.1
subgraph-idle: task duration 459ms 515ms +12% ⚠️ z=4.6
subgraph-idle: script duration 13ms 19ms +44% z=-0.4
subgraph-idle: TBT 0ms 0ms +0%
subgraph-idle: heap used 67.3 MB 67.3 MB +0%
subgraph-idle: DOM nodes 20 19 -5% z=-1.9
subgraph-idle: event listeners 4 4 +0% variance too high
subgraph-mouse-sweep: avg frame time 17ms 17ms +0% z=0.8
subgraph-mouse-sweep: p95 frame time 17ms 17ms +1%
subgraph-mouse-sweep: layout duration 4ms 5ms +22% z=-0.4
subgraph-mouse-sweep: style recalc duration 34ms 39ms +14% z=-1.1
subgraph-mouse-sweep: layout count 16 16 +0%
subgraph-mouse-sweep: style recalc count 77 79 +2% z=-0.8
subgraph-mouse-sweep: task duration 778ms 845ms +8% z=1.1
subgraph-mouse-sweep: script duration 86ms 99ms +16% z=-0.3
subgraph-mouse-sweep: TBT 0ms 0ms +0%
subgraph-mouse-sweep: heap used 44.0 MB 51.2 MB +16%
subgraph-mouse-sweep: DOM nodes -278 -107 -62% z=-77.6
subgraph-mouse-sweep: event listeners -153 -74 -52% 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 12ms 11ms -8%
subgraph-transition-enter: style recalc duration 30ms 29ms -3%
subgraph-transition-enter: layout count 13 15 +15%
subgraph-transition-enter: style recalc count 17 19 +12%
subgraph-transition-enter: task duration 907ms 968ms +7%
subgraph-transition-enter: script duration 34ms 32ms -7%
subgraph-transition-enter: TBT 137ms 128ms -7%
subgraph-transition-enter: heap used 95.3 MB 71.2 MB -25%
subgraph-transition-enter: DOM nodes 13673 13673 +0%
subgraph-transition-enter: event listeners 2375 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 36ms 43ms +22%
viewport-pan-sweep: layout count 0 0 +0%
viewport-pan-sweep: style recalc count 250 251 +0%
viewport-pan-sweep: task duration 4377ms 4589ms +5%
viewport-pan-sweep: script duration 1368ms 1359ms -1%
viewport-pan-sweep: TBT 0ms 0ms +0%
viewport-pan-sweep: heap used 68.4 MB 65.9 MB -4%
viewport-pan-sweep: DOM nodes -281 -280 -0%
viewport-pan-sweep: event listeners -133 -148 +11%
vue-large-graph-idle: avg frame time 18ms 18ms +2%
vue-large-graph-idle: p95 frame time 17ms 17ms -0%
vue-large-graph-idle: layout duration 0ms 0ms +0%
vue-large-graph-idle: style recalc duration 0ms 0ms +0%
vue-large-graph-idle: layout count 0 0 +0%
vue-large-graph-idle: style recalc count 0 0 +0%
vue-large-graph-idle: task duration 16103ms 18009ms +12%
vue-large-graph-idle: script duration 502ms 578ms +15%
vue-large-graph-idle: TBT 0ms 0ms +0%
vue-large-graph-idle: heap used 158.2 MB 167.3 MB +6%
vue-large-graph-idle: DOM nodes -8312 -8314 +0%
vue-large-graph-idle: event listeners -16387 -16386 -0%
vue-large-graph-pan: avg frame time 18ms 18ms +2%
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 16ms 24ms +55%
vue-large-graph-pan: layout count 0 0 +0%
vue-large-graph-pan: style recalc count 141 163 +16%
vue-large-graph-pan: task duration 20321ms 22755ms +12%
vue-large-graph-pan: script duration 881ms 924ms +5%
vue-large-graph-pan: TBT 156ms 555ms +255%
vue-large-graph-pan: heap used 161.3 MB 158.8 MB -2%
vue-large-graph-pan: DOM nodes -8312 -8312 +0%
vue-large-graph-pan: event listeners -16385 -16379 -0%
workflow-execution: avg frame time 17ms 17ms +0% z=0.6
workflow-execution: p95 frame time 17ms 17ms +0%
workflow-execution: layout duration 1ms 1ms +8% z=-3.4
workflow-execution: style recalc duration 21ms 21ms +1% z=-1.4
workflow-execution: layout count 3 4 +17% z=-2.7
workflow-execution: style recalc count 14 14 -4% z=-2.1
workflow-execution: task duration 109ms 115ms +6% z=-0.7
workflow-execution: script duration 9ms 10ms +8% z=-6.4
workflow-execution: TBT 0ms 0ms +0%
workflow-execution: heap used 65.5 MB 65.5 MB +0%
workflow-execution: DOM nodes 123 124 +0% z=-5.2
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-13T00:49:21.968Z",
  "gitSha": "e4ae50204d11717162a4c3b58ed1af5bab97be81",
  "branch": "drjkl/tab-fallback-deferred-scans",
  "measurements": [
    {
      "name": "canvas-idle",
      "durationMs": 2050.0380000000005,
      "styleRecalcs": 8,
      "styleRecalcDurationMs": 7.591000000000002,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 565.1890000000001,
      "heapDeltaBytes": 5962584,
      "heapUsedBytes": 70656148,
      "domNodes": 16,
      "jsHeapTotalBytes": 24117248,
      "scriptDurationMs": 21.833,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "canvas-idle",
      "durationMs": 2020.483000000013,
      "styleRecalcs": 8,
      "styleRecalcDurationMs": 7.2959999999999985,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 516.7570000000001,
      "heapDeltaBytes": 5826096,
      "heapUsedBytes": 70191228,
      "domNodes": 16,
      "jsHeapTotalBytes": 24641536,
      "scriptDurationMs": 19.123,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 1864.0579999999716,
      "styleRecalcs": 76,
      "styleRecalcDurationMs": 46.187,
      "layouts": 12,
      "layoutDurationMs": 3.6270000000000002,
      "taskDurationMs": 950.379,
      "heapDeltaBytes": -13037868,
      "heapUsedBytes": 51330048,
      "domNodes": -281,
      "jsHeapTotalBytes": 24088576,
      "scriptDurationMs": 122.911,
      "eventListeners": -153,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 1881.4709999999195,
      "styleRecalcs": 77,
      "styleRecalcDurationMs": 40.413,
      "layouts": 12,
      "layoutDurationMs": 3.597,
      "taskDurationMs": 919.265,
      "heapDeltaBytes": -18579312,
      "heapUsedBytes": 46138144,
      "domNodes": 5,
      "jsHeapTotalBytes": 23302144,
      "scriptDurationMs": 116.235,
      "eventListeners": -153,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "canvas-zoom-sweep",
      "durationMs": 1729.379999999992,
      "styleRecalcs": 31,
      "styleRecalcDurationMs": 17.395,
      "layouts": 6,
      "layoutDurationMs": 0.5870000000000001,
      "taskDurationMs": 417.133,
      "heapDeltaBytes": 8765540,
      "heapUsedBytes": 73363396,
      "domNodes": 77,
      "jsHeapTotalBytes": 24641536,
      "scriptDurationMs": 24.573000000000004,
      "eventListeners": 19,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "canvas-zoom-sweep",
      "durationMs": 1755.359999999996,
      "styleRecalcs": 32,
      "styleRecalcDurationMs": 19.577000000000005,
      "layouts": 6,
      "layoutDurationMs": 0.6869999999999999,
      "taskDurationMs": 447.399,
      "heapDeltaBytes": 8820260,
      "heapUsedBytes": 73239240,
      "domNodes": 78,
      "jsHeapTotalBytes": 24379392,
      "scriptDurationMs": 25.921,
      "eventListeners": 19,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 657.9360000000065,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 8.987000000000002,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 424.962,
      "heapDeltaBytes": -11367752,
      "heapUsedBytes": 53193548,
      "domNodes": 18,
      "jsHeapTotalBytes": 25165824,
      "scriptDurationMs": 61.959999999999994,
      "eventListeners": 2,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 624.3009999999458,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 8.777,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 407.49099999999993,
      "heapDeltaBytes": -11150680,
      "heapUsedBytes": 53238360,
      "domNodes": 18,
      "jsHeapTotalBytes": 25427968,
      "scriptDurationMs": 64.813,
      "eventListeners": 0,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.699999999999818
    },
    {
      "name": "large-graph-idle",
      "durationMs": 2052.558000000033,
      "styleRecalcs": 8,
      "styleRecalcDurationMs": 6.608,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 763.6959999999999,
      "heapDeltaBytes": 7316644,
      "heapUsedBytes": 67343184,
      "domNodes": -281,
      "jsHeapTotalBytes": 2981888,
      "scriptDurationMs": 112.626,
      "eventListeners": -147,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-idle",
      "durationMs": 2065.7209999999395,
      "styleRecalcs": 9,
      "styleRecalcDurationMs": 8.435000000000002,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 797.6020000000001,
      "heapDeltaBytes": 7633932,
      "heapUsedBytes": 67300396,
      "domNodes": -281,
      "jsHeapTotalBytes": 2195456,
      "scriptDurationMs": 124.03999999999998,
      "eventListeners": -147,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "large-graph-pan",
      "durationMs": 2187.7700000000004,
      "styleRecalcs": 69,
      "styleRecalcDurationMs": 15.651999999999996,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1324.7050000000002,
      "heapDeltaBytes": 4825788,
      "heapUsedBytes": 65766652,
      "domNodes": -283,
      "jsHeapTotalBytes": 3436544,
      "scriptDurationMs": 412.136,
      "eventListeners": -147,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-pan",
      "durationMs": 2151.0079999999334,
      "styleRecalcs": 68,
      "styleRecalcDurationMs": 15.313999999999998,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1308.702,
      "heapDeltaBytes": 1508464,
      "heapUsedBytes": 62651508,
      "domNodes": -283,
      "jsHeapTotalBytes": 4485120,
      "scriptDurationMs": 424.60900000000004,
      "eventListeners": -151,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-zoom",
      "durationMs": 3247.544000000005,
      "styleRecalcs": 65,
      "styleRecalcDurationMs": 15.468,
      "layouts": 60,
      "layoutDurationMs": 8.283999999999999,
      "taskDurationMs": 1609.543,
      "heapDeltaBytes": 795368,
      "heapUsedBytes": 62994728,
      "domNodes": -288,
      "jsHeapTotalBytes": 6127616,
      "scriptDurationMs": 536.356,
      "eventListeners": -153,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "large-graph-zoom",
      "durationMs": 3298.2170000000224,
      "styleRecalcs": 65,
      "styleRecalcDurationMs": 15.861,
      "layouts": 60,
      "layoutDurationMs": 8.424999999999999,
      "taskDurationMs": 1621.0640000000003,
      "heapDeltaBytes": 1394768,
      "heapUsedBytes": 64498496,
      "domNodes": -290,
      "jsHeapTotalBytes": 6127616,
      "scriptDurationMs": 556.9960000000001,
      "eventListeners": -153,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "minimap-idle",
      "durationMs": 2031.6170000000398,
      "styleRecalcs": 6,
      "styleRecalcDurationMs": 5.516,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 747.7570000000001,
      "heapDeltaBytes": 5283108,
      "heapUsedBytes": 66631448,
      "domNodes": -283,
      "jsHeapTotalBytes": 3768320,
      "scriptDurationMs": 114.199,
      "eventListeners": -181,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "minimap-idle",
      "durationMs": 2034.3930000000228,
      "styleRecalcs": 8,
      "styleRecalcDurationMs": 6.422999999999998,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 703.1930000000001,
      "heapDeltaBytes": 7072412,
      "heapUsedBytes": 67575580,
      "domNodes": -281,
      "jsHeapTotalBytes": 3506176,
      "scriptDurationMs": 104.552,
      "eventListeners": -147,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 620.2299999999923,
      "styleRecalcs": 47,
      "styleRecalcDurationMs": 10.774000000000001,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 415.845,
      "heapDeltaBytes": -10682824,
      "heapUsedBytes": 53836184,
      "domNodes": 20,
      "jsHeapTotalBytes": 25690112,
      "scriptDurationMs": 125.79499999999999,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 577.6779999999917,
      "styleRecalcs": 47,
      "styleRecalcDurationMs": 10.318,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 409.2,
      "heapDeltaBytes": -10711668,
      "heapUsedBytes": 53826100,
      "domNodes": 20,
      "jsHeapTotalBytes": 25952256,
      "scriptDurationMs": 120.6,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "subgraph-idle",
      "durationMs": 2010.183999999981,
      "styleRecalcs": 9,
      "styleRecalcDurationMs": 8.156,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 528.9849999999999,
      "heapDeltaBytes": 5851416,
      "heapUsedBytes": 70568204,
      "domNodes": 18,
      "jsHeapTotalBytes": 24903680,
      "scriptDurationMs": 20.741,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "subgraph-idle",
      "durationMs": 2009.27200000001,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 8.627,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 501.239,
      "heapDeltaBytes": 5938272,
      "heapUsedBytes": 70546756,
      "domNodes": 20,
      "jsHeapTotalBytes": 24641536,
      "scriptDurationMs": 17.619,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1827.1819999999934,
      "styleRecalcs": 79,
      "styleRecalcDurationMs": 38.637,
      "layouts": 16,
      "layoutDurationMs": 4.656000000000001,
      "taskDurationMs": 911.8779999999999,
      "heapDeltaBytes": -18236140,
      "heapUsedBytes": 46549524,
      "domNodes": -280,
      "jsHeapTotalBytes": 23040000,
      "scriptDurationMs": 103.80799999999999,
      "eventListeners": -151,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1705.2479999999832,
      "styleRecalcs": 78,
      "styleRecalcDurationMs": 38.812,
      "layouts": 16,
      "layoutDurationMs": 4.513999999999999,
      "taskDurationMs": 777.138,
      "heapDeltaBytes": -3796372,
      "heapUsedBytes": 60769764,
      "domNodes": 67,
      "jsHeapTotalBytes": 24641536,
      "scriptDurationMs": 94.32099999999998,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66999999999998,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "subgraph-transition-enter",
      "durationMs": 1384.016000000031,
      "styleRecalcs": 19,
      "styleRecalcDurationMs": 28.817999999999998,
      "layouts": 15,
      "layoutDurationMs": 11.336000000000002,
      "taskDurationMs": 967.8690000000001,
      "heapDeltaBytes": 3757508,
      "heapUsedBytes": 74667352,
      "domNodes": 13673,
      "jsHeapTotalBytes": 14680064,
      "scriptDurationMs": 31.67299999999999,
      "eventListeners": 2375,
      "totalBlockingTimeMs": 128,
      "frameDurationMs": 16.66999999999998,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "viewport-pan-sweep",
      "durationMs": 8356.683000000032,
      "styleRecalcs": 251,
      "styleRecalcDurationMs": 43.446,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 4689.469,
      "heapDeltaBytes": 10897400,
      "heapUsedBytes": 70431088,
      "domNodes": -281,
      "jsHeapTotalBytes": 5795840,
      "scriptDurationMs": 1386.832,
      "eventListeners": -163,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "viewport-pan-sweep",
      "durationMs": 8319.928000000004,
      "styleRecalcs": 251,
      "styleRecalcDurationMs": 43.037,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 4488.556,
      "heapDeltaBytes": 8847864,
      "heapUsedBytes": 67799208,
      "domNodes": -279,
      "jsHeapTotalBytes": 4747264,
      "scriptDurationMs": 1331.479,
      "eventListeners": -133,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "vue-large-graph-idle",
      "durationMs": 18054.281000000003,
      "styleRecalcs": 0,
      "styleRecalcDurationMs": 0,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 18011.501,
      "heapDeltaBytes": -26823000,
      "heapUsedBytes": 183799680,
      "domNodes": -8312,
      "jsHeapTotalBytes": -9904128,
      "scriptDurationMs": 579.909,
      "eventListeners": -16385,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 18.333333333333332,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "vue-large-graph-idle",
      "durationMs": 18043.137000000002,
      "styleRecalcs": 0,
      "styleRecalcDurationMs": 0,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 18005.619,
      "heapDeltaBytes": -61968124,
      "heapUsedBytes": 167012320,
      "domNodes": -8316,
      "jsHeapTotalBytes": -7282688,
      "scriptDurationMs": 576.138,
      "eventListeners": -16387,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 17.77333333333336,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "vue-large-graph-pan",
      "durationMs": 22204.08500000002,
      "styleRecalcs": 154,
      "styleRecalcDurationMs": 22.48500000000003,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 22165.706,
      "heapDeltaBytes": -31383940,
      "heapUsedBytes": 166523336,
      "domNodes": -8312,
      "jsHeapTotalBytes": -9117696,
      "scriptDurationMs": 922.5669999999999,
      "eventListeners": -16379,
      "totalBlockingTimeMs": 471,
      "frameDurationMs": 18.333333333333332,
      "p95FrameDurationMs": 16.80000000000291
    },
    {
      "name": "vue-large-graph-pan",
      "durationMs": 23399.963999999956,
      "styleRecalcs": 172,
      "styleRecalcDurationMs": 26.156000000000013,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 23343.738,
      "heapDeltaBytes": -59949548,
      "heapUsedBytes": 166597676,
      "domNodes": -8312,
      "jsHeapTotalBytes": -10166272,
      "scriptDurationMs": 925.475,
      "eventListeners": -16379,
      "totalBlockingTimeMs": 638,
      "frameDurationMs": 17.776666666666642,
      "p95FrameDurationMs": 16.80000000000291
    },
    {
      "name": "workflow-execution",
      "durationMs": 456.07100000000855,
      "styleRecalcs": 16,
      "styleRecalcDurationMs": 23.791,
      "layouts": 4,
      "layoutDurationMs": 1.449,
      "taskDurationMs": 124.14200000000001,
      "heapDeltaBytes": 5190236,
      "heapUsedBytes": 68690984,
      "domNodes": 128,
      "jsHeapTotalBytes": 5242880,
      "scriptDurationMs": 11.060999999999998,
      "eventListeners": 99,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "workflow-execution",
      "durationMs": 467.79400000002624,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 18.449,
      "layouts": 3,
      "layoutDurationMs": 0.6509999999999999,
      "taskDurationMs": 105.73,
      "heapDeltaBytes": 5047756,
      "heapUsedBytes": 68589464,
      "domNodes": 119,
      "jsHeapTotalBytes": 5242880,
      "scriptDurationMs": 9.052999999999999,
      "eventListeners": 97,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    }
  ]
}

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6d5a0061-7030-437f-a20b-b043008ad98b

📥 Commits

Reviewing files that changed from the base of the PR and between a3add80 and 419f5c2.

📒 Files selected for processing (1)
  • src/composables/graph/useErrorClearingHooks.ts

📝 Walkthrough

Walkthrough

Added-node error scans now track pending asynchronous verification per graph and execution ID. Scan cancellation suppresses stale results after node removal, graph changes, or hook disposal. RightSidePanel uses pending scan state when validating the Errors tab.

Changes

Execution-error scan lifecycle

Layer / File(s) Summary
Pending scan state contract
src/stores/executionErrorStore.ts, src/stores/executionErrorStore.test.ts
The store tracks overlapping scans per graph and execution ID. It exposes functions to begin scans, finish scans, and query pending state.
Abortable added-node scans
src/composables/graph/useErrorClearingHooks.ts
Added-node model and media verification use tracked asynchronous scans. Node removal and hook disposal cancel pending work and suppress late results.
Scan lifecycle validation
src/composables/graph/useErrorClearingHooks.test.ts
Tests cover pending verification, deferred failures, cancellation, stale results, node removal, disposed callbacks, and cloud model verification.
Pending-scan tab availability
src/components/rightSidePanel/RightSidePanel.vue, src/components/rightSidePanel/RightSidePanel.test.ts, browser_tests/tests/propertiesPanel/errorsTabCloudMissingModels.spec.ts
RightSidePanel checks selected-node execution IDs and keeps the Errors tab available during relevant pending scans. Tests cover clean scans, detected errors, pending scans, subgraph nodes, unmounting, and cloud model verification.

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

Mergeability Score: 🔵 Low · up to 419f5

The PR improves retention and fallback of the Errors tab during deferred node checks, but two regression tests may not reliably exercise the abort and pending-scan states they are intended to protect. The change is mergeable with explicit owner awareness and follow-up to strengthen those assertions.

Sequence Diagram(s)

sequenceDiagram
  participant Graph
  participant useErrorClearingHooks
  participant executionErrorStore
  participant RightSidePanel
  Graph->>useErrorClearingHooks: add node
  useErrorClearingHooks->>executionErrorStore: begin pending scan
  useErrorClearingHooks-->>executionErrorStore: finish scan after verification
  RightSidePanel->>executionErrorStore: query selected-node scan state
  executionErrorStore-->>RightSidePanel: return pending status
  RightSidePanel-->>Graph: retain or fallback active tab
Loading

Possibly related PRs

Suggested labels: risk:R3

Suggested reviewers: christian-byrne

🚥 Pre-merge checks | ✅ 6
✅ Passed checks (6 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the primary fix for tab fallback after deferred error scans.
Description check ✅ Passed The description clearly explains the issue, implementation, edge cases, and testing, although it omits some template headings.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
End-To-End Regression Coverage For Fixes ✅ Passed The PR changes frontend files and also adds browser_tests/tests/propertiesPanel/errorsTabCloudMissingModels.spec.ts, so the missing end-to-end coverage condition is false.
Adr Compliance For Entity/Litegraph Changes ✅ Passed Graph-related files changed, but the available PR metadata shows store/composable scan coordination only; it identifies no spatial mutation, entity method growth, ECS method, or extension callback...
✨ 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 drjkl/tab-fallback-deferred-scans

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

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

Inline comments:
In `@src/components/rightSidePanel/RightSidePanel.vue`:
- Around line 235-241: Add focused regression tests for the RightSidePanel
watchEffect behavior: verify an active errors tab restored before the queued
microtask remains selected, an unavailable active tab selects tabs.value[0], and
unmounting before the microtask prevents rightSidePanelStore.openPanel from
being called.
- Around line 235-241: Update the watchEffect around isActiveTabAvailable to
accept onCleanup and invalidate the queued microtask during cleanup, preventing
rightSidePanelStore.openPanel from running after unmount or a watcher rerun. Add
focused tests covering both cleanup paths: component unmount and watcher rerun
before the microtask executes.
🪄 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: 635bc502-d036-40a2-ab60-0839abd458dd

📥 Commits

Reviewing files that changed from the base of the PR and between 29f1610 and 91e8a19.

📒 Files selected for processing (1)
  • src/components/rightSidePanel/RightSidePanel.vue

Comment on lines +235 to +241
watchEffect(() => {
if (
!tabs.value.some((tab) => tab.value === activeTab.value) &&
!(activeTab.value === 'subgraph' && isSingleSubgraphNode.value)
) {
rightSidePanelStore.openPanel(tabs.value[0].value)
}
if (isActiveTabAvailable()) return
queueMicrotask(() => {
if (!isActiveTabAvailable()) {
rightSidePanelStore.openPanel(tabs.value[0].value)
}
})

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.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add focused component regression tests.

Test both timing outcomes:

  • Restore the active 'errors' tab before the microtask and verify that it remains active.
  • Keep the active tab unavailable and verify that the first available tab is selected.
  • Verify that unmounting before the microtask does not call openPanel.

As per coding guidelines, tests are required for Vue code changes, especially bug fixes.

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

In `@src/components/rightSidePanel/RightSidePanel.vue` around lines 235 - 241, Add
focused regression tests for the RightSidePanel watchEffect behavior: verify an
active errors tab restored before the queued microtask remains selected, an
unavailable active tab selects tabs.value[0], and unmounting before the
microtask prevents rightSidePanelStore.openPanel from being called.

Source: Coding guidelines


🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 10 --glob '*.{ts,tsx,vue}' \
  'queueMicrotask|watchEffect|onCleanup|onScopeDispose|onBeforeUnmount|closePanel|unmount' \
  src

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 50385


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- RightSidePanel.vue ---'
sed -n '1,280p' src/components/rightSidePanel/RightSidePanel.vue

printf '%s\n' '--- Related tests and references ---'
rg -n --glob '*.{ts,tsx,vue}' \
  'RightSidePanel|isActiveTabAvailable|openPanel\(tabs|activeTab|rightSidePanelStore' \
  src tests 2>/dev/null | head -n 300

printf '%s\n' '--- Guidance ---'
sed -n '1,220p' docs/guidance/vue-components.md
sed -n '1,160p' docs/guidance/typescript.md

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 34334


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- Store implementation ---'
cat -n src/stores/workspace/rightSidePanelStore.ts

printf '%s\n' '--- Component lifecycle and template ---'
sed -n '280,430p' src/components/rightSidePanel/RightSidePanel.vue

printf '%s\n' '--- Right-side-panel files and tests ---'
git ls-files '*RightSidePanel*' '*rightSidePanel*' | sort

printf '%s\n' '--- Exact cleanup/deferred-work patterns ---'
rg -n -C 6 --glob '*.{ts,tsx,vue}' \
  'watchEffect\s*\(\s*\(onCleanup|queueMicrotask\s*\(' \
  src/components src/composables src/stores | head -n 240

printf '%s\n' '--- Repository status and relevant diff summary ---'
git status --short
git diff --stat -- src/components/rightSidePanel/RightSidePanel.vue

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 21380


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- RightSidePanel mount condition ---'
rg -n -C 12 'NodePropertiesPanel|RightSidePanel' src/components/graph/GraphCanvas.vue src --glob '*.vue' | head -n 220

printf '%s\n' '--- Existing browser coverage ---'
sed -n '1,260p' browser_tests/tests/rightSidePanelTabs.spec.ts

printf '%s\n' '--- All active-tab writes ---'
rg -n -C 5 --glob '*.{ts,tsx,vue}' \
  'activeTab(?:\.value)?\s*=|openPanel\(' \
  src/stores/workspace/rightSidePanelStore.ts \
  src/components/rightSidePanel \
  src/composables/graph \
  src/services/litegraphService.ts \
  src/composables/useCoreCommands.ts | head -n 320

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 36596


🏁 Script executed:

#!/bin/bash
set -euo pipefail

node - <<'JS'
const events = []

function scheduleFallback(withCleanup, cleanupBeforeRun) {
  let cancelled = false
  const cleanup = () => {
    cancelled = true
  }

  queueMicrotask(() => {
    if (!cancelled) events.push(withCleanup ? 'open-with-cleanup' : 'open')
  })

  if (cleanupBeforeRun) cleanup()
}

scheduleFallback(false, true)
scheduleFallback(true, true)
scheduleFallback(true, false)

queueMicrotask(() => {
  console.log(JSON.stringify(events))
})
JS

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 186


Cancel the deferred fallback during watcher cleanup.

If the component unmounts or the watcher reruns before the microtask runs, the callback can still call rightSidePanelStore.openPanel(...). Pass onCleanup to watchEffect, invalidate the callback during cleanup, and add focused tests for both paths.

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

In `@src/components/rightSidePanel/RightSidePanel.vue` around lines 235 - 241,
Update the watchEffect around isActiveTabAvailable to accept onCleanup and
invalidate the queued microtask during cleanup, preventing
rightSidePanelStore.openPanel from running after unmount or a watcher rerun. Add
focused tests covering both cleanup paths: component unmount and watcher rerun
before the microtask executes.

Source: Path instructions

@codecov

codecov Bot commented Aug 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.93939% with 8 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/composables/graph/useErrorClearingHooks.ts 91.83% 7 Missing and 1 partial ⚠️
@@            Coverage Diff             @@
##             main   #15012      +/-   ##
==========================================
+ Coverage   81.11%   81.15%   +0.04%     
==========================================
  Files        1882     1882              
  Lines      108707   107519    -1188     
  Branches    34153    33205     -948     
==========================================
- Hits        88180    87262     -918     
+ Misses      20174    19873     -301     
- Partials      353      384      +31     
Flag Coverage Δ
unit 72.72% <93.93%> (+0.17%) ⬆️

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

Files with missing lines Coverage Δ
src/components/rightSidePanel/RightSidePanel.vue 90.95% <100.00%> (+6.09%) ⬆️
src/stores/executionErrorStore.ts 96.00% <100.00%> (+4.45%) ⬆️
src/composables/graph/useErrorClearingHooks.ts 95.98% <91.83%> (+1.96%) ⬆️

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

@DrJKL

DrJKL commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

One microtask does not cover all deferred error scans. The fallback in RightSidePanel.vue rechecks after one microtask, but missing-media detection intentionally runs after two microtasks in useErrorClearingHooks.ts (scheduleAddedNodeScan). Cloud model/media candidates also register only after asynchronous verification.

Consequently, pasting a node with missing media—or a cloud-verified missing model—can still switch away from Errors before the error is registered, reproducing the behavior this PR aims to fix. The fallback should coordinate with scan completion rather than assume one microtask is sufficient. A timing-focused regression test should cover this.

Comment thread src/components/rightSidePanel/RightSidePanel.vue Outdated
@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. and removed size:S This PR changes 10-29 lines, ignoring generated files. labels 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: 2

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

Inline comments:
In `@src/components/rightSidePanel/RightSidePanel.test.ts`:
- Around line 118-170: Add a Playwright regression test under browser_tests/
using the documented workflow that performs node addition, selection changes,
deferred model and media verification, and tab fallback in one real browser
flow. Assert that the Errors tab remains active while verification is pending
and remains active when verification surfaces an error, then validate the
expected fallback behavior after the scan settles without errors.
- Around line 125-129: Update the RightSidePanel test to use Vue’s nextTick()
instead of Promise.resolve() when waiting for reactivity at
src/components/rightSidePanel/RightSidePanel.test.ts:125-129. In the
pending-scan unmount case at
src/components/rightSidePanel/RightSidePanel.test.ts:161-169, call unmount()
before finishScan() so completion occurs after unmount.
🪄 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: a1a04273-338a-45d2-98cd-8dc46e1f0132

📥 Commits

Reviewing files that changed from the base of the PR and between b56e4b0 and f2e8b2a.

📒 Files selected for processing (6)
  • src/components/rightSidePanel/RightSidePanel.test.ts
  • src/components/rightSidePanel/RightSidePanel.vue
  • src/composables/graph/useErrorClearingHooks.test.ts
  • src/composables/graph/useErrorClearingHooks.ts
  • src/stores/executionErrorStore.test.ts
  • src/stores/executionErrorStore.ts

Comment thread src/components/rightSidePanel/RightSidePanel.test.ts
Comment thread src/components/rightSidePanel/RightSidePanel.test.ts
@DrJKL
DrJKL requested a review from jaeone94 August 11, 2026 02:20
@DrJKL DrJKL assigned jaeone94 and unassigned DrJKL Aug 11, 2026

@jaeone94 jaeone94 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The core mechanism looks solid — I traced the begin/finish lifecycle across settle, node-removal, and dispose paths and found no counter leak, and the synchronous beginAddedNodeErrorScan inside onNodeAdded genuinely closes the flush-vs-scan race described in the PR body. Store placement and the minimal begin/hasPending surface read well, and the component tests are mutation-sensitive in both directions (reverting the tabs condition fails tests 1–2; over-broadening it fails test 3).

A few things before merge, one of which I think is a real regression:

  • Issue: dispose-time cancel() aborts in-flight verifications on every subgraph enter/exit, which can permanently drop missing-model errors within the same workflow (inline comment with repro).
  • Issue: runAddedNodeScan(...).finally(finish) has no .catch, so a sync throw in a scan stage becomes an unhandled rejection and releases the pending flag while stage-1 verifications are still in flight.
  • Issue: the new onNodeRemoved cancellation line has no test — deleting it fails nothing, and its failure mode is exactly this PR's bug class.
  • Issue: the unmount test can't fail as ordered.
  • Smaller suggestions inline (comment relocation, reactivity guard hardening, i18n messages, coverage gaps).

Also — the PR body still describes the abandoned "re-check one microtask later" approach. What shipped is a ref-counted pending-scan registry that holds the Errors tab for the whole scan lifetime including network verification, which is the better design, but worth updating the description (and noting the visible UX change: an empty Errors tab during verification instead of the tab vanishing). Known-gap on e2e is acknowledged in the body; a tracked follow-up issue link would be good.

if (disposed) return
disposed = true
for (const cancellations of pendingScanCancellations.values()) {
for (const cancel of cancellations) cancel()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Issue: This runs on every canvasStore.currentGraph change — GraphCanvas.vue:266-273 disposes and reinstalls the hooks on subgraph enter/exit, not just on workflow teardown. Since cancel() aborts the verification (not just releases the counter), entering a subgraph while a pasted node's cloud verification is in flight now drops the result at the signal?.aborted check in verifyAndAddPendingModels, and the missing-model error never surfaces. Pre-PR these verifications were only guarded by app.rootGraph !== rootGraphAtScan, which subgraph navigation doesn't change, so the error still appeared (legacy canvas mode has no replay to recover it; Vue-nodes mode happens to recover via useGraphNodeManager).

Would it make sense for dispose to release the pending flags only (so the tab hold ends) and keep the abort for onNodeRemoved, where the result is genuinely irrelevant? Note "does not surface async verification after hooks are disposed" currently pins the abort-on-dispose behavior, so it would need to flip. WDYT?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thank you enormously for tracing this through GraphCanvas and catching the subgraph-navigation regression. This was an exceptionally valuable review point. 4873e0d482 now separates releasing the pending flag from aborting verification: hook disposal releases only, while actual node removal aborts. It also guards workflow/node replacement so late results cannot leak across workflows.

}

cancellationsForNode.add(cancel)
void runAddedNodeScan(node, abortController.signal).finally(finish)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Issue: A sync throw inside a scan stage (e.g. scanNodeModelCandidates on a malformed node) passes through .finally and the void leaves it unhandled — and finish() then releases the pending flag while stage-1 verifications are still outstanding.

Suggested change
void runAddedNodeScan(node, abortController.signal).finally(finish)
void runAddedNodeScan(node, abortController.signal)
.catch((error: unknown) => {
console.warn('[useErrorClearingHooks] added-node scan failed:', error)
})
.finally(finish)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Deeply grateful for this catch—the distinction between handling the rejection and preserving the lifetime of already-started verification was crucial. 4873e0d482 catches scan-stage failures and uses finally inside the scan to await all started verifications before releasing the pending flag.

originalOnNodeRemoved?.call(this, node)
return
}
for (const cancel of pendingScanCancellations.get(node) ?? []) cancel()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Issue: This line has no coverage — deleting it fails no test (the dispose tests cancel via cleanup(), and the existing removal-mid-verification test covers the mode-change path). Its failure mode is this PR's own bug class: a deleted node's in-flight verification holds the counter and keeps the Errors tab pinned. Suggest a test: add a node, wait for verifyAssetSupportedCandidates to be called, graph.remove(node), assert pending releases immediately and nothing surfaces after resolving the verification.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thank you very much for identifying the mutation hole so precisely. 4873e0d482 adds direct node-removal tests for both model and media verification, asserting immediate pending-flag release, signal abortion, and suppression of late results.

scanSingleNodeModelsAndTypes(target, pendingVerifications, signal)
)

await Promise.resolve()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggestion: The rewrite dropped the comment explaining why media waits a second microtask (paste/drop upload handlers set node.isUploading synchronously before their first await). Two bare await Promise.resolve() lines are exactly what a future simplification collapses into one, silently resurrecting the upload race. Suggest moving the old rationale here, and trimming the now-stale caller comment at 515-521 that still narrates the deferral logic that no longer lives there.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thank you for protecting this subtle timing invariant. The rationale now sits directly beside the second microtask, and the stale caller-side narration was removed in 4873e0d482.

// transient upload state before media detection reads the widget value.
if (!ChangeTracker.isLoadingGraph) {
scheduleAddedNodeScan(node)
scheduleAddedNodeScan(node, pendingScanCancellations)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nit: !ChangeTracker.isLoadingGraph is re-checked synchronously first thing inside scheduleAddedNodeScan (line 367), so one of the two is dead. I'd drop this caller-side guard and let scheduleAddedNodeScan own its preconditions.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thank you for the careful cleanup note. The duplicate caller guard is gone; scheduleAddedNodeScan now owns that precondition.

expect(openPanel).not.toHaveBeenCalled()
})

it('does not update the panel after unmount', async () => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Issue: This test can't fail — finishScan() fires and flushes before unmount(), and after mockClear() nothing in the system can still call openPanel, for any implementation. Reordering (unmount first, then finishScan()) makes it meaningful, though at that point it mostly asserts Vue's own effect-scope teardown — deleting it may be the honest fix.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thank you—this was exactly right, and it prevented a false-confidence test from surviving. The test now unmounts first, finishes the scan afterward, and waits for Vue reactivity before asserting.

)
const openPanel = vi.spyOn(rightSidePanelStore, 'openPanel')

const i18n = createI18n({

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggestion: Other component tests import the real messages (import enMessages from '@/locales/en/main.json' with { type: 'json' }) rather than a hand-picked subset — the subset rots silently into vue-i18n fallback warnings when a key is renamed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thank you for calling this out. The test now imports the real English messages rather than maintaining a brittle subset.

expect(openPanel).not.toHaveBeenCalled()

finishScan()
await Promise.resolve()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nit: Tests 1 and 4 use await Promise.resolve() while test 2 uses await nextTick(). The Promise.resolve() variant only works because Vue's scheduler queued its flush first; docs/guidance/vitest.md says to wait for reactivity with nextTick() — and for the not.toHaveBeenCalled() assertion an under-flushed await would pass vacuously.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thank you for the precise scheduler analysis. The component tests now use nextTick(). The scan-settlement test additionally blocks queueMicrotask deliberately so it remains mutation-sensitive and fails against the old deferred fallback implementation.

expect(mediaScan).toHaveBeenCalledOnce()
})

it('keeps an added-node scan pending until async verification settles', async () => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggestion (coverage): The new abort tests only exercise the model path — scanNodeMediaCandidates is mocked to [], so removing signal?.aborted || from verifyAndAddPendingMedia fails nothing. The existing deferMediaVerification fixture makes this cheap to add. Also worth one subgraph-interior-node case: both sides of the pending-scan seam compute the ID via getExecutionIdByNode, but tests only ever check root-level IDs where the two trivially coincide.

Nit: these two async tests duplicate ~25 lines of candidate/spy/deferred-resolve scaffolding — the file already has the deferMediaVerification fixture pattern to mirror.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Extremely grateful for this coverage review. 4873e0d482 adds direct media cancellation/late-result coverage, the analogous model case, and a subgraph-interior execution-ID component case. The existing media deferral fixture is reused.

rootGraph: LGraph,
executionId: NodeExecutionId
): boolean {
void pendingAddedNodeScanRevision.value

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nit: void pendingAddedNodeScanRevision.value is a correct dependency-touch over the non-reactive WeakMap, but it reads as dead code to a future editor (or lint autofix). A one-line comment stating it's the reactivity subscription point would protect it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thank you for anticipating the maintenance hazard. A one-line comment now identifies the revision read as the reactivity subscription point.

@jaeone94 jaeone94 assigned DrJKL and unassigned jaeone94 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: 2

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

Inline comments:
In `@browser_tests/tests/propertiesPanel/errorsTabCloudMissingModels.spec.ts`:
- Around line 198-253: Add a sibling test next to “keeps Errors active through
pasted-node verification and falls back when resolved” that uses a pasted node
with a missing media reference, gates the media verification request, and
verifies the Errors tab remains selected while verification is pending and after
it settles. Then resolve the media reference, trigger the appropriate refresh,
and assert the Errors tab hides with Parameters selected.

In `@src/components/rightSidePanel/RightSidePanel.test.ts`:
- Around line 130-132: Remove the unnecessary globalThis.queueMicrotask spy and
mock from the scan-completion test around finishScan and nextTick, leaving the
existing nextTick watcher flush and beforeEach mock cleanup unchanged.
🪄 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: 93156c02-9335-479d-9159-afb4e85e82f8

📥 Commits

Reviewing files that changed from the base of the PR and between 93ba843 and 4873e0d.

📒 Files selected for processing (6)
  • browser_tests/tests/propertiesPanel/errorsTabCloudMissingModels.spec.ts
  • src/components/rightSidePanel/RightSidePanel.test.ts
  • src/components/rightSidePanel/RightSidePanel.vue
  • src/composables/graph/useErrorClearingHooks.test.ts
  • src/composables/graph/useErrorClearingHooks.ts
  • src/stores/executionErrorStore.ts

Comment thread src/components/rightSidePanel/RightSidePanel.test.ts
@DrJKL

DrJKL commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

@jaeone94 Thank you immensely for the exceptionally thorough and thoughtful review. The lifecycle tracing, concrete repros, mutation checks, and attention to the smaller maintainability details were extraordinarily helpful—especially the subgraph-disposal regression, which would have been easy to miss and consequential in legacy canvas mode. Every inline point was addressed in 4873e0d482, the PR description now reflects the actual pending-scan design, and browser coverage was added. DrJKL is deeply grateful for the time and rigor invested here.

@DrJKL DrJKL assigned jaeone94 and unassigned DrJKL Aug 12, 2026
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

coderabbitai[bot]
coderabbitai Bot previously requested changes Aug 12, 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 `@browser_tests/tests/propertiesPanel/errorsTabCloudMissingModels.spec.ts`:
- Around line 230-232: Update the test around verificationStarted so it first
waits for the pasted node to exist, using the pasted-node locator or assertion
already defined in the test, and only then asserts errorsTab has
aria-selected="true". Keep the existing verificationStarted synchronization, but
ensure the pending-state assertion is tied to the added node’s verification
rather than any unrelated asset request.

In `@src/composables/graph/useErrorClearingHooks.test.ts`:
- Around line 533-553: Extract the repeated model verification setup into a
shared model-side deferral fixture, parallel to deferMediaVerification in
promotedMedia.ts. Update the affected tests to use this fixture for the
MissingModelCandidate, scanNodeModelCandidates, scanNodeMediaCandidates, and
deferred verifyAssetSupportedCandidates behavior, centralizing the verification
signature and preserving each test’s ability to resolve verification and mark
the candidate missing.
- Around line 869-871: Update the verification test around resolveVerification
to await the observable settle marker used by the corresponding media test
before asserting missingModelCandidates is null; remove the single
Promise.resolve flush and preserve the assertion that no late result is stored.
🪄 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: 19f6d6d2-9bb0-469a-bbbf-d5cb68921b1e

📥 Commits

Reviewing files that changed from the base of the PR and between c4f9b91 and a3add80.

📒 Files selected for processing (7)
  • browser_tests/tests/propertiesPanel/errorsTabCloudMissingModels.spec.ts
  • src/components/rightSidePanel/RightSidePanel.test.ts
  • src/components/rightSidePanel/RightSidePanel.vue
  • src/composables/graph/useErrorClearingHooks.test.ts
  • src/composables/graph/useErrorClearingHooks.ts
  • src/stores/executionErrorStore.test.ts
  • src/stores/executionErrorStore.ts

Comment on lines +230 to +232
await verificationStarted

await expect(panel.errorsTab).toHaveAttribute('aria-selected', 'true')

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Tie the pending-state assertion to the pasted node.

verificationStarted resolves on the first /api/assets request that the route intercepts. Any asset request can resolve it, not only the added-node verification. If an unrelated request arrives first, Line 232 can assert aria-selected before the pasted-node scan starts, so the test passes without exercising the pending-scan hold.

Wait for the pasted node to exist before asserting the tab state.

💚 Proposed fix
       await comfyPage.clipboard.paste()
+      await expect.poll(() => comfyPage.nodeOps.getNodeCount()).toBe(2)
       await verificationStarted
 
       await expect(panel.errorsTab).toHaveAttribute('aria-selected', 'true')
       releaseVerification()
-      await expect.poll(() => comfyPage.nodeOps.getNodeCount()).toBe(2)

As per path instructions, browser_tests/README.md is the canonical guide for browser tests, including flake prevention.

📝 Committable suggestion

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

Suggested change
await verificationStarted
await expect(panel.errorsTab).toHaveAttribute('aria-selected', 'true')
await comfyPage.clipboard.paste()
await expect.poll(() => comfyPage.nodeOps.getNodeCount()).toBe(2)
await verificationStarted
await expect(panel.errorsTab).toHaveAttribute('aria-selected', 'true')
releaseVerification()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@browser_tests/tests/propertiesPanel/errorsTabCloudMissingModels.spec.ts`
around lines 230 - 232, Update the test around verificationStarted so it first
waits for the pasted node to exist, using the pasted-node locator or assertion
already defined in the test, and only then asserts errorsTab has
aria-selected="true". Keep the existing verificationStarted synchronization, but
ensure the pending-state assertion is tied to the added node’s verification
rather than any unrelated asset request.

Source: Path instructions

Comment on lines +533 to +553
const candidate = fromAny<MissingModelCandidate, unknown>({
nodeId: '1',
nodeType: 'CheckpointLoaderSimple',
widgetName: 'ckpt_name',
name: 'pending.safetensors',
isMissing: undefined
})
vi.spyOn(missingModelScan, 'scanNodeModelCandidates').mockReturnValue([
candidate
])
vi.spyOn(missingMediaScan, 'scanNodeMediaCandidates').mockReturnValue([])
let resolveVerification: () => void = () => undefined
vi.spyOn(
missingModelScan,
'verifyAssetSupportedCandidates'
).mockImplementation(async () => {
await new Promise<void>((resolve) => {
resolveVerification = resolve
})
candidate.isMissing = true
})

@coderabbitai coderabbitai Bot Aug 12, 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.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract a model-verification deferral fixture.

Five new tests repeat the same candidate literal, scanNodeModelCandidates mock, scanNodeMediaCandidates mock, and deferred verifyAssetSupportedCandidates implementation. The repository already provides deferMediaVerification in src/platform/missingMedia/__fixtures__/promotedMedia.ts. Add the model-side equivalent and reuse it, so future changes to the verification signature touch one place.

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

In `@src/composables/graph/useErrorClearingHooks.test.ts` around lines 533 - 553,
Extract the repeated model verification setup into a shared model-side deferral
fixture, parallel to deferMediaVerification in promotedMedia.ts. Update the
affected tests to use this fixture for the MissingModelCandidate,
scanNodeModelCandidates, scanNodeMediaCandidates, and deferred
verifyAssetSupportedCandidates behavior, centralizing the verification signature
and preserving each test’s ability to resolve verification and mark the
candidate missing.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Open a tracking issue for this.

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.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo="Comfy-Org/ComfyUI_frontend"
body=$(cat <<'EOF'
## Summary

Extract a shared deferred model-verification fixture for
`useErrorClearingHooks` tests.

## Required changes

Add a model-side fixture that is parallel to
`deferMediaVerification` in
`src/platform/missingMedia/__fixtures__/promotedMedia.ts`.

Update the affected tests in
`src/composables/graph/useErrorClearingHooks.test.ts` to use the
fixture for:

- `MissingModelCandidate` setup
- `scanNodeModelCandidates` mocking
- `scanNodeMediaCandidates` mocking
- deferred `verifyAssetSupportedCandidates` behavior

The fixture must preserve each test's ability to resolve verification
and set the candidate `isMissing` state when required.

## Rationale

Several tests duplicate the same deferred model-verification setup.
A shared fixture will centralize the verification signature and reduce
maintenance work when that signature changes.

## Affected areas

- `src/composables/graph/useErrorClearingHooks.test.ts`
- Model verification test fixtures near missing-model test support

## Acceptance criteria

- A reusable deferred model-verification fixture exists.
- The repeated model-verification test setup uses the fixture.
- The updated tests retain their current behavioral coverage.
- Changes are limited to test support and affected tests.

## Backlinks

- Pull request: https://github.com/Comfy-Org/ComfyUI_frontend/pull/15012
- Review comment: https://github.com/Comfy-Org/ComfyUI_frontend/pull/15012#discussion_r3771038268
- Requested by: `@DrJKL`
EOF
)

gh issue create \
  --repo "$repo" \
  --title "Extract a deferred model-verification fixture for error-clearing tests" \
  --body "$body" \
  --assignee "DrJKL"

Length of output: 4305


@DrJKL, I created and assigned tracking issue #15203:
#15203


🧬 Code Graph Analysis Results

src/composables/graph/useErrorClearingHooks.ts:550-636

installErrorClearingHooks(graph) installs node-added, node-removed, and trigger callbacks; tracks and cancels pending scans, derives removed-node execution IDs, removes related errors, restores callbacks during cleanup, and prevents disposed hooks from scheduling scans.

src/lib/litegraph/src/LGraph.ts:1051-1157

LGraph.remove(node) invokes node.onRemoved, sets node.graph = null, then invokes graph.onNodeRemoved(node). This ordering is relevant to removal-hook execution-ID derivation.

src/platform/missingModel/missingModelStore.ts:21-180

Defines missing-model candidate state and methods for setting candidates, removing candidates by node ID or execution-ID prefix, and clearing associated interaction state.

src/platform/missingMedia/missingMediaStore.ts:18-175

Defines missing-media candidate state and methods for setting, adding, and removing candidates by widget, node ID, or execution-ID prefix; verification cancellation is handled by clearMissingMedia().

src/platform/nodeReplacement/missingNodesErrorStore.ts:19-154

Defines missing-node error state and methods for surfacing and removing missing-node entries by node ID or execution-ID prefix; string-based group entries are preserved during prefix removal.

src/types/nodeIdentification.ts:139-148

createNodeExecutionId(nodeIds) validates and joins serialized node-ID segments with :; returns null for an empty or invalid path.

src/lib/litegraph/src/subgraph/__fixtures__/subgraphHelpers.ts:268-288

createTestSubgraphNode(subgraph, options) creates a subgraph container with an optional parent graph and explicit node ID, used by tests involving nested execution-ID paths.

src/platform/missingMedia/__fixtures__/promotedMedia.ts:78-92

deferMediaVerification() returns a verification spy and resolver. Verification remains pending until the resolver is called, then marks all supplied media candidates as missing.

src/platform/missingMedia/__fixtures__/promotedMedia.ts:153-245

createPromotedMediaRuntime(options) builds root, nested subgraph, promoted host, and source-node relationships with configurable depth, IDs, fanout, widget values, and options. It returns graphs, hosts, source nodes, and intermediate hosts for promotion lifecycle tests.

You are interacting with an AI system.

Comment on lines +869 to +871
resolveVerification()
await Promise.resolve()
expect(useMissingModelStore().missingModelCandidates).toBeNull()

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Wait for the verification to settle before asserting no late result.

await Promise.resolve() flushes one microtask. The mocked verifyAssetSupportedCandidates continuation and the post-verification filter need more than one microtask, so expect(...).toBeNull() can pass before any late result could be stored. The assertion then passes vacuously, including against an implementation that ignores the abort signal.

Mirror the media test at Lines 900-902, which awaits an observable settle marker first.

💚 Proposed fix
     resolveVerification()
-    await Promise.resolve()
+    await vi.waitFor(() => expect(verifySpy).toHaveResolved())
     expect(useMissingModelStore().missingModelCandidates).toBeNull()

As per path instructions, required review context states: "Avoid excessive mocks, fragile timing assertions, snapshots, and shared mutable state."

📝 Committable suggestion

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

Suggested change
resolveVerification()
await Promise.resolve()
expect(useMissingModelStore().missingModelCandidates).toBeNull()
resolveVerification()
await vi.waitFor(() => expect(verifySpy).toHaveResolved())
expect(useMissingModelStore().missingModelCandidates).toBeNull()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/composables/graph/useErrorClearingHooks.test.ts` around lines 869 - 871,
Update the verification test around resolveVerification to await the observable
settle marker used by the corresponding media test before asserting
missingModelCandidates is null; remove the single Promise.resolve flush and
preserve the assertion that no late result is stored.

Source: Path instructions

@github-actions github-actions Bot added the risk:R2 PR risk grade (advisory shadow check; grader-owned) label Aug 13, 2026

@jaeone94 jaeone94 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

All findings resolved; nothing further blocking from my side. LGTM.

@jaeone94 jaeone94 assigned DrJKL and unassigned jaeone94 Aug 13, 2026
@DrJKL
DrJKL added this pull request to the merge queue Aug 13, 2026
Merged via the queue into main with commit 4111782 Aug 13, 2026
66 checks passed
@DrJKL
DrJKL deleted the drjkl/tab-fallback-deferred-scans branch August 13, 2026 19:11
pull Bot pushed a commit to Penguinjanator/ComfyUI_frontend that referenced this pull request Aug 17, 2026
…14668)

## Summary

Follow-up to
[Comfy-Org#14578](Comfy-Org#14578): the
comment/naming cleanup requested during its review, plus what remains of
DrJKL's post-merge findings after
[Comfy-Org#14948](Comfy-Org#14948)
landed the stale-value guard. Rebased onto current `main`. Contained to
files Comfy-Org#14578 already touched — no new modules, exports, or store
surfaces.

## Changes

**Reconciliation during multi-node deletion
(`useErrorClearingHooks.ts`)** — deleting N nodes ran the global
candidate walk N times. Targeted removal for the deleted node stays
synchronous; only the global pass is coalesced into one microtask per
burst, and it drops if the hooks were torn down meanwhile. The
mode-change path keeps its synchronous semantics. The scheduler lives in
the `installErrorClearingHooks` closure rather than at module scope, so
it follows the per-installation `disposed` lifecycle Comfy-Org#15012 introduced.

Two promotion lifecycle tests seeded their stale candidate after the
removal and relied on the synchronous pass having already gone by; they
now let the removal's own reconcile land first.

**Un-bypass transition test** — hand-rolled the mode event while leaving
the node in its default mode, so it never established the BYPASS state
its transition claimed. Driven through `setNodeMode`, which already sits
in that file.

**Workflow-load coverage for the stale-value guard** — Comfy-Org#14948 pinned the
guard through the realtime path and through the predicate itself. The
pipeline also writes confirmed candidates into the workflow's
`pendingWarnings`, which `workflowService` restores without
re-validating, so a stale candidate surviving there outlives the
session. Asserting the cache needs a workflow stub, which is why the
`workspaceStore` mock stops returning a null `activeWorkflow`.

**Comment and naming cleanup** — comments say what a thing *is* rather
than narrating the situation around it: the reconciler's module doc
keeps its first line and drops the essay; the notes above the
`dropOutOfScope` guard, the scan's ordering, `isEditableValueOwner`, and
`rootGraph` are gone because the code and the names already carry them.
The unparseable `missingMediaStore.test.ts` comment is deleted.
`findNodesReferencingValues` no longer explains a caller's behaviour.
Settles on `drop` for the operation spelled both `prune` and `drop`.
Aliases `comfyPageFixture` to `test` in
`errorsTabMissingMediaRuntime.spec.ts`, matching the 213 other specs
that do.

**Breaking**: none.

## Dropped on rebase

The stale-value hunk and its realtime regression test are identical to
what Comfy-Org#14948 shipped, so `main` already has them. The promoted-media
harness also encoded an impossible fixture state —
`createPromotedMissingMediaCandidate` derives `name` from
`hostWidget.value`, but the harness overrode `name` while leaving the
widget elsewhere. Comfy-Org#14948 fixed that by seeding through `hostValue`,
which keeps the two in sync rather than dropping the distinct name; that
resolution is kept.

## Review focus

Both behavioural changes carry a mutation-proved regression test on this
base: reverting the coalescing makes the burst test fail (2 calls
instead of 0 before the microtask), and reverting the value guard makes
the workflow-load test fail. Commits are split so each is independently
reviewable and independently green.

Deliberately **not** included:

- Moving `isEditableValueOwner` into `src/core/graph/subgraph/`, raised
on Comfy-Org#14578. The point stands — nothing there combines link state,
promotion and liveness into "is this widget the editable value owner",
and the predicate isn't missing-media-specific. But the move creates a
new shared surface in the area `feature/ecs-migration` is actively
reworking, which is not worth it for a single-caller helper. Left where
it is.
- `WidgetSelectDropdown`'s `root`/`trigger` semantics, and the
reconciler's dependency on node add/remove — both scoped out by the
reviewer, the latter to be handled in his refactor.
- The E2E-structure items — the conditional repair path in
`enterSubgraphWithFallback`, driving promoted-widget setup through user
behaviour instead of `page.evaluate`, the options argument, and the
`_testNodes` mock branches. Those change how the suite is built, so
they're tracked in
[FE-1493](https://linear.app/comfyorg/issue/FE-1493/no-e2e-for-promotedemote-error-reconciliation-and-promoted-media).

## Test plan

Static gates clean on the rebased tree (`typecheck`, `lint`,
`format:check`, `knip`). Unit run scoped to the affected areas —
`composables/graph`, `platform/missingMedia` — 188 tests green, and each
of the four commits verified green on its own. Full suite left to CI.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

refactor risk:R2 PR risk grade (advisory shadow check; grader-owned) size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants