Skip to content

refactor: error system cleanup — store separation, DDD fix, test improvements#10302

Draft
jaeone94 wants to merge 3 commits intomainfrom
refactor/error-system-cleanup
Draft

refactor: error system cleanup — store separation, DDD fix, test improvements#10302
jaeone94 wants to merge 3 commits intomainfrom
refactor/error-system-cleanup

Conversation

@jaeone94
Copy link
Collaborator

@jaeone94 jaeone94 commented Mar 19, 2026

Summary

Refactors the error system to improve separation of concerns, fix DDD layer violations, and address code quality issues.

  • Extract missingNodesErrorStore from executionErrorStore, removing the delegation pattern that coupled missing-node logic into the execution error store
  • Extract useNodeErrorFlagSync composable for node error flag reconciliation (previously inlined)
  • Extract useErrorClearingHooks composable with explicit callback cleanup on node removal
  • Extract useErrorActions composable to deduplicate telemetry+command patterns across error card components
  • Move getCnrIdFromNode/getCnrIdFromProperties to platform/nodeReplacement layer (DDD fix)
  • Move missingNodesErrorStore to platform/nodeReplacement (DDD alignment)
  • Add unmount cancellation guard to useErrorReport async onMounted
  • Return watch stop handle from useNodeErrorFlagSync
  • Add asyncResolvedIds eviction on missingNodesError reset
  • Add console.warn to silent catch blocks and empty array guard
  • Hoist useCommandStore to setup scope, fix floating promises
  • Add data-testid to error groups, image/video error spans, copy button
  • Update E2E tests to use scoped locators and testids
  • Add unit tests for onNodeRemoved restoration and double-install guard

Fixes #9875, Fixes #10027, Fixes #10033, Fixes #10085

Test plan

  • Existing unit tests pass with updated imports and mocks
  • New unit tests for useErrorClearingHooks (callback restoration, double-install guard)
  • E2E tests updated to use scoped locators and data-testid
  • Manual: verify error tab shows runtime errors and missing nodes correctly
  • Manual: verify "Find on GitHub", "Copy", and "Get Help" buttons work in error cards

┆Issue is synchronized with this Notion page by Unito

@github-actions
Copy link

github-actions bot commented Mar 19, 2026

🎨 Storybook: ✅ Built — View Storybook

Details

⏰ Completed at: 03/20/2026, 12:06:33 PM UTC

Links

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 19, 2026

📝 Walkthrough

Walkthrough

Extracted missing-node error state into a new useMissingNodesErrorStore, refactored execution error responsibilities (removed missing-node logic from executionErrorStore), added useErrorActions and useNodeErrorFlagSync, relocated getCnrIdFromNode to platform, and updated tests/selectors to use new stores and test IDs.

Changes

Cohort / File(s) Summary
Test selectors & E2E tests
browser_tests/fixtures/selectors.ts, browser_tests/tests/dialog.spec.ts, browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts
Added test IDs for missing-node/model groups and image/video errors; updated E2E tests to use test IDs and scoped overlay queries.
New missing-nodes store
src/platform/nodeReplacement/missingNodesErrorStore.ts
Added Pinia store managing missing-node error payloads, deduplication, surfacing API, removal, and ancestor execution-id selectors.
Execution store refactor
src/stores/executionErrorStore.ts, src/stores/executionErrorStore.test.ts
Removed missing-node state/APIs from execution store; delegated missing-node concerns to useMissingNodesErrorStore; replaced legacy flag reconciliation with useNodeErrorFlagSync; updated tests.
Error actions & UI wiring
src/components/rightSidePanel/errors/useErrorActions.ts, src/components/rightSidePanel/errors/ErrorNodeCard.vue, src/components/rightSidePanel/errors/FindIssueButton.vue, src/components/rightSidePanel/errors/TabErrors.vue
New composable centralizes GitHub/support/telemetry actions; components delegate to it and added data-testid attributes for copy/error elements.
Node error flag sync
src/composables/graph/useNodeErrorFlagSync.ts
New exported composable that reconciles LGraph/Vue node error flags (includes missing-model handling controlled by UI setting).
Error groups & reporting
src/components/rightSidePanel/errors/useErrorGroups.ts, src/components/rightSidePanel/errors/useErrorReport.ts, src/components/rightSidePanel/errors/useErrorGroups.test.ts
Switched missing-node reads to useMissingNodesErrorStore, streamlined searchable text creation, added logging in catch blocks, and adjusted tests.
Error clearing hooks
src/composables/graph/useErrorClearingHooks.ts, src/composables/graph/useErrorClearingHooks.test.ts
Store original node callbacks in WeakMap, restore hooks on node removal, add idempotent-install guards and tests for lifecycle behavior.
Missing-node plumbing in platform & workflow
src/platform/nodeReplacement/missingNodeScan.ts, src/platform/nodeReplacement/useNodeReplacement.ts, src/platform/nodeReplacement/.../*.test.ts, src/platform/workflow/core/services/workflowService.ts, src/platform/workflow/core/services/workflowService.test.ts
Routed surfacing/removal of missing-node types through useMissingNodesErrorStore; updated imports and tests; fixed platform/workbench import DDD violation by moving getCnrIdFromNode.
Renderer & node components
src/renderer/extensions/vueNodes/components/ImagePreview.vue, src/renderer/extensions/vueNodes/VideoPreview.vue, src/renderer/extensions/vueNodes/components/LGraphNode.vue, src/components/rightSidePanel/RightSidePanel.vue
Added data-testid for image/video load errors; LGraphNode and RightSidePanel now consult useMissingNodesErrorStore for missing-node conditions and compute active missing-node graph ids appropriately.
Tests & minor fixes
src/components/rightSidePanel/errors/*.test.ts, src/platform/nodeReplacement/cnrIdUtil.test.ts, src/scripts/app.ts, src/platform/nodeReplacement/missingNodeScan.test.ts, src/platform/nodeReplacement/useNodeReplacement.test.ts, src/stores/executionStore.test.ts, src/components/rightSidePanel/errors/ErrorNodeCard.test.ts
Updated mocks for hoisting/refs, adjusted tests to use new store APIs, added assertions for fallback logs, and changed import paths to relocated utilities.

Sequence Diagram(s)

sequenceDiagram
    participant UI as User / UI
    participant App as App (rescan / workflow)
    participant MissingStore as useMissingNodesErrorStore
    participant ExecStore as useExecutionErrorStore
    participant RightPanel as RightSidePanel / Error UI
    participant GraphSync as useNodeErrorFlagSync
    participant ErrorActions as useErrorActions
    UI->>App: trigger rescan / detect missing node types
    App->>MissingStore: surfaceMissingNodes(types, ExecStore.showErrorOverlay)
    MissingStore-->>RightPanel: missingNodesError (reactive)
    RightPanel->>GraphSync: watch changes -> reconcile node flags
    UI->>RightPanel: click "Find on GitHub" / "Contact Support"
    RightPanel->>ErrorActions: findOnGitHub / contactSupport
    ErrorActions->>ExecStore: track telemetry
    ErrorActions->>External: open URL / execute support command
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 I hopped through code and found a door,

Missing nodes now have their store.
Actions tidy, hooks restore,
Graph flags set with one pass more,
DDD mended — hop, explore!

🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 34.15% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'refactor: error system cleanup — store separation, DDD fix, test improvements' clearly summarizes the main change: refactoring the error system with focus on store separation and DDD fixes.
Description check ✅ Passed The PR description follows the template structure with a clear Summary section, detailed Changes bullet points, referenced issues, and test plan. All required sections are present and well-articulated.
Linked Issues check ✅ Passed The PR addresses all four linked issues: #9875 (store separation, useErrorClearingHooks, useNodeErrorFlagSync), #10027 (mock hoisting in tests), #10033 (E2E test selectors via testids), and #10085 (DDD fix for getCnrIdFromNode, focusedErrorNodeId watch fix).
Out of Scope Changes check ✅ Passed All changes are within scope: store refactoring (missingNodesErrorStore extraction), composable extraction (useNodeErrorFlagSync, useErrorClearingHooks, useErrorActions), DDD fixes (utility relocation), test improvements, and E2E test updates align with the PR objectives.
End-To-End Regression Coverage For Fixes ✅ Passed PR contains bug-fix language in title and commits, and includes modifications to three browser_tests files (selectors.ts, dialog.spec.ts, uploadWidgets.spec.ts) providing E2E regression test coverage.

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

✨ 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 refactor/error-system-cleanup
📝 Coding Plan
  • Generate coding plan for human review comments

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

❤️ Share

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

@github-actions
Copy link

github-actions bot commented Mar 19, 2026

🎭 Playwright: ✅ 663 passed, 0 failed · 4 flaky

📊 Browser Reports
  • chromium: View Report (✅ 649 / ❌ 0 / ⚠️ 4 / ⏭️ 10)
  • chromium-2x: View Report (✅ 2 / ❌ 0 / ⚠️ 0 / ⏭️ 0)
  • chromium-0.5x: View Report (✅ 1 / ❌ 0 / ⚠️ 0 / ⏭️ 0)
  • mobile-chrome: View Report (✅ 11 / ❌ 0 / ⚠️ 0 / ⏭️ 0)

@github-actions
Copy link

github-actions bot commented Mar 19, 2026

📦 Bundle: 5.02 MB gzip 🔴 +617 B

Details

Summary

  • Raw size: 23.1 MB baseline 23.1 MB — 🔴 +3.01 kB
  • Gzip: 5.02 MB baseline 5.02 MB — 🔴 +617 B
  • Brotli: 3.88 MB baseline 3.88 MB — 🔴 +556 B
  • Bundles: 244 current • 244 baseline • 114 added / 114 removed

Category Glance
Graph Workspace 🔴 +2.43 kB (1.11 MB) · Data & Services 🔴 +576 B (2.94 MB) · Vendor & Third-Party ⚪ 0 B (9.79 MB) · Other ⚪ 0 B (8.24 MB) · Panels & Settings ⚪ 0 B (478 kB) · Utilities & Hooks ⚪ 0 B (322 kB) · + 5 more

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

Main entry bundles and manifests

File Before After Δ Raw Δ Gzip Δ Brotli
assets/index-B6pDRN2m.js (removed) 22.7 kB 🟢 -22.7 kB 🟢 -8.03 kB 🟢 -6.91 kB
assets/index-BkuIjrf9.js (new) 22.7 kB 🔴 +22.7 kB 🔴 +8.03 kB 🔴 +6.91 kB

Status: 1 added / 1 removed

Graph Workspace — 1.11 MB (baseline 1.11 MB) • 🔴 +2.43 kB

Graph editor runtime, canvas, workflow orchestration

File Before After Δ Raw Δ Gzip Δ Brotli
assets/GraphView-8LpVrev2.js (new) 1.11 MB 🔴 +1.11 MB 🔴 +237 kB 🔴 +179 kB
assets/GraphView-DdbCBsCw.js (removed) 1.11 MB 🟢 -1.11 MB 🟢 -237 kB 🟢 -179 kB

Status: 1 added / 1 removed

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

Top-level views, pages, and routed surfaces

File Before After Δ Raw Δ Gzip Δ Brotli
assets/CloudSurveyView-BBjXWo_h.js (removed) 15.6 kB 🟢 -15.6 kB 🟢 -3.38 kB 🟢 -2.88 kB
assets/CloudSurveyView-C-eeA5zx.js (new) 15.6 kB 🔴 +15.6 kB 🔴 +3.38 kB 🔴 +2.88 kB
assets/CloudLoginView-CBjKTKZl.js (removed) 11.9 kB 🟢 -11.9 kB 🟢 -3.31 kB 🟢 -2.92 kB
assets/CloudLoginView-DR9AO4cD.js (new) 11.9 kB 🔴 +11.9 kB 🔴 +3.31 kB 🔴 +2.93 kB
assets/CloudSignupView-DpIU0pVN.js (removed) 9.64 kB 🟢 -9.64 kB 🟢 -2.8 kB 🟢 -2.45 kB
assets/CloudSignupView-DUgWIpnu.js (new) 9.64 kB 🔴 +9.64 kB 🔴 +2.8 kB 🔴 +2.45 kB
assets/UserCheckView-Ci7XgV7B.js (new) 9.01 kB 🔴 +9.01 kB 🔴 +2.31 kB 🔴 +2.02 kB
assets/UserCheckView-JyKz18zV.js (removed) 9.01 kB 🟢 -9.01 kB 🟢 -2.31 kB 🟢 -2.02 kB
assets/CloudLayoutView-CCnbYEKo.js (new) 7.42 kB 🔴 +7.42 kB 🔴 +2.31 kB 🔴 +2.01 kB
assets/CloudLayoutView-CS5_lvU8.js (removed) 7.42 kB 🟢 -7.42 kB 🟢 -2.3 kB 🟢 -2 kB
assets/CloudForgotPasswordView-CSDPtXWz.js (new) 5.85 kB 🔴 +5.85 kB 🔴 +2.04 kB 🔴 +1.79 kB
assets/CloudForgotPasswordView-M0Q1_F-H.js (removed) 5.85 kB 🟢 -5.85 kB 🟢 -2.04 kB 🟢 -1.79 kB
assets/CloudAuthTimeoutView-CznpIHFD.js (new) 5.21 kB 🔴 +5.21 kB 🔴 +1.88 kB 🔴 +1.63 kB
assets/CloudAuthTimeoutView-D-qE-YNi.js (removed) 5.21 kB 🟢 -5.21 kB 🟢 -1.88 kB 🟢 -1.65 kB
assets/CloudSubscriptionRedirectView-Bge1Zzqd.js (removed) 4.98 kB 🟢 -4.98 kB 🟢 -1.86 kB 🟢 -1.64 kB
assets/CloudSubscriptionRedirectView-DXk_ykLF.js (new) 4.98 kB 🔴 +4.98 kB 🔴 +1.86 kB 🔴 +1.64 kB
assets/UserSelectView-B2ssi8ML.js (removed) 4.67 kB 🟢 -4.67 kB 🟢 -1.73 kB 🟢 -1.52 kB
assets/UserSelectView-C470HrjN.js (new) 4.67 kB 🔴 +4.67 kB 🔴 +1.73 kB 🔴 +1.53 kB

Status: 9 added / 9 removed / 2 unchanged

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

Configuration panels, inspectors, and settings screens

File Before After Δ Raw Δ Gzip Δ Brotli
assets/KeybindingPanel-C2vhRvqY.js (new) 46.5 kB 🔴 +46.5 kB 🔴 +9.47 kB 🔴 +8.42 kB
assets/KeybindingPanel-NXtCsWuH.js (removed) 46.5 kB 🟢 -46.5 kB 🟢 -9.48 kB 🟢 -8.43 kB
assets/SecretsPanel-kZJBs2wx.js (new) 22.3 kB 🔴 +22.3 kB 🔴 +5.41 kB 🔴 +4.75 kB
assets/SecretsPanel-o1QUlIih.js (removed) 22.3 kB 🟢 -22.3 kB 🟢 -5.41 kB 🟢 -4.75 kB
assets/LegacyCreditsPanel-0jk4sVhu.js (removed) 21.4 kB 🟢 -21.4 kB 🟢 -5.71 kB 🟢 -5.03 kB
assets/LegacyCreditsPanel-CLtjd-kJ.js (new) 21.4 kB 🔴 +21.4 kB 🔴 +5.71 kB 🔴 +5.03 kB
assets/SubscriptionPanel-C91vK7Tp.js (new) 19.3 kB 🔴 +19.3 kB 🔴 +4.89 kB 🔴 +4.32 kB
assets/SubscriptionPanel-CC_xPWU-.js (removed) 19.3 kB 🟢 -19.3 kB 🟢 -4.89 kB 🟢 -4.31 kB
assets/AboutPanel-Dh9K_gkF.js (new) 12 kB 🔴 +12 kB 🔴 +3.31 kB 🔴 +2.97 kB
assets/AboutPanel-DHbjyDyn.js (removed) 12 kB 🟢 -12 kB 🟢 -3.31 kB 🟢 -2.97 kB
assets/ExtensionPanel-CD_y2j9S.js (new) 9.66 kB 🔴 +9.66 kB 🔴 +2.77 kB 🔴 +2.46 kB
assets/ExtensionPanel-DjQbZ5mi.js (removed) 9.66 kB 🟢 -9.66 kB 🟢 -2.76 kB 🟢 -2.45 kB
assets/ServerConfigPanel-DGTufDXJ.js (new) 6.74 kB 🔴 +6.74 kB 🔴 +2.23 kB 🔴 +2 kB
assets/ServerConfigPanel-Y4CX6Jre.js (removed) 6.74 kB 🟢 -6.74 kB 🟢 -2.23 kB 🟢 -2 kB
assets/UserPanel-ByLVDSCn.js (removed) 6.44 kB 🟢 -6.44 kB 🟢 -2.1 kB 🟢 -1.84 kB
assets/UserPanel-DL2eVaHl.js (new) 6.44 kB 🔴 +6.44 kB 🔴 +2.1 kB 🔴 +1.84 kB
assets/cloudRemoteConfig-CEOS58qU.js (removed) 1.74 kB 🟢 -1.74 kB 🟢 -850 B 🟢 -745 B
assets/cloudRemoteConfig-YCwvOJ-6.js (new) 1.74 kB 🔴 +1.74 kB 🔴 +850 B 🔴 +738 B
assets/refreshRemoteConfig-2xBoqqy_.js (new) 1.45 kB 🔴 +1.45 kB 🔴 +648 B 🔴 +548 B
assets/refreshRemoteConfig-BYW_QtSF.js (removed) 1.45 kB 🟢 -1.45 kB 🟢 -649 B 🟢 -556 B

Status: 10 added / 10 removed / 12 unchanged

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

Authentication, profile, and account management bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/auth-CWj2Ckmf.js (new) 3.57 kB 🔴 +3.57 kB 🔴 +1.26 kB 🔴 +1.07 kB
assets/auth-n_f7JEJ3.js (removed) 3.57 kB 🟢 -3.57 kB 🟢 -1.26 kB 🟢 -1.08 kB
assets/SignUpForm-CfQMVs40.js (removed) 3.18 kB 🟢 -3.18 kB 🟢 -1.29 kB 🟢 -1.15 kB
assets/SignUpForm-DdH15gp-.js (new) 3.18 kB 🔴 +3.18 kB 🔴 +1.29 kB 🔴 +1.15 kB
assets/UpdatePasswordContent-BOT0L0bl.js (new) 2.56 kB 🔴 +2.56 kB 🔴 +1.14 kB 🔴 +1 kB
assets/UpdatePasswordContent-EQk57oJA.js (removed) 2.56 kB 🟢 -2.56 kB 🟢 -1.14 kB 🟢 -1.01 kB
assets/firebaseAuthStore-C_S6gmpL.js (removed) 907 B 🟢 -907 B 🟢 -432 B 🟢 -386 B
assets/firebaseAuthStore-u3u_y2PV.js (new) 907 B 🔴 +907 B 🔴 +437 B 🔴 +388 B
assets/auth-DFDcSYRq.js (removed) 313 B 🟢 -313 B 🟢 -198 B 🟢 -170 B
assets/auth-DpcfDKN9.js (new) 313 B 🔴 +313 B 🔴 +196 B 🔴 +180 B

Status: 5 added / 5 removed / 2 unchanged

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

Modals, dialogs, drawers, and in-app editors

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useShareDialog-COayq7G6.js (removed) 81.2 kB 🟢 -81.2 kB 🟢 -16.9 kB 🟢 -14.5 kB
assets/useShareDialog-DT7mpqvk.js (new) 81.2 kB 🔴 +81.2 kB 🔴 +16.9 kB 🔴 +14.5 kB
assets/useSubscriptionDialog-BZLtiw-O.js (removed) 855 B 🟢 -855 B 🟢 -426 B 🟢 -367 B
assets/useSubscriptionDialog-RkMpH8g2.js (new) 855 B 🔴 +855 B 🔴 +426 B 🔴 +372 B

Status: 2 added / 2 removed

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

Reusable component library chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/ComfyQueueButton-4lBKaS0L.js (new) 14.3 kB 🔴 +14.3 kB 🔴 +4 kB 🔴 +3.58 kB
assets/ComfyQueueButton-BK9g-NwI.js (removed) 14.3 kB 🟢 -14.3 kB 🟢 -4 kB 🟢 -3.58 kB
assets/useTerminalTabs-CvIU9kAO.js (new) 10.6 kB 🔴 +10.6 kB 🔴 +3.55 kB 🔴 +3.12 kB
assets/useTerminalTabs-D67U9rbB.js (removed) 10.6 kB 🟢 -10.6 kB 🟢 -3.55 kB 🟢 -3.11 kB
assets/SubscribeButton-COiaf2-S.js (removed) 2.42 kB 🟢 -2.42 kB 🟢 -1.05 kB 🟢 -917 B
assets/SubscribeButton-D3Dd4IGl.js (new) 2.42 kB 🔴 +2.42 kB 🔴 +1.04 kB 🔴 +917 B
assets/cloudFeedbackTopbarButton-C8QUYcEL.js (new) 1.55 kB 🔴 +1.55 kB 🔴 +792 B 🔴 +710 B
assets/cloudFeedbackTopbarButton-CUZQq_5B.js (removed) 1.55 kB 🟢 -1.55 kB 🟢 -792 B 🟢 -713 B
assets/ComfyQueueButton-C6dNpdPH.js (removed) 912 B 🟢 -912 B 🟢 -438 B 🟢 -389 B
assets/ComfyQueueButton-CjTiWCiu.js (new) 912 B 🔴 +912 B 🔴 +439 B 🔴 +388 B

Status: 5 added / 5 removed / 8 unchanged

Data & Services — 2.94 MB (baseline 2.94 MB) • 🔴 +576 B

Stores, services, APIs, and repositories

File Before After Δ Raw Δ Gzip Δ Brotli
assets/dialogService-DaOVl1II.js (new) 1.92 MB 🔴 +1.92 MB 🔴 +442 kB 🔴 +335 kB
assets/dialogService-B4WA5hg8.js (removed) 1.92 MB 🟢 -1.92 MB 🟢 -442 kB 🟢 -334 kB
assets/api-BoBZmYAt.js (new) 875 kB 🔴 +875 kB 🔴 +209 kB 🔴 +165 kB
assets/api-Bt8sJuRM.js (removed) 875 kB 🟢 -875 kB 🟢 -209 kB 🟢 -165 kB
assets/load3dService-DcDZDf8I.js (removed) 92.3 kB 🟢 -92.3 kB 🟢 -19.6 kB 🟢 -16.9 kB
assets/load3dService-DMMOwZeG.js (new) 92.3 kB 🔴 +92.3 kB 🔴 +19.6 kB 🔴 +16.8 kB
assets/workflowShareService-1jj-77j_.js (new) 14.1 kB 🔴 +14.1 kB 🔴 +4.32 kB 🔴 +3.8 kB
assets/workflowShareService-CPaHGtjZ.js (removed) 14.1 kB 🟢 -14.1 kB 🟢 -4.32 kB 🟢 -3.8 kB
assets/keybindingService-BLL6bHXJ.js (removed) 13.7 kB 🟢 -13.7 kB 🟢 -3.65 kB 🟢 -3.2 kB
assets/keybindingService-DXv3YW_2.js (new) 13.7 kB 🔴 +13.7 kB 🔴 +3.65 kB 🔴 +3.2 kB
assets/releaseStore-Dt8PVvSe.js (removed) 8.12 kB 🟢 -8.12 kB 🟢 -2.27 kB 🟢 -1.99 kB
assets/releaseStore-DyBRhfdo.js (new) 8.12 kB 🔴 +8.12 kB 🔴 +2.27 kB 🔴 +1.99 kB
assets/systemStatsStore-C-Xn9AQo.js (new) 4.92 kB 🔴 +4.92 kB 🔴 +1.67 kB 🔴 +1.44 kB
assets/systemStatsStore-CwQ8k0gJ.js (removed) 4.92 kB 🟢 -4.92 kB 🟢 -1.67 kB 🟢 -1.45 kB
assets/userStore-DH48Sf0O.js (removed) 2.24 kB 🟢 -2.24 kB 🟢 -869 B 🟢 -760 B
assets/userStore-ychEk-Xq.js (new) 2.24 kB 🔴 +2.24 kB 🔴 +869 B 🔴 +769 B
assets/audioService-CbVrubOc.js (new) 1.75 kB 🔴 +1.75 kB 🔴 +862 B 🔴 +743 B
assets/audioService-D58tHrSY.js (removed) 1.75 kB 🟢 -1.75 kB 🟢 -865 B 🟢 -746 B
assets/releaseStore-DG6uEUp2.js (removed) 879 B 🟢 -879 B 🟢 -430 B 🟢 -372 B
assets/releaseStore-DxlLOYST.js (new) 879 B 🔴 +879 B 🔴 +431 B 🔴 +368 B
assets/workflowDraftStore-BNiN6JP_.js (removed) 855 B 🟢 -855 B 🟢 -426 B 🟢 -375 B
assets/workflowDraftStore-CKnxWYIf.js (new) 855 B 🔴 +855 B 🔴 +427 B 🔴 +377 B
assets/dialogService-Cnh9swDe2.js (removed) 844 B 🟢 -844 B 🟢 -417 B 🟢 -368 B
assets/dialogService-DVd9QcF92.js (new) 844 B 🔴 +844 B 🔴 +419 B 🔴 +368 B
assets/settingStore-BYt9h6q4.js (removed) 842 B 🟢 -842 B 🟢 -420 B 🟢 -368 B
assets/settingStore-D7VSzYoo.js (new) 842 B 🔴 +842 B 🔴 +422 B 🔴 +367 B
assets/assetsStore-B_V9iS2q.js (new) 841 B 🔴 +841 B 🔴 +421 B 🔴 +367 B
assets/assetsStore-D5jP8j64.js (removed) 841 B 🟢 -841 B 🟢 -420 B 🟢 -366 B

Status: 14 added / 14 removed / 3 unchanged

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

Helpers, composables, and utility bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useConflictDetection-BSyzguTF.js (new) 231 kB 🔴 +231 kB 🔴 +51.2 kB 🔴 +41.7 kB
assets/useConflictDetection-ClOncGHl.js (removed) 231 kB 🟢 -231 kB 🟢 -51.2 kB 🟢 -41.7 kB
assets/useLoad3dViewer-Cb90DKA-.js (new) 18.6 kB 🔴 +18.6 kB 🔴 +4.42 kB 🔴 +3.85 kB
assets/useLoad3dViewer-DLlQgjlA.js (removed) 18.6 kB 🟢 -18.6 kB 🟢 -4.42 kB 🟢 -3.88 kB
assets/useLoad3d-DD1E_qvt.js (removed) 15 kB 🟢 -15 kB 🟢 -3.78 kB 🟢 -3.36 kB
assets/useLoad3d-LW3V1skN.js (new) 15 kB 🔴 +15 kB 🔴 +3.78 kB 🔴 +3.36 kB
assets/useFeatureFlags-Bmi6UIHU.js (removed) 5.78 kB 🟢 -5.78 kB 🟢 -1.75 kB 🟢 -1.48 kB
assets/useFeatureFlags-DQJZ2Ysq.js (new) 5.78 kB 🔴 +5.78 kB 🔴 +1.75 kB 🔴 +1.48 kB
assets/useWorkspaceUI-B_NnBpg7.js (removed) 3.34 kB 🟢 -3.34 kB 🟢 -982 B 🟢 -813 B
assets/useWorkspaceUI-BaTa9jvp.js (new) 3.34 kB 🔴 +3.34 kB 🔴 +979 B 🔴 +811 B
assets/subscriptionCheckoutUtil-B_7O5BsZ.js (new) 3.04 kB 🔴 +3.04 kB 🔴 +1.31 kB 🔴 +1.15 kB
assets/subscriptionCheckoutUtil-B7TonKMx.js (removed) 3.04 kB 🟢 -3.04 kB 🟢 -1.32 kB 🟢 -1.15 kB
assets/assetPreviewUtil-DftzSQsZ.js (new) 2.27 kB 🔴 +2.27 kB 🔴 +956 B 🔴 +830 B
assets/assetPreviewUtil-DiaxgP8B.js (removed) 2.27 kB 🟢 -2.27 kB 🟢 -957 B 🟢 -836 B
assets/useUpstreamValue-CNOzEjm8.js (new) 2.08 kB 🔴 +2.08 kB 🔴 +804 B 🔴 +706 B
assets/useUpstreamValue-kd53FiP5.js (removed) 2.08 kB 🟢 -2.08 kB 🟢 -805 B 🟢 -718 B
assets/useErrorHandling-C6g3VWRo.js (removed) 1.54 kB 🟢 -1.54 kB 🟢 -648 B 🟢 -551 B
assets/useErrorHandling-CeDufVZA.js (new) 1.54 kB 🔴 +1.54 kB 🔴 +647 B 🔴 +550 B
assets/useLoad3d-CPguSMuX.js (new) 1.02 kB 🔴 +1.02 kB 🔴 +489 B 🔴 +432 B
assets/useLoad3d-nocGeuOT.js (removed) 1.02 kB 🟢 -1.02 kB 🟢 -487 B 🟢 -435 B
assets/audioUtils-3tAamB0m.js (new) 958 B 🔴 +958 B 🔴 +563 B 🔴 +492 B
assets/audioUtils-Bhsr11ng.js (removed) 958 B 🟢 -958 B 🟢 -563 B 🟢 -488 B
assets/useLoad3dViewer-C9aJNYT-.js (removed) 957 B 🟢 -957 B 🟢 -454 B 🟢 -405 B
assets/useLoad3dViewer-D44iSc3u.js (new) 957 B 🔴 +957 B 🔴 +456 B 🔴 +403 B
assets/useCurrentUser-CQhMyfaU.js (new) 841 B 🔴 +841 B 🔴 +422 B 🔴 +365 B
assets/useCurrentUser-Dg6BDllJ.js (removed) 841 B 🟢 -841 B 🟢 -420 B 🟢 -365 B
assets/useWorkspaceSwitch-BqTffO1b.js (new) 747 B 🔴 +747 B 🔴 +385 B 🔴 +330 B
assets/useWorkspaceSwitch-BUSRXsUB.js (removed) 747 B 🟢 -747 B 🟢 -386 B 🟢 -335 B

Status: 14 added / 14 removed / 8 unchanged

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

External libraries and shared vendor chunks

Status: 16 unchanged

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

Bundles that do not match a named category

File Before After Δ Raw Δ Gzip Δ Brotli
assets/core-DGBvNx-F.js (removed) 76.2 kB 🟢 -76.2 kB 🟢 -19.7 kB 🟢 -16.8 kB
assets/core-g68PuPR-.js (new) 76.2 kB 🔴 +76.2 kB 🔴 +19.7 kB 🔴 +16.8 kB
assets/groupNode-CHWgZJBA.js (new) 73.9 kB 🔴 +73.9 kB 🔴 +18.5 kB 🔴 +16.3 kB
assets/groupNode-Tzo8fQ_v.js (removed) 73.9 kB 🟢 -73.9 kB 🟢 -18.5 kB 🟢 -16.3 kB
assets/WidgetSelect-C_uibD2E.js (removed) 63.3 kB 🟢 -63.3 kB 🟢 -13.8 kB 🟢 -11.9 kB
assets/WidgetSelect-CGtBXewb.js (new) 63.3 kB 🔴 +63.3 kB 🔴 +13.8 kB 🔴 +11.9 kB
assets/SubscriptionRequiredDialogContentWorkspace-BV8Ji-Ya.js (new) 47.2 kB 🔴 +47.2 kB 🔴 +8.8 kB 🔴 +7.62 kB
assets/SubscriptionRequiredDialogContentWorkspace-DLAgEnFZ.js (removed) 47.2 kB 🟢 -47.2 kB 🟢 -8.8 kB 🟢 -7.6 kB
assets/WidgetPainter-BOkdlNkn.js (removed) 33.2 kB 🟢 -33.2 kB 🟢 -8.06 kB 🟢 -7.15 kB
assets/WidgetPainter-D_w26Rg0.js (new) 33.2 kB 🔴 +33.2 kB 🔴 +8.06 kB 🔴 +7.14 kB
assets/Load3DControls-DS-VMzyE.js (new) 32.1 kB 🔴 +32.1 kB 🔴 +5.47 kB 🔴 +4.75 kB
assets/Load3DControls-OZ3yqRU7.js (removed) 32.1 kB 🟢 -32.1 kB 🟢 -5.47 kB 🟢 -4.75 kB
assets/WorkspacePanelContent-D18Z8fAB.js (removed) 29.8 kB 🟢 -29.8 kB 🟢 -6.27 kB 🟢 -5.5 kB
assets/WorkspacePanelContent-yzUmnvQc.js (new) 29.8 kB 🔴 +29.8 kB 🔴 +6.27 kB 🔴 +5.5 kB
assets/SubscriptionRequiredDialogContent-CmcXGakg.js (removed) 26.3 kB 🟢 -26.3 kB 🟢 -6.69 kB 🟢 -5.92 kB
assets/SubscriptionRequiredDialogContent-n0VHQTQP.js (new) 26.3 kB 🔴 +26.3 kB 🔴 +6.7 kB 🔴 +5.9 kB
assets/Load3dViewerContent-BdZa52Qc.js (removed) 24.3 kB 🟢 -24.3 kB 🟢 -5.32 kB 🟢 -4.63 kB
assets/Load3dViewerContent-DxI33CuM.js (new) 24.3 kB 🔴 +24.3 kB 🔴 +5.32 kB 🔴 +4.63 kB
assets/WidgetImageCrop-bTvopPw5.js (new) 23.2 kB 🔴 +23.2 kB 🔴 +5.77 kB 🔴 +5.08 kB
assets/WidgetImageCrop-DqyaJ3F7.js (removed) 23.2 kB 🟢 -23.2 kB 🟢 -5.77 kB 🟢 -5.09 kB
assets/SubscriptionPanelContentWorkspace-BDg_IzmU.js (removed) 22.2 kB 🟢 -22.2 kB 🟢 -5.17 kB 🟢 -4.56 kB
assets/SubscriptionPanelContentWorkspace-lVHatzKh.js (new) 22.2 kB 🔴 +22.2 kB 🔴 +5.17 kB 🔴 +4.55 kB
assets/CurrentUserPopoverWorkspace-CD5FELkf.js (new) 20.9 kB 🔴 +20.9 kB 🔴 +5.04 kB 🔴 +4.5 kB
assets/CurrentUserPopoverWorkspace-CULwtD9p.js (removed) 20.9 kB 🟢 -20.9 kB 🟢 -5.04 kB 🟢 -4.5 kB
assets/SignInContent-BY1akllm.js (new) 20.2 kB 🔴 +20.2 kB 🔴 +5.21 kB 🔴 +4.55 kB
assets/SignInContent-CvDBDvSd.js (removed) 20.2 kB 🟢 -20.2 kB 🟢 -5.21 kB 🟢 -4.54 kB
assets/WidgetInputNumber-CQ6YY775.js (removed) 19.1 kB 🟢 -19.1 kB 🟢 -4.84 kB 🟢 -4.3 kB
assets/WidgetInputNumber-DrNNVyAg.js (new) 19.1 kB 🔴 +19.1 kB 🔴 +4.84 kB 🔴 +4.3 kB
assets/WidgetRecordAudio-6CRpGBTu.js (new) 18.1 kB 🔴 +18.1 kB 🔴 +5.15 kB 🔴 +4.62 kB
assets/WidgetRecordAudio-DBx7Hybt.js (removed) 18.1 kB 🟢 -18.1 kB 🟢 -5.14 kB 🟢 -4.62 kB
assets/Load3D-CfE8XV5f.js (removed) 16.9 kB 🟢 -16.9 kB 🟢 -4.11 kB 🟢 -3.58 kB
assets/Load3D-DfFKj0kD.js (new) 16.9 kB 🔴 +16.9 kB 🔴 +4.11 kB 🔴 +3.59 kB
assets/WidgetCurve-C4KNufrS.js (new) 15.1 kB 🔴 +15.1 kB 🔴 +4.67 kB 🔴 +4.2 kB
assets/WidgetCurve-DwQ7L3BK.js (removed) 15.1 kB 🟢 -15.1 kB 🟢 -4.66 kB 🟢 -4.2 kB
assets/load3d-BcBv7bq6.js (removed) 14.9 kB 🟢 -14.9 kB 🟢 -4.28 kB 🟢 -3.71 kB
assets/load3d-BijeolUe.js (new) 14.9 kB 🔴 +14.9 kB 🔴 +4.27 kB 🔴 +3.69 kB
assets/AudioPreviewPlayer-C53704AH.js (new) 11.3 kB 🔴 +11.3 kB 🔴 +3.35 kB 🔴 +2.99 kB
assets/AudioPreviewPlayer-DxgFyZg9.js (removed) 11.3 kB 🟢 -11.3 kB 🟢 -3.35 kB 🟢 -3 kB
assets/nodeTemplates-CTOCO20W.js (new) 9.45 kB 🔴 +9.45 kB 🔴 +3.33 kB 🔴 +2.92 kB
assets/nodeTemplates-qVGYskmS.js (removed) 9.45 kB 🟢 -9.45 kB 🟢 -3.33 kB 🟢 -2.92 kB
assets/InviteMemberDialogContent-CDwa75ZQ.js (new) 7.66 kB 🔴 +7.66 kB 🔴 +2.4 kB 🔴 +2.09 kB
assets/InviteMemberDialogContent-CqR3HX6H.js (removed) 7.66 kB 🟢 -7.66 kB 🟢 -2.4 kB 🟢 -2.1 kB
assets/Load3DConfiguration-Da39ObIk.js (new) 6.55 kB 🔴 +6.55 kB 🔴 +2.03 kB 🔴 +1.77 kB
assets/Load3DConfiguration-DbgP5lDD.js (removed) 6.55 kB 🟢 -6.55 kB 🟢 -2.03 kB 🟢 -1.77 kB
assets/onboardingCloudRoutes-CUg8ryWc.js (removed) 6.31 kB 🟢 -6.31 kB 🟢 -1.97 kB 🟢 -1.72 kB
assets/onboardingCloudRoutes-DuvbPm46.js (new) 6.31 kB 🔴 +6.31 kB 🔴 +1.96 kB 🔴 +1.69 kB
assets/WidgetWithControl-Comu9fGK.js (removed) 5.87 kB 🟢 -5.87 kB 🟢 -2.31 kB 🟢 -2.06 kB
assets/WidgetWithControl-DIZienih.js (new) 5.87 kB 🔴 +5.87 kB 🔴 +2.31 kB 🔴 +2.06 kB
assets/CreateWorkspaceDialogContent-B7KSTENz.js (removed) 5.84 kB 🟢 -5.84 kB 🟢 -2.09 kB 🟢 -1.83 kB
assets/CreateWorkspaceDialogContent-CWjrRIBQ.js (new) 5.84 kB 🔴 +5.84 kB 🔴 +2.09 kB 🔴 +1.82 kB
assets/FreeTierDialogContent-B6EhGijL.js (removed) 5.7 kB 🟢 -5.7 kB 🟢 -1.99 kB 🟢 -1.76 kB
assets/FreeTierDialogContent-BoWrrvXa.js (new) 5.7 kB 🔴 +5.7 kB 🔴 +1.99 kB 🔴 +1.76 kB
assets/EditWorkspaceDialogContent-BFXIlc0_.js (removed) 5.63 kB 🟢 -5.63 kB 🟢 -2.06 kB 🟢 -1.79 kB
assets/EditWorkspaceDialogContent-D_hYxzyB.js (new) 5.63 kB 🔴 +5.63 kB 🔴 +2.06 kB 🔴 +1.79 kB
assets/Preview3d-CMxFOJY7.js (new) 5.24 kB 🔴 +5.24 kB 🔴 +1.74 kB 🔴 +1.51 kB
assets/Preview3d-D4y-QHhW.js (removed) 5.24 kB 🟢 -5.24 kB 🟢 -1.74 kB 🟢 -1.51 kB
assets/ValueControlPopover-DJmMT5he.js (removed) 5.22 kB 🟢 -5.22 kB 🟢 -1.87 kB 🟢 -1.67 kB
assets/ValueControlPopover-WVtK7RcX.js (new) 5.22 kB 🔴 +5.22 kB 🔴 +1.88 kB 🔴 +1.68 kB
assets/WidgetTextarea-Cy2DIwHM.js (new) 5.15 kB 🔴 +5.15 kB 🔴 +2.02 kB 🔴 +1.77 kB
assets/WidgetTextarea-m0Ddyh4o.js (removed) 5.15 kB 🟢 -5.15 kB 🟢 -2.02 kB 🟢 -1.8 kB
assets/CancelSubscriptionDialogContent-B8uMZzBP.js (new) 5.11 kB 🔴 +5.11 kB 🔴 +1.89 kB 🔴 +1.65 kB
assets/CancelSubscriptionDialogContent-DXmAy2eC.js (removed) 5.11 kB 🟢 -5.11 kB 🟢 -1.89 kB 🟢 -1.65 kB
assets/DeleteWorkspaceDialogContent-BxitiB99.js (new) 4.54 kB 🔴 +4.54 kB 🔴 +1.73 kB 🔴 +1.5 kB
assets/DeleteWorkspaceDialogContent-CI4NInde.js (removed) 4.54 kB 🟢 -4.54 kB 🟢 -1.73 kB 🟢 -1.5 kB
assets/tierBenefits-Bu5qcpvG.js (new) 4.47 kB 🔴 +4.47 kB 🔴 +1.58 kB 🔴 +1.36 kB
assets/tierBenefits-Ch7sh5Fn.js (removed) 4.47 kB 🟢 -4.47 kB 🟢 -1.58 kB 🟢 -1.37 kB
assets/LeaveWorkspaceDialogContent-BCPFNKbr.js (removed) 4.37 kB 🟢 -4.37 kB 🟢 -1.68 kB 🟢 -1.45 kB
assets/LeaveWorkspaceDialogContent-FqaXuDUg.js (new) 4.37 kB 🔴 +4.37 kB 🔴 +1.68 kB 🔴 +1.45 kB
assets/RemoveMemberDialogContent-BV2Iu1H6.js (new) 4.35 kB 🔴 +4.35 kB 🔴 +1.64 kB 🔴 +1.42 kB
assets/RemoveMemberDialogContent-yy2ouTEZ.js (removed) 4.35 kB 🟢 -4.35 kB 🟢 -1.63 kB 🟢 -1.43 kB
assets/RevokeInviteDialogContent-BFvmZE-m.js (removed) 4.26 kB 🟢 -4.26 kB 🟢 -1.65 kB 🟢 -1.44 kB
assets/RevokeInviteDialogContent-BnS7O0ik.js (new) 4.26 kB 🔴 +4.26 kB 🔴 +1.64 kB 🔴 +1.44 kB
assets/InviteMemberUpsellDialogContent-CAC8eZG2.js (new) 4.16 kB 🔴 +4.16 kB 🔴 +1.51 kB 🔴 +1.32 kB
assets/InviteMemberUpsellDialogContent-Cu0bVsCq.js (removed) 4.16 kB 🟢 -4.16 kB 🟢 -1.51 kB 🟢 -1.32 kB
assets/cloudSessionCookie-CC8eGOO4.js (removed) 4.02 kB 🟢 -4.02 kB 🟢 -1.44 kB 🟢 -1.25 kB
assets/cloudSessionCookie-CZ-ftDb5.js (new) 4.02 kB 🔴 +4.02 kB 🔴 +1.44 kB 🔴 +1.25 kB
assets/saveMesh-CFUCIk1h.js (new) 3.8 kB 🔴 +3.8 kB 🔴 +1.63 kB 🔴 +1.43 kB
assets/saveMesh-DdbWbugs.js (removed) 3.8 kB 🟢 -3.8 kB 🟢 -1.63 kB 🟢 -1.43 kB
assets/Media3DTop-CPX7AlHn.js (removed) 3.73 kB 🟢 -3.73 kB 🟢 -1.57 kB 🟢 -1.38 kB
assets/Media3DTop-DwXXl4sS.js (new) 3.73 kB 🔴 +3.73 kB 🔴 +1.57 kB 🔴 +1.38 kB
assets/GlobalToast-BalS6xj3.js (removed) 3.04 kB 🟢 -3.04 kB 🟢 -1.26 kB 🟢 -1.08 kB
assets/GlobalToast-DpZIFcAA.js (new) 3.04 kB 🔴 +3.04 kB 🔴 +1.26 kB 🔴 +1.08 kB
assets/SubscribeToRun-BuBGDOse.js (new) 2.13 kB 🔴 +2.13 kB 🔴 +980 B 🔴 +871 B
assets/SubscribeToRun-D8Pf31ic.js (removed) 2.13 kB 🟢 -2.13 kB 🟢 -983 B 🟢 -873 B
assets/CloudRunButtonWrapper-HkhzJHif.js (removed) 1.88 kB 🟢 -1.88 kB 🟢 -859 B 🟢 -760 B
assets/CloudRunButtonWrapper-t_QyoUrk.js (new) 1.88 kB 🔴 +1.88 kB 🔴 +859 B 🔴 +761 B
assets/cloudBadges-Cg72wYOC.js (removed) 1.65 kB 🟢 -1.65 kB 🟢 -839 B 🟢 -741 B
assets/cloudBadges-CNqnFKN3.js (new) 1.65 kB 🔴 +1.65 kB 🔴 +839 B 🔴 +738 B
assets/cloudSubscription-Ba-OBbso.js (new) 1.56 kB 🔴 +1.56 kB 🔴 +757 B 🔴 +649 B
assets/cloudSubscription-CAyUjZgM.js (removed) 1.56 kB 🟢 -1.56 kB 🟢 -759 B 🟢 -648 B
assets/previousFullPath-BMHvyYEq.js (new) 1.53 kB 🔴 +1.53 kB 🔴 +693 B 🔴 +622 B
assets/previousFullPath-DpvEv9XG.js (removed) 1.53 kB 🟢 -1.53 kB 🟢 -694 B 🟢 -601 B
assets/Load3D-CwEPQG8g.js (new) 1.23 kB 🔴 +1.23 kB 🔴 +562 B 🔴 +492 B
assets/Load3D-DaiGxM3v.js (removed) 1.23 kB 🟢 -1.23 kB 🟢 -561 B 🟢 -497 B
assets/nightlyBadges-Dug1yIgD.js (new) 1.18 kB 🔴 +1.18 kB 🔴 +605 B 🔴 +533 B
assets/nightlyBadges-zUmtsHZi.js (removed) 1.18 kB 🟢 -1.18 kB 🟢 -605 B 🟢 -533 B
assets/Load3dViewerContent-B3CRYyMX.js (new) 1.11 kB 🔴 +1.11 kB 🔴 +512 B 🔴 +447 B
assets/Load3dViewerContent-l8Wqq5Pn.js (removed) 1.11 kB 🟢 -1.11 kB 🟢 -513 B 🟢 -449 B
assets/SubscriptionPanelContentWorkspace-BNniRV2E.js (removed) 1.04 kB 🟢 -1.04 kB 🟢 -483 B 🟢 -417 B
assets/SubscriptionPanelContentWorkspace-x3ZcF4Q0.js (new) 1.04 kB 🔴 +1.04 kB 🔴 +483 B 🔴 +414 B
assets/WidgetLegacy-CdmkJRUP.js (new) 864 B 🔴 +864 B 🔴 +432 B 🔴 +374 B
assets/WidgetLegacy-CX1XfpVO.js (removed) 864 B 🟢 -864 B 🟢 -431 B 🟢 -373 B
assets/changeTracker-B1_nqW9v.js (new) 839 B 🔴 +839 B 🔴 +422 B 🔴 +364 B
assets/changeTracker-BFEZbMAU.js (removed) 839 B 🟢 -839 B 🟢 -420 B 🟢 -367 B
assets/graphHasMissingNodes-4girwm5o.js (new) 822 B 🔴 +822 B 🔴 +412 B 🔴 +348 B
assets/graphHasMissingNodes-CmKe7P7l.js (removed) 822 B 🟢 -822 B 🟢 -413 B 🟢 -347 B

Status: 53 added / 53 removed / 79 unchanged

⚡ Performance Report

⚠️ 5 regressions detected

Metric Baseline PR (n=3) Δ Sig
canvas-mouse-sweep: style recalcs 83 84 +1% ⚠️ z=2.3
canvas-mouse-sweep: DOM nodes 66 67 +1% ⚠️ z=2.2
canvas-zoom-sweep: frame duration 17ms 17ms +0% ⚠️ z=3.1
subgraph-mouse-sweep: style recalcs 83 84 +1% ⚠️ z=2.1
workflow-execution: event listeners 71 71 +0% ⚠️ z=4.6
All metrics
Metric Baseline PR (n=3) Δ Sig
canvas-idle: style recalcs 11 11 +6% z=0.5
canvas-idle: layouts 0 0 +0%
canvas-idle: task duration 392ms 380ms -3% z=-0.5
canvas-idle: DOM nodes 21 22 +5% z=0.0
canvas-idle: script duration 24ms 22ms -7% z=-1.3
canvas-idle: event listeners 14 6 -57% z=-1.1
canvas-idle: TBT 0ms 0ms +0%
canvas-idle: frame duration 17ms 17ms -0% z=-1.0
canvas-mouse-sweep: style recalcs 83 84 +1% ⚠️ z=2.3
canvas-mouse-sweep: layouts 12 12 +0%
canvas-mouse-sweep: task duration 1047ms 969ms -7% z=1.8
canvas-mouse-sweep: DOM nodes 66 67 +1% ⚠️ z=2.2
canvas-mouse-sweep: script duration 145ms 135ms -7% z=-0.2
canvas-mouse-sweep: event listeners 6 14 +133% z=1.1
canvas-mouse-sweep: TBT 0ms 0ms +0%
canvas-mouse-sweep: frame duration 17ms 17ms +0% z=-0.3
canvas-zoom-sweep: style recalcs 32 31 -1% z=-0.1
canvas-zoom-sweep: layouts 6 6 +0%
canvas-zoom-sweep: task duration 342ms 306ms -10% z=-1.0
canvas-zoom-sweep: DOM nodes 79 79 +0% z=-0.3
canvas-zoom-sweep: script duration 30ms 24ms -22% z=-1.3
canvas-zoom-sweep: event listeners 19 20 +4% z=-0.9
canvas-zoom-sweep: TBT 0ms 0ms +0%
canvas-zoom-sweep: frame duration 17ms 17ms +0% ⚠️ z=3.1
dom-widget-clipping: style recalcs 11 12 +3% z=-2.4
dom-widget-clipping: layouts 0 0 +0%
dom-widget-clipping: task duration 368ms 341ms -7% z=-1.5
dom-widget-clipping: DOM nodes 18 19 +2% z=-2.1
dom-widget-clipping: script duration 68ms 64ms -6% z=-1.2
dom-widget-clipping: event listeners 2 2 +0% variance too high
dom-widget-clipping: TBT 0ms 0ms +0%
dom-widget-clipping: frame duration 17ms 17ms -0% z=-0.9
large-graph-idle: style recalcs 12 11 -6% z=-1.8
large-graph-idle: layouts 0 0 +0%
large-graph-idle: task duration 602ms 535ms -11% z=-0.3
large-graph-idle: DOM nodes -260 -263 +1% z=-351.2
large-graph-idle: script duration 112ms 95ms -15% z=-0.7
large-graph-idle: event listeners -136 -149 +9% z=-25.0
large-graph-idle: TBT 0ms 0ms +0%
large-graph-idle: frame duration 17ms 17ms +0% z=0.8
large-graph-pan: style recalcs 68 68 +1% z=-1.8
large-graph-pan: layouts 0 0 +0%
large-graph-pan: task duration 1153ms 1087ms -6% z=0.1
large-graph-pan: DOM nodes -270 -268 -1% z=-157.9
large-graph-pan: script duration 418ms 410ms -2% z=0.2
large-graph-pan: event listeners -162 -160 -1% z=-213.7
large-graph-pan: TBT 0ms 0ms +0%
large-graph-pan: frame duration 17ms 17ms -0% z=0.3
minimap-idle: style recalcs 9 9 +4% z=-0.7
minimap-idle: layouts 0 0 +0%
minimap-idle: task duration 591ms 526ms -11% z=-0.2
minimap-idle: DOM nodes -269 -269 +0% z=-220.9
minimap-idle: script duration 106ms 92ms -13% z=-0.7
minimap-idle: event listeners -160 -160 -0% z=-223.9
minimap-idle: TBT 0ms 0ms +0%
minimap-idle: frame duration 17ms 17ms -0% z=-0.9
subgraph-dom-widget-clipping: style recalcs 48 49 +1% z=1.7
subgraph-dom-widget-clipping: layouts 0 0 +0%
subgraph-dom-widget-clipping: task duration 392ms 363ms -7% z=-0.8
subgraph-dom-widget-clipping: DOM nodes 23 23 +3% z=1.0
subgraph-dom-widget-clipping: script duration 135ms 126ms -7% z=-0.4
subgraph-dom-widget-clipping: event listeners 16 16 +0% z=-0.1
subgraph-dom-widget-clipping: TBT 0ms 0ms +0%
subgraph-dom-widget-clipping: frame duration 17ms 17ms -0% z=-0.7
subgraph-idle: style recalcs 11 11 +3% z=0.5
subgraph-idle: layouts 0 0 +0%
subgraph-idle: task duration 379ms 353ms -7% z=-0.7
subgraph-idle: DOM nodes 22 22 +0% z=0.2
subgraph-idle: script duration 20ms 20ms -4% z=-0.5
subgraph-idle: event listeners 14 6 -57% variance too high
subgraph-idle: TBT 0ms 0ms +0%
subgraph-idle: frame duration 17ms 17ms -0% z=-0.7
subgraph-mouse-sweep: style recalcs 83 84 +1% ⚠️ z=2.1
subgraph-mouse-sweep: layouts 16 16 +0%
subgraph-mouse-sweep: task duration 866ms 921ms +6% z=2.0
subgraph-mouse-sweep: DOM nodes 69 -14 -121% z=-39.2
subgraph-mouse-sweep: script duration 103ms 101ms -2% z=-0.1
subgraph-mouse-sweep: event listeners 5 -48 -1006% variance too high
subgraph-mouse-sweep: TBT 0ms 0ms +0%
subgraph-mouse-sweep: frame duration 17ms 17ms +0% z=0.2
vue-large-graph-idle: style recalcs 0 0 +0%
vue-large-graph-idle: layouts 0 0 +0%
vue-large-graph-idle: task duration 12377ms 12060ms -3%
vue-large-graph-idle: DOM nodes -8342 -8342 +0%
vue-large-graph-idle: script duration 615ms 610ms -1%
vue-large-graph-idle: event listeners -16472 -16475 +0%
vue-large-graph-idle: TBT 0ms 0ms +0%
vue-large-graph-idle: frame duration 18ms 17ms -6%
vue-large-graph-pan: style recalcs 66 66 +0%
vue-large-graph-pan: layouts 0 0 +0%
vue-large-graph-pan: task duration 14510ms 14224ms -2%
vue-large-graph-pan: DOM nodes -8343 -8343 +0%
vue-large-graph-pan: script duration 873ms 876ms +0%
vue-large-graph-pan: event listeners -16471 -16469 -0%
vue-large-graph-pan: TBT 0ms 13ms
vue-large-graph-pan: frame duration 19ms 20ms +3%
workflow-execution: style recalcs 18 17 -5% z=-0.3
workflow-execution: layouts 5 5 +0% z=-0.9
workflow-execution: task duration 125ms 122ms -2% z=-0.2
workflow-execution: DOM nodes 161 159 -1% z=-0.2
workflow-execution: script duration 29ms 28ms -5% z=-0.5
workflow-execution: event listeners 71 71 +0% ⚠️ z=4.6
workflow-execution: TBT 0ms 0ms +0%
workflow-execution: frame duration 17ms 17ms +0% z=-0.2
Historical variance (last 10 runs)
Metric μ σ CV
canvas-idle: style recalcs 11 1 4.9%
canvas-idle: layouts 0 0 0.0%
canvas-idle: task duration 397ms 35ms 8.7%
canvas-idle: DOM nodes 22 1 5.9%
canvas-idle: script duration 26ms 2ms 8.9%
canvas-idle: event listeners 10 4 38.6%
canvas-idle: TBT 0ms 0ms 0.0%
canvas-idle: frame duration 17ms 0ms 0.0%
canvas-mouse-sweep: style recalcs 79 2 2.8%
canvas-mouse-sweep: layouts 12 0 0.0%
canvas-mouse-sweep: task duration 866ms 58ms 6.7%
canvas-mouse-sweep: DOM nodes 62 2 3.6%
canvas-mouse-sweep: script duration 136ms 7ms 5.5%
canvas-mouse-sweep: event listeners 9 4 46.3%
canvas-mouse-sweep: TBT 0ms 0ms 0.0%
canvas-mouse-sweep: frame duration 17ms 0ms 0.0%
canvas-zoom-sweep: style recalcs 31 0 1.5%
canvas-zoom-sweep: layouts 6 0 0.0%
canvas-zoom-sweep: task duration 330ms 23ms 7.1%
canvas-zoom-sweep: DOM nodes 79 1 0.8%
canvas-zoom-sweep: script duration 28ms 3ms 11.4%
canvas-zoom-sweep: event listeners 23 4 18.4%
canvas-zoom-sweep: TBT 0ms 0ms 0.0%
canvas-zoom-sweep: frame duration 17ms 0ms 0.0%
dom-widget-clipping: style recalcs 13 1 4.1%
dom-widget-clipping: layouts 0 0 0.0%
dom-widget-clipping: task duration 368ms 18ms 4.8%
dom-widget-clipping: DOM nodes 22 2 7.1%
dom-widget-clipping: script duration 68ms 4ms 5.6%
dom-widget-clipping: event listeners 8 8 99.9%
dom-widget-clipping: TBT 0ms 0ms 0.0%
dom-widget-clipping: frame duration 17ms 0ms 0.0%
large-graph-idle: style recalcs 12 0 2.7%
large-graph-idle: layouts 0 0 0.0%
large-graph-idle: task duration 554ms 59ms 10.6%
large-graph-idle: DOM nodes 24 1 3.3%
large-graph-idle: script duration 104ms 12ms 11.7%
large-graph-idle: event listeners 25 7 28.2%
large-graph-idle: TBT 0ms 0ms 0.0%
large-graph-idle: frame duration 17ms 0ms 0.0%
large-graph-pan: style recalcs 70 1 0.9%
large-graph-pan: layouts 0 0 0.0%
large-graph-pan: task duration 1084ms 50ms 4.7%
large-graph-pan: DOM nodes 19 2 9.7%
large-graph-pan: script duration 405ms 23ms 5.7%
large-graph-pan: event listeners 5 1 15.9%
large-graph-pan: TBT 0ms 0ms 0.0%
large-graph-pan: frame duration 17ms 0ms 0.0%
minimap-idle: style recalcs 9 1 6.9%
minimap-idle: layouts 0 0 0.0%
minimap-idle: task duration 535ms 51ms 9.5%
minimap-idle: DOM nodes 19 1 6.9%
minimap-idle: script duration 99ms 11ms 11.1%
minimap-idle: event listeners 5 1 15.9%
minimap-idle: TBT 0ms 0ms 0.0%
minimap-idle: frame duration 17ms 0ms 0.0%
subgraph-dom-widget-clipping: style recalcs 48 1 1.4%
subgraph-dom-widget-clipping: layouts 0 0 0.0%
subgraph-dom-widget-clipping: task duration 380ms 21ms 5.5%
subgraph-dom-widget-clipping: DOM nodes 22 1 5.7%
subgraph-dom-widget-clipping: script duration 129ms 8ms 5.8%
subgraph-dom-widget-clipping: event listeners 17 7 41.7%
subgraph-dom-widget-clipping: TBT 0ms 0ms 0.0%
subgraph-dom-widget-clipping: frame duration 17ms 0ms 0.0%
subgraph-idle: style recalcs 11 1 6.9%
subgraph-idle: layouts 0 0 0.0%
subgraph-idle: task duration 375ms 31ms 8.3%
subgraph-idle: DOM nodes 22 2 7.8%
subgraph-idle: script duration 21ms 3ms 12.7%
subgraph-idle: event listeners 12 8 63.4%
subgraph-idle: TBT 0ms 0ms 0.0%
subgraph-idle: frame duration 17ms 0ms 0.0%
subgraph-mouse-sweep: style recalcs 80 2 2.2%
subgraph-mouse-sweep: layouts 16 0 0.0%
subgraph-mouse-sweep: task duration 784ms 70ms 9.0%
subgraph-mouse-sweep: DOM nodes 67 2 3.1%
subgraph-mouse-sweep: script duration 102ms 7ms 7.3%
subgraph-mouse-sweep: event listeners 8 4 51.6%
subgraph-mouse-sweep: TBT 0ms 0ms 0.0%
subgraph-mouse-sweep: frame duration 17ms 0ms 0.0%
workflow-execution: style recalcs 18 1 6.8%
workflow-execution: layouts 5 0 5.6%
workflow-execution: task duration 124ms 12ms 9.5%
workflow-execution: DOM nodes 161 6 3.8%
workflow-execution: script duration 30ms 4ms 11.8%
workflow-execution: event listeners 52 4 7.9%
workflow-execution: TBT 0ms 0ms 0.0%
workflow-execution: frame duration 17ms 0ms 0.0%
Trend (last 10 commits on main)
Metric Trend Dir Latest
canvas-idle: style recalcs █▅▁▃▁▃▆█▆█ ➡️ 12
canvas-idle: layouts ▄▄▄▄▄▄▄▄▄▄ ➡️ 0
canvas-idle: task duration ▃▃▅▆▂█▃▁▃▃ ➡️ 391ms
canvas-idle: DOM nodes █▄▁▂▂▅▆▆▇▇ ➡️ 24
canvas-idle: script duration ▅▃▆▇▅█▄▁▅▆ ➡️ 27ms
canvas-idle: event listeners █▂▁▂▇██▂█▆ 📈 11
canvas-idle: TBT ▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
canvas-idle: frame duration ▁▆█▆▆▃▁▁▃▁ ➡️ 17ms
canvas-mouse-sweep: style recalcs ▂▁▄▄▆▇▆▃█▅ ➡️ 79
canvas-mouse-sweep: layouts ▄▄▄▄▄▄▄▄▄▄ ➡️ 12
canvas-mouse-sweep: task duration ▃▂▄▄▅█▆▁▆▄ ➡️ 868ms
canvas-mouse-sweep: DOM nodes ▁▁▄▂▅█▇▃▆▆ ➡️ 64
canvas-mouse-sweep: script duration ▆▆▆▅▅█▆▁▅▆ ➡️ 139ms
canvas-mouse-sweep: event listeners ▁▇▁▁▁██▇▁█ 📈 13
canvas-mouse-sweep: TBT ▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
canvas-mouse-sweep: frame duration ▄▁██▁▅██▅▄ ➡️ 17ms
canvas-zoom-sweep: style recalcs ▃▆█▄▄▆▁▆▁▆ ➡️ 32
canvas-zoom-sweep: layouts ▄▄▄▄▄▄▄▄▄▄ ➡️ 6
canvas-zoom-sweep: task duration ▂▄▅▆▃█▄▁▁▅ ➡️ 338ms
canvas-zoom-sweep: DOM nodes ▁▄█▅▆▆▄▃▅▄ ➡️ 79
canvas-zoom-sweep: script duration ▂▅▇▆▅█▄▁▂▆ ➡️ 30ms
canvas-zoom-sweep: event listeners ▂▂█▁██▇▁█▁ ➡️ 19
canvas-zoom-sweep: TBT ▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
canvas-zoom-sweep: frame duration ▁▁▁█▁▁███▁ ➡️ 17ms
dom-widget-clipping: style recalcs ▄█▇▇▁▇▄▇▂▅ ➡️ 13
dom-widget-clipping: layouts ▄▄▄▄▄▄▄▄▄▄ ➡️ 0
dom-widget-clipping: task duration ▃▅▆▅▂▇█▁▅▅ ➡️ 371ms
dom-widget-clipping: DOM nodes ▄█▇▅▁▅▄▇▃▄ ➡️ 21
dom-widget-clipping: script duration ▅▇▇▆▃█▇▁▇▇ ➡️ 71ms
dom-widget-clipping: event listeners ▅██▁▁▁▁█▁▁ 📉 2
dom-widget-clipping: TBT ▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
dom-widget-clipping: frame duration ▄█▇▅▇▇▅▅▁▇ ➡️ 17ms
large-graph-idle: style recalcs ▁▃▆▃▆▆▃▆██ ➡️ 12
large-graph-idle: layouts ▄▄▄▄▄▄▄▄▄▄ ➡️ 0
large-graph-idle: task duration ▃▃▇▅▃██▁▂▅ ➡️ 569ms
large-graph-idle: DOM nodes ▃▁▂▃▆▆▇▂█▆ ➡️ 25
large-graph-idle: script duration ▅▅▇▆▅█▆▁▃▆ ➡️ 110ms
large-graph-idle: event listeners █▄▁▄▇▇█▂█▇ 📈 29
large-graph-idle: TBT ▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
large-graph-idle: frame duration ▂▁▂▄▅▄▂▂▅█ ➡️ 17ms
large-graph-pan: style recalcs ▂▂▂▁▇▅▃█▆▃ ➡️ 69
large-graph-pan: layouts ▄▄▄▄▄▄▄▄▄▄ ➡️ 0
large-graph-pan: task duration ▄▄▆▄▄█▆▁▂▅ ➡️ 1100ms
large-graph-pan: DOM nodes ▁▃▁▁▅▁▂█▅▂ ➡️ 18
large-graph-pan: script duration ▅▄▆▄▅█▄▁▄▅ ➡️ 413ms
large-graph-pan: event listeners ▆▁▁▃▆▁▃██▃ ➡️ 5
large-graph-pan: TBT ▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
large-graph-pan: frame duration ▃▁█▆▆▆▆█▁▆ ➡️ 17ms
minimap-idle: style recalcs ▄█▁▂▇▂▁▇█▄ ➡️ 9
minimap-idle: layouts ▄▄▄▄▄▄▄▄▄▄ ➡️ 0
minimap-idle: task duration ▃▄▅▇▃█▅▁▁▅ ➡️ 547ms
minimap-idle: DOM nodes ▄█▁▂▇▂▁▇█▄ ➡️ 19
minimap-idle: script duration ▅▆▆▇▅█▅▁▃▆ ➡️ 106ms
minimap-idle: event listeners ▁▃▁▁▆▁▃█▆▁ ➡️ 4
minimap-idle: TBT ▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
minimap-idle: frame duration ▁█▆▆▃▃▆█▆█ ➡️ 17ms
subgraph-dom-widget-clipping: style recalcs ▃▁▆█▇▃▆▇█▅ ➡️ 48
subgraph-dom-widget-clipping: layouts ▄▄▄▄▄▄▄▄▄▄ ➡️ 0
subgraph-dom-widget-clipping: task duration ▅▂▅█▂▆█▁▂▇ ➡️ 398ms
subgraph-dom-widget-clipping: DOM nodes ▂▁▅▅▅▁▇▅█▄ ➡️ 22
subgraph-dom-widget-clipping: script duration ▅▂▄█▂▅▇▁▂▅ ➡️ 131ms
subgraph-dom-widget-clipping: event listeners ▁▅██▁▁█▅█▅ 📈 16
subgraph-dom-widget-clipping: TBT ▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
subgraph-dom-widget-clipping: frame duration ▄█▄▄▄▃▁▆▃▃ ➡️ 17ms
subgraph-idle: style recalcs ▅▁▂▁▆▃▃██▇ ➡️ 12
subgraph-idle: layouts ▄▄▄▄▄▄▄▄▄▄ ➡️ 0
subgraph-idle: task duration ▁▃▆▅▂█▅▁▁▄ ➡️ 378ms
subgraph-idle: DOM nodes ▄▁▂▁▅▃▂▇█▇ ➡️ 24
subgraph-idle: script duration ▂▃▇▆▂█▅▂▁▅ ➡️ 22ms
subgraph-idle: event listeners ▁▁▁▁▅▄▁███ 📈 21
subgraph-idle: TBT ▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
subgraph-idle: frame duration ▃▆▆▆▃▆▁▃▆█ ➡️ 17ms
subgraph-mouse-sweep: style recalcs ▄▂▄█▅▆▃▁▃▄ ➡️ 81
subgraph-mouse-sweep: layouts ▄▄▄▄▄▄▄▄▄▄ ➡️ 16
subgraph-mouse-sweep: task duration ▄▄▅▇▄█▆▁▃▅ ➡️ 785ms
subgraph-mouse-sweep: DOM nodes ▃▂▃█▄▅▃▁▄▃ ➡️ 66
subgraph-mouse-sweep: script duration ▅▆▇▆▅██▁▄▆ ➡️ 105ms
subgraph-mouse-sweep: event listeners ▁▁▁█▇▂▁▇▇▁ ➡️ 5
subgraph-mouse-sweep: TBT ▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
subgraph-mouse-sweep: frame duration ▄▆▄▆▃▃█▁▃▃ ➡️ 17ms
workflow-execution: style recalcs ▃▆█▇▆▆▇▃▄▁ ➡️ 15
workflow-execution: layouts ▆▆▁▆▆█▆▃▆▃ ➡️ 5
workflow-execution: task duration ▄▆▆▆▁▇█▁▃▃ ➡️ 120ms
workflow-execution: DOM nodes ▄▃▅▃█▃▃▄▃▁ ➡️ 152
workflow-execution: script duration ▅▄▅▆▂▇█▁▃▄ ➡️ 29ms
workflow-execution: event listeners ▄███▁██▄█▄ ➡️ 49
workflow-execution: TBT ▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
workflow-execution: frame duration ▆▃▄▁▄█▆▅▄▆ ➡️ 17ms
Raw data
{
  "timestamp": "2026-03-20T12:16:24.436Z",
  "gitSha": "17f6a60751f7b528e42699e8830d1701d790a7fe",
  "branch": "refactor/error-system-cleanup",
  "measurements": [
    {
      "name": "canvas-idle",
      "durationMs": 2074.5660000000044,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 11.368,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 410.165,
      "heapDeltaBytes": 20306572,
      "domNodes": 22,
      "jsHeapTotalBytes": 22806528,
      "scriptDurationMs": 21.074999999999996,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.659999999999947
    },
    {
      "name": "canvas-idle",
      "durationMs": 2019.4930000000113,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 9.838999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 356.559,
      "heapDeltaBytes": 20051376,
      "domNodes": 22,
      "jsHeapTotalBytes": 23068672,
      "scriptDurationMs": 22.537999999999997,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66999999999998
    },
    {
      "name": "canvas-idle",
      "durationMs": 2004.7230000000127,
      "styleRecalcs": 12,
      "styleRecalcDurationMs": 10.828999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 374.576,
      "heapDeltaBytes": 20051704,
      "domNodes": 23,
      "jsHeapTotalBytes": 22544384,
      "scriptDurationMs": 23.705,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.659999999999947
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 2024.2220000000088,
      "styleRecalcs": 86,
      "styleRecalcDurationMs": 46.86800000000001,
      "layouts": 12,
      "layoutDurationMs": 3.89,
      "taskDurationMs": 983.5770000000001,
      "heapDeltaBytes": 16152384,
      "domNodes": 69,
      "jsHeapTotalBytes": 22544384,
      "scriptDurationMs": 138.64499999999998,
      "eventListeners": 30,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66999999999998
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 1982.7809999999886,
      "styleRecalcs": 81,
      "styleRecalcDurationMs": 51.06099999999999,
      "layouts": 12,
      "layoutDurationMs": 3.3289999999999993,
      "taskDurationMs": 954.799,
      "heapDeltaBytes": 15936228,
      "domNodes": 66,
      "jsHeapTotalBytes": 23068672,
      "scriptDurationMs": 133.108,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.659999999999947
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 2034.317000000101,
      "styleRecalcs": 84,
      "styleRecalcDurationMs": 44.324000000000005,
      "layouts": 12,
      "layoutDurationMs": 3.334,
      "taskDurationMs": 968.8890000000001,
      "heapDeltaBytes": 15944312,
      "domNodes": 66,
      "jsHeapTotalBytes": 23855104,
      "scriptDurationMs": 133.126,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66999999999998
    },
    {
      "name": "canvas-zoom-sweep",
      "durationMs": 1758.403000000044,
      "styleRecalcs": 30,
      "styleRecalcDurationMs": 18.189,
      "layouts": 6,
      "layoutDurationMs": 0.7329999999999999,
      "taskDurationMs": 312.191,
      "heapDeltaBytes": 24505488,
      "domNodes": 78,
      "jsHeapTotalBytes": 19922944,
      "scriptDurationMs": 24.46,
      "eventListeners": 19,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.670000000000073
    },
    {
      "name": "canvas-zoom-sweep",
      "durationMs": 1721.0269999999923,
      "styleRecalcs": 32,
      "styleRecalcDurationMs": 17.624000000000006,
      "layouts": 6,
      "layoutDurationMs": 0.49799999999999994,
      "taskDurationMs": 309.079,
      "heapDeltaBytes": 24560040,
      "domNodes": 79,
      "jsHeapTotalBytes": 20709376,
      "scriptDurationMs": 25.397999999999996,
      "eventListeners": 21,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66999999999998
    },
    {
      "name": "canvas-zoom-sweep",
      "durationMs": 1713.653000000022,
      "styleRecalcs": 32,
      "styleRecalcDurationMs": 17.748,
      "layouts": 6,
      "layoutDurationMs": 0.5999999999999999,
      "taskDurationMs": 297.816,
      "heapDeltaBytes": 24411792,
      "domNodes": 80,
      "jsHeapTotalBytes": 20971520,
      "scriptDurationMs": 21.213,
      "eventListeners": 19,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.670000000000073
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 536.9890000000055,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 7.615999999999998,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 321.935,
      "heapDeltaBytes": 7056484,
      "domNodes": 18,
      "jsHeapTotalBytes": 12320768,
      "scriptDurationMs": 58.449999999999996,
      "eventListeners": 2,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.65999999999999
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 592.0810000000074,
      "styleRecalcs": 12,
      "styleRecalcDurationMs": 8.041000000000002,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 353.72999999999996,
      "heapDeltaBytes": 7480032,
      "domNodes": 19,
      "jsHeapTotalBytes": 12058624,
      "scriptDurationMs": 67.67300000000002,
      "eventListeners": 2,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.670000000000027
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 579.9729999999954,
      "styleRecalcs": 12,
      "styleRecalcDurationMs": 8.095,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 348.781,
      "heapDeltaBytes": 7448280,
      "domNodes": 19,
      "jsHeapTotalBytes": 12582912,
      "scriptDurationMs": 64.894,
      "eventListeners": 2,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.65999999999999
    },
    {
      "name": "large-graph-idle",
      "durationMs": 2053.9070000000092,
      "styleRecalcs": 12,
      "styleRecalcDurationMs": 12.048000000000002,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 551.2760000000001,
      "heapDeltaBytes": -20369164,
      "domNodes": -260,
      "jsHeapTotalBytes": 18022400,
      "scriptDurationMs": 99.228,
      "eventListeners": -135,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66999999999998
    },
    {
      "name": "large-graph-idle",
      "durationMs": 2037.6739999999813,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 11.885,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 533.9929999999999,
      "heapDeltaBytes": 4050996,
      "domNodes": -265,
      "jsHeapTotalBytes": 16199680,
      "scriptDurationMs": 96.78299999999999,
      "eventListeners": -155,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66999999999998
    },
    {
      "name": "large-graph-idle",
      "durationMs": 2019.5699999999306,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 10.278000000000002,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 519.2710000000001,
      "heapDeltaBytes": -6158728,
      "domNodes": -265,
      "jsHeapTotalBytes": 15675392,
      "scriptDurationMs": 89.27600000000001,
      "eventListeners": -157,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66999999999998
    },
    {
      "name": "large-graph-pan",
      "durationMs": 2130.970999999988,
      "styleRecalcs": 69,
      "styleRecalcDurationMs": 16.607,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1074.8629999999998,
      "heapDeltaBytes": 16832256,
      "domNodes": -267,
      "jsHeapTotalBytes": 18268160,
      "scriptDurationMs": 396.429,
      "eventListeners": -159,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66999999999998
    },
    {
      "name": "large-graph-pan",
      "durationMs": 2115.2079999999955,
      "styleRecalcs": 68,
      "styleRecalcDurationMs": 15.153,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1063.0230000000001,
      "heapDeltaBytes": 13266424,
      "domNodes": -270,
      "jsHeapTotalBytes": 17219584,
      "scriptDurationMs": 395.10499999999996,
      "eventListeners": -161,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66999999999998
    },
    {
      "name": "large-graph-pan",
      "durationMs": 2135.0499999999784,
      "styleRecalcs": 68,
      "styleRecalcDurationMs": 15.549,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1124.3529999999998,
      "heapDeltaBytes": 1525316,
      "domNodes": -268,
      "jsHeapTotalBytes": 17743872,
      "scriptDurationMs": 439.233,
      "eventListeners": -161,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.659999999999947
    },
    {
      "name": "minimap-idle",
      "durationMs": 2029.440999999963,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 10.482999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 525.474,
      "heapDeltaBytes": 2854436,
      "domNodes": -267,
      "jsHeapTotalBytes": 15937536,
      "scriptDurationMs": 90.293,
      "eventListeners": -159,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.670000000000073
    },
    {
      "name": "minimap-idle",
      "durationMs": 2017.2339999999735,
      "styleRecalcs": 9,
      "styleRecalcDurationMs": 8.793000000000003,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 525.898,
      "heapDeltaBytes": 1727212,
      "domNodes": -271,
      "jsHeapTotalBytes": 16199680,
      "scriptDurationMs": 92.19,
      "eventListeners": -161,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.660000000000036
    },
    {
      "name": "minimap-idle",
      "durationMs": 2010.9579999999596,
      "styleRecalcs": 8,
      "styleRecalcDurationMs": 7.856999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 528.079,
      "heapDeltaBytes": 1709044,
      "domNodes": -269,
      "jsHeapTotalBytes": 16461824,
      "scriptDurationMs": 92.563,
      "eventListeners": -159,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.659999999999947
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 544.2840000000047,
      "styleRecalcs": 50,
      "styleRecalcDurationMs": 14.316999999999998,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 359.061,
      "heapDeltaBytes": 6059352,
      "domNodes": 23,
      "jsHeapTotalBytes": 14155776,
      "scriptDurationMs": 124.514,
      "eventListeners": 8,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.670000000000027
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 553.9380000000165,
      "styleRecalcs": 48,
      "styleRecalcDurationMs": 11.34,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 364.08099999999996,
      "heapDeltaBytes": 6367828,
      "domNodes": 22,
      "jsHeapTotalBytes": 14417920,
      "scriptDurationMs": 123.58100000000002,
      "eventListeners": 8,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.65
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 591.6159999999309,
      "styleRecalcs": 49,
      "styleRecalcDurationMs": 13.834000000000001,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 366.725,
      "heapDeltaBytes": 6651884,
      "domNodes": 25,
      "jsHeapTotalBytes": 13631488,
      "scriptDurationMs": 128.65699999999998,
      "eventListeners": 32,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66999999999998
    },
    {
      "name": "subgraph-idle",
      "durationMs": 2000.9079999999813,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 10.091000000000001,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 341.3,
      "heapDeltaBytes": 19236928,
      "domNodes": 22,
      "jsHeapTotalBytes": 23068672,
      "scriptDurationMs": 19.211,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66999999999998
    },
    {
      "name": "subgraph-idle",
      "durationMs": 2011.825999999985,
      "styleRecalcs": 12,
      "styleRecalcDurationMs": 10.338,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 363.84399999999994,
      "heapDeltaBytes": 10672364,
      "domNodes": 23,
      "jsHeapTotalBytes": 25952256,
      "scriptDurationMs": 19.383999999999997,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.660000000000036
    },
    {
      "name": "subgraph-idle",
      "durationMs": 2020.3310000000556,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 10.007,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 355.19900000000007,
      "heapDeltaBytes": 19362880,
      "domNodes": 22,
      "jsHeapTotalBytes": 23330816,
      "scriptDurationMs": 20.195,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.659999999999947
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 2006.7319999999995,
      "styleRecalcs": 84,
      "styleRecalcDurationMs": 46.682,
      "layouts": 16,
      "layoutDurationMs": 4.17,
      "taskDurationMs": 935.928,
      "heapDeltaBytes": 2252268,
      "domNodes": -191,
      "jsHeapTotalBytes": 23470080,
      "scriptDurationMs": 97.696,
      "eventListeners": -157,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66999999999998
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1975.3899999999476,
      "styleRecalcs": 84,
      "styleRecalcDurationMs": 46.048,
      "layouts": 16,
      "layoutDurationMs": 4.590000000000001,
      "taskDurationMs": 904.8290000000001,
      "heapDeltaBytes": 11276220,
      "domNodes": 73,
      "jsHeapTotalBytes": 23592960,
      "scriptDurationMs": 100.86700000000002,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66999999999998
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1990.6550000000607,
      "styleRecalcs": 85,
      "styleRecalcDurationMs": 53.026,
      "layouts": 16,
      "layoutDurationMs": 4.473,
      "taskDurationMs": 923.55,
      "heapDeltaBytes": 11948672,
      "domNodes": 75,
      "jsHeapTotalBytes": 23330816,
      "scriptDurationMs": 103.49899999999998,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.660000000000036
    },
    {
      "name": "vue-large-graph-idle",
      "durationMs": 12000.580000000013,
      "styleRecalcs": 0,
      "styleRecalcDurationMs": 0,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 11987.934999999998,
      "heapDeltaBytes": -36290764,
      "domNodes": -8342,
      "jsHeapTotalBytes": 17915904,
      "scriptDurationMs": 611.3299999999999,
      "eventListeners": -16474,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.670000000000073
    },
    {
      "name": "vue-large-graph-idle",
      "durationMs": 12082.936999999958,
      "styleRecalcs": 0,
      "styleRecalcDurationMs": 0,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 12073.86,
      "heapDeltaBytes": -29190304,
      "domNodes": -8342,
      "jsHeapTotalBytes": 26566656,
      "scriptDurationMs": 608.1070000000001,
      "eventListeners": -16478,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 18.339999999999783
    },
    {
      "name": "vue-large-graph-idle",
      "durationMs": 12129.71099999993,
      "styleRecalcs": 0,
      "styleRecalcDurationMs": 0,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 12119.546000000002,
      "heapDeltaBytes": -29724172,
      "domNodes": -8342,
      "jsHeapTotalBytes": 25780224,
      "scriptDurationMs": 610.962,
      "eventListeners": -16472,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.659999999999854
    },
    {
      "name": "vue-large-graph-pan",
      "durationMs": 14509.979999999985,
      "styleRecalcs": 65,
      "styleRecalcDurationMs": 13.298999999999978,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 14486.965999999999,
      "heapDeltaBytes": -21048940,
      "domNodes": -8342,
      "jsHeapTotalBytes": -19656704,
      "scriptDurationMs": 887.418,
      "eventListeners": -16468,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 20
    },
    {
      "name": "vue-large-graph-pan",
      "durationMs": 14014.067000000012,
      "styleRecalcs": 65,
      "styleRecalcDurationMs": 13.644000000000018,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 13995.694,
      "heapDeltaBytes": -21070976,
      "domNodes": -8342,
      "jsHeapTotalBytes": -17559552,
      "scriptDurationMs": 879.3740000000001,
      "eventListeners": -16468,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 20
    },
    {
      "name": "vue-large-graph-pan",
      "durationMs": 14209.664000000088,
      "styleRecalcs": 67,
      "styleRecalcDurationMs": 14.913999999999984,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 14188.384,
      "heapDeltaBytes": -33944504,
      "domNodes": -8344,
      "jsHeapTotalBytes": -2093056,
      "scriptDurationMs": 861.6240000000001,
      "eventListeners": -16472,
      "totalBlockingTimeMs": 38,
      "frameDurationMs": 20
    },
    {
      "name": "workflow-execution",
      "durationMs": 460.6950000000438,
      "styleRecalcs": 16,
      "styleRecalcDurationMs": 25.243000000000002,
      "layouts": 5,
      "layoutDurationMs": 1.4580000000000002,
      "taskDurationMs": 126.36699999999998,
      "heapDeltaBytes": 4718464,
      "domNodes": 166,
      "jsHeapTotalBytes": 262144,
      "scriptDurationMs": 30.474999999999994,
      "eventListeners": 71,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.660000000000036
    },
    {
      "name": "workflow-execution",
      "durationMs": 451.652000000081,
      "styleRecalcs": 19,
      "styleRecalcDurationMs": 26.953999999999997,
      "layouts": 5,
      "layoutDurationMs": 1.3980000000000001,
      "taskDurationMs": 126.35799999999998,
      "heapDeltaBytes": 4492252,
      "domNodes": 157,
      "jsHeapTotalBytes": 0,
      "scriptDurationMs": 30.016000000000002,
      "eventListeners": 71,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.65999999999999
    },
    {
      "name": "workflow-execution",
      "durationMs": 439.3489999999929,
      "styleRecalcs": 17,
      "styleRecalcDurationMs": 23.236,
      "layouts": 4,
      "layoutDurationMs": 1.0610000000000002,
      "taskDurationMs": 112.44499999999996,
      "heapDeltaBytes": 4379236,
      "domNodes": 155,
      "jsHeapTotalBytes": 262144,
      "scriptDurationMs": 23.313,
      "eventListeners": 71,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.670000000000027
    }
  ]
}

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

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

⚠️ Outside diff range comments (1)
src/components/rightSidePanel/errors/TabErrors.vue (1)

370-385: ⚠️ Potential issue | 🟠 Major

This focus-collapse loop still closes non-execution groups.

setSectionCollapsed() is still called for every group, so missing_node, swap_nodes, and missing_model end up collapsed whenever focusedErrorNodeId is set. That keeps #10085 unresolved; only execution groups should participate in this behavior.

Suggested change
     const prefix = `${graphNodeId}:`
     for (const group of allErrorGroups.value) {
-      const hasMatch =
-        group.type === 'execution' &&
-        group.cards.some(
-          (card) =>
-            card.graphNodeId === graphNodeId ||
-            (card.nodeId?.startsWith(prefix) ?? false)
-        )
-      setSectionCollapsed(group.title, !hasMatch)
+      if (group.type !== 'execution') continue
+
+      const hasMatch = group.cards.some(
+        (card) =>
+          card.graphNodeId === graphNodeId ||
+          (card.nodeId?.startsWith(prefix) ?? false)
+      )
+      setSectionCollapsed(group.title, !hasMatch)
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/rightSidePanel/errors/TabErrors.vue` around lines 370 - 385,
The watcher on rightSidePanelStore.focusedErrorNodeId is collapsing every group
because setSectionCollapsed(group.title, !) is invoked unconditionally; change
the loop to only call setSectionCollapsed for groups where group.type ===
'execution' (i.e., check group.type before evaluating hasMatch and invoking
setSectionCollapsed), keeping the existing matching logic (card.graphNodeId /
card.nodeId?.startsWith) and still clearing
rightSidePanelStore.focusedErrorNodeId at the end.
🧹 Nitpick comments (6)
src/composables/graph/useErrorClearingHooks.test.ts (1)

319-337: Consider verifying onWidgetChanged restoration as well.

This test verifies that onConnectionsChange is restored, but the implementation also wraps and restores onWidgetChanged. For complete behavioral coverage, consider asserting on both callbacks:

💡 Suggested addition
   it('restores original node callbacks when a node is removed', () => {
     const graph = new LGraph()
     const node = new LGraphNode('test')
     node.addInput('clip', 'CLIP')
+    node.addWidget('number', 'steps', 20, () => undefined, {})
     const originalOnConnectionsChange = vi.fn()
+    const originalOnWidgetChanged = vi.fn()
     node.onConnectionsChange = originalOnConnectionsChange
+    node.onWidgetChanged = originalOnWidgetChanged
     graph.add(node)

     installErrorClearingHooks(graph)

     // Callbacks should be chained (not the originals)
     expect(node.onConnectionsChange).not.toBe(originalOnConnectionsChange)
+    expect(node.onWidgetChanged).not.toBe(originalOnWidgetChanged)

     // Simulate node removal via the graph hook
     graph.onNodeRemoved!(node)

     // Original callbacks should be restored
     expect(node.onConnectionsChange).toBe(originalOnConnectionsChange)
+    expect(node.onWidgetChanged).toBe(originalOnWidgetChanged)
   })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/composables/graph/useErrorClearingHooks.test.ts` around lines 319 - 337,
The test only asserts restoration of onConnectionsChange but
installErrorClearingHooks also wraps onWidgetChanged; update the spec in the
test case (the one creating LGraph, LGraphNode 'test', assigning
originalOnConnectionsChange) to also set an originalOnWidgetChanged mock on
node.onWidgetChanged, assert node.onWidgetChanged is replaced after
installErrorClearingHooks, then after invoking graph.onNodeRemoved!(node) assert
node.onWidgetChanged has been restored to the original mock; reference
installErrorClearingHooks, node.onConnectionsChange, node.onWidgetChanged, and
graph.onNodeRemoved in your changes.
src/platform/nodeReplacement/useNodeReplacement.ts (1)

331-333: Update stale doc comments that still mention the execution error store.

Implementation now removes types from useMissingNodesErrorStore, so the block comments should match to avoid confusion.

Suggested doc-only patch
- * replaced types from the execution error store.
+ * replaced types from the missing nodes error store.
...
- * the succeeded types from the execution error store.
+ * the succeeded types from the missing nodes error store.

Also applies to: 342-344

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/platform/nodeReplacement/useNodeReplacement.ts` around lines 331 - 333,
Update the stale doc comments in useNodeReplacement.ts that refer to "execution
error store" to instead reference the current store name and behavior
(useMissingNodesErrorStore); specifically update the comment block above the
function that "Replaces all nodes in a single swap group and removes
successfully replaced types from the execution error store" to mention removing
types from useMissingNodesErrorStore (also fix the similar text at the later doc
block around the other comment at lines ~342-344) so the prose matches the
implementation in functions like useNodeReplacement and any helpers that call
useMissingNodesErrorStore.
src/components/rightSidePanel/errors/TabErrors.test.ts (1)

219-245: Prove the runtime error is rendered only once.

This currently proves the dedicated panel exists, but it will still pass if the same runtime error also renders inside the accordion. Add a uniqueness or explicit negative assertion so the test actually covers the regression.

💡 One simple way to tighten it
     const runtimePanel = wrapper.find('[data-testid="runtime-error-panel"]')
     expect(runtimePanel.exists()).toBe(true)
+    expect(
+      wrapper.text().match(/RuntimeError: Out of memory/g) ?? []
+    ).toHaveLength(1)
As per coding guidelines, "Do not write tests that just test the mocks; ensure tests fail when code behaves unexpectedly."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/rightSidePanel/errors/TabErrors.test.ts` around lines 219 -
245, The test currently only asserts the dedicated runtime panel exists but not
that the same error isn't also rendered inside the accordion; update the test in
TabErrors.test.ts to assert uniqueness by checking that the runtime error text
(e.g., "RuntimeError: Out of memory" or the node title "KSampler") appears
exactly once or that the accordion container does not contain that text: after
mountComponent (which supplies executionError.lastExecutionError and mocks
getNodeByExecutionId) add an explicit negative assertion that the accordion
element (find by its test id or selector) does not contain the runtime error
content, or assert runtimePanel.length === 1 and no matching elements exist
inside the accordion to ensure the runtime error is not duplicated.
src/platform/nodeReplacement/missingNodeScan.test.ts (1)

23-25: Use undefined in the getCnrIdFromNode mock.

The real helper returns string | undefined, so returning null here introduces a state the production path never emits and can mask null-handling regressions.

💡 Mock the real contract
 vi.mock('@/platform/nodeReplacement/cnrIdUtil', () => ({
-  getCnrIdFromNode: vi.fn(() => null)
+  getCnrIdFromNode: vi.fn(() => undefined)
 }))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/platform/nodeReplacement/missingNodeScan.test.ts` around lines 23 - 25,
The mock for getCnrIdFromNode currently returns null which doesn't match the
real contract (string | undefined); update the vi.mock for
'@/platform/nodeReplacement/cnrIdUtil' so getCnrIdFromNode returns undefined
instead (e.g., vi.fn(() => undefined) or vi.fn().mockReturnValue(undefined)) to
mirror production behavior and avoid introducing a null-only state.
src/components/rightSidePanel/errors/useErrorGroups.test.ts (1)

546-555: Assert the missing-node title, not just non-empty output.

length > 0 only proves that some message was emitted. This will still pass if groupedErrorMessages stops reflecting missing-node data and contains an unrelated entry instead.

💡 Stronger assertion
       missingNodesStore.setMissingNodeTypes([
         makeMissingNodeType('NodeA', { cnrId: 'pack-1' })
       ])
       await nextTick()

-      expect(groups.groupedErrorMessages.value.length).toBeGreaterThan(0)
+      const missingGroup = groups.allErrorGroups.value.find(
+        (g) => g.type === 'missing_node'
+      )
+      if (!missingGroup) throw new Error('missing_node group was not created')
+      expect(groups.groupedErrorMessages.value).toContain(missingGroup.title)
As per coding guidelines, "Do not write tests that just test the mocks; ensure tests fail when code behaves unexpectedly."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/rightSidePanel/errors/useErrorGroups.test.ts` around lines 546
- 555, Replace the weak length-based assertion with a specific check that the
missing-node title emitted by createErrorGroups()/makeMissingNodeType appears in
the grouped messages: after setting
useMissingNodesErrorStore().setMissingNodeTypes([makeMissingNodeType('NodeA', {
cnrId: 'pack-1' })]) and awaiting nextTick(), assert that
groups.groupedErrorMessages.value contains the expected title string produced
for the missing node (e.g., "NodeA" or the exact title format returned by
makeMissingNodeType) rather than only asserting length > 0.
src/components/rightSidePanel/errors/ErrorNodeCard.test.ts (1)

232-240: Assert the fallback serverLogs payload here.

mockGenerateErrorReport always returns a report, so this still passes even if the fallback log string is never forwarded. Please assert the call payload as well; otherwise this recovery path is not really covered.

🧪 Suggested assertion
   expect(mockGenerateErrorReport).toHaveBeenCalledOnce()
+  expect(mockGenerateErrorReport).toHaveBeenCalledWith(
+    expect.objectContaining({
+      serverLogs: 'Failed to retrieve server logs'
+    })
+  )
   expect(wrapper.text()).toContain('ComfyUI Error Report')
As per coding guidelines: "Do not write tests that just test the mocks; ensure tests fail when code behaves unexpectedly."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/rightSidePanel/errors/ErrorNodeCard.test.ts` around lines 232
- 240, The test 'generates report with fallback logs when getLogs fails'
currently only checks that mockGenerateErrorReport was called and the UI text;
update it to assert the actual payload passed to mockGenerateErrorReport
contains the fallback serverLogs string. After calling
mountCard(makeRuntimeErrorCard()) and await flushPromises(), inspect
mockGenerateErrorReport.mock.calls[0] and assert that the first argument's
serverLogs (or payload.serverLogs) equals or contains the expected fallback
message (the string your code sets when mockGetLogs rejects); keep using
mockGetLogs.mockRejectedValue(...) and ensure this assertion fails if the
fallback is not forwarded.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@browser_tests/tests/dialog.spec.ts`:
- Around line 31-32: Replace fragile getByText(/Missing Node Packs/) and similar
text-based assertions with the centralized test IDs from fixtures/selectors.ts:
use the overlay group test IDs (e.g. TestIds.MISSING_NODE_PACKS_GROUP and
TestIds.MISSING_MODELS_GROUP) with errorOverlay.getByTestId(...) and
expect(...).toBeVisible() so the tests no longer depend on English copy or
locale; update the occurrences referenced (around the blocks currently using
missingNodesTitle and the analogous missing models checks at the other ranges)
to use these TestIds instead.
- Around line 147-180: The test currently uses page-global English button labels
which break under non-English locales; change it to scope to the runtime error
panel and use centralized TestIds for locale-safe selectors: after opening the
overlay, anchor to TestIds.dialogs.runtimeErrorPanel (use
comfyPage.page.getByTestId(TestIds.dialogs.runtimeErrorPanel) or similar) and
replace getByRole(name: 'See Errors' / 'Find on GitHub' / 'Copy') with
getByTestId calls that reference the centralized IDs from fixtures/selectors.ts
(e.g., TestIds.dialogs.seeErrorsButton, TestIds.dialogs.findOnGithubButton,
TestIds.dialogs.copyButton), and use the runtime panel locator to query those
buttons so the assertions only target the intended runtime-action UI.

In `@src/components/rightSidePanel/errors/useErrorReport.ts`:
- Around line 60-73: The call to app.rootGraph.serialize() is outside the
try/catch and can throw, preventing fallback error reporting; move or wrap
serialization inside the same try/catch used for generateErrorReport (or add a
small try/catch just around app.rootGraph.serialize()) so that if serialize()
fails you catch the error and still call generateErrorReport with a safe
fallback (e.g., null or an indicative string) for the workflow field; update the
block that iterates runtimeErrors (and the generateErrorReport invocation) to
use the guarded workflow value so serialization failures don't reject the
mounting hook.
- Around line 42-50: The code is triggering refetchSystemStats while the store's
initial fetch may already be in flight; update the logic in useErrorReport to
avoid calling systemStatsStore.refetchSystemStats when the store is currently
loading. Specifically, check the store's loading flag (e.g.,
systemStatsStore.isLoading or systemStatsStore.loading) before calling
refetchSystemStats: only call refetchSystemStats when systemStats is null AND
the store is not loading, otherwise wait for the in-flight load to resolve (or
rely on the store's populated systemStats) to prevent duplicate requests and
race conditions.

In `@src/composables/graph/useNodeErrorFlagSync.ts`:
- Around line 83-103: The watcher for reconciling node error flags misses the
Errors Tab setting as a reactive source, so toggling
Comfy.RightSidePanel.ShowErrorsTab won't retrigger the watcher; update the watch
sources to include the setting (e.g., add a computed or arrow function that
returns useSettingStore().get('Comfy.RightSidePanel.ShowErrorsTab')) alongside
lastNodeErrors and missingModelStore.missingModelNodeIds so that
reconcileNodeErrorFlags(app.rootGraph, lastNodeErrors.value, ...) runs when the
setting changes; keep the rest of the logic (app.isGraphReady guard,
missingModelAncestorExecutionIds branch) unchanged.

---

Outside diff comments:
In `@src/components/rightSidePanel/errors/TabErrors.vue`:
- Around line 370-385: The watcher on rightSidePanelStore.focusedErrorNodeId is
collapsing every group because setSectionCollapsed(group.title, !) is invoked
unconditionally; change the loop to only call setSectionCollapsed for groups
where group.type === 'execution' (i.e., check group.type before evaluating
hasMatch and invoking setSectionCollapsed), keeping the existing matching logic
(card.graphNodeId / card.nodeId?.startsWith) and still clearing
rightSidePanelStore.focusedErrorNodeId at the end.

---

Nitpick comments:
In `@src/components/rightSidePanel/errors/ErrorNodeCard.test.ts`:
- Around line 232-240: The test 'generates report with fallback logs when
getLogs fails' currently only checks that mockGenerateErrorReport was called and
the UI text; update it to assert the actual payload passed to
mockGenerateErrorReport contains the fallback serverLogs string. After calling
mountCard(makeRuntimeErrorCard()) and await flushPromises(), inspect
mockGenerateErrorReport.mock.calls[0] and assert that the first argument's
serverLogs (or payload.serverLogs) equals or contains the expected fallback
message (the string your code sets when mockGetLogs rejects); keep using
mockGetLogs.mockRejectedValue(...) and ensure this assertion fails if the
fallback is not forwarded.

In `@src/components/rightSidePanel/errors/TabErrors.test.ts`:
- Around line 219-245: The test currently only asserts the dedicated runtime
panel exists but not that the same error isn't also rendered inside the
accordion; update the test in TabErrors.test.ts to assert uniqueness by checking
that the runtime error text (e.g., "RuntimeError: Out of memory" or the node
title "KSampler") appears exactly once or that the accordion container does not
contain that text: after mountComponent (which supplies
executionError.lastExecutionError and mocks getNodeByExecutionId) add an
explicit negative assertion that the accordion element (find by its test id or
selector) does not contain the runtime error content, or assert
runtimePanel.length === 1 and no matching elements exist inside the accordion to
ensure the runtime error is not duplicated.

In `@src/components/rightSidePanel/errors/useErrorGroups.test.ts`:
- Around line 546-555: Replace the weak length-based assertion with a specific
check that the missing-node title emitted by
createErrorGroups()/makeMissingNodeType appears in the grouped messages: after
setting
useMissingNodesErrorStore().setMissingNodeTypes([makeMissingNodeType('NodeA', {
cnrId: 'pack-1' })]) and awaiting nextTick(), assert that
groups.groupedErrorMessages.value contains the expected title string produced
for the missing node (e.g., "NodeA" or the exact title format returned by
makeMissingNodeType) rather than only asserting length > 0.

In `@src/composables/graph/useErrorClearingHooks.test.ts`:
- Around line 319-337: The test only asserts restoration of onConnectionsChange
but installErrorClearingHooks also wraps onWidgetChanged; update the spec in the
test case (the one creating LGraph, LGraphNode 'test', assigning
originalOnConnectionsChange) to also set an originalOnWidgetChanged mock on
node.onWidgetChanged, assert node.onWidgetChanged is replaced after
installErrorClearingHooks, then after invoking graph.onNodeRemoved!(node) assert
node.onWidgetChanged has been restored to the original mock; reference
installErrorClearingHooks, node.onConnectionsChange, node.onWidgetChanged, and
graph.onNodeRemoved in your changes.

In `@src/platform/nodeReplacement/missingNodeScan.test.ts`:
- Around line 23-25: The mock for getCnrIdFromNode currently returns null which
doesn't match the real contract (string | undefined); update the vi.mock for
'@/platform/nodeReplacement/cnrIdUtil' so getCnrIdFromNode returns undefined
instead (e.g., vi.fn(() => undefined) or vi.fn().mockReturnValue(undefined)) to
mirror production behavior and avoid introducing a null-only state.

In `@src/platform/nodeReplacement/useNodeReplacement.ts`:
- Around line 331-333: Update the stale doc comments in useNodeReplacement.ts
that refer to "execution error store" to instead reference the current store
name and behavior (useMissingNodesErrorStore); specifically update the comment
block above the function that "Replaces all nodes in a single swap group and
removes successfully replaced types from the execution error store" to mention
removing types from useMissingNodesErrorStore (also fix the similar text at the
later doc block around the other comment at lines ~342-344) so the prose matches
the implementation in functions like useNodeReplacement and any helpers that
call useMissingNodesErrorStore.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d992ca3b-d72f-440f-835c-aac5ca5f32b2

📥 Commits

Reviewing files that changed from the base of the PR and between 3591579 and 6ed156a.

📒 Files selected for processing (37)
  • browser_tests/fixtures/selectors.ts
  • browser_tests/tests/dialog.spec.ts
  • browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts
  • src/components/dialog/content/error/FindIssueButton.vue
  • src/components/rightSidePanel/RightSidePanel.vue
  • src/components/rightSidePanel/errors/ErrorNodeCard.test.ts
  • src/components/rightSidePanel/errors/ErrorNodeCard.vue
  • src/components/rightSidePanel/errors/MissingNodeCard.test.ts
  • src/components/rightSidePanel/errors/MissingPackGroupRow.vue
  • src/components/rightSidePanel/errors/TabErrors.test.ts
  • src/components/rightSidePanel/errors/TabErrors.vue
  • src/components/rightSidePanel/errors/swapNodeGroups.test.ts
  • src/components/rightSidePanel/errors/types.ts
  • src/components/rightSidePanel/errors/useErrorActions.ts
  • src/components/rightSidePanel/errors/useErrorGroups.test.ts
  • src/components/rightSidePanel/errors/useErrorGroups.ts
  • src/components/rightSidePanel/errors/useErrorReport.ts
  • src/composables/graph/useErrorClearingHooks.test.ts
  • src/composables/graph/useErrorClearingHooks.ts
  • src/composables/graph/useNodeErrorFlagSync.ts
  • src/locales/en/main.json
  • src/platform/nodeReplacement/cnrIdUtil.test.ts
  • src/platform/nodeReplacement/cnrIdUtil.ts
  • src/platform/nodeReplacement/missingNodeScan.test.ts
  • src/platform/nodeReplacement/missingNodeScan.ts
  • src/platform/nodeReplacement/missingNodesErrorStore.ts
  • src/platform/nodeReplacement/useNodeReplacement.test.ts
  • src/platform/nodeReplacement/useNodeReplacement.ts
  • src/platform/workflow/core/services/workflowService.test.ts
  • src/platform/workflow/core/services/workflowService.ts
  • src/renderer/extensions/vueNodes/VideoPreview.vue
  • src/renderer/extensions/vueNodes/components/ImagePreview.vue
  • src/renderer/extensions/vueNodes/components/LGraphNode.vue
  • src/scripts/app.ts
  • src/stores/executionErrorStore.test.ts
  • src/stores/executionErrorStore.ts
  • src/stores/executionStore.test.ts

Comment on lines +31 to 32
const missingNodesTitle = errorOverlay.getByText(/Missing Node Packs/)
await expect(missingNodesTitle).toBeVisible()
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use the new group test IDs here instead of English overlay copy.

These assertions still depend on the literal “Missing Node Packs / Missing Models” text, which leaves #10033 unresolved and will break on locale or copy changes. This PR already adds dedicated selectors for both groups, so the overlay checks can be made stable now.

Suggested change
-    const missingNodesTitle = errorOverlay.getByText(/Missing Node Packs/)
+    const missingNodesTitle = errorOverlay.getByTestId(
+      TestIds.dialogs.missingNodePacksGroup
+    )
     await expect(missingNodesTitle).toBeVisible()
-    const missingModelsTitle = errorOverlay.getByText(/Missing Models/)
+    const missingModelsTitle = errorOverlay.getByTestId(
+      TestIds.dialogs.missingModelsGroup
+    )
     await expect(missingModelsTitle).toBeVisible()

As per coding guidelines, use centralized TestIds from fixtures/selectors.ts for DOM element selection in E2E tests.

Also applies to: 45-46, 207-208, 223-240

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@browser_tests/tests/dialog.spec.ts` around lines 31 - 32, Replace fragile
getByText(/Missing Node Packs/) and similar text-based assertions with the
centralized test IDs from fixtures/selectors.ts: use the overlay group test IDs
(e.g. TestIds.MISSING_NODE_PACKS_GROUP and TestIds.MISSING_MODELS_GROUP) with
errorOverlay.getByTestId(...) and expect(...).toBeVisible() so the tests no
longer depend on English copy or locale; update the occurrences referenced
(around the blocks currently using missingNodesTitle and the analogous missing
models checks at the other ranges) to use these TestIds instead.

Comment on lines +147 to +180
test.describe('Error actions in Errors Tab', { tag: '@ui' }, () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
await comfyPage.settings.setSetting(
'Comfy.RightSidePanel.ShowErrorsTab',
true
)
})

test('Should show Find on GitHub and Copy buttons in error card after execution error', async ({
comfyPage
}) => {
await comfyPage.workflow.loadWorkflow('nodes/execution_error')
await comfyPage.command.executeCommand('Comfy.QueuePrompt')
await comfyPage.nextFrame()

// Wait for error overlay and click "See Errors"
const errorOverlay = comfyPage.page.getByTestId(
TestIds.dialogs.errorOverlay
)
await expect(errorOverlay).toBeVisible()
await errorOverlay.getByRole('button', { name: 'See Errors' }).click()
await expect(errorOverlay).not.toBeVisible()

// Verify Find on GitHub button is present in the error card
const findOnGithubButton = comfyPage.page.getByRole('button', {
name: 'Find on GitHub'
})
await expect(findOnGithubButton).toBeVisible()

// Verify Copy button is present in the error card
const copyButton = comfyPage.page.getByRole('button', { name: 'Copy' })
await expect(copyButton).toBeVisible()
})
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Scope the new runtime-action test and make the button selectors locale-safe.

This new spec still uses page-global English button names (See Errors, Find on GitHub, Copy), so it can match unrelated controls and still fails under non-English locales. Please anchor the assertions to TestIds.dialogs.runtimeErrorPanel, then resolve the buttons via a centralized test id or a locale-aware selector instead of raw English text.

As per coding guidelines, use centralized TestIds from fixtures/selectors.ts for DOM element selection in E2E tests.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@browser_tests/tests/dialog.spec.ts` around lines 147 - 180, The test
currently uses page-global English button labels which break under non-English
locales; change it to scope to the runtime error panel and use centralized
TestIds for locale-safe selectors: after opening the overlay, anchor to
TestIds.dialogs.runtimeErrorPanel (use
comfyPage.page.getByTestId(TestIds.dialogs.runtimeErrorPanel) or similar) and
replace getByRole(name: 'See Errors' / 'Find on GitHub' / 'Copy') with
getByTestId calls that reference the centralized IDs from fixtures/selectors.ts
(e.g., TestIds.dialogs.seeErrorsButton, TestIds.dialogs.findOnGithubButton,
TestIds.dialogs.copyButton), and use the runtime panel locator to query those
buttons so the assertions only target the intended runtime-action UI.

…ovements

- Extract missingNodesErrorStore from executionErrorStore, remove delegation pattern
- Extract useNodeErrorFlagSync composable for node error flag reconciliation
- Move getCnrIdFromNode/getCnrIdFromProperties to platform layer (DDD fix)
- Add explicit callback cleanup on node removal in useErrorClearingHooks
- Fix mock hoisting inconsistency in MissingNodeCard.test.ts
- Add data-testid to error groups, image/video error spans
- Update E2E tests to use scoped locators and testids

Fixes #9875, Fixes #10027, Fixes #10033, Fixes #10085
…uality

- Fix per-node callback restoration on cleanup in useErrorClearingHooks
- Add unmount cancellation guard to useErrorReport async onMounted
- Move missingNodesErrorStore to platform/nodeReplacement (DDD alignment)
- Extract activeMissingNodeGraphIds to component-level computed
- Extract useErrorActions composable (deduplicate telemetry+command pattern)
- Add data-testid to copy button, use in test selector
- Add tests for onNodeRemoved restoration and double-install guard
- Return watch stop handle from useNodeErrorFlagSync
- Add asyncResolvedIds eviction on missingNodesError reset
- Add console.warn to silent catch blocks and empty array guard
- Hoist useCommandStore to setup scope, fix floating promises
- Replace replace() with replaceAll() in testid expression
- Convert function expression to declaration in FindIssueButton
@jaeone94 jaeone94 force-pushed the refactor/error-system-cleanup branch from 6ed156a to 9f3cacf Compare March 20, 2026 11:13
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
src/platform/nodeReplacement/useNodeReplacement.ts (1)

334-350: ⚠️ Potential issue | 🟠 Major

Don’t clear missing-node entries by type after only a partial replacement.

replaceNodesInPlace() records a type as soon as the first node of that type succeeds, but these callers then prune the error store by type. If another instance of the same type fails later or the loop aborts, the remaining unresolved placeholders disappear from the Errors tab even though they are still missing. Remove by successfully replaced node ids/execution ids, or only prune a type after all of its instances finish successfully.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/platform/nodeReplacement/useNodeReplacement.ts` around lines 334 - 350,
The current logic in replaceGroup and replaceAllGroups incorrectly calls
useMissingNodesErrorStore().removeMissingNodesByType(replaced) because
replaceNodesInPlace() marks a type as "replaced" on the first success, which can
prematurely clear remaining missing placeholders; change the callers to remove
by the specific successfully replaced node identifiers (or execution ids)
instead of by type, or only call removeMissingNodesByType for a given type after
all instances of that type completed successfully. Concretely: update
replaceNodesInPlace to return the list of concrete replaced node ids/execution
ids (not just types) or return a map of type→successfulIds, then in replaceGroup
and replaceAllGroups call the error store removal API that accepts ids (e.g.,
removeMissingNodesById or a new removeMissingNodesSuccesses) or check counts so
you only invoke removeMissingNodesByType when every instance of a type is
confirmed replaced; keep references to replaceNodesInPlace, replaceGroup,
replaceAllGroups, and useMissingNodesErrorStore().removeMissingNodesByType when
making the change.
♻️ Duplicate comments (1)
src/components/rightSidePanel/errors/useErrorReport.ts (1)

42-50: ⚠️ Potential issue | 🟡 Minor

Avoid triggering a second stats fetch while initial load may still be in flight.

!systemStatsStore.systemStats can be true during the store’s first async load, so this branch may still call refetchSystemStats() unnecessarily.

Please verify store-level dedupe/loading semantics first:

#!/bin/bash
set -euo pipefail

STORE_FILE="$(fd 'systemStatsStore.ts$' src | head -n 1)"
echo "Inspecting: ${STORE_FILE}"

rg -n -C3 'defineStore|systemStats|refetchSystemStats|isLoading|loading|fetch' "${STORE_FILE}"

Expected: either refetchSystemStats is explicitly no-op/deduped while loading, or this caller should guard on a loading flag before refetching.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/rightSidePanel/errors/useErrorReport.ts` around lines 42 - 50,
The guard currently calls systemStatsStore.refetchSystemStats() whenever
!systemStatsStore.systemStats, which can trigger a duplicate fetch during the
store's initial async load; update the logic to first check the store's
loading/dedupe flag (e.g., systemStatsStore.isLoading or
systemStatsStore.loading) and only call refetchSystemStats() if not already
loading, or alternatively make refetchSystemStats itself no-op when a fetch is
in-flight; locate the usage around systemStatsStore.systemStats and
refetchSystemStats and add the loading check (or make refetchSystemStats dedupe
internally) so we never start a second fetch while the initial load is in flight
and keep the existing cancelled and null checks intact.
🧹 Nitpick comments (1)
src/components/rightSidePanel/errors/useErrorReport.ts (1)

58-60: Remove the duplicated cancellation guard.

Line 58 and Line 60 perform the same immediate if (cancelled) return check; the second one is redundant.

Suggested cleanup
-    if (cancelled) return
-
     if (cancelled) return
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/rightSidePanel/errors/useErrorReport.ts` around lines 58 - 60,
The code contains a duplicated cancellation guard in the async flow inside
useErrorReport (two consecutive `if (cancelled) return` checks); remove the
redundant second guard so only a single `if (cancelled) return` remains (locate
the duplicate inside the useErrorReport function and delete the extra
occurrence).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/platform/nodeReplacement/missingNodeScan.test.ts`:
- Around line 23-25: The mock for getCnrIdFromNode in missingNodeScan.test.ts
currently returns null which widens the helper's contract; update the vi.mock
setup so getCnrIdFromNode returns undefined (or no value) instead of null to
match the real helper's string | undefined return type; locate the mock
definition for getCnrIdFromNode and change its implementation to return
undefined so tests exercise the same "missing CNR id" branch as production.

In `@src/platform/nodeReplacement/missingNodesErrorStore.ts`:
- Around line 83-85: The computed missingNodeCount currently collapses any
non-empty missingNodesError to 1; update missingNodeCount to return the actual
number of missing nodes by using the real collection size (e.g.,
missingNodesError.value.size for a Set or missingNodesError.value.length for an
Array) instead of (missingNodesError.value ? 1 : 0); keep hasMissingNodes as is
and ensure missingNodeCount handles null/undefined safely (return 0 when
missingNodesError.value is falsy).

---

Outside diff comments:
In `@src/platform/nodeReplacement/useNodeReplacement.ts`:
- Around line 334-350: The current logic in replaceGroup and replaceAllGroups
incorrectly calls useMissingNodesErrorStore().removeMissingNodesByType(replaced)
because replaceNodesInPlace() marks a type as "replaced" on the first success,
which can prematurely clear remaining missing placeholders; change the callers
to remove by the specific successfully replaced node identifiers (or execution
ids) instead of by type, or only call removeMissingNodesByType for a given type
after all instances of that type completed successfully. Concretely: update
replaceNodesInPlace to return the list of concrete replaced node ids/execution
ids (not just types) or return a map of type→successfulIds, then in replaceGroup
and replaceAllGroups call the error store removal API that accepts ids (e.g.,
removeMissingNodesById or a new removeMissingNodesSuccesses) or check counts so
you only invoke removeMissingNodesByType when every instance of a type is
confirmed replaced; keep references to replaceNodesInPlace, replaceGroup,
replaceAllGroups, and useMissingNodesErrorStore().removeMissingNodesByType when
making the change.

---

Duplicate comments:
In `@src/components/rightSidePanel/errors/useErrorReport.ts`:
- Around line 42-50: The guard currently calls
systemStatsStore.refetchSystemStats() whenever !systemStatsStore.systemStats,
which can trigger a duplicate fetch during the store's initial async load;
update the logic to first check the store's loading/dedupe flag (e.g.,
systemStatsStore.isLoading or systemStatsStore.loading) and only call
refetchSystemStats() if not already loading, or alternatively make
refetchSystemStats itself no-op when a fetch is in-flight; locate the usage
around systemStatsStore.systemStats and refetchSystemStats and add the loading
check (or make refetchSystemStats dedupe internally) so we never start a second
fetch while the initial load is in flight and keep the existing cancelled and
null checks intact.

---

Nitpick comments:
In `@src/components/rightSidePanel/errors/useErrorReport.ts`:
- Around line 58-60: The code contains a duplicated cancellation guard in the
async flow inside useErrorReport (two consecutive `if (cancelled) return`
checks); remove the redundant second guard so only a single `if (cancelled)
return` remains (locate the duplicate inside the useErrorReport function and
delete the extra occurrence).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fb6dcaaa-0c5c-4313-b995-cd6847739771

📥 Commits

Reviewing files that changed from the base of the PR and between 6ed156a and 9f3cacf.

📒 Files selected for processing (33)
  • browser_tests/fixtures/selectors.ts
  • browser_tests/tests/dialog.spec.ts
  • browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts
  • src/components/dialog/content/error/FindIssueButton.vue
  • src/components/rightSidePanel/RightSidePanel.vue
  • src/components/rightSidePanel/errors/ErrorNodeCard.vue
  • src/components/rightSidePanel/errors/MissingNodeCard.test.ts
  • src/components/rightSidePanel/errors/TabErrors.test.ts
  • src/components/rightSidePanel/errors/TabErrors.vue
  • src/components/rightSidePanel/errors/swapNodeGroups.test.ts
  • src/components/rightSidePanel/errors/useErrorActions.ts
  • src/components/rightSidePanel/errors/useErrorGroups.test.ts
  • src/components/rightSidePanel/errors/useErrorGroups.ts
  • src/components/rightSidePanel/errors/useErrorReport.ts
  • src/composables/graph/useErrorClearingHooks.test.ts
  • src/composables/graph/useErrorClearingHooks.ts
  • src/composables/graph/useNodeErrorFlagSync.ts
  • src/platform/nodeReplacement/cnrIdUtil.test.ts
  • src/platform/nodeReplacement/cnrIdUtil.ts
  • src/platform/nodeReplacement/missingNodeScan.test.ts
  • src/platform/nodeReplacement/missingNodeScan.ts
  • src/platform/nodeReplacement/missingNodesErrorStore.ts
  • src/platform/nodeReplacement/useNodeReplacement.test.ts
  • src/platform/nodeReplacement/useNodeReplacement.ts
  • src/platform/workflow/core/services/workflowService.test.ts
  • src/platform/workflow/core/services/workflowService.ts
  • src/renderer/extensions/vueNodes/VideoPreview.vue
  • src/renderer/extensions/vueNodes/components/ImagePreview.vue
  • src/renderer/extensions/vueNodes/components/LGraphNode.vue
  • src/scripts/app.ts
  • src/stores/executionErrorStore.test.ts
  • src/stores/executionErrorStore.ts
  • src/stores/executionStore.test.ts
✅ Files skipped from review due to trivial changes (6)
  • src/platform/nodeReplacement/cnrIdUtil.test.ts
  • src/renderer/extensions/vueNodes/components/ImagePreview.vue
  • src/renderer/extensions/vueNodes/VideoPreview.vue
  • src/composables/graph/useErrorClearingHooks.test.ts
  • src/composables/graph/useNodeErrorFlagSync.ts
  • browser_tests/fixtures/selectors.ts
🚧 Files skipped from review as they are similar to previous changes (18)
  • src/components/dialog/content/error/FindIssueButton.vue
  • src/components/rightSidePanel/errors/swapNodeGroups.test.ts
  • browser_tests/tests/dialog.spec.ts
  • src/components/rightSidePanel/errors/TabErrors.test.ts
  • src/platform/nodeReplacement/useNodeReplacement.test.ts
  • src/components/rightSidePanel/errors/MissingNodeCard.test.ts
  • src/renderer/extensions/vueNodes/components/LGraphNode.vue
  • src/composables/graph/useErrorClearingHooks.ts
  • src/platform/workflow/core/services/workflowService.test.ts
  • src/components/rightSidePanel/errors/useErrorGroups.ts
  • browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts
  • src/stores/executionErrorStore.test.ts
  • src/platform/nodeReplacement/missingNodeScan.ts
  • src/components/rightSidePanel/errors/TabErrors.vue
  • src/components/rightSidePanel/RightSidePanel.vue
  • src/components/rightSidePanel/errors/ErrorNodeCard.vue
  • src/platform/workflow/core/services/workflowService.ts
  • src/components/rightSidePanel/errors/useErrorGroups.test.ts

- Fix focusedErrorNodeId watch collapsing non-execution groups (#10085)
- Fix missingNodeCount always returning 0/1 instead of actual count
- Add ShowErrorsTab setting to useNodeErrorFlagSync watch sources
- Guard rootGraph.serialize() with try/catch, use const declarations
- Deduplicate cancelled guard in useErrorReport
- Await initial systemStats loading before refetching
- Fix mock return type null → undefined in missingNodeScan test
- Update stale doc comments referencing executionErrorStore
- Strengthen test assertions: onWidgetChanged restoration, runtime
  error uniqueness, missing-node title verification, fallback logs
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/components/rightSidePanel/errors/useErrorReport.ts (1)

58-60: Log-fetch failures are still swallowed without diagnostics.

This catch returns a fallback string but drops the underlying error context, which makes field debugging harder when /logs intermittently fails.

♻️ Suggested tweak
-    const logs = await api
-      .getLogs()
-      .catch(() => 'Failed to retrieve server logs')
+    const logs = await api.getLogs().catch((e) => {
+      console.warn('Failed to retrieve server logs for error report:', e)
+      return 'Failed to retrieve server logs'
+    })

As per coding guidelines: "Implement proper error handling".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/rightSidePanel/errors/useErrorReport.ts` around lines 58 - 60,
The current useErrorReport code swallows the original error by using
api.getLogs().catch(() => 'Failed to retrieve server logs'), losing diagnostics;
update the logs retrieval around api.getLogs() (in useErrorReport) to catch the
error, log or attach the original error details (e.g., include the caught error
in processLogger/error reporting or return an object like { message: 'Failed to
retrieve server logs', error }) and rethrow or propagate an enriched error so
callers and telemetry can see the underlying exception from api.getLogs().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/components/rightSidePanel/errors/useErrorReport.ts`:
- Around line 58-60: The current useErrorReport code swallows the original error
by using api.getLogs().catch(() => 'Failed to retrieve server logs'), losing
diagnostics; update the logs retrieval around api.getLogs() (in useErrorReport)
to catch the error, log or attach the original error details (e.g., include the
caught error in processLogger/error reporting or return an object like {
message: 'Failed to retrieve server logs', error }) and rethrow or propagate an
enriched error so callers and telemetry can see the underlying exception from
api.getLogs().

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 989940a0-a53b-4b53-80e6-0c9ccca3e428

📥 Commits

Reviewing files that changed from the base of the PR and between 9f3cacf and 2edd60c.

📒 Files selected for processing (10)
  • src/components/rightSidePanel/errors/ErrorNodeCard.test.ts
  • src/components/rightSidePanel/errors/TabErrors.test.ts
  • src/components/rightSidePanel/errors/TabErrors.vue
  • src/components/rightSidePanel/errors/useErrorGroups.test.ts
  • src/components/rightSidePanel/errors/useErrorReport.ts
  • src/composables/graph/useErrorClearingHooks.test.ts
  • src/composables/graph/useNodeErrorFlagSync.ts
  • src/platform/nodeReplacement/missingNodeScan.test.ts
  • src/platform/nodeReplacement/missingNodesErrorStore.ts
  • src/platform/nodeReplacement/useNodeReplacement.ts
✅ Files skipped from review due to trivial changes (2)
  • src/composables/graph/useErrorClearingHooks.test.ts
  • src/platform/nodeReplacement/missingNodeScan.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/components/rightSidePanel/errors/TabErrors.test.ts
  • src/platform/nodeReplacement/useNodeReplacement.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant