Skip to content

refactor(node-replacement): reorganize domain components and expand comprehensive test suite#9301

Merged
christian-byrne merged 9 commits intomainfrom
chore/node-replacement-tests-and-refactor
Feb 28, 2026
Merged

refactor(node-replacement): reorganize domain components and expand comprehensive test suite#9301
christian-byrne merged 9 commits intomainfrom
chore/node-replacement-tests-and-refactor

Conversation

@jaeone94
Copy link
Collaborator

@jaeone94 jaeone94 commented Feb 28, 2026

Summary

Resolves six open issues by reorganizing node replacement components into a domain-driven folder structure, refactoring event handling to follow the emit pattern, and adding comprehensive test coverage across all affected modules.

Changes

Additional Changes (Post-Review)

  • Edge case guard in placeholder detection (useNodeReplacement.ts): When last_serialization.type is absent (old serialization format), the predicate falls back to n.type, which the app may have already run through sanitizeNodeName — stripping HTML special characters (& < > " ' \ =). In that case, a Set.has()` lookup against the original unsanitized type name would silently miss, causing replacement to be skipped.

    Fixed by including sanitized variants of each target type in the targetTypes Set at construction time. For the overwhelmingly common case (no special characters in type names), the Set deduplicates the entries and runtime behavior is identical to before.

    A regression test was added to cover the specific scenario: last_serialization.type absent + live n.type already sanitized.

Review Focus

…omprehensive test suite

Resolves multiple issues related to node replacement and error handling logic (#9271, #9270, #9267, #9255, #9254, #9231).
Key changes:
- Relocated node replacement UI components and scanning logic to a domain-driven structure under src/platform/nodeReplacement/ for better maintainability.
- Refactored SwapNodeGroupRow.vue to follow the emit pattern (
eplace event), centralizing replacement and cleanup logic within the parent TabErrors.vue.
- Significantly expanded the test suite with the following additions:
  - **Component Tests**: Validated rendering, user interactions, and event contracts for MissingNodeCard, MissingPackGroupRow, SwapNodeGroupRow, and SwapNodesCard.
  - **Logic & Composable Unit Tests**: Verified graph scanning logic in missingNodeScan, placeholder detection criteria in useNodeReplacement, and computed grouping logic in useErrorGroups.
  - **Store Tests**: Added unit tests for executionErrorStore to ensure correct filtering and removal of missing nodes by type.
- Updated import paths in �pp.ts and other relevant files to reflect the new directory structure.
@jaeone94 jaeone94 requested a review from a team as a code owner February 28, 2026 12:27
@dosubot dosubot bot added the size:XXL This PR changes 1000+ lines, ignoring generated files. label Feb 28, 2026
@github-actions
Copy link

github-actions bot commented Feb 28, 2026

🎨 Storybook: ✅ Built — View Storybook

Details

⏰ Completed at: 02/28/2026, 02:01:34 PM UTC

Links

@github-actions
Copy link

github-actions bot commented Feb 28, 2026

🎭 Playwright: ✅ 543 passed, 0 failed · 7 flaky

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 28, 2026

Important

Review skipped

This PR was authored by the user configured for CodeRabbit reviews. By default, CodeRabbit skips reviewing PRs authored by this user. It's recommended to use a dedicated user account to post CodeRabbit review feedback.

To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds extensive tests for missing-node and node-replacement features, refactors swap components to emit a replace event, moves missing-node scan import to the platform path, and exposes replaceGroup/replaceAllGroups in the node-replacement composition.

Changes

Cohort / File(s) Summary
Component Tests
src/components/rightSidePanel/errors/MissingNodeCard.test.ts, src/components/rightSidePanel/errors/MissingPackGroupRow.test.ts
Adds comprehensive component tests for rendering, props, manager integration, Apply Changes behavior, and event emissions (locateNode, openManagerInfo).
Grouping & Logic Tests
src/components/rightSidePanel/errors/useErrorGroups.test.ts, src/components/rightSidePanel/errors/swapNodeGroups.test.ts, src/stores/executionErrorStore.test.ts
Adds unit tests for grouping/filtering logic, swap-group generation, grouped messages, and missing-node deduplication/removal.
Swap Node Component Tests
src/platform/nodeReplacement/components/SwapNodeGroupRow.test.ts, src/platform/nodeReplacement/components/SwapNodesCard.test.ts
Adds tests for swap-row/card rendering, expand/collapse, node lists, locate/replace interactions, and event propagation.
Node Scan & Replacement Tests
src/platform/nodeReplacement/missingNodeScan.test.ts, src/platform/nodeReplacement/useNodeReplacement.test.ts
Adds tests for scanning unregistered node types, placeholder detection predicates, replacement helpers, and replace-all flows (includes some duplicated predicate checks).
Component Refactor
src/platform/nodeReplacement/components/SwapNodeGroupRow.vue, src/platform/nodeReplacement/components/SwapNodesCard.vue, src/components/rightSidePanel/errors/TabErrors.vue
SwapNodeGroupRow now emits a replace event (removes direct store calls); SwapNodesCard propagates replace; TabErrors adds handleReplaceGroup and delegates to useNodeReplacement helpers.
Composition API
src/platform/nodeReplacement/useNodeReplacement.ts
Adds ReplacementGroup type and new public methods replaceGroup and replaceAllGroups that call replaceNodesInPlace and update executionErrorStore; extends target-type matching (sanitized variants).
Import Path Update
src/scripts/app.ts
Updates import for rescanAndSurfaceMissingNodes to @/platform/nodeReplacement/missingNodeScan.

Sequence Diagram(s)

sequenceDiagram
  participant UI as User / Component (SwapRow)
  participant SwapCard as SwapNodesCard
  participant Tab as TabErrors
  participant Replacement as useNodeReplacement
  participant ExecStore as executionErrorStore

  UI->>SwapCard: emit replace(group)
  SwapCard->>Tab: emit replace(group)
  Tab->>Replacement: call replaceGroup(group)
  Replacement->>Replacement: replaceNodesInPlace(group)
  Replacement->>ExecStore: removeMissingNodesByType(group.type)
  ExecStore-->>Tab: missing nodes state updated
  Tab-->>SwapCard: UI updates (rows removed/updated)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • viva-jinyi
  • ltdrdata
  • christian-byrne

Poem

🐇
I hop through tests with tiny paws,
Emitting replaces without a pause.
Spinners twirl, badges gleam with pride,
Groups align and errors hide.
A rabbit cheers — the code’s delight!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.39% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Title clearly describes the main changes: refactoring domain components into proper structure and expanding test coverage across the node-replacement feature.
Description check ✅ Passed Description follows template with Summary, Changes, and Review Focus sections; includes all major refactoring points, files moved, tests added, and links to six resolved issues.
Linked Issues check ✅ Passed All six linked issues (#9231, #9254, #9255, #9267, #9270, #9271) are addressed: component tests added, functions moved, components relocated, event handling refactored, unit tests implemented, and placeholder detection tests added.
Out of Scope Changes check ✅ Passed All changes align with linked issues: file relocations, component refactoring, and test additions directly correspond to the documented objectives; no extraneous modifications detected.

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


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

@github-actions
Copy link

github-actions bot commented Feb 28, 2026

📦 Bundle: 4.46 MB gzip 🟢 -129 B

Details

Summary

  • Raw size: 20.9 MB baseline 20.9 MB — 🔴 +334 B
  • Gzip: 4.46 MB baseline 4.46 MB — 🟢 -129 B
  • Brotli: 3.45 MB baseline 3.45 MB — 🔴 +173 B
  • Bundles: 228 current • 228 baseline • 108 added / 108 removed

Category Glance
Data & Services 🔴 +511 B (2.56 MB) · Graph Workspace 🟢 -177 B (1.03 MB) · Vendor & Third-Party ⚪ 0 B (8.84 MB) · Other ⚪ 0 B (7.87 MB) · Panels & Settings ⚪ 0 B (435 kB) · Views & Navigation ⚪ 0 B (72.1 kB) · + 5 more

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

Main entry bundles and manifests

File Before After Δ Raw Δ Gzip Δ Brotli
assets/index-DedTBNFF.js (new) 17.9 kB 🔴 +17.9 kB 🔴 +6.34 kB 🔴 +5.49 kB
assets/index-DeTn2_3a.js (removed) 17.9 kB 🟢 -17.9 kB 🟢 -6.34 kB 🟢 -5.51 kB

Status: 1 added / 1 removed

Graph Workspace — 1.03 MB (baseline 1.03 MB) • 🟢 -177 B

Graph editor runtime, canvas, workflow orchestration

File Before After Δ Raw Δ Gzip Δ Brotli
assets/GraphView-B3iKVpSP.js (removed) 1.03 MB 🟢 -1.03 MB 🟢 -219 kB 🟢 -165 kB
assets/GraphView-B1-SsoHJ.js (new) 1.03 MB 🔴 +1.03 MB 🔴 +219 kB 🔴 +165 kB

Status: 1 added / 1 removed

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

Top-level views, pages, and routed surfaces

File Before After Δ Raw Δ Gzip Δ Brotli
assets/CloudSurveyView-BdHR47c9.js (new) 15.5 kB 🔴 +15.5 kB 🔴 +3.32 kB 🔴 +2.82 kB
assets/CloudSurveyView-C3Y0BQGY.js (removed) 15.5 kB 🟢 -15.5 kB 🟢 -3.32 kB 🟢 -2.83 kB
assets/CloudLoginView-7FEaswim.js (new) 11.4 kB 🔴 +11.4 kB 🔴 +3.19 kB 🔴 +2.83 kB
assets/CloudLoginView-DrOvkfLg.js (removed) 11.4 kB 🟢 -11.4 kB 🟢 -3.2 kB 🟢 -2.82 kB
assets/CloudSignupView-aRJBs9Hx.js (removed) 9.37 kB 🟢 -9.37 kB 🟢 -2.7 kB 🟢 -2.37 kB
assets/CloudSignupView-D9MziKC_.js (new) 9.37 kB 🔴 +9.37 kB 🔴 +2.7 kB 🔴 +2.37 kB
assets/UserCheckView-D7zrEHJ_.js (new) 8.41 kB 🔴 +8.41 kB 🔴 +2.23 kB 🔴 +1.94 kB
assets/UserCheckView-TQEP5NNs.js (removed) 8.41 kB 🟢 -8.41 kB 🟢 -2.23 kB 🟢 -1.94 kB
assets/CloudLayoutView-DV_hry5n.js (removed) 6.43 kB 🟢 -6.43 kB 🟢 -2.1 kB 🟢 -1.82 kB
assets/CloudLayoutView-iZJkldqG.js (new) 6.43 kB 🔴 +6.43 kB 🔴 +2.1 kB 🔴 +1.83 kB
assets/CloudForgotPasswordView-DReuDCxM.js (removed) 5.56 kB 🟢 -5.56 kB 🟢 -1.94 kB 🟢 -1.7 kB
assets/CloudForgotPasswordView-DRH1IYSf.js (new) 5.56 kB 🔴 +5.56 kB 🔴 +1.94 kB 🔴 +1.71 kB
assets/CloudAuthTimeoutView-D57G97MP.js (new) 4.91 kB 🔴 +4.91 kB 🔴 +1.77 kB 🔴 +1.54 kB
assets/CloudAuthTimeoutView-UqdfNoZn.js (removed) 4.91 kB 🟢 -4.91 kB 🟢 -1.77 kB 🟢 -1.54 kB
assets/CloudSubscriptionRedirectView-B5VzAc-r.js (removed) 4.75 kB 🟢 -4.75 kB 🟢 -1.79 kB 🟢 -1.58 kB
assets/CloudSubscriptionRedirectView-DRsJBBaB.js (new) 4.75 kB 🔴 +4.75 kB 🔴 +1.79 kB 🔴 +1.58 kB
assets/UserSelectView-B9Qf04s_.js (new) 4.5 kB 🔴 +4.5 kB 🔴 +1.64 kB 🔴 +1.47 kB
assets/UserSelectView-Ck_wEI5P.js (removed) 4.5 kB 🟢 -4.5 kB 🟢 -1.64 kB 🟢 -1.47 kB
assets/CloudSorryContactSupportView-Bypca0av.js 1.02 kB 1.02 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/layout-CI4MZk1L.js 296 B 296 B ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 9 added / 9 removed

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

Configuration panels, inspectors, and settings screens

File Before After Δ Raw Δ Gzip Δ Brotli
assets/SecretsPanel-8guzCIHO.js (new) 21.5 kB 🔴 +21.5 kB 🔴 +5.31 kB 🔴 +4.66 kB
assets/SecretsPanel-DjiWupJk.js (removed) 21.5 kB 🟢 -21.5 kB 🟢 -5.31 kB 🟢 -4.66 kB
assets/LegacyCreditsPanel-Di2WuQMj.js (new) 20.6 kB 🔴 +20.6 kB 🔴 +5.56 kB 🔴 +4.9 kB
assets/LegacyCreditsPanel-DJXKVMSF.js (removed) 20.6 kB 🟢 -20.6 kB 🟢 -5.57 kB 🟢 -4.89 kB
assets/SubscriptionPanel-B3_okR4e.js (new) 18.2 kB 🔴 +18.2 kB 🔴 +4.66 kB 🔴 +4.1 kB
assets/SubscriptionPanel-C9Jhv-wK.js (removed) 18.2 kB 🟢 -18.2 kB 🟢 -4.66 kB 🟢 -4.1 kB
assets/KeybindingPanel-CIo5NXO3.js (removed) 12.3 kB 🟢 -12.3 kB 🟢 -3.52 kB 🟢 -3.12 kB
assets/KeybindingPanel-CPUv06ol.js (new) 12.3 kB 🔴 +12.3 kB 🔴 +3.52 kB 🔴 +3.12 kB
assets/AboutPanel-BKVR1flA.js (new) 9.79 kB 🔴 +9.79 kB 🔴 +2.73 kB 🔴 +2.45 kB
assets/AboutPanel-D43Y9RdL.js (removed) 9.79 kB 🟢 -9.79 kB 🟢 -2.73 kB 🟢 -2.45 kB
assets/ExtensionPanel-4T-yEyzS.js (new) 9.38 kB 🔴 +9.38 kB 🔴 +2.65 kB 🔴 +2.36 kB
assets/ExtensionPanel-TVr_uQGZ.js (removed) 9.38 kB 🟢 -9.38 kB 🟢 -2.65 kB 🟢 -2.36 kB
assets/ServerConfigPanel-BLLilCV3.js (new) 6.44 kB 🔴 +6.44 kB 🔴 +2.13 kB 🔴 +1.9 kB
assets/ServerConfigPanel-CZbBcv55.js (removed) 6.44 kB 🟢 -6.44 kB 🟢 -2.13 kB 🟢 -1.92 kB
assets/UserPanel-Bi98qW59.js (removed) 6.16 kB 🟢 -6.16 kB 🟢 -2 kB 🟢 -1.75 kB
assets/UserPanel-q49Wng9T.js (new) 6.16 kB 🔴 +6.16 kB 🔴 +2 kB 🔴 +1.75 kB
assets/cloudRemoteConfig-Dh4tnZu_.js (new) 1.44 kB 🔴 +1.44 kB 🔴 +704 B 🔴 +614 B
assets/cloudRemoteConfig-Zta7reG0.js (removed) 1.44 kB 🟢 -1.44 kB 🟢 -709 B 🟢 -613 B
assets/refreshRemoteConfig-fSKTwPSh.js (removed) 1.14 kB 🟢 -1.14 kB 🟢 -522 B 🟢 -449 B
assets/refreshRemoteConfig-N9c1-8zs.js (new) 1.14 kB 🔴 +1.14 kB 🔴 +518 B 🔴 +460 B
assets/config-CGn5JFmU.js 996 B 996 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-B5oF6TeI.js 29.9 kB 29.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-BVYOg4dh.js 24.5 kB 24.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-CBEvSL1z.js 38.5 kB 38.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-CGx1t8IZ.js 27.8 kB 27.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-CNcb_4nC.js 30.5 kB 30.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-Cx1dZM6H.js 23.9 kB 23.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-Dw-QS6Nb.js 27.9 kB 27.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-DXxgnCSn.js 32.4 kB 32.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-GRFn4guL.js 34.2 kB 34.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-mgwKIVQ2.js 28.8 kB 28.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-s83B801I.js 28.7 kB 28.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 10 added / 10 removed

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

Authentication, profile, and account management bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/auth-BlyhrUG9.js (removed) 3.4 kB 🟢 -3.4 kB 🟢 -1.18 kB 🟢 -989 B
assets/auth-DZe7TCdO.js (new) 3.4 kB 🔴 +3.4 kB 🔴 +1.18 kB 🔴 +987 B
assets/SignUpForm-dd3QrVmJ.js (new) 3.01 kB 🔴 +3.01 kB 🔴 +1.23 kB 🔴 +1.09 kB
assets/SignUpForm-DhGuis2v.js (removed) 3.01 kB 🟢 -3.01 kB 🟢 -1.23 kB 🟢 -1.09 kB
assets/UpdatePasswordContent-d18Md5aH.js (new) 2.37 kB 🔴 +2.37 kB 🔴 +1.07 kB 🔴 +943 B
assets/UpdatePasswordContent-DV0l07Bh.js (removed) 2.37 kB 🟢 -2.37 kB 🟢 -1.07 kB 🟢 -944 B
assets/firebaseAuthStore-CCPWI1po.js (removed) 788 B 🟢 -788 B 🟢 -389 B 🟢 -344 B
assets/firebaseAuthStore-DQRUjxk3.js (new) 788 B 🔴 +788 B 🔴 +387 B 🔴 +350 B
assets/auth-DzmVH_z_.js (removed) 357 B 🟢 -357 B 🟢 -226 B 🟢 -208 B
assets/auth-E2jMsNl1.js (new) 357 B 🔴 +357 B 🔴 +226 B 🔴 +192 B
assets/PasswordFields-DLbVLg8O.js 4.51 kB 4.51 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WorkspaceProfilePic-D6ioir1T.js 1.57 kB 1.57 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 5 added / 5 removed

Editors & Dialogs — 736 B (baseline 736 B) • ⚪ 0 B

Modals, dialogs, drawers, and in-app editors

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useSubscriptionDialog-CjFAcok-.js (removed) 736 B 🟢 -736 B 🟢 -380 B 🟢 -325 B
assets/useSubscriptionDialog-DTEs_-qj.js (new) 736 B 🔴 +736 B 🔴 +378 B 🔴 +354 B

Status: 1 added / 1 removed

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

Reusable component library chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useTerminalTabs-D2ckRPVY.js (new) 9.84 kB 🔴 +9.84 kB 🔴 +3.4 kB 🔴 +3 kB
assets/useTerminalTabs-Do-qPpat.js (removed) 9.84 kB 🟢 -9.84 kB 🟢 -3.4 kB 🟢 -3.01 kB
assets/ComfyQueueButton-COXb3kH3.js (removed) 8.02 kB 🟢 -8.02 kB 🟢 -2.49 kB 🟢 -2.23 kB
assets/ComfyQueueButton-CrrObGDE.js (new) 8.02 kB 🔴 +8.02 kB 🔴 +2.48 kB 🔴 +2.23 kB
assets/SubscribeButton-5vNVJDq6.js (removed) 2.48 kB 🟢 -2.48 kB 🟢 -1.07 kB 🟢 -950 B
assets/SubscribeButton-Bacf8iOJ.js (new) 2.48 kB 🔴 +2.48 kB 🔴 +1.07 kB 🔴 +943 B
assets/cloudFeedbackTopbarButton-B0FmFpGl.js (removed) 1.59 kB 🟢 -1.59 kB 🟢 -852 B 🟢 -765 B
assets/cloudFeedbackTopbarButton-BfmbZHMB.js (new) 1.59 kB 🔴 +1.59 kB 🔴 +851 B 🔴 +755 B
assets/ComfyQueueButton-CLVc-pA-.js (new) 793 B 🔴 +793 B 🔴 +392 B 🔴 +349 B
assets/ComfyQueueButton-CX9OUG4s.js (removed) 793 B 🟢 -793 B 🟢 -396 B 🟢 -350 B
assets/Button-D1z3poyI.js 2.98 kB 2.98 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/CloudBadge-D1z3EKPy.js 1.16 kB 1.16 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/FormSearchInput-Bg4LklDe.js 3.73 kB 3.73 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/ScrubableNumberInput-DecBFGbM.js 5.94 kB 5.94 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/TopbarBadge-CJNpTEnW.js 7.44 kB 7.44 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/UserAvatar-CnQQLXB-.js 1.17 kB 1.17 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetButton-sg8Jj4MY.js 1.84 kB 1.84 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 5 added / 5 removed

Data & Services — 2.56 MB (baseline 2.55 MB) • 🔴 +511 B

Stores, services, APIs, and repositories

File Before After Δ Raw Δ Gzip Δ Brotli
assets/dialogService-CjvaKuMH.js (new) 1.75 MB 🔴 +1.75 MB 🔴 +393 kB 🔴 +296 kB
assets/dialogService-DQCT92LW.js (removed) 1.75 MB 🟢 -1.75 MB 🟢 -393 kB 🟢 -296 kB
assets/api-D0GuEGgs.js (new) 679 kB 🔴 +679 kB 🔴 +154 kB 🔴 +122 kB
assets/api-D2b6DH7I.js (removed) 679 kB 🟢 -679 kB 🟢 -154 kB 🟢 -122 kB
assets/load3dService-BPaCme5m.js (new) 91 kB 🔴 +91 kB 🔴 +19.1 kB 🔴 +16.4 kB
assets/load3dService-Dnzuu9Rj.js (removed) 91 kB 🟢 -91 kB 🟢 -19.1 kB 🟢 -16.4 kB
assets/extensionStore-PMNgdMuM.js (new) 12.1 kB 🔴 +12.1 kB 🔴 +4.21 kB 🔴 +3.69 kB
assets/extensionStore-wQ9RgjqQ.js (removed) 12.1 kB 🟢 -12.1 kB 🟢 -4.21 kB 🟢 -3.7 kB
assets/releaseStore-BBsMR3lK.js (new) 7.96 kB 🔴 +7.96 kB 🔴 +2.22 kB 🔴 +1.95 kB
assets/releaseStore-xJ-WHGSv.js (removed) 7.96 kB 🟢 -7.96 kB 🟢 -2.22 kB 🟢 -1.95 kB
assets/keybindingService-DmE0w0LE.js (new) 6.52 kB 🔴 +6.52 kB 🔴 +1.71 kB 🔴 +1.48 kB
assets/keybindingService-GWMJMFta.js (removed) 6.52 kB 🟢 -6.52 kB 🟢 -1.71 kB 🟢 -1.48 kB
assets/bootstrapStore-BeMggyn_.js (new) 2.08 kB 🔴 +2.08 kB 🔴 +875 B 🔴 +788 B
assets/bootstrapStore-BuvFmRnA.js (removed) 2.08 kB 🟢 -2.08 kB 🟢 -875 B 🟢 -786 B
assets/userStore-BD37JXO7.js (removed) 1.85 kB 🟢 -1.85 kB 🟢 -723 B 🟢 -637 B
assets/userStore-C3RXYEt6.js (new) 1.85 kB 🔴 +1.85 kB 🔴 +721 B 🔴 +635 B
assets/audioService-B8nF9m_D.js (removed) 1.73 kB 🟢 -1.73 kB 🟢 -851 B 🟢 -726 B
assets/audioService-CF0qGZ93.js (new) 1.73 kB 🔴 +1.73 kB 🔴 +851 B 🔴 +726 B
assets/releaseStore-BnGGHJPL.js (removed) 760 B 🟢 -760 B 🟢 -382 B 🟢 -337 B
assets/releaseStore-DyffhBiy.js (new) 760 B 🔴 +760 B 🔴 +384 B 🔴 +340 B
assets/settingStore-BB3OUcSA.js (removed) 744 B 🟢 -744 B 🟢 -387 B 🟢 -339 B
assets/settingStore-Ce9etps5.js (new) 744 B 🔴 +744 B 🔴 +385 B 🔴 +369 B
assets/workflowDraftStore-otflIPFT.js (removed) 736 B 🟢 -736 B 🟢 -379 B 🟢 -331 B
assets/workflowDraftStore-TosYNIOw.js (new) 736 B 🔴 +736 B 🔴 +378 B 🔴 +334 B
assets/dialogService-CzuawA9I.js (new) 725 B 🔴 +725 B 🔴 +366 B 🔴 +350 B
assets/dialogService-DjmbjQEA.js (removed) 725 B 🟢 -725 B 🟢 -368 B 🟢 -339 B
assets/serverConfigStore-EPk4OtIK.js 2.32 kB 2.32 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 13 added / 13 removed

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

Helpers, composables, and utility bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useLoad3d-BsHhF5yS.js (new) 14.6 kB 🔴 +14.6 kB 🔴 +3.63 kB 🔴 +3.21 kB
assets/useLoad3d-DsGCx9cs.js (removed) 14.6 kB 🟢 -14.6 kB 🟢 -3.63 kB 🟢 -3.21 kB
assets/useLoad3dViewer-CTujyCY-.js (removed) 14.1 kB 🟢 -14.1 kB 🟢 -3.15 kB 🟢 -2.8 kB
assets/useLoad3dViewer-UHLMkZCW.js (new) 14.1 kB 🔴 +14.1 kB 🔴 +3.15 kB 🔴 +2.8 kB
assets/useFeatureFlags-BmkXQ8ys.js (removed) 4.14 kB 🟢 -4.14 kB 🟢 -1.24 kB 🟢 -1.06 kB
assets/useFeatureFlags-h8roBDJ8.js (new) 4.14 kB 🔴 +4.14 kB 🔴 +1.24 kB 🔴 +1.05 kB
assets/useWorkspaceUI-moekS7A3.js (removed) 3 kB 🟢 -3 kB 🟢 -822 B 🟢 -704 B
assets/useWorkspaceUI-PrwuwNuI.js (new) 3 kB 🔴 +3 kB 🔴 +820 B 🔴 +700 B
assets/subscriptionCheckoutUtil-CHTqDZ6q.js (removed) 2.53 kB 🟢 -2.53 kB 🟢 -1.06 kB 🟢 -957 B
assets/subscriptionCheckoutUtil-DYDpg1Q7.js (new) 2.53 kB 🔴 +2.53 kB 🔴 +1.06 kB 🔴 +922 B
assets/useErrorHandling-3OVCUWPA.js (new) 1.5 kB 🔴 +1.5 kB 🔴 +631 B 🔴 +534 B
assets/useErrorHandling-Cjd7AASa.js (removed) 1.5 kB 🟢 -1.5 kB 🟢 -631 B 🟢 -534 B
assets/useWorkspaceSwitch-Bh2uCQ0l.js (removed) 1.25 kB 🟢 -1.25 kB 🟢 -544 B 🟢 -484 B
assets/useWorkspaceSwitch-HY8kwM1R.js (new) 1.25 kB 🔴 +1.25 kB 🔴 +545 B 🔴 +485 B
assets/useLoad3d-AlPMdl2C.js (removed) 859 B 🟢 -859 B 🟢 -424 B 🟢 -377 B
assets/useLoad3d-CmbVaxdu.js (new) 859 B 🔴 +859 B 🔴 +422 B 🔴 +383 B
assets/audioUtils-3UqMoV0-.js (new) 858 B 🔴 +858 B 🔴 +499 B 🔴 +420 B
assets/audioUtils-DeEy5Lqt.js (removed) 858 B 🟢 -858 B 🟢 -502 B 🟢 -403 B
assets/useLoad3dViewer-CvM7QU0P.js (removed) 838 B 🟢 -838 B 🟢 -409 B 🟢 -364 B
assets/useLoad3dViewer-id4wXh2Q.js (new) 838 B 🔴 +838 B 🔴 +407 B 🔴 +371 B
assets/useCurrentUser-6Tpvb8OK.js (new) 722 B 🔴 +722 B 🔴 +369 B 🔴 +325 B
assets/useCurrentUser-Bbr__LF1.js (removed) 722 B 🟢 -722 B 🟢 -371 B 🟢 -341 B
assets/_plugin-vue_export-helper-ralzwvFM.js 315 B 315 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/colorUtil-CY7QMUhQ.js 7 kB 7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/envUtil-Clzmwvt4.js 466 B 466 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/markdownRendererUtil-Cddas8Zl.js 1.56 kB 1.56 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SkeletonUtils-BputJAFn.js 133 B 133 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/useExternalLink-DgEMZmBC.js 1.66 kB 1.66 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 11 added / 11 removed

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

External libraries and shared vendor chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/vendor-axios-Cp6hch1I.js 70.7 kB 70.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-chart-BxkFiWzp.js 399 kB 399 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-firebase-BvMr43CG.js 836 kB 836 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-i18n-DNX73mqE.js 133 kB 133 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-markdown-D5S6AC80.js 103 kB 103 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-other-DrYd4O-6.js 1.52 MB 1.52 MB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-primevue-BnCPTL0g.js 1.73 MB 1.73 MB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-reka-ui-DVmi2O2Z.js 388 kB 388 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-sentry-SQwstEKc.js 182 kB 182 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-three-LBLOE6BD.js 1.8 MB 1.8 MB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-tiptap-BnYkbQDM.js 634 kB 634 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-vue-core-DtiQ1dr9.js 311 kB 311 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-vueuse-D2jVNnmE.js 113 kB 113 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-xterm-MKpa1ZAW.js 374 kB 374 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-yjs-CP_4YO8u.js 143 kB 143 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-zod-DcCUUPIi.js 109 kB 109 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
Other — 7.87 MB (baseline 7.87 MB) • ⚪ 0 B

Bundles that do not match a named category

File Before After Δ Raw Δ Gzip Δ Brotli
assets/core-Coo6VYuy.js (removed) 73.4 kB 🟢 -73.4 kB 🟢 -18.9 kB 🟢 -16.2 kB
assets/core-DhAI9-Wx.js (new) 73.4 kB 🔴 +73.4 kB 🔴 +18.9 kB 🔴 +16.2 kB
assets/groupNode-D-EjzbCn.js (removed) 71.8 kB 🟢 -71.8 kB 🟢 -17.7 kB 🟢 -15.5 kB
assets/groupNode-DpF0J47X.js (new) 71.8 kB 🔴 +71.8 kB 🔴 +17.7 kB 🔴 +15.6 kB
assets/WidgetSelect-DxKb7Fbh.js (removed) 58.1 kB 🟢 -58.1 kB 🟢 -12.4 kB 🟢 -10.7 kB
assets/WidgetSelect-esrTNzP0.js (new) 58.1 kB 🔴 +58.1 kB 🔴 +12.4 kB 🔴 +10.7 kB
assets/SubscriptionRequiredDialogContentWorkspace-BgazeEOb.js (new) 46.3 kB 🔴 +46.3 kB 🔴 +8.66 kB 🔴 +7.51 kB
assets/SubscriptionRequiredDialogContentWorkspace-D1beIdaQ.js (removed) 46.3 kB 🟢 -46.3 kB 🟢 -8.66 kB 🟢 -7.51 kB
assets/WidgetPainter-86l45hm5.js (removed) 32.5 kB 🟢 -32.5 kB 🟢 -7.96 kB 🟢 -7.06 kB
assets/WidgetPainter-DfvPzfRw.js (new) 32.5 kB 🔴 +32.5 kB 🔴 +7.96 kB 🔴 +7.06 kB
assets/Load3DControls-BOSSniA4.js (removed) 30.9 kB 🟢 -30.9 kB 🟢 -5.34 kB 🟢 -4.65 kB
assets/Load3DControls-CmJ0lIpD.js (new) 30.9 kB 🔴 +30.9 kB 🔴 +5.34 kB 🔴 +4.65 kB
assets/WorkspacePanelContent-C-EuXVgS.js (removed) 29.3 kB 🟢 -29.3 kB 🟢 -6.15 kB 🟢 -5.39 kB
assets/WorkspacePanelContent-CbY9Ox0N.js (new) 29.3 kB 🔴 +29.3 kB 🔴 +6.15 kB 🔴 +5.42 kB
assets/SubscriptionRequiredDialogContent-BdjVCT80.js (new) 25.7 kB 🔴 +25.7 kB 🔴 +6.57 kB 🔴 +5.79 kB
assets/SubscriptionRequiredDialogContent-CHVi9tCM.js (removed) 25.7 kB 🟢 -25.7 kB 🟢 -6.58 kB 🟢 -5.78 kB
assets/Load3dViewerContent-B-epBmX1.js (new) 23 kB 🔴 +23 kB 🔴 +5.18 kB 🔴 +4.5 kB
assets/Load3dViewerContent-CkkjCsBs.js (removed) 23 kB 🟢 -23 kB 🟢 -5.18 kB 🟢 -4.5 kB
assets/WidgetImageCrop-Bh9MD6Io.js (new) 22.1 kB 🔴 +22.1 kB 🔴 +5.5 kB 🔴 +4.85 kB
assets/WidgetImageCrop-KAngrLIt.js (removed) 22.1 kB 🟢 -22.1 kB 🟢 -5.51 kB 🟢 -4.85 kB
assets/SubscriptionPanelContentWorkspace-CZ1Izzxq.js (removed) 21.6 kB 🟢 -21.6 kB 🟢 -5.06 kB 🟢 -4.46 kB
assets/SubscriptionPanelContentWorkspace-DEu6LRQh.js (new) 21.6 kB 🔴 +21.6 kB 🔴 +5.05 kB 🔴 +4.46 kB
assets/CurrentUserPopoverWorkspace-C1hF-80X.js (removed) 19.9 kB 🟢 -19.9 kB 🟢 -4.88 kB 🟢 -4.34 kB
assets/CurrentUserPopoverWorkspace-DtU39H_t.js (new) 19.9 kB 🔴 +19.9 kB 🔴 +4.88 kB 🔴 +4.34 kB
assets/SignInContent-DTTl1bM0.js (removed) 18.9 kB 🟢 -18.9 kB 🟢 -4.76 kB 🟢 -4.16 kB
assets/SignInContent-VCmoCleb.js (new) 18.9 kB 🔴 +18.9 kB 🔴 +4.76 kB 🔴 +4.16 kB
assets/WidgetInputNumber-C2FZ8Hp3.js (removed) 18.7 kB 🟢 -18.7 kB 🟢 -4.75 kB 🟢 -4.22 kB
assets/WidgetInputNumber-ff8dIzov.js (new) 18.7 kB 🔴 +18.7 kB 🔴 +4.75 kB 🔴 +4.22 kB
assets/WidgetRecordAudio-DMe8FoZC.js (removed) 17.3 kB 🟢 -17.3 kB 🟢 -4.95 kB 🟢 -4.42 kB
assets/WidgetRecordAudio-Uls-OkeS.js (new) 17.3 kB 🔴 +17.3 kB 🔴 +4.94 kB 🔴 +4.43 kB
assets/Load3D-Ccz891rR.js (removed) 16.2 kB 🟢 -16.2 kB 🟢 -4.03 kB 🟢 -3.51 kB
assets/Load3D-odHcQaYu.js (new) 16.2 kB 🔴 +16.2 kB 🔴 +4.03 kB 🔴 +3.51 kB
assets/load3d-DCU2Kyn4.js (removed) 14.7 kB 🟢 -14.7 kB 🟢 -4.19 kB 🟢 -3.63 kB
assets/load3d-UMP_J7Cq.js (new) 14.7 kB 🔴 +14.7 kB 🔴 +4.19 kB 🔴 +3.64 kB
assets/AudioPreviewPlayer-BSHv5o2K.js (removed) 10.9 kB 🟢 -10.9 kB 🟢 -3.2 kB 🟢 -2.85 kB
assets/AudioPreviewPlayer-CF7q_pVf.js (new) 10.9 kB 🔴 +10.9 kB 🔴 +3.2 kB 🔴 +2.87 kB
assets/changeTracker-BZnczJ4E.js (removed) 9.38 kB 🟢 -9.38 kB 🟢 -2.9 kB 🟢 -2.55 kB
assets/changeTracker-Dzdmo0zQ.js (new) 9.38 kB 🔴 +9.38 kB 🔴 +2.89 kB 🔴 +2.55 kB
assets/nodeTemplates-BANIJ6bI.js (removed) 9.29 kB 🟢 -9.29 kB 🟢 -3.26 kB 🟢 -2.86 kB
assets/nodeTemplates-DQ3k-rcf.js (new) 9.29 kB 🔴 +9.29 kB 🔴 +3.26 kB 🔴 +2.86 kB
assets/InviteMemberDialogContent-CP8jgw9F.js (removed) 7.38 kB 🟢 -7.38 kB 🟢 -2.3 kB 🟢 -2 kB
assets/InviteMemberDialogContent-De2v8lbG.js (new) 7.38 kB 🔴 +7.38 kB 🔴 +2.29 kB 🔴 +2 kB
assets/Load3DConfiguration-BAzaiXG_.js (new) 6.27 kB 🔴 +6.27 kB 🔴 +1.91 kB 🔴 +1.68 kB
assets/Load3DConfiguration-Cik9PMSe.js (removed) 6.27 kB 🟢 -6.27 kB 🟢 -1.91 kB 🟢 -1.68 kB
assets/CreateWorkspaceDialogContent-BFqbLPoA.js (new) 5.53 kB 🔴 +5.53 kB 🔴 +1.99 kB 🔴 +1.75 kB
assets/CreateWorkspaceDialogContent-HKFAX5r8.js (removed) 5.53 kB 🟢 -5.53 kB 🟢 -2 kB 🟢 -1.74 kB
assets/onboardingCloudRoutes-CBwC1KuM.js (new) 5.41 kB 🔴 +5.41 kB 🔴 +1.84 kB 🔴 +1.6 kB
assets/onboardingCloudRoutes-DgZXEsPR.js (removed) 5.41 kB 🟢 -5.41 kB 🟢 -1.83 kB 🟢 -1.59 kB
assets/FreeTierDialogContent-BD-JC7u-.js (removed) 5.39 kB 🟢 -5.39 kB 🟢 -1.9 kB 🟢 -1.67 kB
assets/FreeTierDialogContent-DHmPiArE.js (new) 5.39 kB 🔴 +5.39 kB 🔴 +1.9 kB 🔴 +1.67 kB
assets/EditWorkspaceDialogContent-1SnE1DMZ.js (new) 5.33 kB 🔴 +5.33 kB 🔴 +1.95 kB 🔴 +1.7 kB
assets/EditWorkspaceDialogContent-BIy-z1WZ.js (removed) 5.33 kB 🟢 -5.33 kB 🟢 -1.95 kB 🟢 -1.7 kB
assets/ValueControlPopover-CKiNHBub.js (removed) 4.92 kB 🟢 -4.92 kB 🟢 -1.77 kB 🟢 -1.57 kB
assets/ValueControlPopover-CY2cyWZp.js (new) 4.92 kB 🔴 +4.92 kB 🔴 +1.76 kB 🔴 +1.58 kB
assets/Preview3d-Bg_pW7Nj.js (new) 4.81 kB 🔴 +4.81 kB 🔴 +1.56 kB 🔴 +1.36 kB
assets/Preview3d-ZQ7gjX55.js (removed) 4.81 kB 🟢 -4.81 kB 🟢 -1.57 kB 🟢 -1.36 kB
assets/CancelSubscriptionDialogContent-BVsuY6fi.js (removed) 4.79 kB 🟢 -4.79 kB 🟢 -1.78 kB 🟢 -1.56 kB
assets/CancelSubscriptionDialogContent-TgQWrUyr.js (new) 4.79 kB 🔴 +4.79 kB 🔴 +1.78 kB 🔴 +1.56 kB
assets/DeleteWorkspaceDialogContent-BpDjP8Xn.js (removed) 4.23 kB 🟢 -4.23 kB 🟢 -1.63 kB 🟢 -1.42 kB
assets/DeleteWorkspaceDialogContent-p1ni9cLb.js (new) 4.23 kB 🔴 +4.23 kB 🔴 +1.63 kB 🔴 +1.43 kB
assets/WidgetWithControl-B917b5St.js (removed) 4.1 kB 🟢 -4.1 kB 🟢 -1.78 kB 🟢 -1.59 kB
assets/WidgetWithControl-DJ6Cbh_e.js (new) 4.1 kB 🔴 +4.1 kB 🔴 +1.78 kB 🔴 +1.6 kB
assets/LeaveWorkspaceDialogContent-Bb37XJ3S.js (removed) 4.06 kB 🟢 -4.06 kB 🟢 -1.58 kB 🟢 -1.37 kB
assets/LeaveWorkspaceDialogContent-DwJPLXiX.js (new) 4.06 kB 🔴 +4.06 kB 🔴 +1.58 kB 🔴 +1.37 kB
assets/RemoveMemberDialogContent-DXMAWEz7.js (new) 4.04 kB 🔴 +4.04 kB 🔴 +1.52 kB 🔴 +1.33 kB
assets/RemoveMemberDialogContent-toQGRVwJ.js (removed) 4.04 kB 🟢 -4.04 kB 🟢 -1.53 kB 🟢 -1.33 kB
assets/RevokeInviteDialogContent-BwY9QK5K.js (new) 3.95 kB 🔴 +3.95 kB 🔴 +1.54 kB 🔴 +1.35 kB
assets/RevokeInviteDialogContent-DjhPTQhl.js (removed) 3.95 kB 🟢 -3.95 kB 🟢 -1.54 kB 🟢 -1.35 kB
assets/InviteMemberUpsellDialogContent-CUqKpTtD.js (new) 3.82 kB 🔴 +3.82 kB 🔴 +1.4 kB 🔴 +1.23 kB
assets/InviteMemberUpsellDialogContent-DGiKK8M_.js (removed) 3.82 kB 🟢 -3.82 kB 🟢 -1.41 kB 🟢 -1.23 kB
assets/tierBenefits-pgSYy0j2.js (removed) 3.66 kB 🟢 -3.66 kB 🟢 -1.3 kB 🟢 -1.16 kB
assets/tierBenefits-VT06IiLT.js (new) 3.66 kB 🔴 +3.66 kB 🔴 +1.3 kB 🔴 +1.16 kB
assets/saveMesh-CQjD1DQ-.js (removed) 3.38 kB 🟢 -3.38 kB 🟢 -1.45 kB 🟢 -1.29 kB
assets/saveMesh-Dxj4wswF.js (new) 3.38 kB 🔴 +3.38 kB 🔴 +1.45 kB 🔴 +1.29 kB
assets/cloudSessionCookie-4EsVxSHx.js (removed) 3.1 kB 🟢 -3.1 kB 🟢 -1.09 kB 🟢 -981 B
assets/cloudSessionCookie-e9d1jp5B.js (new) 3.1 kB 🔴 +3.1 kB 🔴 +1.09 kB 🔴 +960 B
assets/GlobalToast-C7lSWTa5.js (removed) 2.91 kB 🟢 -2.91 kB 🟢 -1.21 kB 🟢 -1.06 kB
assets/GlobalToast-w0nTfARz.js (new) 2.91 kB 🔴 +2.91 kB 🔴 +1.21 kB 🔴 +1.04 kB
assets/SubscribeToRun-BQZ3CM3d.js (new) 2.2 kB 🔴 +2.2 kB 🔴 +1.01 kB 🔴 +881 B
assets/SubscribeToRun-CxLRi7uE.js (removed) 2.2 kB 🟢 -2.2 kB 🟢 -1.01 kB 🟢 -876 B
assets/CloudRunButtonWrapper-BkWGaMtc.js (new) 1.68 kB 🔴 +1.68 kB 🔴 +784 B 🔴 +712 B
assets/CloudRunButtonWrapper-Cb2JbgM8.js (removed) 1.68 kB 🟢 -1.68 kB 🟢 -789 B 🟢 -703 B
assets/previousFullPath-BBswhmsx.js (new) 1.39 kB 🔴 +1.39 kB 🔴 +648 B 🔴 +580 B
assets/previousFullPath-DnDmOrPU.js (removed) 1.39 kB 🟢 -1.39 kB 🟢 -650 B 🟢 -572 B
assets/cloudBadges-DnwE8TOy.js (new) 1.36 kB 🔴 +1.36 kB 🔴 +705 B 🔴 +612 B
assets/cloudBadges-NRKTJHHP.js (removed) 1.36 kB 🟢 -1.36 kB 🟢 -707 B 🟢 -614 B
assets/cloudSubscription-B1jlmhWA.js (new) 1.33 kB 🔴 +1.33 kB 🔴 +653 B 🔴 +570 B
assets/cloudSubscription-B88CCGVe.js (removed) 1.33 kB 🟢 -1.33 kB 🟢 -657 B 🟢 -567 B
assets/Load3D-1n17pkVh.js (removed) 1.07 kB 🟢 -1.07 kB 🟢 -495 B 🟢 -437 B
assets/Load3D-D0L9i5WI.js (new) 1.07 kB 🔴 +1.07 kB 🔴 +495 B 🔴 +444 B
assets/nightlyBadges-Dk9oLArh.js (new) 1 kB 🔴 +1 kB 🔴 +529 B 🔴 +473 B
assets/nightlyBadges-Dt1Qig2p.js (removed) 1 kB 🟢 -1 kB 🟢 -530 B 🟢 -472 B
assets/Load3dViewerContent-CrOz9Jnh.js (removed) 993 B 🟢 -993 B 🟢 -466 B 🟢 -417 B
assets/Load3dViewerContent-xAzDJwHK.js (new) 993 B 🔴 +993 B 🔴 +465 B 🔴 +415 B
assets/SubscriptionPanelContentWorkspace-Ob_0fzAe.js (new) 920 B 🔴 +920 B 🔴 +437 B 🔴 +381 B
assets/SubscriptionPanelContentWorkspace-XILGhpTs.js (removed) 920 B 🟢 -920 B 🟢 -441 B 🟢 -378 B
assets/graphHasMissingNodes-b2hl3Bmu.js (new) 761 B 🔴 +761 B 🔴 +373 B 🔴 +319 B
assets/graphHasMissingNodes-D5Ioej4s.js (removed) 761 B 🟢 -761 B 🟢 -373 B 🟢 -330 B
assets/changeTracker-C7FFjKC3.js (removed) 757 B 🟢 -757 B 🟢 -382 B 🟢 -352 B
assets/changeTracker-jBjMk-cE.js (new) 757 B 🔴 +757 B 🔴 +383 B 🔴 +339 B
assets/WidgetLegacy-CiYCl3Pu.js (new) 745 B 🔴 +745 B 🔴 +381 B 🔴 +359 B
assets/WidgetLegacy-D-G2DOsa.js (removed) 745 B 🟢 -745 B 🟢 -383 B 🟢 -351 B
assets/WidgetInputNumber-Ceaam-Wx.js (removed) 469 B 🟢 -469 B 🟢 -265 B 🟢 -228 B
assets/WidgetInputNumber-D7vv_Izu.js (new) 469 B 🔴 +469 B 🔴 +264 B 🔴 +228 B
assets/AnimationControls-e1OB6oJR.js 4.61 kB 4.61 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/ApiNodesSignInContent-DyHLRzKO.js 2.69 kB 2.69 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/auto-BTnZwrs2.js 1.7 kB 1.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/BaseViewTemplate-DQKI7wOs.js 1.78 kB 1.78 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/comfy-logo-single-D9MrYETV.js 198 B 198 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/ComfyOrgHeader-CuEodz4y.js 910 B 910 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-B-AdR9IA.js 17.5 kB 17.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-CbkxT8K8.js 16.1 kB 16.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-CJGmjcIS.js 15.9 kB 15.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-CMaLgTTb.js 16.7 kB 16.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-Cw07MMbJ.js 18.8 kB 18.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-D7EtdE6o.js 16.9 kB 16.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-DknEFpK3.js 15.2 kB 15.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-Ds6WuXnw.js 16.1 kB 16.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-Dvq-F-mb.js 17.5 kB 17.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-pUOay9Eo.js 15.1 kB 15.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-u2AZ8xU4.js 16.1 kB 16.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/constants-htt0vt7m.js 579 B 579 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/i18n-CbILVHkU.js 532 kB 532 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/i18n-DMNd42BQ.js 199 B 199 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-9hDdrYl6.js 156 kB 156 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-BCxyPdDP.js 148 kB 148 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-Bvh07s5y.js 186 kB 186 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-C2cSZVv4.js 130 kB 130 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-C7AXDyHB.js 179 kB 179 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-CCfW6OsY.js 151 kB 151 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-CIUvAJ2X.js 208 kB 208 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-CoeFC4KG.js 149 kB 149 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-CuCSlYvh.js 171 kB 171 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-CWzA8F-K.js 131 kB 131 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-D6ntfRCO.js 153 kB 153 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Media3DTop-Dqa2c7nZ.js 1.82 kB 1.82 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaAudioTop-DLiWNcHw.js 1.43 kB 1.43 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaImageTop-BLQErkwF.js 1.75 kB 1.75 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaOtherTop-NQGNpa4H.js 1.02 kB 1.02 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaTextTop-0crUoXWV.js 1.01 kB 1.01 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaVideoTop-sJMrm9jB.js 2.77 kB 2.77 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-C65EmrE8.js 449 kB 449 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-C8Y2lLDs.js 390 kB 390 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-Cd21bOuN.js 398 kB 398 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-CFVNYgsZ.js 415 kB 415 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-CLQAr6Rt.js 399 kB 399 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-CWAeDqZ0.js 490 kB 490 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-D8p6fLf4.js 395 kB 395 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-DoD_2GTf.js 362 kB 362 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-DpF2P1me.js 403 kB 403 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-DwWCG4ag.js 366 kB 366 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-K2c3p32c.js 449 kB 449 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/OBJLoader2WorkerModule-DTMpvldF.js 109 kB 109 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Popover-BIYdg9E5.js 3.65 kB 3.65 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/rolldown-runtime-DLICfi3-.js 1.97 kB 1.97 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SelectValue-C_7cycpB.js 8.94 kB 8.94 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/signInSchema-BXbk8sn-.js 1.53 kB 1.53 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Slider-DVkw5nPu.js 3.52 kB 3.52 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/src-CbNGuSYA.js 251 B 251 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SubscriptionBenefits-DVSfLULk.js 2.01 kB 2.01 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/telemetry-zZf2dHJ2.js 226 B 226 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/types-DT3N7am7.js 204 B 204 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/VideoPlayOverlay-D-ZhKuWc.js 1.35 kB 1.35 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widget-NeEr3XWN.js 586 B 586 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetBoundingBox-BYbwNME9.js 283 B 283 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetBoundingBox-GzA4D-L-.js 3.19 kB 3.19 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetChart-BtoXUSiF.js 2.21 kB 2.21 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetColorPicker-DCbDfd19.js 2.9 kB 2.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetCurve-CIcV8pqy.js 9.36 kB 9.36 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetGalleria-DZSYhGzO.js 3.61 kB 3.61 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetImageCompare-oYMwrOjF.js 7 kB 7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetInputText-0CncUIzA.js 1.86 kB 1.86 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetLayoutField-TvCt3ARa.js 1.98 kB 1.98 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetMarkdown-Cqdttdn1.js 2.93 kB 2.93 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widgetPropFilter-DN03zIgB.js 1.1 kB 1.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetTextarea-B7IIifV6.js 3.96 kB 3.96 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetToggleSwitch-CTquGMvp.js 6.8 kB 6.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widgetTypes-Br_tbhcL.js 393 B 393 B ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 52 added / 52 removed

@github-actions
Copy link

github-actions bot commented Feb 28, 2026

⚡ Performance Report

Metric Baseline PR Δ
canvas-idle: style recalcs 124 124 +0% ⚪
canvas-idle: layouts 0 0 +0% ⚪
canvas-idle: task duration 418ms 404ms -3% ⚪
canvas-mouse-sweep: style recalcs 186 189 +2% ⚪
canvas-mouse-sweep: layouts 13 12 -8% ⚪
canvas-mouse-sweep: task duration 929ms 1108ms +19% 🟠
dom-widget-clipping: style recalcs 41 43 +5% ⚪
dom-widget-clipping: layouts 0 0 +0% ⚪
dom-widget-clipping: task duration 337ms 367ms +9% ⚪
Raw data
{
  "timestamp": "2026-02-28T14:03:30.814Z",
  "gitSha": "dba53273f0da3eda8662b0e1a877690632843404",
  "branch": "chore/node-replacement-tests-and-refactor",
  "measurements": [
    {
      "name": "canvas-idle",
      "durationMs": 2029.1149999999902,
      "styleRecalcs": 124,
      "styleRecalcDurationMs": 20.318999999999996,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 403.796,
      "heapDeltaBytes": -3392184
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 2097.126000000003,
      "styleRecalcs": 189,
      "styleRecalcDurationMs": 57.879,
      "layouts": 12,
      "layoutDurationMs": 3.885,
      "taskDurationMs": 1107.942,
      "heapDeltaBytes": -3719044
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 583.4609999999998,
      "styleRecalcs": 43,
      "styleRecalcDurationMs": 14.172999999999998,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 367.20599999999996,
      "heapDeltaBytes": 8041248
    }
  ]
}

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: 1

🧹 Nitpick comments (6)
src/components/rightSidePanel/errors/MissingNodeCard.test.ts (1)

124-132: Count MissingPackGroupRow instances by component, not stub class.

At Line 126 and Line 131, using .pack-row ties the test to mock template markup. Prefer component-level queries to validate parent rendering behavior directly.

♻️ Suggested assertion update
- expect(wrapper.findAll('.pack-row')).toHaveLength(3)
+ expect(
+   wrapper.findAllComponents({ name: 'MissingPackGroupRow' })
+ ).toHaveLength(3)

- expect(wrapper.findAll('.pack-row')).toHaveLength(0)
+ expect(
+   wrapper.findAllComponents({ name: 'MissingPackGroupRow' })
+ ).toHaveLength(0)

As per coding guidelines: “Do not write tests dependent on non-behavioral features like utility classes or styles”.

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

In `@src/components/rightSidePanel/errors/MissingNodeCard.test.ts` around lines
124 - 132, The tests should count MissingPackGroupRow components instead of
querying DOM utility classes; update the two assertions in
MissingNodeCard.test.ts to use component-level queries (e.g., call mountCard({
missingPackGroups: ... }) then use
wrapper.findAllComponents(MissingPackGroupRow) and expect its length to be 3 or
0 respectively), referencing the MissingPackGroupRow component and the mountCard
helper to locate the code to change.
src/platform/nodeReplacement/useNodeReplacement.test.ts (1)

693-723: Contain LiteGraph.registered_node_types mutations to avoid cross-test bleed.

At Line 695 and Line 720-Line 722, this test mutates shared mock state and relies on in-test cleanup. If an assertion throws before cleanup, later tests can inherit stale registrations.

♻️ Safer reset pattern
  beforeEach(() => {
    vi.clearAllMocks()
    setActivePinia(createPinia())
+   for (const key of Object.keys(LiteGraph.registered_node_types)) {
+     delete (LiteGraph.registered_node_types as Record<string, unknown>)[key]
+   }
  })
...
-      // Cleanup
-      delete (LiteGraph.registered_node_types as Record<string, unknown>)[
-        'OldNode'
-      ]

As per coding guidelines: “For mocking in Vitest, leverage Vitest's utilities where possible; keep module mocks contained without global mutable state; use vi.hoisted() if necessary for per-test mock state manipulation”.

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

In `@src/platform/nodeReplacement/useNodeReplacement.test.ts` around lines 693 -
723, The test mutates shared state LiteGraph.registered_node_types which can
leak between tests; update the test to save and restore that map (or clone and
reassign) around the mutation using a safe per-test setup/teardown pattern
(e.g., store const original = { ...LiteGraph.registered_node_types } at start of
the it block or in beforeEach, perform the assignment to register 'OldNode',
then in finally or afterEach restore LiteGraph.registered_node_types =
original), or convert the mutation to a vitest-scoped mock via
vi.hoisted/vi.mock so the registration is isolated; reference
LiteGraph.registered_node_types and the test that assigns 'OldNode' to locate
where to add the save/restore.
src/platform/nodeReplacement/components/SwapNodeGroupRow.vue (1)

105-105: Decouple SwapNodeGroupRow from right-side-panel internals.

Line 105 imports SwapNodeGroup from @/components/rightSidePanel/errors/useErrorGroups, which introduces reverse coupling from platform/nodeReplacement back into a panel-specific composable. Consider extracting SwapNodeGroup to a shared node-replacement type module.

♻️ Suggested direction
- import type { SwapNodeGroup } from '@/components/rightSidePanel/errors/useErrorGroups'
+ import type { SwapNodeGroup } from '@/platform/nodeReplacement/types'

Then have both useErrorGroups.ts and node-replacement components import from that shared type module.

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

In `@src/platform/nodeReplacement/components/SwapNodeGroupRow.vue` at line 105,
SwapNodeGroupRow.vue currently imports the SwapNodeGroup type from
useErrorGroups which creates a reverse coupling; extract the SwapNodeGroup type
into a new shared node-replacement type module (e.g., nodeReplacementTypes or
types/nodeReplacement) and export it there, then update SwapNodeGroupRow.vue to
import SwapNodeGroup from that new shared module and update useErrorGroups.ts to
also import the type from the shared module so both consumers use the same
centralized type instead of importing from the right-side-panel composable.
src/platform/nodeReplacement/components/SwapNodesCard.test.ts (1)

61-74: Prefer component-based row assertions over mock CSS class checks.

The row-count assertions are coupled to the mocked .swap-row class rather than the component behavior. Counting SwapNodeGroupRow components is more robust.

♻️ Suggested refactor
- expect(wrapper.findAll('.swap-row')).toHaveLength(3)
+ expect(
+   wrapper.findAllComponents({ name: 'SwapNodeGroupRow' })
+ ).toHaveLength(3)

- expect(wrapper.findAll('.swap-row')).toHaveLength(0)
+ expect(
+   wrapper.findAllComponents({ name: 'SwapNodeGroupRow' })
+ ).toHaveLength(0)

- expect(wrapper.findAll('.swap-row')).toHaveLength(1)
+ expect(
+   wrapper.findAllComponents({ name: 'SwapNodeGroupRow' })
+ ).toHaveLength(1)

Based on learnings: Applies to src/**/*.test.ts : Do not write tests dependent on non-behavioral features like utility classes or styles.

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

In `@src/platform/nodeReplacement/components/SwapNodesCard.test.ts` around lines
61 - 74, Update the three assertions in SwapNodesCard.test.ts to count actual
SwapNodeGroupRow components instead of querying the mocked .swap-row CSS class:
locate the tests that call mountCard(...) and replace
expect(wrapper.findAll('.swap-row')).toHaveLength(n) with an assertion that uses
wrapper.findAllComponents(SwapNodeGroupRow) (or wrapper.findAllComponents({
name: 'SwapNodeGroupRow' }) if using name lookup) to assert the component count;
ensure SwapNodeGroupRow is imported into the test file if not already.
src/components/rightSidePanel/errors/TabErrors.vue (1)

269-273: Use actual replaced types for cleanup in single-group replace.

To keep behavior aligned with handleReplaceAll(), pass replaced to removeMissingNodesByType instead of [group.type].

♻️ Suggested refactor
 function handleReplaceGroup(group: SwapNodeGroup) {
   const replaced = replaceNodesInPlace(group.nodeTypes)
   if (replaced.length > 0) {
-    executionErrorStore.removeMissingNodesByType([group.type])
+    executionErrorStore.removeMissingNodesByType(replaced)
   }
 }
🤖 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 269 - 273,
In handleReplaceGroup, after calling replaceNodesInPlace(group.nodeTypes) you
currently call executionErrorStore.removeMissingNodesByType([group.type]);
change it to pass the actual replaced array (the replaced variable) to
executionErrorStore.removeMissingNodesByType so the cleanup mirrors
handleReplaceAll—i.e., use replaced instead of [group.type] to remove missing
node types that were actually replaced.
src/platform/nodeReplacement/components/SwapNodeGroupRow.test.ts (1)

30-35: Replace icon-markup matching with semantic button queries.

Selectors based on html().includes('chevron'|'repeat'|'locate') are brittle. Prefer role/name or visible-label based queries so tests validate user-visible behavior instead of icon implementation details.

Based on learnings: In test files, prefer selecting or asserting on accessible properties (text content, aria-label, role, accessible name) over implementation-detail selectors.

Also applies to: 99-103, 174-177, 187-190, 199-200, 213-214

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

In `@src/platform/nodeReplacement/components/SwapNodeGroupRow.test.ts` around
lines 30 - 35, Replace brittle icon-markup matching in findChevron and the other
occurrences (lines referenced around 99-103, 174-177, 187-190, 199-200, 213-214)
with semantic queries that target accessible button properties: use the mounted
wrapper helpers (e.g., mountRow) to find buttons by aria-label, title, visible
text, or role/accessible name instead of
html().includes('chevron'|'repeat'|'locate'); update findChevron to search
wrapper.findAll('button').find(b => b.attributes('aria-label') === 'Chevron' ||
b.text().includes('Chevron') || b.props('aria-label') === 'Chevron') (or the
app's actual accessible name) and do the same for the other helper/finders so
tests assert on user-visible labels rather than icon markup.
🤖 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/components/rightSidePanel/errors/MissingPackGroupRow.test.ts`:
- Around line 151-159: The tests rely on icon/class selectors (e.g.,
findChevron, `.icon-[lucide--check]`, `.dot-spinner`) which are brittle; update
findChevron(wrapper) to locate the toggle button by its accessible attributes
instead of markup (e.g., find a button whose aria-label matches
/expand|collapse/i or use getByRole('button', { name: /expand|collapse/i }) on
the mounted wrapper), and replace other assertions in the referenced ranges
(lines ~187-190, 225-244, 271-303, 319-321, 348-378) to use role, visible text,
or aria-label queries (getByRole/getByText/getByLabelText or
wrapper.find('button[aria-label="..."]')) so tests assert user-observable
behavior rather than icon/class names; keep the same semantic checks
(expanded/collapsed state, presence of download/search/info actions, spinner vs
success state) but select elements by accessible labels or roles.

---

Nitpick comments:
In `@src/components/rightSidePanel/errors/MissingNodeCard.test.ts`:
- Around line 124-132: The tests should count MissingPackGroupRow components
instead of querying DOM utility classes; update the two assertions in
MissingNodeCard.test.ts to use component-level queries (e.g., call mountCard({
missingPackGroups: ... }) then use
wrapper.findAllComponents(MissingPackGroupRow) and expect its length to be 3 or
0 respectively), referencing the MissingPackGroupRow component and the mountCard
helper to locate the code to change.

In `@src/components/rightSidePanel/errors/TabErrors.vue`:
- Around line 269-273: In handleReplaceGroup, after calling
replaceNodesInPlace(group.nodeTypes) you currently call
executionErrorStore.removeMissingNodesByType([group.type]); change it to pass
the actual replaced array (the replaced variable) to
executionErrorStore.removeMissingNodesByType so the cleanup mirrors
handleReplaceAll—i.e., use replaced instead of [group.type] to remove missing
node types that were actually replaced.

In `@src/platform/nodeReplacement/components/SwapNodeGroupRow.test.ts`:
- Around line 30-35: Replace brittle icon-markup matching in findChevron and the
other occurrences (lines referenced around 99-103, 174-177, 187-190, 199-200,
213-214) with semantic queries that target accessible button properties: use the
mounted wrapper helpers (e.g., mountRow) to find buttons by aria-label, title,
visible text, or role/accessible name instead of
html().includes('chevron'|'repeat'|'locate'); update findChevron to search
wrapper.findAll('button').find(b => b.attributes('aria-label') === 'Chevron' ||
b.text().includes('Chevron') || b.props('aria-label') === 'Chevron') (or the
app's actual accessible name) and do the same for the other helper/finders so
tests assert on user-visible labels rather than icon markup.

In `@src/platform/nodeReplacement/components/SwapNodeGroupRow.vue`:
- Line 105: SwapNodeGroupRow.vue currently imports the SwapNodeGroup type from
useErrorGroups which creates a reverse coupling; extract the SwapNodeGroup type
into a new shared node-replacement type module (e.g., nodeReplacementTypes or
types/nodeReplacement) and export it there, then update SwapNodeGroupRow.vue to
import SwapNodeGroup from that new shared module and update useErrorGroups.ts to
also import the type from the shared module so both consumers use the same
centralized type instead of importing from the right-side-panel composable.

In `@src/platform/nodeReplacement/components/SwapNodesCard.test.ts`:
- Around line 61-74: Update the three assertions in SwapNodesCard.test.ts to
count actual SwapNodeGroupRow components instead of querying the mocked
.swap-row CSS class: locate the tests that call mountCard(...) and replace
expect(wrapper.findAll('.swap-row')).toHaveLength(n) with an assertion that uses
wrapper.findAllComponents(SwapNodeGroupRow) (or wrapper.findAllComponents({
name: 'SwapNodeGroupRow' }) if using name lookup) to assert the component count;
ensure SwapNodeGroupRow is imported into the test file if not already.

In `@src/platform/nodeReplacement/useNodeReplacement.test.ts`:
- Around line 693-723: The test mutates shared state
LiteGraph.registered_node_types which can leak between tests; update the test to
save and restore that map (or clone and reassign) around the mutation using a
safe per-test setup/teardown pattern (e.g., store const original = {
...LiteGraph.registered_node_types } at start of the it block or in beforeEach,
perform the assignment to register 'OldNode', then in finally or afterEach
restore LiteGraph.registered_node_types = original), or convert the mutation to
a vitest-scoped mock via vi.hoisted/vi.mock so the registration is isolated;
reference LiteGraph.registered_node_types and the test that assigns 'OldNode' to
locate where to add the save/restore.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 45f112e and a582287.

📒 Files selected for processing (14)
  • src/components/rightSidePanel/errors/MissingNodeCard.test.ts
  • src/components/rightSidePanel/errors/MissingPackGroupRow.test.ts
  • src/components/rightSidePanel/errors/TabErrors.vue
  • src/components/rightSidePanel/errors/swapNodeGroups.test.ts
  • src/components/rightSidePanel/errors/useErrorGroups.test.ts
  • src/platform/nodeReplacement/components/SwapNodeGroupRow.test.ts
  • src/platform/nodeReplacement/components/SwapNodeGroupRow.vue
  • src/platform/nodeReplacement/components/SwapNodesCard.test.ts
  • src/platform/nodeReplacement/components/SwapNodesCard.vue
  • src/platform/nodeReplacement/missingNodeScan.test.ts
  • src/platform/nodeReplacement/missingNodeScan.ts
  • src/platform/nodeReplacement/useNodeReplacement.test.ts
  • src/scripts/app.ts
  • src/stores/executionErrorStore.test.ts

… clean up test code

- Move replaceGroup() and replaceAllGroups() helpers into useNodeReplacement,
  removing inline replace logic from TabErrors.vue
- Remove unused useExecutionErrorStore import from TabErrors.vue
- Clean up section dividers and non-null assertions in executionErrorStore.test.ts
- Add useExecutionErrorStore mock to useNodeReplacement.test.ts
…omponents

- Replace brittle icon/class-based selectors with semantic aria-label and
  text-content queries (e.g., �utton[aria-label='Expand'], .toContain())
- Populate i18n messages with actual translation strings instead of empty
  objects so assertions reflect real user-visible text
- Remove custom indChevron / indButtonByAriaLabel helper functions;
  use wrapper.get() / wrapper.findAll() directly per project style
- Update DotSpinner stub to use 
ole='status' and query via
  [role='status'] instead of class-based .dot-spinner selector
- Replace non-null assertion operators (!) with optional chaining (?.)
Affected files:
  - MissingPackGroupRow.test.ts
  - SwapNodeGroupRow.test.ts
  - MissingNodeCard.test.ts
  - SwapNodesCard.test.ts
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: 1

🧹 Nitpick comments (3)
src/platform/nodeReplacement/useNodeReplacement.ts (1)

10-10: Decouple node-replacement domain code from right-side-panel UI types.

useNodeReplacement now depends on SwapNodeGroup from a UI module path. Consider moving this contract into a node-replacement domain type (or defining a minimal local interface) to keep dependency direction clean.

♻️ Suggested refactor
-import type { SwapNodeGroup } from '@/components/rightSidePanel/errors/useErrorGroups'
+interface ReplacementGroup {
+  type: string
+  nodeTypes: MissingNodeType[]
+}
...
-  function replaceGroup(group: SwapNodeGroup): void {
+  function replaceGroup(group: ReplacementGroup): void {
...
-  function replaceAllGroups(groups: SwapNodeGroup[]): void {
+  function replaceAllGroups(groups: ReplacementGroup[]): void {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/platform/nodeReplacement/useNodeReplacement.ts` at line 10, The hook
useNodeReplacement currently imports the UI type SwapNodeGroup from
'@/components/rightSidePanel/errors/useErrorGroups', creating a UI→domain
dependency; remove that import and instead declare a domain-level type (or a
minimal local interface) that captures only the fields useNodeReplacement needs,
then update all references in useNodeReplacement to use that new type (or export
it from a node-replacement domain module) so the hook no longer depends on
right-side-panel UI code.
src/platform/nodeReplacement/components/SwapNodesCard.test.ts (1)

61-74: Prefer component queries over mock CSS-class selectors for row counts.

The .swap-row assertions are coupled to mock template markup. Use findAllComponents({ name: 'SwapNodeGroupRow' }) so the test asserts component behavior rather than mock styling details.

♻️ Suggested update
-      expect(wrapper.findAll('.swap-row')).toHaveLength(3)
+      expect(
+        wrapper.findAllComponents({ name: 'SwapNodeGroupRow' })
+      ).toHaveLength(3)
...
-      expect(wrapper.findAll('.swap-row')).toHaveLength(0)
+      expect(
+        wrapper.findAllComponents({ name: 'SwapNodeGroupRow' })
+      ).toHaveLength(0)
...
-      expect(wrapper.findAll('.swap-row')).toHaveLength(1)
+      expect(
+        wrapper.findAllComponents({ name: 'SwapNodeGroupRow' })
+      ).toHaveLength(1)
As per coding guidelines: `src/**/*.test.ts`: Do not write tests dependent on non-behavioral features like utility classes or styles.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/platform/nodeReplacement/components/SwapNodesCard.test.ts` around lines
61 - 74, Replace CSS-class based assertions that use
wrapper.findAll('.swap-row') with component queries to assert real behavior: use
wrapper.findAllComponents({ name: 'SwapNodeGroupRow' }) in the three tests
inside SwapNodesCard.test.ts (the tests that call mountCard and currently assert
lengths 3, 0, and 1). Keep the same expected lengths but change the selector to
target the SwapNodeGroupRow component by name so tests rely on component
presence rather than styling.
src/components/rightSidePanel/errors/MissingNodeCard.test.ts (1)

124-132: Use child-component queries instead of mock class selectors for row-count tests.

Counting .pack-row couples these assertions to mock template details. Prefer findAllComponents({ name: 'MissingPackGroupRow' }) to keep tests behavior-focused.

♻️ Suggested update
-      expect(wrapper.findAll('.pack-row')).toHaveLength(3)
+      expect(
+        wrapper.findAllComponents({ name: 'MissingPackGroupRow' })
+      ).toHaveLength(3)
...
-      expect(wrapper.findAll('.pack-row')).toHaveLength(0)
+      expect(
+        wrapper.findAllComponents({ name: 'MissingPackGroupRow' })
+      ).toHaveLength(0)
As per coding guidelines: `src/**/*.test.ts`: Do not write tests dependent on non-behavioral features like utility classes or styles.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/rightSidePanel/errors/MissingNodeCard.test.ts` around lines
124 - 132, Tests in MissingNodeCard.test.ts are asserting row counts by querying
the mock CSS selector '.pack-row', which couples tests to template
implementation; update the two assertions to use component queries instead—use
mountCard(...) as before but replace wrapper.findAll('.pack-row') with
wrapper.findAllComponents({ name: 'MissingPackGroupRow' }) (or the equivalent
named component query) so the tests count MissingPackGroupRow components
produced when calling mountCard({ missingPackGroups: makePackGroups(3) }) and
mountCard({ missingPackGroups: [] }).
🤖 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/components/SwapNodeGroupRow.test.ts`:
- Around line 130-135: The test currently asserts on the utility class
"rotate-180" which is fragile; update the "toggles chevron rotation when
expanded" spec to assert user-observable behavior instead by using mountRow and
the Expand button: trigger the click on button[aria-label="Expand"] and then
assert the accessible/visible state change (for example check the button's
aria-expanded attribute toggles or that the expanded panel/content for
SwapNodeGroupRow becomes present/hidden) rather than checking for the
"rotate-180" CSS class.

---

Nitpick comments:
In `@src/components/rightSidePanel/errors/MissingNodeCard.test.ts`:
- Around line 124-132: Tests in MissingNodeCard.test.ts are asserting row counts
by querying the mock CSS selector '.pack-row', which couples tests to template
implementation; update the two assertions to use component queries instead—use
mountCard(...) as before but replace wrapper.findAll('.pack-row') with
wrapper.findAllComponents({ name: 'MissingPackGroupRow' }) (or the equivalent
named component query) so the tests count MissingPackGroupRow components
produced when calling mountCard({ missingPackGroups: makePackGroups(3) }) and
mountCard({ missingPackGroups: [] }).

In `@src/platform/nodeReplacement/components/SwapNodesCard.test.ts`:
- Around line 61-74: Replace CSS-class based assertions that use
wrapper.findAll('.swap-row') with component queries to assert real behavior: use
wrapper.findAllComponents({ name: 'SwapNodeGroupRow' }) in the three tests
inside SwapNodesCard.test.ts (the tests that call mountCard and currently assert
lengths 3, 0, and 1). Keep the same expected lengths but change the selector to
target the SwapNodeGroupRow component by name so tests rely on component
presence rather than styling.

In `@src/platform/nodeReplacement/useNodeReplacement.ts`:
- Line 10: The hook useNodeReplacement currently imports the UI type
SwapNodeGroup from '@/components/rightSidePanel/errors/useErrorGroups', creating
a UI→domain dependency; remove that import and instead declare a domain-level
type (or a minimal local interface) that captures only the fields
useNodeReplacement needs, then update all references in useNodeReplacement to
use that new type (or export it from a node-replacement domain module) so the
hook no longer depends on right-side-panel UI code.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a582287 and dd58bf6.

📒 Files selected for processing (8)
  • src/components/rightSidePanel/errors/MissingNodeCard.test.ts
  • src/components/rightSidePanel/errors/MissingPackGroupRow.test.ts
  • src/components/rightSidePanel/errors/TabErrors.vue
  • src/platform/nodeReplacement/components/SwapNodeGroupRow.test.ts
  • src/platform/nodeReplacement/components/SwapNodesCard.test.ts
  • src/platform/nodeReplacement/useNodeReplacement.test.ts
  • src/platform/nodeReplacement/useNodeReplacement.ts
  • src/stores/executionErrorStore.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/platform/nodeReplacement/useNodeReplacement.test.ts

…ection

When last_serialization.type is absent (old serialization format),
the predicate falls back to n.type, which app.ts may have already
passed through sanitizeNodeName — stripping HTML special chars
(& < > '  =). In that case, targetTypes.has(n.type) would silently
fail because targetTypes holds the original unsanitized names.
Fix by also adding sanitized variants of each type into targetTypes at
construction time. For the common case (no special chars), the Set
deduplicates them and behavior is identical to before.
- Add sanitized variants to targetTypes Set in replaceNodesInPlace
- Add regression test covering the edge case:
  last_serialization.type absent + live type already sanitized
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: 1

🧹 Nitpick comments (2)
src/platform/nodeReplacement/components/SwapNodeGroupRow.test.ts (1)

182-192: Consider using explicit edge-case typing instead of as never.

The as never casts bypass type checking entirely. For edge-case testing, a more explicit approach signals intent better:

Optional: More explicit edge-case typing
     it('does not render Locate button for nodeTypes without nodeId', async () => {
       const wrapper = mountRow({
         group: makeGroup({
-          nodeTypes: [{ type: 'NoIdNode', isReplaceable: true } as never]
+          // Intentionally malformed: missing nodeId to test graceful handling
+          nodeTypes: [{ type: 'NoIdNode', isReplaceable: true }] as unknown as typeof makeGroup extends () => infer G ? G['nodeTypes'] : never
         })
       })

Alternatively, if MissingNodeType.nodeId is actually optional, update the test data to match the real type signature.

Also applies to: 229-237

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

In `@src/platform/nodeReplacement/components/SwapNodeGroupRow.test.ts` around
lines 182 - 192, The test uses an unsafe cast (`as never`) when passing
nodeTypes to makeGroup; replace that with an explicit edge-case typed object or
construct a proper test fixture matching the actual type signature instead of
silencing the compiler. Update the test in "does not render Locate button for
nodeTypes without nodeId" to call mountRow(makeGroup(...)) with a nodeTypes
entry shaped to the real NodeType (e.g., a partial/Optional type or a dedicated
MissingNodeType variant) rather than using `as never`; ensure other occurrences
(the similar case around lines 229-237) are updated the same way so tests
reflect real types and preserve type safety for functions makeGroup and
mountRow.
src/platform/nodeReplacement/useNodeReplacement.test.ts (1)

661-833: Add behavioral tests for the new cleanup helpers (replaceGroup / replaceAllGroups).

This suite thoroughly checks predicate behavior, but it does not assert the new
store-cleanup side effects from replaceGroup and replaceAllGroups. Please
add tests that verify removeMissingNodesByType receives all successfully
replaced types (including multi-type group scenarios).

As per coding guidelines: "Do not write tests that just test the mocks; ensure tests fail when code behaves unexpectedly" and "aim for behavioral coverage of critical and new features."

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

In `@src/platform/nodeReplacement/useNodeReplacement.test.ts` around lines 661 -
833, Add behavioral tests that exercise replaceGroup and replaceAllGroups from
useNodeReplacement and assert the store cleanup helper removeMissingNodesByType
is called with the exact set of types actually replaced (including multi-type
group cases). Specifically: mock or spy on removeMissingNodesByType to capture
its arguments, construct a group payload (and a multi-type group) via
makeMissingNodeType entries, ensure collectAllNodes returns the nodes to be
replaced, call replaceGroup and replaceAllGroups, then expect
removeMissingNodesByType to have been invoked with an array containing all
original old_node_id types that were successfully replaced; also reset/cleanup
the spy between tests. Reference functions/helpers: replaceGroup,
replaceAllGroups, removeMissingNodesByType, useNodeReplacement,
makeMissingNodeType, and collectAllNodes.
🤖 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/useNodeReplacement.ts`:
- Around line 331-335: The cleanup in replaceGroup currently always removes only
group.type even though replaceNodesInPlace(group.nodeTypes) returns the actual
list of successfully replaced types; update replaceGroup to call
useExecutionErrorStore().removeMissingNodesByType with the replaced array (the
value returned by replaceNodesInPlace) instead of [group.type] so you clear
stale missing-node errors for all types that were actually replaced; locate this
logic inside the replaceGroup function and change the removeMissingNodesByType
argument from [group.type] to replaced.

---

Nitpick comments:
In `@src/platform/nodeReplacement/components/SwapNodeGroupRow.test.ts`:
- Around line 182-192: The test uses an unsafe cast (`as never`) when passing
nodeTypes to makeGroup; replace that with an explicit edge-case typed object or
construct a proper test fixture matching the actual type signature instead of
silencing the compiler. Update the test in "does not render Locate button for
nodeTypes without nodeId" to call mountRow(makeGroup(...)) with a nodeTypes
entry shaped to the real NodeType (e.g., a partial/Optional type or a dedicated
MissingNodeType variant) rather than using `as never`; ensure other occurrences
(the similar case around lines 229-237) are updated the same way so tests
reflect real types and preserve type safety for functions makeGroup and
mountRow.

In `@src/platform/nodeReplacement/useNodeReplacement.test.ts`:
- Around line 661-833: Add behavioral tests that exercise replaceGroup and
replaceAllGroups from useNodeReplacement and assert the store cleanup helper
removeMissingNodesByType is called with the exact set of types actually replaced
(including multi-type group cases). Specifically: mock or spy on
removeMissingNodesByType to capture its arguments, construct a group payload
(and a multi-type group) via makeMissingNodeType entries, ensure collectAllNodes
returns the nodes to be replaced, call replaceGroup and replaceAllGroups, then
expect removeMissingNodesByType to have been invoked with an array containing
all original old_node_id types that were successfully replaced; also
reset/cleanup the spy between tests. Reference functions/helpers: replaceGroup,
replaceAllGroups, removeMissingNodesByType, useNodeReplacement,
makeMissingNodeType, and collectAllNodes.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dd58bf6 and 3164b68.

📒 Files selected for processing (3)
  • src/platform/nodeReplacement/components/SwapNodeGroupRow.test.ts
  • src/platform/nodeReplacement/useNodeReplacement.test.ts
  • src/platform/nodeReplacement/useNodeReplacement.ts

…-class selectors in tests

- Replace SwapNodeGroup import in useNodeReplacement.ts with a local
  ReplacementGroup interface to keep dependency direction clean
- Replace .swap-row and .pack-row class selectors in tests with
  findAllComponents() to assert component behavior, not mock markup
replaceGroup was removing group.type unconditionally on any success,
which could leave stale errors if only a subset of node types in the
group was actually replaced. Use the returned replaced list instead,
consistent with replaceAllGroups.
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 (2)
src/platform/nodeReplacement/components/SwapNodesCard.test.ts (1)

9-16: Consider simplifying the mock template.

The class="swap-row" in the mock template is never used in the tests. You could simplify to just <div />.

♻️ Optional simplification
 vi.mock('./SwapNodeGroupRow.vue', () => ({
   default: {
     name: 'SwapNodeGroupRow',
-    template: '<div class="swap-row" />',
+    template: '<div />',
     props: ['group', 'showNodeIdBadge'],
     emits: ['locate-node', 'replace']
   }
 }))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/platform/nodeReplacement/components/SwapNodesCard.test.ts` around lines 9
- 16, The mock for SwapNodeGroupRow can be simplified by removing the unused
class attribute: in the vi.mock block that defines the default export for
'SwapNodeGroupRow' (the object with name: 'SwapNodeGroupRow', template, props
and emits), change the template from '<div class="swap-row" />' to a plain '<div
/>' so the mock remains minimal and tests remain unaffected.
src/components/rightSidePanel/errors/MissingNodeCard.test.ts (1)

17-38: Minor inconsistency in mock value containers.

mockIsRestarting uses ref() (line 18) while mockShouldShowManagerButtons uses a plain object with a value property (line 33). Both work, but using ref() consistently would be slightly cleaner since Vue Test Utils handles refs appropriately.

♻️ Optional: use ref() consistently
-const mockShouldShowManagerButtons = { value: false }
+const mockShouldShowManagerButtons = ref(false)
 vi.mock('@/workbench/extensions/manager/composables/useManagerState', () => ({
   useManagerState: () => ({
     shouldShowManagerButtons: mockShouldShowManagerButtons
   })
 }))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/rightSidePanel/errors/MissingNodeCard.test.ts` around lines 17
- 38, Replace the plain object container for the manager buttons mock with a Vue
ref to match the pattern used by mockIsRestarting: change
mockShouldShowManagerButtons (used in the useManagerState mock) to be a
ref(false) so tests consistently expose a reactive .value and align with how
mockIsRestarting is provided by useApplyChanges.
🤖 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/MissingNodeCard.test.ts`:
- Around line 17-38: Replace the plain object container for the manager buttons
mock with a Vue ref to match the pattern used by mockIsRestarting: change
mockShouldShowManagerButtons (used in the useManagerState mock) to be a
ref(false) so tests consistently expose a reactive .value and align with how
mockIsRestarting is provided by useApplyChanges.

In `@src/platform/nodeReplacement/components/SwapNodesCard.test.ts`:
- Around line 9-16: The mock for SwapNodeGroupRow can be simplified by removing
the unused class attribute: in the vi.mock block that defines the default export
for 'SwapNodeGroupRow' (the object with name: 'SwapNodeGroupRow', template,
props and emits), change the template from '<div class="swap-row" />' to a plain
'<div />' so the mock remains minimal and tests remain unaffected.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3164b68 and 0254ce1.

📒 Files selected for processing (3)
  • src/components/rightSidePanel/errors/MissingNodeCard.test.ts
  • src/platform/nodeReplacement/components/SwapNodesCard.test.ts
  • src/platform/nodeReplacement/useNodeReplacement.ts

…havioral tests

- Replace �s never type casts in SwapNodeGroupRow.test.ts with
  �s unknown as MissingNodeType[] and intent-clarifying comments
- Add shared mockRemoveMissingNodesByType spy to executionErrorStore mock
  so store cleanup side-effects can be asserted across call sites
- Add behavioral tests for replaceGroup and replaceAllGroups covering:
  - successful replacement calls removeMissingNodesByType with replaced types
  - no replacement skips the cleanup call
  - partial failure removes only the types that actually succeeded
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

🤖 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/components/SwapNodeGroupRow.test.ts`:
- Around line 111-114: The test "starts collapsed — node list not visible" is
non-diagnostic because mountRow() defaults showNodeIdBadge: false; update the
test to mountRow with showNodeIdBadge: true (e.g., mountRow({ showNodeIdBadge:
true })) and then assert collapse behavior by verifying '#1' is not present
initially and becomes present after triggering the expand action (e.g., call the
row's expand/toggle handler or simulate the click). Use the mountRow helper and
the component's expand/toggle method to prove behavior rather than relying on
default props.

In `@src/platform/nodeReplacement/useNodeReplacement.test.ts`:
- Around line 47-52: The module factory is closing over a module-scope spy
(mockRemoveMissingNodesByType); replace that top-level vi.fn with a hoisted spy
using vi.hoisted() and return it from the mocked module factory so the mock is
created in a hoisted context; update the code around
mockRemoveMissingNodesByType and the vi.mock of useExecutionErrorStore to
reference the hoisted spy so tests follow the project convention and avoid
global mutable state.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0254ce1 and 5aa2d5f.

📒 Files selected for processing (2)
  • src/platform/nodeReplacement/components/SwapNodeGroupRow.test.ts
  • src/platform/nodeReplacement/useNodeReplacement.test.ts

…store spy

- Add showNodeIdBadge: true to the 'starts collapsed' test so that
  the assertion actually exercises collapse behavior (without it, #1
  never renders regardless of expand state)
- Replace module-scope const spy with vi.hoisted() per project convention
  to ensure the spy is initialized before the vi.mock() factory runs
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 (3)
src/platform/nodeReplacement/useNodeReplacement.test.ts (1)

702-732: Harden test isolation around registered_node_types mutation.

Line 704 mutates global mocked state, and cleanup at Line 729 only runs if the test reaches the end. If an assertion fails early, later tests can inherit polluted state. Wrap mutation/cleanup in try/finally.

Suggested patch
-      ;(LiteGraph.registered_node_types as Record<string, unknown>)['OldNode'] =
-        {}
+      const registeredNodeTypes =
+        LiteGraph.registered_node_types as Record<string, unknown>
+      registeredNodeTypes['OldNode'] = {}

-      const placeholder = createPlaceholderNode(1, 'OldNode')
-      const graph = createMockGraph([placeholder])
-      placeholder.graph = graph
-      Object.assign(app, { rootGraph: graph })
-
-      vi.mocked(collectAllNodes).mockReturnValue([])
-
-      const { replaceNodesInPlace } = useNodeReplacement()
-      replaceNodesInPlace([
-        makeMissingNodeType('OldNode', {
-          new_node_id: 'NewNode',
-          old_node_id: 'OldNode',
-          old_widget_ids: null,
-          input_mapping: null,
-          output_mapping: null
-        })
-      ])
-
-      const predicate = capturedPredicate()
-      expect(predicate(placeholder)).toBe(true)
-
-      // Cleanup
-      delete (LiteGraph.registered_node_types as Record<string, unknown>)[
-        'OldNode'
-      ]
+      try {
+        const placeholder = createPlaceholderNode(1, 'OldNode')
+        const graph = createMockGraph([placeholder])
+        placeholder.graph = graph
+        Object.assign(app, { rootGraph: graph })
+
+        vi.mocked(collectAllNodes).mockReturnValue([])
+
+        const { replaceNodesInPlace } = useNodeReplacement()
+        replaceNodesInPlace([
+          makeMissingNodeType('OldNode', {
+            new_node_id: 'NewNode',
+            old_node_id: 'OldNode',
+            old_widget_ids: null,
+            input_mapping: null,
+            output_mapping: null
+          })
+        ])
+
+        const predicate = capturedPredicate()
+        expect(predicate(placeholder)).toBe(true)
+      } finally {
+        delete registeredNodeTypes['OldNode']
+      }
As per coding guidelines: "For mocking in Vitest, leverage Vitest's utilities where possible; keep module mocks contained without global mutable state; use `vi.hoisted()` if necessary for per-test mock state manipulation".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/platform/nodeReplacement/useNodeReplacement.test.ts` around lines 702 -
732, The test mutates global state by assigning to
LiteGraph.registered_node_types['OldNode'] and only deletes it at the end,
risking test pollution; wrap the mutation and the assertion block in a
try/finally so the deletion always runs (or use a per-test mocked copy via vi
mocks), i.e. save any original value from
LiteGraph.registered_node_types['OldNode'], set the test stub, run the logic
that obtains replaceNodesInPlace and asserts predicate(placeholder), and in
finally restore the original value (delete if undefined) to ensure isolation for
useNodeReplacement(), createPlaceholderNode(), replaceNodesInPlace(), and
collectAllNodes() interactions.
src/platform/nodeReplacement/components/SwapNodeGroupRow.test.ts (2)

186-190: Remove as unknown as MissingNodeType[] casts in fixtures

Line 189 and Line 237 use double assertions that weaken type checks. These fixtures appear directly representable as MissingNodeType[] without casting (or via satisfies).

Proposed cleanup
-          nodeTypes: [
-            { type: 'NoIdNode', isReplaceable: true }
-          ] as unknown as MissingNodeType[]
+          nodeTypes: [{ type: 'NoIdNode', isReplaceable: true }]
@@
-          nodeTypes: ['StringType'] as unknown as MissingNodeType[]
+          nodeTypes: ['StringType']

Based on learnings: "In test files matching **/*.test.ts under src, when creating test helper functions that construct mock objects implementing an interface, prefer using satisfies InterfaceType for shape validation instead of type assertions."

Also applies to: 236-238

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

In `@src/platform/nodeReplacement/components/SwapNodeGroupRow.test.ts` around
lines 186 - 190, Remove the unsafe double-cast "as unknown as MissingNodeType[]"
used for the nodeTypes fixture in SwapNodeGroupRow.test.ts and instead construct
the fixture to directly satisfy the MissingNodeType shape (or use the TypeScript
"satisfies MissingNodeType[]" operator) so the compiler validates the object
shape; update the two occurrences around the nodeTypes array (the fixture that
contains { type: 'NoIdNode', isReplaceable: true }) to either be typed as const
and use satisfies MissingNodeType[] or to explicitly include the MissingNodeType
properties without any "as unknown as" casts.

46-51: Type mountRow props from the component to avoid prop-shape drift

The helper currently duplicates the prop shape manually. Import ComponentProps from vue-component-type-helpers and type the helper as:

function mountRow(
  props: Partial<ComponentProps<typeof SwapNodeGroupRow>> = {}
)

This keeps tests aligned when the component's props evolve.

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

In `@src/platform/nodeReplacement/components/SwapNodeGroupRow.test.ts` around
lines 46 - 51, Replace the manual prop typing for the test helper with the
component's actual props: import ComponentProps from
'vue-component-type-helpers' and change the mountRow signature to use
Partial<ComponentProps<typeof SwapNodeGroupRow>> so mountRow(props = {}) is
typed against SwapNodeGroupRow; update any references to the previous inline
type and ensure the import and type usage reference the SwapNodeGroupRow symbol.
🤖 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/platform/nodeReplacement/components/SwapNodeGroupRow.test.ts`:
- Around line 186-190: Remove the unsafe double-cast "as unknown as
MissingNodeType[]" used for the nodeTypes fixture in SwapNodeGroupRow.test.ts
and instead construct the fixture to directly satisfy the MissingNodeType shape
(or use the TypeScript "satisfies MissingNodeType[]" operator) so the compiler
validates the object shape; update the two occurrences around the nodeTypes
array (the fixture that contains { type: 'NoIdNode', isReplaceable: true }) to
either be typed as const and use satisfies MissingNodeType[] or to explicitly
include the MissingNodeType properties without any "as unknown as" casts.
- Around line 46-51: Replace the manual prop typing for the test helper with the
component's actual props: import ComponentProps from
'vue-component-type-helpers' and change the mountRow signature to use
Partial<ComponentProps<typeof SwapNodeGroupRow>> so mountRow(props = {}) is
typed against SwapNodeGroupRow; update any references to the previous inline
type and ensure the import and type usage reference the SwapNodeGroupRow symbol.

In `@src/platform/nodeReplacement/useNodeReplacement.test.ts`:
- Around line 702-732: The test mutates global state by assigning to
LiteGraph.registered_node_types['OldNode'] and only deletes it at the end,
risking test pollution; wrap the mutation and the assertion block in a
try/finally so the deletion always runs (or use a per-test mocked copy via vi
mocks), i.e. save any original value from
LiteGraph.registered_node_types['OldNode'], set the test stub, run the logic
that obtains replaceNodesInPlace and asserts predicate(placeholder), and in
finally restore the original value (delete if undefined) to ensure isolation for
useNodeReplacement(), createPlaceholderNode(), replaceNodesInPlace(), and
collectAllNodes() interactions.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5aa2d5f and 44d34f0.

📒 Files selected for processing (2)
  • src/platform/nodeReplacement/components/SwapNodeGroupRow.test.ts
  • src/platform/nodeReplacement/useNodeReplacement.test.ts

Copy link
Contributor

@christian-byrne christian-byrne left a comment

Choose a reason for hiding this comment

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

LGTM

@christian-byrne christian-byrne merged commit a0e518a into main Feb 28, 2026
32 checks passed
@christian-byrne christian-byrne deleted the chore/node-replacement-tests-and-refactor branch February 28, 2026 14:17
DrJKL pushed a commit that referenced this pull request Feb 28, 2026
…omprehensive test suite (#9301)

## Summary

Resolves six open issues by reorganizing node replacement components
into a domain-driven folder structure, refactoring event handling to
follow the emit pattern, and adding comprehensive test coverage across
all affected modules.

## Changes

- **What**:
- Moved `SwapNodeGroupRow.vue` and `SwapNodesCard.vue` from
`src/components/rightSidePanel/errors/` to
`src/platform/nodeReplacement/components/` (Issues #9255)
- Moved `useMissingNodeScan.ts` from `src/composables/` to
`src/platform/nodeReplacement/missingNodeScan.ts`, renamed to reflect it
is a plain function not a Vue composable (Issues #9254)
- Refactored `SwapNodeGroupRow.vue` to emit a `'replace'` event instead
of calling `useNodeReplacement()` and `useExecutionErrorStore()`
directly; replacement logic now handled in `TabErrors.vue` (Issue #9267)
- Added unit tests for `removeMissingNodesByType`
(`executionErrorStore.test.ts`), `scanMissingNodes`
(`missingNodeScan.test.ts`), and `swapNodeGroups` computed
(`swapNodeGroups.test.ts`, `useErrorGroups.test.ts`) (Issue #9270)
- Added placeholder detection tests covering unregistered-type detection
when `has_errors` is false, and exclusion of registered types
(`useNodeReplacement.test.ts`) (Issue #9271)
- Added component tests for `MissingNodeCard` and `MissingPackGroupRow`
covering rendering, expand/collapse, events, install states, and edge
cases (Issue #9231)
- Added component tests for `SwapNodeGroupRow` and `SwapNodesCard`
(Issues #9255, #9267)

## Additional Changes (Post-Review)

- **Edge case guard in placeholder detection**
(`useNodeReplacement.ts`): When `last_serialization.type` is absent (old
serialization format), the predicate falls back to `n.type`, which the
app may have already run through `sanitizeNodeName` — stripping HTML
special characters (`& < > " ' \` =`). In that case, a `Set.has()`
lookup against the original unsanitized type name would silently miss,
causing replacement to be skipped.

Fixed by including sanitized variants of each target type in the
`targetTypes` Set at construction time. For the overwhelmingly common
case (no special characters in type names), the Set deduplicates the
entries and runtime behavior is identical to before.

A regression test was added to cover the specific scenario:
`last_serialization.type` absent + live `n.type` already sanitized.

## Review Focus

- `TabErrors.vue`: confirm the new `@replace` event handler correctly
replaces nodes and removes them from missing nodes list (mirrors the old
inline logic in `SwapNodeGroupRow`)
- `missingNodeScan.ts`: filename/export name change from
`useMissingNodeScan` — verify all call sites updated via `app.ts`
- Test mocking strategy: module-level `vi.mock()` factories use closures
over `ref`/plain objects to allow per-test overrides without global
mutable state

- Fixes #9231
- Fixes #9254
- Fixes #9255
- Fixes #9267
- Fixes #9270
- Fixes #9271
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment