Skip to content

fix: layer editor UX and review follow-ups - #14941

Open
jtydhr88 wants to merge 4 commits into
mainfrom
fix/layer-editor-review-nits
Open

fix: layer editor UX and review follow-ups#14941
jtydhr88 wants to merge 4 commits into
mainfrom
fix/layer-editor-review-nits

Conversation

@jtydhr88

@jtydhr88 jtydhr88 commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • pixel-accurate canvas picking: clicks select the topmost layer with opaque pixels under the cursor, transparent pixels fall through, and gizmo handles still win, instead of pinning to the selected bounding box
  • drag to reorder layers in the panel with an above/below indicator, landing as a single undoable reorder above the pinned background
  • shift-click multi-select no longer drags text selection along
  • compositor widget no longer crashes in the node search preview when the node is not in the graph
  • History: merges no longer inflate the dirty counter and cannot cross a save point, so dirty tracking survives merged edits
  • renderStack frees group targets when the composite throws; zero-size resize observations no longer consume the first fitView; an emptied number field reverts instead of committing 0; refusing to open the editor toasts; visible focus ring on the viewport

@jtydhr88
jtydhr88 requested a review from a team August 8, 2026 15:07
@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Aug 8, 2026
@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown

🎭 Playwright: ✅ 1840 passed, 0 failed

📊 Browser Reports
  • chromium: View Report (✅ 1819 / ❌ 0 / ⚠️ 0 / ⏭️ 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)
  • New-test walkthrough (chromium, recorded video): View Report

🎨 Storybook: ✅ Built — View Storybook

Details

⏰ Completed at: 08/20/2026, 03:27:02 AM UTC

Links

📦 Bundle: 9.12 MB gzip 🔴 +1.28 kB

Details

Summary

  • Raw size: 38.7 MB baseline 38.7 MB — 🔴 +4.37 kB
  • Gzip: 9.12 MB baseline 9.12 MB — 🔴 +1.28 kB
  • Brotli: 6.38 MB baseline 6.38 MB — 🔴 +1.45 kB
  • Bundles: 439 current • 439 baseline • 156 added / 156 removed

Category Glance
Other 🔴 +2.96 kB (14.2 MB) · Utilities & Hooks 🔴 +1.23 kB (550 kB) · Editors & Dialogs 🔴 +172 B (126 kB) · Graph Workspace 🔴 +1 B (1.37 MB) · Vendor & Third-Party ⚪ 0 B (18.1 MB) · Data & Services ⚪ 0 B (3.53 MB) · + 5 more

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

Main entry bundles and manifests

File Before After Δ Raw Δ Gzip Δ Brotli
assets/index-B6Ta37J6.js (new) 3.71 kB 🔴 +3.71 kB 🔴 +1.86 kB 🔴 +1.6 kB
assets/index-DhI90Xg8.js (removed) 3.71 kB 🟢 -3.71 kB 🟢 -1.86 kB 🟢 -1.59 kB

Status: 1 added / 1 removed

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

Graph editor runtime, canvas, workflow orchestration

File Before After Δ Raw Δ Gzip Δ Brotli
assets/GraphView-B8MV0U1Z.js (removed) 1.36 MB 🟢 -1.36 MB 🟢 -297 kB 🟢 -223 kB
assets/GraphView-flfu2Q84.js (new) 1.36 MB 🔴 +1.36 MB 🔴 +297 kB 🔴 +223 kB
assets/WidgetCompositor-RD7h51QD.js (new) 8.17 kB 🔴 +8.17 kB 🔴 +2.76 kB 🔴 +2.46 kB
assets/WidgetCompositor-D5B0sZa1.js (removed) 8.17 kB 🟢 -8.17 kB 🟢 -2.76 kB 🟢 -2.46 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-B5EORcUu.js (new) 25 kB 🔴 +25 kB 🔴 +6.25 kB 🔴 +5.52 kB
assets/CloudSurveyView-DUt5EnbQ.js (removed) 25 kB 🟢 -25 kB 🟢 -6.25 kB 🟢 -5.52 kB
assets/CloudLayoutView-BwS0bzYJ.js (removed) 21.8 kB 🟢 -21.8 kB 🟢 -6.57 kB 🟢 -5.74 kB
assets/CloudLayoutView-Co7X3jGg.js (new) 21.8 kB 🔴 +21.8 kB 🔴 +6.57 kB 🔴 +5.75 kB
assets/UserCheckView-C-tgcaHn.js (new) 8.75 kB 🔴 +8.75 kB 🔴 +2.19 kB 🔴 +1.91 kB
assets/UserCheckView-CpSZGdXV.js (removed) 8.75 kB 🟢 -8.75 kB 🟢 -2.19 kB 🟢 -1.91 kB
assets/CloudLoginView-Bq0K0uaB.js (new) 8.74 kB 🔴 +8.74 kB 🔴 +2.56 kB 🔴 +2.26 kB
assets/CloudLoginView-BRBIT9ov.js (removed) 8.74 kB 🟢 -8.74 kB 🟢 -2.56 kB 🟢 -2.28 kB
assets/useCloudAuthPage-ChBBDGAD.js (removed) 7.08 kB 🟢 -7.08 kB 🟢 -2.52 kB 🟢 -2.2 kB
assets/useCloudAuthPage-D9O55zAs.js (new) 7.08 kB 🔴 +7.08 kB 🔴 +2.52 kB 🔴 +2.21 kB
assets/CloudSignupView-5tXeGFBl.js (removed) 6.96 kB 🟢 -6.96 kB 🟢 -2.28 kB 🟢 -2.01 kB
assets/CloudSignupView-DZiXZFuZ.js (new) 6.96 kB 🔴 +6.96 kB 🔴 +2.28 kB 🔴 +2 kB
assets/WidgetTextPreview-dd_rnLid.js (removed) 6.07 kB 🟢 -6.07 kB 🟢 -2.13 kB 🟢 -1.9 kB
assets/WidgetTextPreview-U34u7BXE.js (new) 6.07 kB 🔴 +6.07 kB 🔴 +2.13 kB 🔴 +1.9 kB
assets/CloudSubscriptionRedirectView-AaCO_-1x.js (removed) 6.05 kB 🟢 -6.05 kB 🟢 -2.25 kB 🟢 -1.96 kB
assets/CloudSubscriptionRedirectView-CaP5NUWH.js (new) 6.05 kB 🔴 +6.05 kB 🔴 +2.25 kB 🔴 +1.96 kB
assets/UserSelectView-CeKJmjuQ.js (new) 5.49 kB 🔴 +5.49 kB 🔴 +1.96 kB 🔴 +1.71 kB
assets/UserSelectView-cSMoSBxJ.js (removed) 5.49 kB 🟢 -5.49 kB 🟢 -1.96 kB 🟢 -1.72 kB
assets/CloudForgotPasswordView-BNaUsRSV.js (removed) 4.97 kB 🟢 -4.97 kB 🟢 -1.72 kB 🟢 -1.49 kB
assets/CloudForgotPasswordView-jwwwS0hT.js (new) 4.97 kB 🔴 +4.97 kB 🔴 +1.73 kB 🔴 +1.49 kB
assets/CloudAuthTimeoutView-2YKOga0y.js (new) 4.43 kB 🔴 +4.43 kB 🔴 +1.54 kB 🔴 +1.34 kB
assets/CloudAuthTimeoutView-fghaAq6N.js (removed) 4.43 kB 🟢 -4.43 kB 🟢 -1.54 kB 🟢 -1.33 kB
assets/OAuthLayoutView-BzUbkzyX.js (removed) 1.31 kB 🟢 -1.31 kB 🟢 -700 B 🟢 -588 B
assets/OAuthLayoutView-DVq0UO_r.js (new) 1.31 kB 🔴 +1.31 kB 🔴 +700 B 🔴 +587 B
assets/WidgetTextPreview-gwJB-WFo.js (new) 131 B 🔴 +131 B 🔴 +100 B 🔴 +93 B
assets/WidgetTextPreview-yxjYxxbo.js (removed) 131 B 🟢 -131 B 🟢 -100 B 🟢 -95 B

Status: 13 added / 13 removed / 4 unchanged

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

Configuration panels, inspectors, and settings screens

File Before After Δ Raw Δ Gzip Δ Brotli
assets/KeybindingPanel-CkRkJ-Oi.js (removed) 49.4 kB 🟢 -49.4 kB 🟢 -9.95 kB 🟢 -8.79 kB
assets/KeybindingPanel-DYu_9S6b.js (new) 49.4 kB 🔴 +49.4 kB 🔴 +9.95 kB 🔴 +8.8 kB
assets/SecretsPanel-C37UAZU9.js (removed) 33.7 kB 🟢 -33.7 kB 🟢 -7.88 kB 🟢 -6.91 kB
assets/SecretsPanel-DQSc9spK.js (new) 33.7 kB 🔴 +33.7 kB 🔴 +7.88 kB 🔴 +6.92 kB
assets/CreditsPanel-CElw1837.js (new) 11.5 kB 🔴 +11.5 kB 🔴 +3.21 kB 🔴 +2.82 kB
assets/CreditsPanel-DdZTaMHM.js (removed) 11.5 kB 🟢 -11.5 kB 🟢 -3.21 kB 🟢 -2.82 kB
assets/AboutPanel-Dan-cl1s.js (new) 11.2 kB 🔴 +11.2 kB 🔴 +3.12 kB 🔴 +2.81 kB
assets/AboutPanel-tJ5RgKVT.js (removed) 11.2 kB 🟢 -11.2 kB 🟢 -3.12 kB 🟢 -2.8 kB
assets/ExtensionPanel-CxLpKJvn.js (new) 9.19 kB 🔴 +9.19 kB 🔴 +2.51 kB 🔴 +2.23 kB
assets/ExtensionPanel-nLkblog5.js (removed) 9.19 kB 🟢 -9.19 kB 🟢 -2.51 kB 🟢 -2.23 kB
assets/ServerConfigPanel-Du7XbJqw.js (new) 6.09 kB 🔴 +6.09 kB 🔴 +1.94 kB 🔴 +1.73 kB
assets/ServerConfigPanel-P9BkAcL2.js (removed) 6.09 kB 🟢 -6.09 kB 🟢 -1.94 kB 🟢 -1.72 kB
assets/UserPanel-Ci1tBmWq.js (removed) 5.73 kB 🟢 -5.73 kB 🟢 -1.79 kB 🟢 -1.54 kB
assets/UserPanel-vh52iA0P.js (new) 5.73 kB 🔴 +5.73 kB 🔴 +1.79 kB 🔴 +1.54 kB
assets/refreshRemoteConfig-DRUgxOA_.js (removed) 4.24 kB 🟢 -4.24 kB 🟢 -1.51 kB 🟢 -1.34 kB
assets/refreshRemoteConfig-Tkna_3Pb.js (new) 4.24 kB 🔴 +4.24 kB 🔴 +1.51 kB 🔴 +1.34 kB
assets/cloudRemoteConfig-Cu2Gg6je.js (removed) 951 B 🟢 -951 B 🟢 -515 B 🟢 -421 B
assets/cloudRemoteConfig-CzP-p4Rc.js (new) 951 B 🔴 +951 B 🔴 +517 B 🔴 +422 B
assets/refreshRemoteConfig-BBIXgNEA.js (removed) 110 B 🟢 -110 B 🟢 -89 B 🟢 -86 B
assets/refreshRemoteConfig-DxnTCtG5.js (new) 110 B 🔴 +110 B 🔴 +89 B 🔴 +86 B

Status: 10 added / 10 removed / 16 unchanged

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

Authentication, profile, and account management bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/SignUpForm-BWNWETRj.js (removed) 13.3 kB 🟢 -13.3 kB 🟢 -4.53 kB 🟢 -3.94 kB
assets/SignUpForm-DkO40aX0.js (new) 13.3 kB 🔴 +13.3 kB 🔴 +4.53 kB 🔴 +3.94 kB
assets/auth-CZEP_g9i.js (new) 3.48 kB 🔴 +3.48 kB 🔴 +1.2 kB 🔴 +1.01 kB
assets/auth-ok7NLPIO.js (removed) 3.48 kB 🟢 -3.48 kB 🟢 -1.21 kB 🟢 -1.01 kB
assets/UpdatePasswordContent-BrE1zQMd.js (new) 1.85 kB 🔴 +1.85 kB 🔴 +844 B 🔴 +737 B
assets/UpdatePasswordContent-D5kJv4ZJ.js (removed) 1.85 kB 🟢 -1.85 kB 🟢 -843 B 🟢 -740 B
assets/authStore-CNWRbYxF.js (removed) 128 B 🟢 -128 B 🟢 -104 B 🟢 -104 B
assets/authStore-DGSZHf-S.js (new) 128 B 🔴 +128 B 🔴 +104 B 🔴 +106 B
assets/workspaceAuthStore-DoMQ3BIB.js (removed) 108 B 🟢 -108 B 🟢 -99 B 🟢 -107 B
assets/workspaceAuthStore-jR9JTe60.js (new) 108 B 🔴 +108 B 🔴 +99 B 🔴 +109 B
assets/auth-CwkMDN_V.js (new) 105 B 🔴 +105 B 🔴 +96 B 🔴 +79 B
assets/auth-jwzecDVv.js (removed) 105 B 🟢 -105 B 🟢 -96 B 🟢 -84 B

Status: 6 added / 6 removed / 5 unchanged

Editors & Dialogs — 126 kB (baseline 125 kB) • 🔴 +172 B

Modals, dialogs, drawers, and in-app editors

File Before After Δ Raw Δ Gzip Δ Brotli
assets/ComfyHubPublishDialog-IFpVILHe.js (removed) 90.1 kB 🟢 -90.1 kB 🟢 -19.3 kB 🟢 -16.5 kB
assets/ComfyHubPublishDialog-msjXUA_g.js (new) 90.1 kB 🔴 +90.1 kB 🔴 +19.3 kB 🔴 +16.5 kB
assets/useShareDialog-B5ws9iY8.js (removed) 23.9 kB 🟢 -23.9 kB 🟢 -5.7 kB 🟢 -5.04 kB
assets/useShareDialog-D4pTKTLe.js (new) 23.9 kB 🔴 +23.9 kB 🔴 +5.69 kB 🔴 +5.04 kB
assets/feedbackDialog-C_A5oP_Z.js (new) 4.45 kB 🔴 +4.45 kB 🔴 +1.87 kB 🔴 +1.59 kB
assets/feedbackDialog-F5RVLwJ-.js (removed) 4.45 kB 🟢 -4.45 kB 🟢 -1.87 kB 🟢 -1.59 kB
assets/useRangeEditor-aOxpsbBk.js (new) 3.29 kB 🔴 +3.29 kB 🔴 +1.14 kB 🔴 +1.04 kB
assets/useRangeEditor-CfQGLMMx.js (removed) 3.29 kB 🟢 -3.29 kB 🟢 -1.14 kB 🟢 -1.03 kB
assets/useLayerEditor-CwY9qNgR.js (new) 1.19 kB 🔴 +1.19 kB 🔴 +566 B 🔴 +495 B
assets/useLayerEditor-DcgE5tn4.js (removed) 1.01 kB 🟢 -1.01 kB 🟢 -490 B 🟢 -405 B
assets/ComfyHubPublishDialog-4rZW1mm1.js (removed) 143 B 🟢 -143 B 🟢 -105 B 🟢 -91 B
assets/ComfyHubPublishDialog-CoyxjiOT.js (new) 143 B 🔴 +143 B 🔴 +105 B 🔴 +92 B
assets/useSubscriptionDialog-DabGVv7e.js (new) 108 B 🔴 +108 B 🔴 +102 B 🔴 +92 B
assets/useSubscriptionDialog-Dd9MiHBd.js (removed) 108 B 🟢 -108 B 🟢 -102 B 🟢 -91 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-U3DMlZNn.js (new) 14.6 kB 🔴 +14.6 kB 🔴 +3.97 kB 🔴 +3.53 kB
assets/ComfyQueueButton-wk6hbVzm.js (removed) 14.6 kB 🟢 -14.6 kB 🟢 -3.97 kB 🟢 -3.53 kB
assets/useTerminalTabs-M5rImSXb.js (new) 11.8 kB 🔴 +11.8 kB 🔴 +3.69 kB 🔴 +3.27 kB
assets/useTerminalTabs-t_Czh6em.js (removed) 11.8 kB 🟢 -11.8 kB 🟢 -3.69 kB 🟢 -3.28 kB
assets/InviteMembersForm-9UPJWimW.js (new) 8.23 kB 🔴 +8.23 kB 🔴 +2.74 kB 🔴 +2.44 kB
assets/InviteMembersForm-C6prfxin.js (removed) 8.23 kB 🟢 -8.23 kB 🟢 -2.74 kB 🟢 -2.43 kB
assets/SubscribeButton-BNjC8SoQ.js (removed) 2.15 kB 🟢 -2.15 kB 🟢 -970 B 🟢 -850 B
assets/SubscribeButton-DFK3_BXc.js (new) 2.15 kB 🔴 +2.15 kB 🔴 +970 B 🔴 +850 B
assets/cloudFeedbackTopbarButton-BrsSlPzk.js (new) 705 B 🔴 +705 B 🔴 +423 B 🔴 +360 B
assets/cloudFeedbackTopbarButton-CcNSdYaY.js (removed) 705 B 🟢 -705 B 🟢 -420 B 🟢 -360 B
assets/ComfyQueueButton-DHu_al7Y.js (removed) 128 B 🟢 -128 B 🟢 -99 B 🟢 -89 B
assets/ComfyQueueButton-Dwz2bMxu.js (new) 128 B 🔴 +128 B 🔴 +99 B 🔴 +87 B

Status: 6 added / 6 removed / 8 unchanged

Data & Services — 3.53 MB (baseline 3.53 MB) • ⚪ 0 B

Stores, services, APIs, and repositories

File Before After Δ Raw Δ Gzip Δ Brotli
assets/settingStore-CGzrLIJj.js (removed) 3.16 MB 🟢 -3.16 MB 🟢 -742 kB 🟢 -558 kB
assets/settingStore-D5HlZYW4.js (new) 3.16 MB 🔴 +3.16 MB 🔴 +742 kB 🔴 +558 kB
assets/api-COUfAdKN.js (new) 186 kB 🔴 +186 kB 🔴 +39.8 kB 🔴 +33.4 kB
assets/api-uWfJ2q5S.js (removed) 186 kB 🟢 -186 kB 🟢 -39.8 kB 🟢 -33.3 kB
assets/load3dService-BTZhDqtb.js (new) 132 kB 🔴 +132 kB 🔴 +29.3 kB 🔴 +24.7 kB
assets/load3dService-hmQVBkMN.js (removed) 132 kB 🟢 -132 kB 🟢 -29.3 kB 🟢 -24.7 kB
assets/workflowShareService-C4XThT5S.js (removed) 16.5 kB 🟢 -16.5 kB 🟢 -4.92 kB 🟢 -4.34 kB
assets/workflowShareService-CnmUazZa.js (new) 16.5 kB 🔴 +16.5 kB 🔴 +4.92 kB 🔴 +4.34 kB
assets/keybindingService-DdgJ6x7j.js (removed) 6.95 kB 🟢 -6.95 kB 🟢 -1.74 kB 🟢 -1.51 kB
assets/keybindingService-gRO5NhgW.js (new) 6.95 kB 🔴 +6.95 kB 🔴 +1.74 kB 🔴 +1.51 kB
assets/releaseStore-B_DA7rFo.js (new) 6.72 kB 🔴 +6.72 kB 🔴 +2.03 kB 🔴 +1.77 kB
assets/releaseStore-BB5zkunj.js (removed) 6.72 kB 🟢 -6.72 kB 🟢 -2.03 kB 🟢 -1.77 kB
assets/systemStatsStore-BenBB442.js (removed) 4.93 kB 🟢 -4.93 kB 🟢 -1.74 kB 🟢 -1.47 kB
assets/systemStatsStore-CCnDa3GE.js (new) 4.93 kB 🔴 +4.93 kB 🔴 +1.74 kB 🔴 +1.47 kB
assets/userStore-DL1_w_i3.js (removed) 2.38 kB 🟢 -2.38 kB 🟢 -899 B 🟢 -795 B
assets/userStore-OWsmJ6dI.js (new) 2.38 kB 🔴 +2.38 kB 🔴 +897 B 🔴 +795 B
assets/audioService-8rMLoFgM.js (new) 1.71 kB 🔴 +1.71 kB 🔴 +830 B 🔴 +722 B
assets/audioService-CLTVgXK2.js (removed) 1.71 kB 🟢 -1.71 kB 🟢 -830 B 🟢 -722 B
assets/dialogService-CkmwKYNM.js (new) 98 B 🔴 +98 B 🔴 +97 B 🔴 +81 B
assets/dialogService-eOh1igI5.js (removed) 98 B 🟢 -98 B 🟢 -97 B 🟢 -83 B
assets/releaseStore-DiTG14eT.js (removed) 95 B 🟢 -95 B 🟢 -86 B 🟢 -82 B
assets/releaseStore-DRjUNfPv.js (new) 95 B 🔴 +95 B 🔴 +86 B 🔴 +86 B
assets/settingStore-BC6N3gua.js (new) 95 B 🔴 +95 B 🔴 +86 B 🔴 +78 B
assets/settingStore-CRZ4hK3m.js (removed) 95 B 🟢 -95 B 🟢 -86 B 🟢 -90 B
assets/assetsStore-BrbjA4jz.js (new) 94 B 🔴 +94 B 🔴 +92 B 🔴 +85 B
assets/assetsStore-jYpcXRdZ.js (removed) 94 B 🟢 -94 B 🟢 -92 B 🟢 -84 B
assets/api-DgjfRIc7.js (new) 62 B 🔴 +62 B 🔴 +74 B 🔴 +66 B
assets/api-Dpq229qW.js (removed) 62 B 🟢 -62 B 🟢 -74 B 🟢 -66 B

Status: 14 added / 14 removed / 3 unchanged

Utilities & Hooks — 550 kB (baseline 549 kB) • 🔴 +1.23 kB

Helpers, composables, and utility bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useConflictDetection-BpP1TpMT.js (new) 236 kB 🔴 +236 kB 🔴 +53.1 kB 🔴 +43.2 kB
assets/useConflictDetection-Dhk8jJ-T.js (removed) 236 kB 🟢 -236 kB 🟢 -53.1 kB 🟢 -43.2 kB
assets/useLayerEditorSession-CAUQdGtD.js (new) 159 kB 🔴 +159 kB 🔴 +40.9 kB 🔴 +34.5 kB
assets/useLayerEditorSession-Ca6Wr2A2.js (removed) 158 kB 🟢 -158 kB 🟢 -40.8 kB 🟢 -34.3 kB
assets/useLoad3d-DABQdOdB.js (new) 25.8 kB 🔴 +25.8 kB 🔴 +5.8 kB 🔴 +5.16 kB
assets/useLoad3d-XBlpEQlh.js (removed) 25.8 kB 🟢 -25.8 kB 🟢 -5.8 kB 🟢 -5.15 kB
assets/useLoad3dViewer-8Ve_jvux.js (new) 21.2 kB 🔴 +21.2 kB 🔴 +4.98 kB 🔴 +4.37 kB
assets/useLoad3dViewer-CLIgAyKd.js (removed) 21.2 kB 🟢 -21.2 kB 🟢 -4.98 kB 🟢 -4.36 kB
assets/useImageCrop-44B3kuYd.js (new) 14.9 kB 🔴 +14.9 kB 🔴 +3.42 kB 🔴 +2.99 kB
assets/useImageCrop-pHWsXol-.js (removed) 14.9 kB 🟢 -14.9 kB 🟢 -3.42 kB 🟢 -2.99 kB
assets/useDowngradeToPersonal-CnLBixEY.js (new) 10.9 kB 🔴 +10.9 kB 🔴 +2.74 kB 🔴 +2.35 kB
assets/useDowngradeToPersonal-CTOjOudd.js (removed) 10.9 kB 🟢 -10.9 kB 🟢 -2.73 kB 🟢 -2.35 kB
assets/useFeatureFlags-DGTg09GD.js (removed) 7.32 kB 🟢 -7.32 kB 🟢 -2.06 kB 🟢 -1.74 kB
assets/useFeatureFlags-Uoqc_CVF.js (new) 7.32 kB 🔴 +7.32 kB 🔴 +2.06 kB 🔴 +1.76 kB
assets/useCompositorLayers-5JaVawq-.js (new) 3.09 kB 🔴 +3.09 kB 🔴 +927 B 🔴 +827 B
assets/useCompositorLayers-uV8zlrM_.js (removed) 2.94 kB 🟢 -2.94 kB 🟢 -898 B 🟢 -804 B
assets/useExternalLink-Ck9uCMqu.js (new) 2.85 kB 🔴 +2.85 kB 🔴 +1.07 kB 🔴 +922 B
assets/useExternalLink-vLTS5Any.js (removed) 2.85 kB 🟢 -2.85 kB 🟢 -1.06 kB 🟢 -922 B
assets/assetPreviewUtil-9rqegOj3.js (removed) 2.35 kB 🟢 -2.35 kB 🟢 -970 B 🟢 -847 B
assets/assetPreviewUtil-COzMybgb.js (new) 2.35 kB 🔴 +2.35 kB 🔴 +970 B 🔴 +847 B
assets/downloadUtil-B7TLEo9-.js (removed) 2.04 kB 🟢 -2.04 kB 🟢 -906 B 🟢 -724 B
assets/downloadUtil-DHukiSqm.js (new) 2.04 kB 🔴 +2.04 kB 🔴 +907 B 🔴 +726 B
assets/useUpstreamValue-Btnokvia.js (removed) 1.99 kB 🟢 -1.99 kB 🟢 -758 B 🟢 -671 B
assets/useUpstreamValue-DnOM8sii.js (new) 1.99 kB 🔴 +1.99 kB 🔴 +760 B 🔴 +685 B
assets/useWorkspaceTierLabel-EFmJM87t.js (removed) 1.93 kB 🟢 -1.93 kB 🟢 -814 B 🟢 -697 B
assets/useWorkspaceTierLabel-xmE1BmQ-.js (new) 1.93 kB 🔴 +1.93 kB 🔴 +813 B 🔴 +697 B
assets/useErrorHandling--pTKCBZu.js (new) 1.49 kB 🔴 +1.49 kB 🔴 +614 B 🔴 +522 B
assets/useErrorHandling-VHP-cpqL.js (removed) 1.49 kB 🟢 -1.49 kB 🟢 -613 B 🟢 -521 B
assets/subscriptionCheckoutUtil-Cwe3Cdi6.js (new) 877 B 🔴 +877 B 🔴 +523 B 🔴 +455 B
assets/subscriptionCheckoutUtil-kkwwii9_.js (removed) 877 B 🟢 -877 B 🟢 -521 B 🟢 -435 B
assets/useSessionCookie-Dxr-DT1i.js (removed) 652 B 🟢 -652 B 🟢 -337 B 🟢 -290 B
assets/useSessionCookie-iun8e1K8.js (new) 652 B 🔴 +652 B 🔴 +338 B 🔴 +292 B
assets/useLoad3d-D4ghfkXV.js (new) 311 B 🔴 +311 B 🔴 +163 B 🔴 +148 B
assets/useLoad3d-DsxkIWxt.js (removed) 311 B 🟢 -311 B 🟢 -164 B 🟢 -150 B
assets/useSessionCookie-BgXvPBzt.js (removed) 101 B 🟢 -101 B 🟢 -86 B 🟢 -78 B
assets/useSessionCookie-XyE820EK.js (new) 101 B 🔴 +101 B 🔴 +86 B 🔴 +80 B
assets/useFeatureFlags-CXVxJLLt.js (new) 98 B 🔴 +98 B 🔴 +85 B 🔴 +87 B
assets/useFeatureFlags-DKjh51b8.js (removed) 98 B 🟢 -98 B 🟢 -85 B 🟢 -80 B
assets/useLoad3dViewer-BqKGiv7A.js (removed) 98 B 🟢 -98 B 🟢 -85 B 🟢 -93 B
assets/useLoad3dViewer-DaXI903b.js (new) 98 B 🔴 +98 B 🔴 +85 B 🔴 +89 B
assets/useCurrentUser-BKjEBYsC.js (removed) 94 B 🟢 -94 B 🟢 -95 B 🟢 -85 B
assets/useCurrentUser-Brub4AtS.js (new) 94 B 🔴 +94 B 🔴 +95 B 🔴 +93 B

Status: 21 added / 21 removed / 16 unchanged

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

External libraries and shared vendor chunks

Status: 18 unchanged

Other — 14.2 MB (baseline 14.2 MB) • 🔴 +2.96 kB

Bundles that do not match a named category

File Before After Δ Raw Δ Gzip Δ Brotli
assets/main-Ed4VIAJd.js (new) 207 kB 🔴 +207 kB 🔴 +60.1 kB 🔴 +50.4 kB
assets/main-C2VbHNI1.js (removed) 207 kB 🟢 -207 kB 🟢 -60.1 kB 🟢 -50.4 kB
assets/core-B2NA0bHP.js (new) 115 kB 🔴 +115 kB 🔴 +30 kB 🔴 +25.3 kB
assets/core-7MelGzY8.js (removed) 115 kB 🟢 -115 kB 🟢 -30 kB 🟢 -25.3 kB
assets/WidgetSelect-3J8gkIxW.js (removed) 88.8 kB 🟢 -88.8 kB 🟢 -20.1 kB 🟢 -17.2 kB
assets/WidgetSelect-O7LCM0uO.js (new) 88.8 kB 🔴 +88.8 kB 🔴 +20.1 kB 🔴 +17.2 kB
assets/SubscriptionPanelContentWorkspace-BUckNZxv.js (removed) 81.2 kB 🟢 -81.2 kB 🟢 -16.1 kB 🟢 -13.9 kB
assets/SubscriptionPanelContentWorkspace-DLLdfbG0.js (new) 81.2 kB 🔴 +81.2 kB 🔴 +16.1 kB 🔴 +13.9 kB
assets/Load3D-DAKsLqQD.js (new) 73.3 kB 🔴 +73.3 kB 🔴 +12.1 kB 🔴 +10.3 kB
assets/Load3D-Dr9Nwm91.js (removed) 73.3 kB 🟢 -73.3 kB 🟢 -12.1 kB 🟢 -10.3 kB
assets/WidgetVideoEdit-Br5ovtVl.js (removed) 71.2 kB 🟢 -71.2 kB 🟢 -17 kB 🟢 -14.9 kB
assets/WidgetVideoEdit-DWTSRHWQ.js (new) 71.2 kB 🔴 +71.2 kB 🔴 +17 kB 🔴 +14.9 kB
assets/SubscriptionTransitionPreviewWorkspace-BRCznFab.js (removed) 66.6 kB 🟢 -66.6 kB 🟢 -13.4 kB 🟢 -11.7 kB
assets/SubscriptionTransitionPreviewWorkspace-COanlB9K.js (new) 66.6 kB 🔴 +66.6 kB 🔴 +13.5 kB 🔴 +11.7 kB
assets/WorkspaceSettingsPanelContent-C5uBwfmi.js (removed) 58.2 kB 🟢 -58.2 kB 🟢 -12.4 kB 🟢 -10.8 kB
assets/WorkspaceSettingsPanelContent-LnYbs-UB.js (new) 58.2 kB 🔴 +58.2 kB 🔴 +12.4 kB 🔴 +10.8 kB
assets/Preview3d-Cjod4SOo.js (new) 50.9 kB 🔴 +50.9 kB 🔴 +8.32 kB 🔴 +7.24 kB
assets/Preview3d-hUEc_-Ny.js (removed) 50.9 kB 🟢 -50.9 kB 🟢 -8.32 kB 🟢 -7.24 kB
assets/LayerEditorContent-4x3beS-j.js (new) 45.4 kB 🔴 +45.4 kB 🔴 +10.6 kB 🔴 +9.26 kB
assets/main-C6OuFkge.js (new) 45.4 kB 🔴 +45.4 kB 🔴 +13.2 kB 🔴 +11.5 kB
assets/main-Ce8blh71.js (removed) 45.4 kB 🟢 -45.4 kB 🟢 -13.2 kB 🟢 -11.5 kB
assets/SubscriptionRequiredDialogContentUnified-DjfoPi8p.js (removed) 42.7 kB 🟢 -42.7 kB 🟢 -9.39 kB 🟢 -8.19 kB
assets/SubscriptionRequiredDialogContentUnified-WS8d6k7A.js (new) 42.7 kB 🔴 +42.7 kB 🔴 +9.39 kB 🔴 +8.19 kB
assets/LayerEditorContent-eg-EAnYG.js (removed) 42.5 kB 🟢 -42.5 kB 🟢 -9.62 kB 🟢 -8.43 kB
assets/WidgetBoundingBoxes-B22aPC_t.js (new) 33.7 kB 🔴 +33.7 kB 🔴 +9.16 kB 🔴 +8.12 kB
assets/WidgetBoundingBoxes-ByZeI6v4.js (removed) 33.7 kB 🟢 -33.7 kB 🟢 -9.16 kB 🟢 -8.12 kB
assets/WidgetPainter-B7O2UjIR.js (removed) 32.6 kB 🟢 -32.6 kB 🟢 -7.87 kB 🟢 -6.95 kB
assets/WidgetPainter-GOV7C9B9.js (new) 32.6 kB 🔴 +32.6 kB 🔴 +7.88 kB 🔴 +6.99 kB
assets/Load3dViewerContent-CUu0B5Wq.js (new) 30.8 kB 🔴 +30.8 kB 🔴 +6.28 kB 🔴 +5.45 kB
assets/Load3dViewerContent-ZE4aQYEo.js (removed) 30.8 kB 🟢 -30.8 kB 🟢 -6.28 kB 🟢 -5.45 kB
assets/HdrViewerContent-BZH5xorr.js (new) 29.8 kB 🔴 +29.8 kB 🔴 +8.25 kB 🔴 +7.27 kB
assets/HdrViewerContent-EoBiIwDW.js (removed) 29.8 kB 🟢 -29.8 kB 🟢 -8.25 kB 🟢 -7.27 kB
assets/SubscriptionRequiredDialogContent-Ct9E3ZQN.js (new) 26.9 kB 🔴 +26.9 kB 🔴 +6.36 kB 🔴 +5.61 kB
assets/SubscriptionRequiredDialogContent-DOPsNJrq.js (removed) 26.9 kB 🟢 -26.9 kB 🟢 -6.36 kB 🟢 -5.6 kB
assets/SubscriptionRequiredDialogContentWorkspace-DFnDXXiT.js (removed) 25.1 kB 🟢 -25.1 kB 🟢 -5.81 kB 🟢 -5.12 kB
assets/SubscriptionRequiredDialogContentWorkspace-DN6UWVq8.js (new) 25.1 kB 🔴 +25.1 kB 🔴 +5.81 kB 🔴 +5.11 kB
assets/CreditsTile-CqiB76c7.js (removed) 24.9 kB 🟢 -24.9 kB 🟢 -6.65 kB 🟢 -5.84 kB
assets/CreditsTile-DkmYl0Pe.js (new) 24.9 kB 🔴 +24.9 kB 🔴 +6.65 kB 🔴 +5.83 kB
assets/load3d-hoVJI_eM.js (new) 22.2 kB 🔴 +22.2 kB 🔴 +5.41 kB 🔴 +4.68 kB
assets/load3d-n9n5VdxJ.js (removed) 22.2 kB 🟢 -22.2 kB 🟢 -5.41 kB 🟢 -4.67 kB
assets/CurrentUserPopoverWorkspace-Bm597eeb.js (removed) 21.9 kB 🟢 -21.9 kB 🟢 -5 kB 🟢 -4.43 kB
assets/CurrentUserPopoverWorkspace-CuwbVOnE.js (new) 21.9 kB 🔴 +21.9 kB 🔴 +5 kB 🔴 +4.44 kB
assets/SignInContent-B3-zvfb7.js (removed) 20.6 kB 🟢 -20.6 kB 🟢 -5.18 kB 🟢 -4.53 kB
assets/SignInContent-C7gwrjk2.js (new) 20.6 kB 🔴 +20.6 kB 🔴 +5.18 kB 🔴 +4.54 kB
assets/i18n-CBHOpxdK.js (new) 16.7 kB 🔴 +16.7 kB 🔴 +4.55 kB 🔴 +3.81 kB
assets/i18n-CKcspdJI.js (removed) 16.7 kB 🟢 -16.7 kB 🟢 -4.55 kB 🟢 -3.82 kB
assets/WidgetRecordAudio-ciLoKsC0.js (removed) 16.6 kB 🟢 -16.6 kB 🟢 -4.6 kB 🟢 -4.1 kB
assets/WidgetRecordAudio-DxWtyG04.js (new) 16.6 kB 🔴 +16.6 kB 🔴 +4.6 kB 🔴 +4.1 kB
assets/WidgetInputNumber-CFZKtLTc.js (new) 13.9 kB 🔴 +13.9 kB 🔴 +3.63 kB 🔴 +3.21 kB
assets/WidgetInputNumber-KHaZYIIi.js (removed) 13.9 kB 🟢 -13.9 kB 🟢 -3.63 kB 🟢 -3.21 kB
assets/WidgetRange-BuDTj71W.js (new) 13.7 kB 🔴 +13.7 kB 🔴 +3.55 kB 🔴 +3.12 kB
assets/WidgetRange-D_1xlQuP.js (removed) 13.7 kB 🟢 -13.7 kB 🟢 -3.55 kB 🟢 -3.12 kB
assets/WaveAudioPlayer-Btz3MFBP.js (removed) 12.8 kB 🟢 -12.8 kB 🟢 -3.46 kB 🟢 -3.03 kB
assets/WaveAudioPlayer-DwilgUck.js (new) 12.8 kB 🔴 +12.8 kB 🔴 +3.46 kB 🔴 +3.05 kB
assets/WidgetCurve-AUBIu3Tr.js (new) 11.2 kB 🔴 +11.2 kB 🔴 +3.47 kB 🔴 +3.15 kB
assets/WidgetCurve-CIEPjo2-.js (removed) 11.2 kB 🟢 -11.2 kB 🟢 -3.47 kB 🟢 -3.15 kB
assets/AudioPreviewPlayer-CBRLHZXW.js (removed) 10.6 kB 🟢 -10.6 kB 🟢 -3.05 kB 🟢 -2.74 kB
assets/AudioPreviewPlayer-yTkifLsX.js (new) 10.6 kB 🔴 +10.6 kB 🔴 +3.05 kB 🔴 +2.72 kB
assets/TeamWorkspacesDialogContent-Bj6xP7Bx.js (removed) 10.3 kB 🟢 -10.3 kB 🟢 -2.97 kB 🟢 -2.63 kB
assets/TeamWorkspacesDialogContent-jjqVC7PK.js (new) 10.3 kB 🔴 +10.3 kB 🔴 +2.97 kB 🔴 +2.63 kB
assets/onboardingCloudRoutes-BFTmGnX8.js (new) 9.49 kB 🔴 +9.49 kB 🔴 +2.85 kB 🔴 +2.45 kB
assets/onboardingCloudRoutes-CeFOutEJ.js (removed) 9.49 kB 🟢 -9.49 kB 🟢 -2.85 kB 🟢 -2.44 kB
assets/Load3DConfiguration-BlE92Jxy.js (removed) 8.91 kB 🟢 -8.91 kB 🟢 -2.61 kB 🟢 -2.3 kB
assets/Load3DConfiguration-C8188N6B.js (new) 8.91 kB 🔴 +8.91 kB 🔴 +2.61 kB 🔴 +2.3 kB
assets/WidgetImageCrop-DwJjekeh.js (removed) 8.49 kB 🟢 -8.49 kB 🟢 -2.65 kB 🟢 -2.34 kB
assets/WidgetImageCrop-zWBvSNZI.js (new) 8.49 kB 🔴 +8.49 kB 🔴 +2.66 kB 🔴 +2.34 kB
assets/SetMemberCreditLimitDialogContent-BvL7pTPM.js (removed) 8.47 kB 🟢 -8.47 kB 🟢 -2.34 kB 🟢 -2.06 kB
assets/SetMemberCreditLimitDialogContent-CBIA8QD5.js (new) 8.47 kB 🔴 +8.47 kB 🔴 +2.34 kB 🔴 +2.06 kB
assets/nodeTemplates-CptIfJqd.js (new) 8.32 kB 🔴 +8.32 kB 🔴 +2.86 kB 🔴 +2.5 kB
assets/nodeTemplates-qrroFxB9.js (removed) 8.32 kB 🟢 -8.32 kB 🟢 -2.86 kB 🟢 -2.51 kB
assets/NightlySurveyController-DdeHEyo0.js (removed) 7.5 kB 🟢 -7.5 kB 🟢 -2.56 kB 🟢 -2.25 kB
assets/NightlySurveyController-Ddo_oYTy.js (new) 7.5 kB 🔴 +7.5 kB 🔴 +2.56 kB 🔴 +2.25 kB
assets/CloudRunButtonWrapper-fvAoIE5C.js (removed) 7.29 kB 🟢 -7.29 kB 🟢 -2.29 kB 🟢 -2.01 kB
assets/CloudRunButtonWrapper-OPhZqy28.js (new) 7.29 kB 🔴 +7.29 kB 🔴 +2.3 kB 🔴 +2.01 kB
assets/WidgetWithControl-4bUtepAi.js (new) 6.51 kB 🔴 +6.51 kB 🔴 +2.67 kB 🔴 +2.32 kB
assets/WidgetWithControl-AESg2mqL.js (removed) 6.51 kB 🟢 -6.51 kB 🟢 -2.67 kB 🟢 -2.35 kB
assets/missingModelMetadata-BZWndsNF.js (new) 6.45 kB 🔴 +6.45 kB 🔴 +2.21 kB 🔴 +1.93 kB
assets/missingModelMetadata-CpmCxEZf.js (removed) 6.45 kB 🟢 -6.45 kB 🟢 -2.21 kB 🟢 -1.93 kB
assets/CancelSubscriptionDialogContent-BGTn8kcN.js (new) 5.97 kB 🔴 +5.97 kB 🔴 +1.98 kB 🔴 +1.75 kB
assets/CancelSubscriptionDialogContent-CKFTVqjj.js (removed) 5.97 kB 🟢 -5.97 kB 🟢 -1.98 kB 🟢 -1.75 kB
assets/main-B9Bt5NT3.js (new) 5.92 kB 🔴 +5.92 kB 🔴 +1.9 kB 🔴 +1.59 kB
assets/main-CEUpmmm4.js (removed) 5.92 kB 🟢 -5.92 kB 🟢 -1.91 kB 🟢 -1.59 kB
assets/load3dPreviewExtensions-B9YZ5852.js (removed) 5.88 kB 🟢 -5.88 kB 🟢 -1.81 kB 🟢 -1.6 kB
assets/load3dPreviewExtensions-uJe2JSIR.js (new) 5.88 kB 🔴 +5.88 kB 🔴 +1.81 kB 🔴 +1.6 kB
assets/launchCancellationFlow-Bh84GRp8.js (removed) 5.18 kB 🟢 -5.18 kB 🟢 -1.78 kB 🟢 -1.56 kB
assets/launchCancellationFlow-CrCdCYqh.js (new) 5.18 kB 🔴 +5.18 kB 🔴 +1.78 kB 🔴 +1.56 kB
assets/CreateWorkspaceDialogContent--fLy67u4.js (new) 5.12 kB 🔴 +5.12 kB 🔴 +1.79 kB 🔴 +1.55 kB
assets/CreateWorkspaceDialogContent-B56N9L91.js (removed) 5.12 kB 🟢 -5.12 kB 🟢 -1.8 kB 🟢 -1.55 kB
assets/ChangeMemberRoleDialogContent-BLY02dOE.js (new) 4.97 kB 🔴 +4.97 kB 🔴 +1.64 kB 🔴 +1.43 kB
assets/ChangeMemberRoleDialogContent-DGwi_Onx.js (removed) 4.97 kB 🟢 -4.97 kB 🟢 -1.64 kB 🟢 -1.43 kB
assets/InviteMemberDialogContent-CNXnyHef.js (new) 4.96 kB 🔴 +4.96 kB 🔴 +1.65 kB 🔴 +1.44 kB
assets/InviteMemberDialogContent-Dj1NNsSS.js (removed) 4.96 kB 🟢 -4.96 kB 🟢 -1.65 kB 🟢 -1.44 kB
assets/EditWorkspaceDialogContent-BfD7ZN0y.js (new) 4.93 kB 🔴 +4.93 kB 🔴 +1.76 kB 🔴 +1.53 kB
assets/EditWorkspaceDialogContent-Bu33qmPJ.js (removed) 4.93 kB 🟢 -4.93 kB 🟢 -1.76 kB 🟢 -1.52 kB
assets/WidgetTextarea-C8xER7Ba.js (removed) 4.83 kB 🟢 -4.83 kB 🟢 -1.88 kB 🟢 -1.64 kB
assets/WidgetTextarea-qCimPh3D.js (new) 4.83 kB 🔴 +4.83 kB 🔴 +1.88 kB 🔴 +1.65 kB
assets/saveMesh-BGCYvdZR.js (new) 4.76 kB 🔴 +4.76 kB 🔴 +1.52 kB 🔴 +1.37 kB
assets/saveMesh-DdAbG_v3.js (removed) 4.76 kB 🟢 -4.76 kB 🟢 -1.52 kB 🟢 -1.34 kB
assets/WorkspacePanelContent-6avP1E4c.js (new) 4.74 kB 🔴 +4.74 kB 🔴 +1.63 kB 🔴 +1.44 kB
assets/WorkspacePanelContent-B70A3bKd.js (removed) 4.74 kB 🟢 -4.74 kB 🟢 -1.63 kB 🟢 -1.44 kB
assets/ValueControlPopover-V8LZqv0M.js (removed) 4.49 kB 🟢 -4.49 kB 🟢 -1.55 kB 🟢 -1.38 kB
assets/ValueControlPopover-yPe4LR7F.js (new) 4.49 kB 🔴 +4.49 kB 🔴 +1.55 kB 🔴 +1.38 kB
assets/ApiNodesSignInContent-Ba5RB63K.js (new) 4.08 kB 🔴 +4.08 kB 🔴 +1.31 kB 🔴 +1.16 kB
assets/ApiNodesSignInContent-SERbGLjv.js (removed) 4.08 kB 🟢 -4.08 kB 🟢 -1.31 kB 🟢 -1.15 kB
assets/DeleteWorkspaceDialogContent-C0cKP5nK.js (new) 3.84 kB 🔴 +3.84 kB 🔴 +1.44 kB 🔴 +1.24 kB
assets/DeleteWorkspaceDialogContent-XvCluFfv.js (removed) 3.84 kB 🟢 -3.84 kB 🟢 -1.44 kB 🟢 -1.24 kB
assets/RemoveMemberDialogContent-C1cv2WFq.js (new) 3.76 kB 🔴 +3.76 kB 🔴 +1.39 kB 🔴 +1.2 kB
assets/RemoveMemberDialogContent-D51L0JAN.js (removed) 3.76 kB 🟢 -3.76 kB 🟢 -1.39 kB 🟢 -1.2 kB
assets/RevokeInviteDialogContent-CdUc4fZQ.js (removed) 3.67 kB 🟢 -3.67 kB 🟢 -1.39 kB 🟢 -1.21 kB
assets/RevokeInviteDialogContent-si-hqU64.js (new) 3.67 kB 🔴 +3.67 kB 🔴 +1.4 kB 🔴 +1.21 kB
assets/LeaveWorkspaceDialogContent-j9lW1eLC.js (new) 3.67 kB 🔴 +3.67 kB 🔴 +1.39 kB 🔴 +1.19 kB
assets/LeaveWorkspaceDialogContent-tk77JW7Y.js (removed) 3.67 kB 🟢 -3.67 kB 🟢 -1.39 kB 🟢 -1.19 kB
assets/InviteMemberUpsellDialogContent-D2qAE3zv.js (removed) 3.47 kB 🟢 -3.47 kB 🟢 -1.24 kB 🟢 -1.09 kB
assets/InviteMemberUpsellDialogContent-zyEdXM-T.js (new) 3.47 kB 🔴 +3.47 kB 🔴 +1.24 kB 🔴 +1.09 kB
assets/workspaceCheckoutTelemetry-w91fTtMT.js (removed) 3.4 kB 🟢 -3.4 kB 🟢 -1.52 kB 🟢 -1.32 kB
assets/workspaceCheckoutTelemetry-x7KHIiER.js (new) 3.4 kB 🔴 +3.4 kB 🔴 +1.52 kB 🔴 +1.32 kB
assets/GlobalToast-C8nzWYiX.js (removed) 3.25 kB 🟢 -3.25 kB 🟢 -1.3 kB 🟢 -1.11 kB
assets/GlobalToast-DGTa4nZm.js (new) 3.25 kB 🔴 +3.25 kB 🔴 +1.3 kB 🔴 +1.11 kB
assets/Media3DTop-BYOwP2cZ.js (new) 3.21 kB 🔴 +3.21 kB 🔴 +1.26 kB 🔴 +1.11 kB
assets/Media3DTop-DkO7i1Uu.js (removed) 3.21 kB 🟢 -3.21 kB 🟢 -1.26 kB 🟢 -1.11 kB
assets/load3dAdvanced-BgOQ4Okv.js (removed) 2.82 kB 🟢 -2.82 kB 🟢 -1.09 kB 🟢 -954 B
assets/load3dAdvanced-C71fbqXk.js (new) 2.82 kB 🔴 +2.82 kB 🔴 +1.1 kB 🔴 +955 B
assets/SubscribeToRun-2BmxA3bZ.js (removed) 2.39 kB 🟢 -2.39 kB 🟢 -1.04 kB 🟢 -911 B
assets/SubscribeToRun-B7G8AOQm.js (new) 2.39 kB 🔴 +2.39 kB 🔴 +1.04 kB 🔴 +909 B
assets/MediaAudioTop-BvX4NGDm.js (removed) 1.62 kB 🟢 -1.62 kB 🟢 -807 B 🟢 -672 B
assets/MediaAudioTop-CnkTJfab.js (new) 1.62 kB 🔴 +1.62 kB 🔴 +805 B 🔴 +666 B
assets/signInSchema-C6d7OP5E.js (new) 1.55 kB 🔴 +1.55 kB 🔴 +555 B 🔴 +519 B
assets/signInSchema-CkrGCURU.js (removed) 1.55 kB 🟢 -1.55 kB 🟢 -553 B 🟢 -490 B
assets/cloudSessionCookie-CZoApUqN.js (removed) 933 B 🟢 -933 B 🟢 -430 B 🟢 -393 B
assets/cloudSessionCookie-DH7FiGbI.js (new) 933 B 🔴 +933 B 🔴 +433 B 🔴 +377 B
assets/cloudBadges-BwvwhGjB.js (removed) 922 B 🟢 -922 B 🟢 -513 B 🟢 -470 B
assets/cloudBadges-DEjodhap.js (new) 922 B 🔴 +922 B 🔴 +517 B 🔴 +442 B
assets/Load3DAdvanced-B3tXGNpU.js (new) 761 B 🔴 +761 B 🔴 +423 B 🔴 +359 B
assets/Load3DAdvanced-BzFeozx-.js (removed) 761 B 🟢 -761 B 🟢 -423 B 🟢 -361 B
assets/nightlyBadges-CxdajyHD.js (new) 411 B 🔴 +411 B 🔴 +274 B 🔴 +230 B
assets/nightlyBadges-VaNsuaxP.js (removed) 411 B 🟢 -411 B 🟢 -270 B 🟢 -231 B
assets/Load3dViewerContent-B0YyGvLo.js (new) 137 B 🔴 +137 B 🔴 +103 B 🔴 +91 B
assets/Load3dViewerContent-DEsJIzdh.js (removed) 137 B 🟢 -137 B 🟢 -103 B 🟢 -106 B
assets/missingModelMetadata-BaHlLjad.js (removed) 125 B 🟢 -125 B 🟢 -103 B 🟢 -103 B
assets/missingModelMetadata-FsgzGJPP.js (new) 125 B 🔴 +125 B 🔴 +103 B 🔴 +109 B
assets/Load3DAdvanced-__pdFxIj.js (removed) 122 B 🟢 -122 B 🟢 -97 B 🟢 -90 B
assets/Load3DAdvanced-Ayb76Zp1.js (new) 122 B 🔴 +122 B 🔴 +97 B 🔴 +89 B
assets/WidgetLegacy-CphO_x9b.js (new) 117 B 🔴 +117 B 🔴 +106 B 🔴 +102 B
assets/WidgetLegacy-DSI69ZKg.js (removed) 117 B 🟢 -117 B 🟢 -106 B 🟢 -104 B
assets/workflowDraftStoreV2-DxtUL_Vj.js (removed) 112 B 🟢 -112 B 🟢 -101 B 🟢 -110 B
assets/workflowDraftStoreV2-l0cdrvU0.js (new) 112 B 🔴 +112 B 🔴 +101 B 🔴 +113 B
assets/Load3D-Ck9fwZW_.js (removed) 98 B 🟢 -98 B 🟢 -89 B 🟢 -88 B
assets/Load3D-CwYn58K4.js (new) 98 B 🔴 +98 B 🔴 +89 B 🔴 +87 B
assets/i18n-B3agudHe.js (new) 97 B 🔴 +97 B 🔴 +92 B 🔴 +86 B
assets/i18n-CncTt5YL.js (removed) 97 B 🟢 -97 B 🟢 -92 B 🟢 -84 B
assets/changeTracker-9i7024OD.js (removed) 91 B 🟢 -91 B 🟢 -93 B 🟢 -85 B
assets/changeTracker-ELzc32sE.js (new) 91 B 🔴 +91 B 🔴 +93 B 🔴 +86 B

Status: 76 added / 76 removed / 211 unchanged

⚡ Performance Report

canvas-idle: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 73.0 MB heap
canvas-mouse-sweep: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 53.7 MB heap
canvas-zoom-sweep: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 61.0 MB heap
dom-widget-clipping: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 69.1 MB heap
large-graph-idle: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 75.8 MB heap
large-graph-pan: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 64.5 MB heap
large-graph-zoom: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 69.0 MB heap
legacy-node-drag: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 88.3 MB heap
minimap-idle: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 66.1 MB heap
subgraph-dom-widget-clipping: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 69.7 MB heap
subgraph-idle: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 57.7 MB heap
subgraph-mouse-sweep: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 73.8 MB heap
subgraph-transition-enter: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 131ms TBT · 82.8 MB heap
viewport-pan-sweep: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 65.8 MB heap
vue-large-graph-idle: · 56.2 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 2ms TBT · 173.7 MB heap
vue-large-graph-pan: · 55.4 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 187ms TBT · 185.1 MB heap
workflow-execution: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 61.4 MB heap

⚠️ 6 regressions detected

Show regressions
Metric Baseline PR (median) Δ Sig
canvas-idle: task duration 424ms 526ms +24% ⚠️ z=4.2
canvas-zoom-sweep: task duration 293ms 402ms +37% ⚠️ z=3.2
large-graph-pan: task duration 900ms 1189ms +32% ⚠️ z=2.5
minimap-idle: task duration 529ms 697ms +32% ⚠️ z=3.6
subgraph-idle: task duration 415ms 515ms +24% ⚠️ z=4.6
workflow-execution: layout duration 1ms 2ms +140% ⚠️ z=2.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 5ms 8ms +41% z=-3.6
canvas-idle: layout count 0 0 +0%
canvas-idle: style recalc count 10 9 -15% z=-4.7
canvas-idle: task duration 424ms 526ms +24% ⚠️ z=4.2
canvas-idle: script duration 6ms 8ms +42% z=-7.6
canvas-idle: TBT 0ms 0ms +0%
canvas-idle: heap used 76.8 MB 73.0 MB -5%
canvas-idle: DOM nodes -281 -283 +1% z=-239.2
canvas-idle: event listeners -151 -151 +0% z=-34.4
canvas-mouse-sweep: avg frame time 17ms 17ms +0% z=0.2
canvas-mouse-sweep: p95 frame time 17ms 17ms +0%
canvas-mouse-sweep: layout duration 2ms 4ms +60% z=0.9
canvas-mouse-sweep: style recalc duration 23ms 41ms +80% z=-0.6
canvas-mouse-sweep: layout count 12 12 +0%
canvas-mouse-sweep: style recalc count 71 76 +6% z=-1.4
canvas-mouse-sweep: task duration 639ms 946ms +48% z=1.4
canvas-mouse-sweep: script duration 70ms 109ms +55% z=-4.1
canvas-mouse-sweep: TBT 0ms 0ms +0%
canvas-mouse-sweep: heap used 66.0 MB 53.7 MB -19%
canvas-mouse-sweep: DOM nodes -281 -284 +1% z=-133.9
canvas-mouse-sweep: event listeners -151 -152 +1% z=-38.5
canvas-zoom-sweep: avg frame time 17ms 17ms +0% z=0.5
canvas-zoom-sweep: p95 frame time 17ms 17ms +0%
canvas-zoom-sweep: layout duration 0ms 1ms +47% z=1.3
canvas-zoom-sweep: style recalc duration 12ms 18ms +43% z=-0.9
canvas-zoom-sweep: layout count 6 6 +0%
canvas-zoom-sweep: style recalc count 31 31 +0% z=-0.6
canvas-zoom-sweep: task duration 293ms 402ms +37% ⚠️ z=3.2
canvas-zoom-sweep: script duration 8ms 12ms +60% z=-5.0
canvas-zoom-sweep: TBT 0ms 0ms +0%
canvas-zoom-sweep: heap used 61.2 MB 61.0 MB -0%
canvas-zoom-sweep: DOM nodes 79 76 -4% z=-4.7
canvas-zoom-sweep: event listeners 19 19 +0% z=-0.9
dom-widget-clipping: avg frame time 17ms 17ms -0% z=-0.2
dom-widget-clipping: p95 frame time 17ms 17ms +1%
dom-widget-clipping: layout duration 0ms 0ms +0%
dom-widget-clipping: style recalc duration 6ms 7ms +27% z=-3.3
dom-widget-clipping: layout count 0 0 +0%
dom-widget-clipping: style recalc count 11 11 -5% z=-5.2
dom-widget-clipping: task duration 272ms 357ms +31% z=-0.4
dom-widget-clipping: script duration 38ms 54ms +40% z=-4.3
dom-widget-clipping: TBT 0ms 0ms +0%
dom-widget-clipping: heap used 68.3 MB 69.1 MB +1%
dom-widget-clipping: DOM nodes 18 17 -6% z=-3.6
dom-widget-clipping: event listeners 0 1 variance too high
large-graph-idle: avg frame time 17ms 17ms +0% z=-0.2
large-graph-idle: p95 frame time 17ms 17ms -0%
large-graph-idle: layout duration 0ms 0ms +0%
large-graph-idle: style recalc duration 6ms 9ms +51% z=-3.2
large-graph-idle: layout count 0 0 +0%
large-graph-idle: style recalc count 10 9 -10% z=-8.3
large-graph-idle: task duration 513ms 632ms +23% z=1.7
large-graph-idle: script duration 12ms 16ms +41% z=-8.2
large-graph-idle: TBT 0ms 0ms +0%
large-graph-idle: heap used 66.3 MB 75.8 MB +14%
large-graph-idle: DOM nodes -275 -280 +2% z=-336.6
large-graph-idle: event listeners -147 -165 +12% z=-31.3
large-graph-pan: avg frame time 17ms 17ms -0% z=-0.2
large-graph-pan: p95 frame time 17ms 17ms -0%
large-graph-pan: layout duration 0ms 0ms +0%
large-graph-pan: style recalc duration 10ms 16ms +54% z=-2.2
large-graph-pan: layout count 0 0 +0%
large-graph-pan: style recalc count 70 69 -1% z=-0.9
large-graph-pan: task duration 900ms 1189ms +32% ⚠️ z=2.5
large-graph-pan: script duration 235ms 318ms +35% z=-4.5
large-graph-pan: TBT 0ms 0ms +0%
large-graph-pan: heap used 68.5 MB 64.5 MB -6%
large-graph-pan: DOM nodes -266 -274 +3% z=-177.3
large-graph-pan: event listeners -149 -147 -1% z=-183.5
large-graph-zoom: avg frame time 17ms 17ms +0%
large-graph-zoom: p95 frame time 17ms 17ms +1%
large-graph-zoom: layout duration 6ms 8ms +32%
large-graph-zoom: style recalc duration 12ms 15ms +32%
large-graph-zoom: layout count 60 60 +0%
large-graph-zoom: style recalc count 66 65 -2%
large-graph-zoom: task duration 1075ms 1393ms +30%
large-graph-zoom: script duration 269ms 389ms +45%
large-graph-zoom: TBT 0ms 0ms +0%
large-graph-zoom: heap used 66.3 MB 69.0 MB +4%
large-graph-zoom: DOM nodes 16 -281 -1856%
large-graph-zoom: event listeners -145 -150 +3%
legacy-node-drag: avg frame time 17ms 17ms -0%
legacy-node-drag: p95 frame time 17ms 17ms -0%
legacy-node-drag: layout duration 0ms 0ms +0%
legacy-node-drag: style recalc duration 7ms 10ms +46%
legacy-node-drag: layout count 0 0 +0%
legacy-node-drag: style recalc count 48 46 -5%
legacy-node-drag: task duration 1097ms 1408ms +28%
legacy-node-drag: script duration 335ms 461ms +38%
legacy-node-drag: TBT 0ms 0ms +0%
legacy-node-drag: heap used 66.4 MB 88.3 MB +33%
legacy-node-drag: DOM nodes 16 11 -31%
legacy-node-drag: event listeners 186 186 +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 6ms 7ms +10% z=-3.4
minimap-idle: layout count 0 0 +0%
minimap-idle: style recalc count 9 8 -11% z=-2.3
minimap-idle: task duration 529ms 697ms +32% ⚠️ z=3.6
minimap-idle: script duration 12ms 19ms +59% z=-8.0
minimap-idle: TBT 0ms 0ms +0%
minimap-idle: heap used 68.8 MB 66.1 MB -4%
minimap-idle: DOM nodes -273 -282 +3% z=-220.2
minimap-idle: event listeners -149 -149 +0% z=-232.6
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 8ms 11ms +38% z=-1.7
subgraph-dom-widget-clipping: layout count 0 0 +0%
subgraph-dom-widget-clipping: style recalc count 48 47 -2% z=-1.6
subgraph-dom-widget-clipping: task duration 294ms 401ms +36% z=1.2
subgraph-dom-widget-clipping: script duration 84ms 121ms +45% z=-1.1
subgraph-dom-widget-clipping: TBT 0ms 0ms +0%
subgraph-dom-widget-clipping: heap used 69.9 MB 69.7 MB -0%
subgraph-dom-widget-clipping: DOM nodes 22 20 -9% z=-1.9
subgraph-dom-widget-clipping: event listeners 8 8 +0% z=-1.4
subgraph-idle: avg frame time 17ms 17ms +0% z=0.4
subgraph-idle: p95 frame time 17ms 17ms -0%
subgraph-idle: layout duration 0ms 0ms +0%
subgraph-idle: style recalc duration 6ms 9ms +51% z=-2.2
subgraph-idle: layout count 0 0 +0%
subgraph-idle: style recalc count 9 10 +6% z=-2.1
subgraph-idle: task duration 415ms 515ms +24% ⚠️ z=4.6
subgraph-idle: script duration 6ms 8ms +38% z=-4.6
subgraph-idle: TBT 0ms 0ms +0%
subgraph-idle: heap used 63.5 MB 57.7 MB -9%
subgraph-idle: DOM nodes -282 -281 -1% z=-202.2
subgraph-idle: event listeners -151 -166 +10% variance too high
subgraph-mouse-sweep: avg frame time 17ms 17ms -0% z=-0.1
subgraph-mouse-sweep: p95 frame time 17ms 17ms +0%
subgraph-mouse-sweep: layout duration 3ms 5ms +43% z=0.0
subgraph-mouse-sweep: style recalc duration 26ms 38ms +45% z=-1.4
subgraph-mouse-sweep: layout count 16 16 +0%
subgraph-mouse-sweep: style recalc count 75 76 +1% z=-2.4
subgraph-mouse-sweep: task duration 636ms 811ms +28% z=0.6
subgraph-mouse-sweep: script duration 59ms 85ms +43% z=-2.5
subgraph-mouse-sweep: TBT 0ms 0ms +0%
subgraph-mouse-sweep: heap used 62.4 MB 73.8 MB +18%
subgraph-mouse-sweep: DOM nodes -280 -280 -0% z=-155.1
subgraph-mouse-sweep: event listeners -181 -151 -17% 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 9ms 14ms +48%
subgraph-transition-enter: style recalc duration 24ms 32ms +35%
subgraph-transition-enter: layout count 15 14 -7%
subgraph-transition-enter: style recalc count 20 19 -5%
subgraph-transition-enter: task duration 712ms 952ms +34%
subgraph-transition-enter: script duration 13ms 18ms +40%
subgraph-transition-enter: TBT 109ms 131ms +20%
subgraph-transition-enter: heap used 82.2 MB 82.8 MB +1%
subgraph-transition-enter: DOM nodes 13673 13673 +0%
subgraph-transition-enter: event listeners 2373 2375 +0%
viewport-pan-sweep: avg frame time 17ms 17ms -0%
viewport-pan-sweep: p95 frame time 17ms 17ms -0%
viewport-pan-sweep: layout duration 0ms 0ms +0%
viewport-pan-sweep: style recalc duration 24ms 44ms +82%
viewport-pan-sweep: layout count 0 0 +0%
viewport-pan-sweep: style recalc count 249 250 +0%
viewport-pan-sweep: task duration 3135ms 4370ms +39%
viewport-pan-sweep: script duration 706ms 1002ms +42%
viewport-pan-sweep: TBT 0ms 0ms +0%
viewport-pan-sweep: heap used 86.1 MB 65.8 MB -24%
viewport-pan-sweep: DOM nodes -281 -280 -1%
viewport-pan-sweep: event listeners -163 -147 -10%
vue-large-graph-idle: avg frame time 17ms 18ms +3%
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 11328ms 17724ms +56%
vue-large-graph-idle: script duration 80ms 128ms +60%
vue-large-graph-idle: TBT 0ms 2ms
vue-large-graph-idle: heap used 169.8 MB 173.7 MB +2%
vue-large-graph-idle: DOM nodes -8312 -8312 +0%
vue-large-graph-idle: event listeners -16389 -16388 -0%
vue-large-graph-pan: avg frame time 17ms 18ms +5%
vue-large-graph-pan: p95 frame time 17ms 17ms +1%
vue-large-graph-pan: layout duration 0ms 0ms +0%
vue-large-graph-pan: style recalc duration 13ms 25ms +102%
vue-large-graph-pan: layout count 0 0 +0%
vue-large-graph-pan: style recalc count 73 176 +141%
vue-large-graph-pan: task duration 14192ms 22738ms +60%
vue-large-graph-pan: script duration 324ms 461ms +42%
vue-large-graph-pan: TBT 0ms 187ms
vue-large-graph-pan: heap used 182.7 MB 185.1 MB +1%
vue-large-graph-pan: DOM nodes -8312 -8312 +0%
vue-large-graph-pan: event listeners -16387 -16380 -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 2ms +140% ⚠️ z=2.6
workflow-execution: style recalc duration 9ms 18ms +94% z=-2.7
workflow-execution: layout count 2 7 +225% z=2.8
workflow-execution: style recalc count 6 10 +58% z=-4.0
workflow-execution: task duration 54ms 85ms +58% z=-3.5
workflow-execution: script duration 4ms 7ms +84% z=-7.5
workflow-execution: TBT 0ms 0ms +0%
workflow-execution: heap used 61.4 MB 61.4 MB +0%
workflow-execution: DOM nodes 123 133 +8% z=-4.0
workflow-execution: event listeners 25 26 +4% z=-5.9
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-20T03:38:05.059Z",
  "gitSha": "aa2cfa97e35941bf3bdc38f41e54b95db57833a7",
  "branch": "fix/layer-editor-review-nits",
  "measurements": [
    {
      "name": "canvas-idle",
      "durationMs": 2047.2110000000043,
      "styleRecalcs": 7,
      "styleRecalcDurationMs": 5.9430000000000005,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 511.48199999999997,
      "heapDeltaBytes": 15288132,
      "heapUsedBytes": 76564116,
      "domNodes": -285,
      "jsHeapTotalBytes": 4448256,
      "scriptDurationMs": 7.285,
      "eventListeners": -151,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "canvas-idle",
      "durationMs": 2054.4869999999946,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 9.358,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 539.871,
      "heapDeltaBytes": 15157984,
      "heapUsedBytes": 76499812,
      "domNodes": -280,
      "jsHeapTotalBytes": 3923968,
      "scriptDurationMs": 9.427999999999999,
      "eventListeners": -151,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 2021.2339999999926,
      "styleRecalcs": 77,
      "styleRecalcDurationMs": 41.344,
      "layouts": 12,
      "layoutDurationMs": 3.6299999999999994,
      "taskDurationMs": 990.0659999999999,
      "heapDeltaBytes": -4597616,
      "heapUsedBytes": 56813736,
      "domNodes": -285,
      "jsHeapTotalBytes": 4186112,
      "scriptDurationMs": 109.23,
      "eventListeners": -151,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.670000000000012,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 1878.1270000000632,
      "styleRecalcs": 74,
      "styleRecalcDurationMs": 40.324,
      "layouts": 12,
      "layoutDurationMs": 3.9579999999999997,
      "taskDurationMs": 901.694,
      "heapDeltaBytes": -5393448,
      "heapUsedBytes": 55740452,
      "domNodes": -283,
      "jsHeapTotalBytes": 4710400,
      "scriptDurationMs": 108.392,
      "eventListeners": -153,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "canvas-zoom-sweep",
      "durationMs": 1744.466999999986,
      "styleRecalcs": 31,
      "styleRecalcDurationMs": 19.264999999999997,
      "layouts": 6,
      "layoutDurationMs": 0.821,
      "taskDurationMs": 439.094,
      "heapDeltaBytes": 2760196,
      "heapUsedBytes": 64179348,
      "domNodes": 76,
      "jsHeapTotalBytes": 4718592,
      "scriptDurationMs": 14.278999999999998,
      "eventListeners": 19,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "canvas-zoom-sweep",
      "durationMs": 1725.8350000000746,
      "styleRecalcs": 31,
      "styleRecalcDurationMs": 16.231999999999996,
      "layouts": 6,
      "layoutDurationMs": 0.586,
      "taskDurationMs": 364.01300000000003,
      "heapDeltaBytes": 2474232,
      "heapUsedBytes": 63821804,
      "domNodes": 75,
      "jsHeapTotalBytes": 4456448,
      "scriptDurationMs": 10.030000000000001,
      "eventListeners": 19,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 560.7750000000351,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 7.297999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 350.5160000000001,
      "heapDeltaBytes": 10526288,
      "heapUsedBytes": 73014700,
      "domNodes": 18,
      "jsHeapTotalBytes": 4980736,
      "scriptDurationMs": 52.36999999999999,
      "eventListeners": 0,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 570.4640000000154,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 7.159000000000001,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 364.30300000000005,
      "heapDeltaBytes": 10739984,
      "heapUsedBytes": 71833100,
      "domNodes": 16,
      "jsHeapTotalBytes": 4718592,
      "scriptDurationMs": 55.486999999999995,
      "eventListeners": 2,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "large-graph-idle",
      "durationMs": 2023.46,
      "styleRecalcs": 9,
      "styleRecalcDurationMs": 7.173000000000003,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 630.4019999999999,
      "heapDeltaBytes": 13060820,
      "heapUsedBytes": 87693968,
      "domNodes": -282,
      "jsHeapTotalBytes": 2879488,
      "scriptDurationMs": 14.962999999999997,
      "eventListeners": -179,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.699999999999818
    },
    {
      "name": "large-graph-idle",
      "durationMs": 2037.093999999911,
      "styleRecalcs": 9,
      "styleRecalcDurationMs": 10.505,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 633.088,
      "heapDeltaBytes": -4532540,
      "heapUsedBytes": 71319620,
      "domNodes": -277,
      "jsHeapTotalBytes": -528384,
      "scriptDurationMs": 17.558,
      "eventListeners": -151,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "large-graph-pan",
      "durationMs": 2159.631999999988,
      "styleRecalcs": 69,
      "styleRecalcDurationMs": 15.036000000000005,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1180.1209999999999,
      "heapDeltaBytes": -4698832,
      "heapUsedBytes": 71824112,
      "domNodes": -270,
      "jsHeapTotalBytes": -565248,
      "scriptDurationMs": 316.913,
      "eventListeners": -147,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "large-graph-pan",
      "durationMs": 2160.87600000003,
      "styleRecalcs": 69,
      "styleRecalcDurationMs": 15.982000000000003,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1198.453,
      "heapDeltaBytes": -13293380,
      "heapUsedBytes": 63481136,
      "domNodes": -277,
      "jsHeapTotalBytes": 1568768,
      "scriptDurationMs": 319.587,
      "eventListeners": -147,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-zoom",
      "durationMs": 3214.683999999977,
      "styleRecalcs": 65,
      "styleRecalcDurationMs": 15.675999999999995,
      "layouts": 60,
      "layoutDurationMs": 8.129,
      "taskDurationMs": 1403.7469999999998,
      "heapDeltaBytes": -10064552,
      "heapUsedBytes": 67803196,
      "domNodes": -287,
      "jsHeapTotalBytes": 4452352,
      "scriptDurationMs": 393.904,
      "eventListeners": -155,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666696,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "large-graph-zoom",
      "durationMs": 3136.3979999999856,
      "styleRecalcs": 64,
      "styleRecalcDurationMs": 14.810999999999998,
      "layouts": 60,
      "layoutDurationMs": 7.963999999999999,
      "taskDurationMs": 1382.9669999999999,
      "heapDeltaBytes": -1113532,
      "heapUsedBytes": 76952300,
      "domNodes": -275,
      "jsHeapTotalBytes": -2101248,
      "scriptDurationMs": 384.324,
      "eventListeners": -145,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "legacy-node-drag",
      "durationMs": 2221.6399999999794,
      "styleRecalcs": 45,
      "styleRecalcDurationMs": 9.294,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1394.882,
      "heapDeltaBytes": 11943572,
      "heapUsedBytes": 95606600,
      "domNodes": 10,
      "jsHeapTotalBytes": 8581120,
      "scriptDurationMs": 456.173,
      "eventListeners": 186,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "legacy-node-drag",
      "durationMs": 2259.531000000038,
      "styleRecalcs": 46,
      "styleRecalcDurationMs": 10.883000000000001,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1421.4640000000002,
      "heapDeltaBytes": 10779140,
      "heapUsedBytes": 89555788,
      "domNodes": 12,
      "jsHeapTotalBytes": 7532544,
      "scriptDurationMs": 466.252,
      "eventListeners": 186,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "minimap-idle",
      "durationMs": 2039.9429999999938,
      "styleRecalcs": 8,
      "styleRecalcDurationMs": 6.959999999999997,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 770.4899999999999,
      "heapDeltaBytes": -12950488,
      "heapUsedBytes": 69168628,
      "domNodes": -282,
      "jsHeapTotalBytes": 3928064,
      "scriptDurationMs": 20.610000000000003,
      "eventListeners": -151,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.670000000000012,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "minimap-idle",
      "durationMs": 2045.6349999999475,
      "styleRecalcs": 8,
      "styleRecalcDurationMs": 6.541999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 624.136,
      "heapDeltaBytes": -12801920,
      "heapUsedBytes": 69387672,
      "domNodes": -282,
      "jsHeapTotalBytes": 2355200,
      "scriptDurationMs": 16.705000000000005,
      "eventListeners": -147,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.699999999999818
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 635.0050000000351,
      "styleRecalcs": 46,
      "styleRecalcDurationMs": 10.959999999999997,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 418.693,
      "heapDeltaBytes": 11444260,
      "heapUsedBytes": 72803124,
      "domNodes": 18,
      "jsHeapTotalBytes": 5505024,
      "scriptDurationMs": 123.854,
      "eventListeners": 8,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 583.0609999999297,
      "styleRecalcs": 48,
      "styleRecalcDurationMs": 11.174,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 382.53000000000003,
      "heapDeltaBytes": 11915696,
      "heapUsedBytes": 73376628,
      "domNodes": 22,
      "jsHeapTotalBytes": 5505024,
      "scriptDurationMs": 119.061,
      "eventListeners": 8,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "subgraph-idle",
      "durationMs": 2009.3590000000177,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 9.383000000000001,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 498.878,
      "heapDeltaBytes": -5810816,
      "heapUsedBytes": 55548512,
      "domNodes": -280,
      "jsHeapTotalBytes": 4710400,
      "scriptDurationMs": 7.1819999999999995,
      "eventListeners": -151,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "subgraph-idle",
      "durationMs": 2042.748999999958,
      "styleRecalcs": 9,
      "styleRecalcDurationMs": 8.005,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 532.0709999999999,
      "heapDeltaBytes": 3924324,
      "heapUsedBytes": 65439500,
      "domNodes": -281,
      "jsHeapTotalBytes": 4448256,
      "scriptDurationMs": 8.607999999999997,
      "eventListeners": -181,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1762.61199999999,
      "styleRecalcs": 76,
      "styleRecalcDurationMs": 36.504000000000005,
      "layouts": 16,
      "layoutDurationMs": 4.609999999999999,
      "taskDurationMs": 791.763,
      "heapDeltaBytes": 16527548,
      "heapUsedBytes": 78042844,
      "domNodes": -280,
      "jsHeapTotalBytes": 4448256,
      "scriptDurationMs": 79.597,
      "eventListeners": -151,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1709.2840000000251,
      "styleRecalcs": 75,
      "styleRecalcDurationMs": 38.958,
      "layouts": 16,
      "layoutDurationMs": 4.809,
      "taskDurationMs": 829.4090000000001,
      "heapDeltaBytes": 15595568,
      "heapUsedBytes": 76704236,
      "domNodes": -279,
      "jsHeapTotalBytes": 5234688,
      "scriptDurationMs": 89.88600000000001,
      "eventListeners": -151,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "subgraph-transition-enter",
      "durationMs": 1384.7640000000183,
      "styleRecalcs": 19,
      "styleRecalcDurationMs": 32.002,
      "layouts": 14,
      "layoutDurationMs": 13.889999999999999,
      "taskDurationMs": 951.7549999999999,
      "heapDeltaBytes": -7852608,
      "heapUsedBytes": 86823724,
      "domNodes": 13673,
      "jsHeapTotalBytes": 12320768,
      "scriptDurationMs": 17.668999999999997,
      "eventListeners": 2375,
      "totalBlockingTimeMs": 131,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "viewport-pan-sweep",
      "durationMs": 8532.553000000007,
      "styleRecalcs": 250,
      "styleRecalcDurationMs": 44.98700000000001,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 4556.698,
      "heapDeltaBytes": -2582808,
      "heapUsedBytes": 73279296,
      "domNodes": -277,
      "jsHeapTotalBytes": 745472,
      "scriptDurationMs": 1040.39,
      "eventListeners": -131,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.80000000000109
    },
    {
      "name": "viewport-pan-sweep",
      "durationMs": 8278.654999999959,
      "styleRecalcs": 249,
      "styleRecalcDurationMs": 42.24400000000001,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 4182.593,
      "heapDeltaBytes": -10156044,
      "heapUsedBytes": 64706236,
      "domNodes": -282,
      "jsHeapTotalBytes": 4452352,
      "scriptDurationMs": 963.1030000000001,
      "eventListeners": -163,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "vue-large-graph-idle",
      "durationMs": 18387.598000000027,
      "styleRecalcs": 0,
      "styleRecalcDurationMs": 0,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 17625.29,
      "heapDeltaBytes": -22169984,
      "heapUsedBytes": 184732028,
      "domNodes": -8312,
      "jsHeapTotalBytes": -9900032,
      "scriptDurationMs": 120.402,
      "eventListeners": -16387,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 17.779999999999927,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "vue-large-graph-idle",
      "durationMs": 18750.69600000006,
      "styleRecalcs": 0,
      "styleRecalcDurationMs": 0,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 17823.449999999997,
      "heapDeltaBytes": -27644448,
      "heapUsedBytes": 179592020,
      "domNodes": -8312,
      "jsHeapTotalBytes": -13570048,
      "scriptDurationMs": 135.44899999999998,
      "eventListeners": -16389,
      "totalBlockingTimeMs": 3,
      "frameDurationMs": 17.776666666666642,
      "p95FrameDurationMs": 16.80000000000291
    },
    {
      "name": "vue-large-graph-pan",
      "durationMs": 23511.266999999974,
      "styleRecalcs": 175,
      "styleRecalcDurationMs": 27.907000000000014,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 23144.033999999996,
      "heapDeltaBytes": -14140720,
      "heapUsedBytes": 194092368,
      "domNodes": -8312,
      "jsHeapTotalBytes": -12820480,
      "scriptDurationMs": 469.13899999999995,
      "eventListeners": -16381,
      "totalBlockingTimeMs": 147,
      "frameDurationMs": 18.33666666666662,
      "p95FrameDurationMs": 16.80000000000291
    },
    {
      "name": "vue-large-graph-pan",
      "durationMs": 23022.667999999954,
      "styleRecalcs": 177,
      "styleRecalcDurationMs": 23.089000000000027,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 22331.613999999998,
      "heapDeltaBytes": -26558444,
      "heapUsedBytes": 194170180,
      "domNodes": -8312,
      "jsHeapTotalBytes": -10756096,
      "scriptDurationMs": 452.26199999999994,
      "eventListeners": -16379,
      "totalBlockingTimeMs": 227,
      "frameDurationMs": 17.780000000000047,
      "p95FrameDurationMs": 16.80000000000291
    },
    {
      "name": "workflow-execution",
      "durationMs": 138.77999999999702,
      "styleRecalcs": 13,
      "styleRecalcDurationMs": 21.875,
      "layouts": 10,
      "layoutDurationMs": 2.7920000000000003,
      "taskDurationMs": 98.76299999999999,
      "heapDeltaBytes": 3198456,
      "heapUsedBytes": 64511852,
      "domNodes": 139,
      "jsHeapTotalBytes": 0,
      "scriptDurationMs": 8.843,
      "eventListeners": 25,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "workflow-execution",
      "durationMs": 110.80600000002505,
      "styleRecalcs": 6,
      "styleRecalcDurationMs": 14.528,
      "layouts": 3,
      "layoutDurationMs": 1.068,
      "taskDurationMs": 71.42300000000002,
      "heapDeltaBytes": 2949564,
      "heapUsedBytes": 64267920,
      "domNodes": 126,
      "jsHeapTotalBytes": 0,
      "scriptDurationMs": 4.619,
      "eventListeners": 27,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    }
  ]
}

@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown

🎨 Storybook: 🚧 Building...

@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The pull request adds layer-panel drag-and-drop reordering, alpha-aware layer picking, localized validation feedback, transient compositor-state persistence, safer numeric input handling, and fixes for history merging and render-target cleanup.

Changes

Layer editor interaction and reliability

Layer / File(s) Summary
Session picking and layer movement
src/renderer/extensions/layerEditor/composables/useLayerEditorSession.*
The session supports alpha-aware picking, guarded layer movement, transform-handle precedence, and related tests.
Layer panel drag-and-drop reordering
src/renderer/extensions/layerEditor/composables/layerPanelDnd.*, src/renderer/extensions/layerEditor/components/LayerPanel.vue
Layer rows calculate above or below drop positions, show drop hints, and call moveLayerTo.
Editor feedback and input validation
src/renderer/extensions/layerEditor/components/*, src/renderer/extensions/layerEditor/composables/useLayerEditor.*, src/locales/en/main.json
The editor adds localized insufficient-image feedback, safer blank-input handling, stricter blend-mode checks, focus styling, and viewport resize guards.
Compositor widget state handling
src/renderer/extensions/compositor/components/WidgetCompositor.*
The widget normalizes missing node values and tests empty and enabled states.
Layer opacity sampling
src/renderer/extensions/layerEditor/engine/editor/pickOps.*
Alpha sampling creates a rendering context per call and applies group opacity to sampled child opacity.
Workflow transient compositor state
src/platform/workflow/management/workflowTransientState.*, src/renderer/extensions/compositor/composables/useCompositorLayers.*, src/extensions/core/imageCompositor.ts, src/scripts/changeTracker.ts
Workflow state providers snapshot and restore compositor layer caches through change-tracker storage.
History and render cleanup reliability
src/renderer/extensions/layerEditor/engine/history.*, src/renderer/extensions/layerEditor/engine/render/renderStack.*, src/renderer/extensions/layerEditor/engine/compositor/webglCompositor.ts
History preserves save boundaries during command merging. Rendering frees allocated targets when compositing fails. The context-loss warning uses the LayerEditor prefix.

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

Suggested reviewers: jaeone94

Sequence Diagram(s)

sequenceDiagram
  participant LayerPanel
  participant layerPanelDnd
  participant useLayerEditorSession
  participant History
  LayerPanel->>layerPanelDnd: calculate drop position
  layerPanelDnd-->>LayerPanel: return destination index
  LayerPanel->>useLayerEditorSession: call moveLayerTo
  useLayerEditorSession->>History: record layer movement
  History-->>useLayerEditorSession: update undo state
Loading
🚥 Pre-merge checks | ✅ 3 | ❌ 3

❌ Failed checks (1 warning, 2 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description summarizes the main changes, but it omits the required Changes, Review Focus, and Screenshots sections. Add the required Changes and Review Focus sections, and add Screenshots or state that screenshots are not applicable.
End-To-End Regression Coverage For Fixes ❓ Inconclusive Repository clone failed, so this custom check could not run with code access. Retry the review run. If this persists, inspect pre-merge custom-check logs for infrastructure or agent runtime failures.
Adr Compliance For Entity/Litegraph Changes ❓ Inconclusive Repository clone failed, so this custom check could not run with code access. Retry the review run. If this persists, inspect pre-merge custom-check logs for infrastructure or agent runtime failures.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately identifies layer editor UX fixes and review follow-ups, but it is broad relative to the many specific changes.
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.
✨ 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 fix/layer-editor-review-nits

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

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 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/renderer/extensions/compositor/components/WidgetCompositor.test.ts`:
- Around line 43-45: Remove the vue-i18n mock around WidgetCompositor and update
renderWidget to install the project’s real i18n test plugin/configuration.
Ensure WidgetCompositor’s useI18n call resolves through the installed plugin
while preserving the existing test rendering behavior.

In `@src/renderer/extensions/layerEditor/components/PropertyNumberField.test.ts`:
- Around line 28-33: Update the emptied-field test around commitValue and the
restored-value assertion to await Vue’s nextTick after tabbing before reading
input.value. Keep the existing commit assertion and verify the field restores to
“40” only after the scheduled draft reset has rendered.

In `@src/renderer/extensions/layerEditor/composables/layerPanelDnd.ts`:
- Around line 7-16: The drop-index calculation must account for removal of the
dragged layer. In
src/renderer/extensions/layerEditor/composables/layerPanelDnd.ts lines 7-16,
update reorderDropIndex to accept draggedId, remove it before finding targetId,
and calculate the position from the filtered list. In
src/renderer/extensions/layerEditor/components/LayerPanel.vue lines 244-250,
pass dragged to reorderDropIndex. In
src/renderer/extensions/layerEditor/composables/layerPanelDnd.test.ts lines
13-32, add coverage for dragged layers both before and after the target.

In `@src/renderer/extensions/layerEditor/engine/history.ts`:
- Around line 100-105: The commit path around merge handling must invalidate the
saved-state reachability when branching from an undone state: before clearing
redoStack, check whether it contains mergeBarrier and set cleanReachable to
false when present. Preserve existing dirtyCount behavior and add a regression
test covering push A, markSaved, undo A, then push B.

In `@src/renderer/extensions/layerEditor/engine/render/renderStack.ts`:
- Around line 286-294: Make the nested group allocation path in buildInputs
exception-safe: when the group composite using handle throws, free handle and
invoke sub.cleanup() before propagating the error. Preserve normal cleanup
ownership for successful composites, and extend the regression test to cover a
throwing composite with a non-null target.
🪄 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: 4feb2437-f0a6-415f-be59-b83f3d1d2b20

📥 Commits

Reviewing files that changed from the base of the PR and between bdc0345 and 4367182.

📒 Files selected for processing (19)
  • src/locales/en/main.json
  • src/renderer/extensions/compositor/components/WidgetCompositor.test.ts
  • src/renderer/extensions/compositor/components/WidgetCompositor.vue
  • src/renderer/extensions/layerEditor/components/LayerEditorCanvas.vue
  • src/renderer/extensions/layerEditor/components/LayerPanel.vue
  • src/renderer/extensions/layerEditor/components/LayerPropertiesPanel.vue
  • src/renderer/extensions/layerEditor/components/PropertyNumberField.test.ts
  • src/renderer/extensions/layerEditor/components/PropertyNumberField.vue
  • src/renderer/extensions/layerEditor/composables/layerPanelDnd.test.ts
  • src/renderer/extensions/layerEditor/composables/layerPanelDnd.ts
  • src/renderer/extensions/layerEditor/composables/useLayerEditor.test.ts
  • src/renderer/extensions/layerEditor/composables/useLayerEditor.ts
  • src/renderer/extensions/layerEditor/composables/useLayerEditorSession.test.ts
  • src/renderer/extensions/layerEditor/composables/useLayerEditorSession.ts
  • src/renderer/extensions/layerEditor/engine/compositor/webglCompositor.ts
  • src/renderer/extensions/layerEditor/engine/history.test.ts
  • src/renderer/extensions/layerEditor/engine/history.ts
  • src/renderer/extensions/layerEditor/engine/render/renderStack.test.ts
  • src/renderer/extensions/layerEditor/engine/render/renderStack.ts

Comment thread src/renderer/extensions/compositor/components/WidgetCompositor.test.ts Outdated
Comment thread src/renderer/extensions/layerEditor/composables/layerPanelDnd.ts
Comment thread src/renderer/extensions/layerEditor/engine/history.ts
Comment thread src/renderer/extensions/layerEditor/engine/render/renderStack.ts

@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

Caution

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

⚠️ Outside diff range comments (1)
src/renderer/extensions/compositor/components/WidgetCompositor.test.ts (1)

92-103: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Cover the node-present, no-layers branch.

The current tests cover a missing node and a node with cached layers. They do not cover an existing node when hasCompositorLayers(node) returns false. WidgetCompositor.vue requires both conditions before enabling the button. Add this case and assert that the open button remains disabled. Otherwise, a regression to node-only availability would pass.

As per path instructions, colocated Vitest tests must cover the changed widget-state behavior with real behavioral assertions.

🤖 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/renderer/extensions/compositor/components/WidgetCompositor.test.ts`
around lines 92 - 103, Extend the colocated WidgetCompositor tests with an
existing graph node that has no compositor layers, ensuring
hasCompositorLayers(graphNode) remains false; render via renderWidget and assert
the compositor-open-button is disabled. Preserve the existing missing-node and
cached-layer cases.

Source: Path instructions

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

Inline comments:
In `@src/renderer/extensions/compositor/components/WidgetCompositor.test.ts`:
- Around line 84-88: Update the rendered-state assertions in the
WidgetCompositor test to query the empty message with getByText and the
compositor control with getByRole('button', { name: 'Open Compositor' }); remove
the data-testid-based lookups and assert the button through its accessible name.

In
`@src/renderer/extensions/layerEditor/composables/useLayerEditorSession.test.ts`:
- Around line 391-392: Update the test helper idOf and the related
reorderDropIndex usage to explicitly check nullable results before accessing or
passing them to moveLayerTo. Fail with clear messages when a layer name is
missing or the drop index is unavailable, and remove the non-null assertions
while preserving the existing test flow.

---

Outside diff comments:
In `@src/renderer/extensions/compositor/components/WidgetCompositor.test.ts`:
- Around line 92-103: Extend the colocated WidgetCompositor tests with an
existing graph node that has no compositor layers, ensuring
hasCompositorLayers(graphNode) remains false; render via renderWidget and assert
the compositor-open-button is disabled. Preserve the existing missing-node and
cached-layer cases.
🪄 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: bad2ba8c-83ca-48f5-aed5-89ef1272abd9

📥 Commits

Reviewing files that changed from the base of the PR and between 4367182 and 5a910e1.

📒 Files selected for processing (7)
  • src/renderer/extensions/compositor/components/WidgetCompositor.test.ts
  • src/renderer/extensions/layerEditor/components/PropertyNumberField.test.ts
  • src/renderer/extensions/layerEditor/composables/useLayerEditorSession.test.ts
  • src/renderer/extensions/layerEditor/engine/history.test.ts
  • src/renderer/extensions/layerEditor/engine/history.ts
  • src/renderer/extensions/layerEditor/engine/render/renderStack.test.ts
  • src/renderer/extensions/layerEditor/engine/render/renderStack.ts

@codecov

codecov Bot commented Aug 8, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 63.07692% with 48 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...r/extensions/layerEditor/components/LayerPanel.vue 0.00% 43 Missing ⚠️
...sions/layerEditor/components/LayerEditorCanvas.vue 0.00% 3 Missing ⚠️
...ns/layerEditor/components/LayerPropertiesPanel.vue 0.00% 1 Missing ⚠️
...er/extensions/layerEditor/engine/editor/pickOps.ts 75.00% 1 Missing ⚠️
@@            Coverage Diff             @@
##             main   #14941      +/-   ##
==========================================
+ Coverage   79.22%   81.76%   +2.53%     
==========================================
  Files        2213     1887     -326     
  Lines      113476   107256    -6220     
  Branches    34896    33477    -1419     
==========================================
- Hits        89898    87693    -2205     
+ Misses      23106    19208    -3898     
+ Partials      472      355     -117     
Flag Coverage Δ
unit 73.29% <63.07%> (+0.03%) ⬆️
website-unit ?

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

Files with missing lines Coverage Δ
src/extensions/core/imageCompositor.ts 100.00% <100.00%> (ø)
...ensions/compositor/components/WidgetCompositor.vue 70.00% <100.00%> (+70.00%) ⬆️
...ons/compositor/composables/compositorLayerState.ts 97.05% <100.00%> (+0.03%) ⬆️
...nsions/compositor/composables/compositorSession.ts 100.00% <100.00%> (ø)
...ions/compositor/composables/useCompositorLayers.ts 85.36% <100.00%> (+1.15%) ⬆️
...ons/layerEditor/components/PropertyNumberField.vue 89.47% <100.00%> (+78.36%) ⬆️
...xtensions/layerEditor/composables/layerPanelDnd.ts 100.00% <100.00%> (ø)
...tensions/layerEditor/composables/useLayerEditor.ts 100.00% <100.00%> (ø)
...s/layerEditor/composables/useLayerEditorSession.ts 94.02% <100.00%> (+0.04%) ⬆️
.../renderer/extensions/layerEditor/engine/history.ts 100.00% <100.00%> (ø)
... and 5 more

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

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

@christian-byrne christian-byrne left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review — layer editor UX follow-ups

15 parallel agents (12 domain + 3 adversarial/skeptic) reviewed this PR. One blind-spot candidate (missing crossOrigin) was a false positive — already set at useLayerEditorSession.ts:82. All findings below are post-skeptic confirmed.


issue: layerOpacityAt ignores group-level opacity — wrong-layer picks on semi-transparent groups

src/renderer/extensions/layerEditor/engine/editor/pickOps.ts line 81

The group case returns best (max child alpha) without multiplying by node.opacity. A group set to e.g. 20% opacity with fully-opaque raster children returns 1.0, exceeding PICK_OPACITY_THRESHOLD — the click lands on the nearly-invisible group instead of passing through. This contradicts the pixel-accurate picking intent of this PR.

// current
return best

// fix
return best * node.opacity

The existing if (node.opacity <= 0) return 0 early-out already handles the fully-transparent edge case, so this one-line change is safe.


issue: onRowDrop calls e.preventDefault() unconditionally — swallows external file drops

src/renderer/extensions/layerEditor/components/LayerPanel.vue, onRowDrop

onRowDragOver correctly guards with if (!dragId.value || dragId.value === id) return (without calling preventDefault), showing the no-drop cursor for external drags. But onRowDrop calls e.preventDefault() before checking dragId.value, silently swallowing any external file drop and preventing an outer upload zone from handling it.

function onRowDrop(id: string, e: DragEvent): void {
  const dragged = dragId.value
  const hint = dropHint.value
  if (!dragged || hint?.id !== id) {
    endDrag()
    return // let external drops propagate
  }
  e.preventDefault()
  // ... rest
}

suggestion: (non-blocking) No @dragleavedropHint indicator persists when cursor exits panel

src/renderer/extensions/layerEditor/components/LayerPanel.vue (three agents flagged independently)

When the cursor moves from a row to the scrollbar or a gap between rows, dragover stops firing but dragend has not yet. The drop-indicator line stays on the last-hovered row. A container-level @dragleave checking e.relatedTarget would clear dropHint cleanly:

<div class="..." @dragleave="onContainerDragLeave">
function onContainerDragLeave(e: DragEvent): void {
  const container = e.currentTarget as HTMLElement
  if (!container.contains(e.relatedTarget as Node | null)) endDrag()
}

question: Does clicking a selected layer on a transparent interior with nothing below intentionally deselect it?

src/renderer/extensions/layerEditor/composables/useLayerEditorSession.ts:871

With insideBox removed: user has raster layer A selected → clicks inside A's bounding box on a transparent pixel where no other layer is underneath → pickLayerAt returns null → setSelectedNodes([]) fires → A is deselected → drag does not start.

In Figma/PS, clicking inside a selected layer's bounding box always initiates drag regardless of pixel transparency. The PR description covers "transparent pixels fall through" for unselected layers but does not address this edge case for already-selected layers. Is this intentional? If so a brief note would help future reviewers.


suggestion: (non-blocking) buildInputssub resources may leak if allocTarget throws before cleanup registration

src/renderer/extensions/layerEditor/engine/render/renderStack.ts

The exception-safety refactor handles composite(sub.inputs, handle) throwing correctly. However, between buildInputs(g, ...) returning sub and the allocTarget call entering the inner try block, if allocTarget throws, the outer catch runs accumulated cleanups — but sub.cleanup is not yet registered there, so sub's allocated GPU targets are orphaned.

In practice this is GPU-loss territory where the render loop halts anyway, so practical impact is low. But structurally the exception-safety story has this gap. Fix: register sub.cleanup in cleanups immediately after buildInputs returns, or wrap the post-buildInputs block (including allocTarget) in its own try/catch that calls sub.cleanup() on throw.


suggestion: (non-blocking) mergeBarrier becomes stale after undo() and evict()

src/renderer/extensions/layerEditor/engine/history.ts

After markSaved() sets mergeBarrier to the undo-stack top, a subsequent undo() moves that command to redoStack. The barrier ref is not cleared. On the next push() (which clears redoStack), mergeBarrier points to a command in neither stack — merge-barrier protection for that save point is silently lost.

Additionally, evict() can remove the barrier command from the undo stack while mergeBarrier still holds a strong reference, preventing GC of the command's undo-state snapshot until the next clear() or markSaved().

// in undo():
if (cmd === this.mergeBarrier) this.mergeBarrier = null

// in evict():
if (dropped === this.mergeBarrier) this.mergeBarrier = null

nitpick: (non-blocking) dataTransfer.setData is dead code with a subtle side effect

src/renderer/extensions/layerEditor/components/LayerPanel.vue, onRowDragStart

dragId.value is the source of truth in onRowDrop — the getData counterpart is never called. Side effect: browsers treat this as a text drag, so dropping a layer row onto any text input or external drop zone delivers the layer UUID as typed text.

if (e.dataTransfer) {
  e.dataTransfer.effectAllowed = 'move'
  // remove: e.dataTransfer.setData('text/plain', id)
}

nitpick: (non-blocking) reorderDropIndex — document the bottom-up z-order convention

src/renderer/extensions/layerEditor/composables/layerPanelDnd.ts

The visual panel rows are rendered top-down ([...imageLayers].reverse()) but bottomUpIds is passed bottom-up (z=0 at index 0). The formula pos === 'above' ? index + 1 : index depends on this inversion — "above" in visual space = higher z-index = higher array index. A doc comment prevents future readers from second-guessing the math:

/**
 * Returns the target index in root.children for a drag-drop reorder.
 * @param bottomUpIds  IDs ordered z=0 (bottom) to z=n (top) — inverse of panel display order.
 * @param pos  Visual drop position. 'above' = higher z-order (index + 1).
 * @param offset  Reserved bottom slots (1 if a pinned background fill exists, else 0).
 */
export function reorderDropIndex(

nitpick: (non-blocking) History merge path intentionally omits bumpDirty() — add a comment

src/renderer/extensions/layerEditor/engine/history.ts (merge path)

The missing bumpDirty() in the merge path is the core of the dirty-tracking fix (merged edits share one dirty step). Worth a one-liner so a future reader does not add it back thinking it was accidentally dropped:

// Merged edits share the first commit's dirty step — intentionally no bumpDirty() here.
this.emit(cmd.dirtyMask)

nitpick: (non-blocking) dropHintClass returns 'relative' for every row — add it to rowClass once

src/renderer/extensions/layerEditor/components/LayerPanel.vue:211

Since pseudo-elements need position: relative on the parent, dropHintClass returns 'relative' for all rows (hinted and non-hinted). Cleaner to add relative to rowClass once and have dropHintClass return '' for non-hinted rows.


suggestion: (non-blocking) history.test.ts — add redo-after-undo-of-merged-edit coverage

src/renderer/extensions/layerEditor/engine/history.test.ts

The new tests verify the undo direction. The redo round-trip is unverified:

it('redo after undoing a merge makes dirty again', () => {
  const h = new History()
  h.push(new MergingCommand('opacity'))
  h.push(new MergingCommand('opacity'))
  h.markSaved()
  h.push(new MergingCommand('opacity'))
  h.undo()
  expect(h.dirty()).toBe(false)
  h.redo()
  expect(h.dirty()).toBe(true)
})

nitpick: (non-blocking) sampleCache WeakMap may be unnecessary

src/renderer/extensions/layerEditor/engine/editor/pickOps.ts:13

canvas.getContext('2d') is idempotent per the HTML spec — browsers return the same context object on every call. The WeakMap adds a three-way cache-state check (undefined vs null vs context) and a subtle risk: if a canvas later acquires a WebGL context (getContext('2d') would return null), the cached non-null 2D context produces stale pixel reads. Consider removing it and calling getContext('2d') directly, or at minimum document that content canvases are never repurposed for WebGL.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

Inline comments:
In `@src/renderer/extensions/layerEditor/engine/editor/pickOps.test.ts`:
- Around line 85-96: Replace the `group` fixture’s `unknown`-based cast with a
complete object validated using `satisfies GroupData`, including the correct
typed value for `mode` and all required group fields. If an existing typed group
factory is available, reuse it instead, while preserving the fixture’s current
test values.
🪄 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: e6a6b63b-4519-485a-8d44-5a02f2b58930

📥 Commits

Reviewing files that changed from the base of the PR and between 5a910e1 and 9634002.

📒 Files selected for processing (8)
  • src/renderer/extensions/layerEditor/components/LayerPanel.vue
  • src/renderer/extensions/layerEditor/composables/layerPanelDnd.ts
  • src/renderer/extensions/layerEditor/engine/editor/pickOps.test.ts
  • src/renderer/extensions/layerEditor/engine/editor/pickOps.ts
  • src/renderer/extensions/layerEditor/engine/history.test.ts
  • src/renderer/extensions/layerEditor/engine/history.ts
  • src/renderer/extensions/layerEditor/engine/render/renderStack.test.ts
  • src/renderer/extensions/layerEditor/engine/render/renderStack.ts

Comment thread src/renderer/extensions/layerEditor/engine/editor/pickOps.test.ts
coderabbitai[bot]
coderabbitai Bot previously approved these changes Aug 9, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
src/scripts/changeTracker.ts (1)

313-359: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Add ChangeTracker integration coverage.

Add a test that stores transient state for one workflow, switches state, and restores the original workflow. Assert that compositor layers are restored. Also assert that a workflow without transient state clears the compositor cache.

The provided unit tests do not exercise the ChangeTracker.store() to ChangeTracker.restore() contract.

As per coding guidelines, “Write tests for code changes.”

🤖 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/scripts/changeTracker.ts` around lines 313 - 359, Add integration
coverage for the ChangeTracker.store() and ChangeTracker.restore() contract:
save transient state for one workflow, switch workflow state, restore the
original workflow, and assert its compositor layers are restored. Also cover
restoring a workflow with no transient state and verify that the compositor
cache is cleared, using the existing ChangeTracker and compositor-state test
utilities.

Source: Coding guidelines

🤖 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/extensions/core/imageCompositor.ts`:
- Around line 23-27: Update the restore callback for
registerWorkflowTransientState('Comfy.ImageCompositor.layers') to validate the
unknown state as a valid ReadonlyMap<NodeLocatorId, CompositorNodeCache> before
calling restoreCompositorLayers. For invalid or unsupported values, clear the
compositor state instead, preventing restoreCompositorLayers from throwing or
creating invalid cache entries.

---

Outside diff comments:
In `@src/scripts/changeTracker.ts`:
- Around line 313-359: Add integration coverage for the ChangeTracker.store()
and ChangeTracker.restore() contract: save transient state for one workflow,
switch workflow state, restore the original workflow, and assert its compositor
layers are restored. Also cover restoring a workflow with no transient state and
verify that the compositor cache is cleared, using the existing ChangeTracker
and compositor-state test utilities.
🪄 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: a7647414-f250-475f-baac-fd4c047d0f83

📥 Commits

Reviewing files that changed from the base of the PR and between 9634002 and c80c4fc.

📒 Files selected for processing (7)
  • src/extensions/core/imageCompositor.ts
  • src/locales/en/main.json
  • src/platform/workflow/management/workflowTransientState.test.ts
  • src/platform/workflow/management/workflowTransientState.ts
  • src/renderer/extensions/compositor/composables/useCompositorLayers.test.ts
  • src/renderer/extensions/compositor/composables/useCompositorLayers.ts
  • src/scripts/changeTracker.ts

Comment thread src/extensions/core/imageCompositor.ts Outdated

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

Caution

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

⚠️ Outside diff range comments (1)
src/scripts/changeTracker.ts (1)

313-313: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Deep-copy compositor snapshots before storing them.

snapshotCompositorLayers() copies only the Map. It shares each cache object and its mutable arrays with the live state. A later mutation through the compositor API can change the snapshot used by store(). Copy the cache values and arrays, then add a round-trip test that mutates compositor state after store() and checks that restore() returns the saved state.

🤖 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/scripts/changeTracker.ts` at line 313, Update the snapshot workflow
around snapshotWorkflowTransientState and snapshotCompositorLayers so compositor
cache values and their mutable arrays are deep-copied before being stored in
transientState. Preserve the saved snapshot when compositor state is mutated
after store(), and add a round-trip test verifying restore() returns the
original state.
🤖 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.

Outside diff comments:
In `@src/scripts/changeTracker.ts`:
- Line 313: Update the snapshot workflow around snapshotWorkflowTransientState
and snapshotCompositorLayers so compositor cache values and their mutable arrays
are deep-copied before being stored in transientState. Preserve the saved
snapshot when compositor state is mutated after store(), and add a round-trip
test verifying restore() returns the original state.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 9caafd94-ffaf-4779-b874-212d7c614896

📥 Commits

Reviewing files that changed from the base of the PR and between c80c4fc and ce68a9f.

📒 Files selected for processing (2)
  • src/locales/en/main.json
  • src/scripts/changeTracker.ts

@christian-byrne christian-byrne left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Sorry, forgot to approve this. Wrong PR

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.

Can you provide some reasoning for this? The problem being solved, why this approach was chosen, and what alternatives exist? Is there some limitation in the current systems that require this new concept?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The compositor's layer cache (temp refs + fingerprints from ui.* on execute) is the same lifecycle as nodeOutputs: execution-scoped, not serializable (backend-session temp files, putting them in the workflow JSON means dead refs cross-session).

Tab switches run configure() -> LGraph.clear(), which fires onRemoved on every node and wipes the cache; nodeOutputs survives because ChangeTracker snapshots/restores it, ours had no seat on that mechanism.

My first version wired it directly into ChangeTracker next to nodeOutputs, but that makes the tracker import a feature module, and every future node with editor state tied to execution needs another hand-written pair there.

The registry is just that same snapshot/restore lifecycle turned into an extension point, the tracker calls it at the two spots it already handles nodeOutputs, features register themselves.

No new lifecycle, no store; a missing key restores as undefined so workflows stay isolated.

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.

Thanks for the explanation. After reviewing the broader context, I do not think we should add this generic snapshot/restore registry here.

The per-workflow state TDD and proposed frontend document model identify the missing primitives as document identity and lifecycle events. This registry provides only snapshot/restore semantics, without document identity, close/disposal, or explicit ownership.

Suggested next steps:

  1. Remove the workflowTransientState commit from this PR so the layer-editor UX fixes can land independently.
  2. Model compositor state by document uid and manage it through document lifecycle events such as Deactivate, Activate, Close, and PostClose.
  3. Do not wire those events directly to beforeLoadNewGraph/afterLoadNewGraph, since those hooks also run for undo and same-document reloads. Lifecycle events must be gated on document identity transitions.

If a temporary registry is still required, it should be handled separately, marked @internal, and include an explicit removal condition tied to the document lifecycle implementation.

christian-byrne added a commit that referenced this pull request Aug 19, 2026
## Summary

Node preview images are lost permanently on a workflow tab switch,
because `app.clean()` revokes their object URLs and nothing restores
them. This scopes preview state per workflow instead.

Fixes FE-1645

## Changes

- **What**: `workflowService.beforeLoadNewGraph()` hands the live
previews to `nodeOutputStore` keyed by the outgoing workflow path, so
`app.clean()` has nothing left to revoke. `afterLoadNewGraph()` puts
back the previews belonging to the workflow that just became active.

### Before

1. Open workflow A with a KSampler, preview method anything but `none`.
2. Queue it and let it finish — the KSampler shows its final latent
preview.
3. Switch to tab B, switch back to tab A.
4. The preview is gone and never comes back. Output images on other
nodes survive the same switch.

`app.clean()` (`src/scripts/app.ts:2434`) runs on every workflow load
and calls `resetAllOutputsAndPreviews()` → `revokeAllPreviews()`
(`src/stores/nodeOutputStore.ts:301`), which releases every preview
object URL and empties the maps. `nodeOutputs` survives only because
`ChangeTracker` snapshots and restores it
(`src/scripts/changeTracker.ts:306`/`350`); previews have no equivalent.
Nothing revokes previews at execution end, so a finished run's last
frame is real state a user is looking at — and a `b_preview` websocket
frame cannot be re-fetched, so revoking makes the loss permanent.

### After

The preview is still there when you come back to the tab.

## Review Focus

- Keying by workflow path is load-bearing, not incidental. Node locator
ids for root-graph nodes are bare node ids, so simply not clearing on
load would show workflow A's node 5 preview on workflow B's node 5.
- Ownership of the object URL retain moves from the live map to the
stash and back, so nothing is released on a tab switch. Released instead
when: the workflow is cleared in place (`Clear Workflow` still calls
`app.clean()` with no load following it), the workflow has been closed,
or a preview arriving for the incoming graph supersedes the stashed one.
- Undo/redo goes through `loadGraphData(..., clean = false)` for the
same workflow — it stashes and restores under the same path, so the net
effect is unchanged.
- Deliberately not done: no `ChangeTracker` snapshot/restore field for
previews, and no use of the `workflowTransientState` mechanism proposed
in #14941, so this does not prejudge that review.

## Tests

`src/stores/nodeOutputStore.workflowSwitch.test.ts` covers the round
trip, the same-node-id cross-workflow case, release on close, and
clear-in-place. `workflowService.test.ts` covers the two lifecycle
hooks.
comfydesigner pushed a commit that referenced this pull request Aug 20, 2026
## Summary

Node preview images are lost permanently on a workflow tab switch,
because `app.clean()` revokes their object URLs and nothing restores
them. This scopes preview state per workflow instead.

Fixes FE-1645

## Changes

- **What**: `workflowService.beforeLoadNewGraph()` hands the live
previews to `nodeOutputStore` keyed by the outgoing workflow path, so
`app.clean()` has nothing left to revoke. `afterLoadNewGraph()` puts
back the previews belonging to the workflow that just became active.

### Before

1. Open workflow A with a KSampler, preview method anything but `none`.
2. Queue it and let it finish — the KSampler shows its final latent
preview.
3. Switch to tab B, switch back to tab A.
4. The preview is gone and never comes back. Output images on other
nodes survive the same switch.

`app.clean()` (`src/scripts/app.ts:2434`) runs on every workflow load
and calls `resetAllOutputsAndPreviews()` → `revokeAllPreviews()`
(`src/stores/nodeOutputStore.ts:301`), which releases every preview
object URL and empties the maps. `nodeOutputs` survives only because
`ChangeTracker` snapshots and restores it
(`src/scripts/changeTracker.ts:306`/`350`); previews have no equivalent.
Nothing revokes previews at execution end, so a finished run's last
frame is real state a user is looking at — and a `b_preview` websocket
frame cannot be re-fetched, so revoking makes the loss permanent.

### After

The preview is still there when you come back to the tab.

## Review Focus

- Keying by workflow path is load-bearing, not incidental. Node locator
ids for root-graph nodes are bare node ids, so simply not clearing on
load would show workflow A's node 5 preview on workflow B's node 5.
- Ownership of the object URL retain moves from the live map to the
stash and back, so nothing is released on a tab switch. Released instead
when: the workflow is cleared in place (`Clear Workflow` still calls
`app.clean()` with no load following it), the workflow has been closed,
or a preview arriving for the incoming graph supersedes the stashed one.
- Undo/redo goes through `loadGraphData(..., clean = false)` for the
same workflow — it stashes and restores under the same path, so the net
effect is unchanged.
- Deliberately not done: no `ChangeTracker` snapshot/restore field for
previews, and no use of the `workflowTransientState` mechanism proposed
in #14941, so this does not prejudge that review.

## Tests

`src/stores/nodeOutputStore.workflowSwitch.test.ts` covers the round
trip, the same-node-id cross-workflow case, release on close, and
clear-in-place. `workflowService.test.ts` covers the two lifecycle
hooks.
@jtydhr88
jtydhr88 force-pushed the fix/layer-editor-review-nits branch from ce68a9f to b1fb868 Compare August 20, 2026 01:22
@github-actions github-actions Bot added the risk:R2 PR risk grade (advisory shadow check; grader-owned) label Aug 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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.

2 participants