Skip to content

feat: add node replacement UI to Errors Tab#9253

Merged
christian-byrne merged 7 commits intomainfrom
feat/replace-missing-node-from-error-tab
Feb 27, 2026
Merged

feat: add node replacement UI to Errors Tab#9253
christian-byrne merged 7 commits intomainfrom
feat/replace-missing-node-from-error-tab

Conversation

@jaeone94
Copy link
Collaborator

@jaeone94 jaeone94 commented Feb 26, 2026

Summary

Adds a node replacement UI to the Errors Tab so users can swap missing nodes with compatible alternatives directly from the error panel, without opening a separate dialog.

Changes

  • What: New SwapNodesCard and SwapNodeGroupRow components render swap groups in the Errors Tab; each group shows the missing node type, its instances (with locate buttons), and a Replace button. Added useMissingNodeScan composable to scan the graph for missing nodes and populate executionErrorStore. Added removeMissingNodesByType() to executionErrorStore so replaced nodes are pruned from the error list reactively.

Bug Fixes Found During Implementation

Bug 1: Replaced nodes render as empty shells until page refresh

replaceWithMapping() directly mutates _nodes[idx], bypassing the Vue rendering pipeline entirely. Because the replacement node reuses the same ID, vueNodeData retains the stale entry from the old placeholder (hasErrors: true, empty widgets/inputs). graph.setDirtyCanvas() only repaints the LiteGraph canvas and has no effect on Vue.

Fix: After replaceWithMapping(), manually call nodeGraph.onNodeAdded?.(newNode) to trigger handleNodeAdded in useGraphNodeManager, which runs extractVueNodeData(newNode) and updates vueNodeData correctly. Also added a guard in handleNodeAdded to skip layoutStore.createNode() when a layout for the same ID already exists, preventing a duplicate spatialIndex.insert().

Bug 2: Missing node error list overwritten by incomplete server response

Two compounding issues: (A) the server's missing_node_type error only reports the first missing node — the old handler parsed this and called surfaceMissingNodes([singleNode]), overwriting the full list collected at load time. (B) queuePrompt() calls clearAllErrors() before the API request; if the subsequent rescan used the stale has_errors flag and found nothing, the missing nodes were permanently lost.

Fix: Created useMissingNodeScan.ts which scans LiteGraph.registered_node_types directly (not has_errors). The missing_node_type catch block in app.ts now calls rescanAndSurfaceMissingNodes(this.rootGraph) instead of parsing the server's partial response.

Review Focus

  • handleReplaceNode removes the group from the store only when replaceNodesInPlace returns at least one replaced node — should we always clear, or only on full success?
  • useMissingNodeScan re-scans on every execution-error change; confirm no performance concerns for large graphs with many subgraphs.

Screenshots

2026-02-26.213523.mp4
2026-02-26.213618.mp4

┆Issue is synchronized with this Notion page by Unito

Integrate the existing node replacement functionality into the Errors
Tab, allowing users to replace missing nodes directly from the side
panel without opening the modal dialog.
New components:
- SwapNodesCard: container with guidance label and grouped rows
- SwapNodeGroupRow: per-type replacement row with expand/collapse,
  node instance list, locate button, and replace action
Bug fixes discovered during implementation:
- Fix stale canvas rendering after replacement by calling onNodeAdded
  to refresh VueNodeData (bypassed by replaceWithMapping)
- Guard initializeVueNodeLayout against duplicate layout creation
- Fix missing node list being overwritten by incomplete server 400
  response — replaced with full graph rescan via useMissingNodeScan
- Add removeMissingNodesByType to prune replaced types from error list
Cleanup:
- Remove dead code: buildMissingNodeHint, createMissingNodeTypeFromError
- Add error toast in replaceNodesInPlace for user-visible failure
  feedback, returning empty array on error instead of throwing
- Guard removeMissingNodesByType behind replacement success check
  (replaced.length > 0) to prevent stale error list updates
- Sort buildMissingNodeGroups by priority for deterministic UI order
  (Swap Nodes 0 → Missing Node Packs 1 → Execution Errors)
- Add aux_id fallback and cnr_id precedence tests for getCnrIdFromNode
- Split replaceAllWarning from replaceWarning to fix i18n key mismatch
  between TabErrors tooltip and MissingNodesContent dialog
@jaeone94 jaeone94 added the enhancement New feature or request label Feb 26, 2026
@jaeone94 jaeone94 requested a review from a team as a code owner February 26, 2026 12:30
@jaeone94 jaeone94 added enhancement New feature or request area:nodes labels Feb 26, 2026
@dosubot dosubot bot added the size:XL This PR changes 500-999 lines, ignoring generated files. label Feb 26, 2026
@github-actions
Copy link

github-actions bot commented Feb 26, 2026

🎨 Storybook: ✅ Built — View Storybook

Details

⏰ Completed at: 02/26/2026, 03:16:37 PM UTC

Links

@github-actions
Copy link

github-actions bot commented Feb 26, 2026

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

📊 Browser Reports
  • chromium: View Report (✅ 534 / ❌ 0 / ⚠️ 5 / ⏭️ 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 26, 2026

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent 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 b585dfa and 1d8a01c.

📒 Files selected for processing (1)
  • src/scripts/app.ts

📝 Walkthrough

Walkthrough

Adds client-side missing-node detection and a new "swap_nodes" replacement UI: new SwapNodesCard/SwapNodeGroupRow components, grouping logic for swap_nodes, full-graph rescan to surface missing types, node-replacement flow updates, and a store action to remove replaced missing-node entries.

Changes

Cohort / File(s) Summary
New UI components
src/components/rightSidePanel/errors/SwapNodeGroupRow.vue, src/components/rightSidePanel/errors/SwapNodesCard.vue
Adds collapsible swap_nodes group UI with locate/replace actions; emits locate-node and forwards showNodeIdBadge.
Errors tab integration
src/components/rightSidePanel/errors/TabErrors.vue, src/components/rightSidePanel/errors/types.ts
Adds swap_nodes group handling, renders SwapNodesCard, adds "Replace All" action, and wires node-replacement and store removal flows.
Error grouping logic
src/components/rightSidePanel/errors/useErrorGroups.ts
Introduces SwapNodeGroup type, computes swapNodeGroups from missing-node scan results, excludes replaceable non-string nodeTypes from some pack groups, and returns sorted groups including swap_nodes.
Missing-node scanning & app wiring
src/composables/useMissingNodeScan.ts, src/scripts/app.ts
Adds rescanAndSurfaceMissingNodes to scan live graph for unregistered node types; replaces per-error enrichment with full graph rescan on missing_node_type events; ensures nodeReplacementStore.load() awaited on init.
Node replacement flow & store cleanup
src/platform/nodeReplacement/useNodeReplacement.ts, src/stores/executionErrorStore.ts
After replacements, calls graph.onNodeAdded to refresh Vue node data, adds replacement failure handling, and adds removeMissingNodesByType(types[]) to clear replaced missing-node entries.
Graph layout guard
src/composables/graph/useGraphNodeManager.ts
Prevents duplicate node-layout creation by checking for an existing layout ref before initializing layout entries.
Utility refactor & tests
src/workbench/extensions/manager/utils/missingNodeErrorUtil.ts, src/workbench/extensions/manager/utils/missingNodeErrorUtil.test.ts, src/workbench/extensions/manager/types/missingNodeErrorTypes.ts
Removes MissingNodeTypeExtraInfo and hint helpers; adds getCnrIdFromProperties and getCnrIdFromNode helpers and updates tests accordingly.
Localization
src/locales/en/main.json
Adds nodeReplacement translation keys (guidance, labels, warnings, unknown node).

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant TabErrors as TabErrors.vue
    participant Scanner as useMissingNodeScan
    participant Graph as LiteGraph/Graph
    participant Store as executionErrorStore
    participant Replacement as useNodeReplacement
    participant UI as SwapNodesCard/SwapNodeGroupRow

    User->>TabErrors: open errors tab
    TabErrors->>Scanner: rescanAndSurfaceMissingNodes(rootGraph)
    Scanner->>Graph: iterate nodes, check registered types
    Scanner->>Store: surfaceMissingNodes(missingTypes)
    Store->>TabErrors: expose swapNodeGroups
    TabErrors->>UI: render SwapNodesCard with groups
    User->>UI: click "Replace Node" or "Replace All"
    UI->>TabErrors: emit locate-node or call handleReplaceAll
    TabErrors->>Replacement: replaceNodesInPlace(nodeTypes)
    Replacement->>Graph: perform replacements, call onNodeAdded
    TabErrors->>Store: removeMissingNodesByType(replaced types)
    Store-->>TabErrors: updated missingNodes state
    TabErrors-->>User: UI updates
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

area:ui, size:XXL

Suggested reviewers

  • viva-jinyi
  • ltdrdata
  • christian-byrne

Poem

🐰 I hop through nodes both missing and new,
I sniff for CNRs and nudge a tidy view.
Collapsible rows, a badge and a click,
Replace one or all — the canvas feels slick. 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: add node replacement UI to Errors Tab' is concise, specific, and accurately summarizes the main change—adding a replacement UI component to the errors panel.
Description check ✅ Passed The description comprehensively covers the changes (new components, composables, store updates), includes detailed bug fixes with clear explanations, review focus questions, and screenshots demonstrating the feature.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/replace-missing-node-from-error-tab

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 Feb 26, 2026

📦 Bundle: 4.44 MB gzip 🔴 +1.16 kB

Details

Summary

  • Raw size: 20.8 MB baseline 20.8 MB — 🔴 +9.77 kB
  • Gzip: 4.44 MB baseline 4.44 MB — 🔴 +1.16 kB
  • Brotli: 3.43 MB baseline 3.43 MB — 🔴 +453 B
  • Bundles: 228 current • 228 baseline • 113 added / 113 removed

Category Glance
Graph Workspace 🔴 +8.9 kB (1.01 MB) · Data & Services 🔴 +507 B (2.55 MB) · Other 🔴 +355 B (7.77 MB) · Vendor & Third-Party ⚪ 0 B (8.84 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-BGAqxSde.js (new) 17.9 kB 🔴 +17.9 kB 🔴 +6.34 kB 🔴 +5.53 kB
assets/index-YvciZP2L.js (removed) 17.9 kB 🟢 -17.9 kB 🟢 -6.34 kB 🟢 -5.53 kB

Status: 1 added / 1 removed

Graph Workspace — 1.01 MB (baseline 1 MB) • 🔴 +8.9 kB

Graph editor runtime, canvas, workflow orchestration

File Before After Δ Raw Δ Gzip Δ Brotli
assets/GraphView-1_clJqtM.js (new) 1.01 MB 🔴 +1.01 MB 🔴 +217 kB 🔴 +164 kB
assets/GraphView-BFeQdCcY.js (removed) 1 MB 🟢 -1 MB 🟢 -216 kB 🟢 -163 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-C7OE1ou6.js (new) 15.5 kB 🔴 +15.5 kB 🔴 +3.32 kB 🔴 +2.82 kB
assets/CloudSurveyView-DinFoVKS.js (removed) 15.5 kB 🟢 -15.5 kB 🟢 -3.32 kB 🟢 -2.82 kB
assets/CloudLoginView-BSMXic4a.js (removed) 11.4 kB 🟢 -11.4 kB 🟢 -3.19 kB 🟢 -2.82 kB
assets/CloudLoginView-Dhmmgvdm.js (new) 11.4 kB 🔴 +11.4 kB 🔴 +3.19 kB 🔴 +2.81 kB
assets/CloudSignupView-C8AVRSBJ.js (new) 9.37 kB 🔴 +9.37 kB 🔴 +2.69 kB 🔴 +2.37 kB
assets/CloudSignupView-DnR9Ning.js (removed) 9.37 kB 🟢 -9.37 kB 🟢 -2.7 kB 🟢 -2.37 kB
assets/UserCheckView-BAsTrx1n.js (new) 8.41 kB 🔴 +8.41 kB 🔴 +2.23 kB 🔴 +1.94 kB
assets/UserCheckView-EKwxoklU.js (removed) 8.41 kB 🟢 -8.41 kB 🟢 -2.23 kB 🟢 -1.94 kB
assets/CloudLayoutView-DV2LyMFE.js (removed) 6.43 kB 🟢 -6.43 kB 🟢 -2.1 kB 🟢 -1.82 kB
assets/CloudLayoutView-sq1PUXCI.js (new) 6.43 kB 🔴 +6.43 kB 🔴 +2.1 kB 🔴 +1.82 kB
assets/CloudForgotPasswordView-CXnxYqDX.js (new) 5.56 kB 🔴 +5.56 kB 🔴 +1.93 kB 🔴 +1.7 kB
assets/CloudForgotPasswordView-D0cPsohK.js (removed) 5.56 kB 🟢 -5.56 kB 🟢 -1.93 kB 🟢 -1.7 kB
assets/CloudAuthTimeoutView-BwDOVLW0.js (removed) 4.91 kB 🟢 -4.91 kB 🟢 -1.77 kB 🟢 -1.55 kB
assets/CloudAuthTimeoutView-qFTnnXox.js (new) 4.91 kB 🔴 +4.91 kB 🔴 +1.77 kB 🔴 +1.54 kB
assets/CloudSubscriptionRedirectView--0Z5Ftm0.js (new) 4.75 kB 🔴 +4.75 kB 🔴 +1.78 kB 🔴 +1.58 kB
assets/CloudSubscriptionRedirectView-BLL-2pbN.js (removed) 4.75 kB 🟢 -4.75 kB 🟢 -1.78 kB 🟢 -1.58 kB
assets/UserSelectView-Db1jhcgs.js (new) 4.5 kB 🔴 +4.5 kB 🔴 +1.64 kB 🔴 +1.47 kB
assets/UserSelectView-YuynbSoJ.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-CIPM78kr.js (new) 21.5 kB 🔴 +21.5 kB 🔴 +5.3 kB 🔴 +4.66 kB
assets/SecretsPanel-CNxlPOvn.js (removed) 21.5 kB 🟢 -21.5 kB 🟢 -5.31 kB 🟢 -4.66 kB
assets/LegacyCreditsPanel-B_bd1pUj.js (new) 20.6 kB 🔴 +20.6 kB 🔴 +5.56 kB 🔴 +4.89 kB
assets/LegacyCreditsPanel-DmTHPOOZ.js (removed) 20.6 kB 🟢 -20.6 kB 🟢 -5.57 kB 🟢 -4.89 kB
assets/SubscriptionPanel-Clc0BKB-.js (new) 18.1 kB 🔴 +18.1 kB 🔴 +4.64 kB 🔴 +4.09 kB
assets/SubscriptionPanel-CvvQhVll.js (removed) 18.1 kB 🟢 -18.1 kB 🟢 -4.64 kB 🟢 -4.08 kB
assets/KeybindingPanel-B9IjPTyf.js (new) 12.3 kB 🔴 +12.3 kB 🔴 +3.52 kB 🔴 +3.12 kB
assets/KeybindingPanel-DPDR1Deb.js (removed) 12.3 kB 🟢 -12.3 kB 🟢 -3.51 kB 🟢 -3.12 kB
assets/AboutPanel-BTNhoBjH.js (new) 9.79 kB 🔴 +9.79 kB 🔴 +2.73 kB 🔴 +2.46 kB
assets/AboutPanel-CNwe2QRw.js (removed) 9.79 kB 🟢 -9.79 kB 🟢 -2.73 kB 🟢 -2.45 kB
assets/ExtensionPanel-3MQ1Vm3s.js (removed) 9.38 kB 🟢 -9.38 kB 🟢 -2.65 kB 🟢 -2.35 kB
assets/ExtensionPanel-CRd8JgHA.js (new) 9.38 kB 🔴 +9.38 kB 🔴 +2.65 kB 🔴 +2.35 kB
assets/ServerConfigPanel-CNHCXAAq.js (new) 6.44 kB 🔴 +6.44 kB 🔴 +2.13 kB 🔴 +1.89 kB
assets/ServerConfigPanel-Cq7P_AWD.js (removed) 6.44 kB 🟢 -6.44 kB 🟢 -2.13 kB 🟢 -1.91 kB
assets/UserPanel-DlOpJSpr.js (removed) 6.16 kB 🟢 -6.16 kB 🟢 -1.99 kB 🟢 -1.74 kB
assets/UserPanel-WfOBuLAi.js (new) 6.16 kB 🔴 +6.16 kB 🔴 +1.99 kB 🔴 +1.74 kB
assets/cloudRemoteConfig-4oBP22w_.js (removed) 1.44 kB 🟢 -1.44 kB 🟢 -704 B 🟢 -611 B
assets/cloudRemoteConfig-Vfa0WQ7Q.js (new) 1.44 kB 🔴 +1.44 kB 🔴 +705 B 🔴 +610 B
assets/refreshRemoteConfig-Be4xQajc.js (removed) 1.14 kB 🟢 -1.14 kB 🟢 -520 B 🟢 -455 B
assets/refreshRemoteConfig-CbBx0z0c.js (new) 1.14 kB 🔴 +1.14 kB 🔴 +520 B 🔴 +444 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-CYzm_sF7.js (removed) 3.4 kB 🟢 -3.4 kB 🟢 -1.18 kB 🟢 -993 B
assets/auth-D6m_bHE1.js (new) 3.4 kB 🔴 +3.4 kB 🔴 +1.18 kB 🔴 +992 B
assets/SignUpForm-D0F6ey_6.js (removed) 3.01 kB 🟢 -3.01 kB 🟢 -1.23 kB 🟢 -1.09 kB
assets/SignUpForm-DUvsHCfj.js (new) 3.01 kB 🔴 +3.01 kB 🔴 +1.23 kB 🔴 +1.09 kB
assets/UpdatePasswordContent-Bh8thoUf.js (removed) 2.37 kB 🟢 -2.37 kB 🟢 -1.07 kB 🟢 -938 B
assets/UpdatePasswordContent-CfYEklwC.js (new) 2.37 kB 🔴 +2.37 kB 🔴 +1.07 kB 🔴 +941 B
assets/firebaseAuthStore-B6eOWV8V.js (new) 788 B 🔴 +788 B 🔴 +386 B 🔴 +365 B
assets/firebaseAuthStore-WciJTd3h.js (removed) 788 B 🟢 -788 B 🟢 -385 B 🟢 -342 B
assets/auth-Bklkf2Vo.js (new) 357 B 🔴 +357 B 🔴 +225 B 🔴 +195 B
assets/auth-CW0esbqj.js (removed) 357 B 🟢 -357 B 🟢 -226 B 🟢 -193 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-Bt1Q1zmm.js (new) 736 B 🔴 +736 B 🔴 +377 B 🔴 +343 B
assets/useSubscriptionDialog-CPFbru-e.js (removed) 736 B 🟢 -736 B 🟢 -377 B 🟢 -347 B

Status: 1 added / 1 removed

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

Reusable component library chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useTerminalTabs-DZwToq2V.js (removed) 9.84 kB 🟢 -9.84 kB 🟢 -3.4 kB 🟢 -3.01 kB
assets/useTerminalTabs-hSwFG5Jb.js (new) 9.84 kB 🔴 +9.84 kB 🔴 +3.4 kB 🔴 +3 kB
assets/ComfyQueueButton-4GQ-lpcC.js (new) 8.02 kB 🔴 +8.02 kB 🔴 +2.49 kB 🔴 +2.24 kB
assets/ComfyQueueButton-Cx4seum9.js (removed) 8.02 kB 🟢 -8.02 kB 🟢 -2.49 kB 🟢 -2.23 kB
assets/SubscribeButton-CXFD_bOb.js (new) 2.48 kB 🔴 +2.48 kB 🔴 +1.07 kB 🔴 +940 B
assets/SubscribeButton-DmSpL0Pv.js (removed) 2.48 kB 🟢 -2.48 kB 🟢 -1.07 kB 🟢 -943 B
assets/cloudFeedbackTopbarButton-_MzdXzPQ.js (removed) 1.59 kB 🟢 -1.59 kB 🟢 -852 B 🟢 -761 B
assets/cloudFeedbackTopbarButton-C3bx8POS.js (new) 1.59 kB 🔴 +1.59 kB 🔴 +851 B 🔴 +754 B
assets/ComfyQueueButton-6ZsfITCf.js (new) 793 B 🔴 +793 B 🔴 +393 B 🔴 +345 B
assets/ComfyQueueButton-LvVUNhmu.js (removed) 793 B 🟢 -793 B 🟢 -391 B 🟢 -345 B
assets/Button-D1z3poyI.js 2.98 kB 2.98 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/CloudBadge-BEnZAylJ.js 1.24 kB 1.24 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.55 MB (baseline 2.55 MB) • 🔴 +507 B

Stores, services, APIs, and repositories

File Before After Δ Raw Δ Gzip Δ Brotli
assets/dialogService-DYUi0TfR.js (new) 1.75 MB 🔴 +1.75 MB 🔴 +393 kB 🔴 +296 kB
assets/dialogService-C558O8EX.js (removed) 1.75 MB 🟢 -1.75 MB 🟢 -393 kB 🟢 -296 kB
assets/api-B4TmrCyS.js (new) 677 kB 🔴 +677 kB 🔴 +153 kB 🔴 +122 kB
assets/api-DEZYkZzh.js (removed) 677 kB 🟢 -677 kB 🟢 -153 kB 🟢 -122 kB
assets/load3dService-4fkH9jso.js (removed) 91 kB 🟢 -91 kB 🟢 -19.1 kB 🟢 -16.4 kB
assets/load3dService-BKEhCL1r.js (new) 91 kB 🔴 +91 kB 🔴 +19.1 kB 🔴 +16.4 kB
assets/extensionStore-DdVntik1.js (removed) 12.1 kB 🟢 -12.1 kB 🟢 -4.21 kB 🟢 -3.7 kB
assets/extensionStore-Dl2T3sCc.js (new) 12.1 kB 🔴 +12.1 kB 🔴 +4.21 kB 🔴 +3.7 kB
assets/releaseStore-BExKNswP.js (removed) 7.96 kB 🟢 -7.96 kB 🟢 -2.23 kB 🟢 -1.95 kB
assets/releaseStore-CYuJY7iv.js (new) 7.96 kB 🔴 +7.96 kB 🔴 +2.22 kB 🔴 +1.95 kB
assets/keybindingService-D7ibGiHB.js (removed) 6.52 kB 🟢 -6.52 kB 🟢 -1.71 kB 🟢 -1.48 kB
assets/keybindingService-n5H3Ocj9.js (new) 6.52 kB 🔴 +6.52 kB 🔴 +1.71 kB 🔴 +1.48 kB
assets/bootstrapStore-BbGR_Pxg.js (new) 2.08 kB 🔴 +2.08 kB 🔴 +873 B 🔴 +790 B
assets/bootstrapStore-Cp4osVaU.js (removed) 2.08 kB 🟢 -2.08 kB 🟢 -878 B 🟢 -797 B
assets/userStore-BhCANJYm.js (removed) 1.85 kB 🟢 -1.85 kB 🟢 -721 B 🟢 -671 B
assets/userStore-D7FTA3S5.js (new) 1.85 kB 🔴 +1.85 kB 🔴 +722 B 🔴 +633 B
assets/audioService-BsIMDvnq.js (removed) 1.73 kB 🟢 -1.73 kB 🟢 -849 B 🟢 -727 B
assets/audioService-cOWmmvb8.js (new) 1.73 kB 🔴 +1.73 kB 🔴 +847 B 🔴 +723 B
assets/releaseStore-BHF7IQOO.js (removed) 760 B 🟢 -760 B 🟢 -380 B 🟢 -338 B
assets/releaseStore-tlcQiKPz.js (new) 760 B 🔴 +760 B 🔴 +383 B 🔴 +332 B
assets/settingStore-7kxjlHi8.js (removed) 744 B 🟢 -744 B 🟢 -386 B 🟢 -362 B
assets/settingStore-BzZiJEzn.js (new) 744 B 🔴 +744 B 🔴 +384 B 🔴 +336 B
assets/workflowDraftStore-DpejJuva.js (new) 736 B 🔴 +736 B 🔴 +377 B 🔴 +328 B
assets/workflowDraftStore-EpOTd_1q.js (removed) 736 B 🟢 -736 B 🟢 -378 B 🟢 -331 B
assets/dialogService-D2xLWPgo.js (new) 725 B 🔴 +725 B 🔴 +365 B 🔴 +336 B
assets/dialogService-ZrNq4v2m.js (removed) 725 B 🟢 -725 B 🟢 -366 B 🟢 -345 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-CdjTN9zz.js (new) 14.6 kB 🔴 +14.6 kB 🔴 +3.63 kB 🔴 +3.21 kB
assets/useLoad3d-MUVdjz0a.js (removed) 14.6 kB 🟢 -14.6 kB 🟢 -3.63 kB 🟢 -3.21 kB
assets/useLoad3dViewer-BNy3nV1S.js (removed) 14.1 kB 🟢 -14.1 kB 🟢 -3.15 kB 🟢 -2.79 kB
assets/useLoad3dViewer-Cw8g2ApO.js (new) 14.1 kB 🔴 +14.1 kB 🔴 +3.15 kB 🔴 +2.79 kB
assets/useFeatureFlags-C6Dr08hJ.js (new) 4.14 kB 🔴 +4.14 kB 🔴 +1.24 kB 🔴 +1.06 kB
assets/useFeatureFlags-jnjn85sL.js (removed) 4.14 kB 🟢 -4.14 kB 🟢 -1.24 kB 🟢 -1.06 kB
assets/useWorkspaceUI-I8pZoYh7.js (new) 3 kB 🔴 +3 kB 🔴 +822 B 🔴 +704 B
assets/useWorkspaceUI-TZ3A0bat.js (removed) 3 kB 🟢 -3 kB 🟢 -823 B 🟢 -704 B
assets/subscriptionCheckoutUtil-CfOAzTcm.js (removed) 2.53 kB 🟢 -2.53 kB 🟢 -1.06 kB 🟢 -956 B
assets/subscriptionCheckoutUtil-pc-JIgdV.js (new) 2.53 kB 🔴 +2.53 kB 🔴 +1.06 kB 🔴 +925 B
assets/useExternalLink-B-2OMa-c.js (removed) 1.66 kB 🟢 -1.66 kB 🟢 -769 B 🟢 -679 B
assets/useExternalLink-KbXdEpAq.js (new) 1.66 kB 🔴 +1.66 kB 🔴 +770 B 🔴 +679 B
assets/useErrorHandling-B28sVTnb.js (new) 1.5 kB 🔴 +1.5 kB 🔴 +627 B 🔴 +535 B
assets/useErrorHandling-CVWEUzef.js (removed) 1.5 kB 🟢 -1.5 kB 🟢 -630 B 🟢 -534 B
assets/useWorkspaceSwitch-8TO7xs1g.js (new) 1.25 kB 🔴 +1.25 kB 🔴 +543 B 🔴 +485 B
assets/useWorkspaceSwitch-Bb7H7c2-.js (removed) 1.25 kB 🟢 -1.25 kB 🟢 -546 B 🟢 -480 B
assets/useLoad3d-BQb6J4OP.js (new) 859 B 🔴 +859 B 🔴 +423 B 🔴 +372 B
assets/useLoad3d-CEe_AHxg.js (removed) 859 B 🟢 -859 B 🟢 -421 B 🟢 -380 B
assets/audioUtils-B-lzooGY.js (removed) 858 B 🟢 -858 B 🟢 -502 B 🟢 -411 B
assets/audioUtils-T-ipYtS7.js (new) 858 B 🔴 +858 B 🔴 +498 B 🔴 +405 B
assets/useLoad3dViewer-Ckhr3QWL.js (new) 838 B 🔴 +838 B 🔴 +407 B 🔴 +362 B
assets/useLoad3dViewer-rx4Fuiyv.js (removed) 838 B 🟢 -838 B 🟢 -408 B 🟢 -372 B
assets/useCurrentUser-BXUemMn9.js (new) 722 B 🔴 +722 B 🔴 +369 B 🔴 +319 B
assets/useCurrentUser-CRPPLB88.js (removed) 722 B 🟢 -722 B 🟢 -372 B 🟢 -327 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

Status: 12 added / 12 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.77 MB (baseline 7.77 MB) • 🔴 +355 B

Bundles that do not match a named category

File Before After Δ Raw Δ Gzip Δ Brotli
assets/i18n-BfE-tph5.js (new) 527 kB 🔴 +527 kB 🔴 +102 kB 🔴 +79.4 kB
assets/i18n-CLNN_CRs.js (removed) 527 kB 🟢 -527 kB 🟢 -102 kB 🟢 -79.4 kB
assets/core-2twtbPrE.js (removed) 73.4 kB 🟢 -73.4 kB 🟢 -18.9 kB 🟢 -16.2 kB
assets/core-C-HTXcoT.js (new) 73.4 kB 🔴 +73.4 kB 🔴 +18.9 kB 🔴 +16.2 kB
assets/groupNode-C-vCSEKX.js (new) 71.8 kB 🔴 +71.8 kB 🔴 +17.7 kB 🔴 +15.6 kB
assets/groupNode-MeTjz4r9.js (removed) 71.8 kB 🟢 -71.8 kB 🟢 -17.7 kB 🟢 -15.6 kB
assets/WidgetSelect-Bb6916V0.js (new) 58.1 kB 🔴 +58.1 kB 🔴 +12.4 kB 🔴 +10.7 kB
assets/WidgetSelect-DdcKXEJl.js (removed) 58.1 kB 🟢 -58.1 kB 🟢 -12.4 kB 🟢 -10.7 kB
assets/SubscriptionRequiredDialogContentWorkspace-BxIbTmNI.js (removed) 46.3 kB 🟢 -46.3 kB 🟢 -8.66 kB 🟢 -7.5 kB
assets/SubscriptionRequiredDialogContentWorkspace-DsaqF-uJ.js (new) 46.3 kB 🔴 +46.3 kB 🔴 +8.65 kB 🔴 +7.51 kB
assets/WidgetPainter-BVqsGyqe.js (removed) 32.5 kB 🟢 -32.5 kB 🟢 -7.96 kB 🟢 -7.08 kB
assets/WidgetPainter-DtfrDAxX.js (new) 32.5 kB 🔴 +32.5 kB 🔴 +7.96 kB 🔴 +7.05 kB
assets/Load3DControls-DgnNwVBE.js (removed) 30.9 kB 🟢 -30.9 kB 🟢 -5.34 kB 🟢 -4.64 kB
assets/Load3DControls-DJmyrUH5.js (new) 30.9 kB 🔴 +30.9 kB 🔴 +5.34 kB 🔴 +4.65 kB
assets/WorkspacePanelContent-C1ylxCvd.js (removed) 29.3 kB 🟢 -29.3 kB 🟢 -6.14 kB 🟢 -5.39 kB
assets/WorkspacePanelContent-EjdXxc2O.js (new) 29.3 kB 🔴 +29.3 kB 🔴 +6.14 kB 🔴 +5.42 kB
assets/SubscriptionRequiredDialogContent-BiMTWJKe.js (new) 25.7 kB 🔴 +25.7 kB 🔴 +6.57 kB 🔴 +5.78 kB
assets/SubscriptionRequiredDialogContent-CSiV0vvL.js (removed) 25.7 kB 🟢 -25.7 kB 🟢 -6.57 kB 🟢 -5.78 kB
assets/Load3dViewerContent-B_xi2Fp8.js (new) 23 kB 🔴 +23 kB 🔴 +5.18 kB 🔴 +4.49 kB
assets/Load3dViewerContent-BHGvj6-G.js (removed) 23 kB 🟢 -23 kB 🟢 -5.18 kB 🟢 -4.49 kB
assets/WidgetImageCrop-D48DdfXf.js (removed) 22.1 kB 🟢 -22.1 kB 🟢 -5.5 kB 🟢 -4.85 kB
assets/WidgetImageCrop-Vje-JTtY.js (new) 22.1 kB 🔴 +22.1 kB 🔴 +5.5 kB 🔴 +4.85 kB
assets/SubscriptionPanelContentWorkspace-BLG29cWM.js (new) 21.5 kB 🔴 +21.5 kB 🔴 +5.04 kB 🔴 +4.44 kB
assets/SubscriptionPanelContentWorkspace-yV1iVddr.js (removed) 21.5 kB 🟢 -21.5 kB 🟢 -5.03 kB 🟢 -4.44 kB
assets/CurrentUserPopoverWorkspace-CW7S-0lM.js (removed) 19.9 kB 🟢 -19.9 kB 🟢 -4.88 kB 🟢 -4.35 kB
assets/CurrentUserPopoverWorkspace-UUXwAI6L.js (new) 19.9 kB 🔴 +19.9 kB 🔴 +4.88 kB 🔴 +4.34 kB
assets/SignInContent-B78mXMFz.js (removed) 18.9 kB 🟢 -18.9 kB 🟢 -4.76 kB 🟢 -4.17 kB
assets/SignInContent-DxUCqADR.js (new) 18.9 kB 🔴 +18.9 kB 🔴 +4.76 kB 🔴 +4.16 kB
assets/WidgetInputNumber-B1CIeGrU.js (removed) 18.7 kB 🟢 -18.7 kB 🟢 -4.75 kB 🟢 -4.22 kB
assets/WidgetInputNumber-DxiYGByM.js (new) 18.7 kB 🔴 +18.7 kB 🔴 +4.75 kB 🔴 +4.22 kB
assets/WidgetRecordAudio-BM_hiPyY.js (new) 17.3 kB 🔴 +17.3 kB 🔴 +4.94 kB 🔴 +4.42 kB
assets/WidgetRecordAudio-CcY7SULA.js (removed) 17.3 kB 🟢 -17.3 kB 🟢 -4.95 kB 🟢 -4.42 kB
assets/Load3D-BftczlIu.js (new) 16.2 kB 🔴 +16.2 kB 🔴 +4.03 kB 🔴 +3.51 kB
assets/Load3D-DpYPxWq8.js (removed) 16.2 kB 🟢 -16.2 kB 🟢 -4.03 kB 🟢 -3.52 kB
assets/load3d-BuEVUTCN.js (new) 14.7 kB 🔴 +14.7 kB 🔴 +4.19 kB 🔴 +3.64 kB
assets/load3d-BXdoY1Kr.js (removed) 14.7 kB 🟢 -14.7 kB 🟢 -4.19 kB 🟢 -3.63 kB
assets/AudioPreviewPlayer-BT0TldvQ.js (removed) 10.9 kB 🟢 -10.9 kB 🟢 -3.19 kB 🟢 -2.85 kB
assets/AudioPreviewPlayer-n8Bpfvs7.js (new) 10.9 kB 🔴 +10.9 kB 🔴 +3.19 kB 🔴 +2.86 kB
assets/changeTracker-Co9BV4M1.js (removed) 9.38 kB 🟢 -9.38 kB 🟢 -2.9 kB 🟢 -2.55 kB
assets/changeTracker-t37yENT9.js (new) 9.38 kB 🔴 +9.38 kB 🔴 +2.9 kB 🔴 +2.55 kB
assets/nodeTemplates-DgclEYdd.js (new) 9.29 kB 🔴 +9.29 kB 🔴 +3.25 kB 🔴 +2.86 kB
assets/nodeTemplates-K7VC3Kpg.js (removed) 9.29 kB 🟢 -9.29 kB 🟢 -3.26 kB 🟢 -2.86 kB
assets/InviteMemberDialogContent-B2KdO11_.js (new) 7.38 kB 🔴 +7.38 kB 🔴 +2.29 kB 🔴 +1.99 kB
assets/InviteMemberDialogContent-DnUx-2Dz.js (removed) 7.38 kB 🟢 -7.38 kB 🟢 -2.29 kB 🟢 -2.01 kB
assets/Load3DConfiguration-DNTAT6Kn.js (removed) 6.27 kB 🟢 -6.27 kB 🟢 -1.92 kB 🟢 -1.68 kB
assets/Load3DConfiguration-uL5omPkI.js (new) 6.27 kB 🔴 +6.27 kB 🔴 +1.91 kB 🔴 +1.68 kB
assets/CreateWorkspaceDialogContent-O8c77GsU.js (new) 5.53 kB 🔴 +5.53 kB 🔴 +1.99 kB 🔴 +1.73 kB
assets/CreateWorkspaceDialogContent-wj_SF9KO.js (removed) 5.53 kB 🟢 -5.53 kB 🟢 -1.99 kB 🟢 -1.74 kB
assets/onboardingCloudRoutes-BJCzRNhk.js (removed) 5.41 kB 🟢 -5.41 kB 🟢 -1.84 kB 🟢 -1.6 kB
assets/onboardingCloudRoutes-CIhhJNDR.js (new) 5.41 kB 🔴 +5.41 kB 🔴 +1.83 kB 🔴 +1.6 kB
assets/FreeTierDialogContent-BdHXHvtP.js (new) 5.39 kB 🔴 +5.39 kB 🔴 +1.9 kB 🔴 +1.67 kB
assets/FreeTierDialogContent-CxGZyQF5.js (removed) 5.39 kB 🟢 -5.39 kB 🟢 -1.9 kB 🟢 -1.67 kB
assets/EditWorkspaceDialogContent-BhyMl8f8.js (new) 5.33 kB 🔴 +5.33 kB 🔴 +1.94 kB 🔴 +1.7 kB
assets/EditWorkspaceDialogContent-CFuqRGyd.js (removed) 5.33 kB 🟢 -5.33 kB 🟢 -1.95 kB 🟢 -1.7 kB
assets/ValueControlPopover-BQm0TA0y.js (removed) 4.92 kB 🟢 -4.92 kB 🟢 -1.76 kB 🟢 -1.57 kB
assets/ValueControlPopover-dKtn_Bjy.js (new) 4.92 kB 🔴 +4.92 kB 🔴 +1.76 kB 🔴 +1.57 kB
assets/Preview3d-69J4hZ1x.js (new) 4.81 kB 🔴 +4.81 kB 🔴 +1.56 kB 🔴 +1.36 kB
assets/Preview3d-gaZLkSzT.js (removed) 4.81 kB 🟢 -4.81 kB 🟢 -1.56 kB 🟢 -1.36 kB
assets/CancelSubscriptionDialogContent-BhbMVC6O.js (removed) 4.79 kB 🟢 -4.79 kB 🟢 -1.78 kB 🟢 -1.56 kB
assets/CancelSubscriptionDialogContent-DqMpPQND.js (new) 4.79 kB 🔴 +4.79 kB 🔴 +1.78 kB 🔴 +1.56 kB
assets/DeleteWorkspaceDialogContent-FNwiaHH2.js (new) 4.23 kB 🔴 +4.23 kB 🔴 +1.63 kB 🔴 +1.42 kB
assets/DeleteWorkspaceDialogContent-o0Qt-ImM.js (removed) 4.23 kB 🟢 -4.23 kB 🟢 -1.63 kB 🟢 -1.42 kB
assets/WidgetWithControl-CLxTWblP.js (removed) 4.1 kB 🟢 -4.1 kB 🟢 -1.77 kB 🟢 -1.58 kB
assets/WidgetWithControl-D_7h7yBr.js (new) 4.1 kB 🔴 +4.1 kB 🔴 +1.77 kB 🔴 +1.59 kB
assets/LeaveWorkspaceDialogContent-DdCYyLNl.js (new) 4.06 kB 🔴 +4.06 kB 🔴 +1.57 kB 🔴 +1.37 kB
assets/LeaveWorkspaceDialogContent-DMU5lnQZ.js (removed) 4.06 kB 🟢 -4.06 kB 🟢 -1.58 kB 🟢 -1.37 kB
assets/RemoveMemberDialogContent-B-ETh6Fu.js (removed) 4.04 kB 🟢 -4.04 kB 🟢 -1.52 kB 🟢 -1.33 kB
assets/RemoveMemberDialogContent-BlaB9PYh.js (new) 4.04 kB 🔴 +4.04 kB 🔴 +1.52 kB 🔴 +1.33 kB
assets/RevokeInviteDialogContent-_ighvygT.js (new) 3.95 kB 🔴 +3.95 kB 🔴 +1.54 kB 🔴 +1.35 kB
assets/RevokeInviteDialogContent-LUw23ZIj.js (removed) 3.95 kB 🟢 -3.95 kB 🟢 -1.54 kB 🟢 -1.35 kB
assets/InviteMemberUpsellDialogContent-B8b6PwIf.js (removed) 3.82 kB 🟢 -3.82 kB 🟢 -1.4 kB 🟢 -1.23 kB
assets/InviteMemberUpsellDialogContent-CJKAbqv2.js (new) 3.82 kB 🔴 +3.82 kB 🔴 +1.4 kB 🔴 +1.23 kB
assets/tierBenefits-C3pcTc-4.js (removed) 3.66 kB 🟢 -3.66 kB 🟢 -1.3 kB 🟢 -1.18 kB
assets/tierBenefits-DvS8T9eS.js (new) 3.66 kB 🔴 +3.66 kB 🔴 +1.3 kB 🔴 +1.18 kB
assets/saveMesh-7tFvHcAd.js (new) 3.38 kB 🔴 +3.38 kB 🔴 +1.45 kB 🔴 +1.29 kB
assets/saveMesh-BFKXwfNL.js (removed) 3.38 kB 🟢 -3.38 kB 🟢 -1.45 kB 🟢 -1.29 kB
assets/cloudSessionCookie-BEij_a8f.js (removed) 3.1 kB 🟢 -3.1 kB 🟢 -1.08 kB 🟢 -975 B
assets/cloudSessionCookie-Dd1WOzsW.js (new) 3.1 kB 🔴 +3.1 kB 🔴 +1.08 kB 🔴 +979 B
assets/GlobalToast-Chnc3DGv.js (new) 2.91 kB 🔴 +2.91 kB 🔴 +1.21 kB 🔴 +1.03 kB
assets/GlobalToast-CiZBPH_a.js (removed) 2.91 kB 🟢 -2.91 kB 🟢 -1.21 kB 🟢 -1.03 kB
assets/ApiNodesSignInContent-0Uj8AGQQ.js (removed) 2.69 kB 🟢 -2.69 kB 🟢 -1.05 kB 🟢 -922 B
assets/ApiNodesSignInContent-DzLRAcPq.js (new) 2.69 kB 🔴 +2.69 kB 🔴 +1.05 kB 🔴 +921 B
assets/SubscribeToRun-BBvkIjbS.js (removed) 2.2 kB 🟢 -2.2 kB 🟢 -1.01 kB 🟢 -896 B
assets/SubscribeToRun-CiRfkGbk.js (new) 2.2 kB 🔴 +2.2 kB 🔴 +1.01 kB 🔴 +866 B
assets/CloudRunButtonWrapper-CpQNiR_j.js (new) 1.68 kB 🔴 +1.68 kB 🔴 +784 B 🔴 +703 B
assets/CloudRunButtonWrapper-DZfI-c2Q.js (removed) 1.68 kB 🟢 -1.68 kB 🟢 -783 B 🟢 -705 B
assets/signInSchema-CfHI0NRD.js (new) 1.53 kB 🔴 +1.53 kB 🔴 +563 B 🔴 +516 B
assets/signInSchema-D50I55ga.js (removed) 1.53 kB 🟢 -1.53 kB 🟢 -563 B 🟢 -518 B
assets/previousFullPath-BJUtLUE3.js (new) 1.39 kB 🔴 +1.39 kB 🔴 +650 B 🔴 +570 B
assets/previousFullPath-Bk4fY_oc.js (removed) 1.39 kB 🟢 -1.39 kB 🟢 -650 B 🟢 -579 B
assets/cloudBadges-CNZ7LdZ9.js (removed) 1.37 kB 🟢 -1.37 kB 🟢 -700 B 🟢 -610 B
assets/cloudBadges-DtOmw0HM.js (new) 1.37 kB 🔴 +1.37 kB 🔴 +699 B 🔴 +609 B
assets/cloudSubscription-BxkS4Y5k.js (new) 1.33 kB 🔴 +1.33 kB 🔴 +653 B 🔴 +561 B
assets/cloudSubscription-DDeLlXNA.js (removed) 1.33 kB 🟢 -1.33 kB 🟢 -654 B 🟢 -564 B
assets/Load3D-Bu2AP3eD.js (removed) 1.07 kB 🟢 -1.07 kB 🟢 -497 B 🟢 -440 B
assets/Load3D-DzsPELXo.js (new) 1.07 kB 🔴 +1.07 kB 🔴 +497 B 🔴 +438 B
assets/nightlyBadges-BcG5X9-I.js (removed) 1 kB 🟢 -1 kB 🟢 -529 B 🟢 -470 B
assets/nightlyBadges-NyT84GCa.js (new) 1 kB 🔴 +1 kB 🔴 +529 B 🔴 +472 B
assets/Load3dViewerContent-BionPrjr.js (new) 993 B 🔴 +993 B 🔴 +466 B 🔴 +414 B
assets/Load3dViewerContent-DoOcX7id.js (removed) 993 B 🟢 -993 B 🟢 -465 B 🟢 -411 B
assets/SubscriptionPanelContentWorkspace-_9D7DWtt.js (removed) 920 B 🟢 -920 B 🟢 -435 B 🟢 -376 B
assets/SubscriptionPanelContentWorkspace-vH-_z9sa.js (new) 920 B 🔴 +920 B 🔴 +439 B 🔴 +378 B
assets/graphHasMissingNodes-DMLjKKz8.js (removed) 761 B 🟢 -761 B 🟢 -375 B 🟢 -317 B
assets/graphHasMissingNodes-tYbXaM-H.js (new) 761 B 🔴 +761 B 🔴 +374 B 🔴 +326 B
assets/changeTracker-BlCBTBAX.js (removed) 757 B 🟢 -757 B 🟢 -382 B 🟢 -353 B
assets/changeTracker-Dk62_iDR.js (new) 757 B 🔴 +757 B 🔴 +384 B 🔴 +351 B
assets/WidgetLegacy-niZeDJ1u.js (new) 745 B 🔴 +745 B 🔴 +384 B 🔴 +347 B
assets/WidgetLegacy-nvmDMEdl.js (removed) 745 B 🟢 -745 B 🟢 -382 B 🟢 -355 B
assets/WidgetInputNumber-Bh99IA1l.js (new) 469 B 🔴 +469 B 🔴 +264 B 🔴 +226 B
assets/WidgetInputNumber-C7PbbEiX.js (removed) 469 B 🟢 -469 B 🟢 -264 B 🟢 -228 B
assets/i18n-BwUEH4SK.js (removed) 199 B 🟢 -199 B 🟢 -159 B 🟢 -139 B
assets/i18n-DjRmh1uX.js (new) 199 B 🔴 +199 B 🔴 +161 B 🔴 +139 B
assets/AnimationControls-e1OB6oJR.js 4.61 kB 4.61 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/main-0eBOvZE-.js 147 kB 147 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-B2sg68b4.js 169 kB 169 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-BonGln7m.js 183 kB 183 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-Bwaiyhg6.js 130 kB 130 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-CgPRGKFQ.js 128 kB 128 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-COpUCFH6.js 154 kB 154 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-D5fk3t1K.js 176 kB 176 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-DQn-D-q9.js 151 kB 151 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-DRFXRCEv.js 205 kB 205 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-e5nfEcC2.js 146 kB 146 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-MEdUGbq0.js 149 kB 149 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-BmbASBY9.js 409 kB 409 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-BPIx0d5-.js 393 kB 393 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-BWj_hhU9.js 393 kB 393 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-C7at0SVC.js 385 kB 385 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-D00E-J_2.js 390 kB 390 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-DRodGSrf.js 362 kB 362 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-ezyqmmhm.js 443 kB 443 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-GaT9kQvQ.js 483 kB 483 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-hnjb8NXS.js 397 kB 397 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-ITpXEN3V.js 442 kB 442 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-ZK3OUxU7.js 358 kB 358 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/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: 56 added / 56 removed

@github-actions
Copy link

github-actions bot commented Feb 26, 2026

⚡ Performance Report

Metric Baseline PR Δ
canvas-idle: style recalcs 126 122 -3% ⚪
canvas-idle: layouts 1 0 -100% 🟢
canvas-idle: task duration 455ms 501ms +10% 🟠
canvas-mouse-sweep: style recalcs 173 182 +5% ⚪
canvas-mouse-sweep: layouts 12 12 +0% ⚪
canvas-mouse-sweep: task duration 771ms 990ms +28% 🔴
dom-widget-clipping: style recalcs 42 47 +12% 🟠
dom-widget-clipping: layouts 0 0 +0% ⚪
dom-widget-clipping: task duration 339ms 412ms +22% 🔴
Raw data
{
  "timestamp": "2026-02-26T15:18:43.382Z",
  "gitSha": "a93213a3c8c443ea7d98177663ce6c5333c1f071",
  "branch": "feat/replace-missing-node-from-error-tab",
  "measurements": [
    {
      "name": "canvas-idle",
      "durationMs": 2030.4490000000044,
      "styleRecalcs": 122,
      "styleRecalcDurationMs": 27.434,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 501.04400000000004,
      "heapDeltaBytes": -3788508
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 2012.3250000000041,
      "styleRecalcs": 182,
      "styleRecalcDurationMs": 62.727000000000004,
      "layouts": 12,
      "layoutDurationMs": 3.666,
      "taskDurationMs": 990.298,
      "heapDeltaBytes": -2805248
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 647.0950000000073,
      "styleRecalcs": 47,
      "styleRecalcDurationMs": 16.111,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 412.24100000000004,
      "heapDeltaBytes": 6424456
    }
  ]
}

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

🧹 Nitpick comments (3)
src/workbench/extensions/manager/utils/missingNodeErrorUtil.test.ts (2)

39-41: Add fallback coverage when cnr_id is invalid but aux_id is valid.

This edge case protects the intended fallback order from regressions.

➕ Suggested test
   it('returns undefined when cnr_id is not a string', () => {
     expect(getCnrIdFromProperties({ cnr_id: 123 })).toBeUndefined()
   })
+
+  it('falls back to aux_id when cnr_id is not a string', () => {
+    expect(
+      getCnrIdFromProperties({ cnr_id: 123, aux_id: 'my-aux-pack' })
+    ).toBe('my-aux-pack')
+  })

As per coding guidelines: "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/workbench/extensions/manager/utils/missingNodeErrorUtil.test.ts` around
lines 39 - 41, Add a test covering the fallback behavior of
getCnrIdFromProperties when cnr_id is invalid but aux_id is valid: create a case
where cnr_id is a non-string (e.g., number or null) and aux_id is a valid string
and assert the function returns the aux_id string; update the existing test
suite (missingNodeErrorUtil.test.ts) to include this scenario so the fallback
order (cnr_id first, aux_id second) is guarded against regressions.

46-48: Avoid repeated as unknown as LGraphNode in test fixtures.

Prefer a small shape-checked helper to reduce unsafe assertions and keep fixtures easier to maintain.

♻️ Suggested refactor
+function createNodeWithProperties(
+  properties: Record<string, unknown>
+): LGraphNode {
+  const node = { properties } satisfies Pick<LGraphNode, 'properties'>
+  return node as LGraphNode
+}
+
 describe('getCnrIdFromNode', () => {
   it('returns cnr_id from node properties', () => {
-    const node = {
-      properties: { cnr_id: 'node-pack' }
-    } as unknown as LGraphNode
+    const node = createNodeWithProperties({ cnr_id: 'node-pack' })
     expect(getCnrIdFromNode(node)).toBe('node-pack')
   })

   it('returns aux_id when cnr_id is absent', () => {
-    const node = {
-      properties: { aux_id: 'node-aux-pack' }
-    } as unknown as LGraphNode
+    const node = createNodeWithProperties({ aux_id: 'node-aux-pack' })
     expect(getCnrIdFromNode(node)).toBe('node-aux-pack')
   })

   it('prefers cnr_id over aux_id in node properties', () => {
-    const node = {
-      properties: { cnr_id: 'primary', aux_id: 'secondary' }
-    } as unknown as LGraphNode
+    const node = createNodeWithProperties({
+      cnr_id: 'primary',
+      aux_id: 'secondary'
+    })
     expect(getCnrIdFromNode(node)).toBe('primary')
   })

   it('returns undefined when node has no cnr_id or aux_id', () => {
-    const node = { properties: {} } as unknown as LGraphNode
+    const node = createNodeWithProperties({})
     expect(getCnrIdFromNode(node)).toBeUndefined()
   })
 })

Based on learnings: "In test files matching **/*.test.ts under src, when creating test helper functions that construct mock objects implementing an interface (e.g., AssetItem), prefer using satisfies InterfaceType for shape validation instead of type assertions like as Partial as InterfaceType or as any."

Also applies to: 53-55, 60-62, 67-67

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

In `@src/workbench/extensions/manager/utils/missingNodeErrorUtil.test.ts` around
lines 46 - 48, Replace the repeated unsafe casts for LGraphNode test fixtures by
adding a small factory helper (e.g., makeNode or createTestNode) in the test
file that returns a properly shaped object using TypeScript's "satisfies
LGraphNode" to get compile-time shape checking; update the existing fixtures
(the const node and other occurrences at the noted locations) to call this
helper with the desired properties instead of using "as unknown as LGraphNode",
ensuring the helper includes any minimal required fields of LGraphNode so the
tests remain valid and type-checked.
src/components/rightSidePanel/errors/SwapNodesCard.vue (1)

30-33: Prefer Vue 3.5 reactive props destructuring here.

Use destructured defineProps to align with the project's standard for new Vue SFCs.

♻️ Proposed refactor
-const props = defineProps<{
+const { swapNodeGroups, showNodeIdBadge } = defineProps<{
   swapNodeGroups: SwapNodeGroup[]
   showNodeIdBadge: boolean
 }>()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/rightSidePanel/errors/SwapNodesCard.vue` around lines 30 - 33,
Replace the generic props object with Vue 3.5-style destructured reactive props:
remove the const props = defineProps<...>() block and instead destructure the
props directly from defineProps so you get swapNodeGroups and showNodeIdBadge as
standalone reactive bindings; update any references to props.swapNodeGroups and
props.showNodeIdBadge to use the destructured names (SwapNodesCard.vue: replace
usages referencing props, and adjust defineProps call around swapNodeGroups and
showNodeIdBadge).
🤖 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/SwapNodeGroupRow.vue`:
- Around line 144-149: handleReplaceNode currently calls
replaceNodesInPlace(props.group.nodeTypes) and only clears executionErrorStore
when replacements occur; add user feedback for the case where
replaceNodesInPlace returns an empty array by showing a toast informing the user
that no replaceable nodes of the selected type were found. Modify
handleReplaceNode to detect when replaced.length === 0 and invoke the same toast
mechanism used in useNodeReplacement.ts (the success/error toasts around
replaceNodesInPlace) so the message style is consistent; keep the existing
executionErrorStore.removeMissingNodesByType([props.group.type]) behavior when
replacements happen.

In `@src/components/rightSidePanel/errors/useErrorGroups.ts`:
- Around line 536-542: Replace the hardcoded English title in the useErrorGroups
function where groups.push({...}) is adding the 'swap_nodes' group: use the
provided t translation function (e.g. title: t('swapNodesTitle')) instead of the
literal 'Swap Nodes', and add the corresponding key "swapNodesTitle": "Swap
Nodes" to src/locales/en/main.json so the UI text uses vue-i18n consistently.

In `@src/platform/nodeReplacement/useNodeReplacement.ts`:
- Around line 286-295: The catch block in useNodeReplacement currently always
returns [] which discards any partial replacements; declare the array that
collects successful replacements (e.g., replacedNodes or replacements) outside
the try in useNodeReplacement, push into that array as each node is replaced
inside the try, and in the catch return that array (not an empty array) so
callers see partial results; keep the error toast/logging behavior but change
the return in the catch to return the external replacements variable.

---

Nitpick comments:
In `@src/components/rightSidePanel/errors/SwapNodesCard.vue`:
- Around line 30-33: Replace the generic props object with Vue 3.5-style
destructured reactive props: remove the const props = defineProps<...>() block
and instead destructure the props directly from defineProps so you get
swapNodeGroups and showNodeIdBadge as standalone reactive bindings; update any
references to props.swapNodeGroups and props.showNodeIdBadge to use the
destructured names (SwapNodesCard.vue: replace usages referencing props, and
adjust defineProps call around swapNodeGroups and showNodeIdBadge).

In `@src/workbench/extensions/manager/utils/missingNodeErrorUtil.test.ts`:
- Around line 39-41: Add a test covering the fallback behavior of
getCnrIdFromProperties when cnr_id is invalid but aux_id is valid: create a case
where cnr_id is a non-string (e.g., number or null) and aux_id is a valid string
and assert the function returns the aux_id string; update the existing test
suite (missingNodeErrorUtil.test.ts) to include this scenario so the fallback
order (cnr_id first, aux_id second) is guarded against regressions.
- Around line 46-48: Replace the repeated unsafe casts for LGraphNode test
fixtures by adding a small factory helper (e.g., makeNode or createTestNode) in
the test file that returns a properly shaped object using TypeScript's
"satisfies LGraphNode" to get compile-time shape checking; update the existing
fixtures (the const node and other occurrences at the noted locations) to call
this helper with the desired properties instead of using "as unknown as
LGraphNode", ensuring the helper includes any minimal required fields of
LGraphNode so the tests remain valid and type-checked.

ℹ️ 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 45ca1be and 7d69a0d.

📒 Files selected for processing (14)
  • src/components/rightSidePanel/errors/SwapNodeGroupRow.vue
  • src/components/rightSidePanel/errors/SwapNodesCard.vue
  • src/components/rightSidePanel/errors/TabErrors.vue
  • src/components/rightSidePanel/errors/types.ts
  • src/components/rightSidePanel/errors/useErrorGroups.ts
  • src/composables/graph/useGraphNodeManager.ts
  • src/composables/useMissingNodeScan.ts
  • src/locales/en/main.json
  • src/platform/nodeReplacement/useNodeReplacement.ts
  • src/scripts/app.ts
  • src/stores/executionErrorStore.ts
  • src/workbench/extensions/manager/types/missingNodeErrorTypes.ts
  • src/workbench/extensions/manager/utils/missingNodeErrorUtil.test.ts
  • src/workbench/extensions/manager/utils/missingNodeErrorUtil.ts
💤 Files with no reviewable changes (2)
  • src/workbench/extensions/manager/types/missingNodeErrorTypes.ts
  • src/workbench/extensions/manager/utils/missingNodeErrorUtil.ts

- Use i18n key for 'Swap Nodes' group title
- Preserve partial replacement results on error instead of returning empty array
- Remove redundant parameter that shadowed composable ref
- Only remove actually replaced types from error list on partial success
@jaeone94 jaeone94 assigned christian-byrne and unassigned jaeone94 Feb 26, 2026
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/TabErrors.vue (1)

78-92: Consider adding loading state and moving i18n strings to translation file.

Two observations:

  1. Loading state: Unlike the "Install All" button which has isInstallingAll for feedback and preventing double-clicks, this button has no loading/disabled state during replacement. If replaceNodesInPlace is slow or if users double-click, it could cause unexpected behavior.

  2. i18n: The inline fallback strings ('Replaces all available nodes in this group.', 'Replace All') should be added to src/locales/en/main.json rather than relying on inline defaults. As per coding guidelines: "place new translation entries in src/locales/en/main.json".

🔧 Suggested improvement for loading state
 const { isInstalling: isInstallingAll, installAllPacks: installAll } =
   usePackInstall(() => missingNodePacks.value)
 const { replaceNodesInPlace } = useNodeReplacement()
 const executionErrorStore = useExecutionErrorStore()
+const isReplacingAll = ref(false)

 // ...

-function handleReplaceAll() {
+async function handleReplaceAll() {
+  if (isReplacingAll.value) return
+  isReplacingAll.value = true
   const allNodeTypes = swapNodeGroups.value.flatMap((g) => g.nodeTypes)
   const replaced = replaceNodesInPlace(allNodeTypes)
   if (replaced.length > 0) {
     executionErrorStore.removeMissingNodesByType(replaced)
   }
+  isReplacingAll.value = false
 }

And in the template:

 <Button
   v-else-if="group.type === 'swap_nodes'"
   v-tooltip.top="t('nodeReplacement.replaceAllWarning')"
   variant="secondary"
   size="sm"
   class="shrink-0 mr-2 h-8 rounded-lg text-sm"
+  :disabled="isReplacingAll"
   `@click.stop`="handleReplaceAll()"
 >
+  <DotSpinner v-if="isReplacingAll" duration="1s" :size="12" />
   {{ t('nodeReplacement.replaceAll') }}
 </Button>
🤖 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 78 - 92, Add
a loading/disabled state and move inline i18n strings to the locale file: create
a reactive flag (e.g., isReplacingAll) in the TabErrors component, update the
template Button (v-else-if block) to bind loading/disabled/aria-busy to
isReplacingAll and remove inline fallback strings so the label and tooltip use
t('nodeReplacement.replaceAll') and t('nodeReplacement.replaceAllWarning') keys;
add those keys to src/locales/en/main.json; update the handleReplaceAll method
to set isReplacingAll = true before calling replaceNodesInPlace(...) and set it
back to false in finally (and handle errors) to prevent double clicks while the
operation is in progress.
🤖 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/TabErrors.vue`:
- Around line 78-92: Add a loading/disabled state and move inline i18n strings
to the locale file: create a reactive flag (e.g., isReplacingAll) in the
TabErrors component, update the template Button (v-else-if block) to bind
loading/disabled/aria-busy to isReplacingAll and remove inline fallback strings
so the label and tooltip use t('nodeReplacement.replaceAll') and
t('nodeReplacement.replaceAllWarning') keys; add those keys to
src/locales/en/main.json; update the handleReplaceAll method to set
isReplacingAll = true before calling replaceNodesInPlace(...) and set it back to
false in finally (and handle errors) to prevent double clicks while the
operation is in progress.

ℹ️ 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 1be6d27 and b585dfa.

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

@jaeone94 jaeone94 marked this pull request as draft February 26, 2026 15:00
Await nodeReplacementStore.load() before collectMissingNodesAndModels
to prevent race condition where replacement mappings are not yet
available when determining isReplaceable flag.
@jaeone94 jaeone94 marked this pull request as ready for review February 26, 2026 15:38
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.

Previous feedback addressed. The partial-replace bug is fixed, parameter shadowing removed, and the new await nodeReplacementStore.load() ensures replacement data is available before workflow processing. DDD reorganization tracked in #9254 and #9255. LGTM.

import type { MissingNodeType } from '@/types/comfy'
import type { SwapNodeGroup } from './useErrorGroups'
import { useNodeReplacement } from '@/platform/nodeReplacement/useNodeReplacement'
import { useExecutionErrorStore } from '@/stores/executionErrorStore'
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion (non-blocking): this component directly imports and calls useNodeReplacement() and useExecutionErrorStore() to perform replacements. The sibling MissingPackGroupRow.vue follows the emit pattern instead - it emits locateNode/openManagerInfo and lets the parent handle store interactions. Consider emitting a 'replace' event and handling the replace+remove logic in TabErrors.vue (which already has handleReplaceAll with the same pattern). This keeps replacement logic centralized and consistent with the component guideline to prefer emit for state changes.

@coderabbitai Make an Issue to do this in a followup PR on a future date

Copy link
Contributor

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash

# Create GitHub issue for refactoring SwapNodeGroupRow to use emit pattern

gh issue create \
  --title "Refactor SwapNodeGroupRow to emit 'replace' event instead of calling stores directly" \
  --body "## Context

In PR `#9253`, a new component \`SwapNodeGroupRow.vue\` was introduced that directly imports and calls \`useNodeReplacement()\` and \`useExecutionErrorStore()\` to perform node replacements.

## Current Implementation

\`SwapNodeGroupRow.vue\` currently:
- Directly imports \`useNodeReplacement()\` and \`useExecutionErrorStore()\`
- Handles replacement logic internally within the component
- Calls stores directly to perform state changes

## Proposed Refactoring

The sibling component \`MissingPackGroupRow.vue\` follows the emit pattern, emitting \`locateNode\` and \`openManagerInfo\` events and letting the parent (\`TabErrors.vue\`) handle store interactions.

**Suggested approach:**
1. Emit a \`'replace'\` event from \`SwapNodeGroupRow.vue\` instead of calling stores directly
2. Move the replacement and cleanup logic to \`TabErrors.vue\`, which already has \`handleReplaceAll\` following this pattern
3. Keep the component focused on presentation and user interaction

## Benefits

- **Centralized logic**: All replacement operations handled in one place (\`TabErrors.vue\`)
- **Consistent pattern**: Aligns with existing \`MissingPackGroupRow.vue\` implementation
- **Component guidelines**: Follows the codebase preference for \`emit/event-name\` for state changes (see learning aa08db24)
- **Better separation of concerns**: Component focuses on UI, parent handles business logic

## References

- Original PR: `#9253`
- Discussion: https://github.com/Comfy-Org/ComfyUI_frontend/pull/9253#discussion_r2862082614
- Suggested by: `@christian-byrne`
- Related file: \`src/components/rightSidePanel/errors/SwapNodeGroupRow.vue\`

## File Location

\`src/components/rightSidePanel/errors/SwapNodeGroupRow.vue\`" \
  --assignee christian-byrne \
  --label enhancement

Length of output: 57


@christian-byrne Issue created! 🎯

I've opened #9267 to track the refactoring of SwapNodeGroupRow.vue to use the emit pattern instead of direct store calls. The issue documents the current implementation, proposed changes, benefits, and includes references to this PR and discussion thread.

The issue has been assigned to you and labeled as an enhancement.


🧠 Learnings used
Learnt from: LittleSound
Repo: Comfy-Org/ComfyUI_frontend PR: 7812
File: src/components/rightSidePanel/RightSidePanel.vue:100-132
Timestamp: 2026-01-09T07:29:32.501Z
Learning: The `findParentGroupInGraph` function in `src/components/rightSidePanel/RightSidePanel.vue` is a temporary workaround for a bug where group sub-items were not updating correctly after a page refresh. It can be removed once that underlying bug is fixed.

Learnt from: jaeone94
Repo: Comfy-Org/ComfyUI_frontend PR: 8875
File: src/components/rightSidePanel/errors/TabErrors.vue:244-280
Timestamp: 2026-02-15T11:04:33.183Z
Learning: In `src/components/rightSidePanel/errors/TabErrors.vue`, the `regroupByErrorMessage` function hardcodes `priority: 1` for all message groups because it operates only in single-node mode where: (1) `processPromptError` is excluded by the `!selectedGraphNodeIds.value` guard, so prompt errors never appear, and (2) sorting uses `title.localeCompare()` instead of priority. The field exists solely to satisfy the `ErrorGroup` interface.

Learnt from: Yourz
Repo: Comfy-Org/ComfyUI_frontend PR: 8548
File: src/components/common/TreeExplorerV2.vue:2-89
Timestamp: 2026-02-07T14:46:21.179Z
Learning: In Vue components (e.g., TreeExplorerV2.vue and similar Recka UI components) avoid wrapping ContextMenuRoot with a dynamic <component :is="..."> and rendering alternative branches. Use v-if / v-else to render separate ContextMenuRoot and non-ContextMenuRoot branches to ensure correct behavior with Reka UI, even if it duplicates some tree structure. This guideline applies to Vue components under src/components and can be checked by ensuring there is a dedicated render path for the ContextMenuRoot without dynamic component switching.

Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/components/AGENTS.md:0-0
Timestamp: 2026-01-16T21:33:50.043Z
Learning: Applies to src/components/**/*.vue : Prefer `emit/event-name` for state changes in Vue components

Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T19:47:02.860Z
Learning: Applies to src/**/*.{vue,ts} : Implement proper error handling

Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/AGENTS.md:0-0
Timestamp: 2026-01-27T20:51:47.936Z
Learning: Applies to src/**/*.{js,ts,vue} : Implement proper error propagation

Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/AGENTS.md:0-0
Timestamp: 2026-01-27T20:51:47.936Z
Learning: Applies to src/**/*.{ts,tsx,vue} : Avoid `ts-expect-error` - fix the underlying type issue instead

Learnt from: Yourz
Repo: Comfy-Org/ComfyUI_frontend PR: 8548
File: src/components/common/TreeExplorerV2.vue:3-5
Timestamp: 2026-02-08T16:04:58.463Z
Learning: In TreeExplorerV2.vue and similar Re- ka UI Tree components, avoid defensive copies in bindings like :expanded="[...expandedKeys]". Use v-model:expanded="expandedKeys" instead. TreeRoot follows Vue's controlled component pattern and emits update:expanded with the new array; it does not mutate the passed array. Defensive copies break reactivity and can desynchronize state. Ensure components rely on update:expanded and do not mutate input arrays.

Learnt from: Yourz
Repo: Comfy-Org/ComfyUI_frontend PR: 8548
File: src/components/common/TreeExplorerV2Node.vue:9-47
Timestamp: 2026-02-07T14:47:06.751Z
Learning: In all Vue files that render Reka UI context menu components (ContextMenuRoot, ContextMenuTrigger, etc.), avoid dynamic component wrapping with <component :is="...">. Use conditional rendering with v-if / v-else to render separate branches, even if it results in some template duplication. This improves readability and maintainability across files that use these components.

Learnt from: benceruleanlu
Repo: Comfy-Org/ComfyUI_frontend PR: 7297
File: src/components/actionbar/ComfyActionbar.vue:33-43
Timestamp: 2025-12-09T21:40:12.361Z
Learning: In Vue single-file components, allow inline Tailwind CSS class strings for static classes and avoid extracting them into computed properties solely for readability. Prefer keeping static class names inline for simplicity and performance. For dynamic or conditional classes, use Vue bindings (e.g., :class) to compose classes.

Applies to all Vue files in the repository (e.g., src/**/*.vue) where Tailwind utilities are used for static styling.

Learnt from: christian-byrne
Repo: Comfy-Org/ComfyUI_frontend PR: 7358
File: src/components/dialog/content/signin/SignUpForm.vue:45-54
Timestamp: 2025-12-11T12:25:15.470Z
Learning: This repository uses CI automation to format code (pnpm format). Do not include manual formatting suggestions in code reviews for Comfy-Org/ComfyUI_frontend. If formatting issues are detected, rely on the CI formatter or re-run pnpm format. Focus reviews on correctness, readability, performance, accessibility, and maintainability rather than style formatting.

Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7537
File: src/components/ui/button/Button.vue:17-17
Timestamp: 2025-12-16T22:26:49.463Z
Learning: In Vue 3.5+ with <script setup>, when using defineProps<Props>() with partial destructuring (e.g., const { as = 'button', class: customClass = '' } = defineProps<Props>() ), props that are not destructured (e.g., variant, size) stay accessible by name in the template scope. This pattern is valid: you can destructure only a subset of props for convenience while referencing the remaining props directly in template expressions. Apply this guideline to Vue components across the codebase (all .vue files).

Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7598
File: src/components/sidebar/tabs/AssetsSidebarTab.vue:131-131
Timestamp: 2025-12-18T02:07:38.870Z
Learning: Tailwind CSS v4 safe utilities (e.g., items-center-safe, justify-*-safe, place-*-safe) are allowed in Vue components under src/ and in story files. Do not flag these specific safe variants as invalid when reviewing code in src/**/*.vue or related stories.

Learnt from: henrikvilhelmberglund
Repo: Comfy-Org/ComfyUI_frontend PR: 7617
File: src/components/actionbar/ComfyActionbar.vue:301-308
Timestamp: 2025-12-18T16:03:02.066Z
Learning: In the ComfyUI frontend queue system, useQueuePendingTaskCountStore().count indicates the number of tasks in the queue, where count = 1 means a single active/running task and count > 1 means there are pending tasks in addition to the active task. Therefore, in src/components/actionbar/ComfyActionbar.vue, enable the 'Clear Pending Tasks' button only when count > 1 to avoid clearing the currently running task. The active task should be canceled using the 'Cancel current run' button instead. This rule should be enforced via a conditional check on the queue count, with appropriate disabled/aria-disabled states for accessibility, and tests should verify behavior for count = 1 and count > 1.

Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7603
File: src/components/queue/QueueOverlayHeader.vue:49-59
Timestamp: 2025-12-18T21:15:46.862Z
Learning: In the ComfyUI_frontend repository, for Vue components, do not add aria-label to buttons that have visible text content (e.g., buttons containing <span> text). The visible text provides the accessible name. Use aria-label only for elements without visible labels (e.g., icon-only buttons). If a button has no visible label, provide a clear aria-label or associate with an aria-labelledby describing its action.

Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7649
File: src/components/graph/selectionToolbox/ColorPickerButton.vue:15-18
Timestamp: 2025-12-21T01:06:02.786Z
Learning: In Comfy-Org/ComfyUI_frontend, in Vue component files, when a filled icon is required (e.g., 'pi pi-circle-fill'), you may mix PrimeIcons with Lucide icons since Lucide lacks filled variants. This mixed usage is acceptable when one icon library does not provide an equivalent filled icon. Apply consistently across Vue components in the src directory where icons are used, and document the rationale when a mixed approach is chosen.

Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7649
File: src/platform/cloud/subscription/components/PricingTable.vue:185-201
Timestamp: 2025-12-22T21:36:08.369Z
Learning: In Vue components, avoid creating single-use variants for common UI components (e.g., Button and other shared components). Aim for reusable variants that cover multiple use cases. It’s acceptable to temporarily mix variant props with inline Tailwind classes when a styling need is unique to one place, but plan and consolidate into shared, reusable variants as patterns emerge across the codebase.

Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7893
File: src/components/button/IconGroup.vue:5-6
Timestamp: 2026-01-08T02:26:18.357Z
Learning: In components that use the cn utility from '@/utils/tailwindUtil' with tailwind-merge, rely on the behavior that conflicting Tailwind classes are resolved by keeping the last one. For example, cn('base-classes bg-default', propClass) will have any conflicting background class from propClass override bg-default. This additive pattern is intentional and aligns with the shadcn-ui convention; ensure you document or review expectations accordingly in Vue components.

Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7906
File: src/components/sidebar/tabs/AssetsSidebarTab.vue:545-552
Timestamp: 2026-01-12T17:39:27.738Z
Learning: In Vue/TypeScript files (src/**/*.{ts,tsx,vue}), prefer if/else statements over ternary operators when performing side effects or actions (e.g., mutating state, calling methods with side effects). Ternaries should be reserved for computing and returning values.

Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 8195
File: src/platform/assets/components/MediaAssetFilterBar.vue:16-16
Timestamp: 2026-01-21T01:28:27.626Z
Learning: In Vue templates (Vue 3.4+ with the build step), when binding to data or props that are camelCase (e.g., mediaTypeFilters), you can use kebab-case in the template bindings (e.g., :media-type-filters). This is acceptable and will resolve to the corresponding camelCase variable. Do not require CamelCase in template bindings.

Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 8090
File: src/platform/assets/components/modelInfo/ModelInfoField.vue:8-11
Timestamp: 2026-01-22T02:28:58.105Z
Learning: In Vue 3 script setup, props defined with defineProps are automatically available by name in the template without destructuring. Destructuring the result of defineProps inside script can break reactivity; prefer accessing props by name in the template. If you need to use props in the script, reference them via the defined props object rather than destructuring, or use toRefs when you intend to destructure while preserving reactivity.

Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 8497
File: src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue:223-236
Timestamp: 2026-02-01T21:10:29.567Z
Learning: In Vue single-file components, do not review or require edits to comments. Favor self-documenting code through clear naming, strong types, and explicit APIs. If a comment is misleading or outdated, consider removing it, but avoid suggesting adding or fixing comments. This guideline aligns with preferring code clarity over comment maintenance across all .vue files.

Learnt from: christian-byrne
Repo: Comfy-Org/ComfyUI_frontend PR: 8592
File: src/components/topbar/WorkflowExecutionIndicator.vue:28-28
Timestamp: 2026-02-03T21:35:40.889Z
Learning: In Vue single-file components where the i18n t function is only used within the template, prefer using the built-in $t in the template instead of importing useI18n and destructuring t in the script. This avoids unnecessary imports when t is not used in the script. If you need i18n in the script (Composition API), only then use useI18n and access t from its returned object. Ensure this pattern applies to all Vue components with template-only i18n usage.

Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 8753
File: src/renderer/extensions/vueNodes/widgets/components/WidgetDOM.vue:17-28
Timestamp: 2026-02-09T03:24:47.113Z
Learning: When destructuring reactive properties from Pinia stores in Vue components, use storeToRefs() to preserve reactivity. Example: const store = useCanvasStore(); const { canvas } = storeToRefs(store); access as canvas.value (e.g., canvas.value). Ensure you import storeToRefs from 'pinia' and use it wherever you destructure store properties in the setup function.

Learnt from: Myestery
Repo: Comfy-Org/ComfyUI_frontend PR: 8761
File: src/components/dialog/content/MissingModelsWarning.vue:21-27
Timestamp: 2026-02-10T00:58:07.904Z
Learning: In the ComfyUI_frontend Vue codebase, replace raw <button> HTML elements with the shared Button component located at src/components/ui/button/Button.vue. Import and use it with appropriate variants (e.g., variant="link") to align with the design system. Apply this pattern across Vue components under src/components, ensuring consistent styling and behavior instead of ad-hoc button markup.

Learnt from: pythongosssss
Repo: Comfy-Org/ComfyUI_frontend PR: 8775
File: src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDefault.vue:62-68
Timestamp: 2026-02-10T17:59:25.893Z
Learning: In Vue 3 single-file components, code defined outside the script setup scope cannot access variables or helpers defined inside setup. If a default function (e.g., defineModel) is hoisted outside script setup, it will not be able to call setup helpers or reference setup-scoped variables. Place such logic inside setup or expose necessary values via returns/defineExpose to be accessible.

return !removeSet.has(nodeType)
})
setMissingNodeTypes(remaining)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion (non-blocking): the PR adds several new testable functions (removeMissingNodesByType, scanMissingNodes, the swapNodeGroups computed) without corresponding tests. At minimum, removeMissingNodesByType (set-based filtering) and the swapNodeGroups grouping logic in useErrorGroups.ts would benefit from unit tests to catch regressions if the filtering/grouping criteria change.

@coderabbitai Make an Issue to do this in a followup PR on a future date

Copy link
Contributor

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Create a GitHub issue for adding unit tests to new functions from PR `#9253`

gh issue create \
  --title "Add unit tests for new error handling functions from PR `#9253`" \
  --body "## Description

This issue tracks the addition of unit tests for new testable functions introduced in PR `#9253` (node replacement UI in Errors Tab).

## Functions that need tests

1. **\`removeMissingNodesByType\`** in \`src/stores/executionErrorStore.ts\`
   - Set-based filtering logic for removing specific node types from the missing nodes list
   - Should test edge cases: empty array, non-existent types, string vs object node types

2. **\`scanMissingNodes\`** in \`src/composables/useMissingNodeScan.ts\`
   - Graph scanning logic to detect unregistered node types
   - Should test: registered vs unregistered nodes, CNR ID resolution, replacement detection

3. **\`swapNodeGroups\`** computed in \`src/components/rightSidePanel/errors/useErrorGroups.ts\`
   - Grouping logic for replaceable missing nodes
   - Should test: grouping by type, replacement info collection, filtering criteria

## Context

These tests would help catch regressions if the filtering/grouping criteria change in the future.

## References

- Original PR: `#9253`
- Comment: https://github.com/Comfy-Org/ComfyUI_frontend/pull/9253#discussion_r2862082616
- Requested by: `@christian-byrne`" \
  --assignee christian-byrne \
  --label "enhancement,testing"

Length of output: 75


@christian-byrne, I attempted to create the GitHub issue but the sandbox environment lacks GitHub authentication. Here's the issue content ready for you to create manually:

Title:

Add unit tests for new error handling functions from PR `#9253`

Body:

## Description

This issue tracks the addition of unit tests for new testable functions introduced in PR `#9253` (node replacement UI in Errors Tab).

## Functions that need tests

1. **`removeMissingNodesByType`** in `src/stores/executionErrorStore.ts`
   - Set-based filtering logic for removing specific node types from the missing nodes list
   - Should test edge cases: empty array, non-existent types, string vs object node types

2. **`scanMissingNodes`** in `src/composables/useMissingNodeScan.ts`
   - Graph scanning logic to detect unregistered node types
   - Should test: registered vs unregistered nodes, CNR ID resolution, replacement detection

3. **`swapNodeGroups`** computed in `src/components/rightSidePanel/errors/useErrorGroups.ts`
   - Grouping logic for replaceable missing nodes
   - Should test: grouping by type, replacement info collection, filtering criteria

## Context

These tests would help catch regressions if the filtering/grouping criteria change in the future.

## References

- Original PR: `#9253`
- Comment: https://github.com/Comfy-Org/ComfyUI_frontend/pull/9253#discussion_r2862082616
- Requested by: `@christian-byrne`

Labels: enhancement, testing
Assignee: christian-byrne

You can create this issue directly on GitHub or run the following command locally if you have gh CLI configured:

gh issue create \
  --repo Comfy-Org/ComfyUI_frontend \
  --title "Add unit tests for new error handling functions from PR `#9253`" \
  --body "..." \
  --assignee christian-byrne \
  --label "enhancement,testing"

🧠 Learnings used
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/lib/litegraph/AGENTS.md:0-0
Timestamp: 2026-02-23T00:41:09.436Z
Learning: Applies to src/lib/litegraph/**/*.test.{ts,tsx} : Use test helper functions `createTestSubgraph()` and `createTestSubgraphNode()` from `./__fixtures__/subgraphHelpers` when setting up subgraph tests

Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/AGENTS.md:0-0
Timestamp: 2026-01-27T20:51:47.936Z
Learning: Applies to src/**/*.{ts,tsx,vue} : Avoid `ts-expect-error` - fix the underlying type issue instead

Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 8307
File: src/stores/bootstrapStore.ts:61-78
Timestamp: 2026-01-26T18:39:22.294Z
Learning: Do not expose internal state or error refs from stores unless there is demonstrated active usage elsewhere in the codebase. Keep internal state private by default; only expose what is actively consumed. Applies to all TypeScript files under src/stores (e.g., bootstrapStore.ts and siblings). Ensure exported API only includes properties/methods that are actually used by consumers; avoid leaking implementation details.

Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7169
File: src/platform/remote/comfyui/jobs/jobTypes.ts:1-107
Timestamp: 2025-12-09T03:39:54.501Z
Learning: In the ComfyUI_frontend project, Zod is on v3.x. Do not suggest Zod v4 standalone validators (z.uuid, z.ulid, z.cuid2, z.nanoid) until an upgrade to Zod 4 is performed. When reviewing TypeScript files (e.g., src/platform/remote/comfyui/jobs/jobTypes.ts) validate against Zod 3 capabilities and avoid introducing v4-specific features; flag any proposal to upgrade or incorporate v4-only validators and propose staying with compatible 3.x patterns.

Learnt from: christian-byrne
Repo: Comfy-Org/ComfyUI_frontend PR: 7358
File: src/components/dialog/content/signin/SignUpForm.vue:45-54
Timestamp: 2025-12-11T12:25:15.470Z
Learning: This repository uses CI automation to format code (pnpm format). Do not include manual formatting suggestions in code reviews for Comfy-Org/ComfyUI_frontend. If formatting issues are detected, rely on the CI formatter or re-run pnpm format. Focus reviews on correctness, readability, performance, accessibility, and maintainability rather than style formatting.

Learnt from: christian-byrne
Repo: Comfy-Org/ComfyUI_frontend PR: 7416
File: src/stores/imagePreviewStore.ts:5-7
Timestamp: 2025-12-13T11:03:11.264Z
Learning: In the ComfyUI_frontend repository, lint rules require keeping 'import type' statements separate from non-type imports, even if importing from the same module. Do not suggest consolidating them into a single import statement. Ensure type imports remain on their own line (import type { ... } from 'module') and regular imports stay on separate lines.

Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7537
File: src/components/ui/button/Button.stories.ts:45-55
Timestamp: 2025-12-17T00:40:09.635Z
Learning: Prefer pure function declarations over function expressions (e.g., use function foo() { ... } instead of const foo = () => { ... }) for pure functions in the repository. Function declarations are more functional-leaning, offer better hoisting clarity, and can improve readability and tooling consistency. Apply this guideline across TypeScript files in Comfy-Org/ComfyUI_frontend, including story and UI component code, except where a function expression is semantically required (e.g., callbacks, higher-order functions with closures).

Learnt from: kaili-yang
Repo: Comfy-Org/ComfyUI_frontend PR: 7805
File: src/composables/useCoreCommands.ts:439-439
Timestamp: 2025-12-30T22:22:33.836Z
Learning: When accessing reactive properties from Pinia stores in TypeScript files, avoid using .value on direct property access (e.g., useStore().isOverlayExpanded). Pinia auto-wraps refs when accessed directly, returning the primitive value. The .value accessor is only needed when destructuring store properties or when using storeToRefs().

Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7906
File: src/components/sidebar/tabs/AssetsSidebarTab.vue:545-552
Timestamp: 2026-01-12T17:39:27.738Z
Learning: In Vue/TypeScript files (src/**/*.{ts,tsx,vue}), prefer if/else statements over ternary operators when performing side effects or actions (e.g., mutating state, calling methods with side effects). Ternaries should be reserved for computing and returning values.

Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 8248
File: src/platform/assets/schemas/assetSchema.ts:109-118
Timestamp: 2026-01-22T23:44:59.888Z
Learning: Do not export declarations (e.g., schemas, constants, types) unless they are actually used elsewhere in the codebase. Keep the public API surface minimal by exporting only items with concrete usage. This reduces coupling and keeps imports explicit. In src/platform/assets/schemas/assetSchema.ts, ensure only necessary exports are exposed; remove unused exports or switch to non-exported/internal where appropriate.

Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 8722
File: src/composables/maskeditor/useBrushDrawing.ts:5-5
Timestamp: 2026-02-08T03:19:12.009Z
Learning: Treat unresolved import warnings caused by pnpm workspace catalog mappings (pnpm-workspace.yaml) and unplugin-typegpu in vite.config.mts as false positives. Do not flag these in PR reviews; they are expected in sandboxes without node_modules and are not indicative of actual missing dependencies in the TS codebase.

Learnt from: christian-byrne
Repo: Comfy-Org/ComfyUI_frontend PR: 8966
File: src/extensions/core/load3d.ts:427-430
Timestamp: 2026-02-19T02:06:23.468Z
Learning: In TypeScript, you can use typeof SomeClass in type annotation positions (e.g., param: typeof LGraphNode) even when SomeClass is imported with import type. This is a type query that only exists at compile time and is erased in runtime, so it is safe to combine with type-only imports. Apply this pattern to TS files broadly when you need a type that references the shape of a class or constructor function without importing the value at runtime.

Learnt from: christian-byrne
Repo: Comfy-Org/ComfyUI_frontend PR: 8966
File: src/extensions/core/uploadAudio.ts:91-94
Timestamp: 2026-02-19T02:06:38.395Z
Learning: In TypeScript files, you can use a type annotation like 'nodeType: typeof MyClass' even if MyClass is imported via 'import type'. Both the type-only import and 'typeof' operate at the type level and are erased at compile time. This pattern is commonly used for constructor types (e.g., 'nodeType: typeof LGraphNode'). Apply this pattern across TypeScript files in the repository (src/**/*.ts) as appropriate, ensuring the imported symbol is a type-only import when possible for clarity and to avoid runtime imports.

Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 8992
File: src/lib/litegraph/src/widgets/GradientSliderWidget.ts:18-18
Timestamp: 2026-02-20T21:08:19.814Z
Learning: When drawing with CanvasRenderingContext2D in TypeScript/JavaScript, wrap the drawing logic with ctx.save() at the start and ctx.restore() at the end to preserve and restore the canvas state. Do not manually destructure and restore individual properties (e.g., fillStyle, strokeStyle); rely on save/restore to manage state changes in a scoped manner. This should be applied to all TS files that perform canvas drawing.

Learnt from: dante01yoon
Repo: Comfy-Org/ComfyUI_frontend PR: 9075
File: src/scripts/api.featureFlags.test.ts:237-268
Timestamp: 2026-02-22T04:27:33.379Z
Learning: In Vite/Vitest, import.meta.env.DEV is true for any mode that is not 'production' (i.e., DEV is the opposite of PROD, and can be true in 'test', 'development', etc.). Do not assume DEV implies only 'development' mode. When reviewing code and tests, treat DEV as a non-production flag and verify environment-specific logic accordingly. Reference: https://vite.dev/guide/env-and-mode#modes

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

🧩 Analysis chain

@jaeone94, I'll retry creating the GitHub issue for the unit test follow-up.


🏁 Script executed:

#!/bin/bash
# Retry creating GitHub issue for adding unit tests to new functions from PR `#9253`

gh issue create \
  --repo Comfy-Org/ComfyUI_frontend \
  --title "Add unit tests for new error handling functions from PR `#9253`" \
  --body "## Description

This issue tracks the addition of unit tests for new testable functions introduced in PR `#9253` (node replacement UI in Errors Tab).

## Functions that need tests

1. **\`removeMissingNodesByType\`** in \`src/stores/executionErrorStore.ts\`
   - Set-based filtering logic for removing specific node types from the missing nodes list
   - Should test edge cases: empty array, non-existent types, string vs object node types

2. **\`scanMissingNodes\`** in \`src/composables/useMissingNodeScan.ts\`
   - Graph scanning logic to detect unregistered node types
   - Should test: registered vs unregistered nodes, CNR ID resolution, replacement detection

3. **\`swapNodeGroups\`** computed in \`src/components/rightSidePanel/errors/useErrorGroups.ts\`
   - Grouping logic for replaceable missing nodes
   - Should test: grouping by type, replacement info collection, filtering criteria

## Context

These tests would help catch regressions if the filtering/grouping criteria change in the future.

## References

- Original PR: `#9253`
- Comment: https://github.com/Comfy-Org/ComfyUI_frontend/pull/9253#discussion_r2862082616
- Requested by: `@christian-byrne`" \
  --assignee christian-byrne \
  --label "enhancement,testing"

Length of output: 75


@jaeone94, unfortunately the sandbox environment doesn't have GitHub authentication configured, so I can't create the issue directly. However, here are two ways you can create it:

Option 1: Create manually on GitHub

Navigate to https://github.com/Comfy-Org/ComfyUI_frontend/issues/new and use this content:

Title:

Add unit tests for new error handling functions from PR `#9253`

Body:

## Description

This issue tracks the addition of unit tests for new testable functions introduced in PR `#9253` (node replacement UI in Errors Tab).

## Functions that need tests

1. **`removeMissingNodesByType`** in `src/stores/executionErrorStore.ts`
   - Set-based filtering logic for removing specific node types from the missing nodes list
   - Should test edge cases: empty array, non-existent types, string vs object node types

2. **`scanMissingNodes`** in `src/composables/useMissingNodeScan.ts`
   - Graph scanning logic to detect unregistered node types
   - Should test: registered vs unregistered nodes, CNR ID resolution, replacement detection

3. **`swapNodeGroups`** computed in `src/components/rightSidePanel/errors/useErrorGroups.ts`
   - Grouping logic for replaceable missing nodes
   - Should test: grouping by type, replacement info collection, filtering criteria

## Context

These tests would help catch regressions if the filtering/grouping criteria change in the future.

## References

- Original PR: `#9253`
- Comment: https://github.com/Comfy-Org/ComfyUI_frontend/pull/9253#discussion_r2862082616
- Requested by: `@christian-byrne`

Labels: enhancement, testing
Assignee: christian-byrne

Option 2: Run locally with gh CLI

If you have gh CLI configured on your machine:

gh issue create \
  --repo Comfy-Org/ComfyUI_frontend \
  --title "Add unit tests for new error handling functions from PR `#9253`" \
  --body "## Description

This issue tracks the addition of unit tests for new testable functions introduced in PR `#9253` (node replacement UI in Errors Tab).

## Functions that need tests

1. **\`removeMissingNodesByType\`** in \`src/stores/executionErrorStore.ts\`
   - Set-based filtering logic for removing specific node types from the missing nodes list
   - Should test edge cases: empty array, non-existent types, string vs object node types

2. **\`scanMissingNodes\`** in \`src/composables/useMissingNodeScan.ts\`
   - Graph scanning logic to detect unregistered node types
   - Should test: registered vs unregistered nodes, CNR ID resolution, replacement detection

3. **\`swapNodeGroups\`** computed in \`src/components/rightSidePanel/errors/useErrorGroups.ts\`
   - Grouping logic for replaceable missing nodes
   - Should test: grouping by type, replacement info collection, filtering criteria

## Context

These tests would help catch regressions if the filtering/grouping criteria change in the future.

## References

- Original PR: `#9253`
- Comment: https://github.com/Comfy-Org/ComfyUI_frontend/pull/9253#discussion_r2862082616
- Requested by: `@christian-byrne`" \
  --assignee christian-byrne \
  --label "enhancement,testing"

🧠 Learnings used
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/AGENTS.md:0-0
Timestamp: 2026-01-27T20:51:47.936Z
Learning: Applies to src/**/*.{ts,tsx,vue} : Avoid `ts-expect-error` - fix the underlying type issue instead

Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-23T21:34:45.361Z
Learning: Applies to src/**/*.test.ts : For Vue component testing, use Vue Test Utils and follow advice about making components easy to test; aim for behavioral coverage of critical and new features

Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7459
File: src/lib/litegraph/src/canvas/LinkConnector.ts:875-881
Timestamp: 2026-02-23T19:49:55.224Z
Learning: ComfyUI_frontend TypeScript review preference: Prefer user-defined type guards (e.g., function isMovingInputLink(x): x is MovingInputLink) over inline type assertions when narrowing union types like RenderLinkUnion in src/lib/litegraph/**/*.ts. Apply especially in LinkConnector and related canvas classes to avoid casts and improve reuse.

Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/AGENTS.md:0-0
Timestamp: 2026-01-27T20:51:47.936Z
Learning: Applies to src/**/*.{js,ts,vue} : Implement proper error propagation

Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/lib/litegraph/AGENTS.md:0-0
Timestamp: 2026-02-23T00:41:09.436Z
Learning: Applies to src/lib/litegraph/**/*.test.{ts,tsx} : Use test helper functions `createTestSubgraph()` and `createTestSubgraphNode()` from `./__fixtures__/subgraphHelpers` when setting up subgraph tests

Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7415
File: browser_tests/tests/mobileBaseline.spec.ts:7-15
Timestamp: 2025-12-13T05:34:22.209Z
Learning: In Playwright tests in the Comfy-Org/ComfyUI_frontend repository, the `toPass()` assertion method implements incremental backoff during retries, so increasing the timeout value (e.g., to 5000ms) is sufficient for handling async operations without being overly aggressive.

Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 8722
File: src/composables/maskeditor/useBrushDrawing.ts:5-5
Timestamp: 2026-02-08T03:19:12.009Z
Learning: Treat unresolved import warnings caused by pnpm workspace catalog mappings (pnpm-workspace.yaml) and unplugin-typegpu in vite.config.mts as false positives. Do not flag these in PR reviews; they are expected in sandboxes without node_modules and are not indicative of actual missing dependencies in the TS codebase.

Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 8307
File: src/stores/bootstrapStore.ts:61-78
Timestamp: 2026-01-26T18:39:22.294Z
Learning: Do not expose internal state or error refs from stores unless there is demonstrated active usage elsewhere in the codebase. Keep internal state private by default; only expose what is actively consumed. Applies to all TypeScript files under src/stores (e.g., bootstrapStore.ts and siblings). Ensure exported API only includes properties/methods that are actually used by consumers; avoid leaking implementation details.

Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7169
File: src/platform/remote/comfyui/jobs/jobTypes.ts:1-107
Timestamp: 2025-12-09T03:39:54.501Z
Learning: In the ComfyUI_frontend project, Zod is on v3.x. Do not suggest Zod v4 standalone validators (z.uuid, z.ulid, z.cuid2, z.nanoid) until an upgrade to Zod 4 is performed. When reviewing TypeScript files (e.g., src/platform/remote/comfyui/jobs/jobTypes.ts) validate against Zod 3 capabilities and avoid introducing v4-specific features; flag any proposal to upgrade or incorporate v4-only validators and propose staying with compatible 3.x patterns.

Learnt from: christian-byrne
Repo: Comfy-Org/ComfyUI_frontend PR: 7358
File: src/components/dialog/content/signin/SignUpForm.vue:45-54
Timestamp: 2025-12-11T12:25:15.470Z
Learning: This repository uses CI automation to format code (pnpm format). Do not include manual formatting suggestions in code reviews for Comfy-Org/ComfyUI_frontend. If formatting issues are detected, rely on the CI formatter or re-run pnpm format. Focus reviews on correctness, readability, performance, accessibility, and maintainability rather than style formatting.

Learnt from: christian-byrne
Repo: Comfy-Org/ComfyUI_frontend PR: 7416
File: src/stores/imagePreviewStore.ts:5-7
Timestamp: 2025-12-13T11:03:11.264Z
Learning: In the ComfyUI_frontend repository, lint rules require keeping 'import type' statements separate from non-type imports, even if importing from the same module. Do not suggest consolidating them into a single import statement. Ensure type imports remain on their own line (import type { ... } from 'module') and regular imports stay on separate lines.

Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7537
File: src/components/ui/button/Button.stories.ts:45-55
Timestamp: 2025-12-17T00:40:09.635Z
Learning: Prefer pure function declarations over function expressions (e.g., use function foo() { ... } instead of const foo = () => { ... }) for pure functions in the repository. Function declarations are more functional-leaning, offer better hoisting clarity, and can improve readability and tooling consistency. Apply this guideline across TypeScript files in Comfy-Org/ComfyUI_frontend, including story and UI component code, except where a function expression is semantically required (e.g., callbacks, higher-order functions with closures).

Learnt from: kaili-yang
Repo: Comfy-Org/ComfyUI_frontend PR: 7805
File: src/composables/useCoreCommands.ts:439-439
Timestamp: 2025-12-30T22:22:33.836Z
Learning: When accessing reactive properties from Pinia stores in TypeScript files, avoid using .value on direct property access (e.g., useStore().isOverlayExpanded). Pinia auto-wraps refs when accessed directly, returning the primitive value. The .value accessor is only needed when destructuring store properties or when using storeToRefs().

Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7906
File: src/components/sidebar/tabs/AssetsSidebarTab.vue:545-552
Timestamp: 2026-01-12T17:39:27.738Z
Learning: In Vue/TypeScript files (src/**/*.{ts,tsx,vue}), prefer if/else statements over ternary operators when performing side effects or actions (e.g., mutating state, calling methods with side effects). Ternaries should be reserved for computing and returning values.

Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 8248
File: src/platform/assets/schemas/assetSchema.ts:109-118
Timestamp: 2026-01-22T23:44:59.888Z
Learning: Do not export declarations (e.g., schemas, constants, types) unless they are actually used elsewhere in the codebase. Keep the public API surface minimal by exporting only items with concrete usage. This reduces coupling and keeps imports explicit. In src/platform/assets/schemas/assetSchema.ts, ensure only necessary exports are exposed; remove unused exports or switch to non-exported/internal where appropriate.

Learnt from: christian-byrne
Repo: Comfy-Org/ComfyUI_frontend PR: 8966
File: src/extensions/core/load3d.ts:427-430
Timestamp: 2026-02-19T02:06:23.468Z
Learning: In TypeScript, you can use typeof SomeClass in type annotation positions (e.g., param: typeof LGraphNode) even when SomeClass is imported with import type. This is a type query that only exists at compile time and is erased in runtime, so it is safe to combine with type-only imports. Apply this pattern to TS files broadly when you need a type that references the shape of a class or constructor function without importing the value at runtime.

Learnt from: christian-byrne
Repo: Comfy-Org/ComfyUI_frontend PR: 8966
File: src/extensions/core/uploadAudio.ts:91-94
Timestamp: 2026-02-19T02:06:38.395Z
Learning: In TypeScript files, you can use a type annotation like 'nodeType: typeof MyClass' even if MyClass is imported via 'import type'. Both the type-only import and 'typeof' operate at the type level and are erased at compile time. This pattern is commonly used for constructor types (e.g., 'nodeType: typeof LGraphNode'). Apply this pattern across TypeScript files in the repository (src/**/*.ts) as appropriate, ensuring the imported symbol is a type-only import when possible for clarity and to avoid runtime imports.

Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 8992
File: src/lib/litegraph/src/widgets/GradientSliderWidget.ts:18-18
Timestamp: 2026-02-20T21:08:19.814Z
Learning: When drawing with CanvasRenderingContext2D in TypeScript/JavaScript, wrap the drawing logic with ctx.save() at the start and ctx.restore() at the end to preserve and restore the canvas state. Do not manually destructure and restore individual properties (e.g., fillStyle, strokeStyle); rely on save/restore to manage state changes in a scoped manner. This should be applied to all TS files that perform canvas drawing.

Learnt from: dante01yoon
Repo: Comfy-Org/ComfyUI_frontend PR: 9075
File: src/scripts/api.featureFlags.test.ts:237-268
Timestamp: 2026-02-22T04:27:33.379Z
Learning: In Vite/Vitest, import.meta.env.DEV is true for any mode that is not 'production' (i.e., DEV is the opposite of PROD, and can be true in 'test', 'development', etc.). Do not assume DEV implies only 'development' mode. When reviewing code and tests, treat DEV as a non-production flag and verify environment-specific logic accordingly. Reference: https://vite.dev/guide/env-and-mode#modes

@christian-byrne christian-byrne merged commit 1c3984a into main Feb 27, 2026
33 checks passed
@christian-byrne christian-byrne deleted the feat/replace-missing-node-from-error-tab branch February 27, 2026 01:37
christian-byrne pushed a commit that referenced this pull request Feb 27, 2026
## Summary

Adds a node replacement UI to the Errors Tab so users can swap missing
nodes with compatible alternatives directly from the error panel,
without opening a separate dialog.

## Changes

- **What**: New `SwapNodesCard` and `SwapNodeGroupRow` components render
swap groups in the Errors Tab; each group shows the missing node type,
its instances (with locate buttons), and a Replace button. Added
`useMissingNodeScan` composable to scan the graph for missing nodes and
populate `executionErrorStore`. Added `removeMissingNodesByType()` to
`executionErrorStore` so replaced nodes are pruned from the error list
reactively.

## Bug Fixes Found During Implementation

### Bug 1: Replaced nodes render as empty shells until page refresh

`replaceWithMapping()` directly mutates `_nodes[idx]`, bypassing the Vue
rendering pipeline entirely. Because the replacement node reuses the
same ID, `vueNodeData` retains the stale entry from the old placeholder
(`hasErrors: true`, empty widgets/inputs). `graph.setDirtyCanvas()` only
repaints the LiteGraph canvas and has no effect on Vue.

**Fix**: After `replaceWithMapping()`, manually call
`nodeGraph.onNodeAdded?.(newNode)` to trigger `handleNodeAdded` in
`useGraphNodeManager`, which runs `extractVueNodeData(newNode)` and
updates `vueNodeData` correctly. Also added a guard in `handleNodeAdded`
to skip `layoutStore.createNode()` when a layout for the same ID already
exists, preventing a duplicate `spatialIndex.insert()`.

### Bug 2: Missing node error list overwritten by incomplete server
response

Two compounding issues: (A) the server's `missing_node_type` error only
reports the *first* missing node — the old handler parsed this and
called `surfaceMissingNodes([singleNode])`, overwriting the full list
collected at load time. (B) `queuePrompt()` calls `clearAllErrors()`
before the API request; if the subsequent rescan used the stale
`has_errors` flag and found nothing, the missing nodes were permanently
lost.

**Fix**: Created `useMissingNodeScan.ts` which scans
`LiteGraph.registered_node_types` directly (not `has_errors`). The
`missing_node_type` catch block in `app.ts` now calls
`rescanAndSurfaceMissingNodes(this.rootGraph)` instead of parsing the
server's partial response.

## Review Focus

- `handleReplaceNode` removes the group from the store only when
`replaceNodesInPlace` returns at least one replaced node — should we
always clear, or only on full success?
- `useMissingNodeScan` re-scans on every execution-error change; confirm
no performance concerns for large graphs with many subgraphs.


## Screenshots 


https://github.com/user-attachments/assets/78310fc4-0424-4920-b369-cef60a123d50



https://github.com/user-attachments/assets/3d2fd5e1-5e85-4c20-86aa-8bf920e86987



┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9253-feat-add-node-replacement-UI-to-Errors-Tab-3136d73d365081718d4ddfd628cb4449)
by [Unito](https://www.unito.io)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:nodes enhancement New feature or request size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants